[Cocoa] Web Automation: add SPI to tell whether the automation session is currently...
[WebKit-https.git] / Source / WebKit / UIProcess / Automation / WebAutomationSession.cpp
1 /*
2  * Copyright (C) 2016, 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "WebAutomationSession.h"
28
29 #include "APIArray.h"
30 #include "APIAutomationSessionClient.h"
31 #include "APIOpenPanelParameters.h"
32 #include "AutomationProtocolObjects.h"
33 #include "WebAutomationSessionMacros.h"
34 #include "WebAutomationSessionMessages.h"
35 #include "WebAutomationSessionProxyMessages.h"
36 #include "WebCookieManagerProxy.h"
37 #include "WebInspectorProxy.h"
38 #include "WebOpenPanelResultListenerProxy.h"
39 #include "WebProcessPool.h"
40 #include <JavaScriptCore/InspectorBackendDispatcher.h>
41 #include <JavaScriptCore/InspectorFrontendRouter.h>
42 #include <WebCore/MIMETypeRegistry.h>
43 #include <WebCore/URL.h>
44 #include <algorithm>
45 #include <wtf/HashMap.h>
46 #include <wtf/Optional.h>
47 #include <wtf/UUID.h>
48 #include <wtf/text/StringConcatenate.h>
49
50 using namespace Inspector;
51
52 namespace WebKit {
53
54 // §8. Sessions
55 // https://www.w3.org/TR/webdriver/#dfn-session-page-load-timeout
56 static const Seconds defaultPageLoadTimeout = 300_s;
57 // https://www.w3.org/TR/webdriver/#dfn-page-loading-strategy
58 static const Inspector::Protocol::Automation::PageLoadStrategy defaultPageLoadStrategy = Inspector::Protocol::Automation::PageLoadStrategy::Normal;
59
60 WebAutomationSession::WebAutomationSession()
61     : m_client(std::make_unique<API::AutomationSessionClient>())
62     , m_frontendRouter(FrontendRouter::create())
63     , m_backendDispatcher(BackendDispatcher::create(m_frontendRouter.copyRef()))
64     , m_domainDispatcher(AutomationBackendDispatcher::create(m_backendDispatcher, this))
65     , m_domainNotifier(std::make_unique<AutomationFrontendDispatcher>(m_frontendRouter))
66     , m_loadTimer(RunLoop::main(), this, &WebAutomationSession::loadTimerFired)
67 {
68 }
69
70 WebAutomationSession::~WebAutomationSession()
71 {
72     ASSERT(!m_client);
73
74     if (m_processPool)
75         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
76 }
77
78 void WebAutomationSession::setClient(std::unique_ptr<API::AutomationSessionClient>&& client)
79 {
80     m_client = WTFMove(client);
81 }
82
83 void WebAutomationSession::setProcessPool(WebKit::WebProcessPool* processPool)
84 {
85     if (m_processPool)
86         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
87
88     m_processPool = processPool;
89
90     if (m_processPool)
91         m_processPool->addMessageReceiver(Messages::WebAutomationSession::messageReceiverName(), *this);
92 }
93
94 // NOTE: this class could be split at some point to support local and remote automation sessions.
95 // For now, it only works with a remote automation driver over a RemoteInspector connection.
96
97 #if ENABLE(REMOTE_INSPECTOR)
98
99 // Inspector::RemoteAutomationTarget API
100
101 void WebAutomationSession::dispatchMessageFromRemote(const String& message)
102 {
103     m_backendDispatcher->dispatch(message);
104 }
105
106 void WebAutomationSession::connect(Inspector::FrontendChannel* channel, bool isAutomaticConnection, bool immediatelyPause)
107 {
108     UNUSED_PARAM(isAutomaticConnection);
109     UNUSED_PARAM(immediatelyPause);
110
111     m_remoteChannel = channel;
112     m_frontendRouter->connectFrontend(channel);
113
114     setIsPaired(true);
115 }
116
117 void WebAutomationSession::disconnect(Inspector::FrontendChannel* channel)
118 {
119     ASSERT(channel == m_remoteChannel);
120     terminate();
121 }
122
123 #endif // ENABLE(REMOTE_INSPECTOR)
124
125 void WebAutomationSession::terminate()
126 {
127 #if ENABLE(REMOTE_INSPECTOR)
128     if (Inspector::FrontendChannel* channel = m_remoteChannel) {
129         m_remoteChannel = nullptr;
130         m_frontendRouter->disconnectFrontend(channel);
131     }
132
133     setIsPaired(false);
134 #endif
135
136     if (m_client)
137         m_client->didDisconnectFromRemote(*this);
138 }
139
140 WebPageProxy* WebAutomationSession::webPageProxyForHandle(const String& handle)
141 {
142     auto iter = m_handleWebPageMap.find(handle);
143     if (iter == m_handleWebPageMap.end())
144         return nullptr;
145     return WebProcessProxy::webPage(iter->value);
146 }
147
148 String WebAutomationSession::handleForWebPageProxy(const WebPageProxy& webPageProxy)
149 {
150     auto iter = m_webPageHandleMap.find(webPageProxy.pageID());
151     if (iter != m_webPageHandleMap.end())
152         return iter->value;
153
154     String handle = "page-" + createCanonicalUUIDString().convertToASCIIUppercase();
155
156     auto firstAddResult = m_webPageHandleMap.add(webPageProxy.pageID(), handle);
157     RELEASE_ASSERT(firstAddResult.isNewEntry);
158
159     auto secondAddResult = m_handleWebPageMap.add(handle, webPageProxy.pageID());
160     RELEASE_ASSERT(secondAddResult.isNewEntry);
161
162     return handle;
163 }
164
165 std::optional<uint64_t> WebAutomationSession::webFrameIDForHandle(const String& handle)
166 {
167     if (handle.isEmpty())
168         return 0;
169
170     auto iter = m_handleWebFrameMap.find(handle);
171     if (iter == m_handleWebFrameMap.end())
172         return std::nullopt;
173
174     return iter->value;
175 }
176
177 String WebAutomationSession::handleForWebFrameID(uint64_t frameID)
178 {
179     if (!frameID)
180         return emptyString();
181
182     for (auto& process : m_processPool->processes()) {
183         if (WebFrameProxy* frame = process->webFrame(frameID)) {
184             if (frame->isMainFrame())
185                 return emptyString();
186             break;
187         }
188     }
189
190     auto iter = m_webFrameHandleMap.find(frameID);
191     if (iter != m_webFrameHandleMap.end())
192         return iter->value;
193
194     String handle = "frame-" + createCanonicalUUIDString().convertToASCIIUppercase();
195
196     auto firstAddResult = m_webFrameHandleMap.add(frameID, handle);
197     RELEASE_ASSERT(firstAddResult.isNewEntry);
198
199     auto secondAddResult = m_handleWebFrameMap.add(handle, frameID);
200     RELEASE_ASSERT(secondAddResult.isNewEntry);
201
202     return handle;
203 }
204
205 String WebAutomationSession::handleForWebFrameProxy(const WebFrameProxy& webFrameProxy)
206 {
207     return handleForWebFrameID(webFrameProxy.frameID());
208 }
209
210 Ref<Inspector::Protocol::Automation::BrowsingContext> WebAutomationSession::buildBrowsingContextForPage(WebPageProxy& page, WebCore::FloatRect windowFrame)
211 {
212     auto originObject = Inspector::Protocol::Automation::Point::create()
213         .setX(windowFrame.x())
214         .setY(windowFrame.y())
215         .release();
216
217     auto sizeObject = Inspector::Protocol::Automation::Size::create()
218         .setWidth(windowFrame.width())
219         .setHeight(windowFrame.height())
220         .release();
221
222     String handle = handleForWebPageProxy(page);
223
224     return Inspector::Protocol::Automation::BrowsingContext::create()
225         .setHandle(handle)
226         .setActive(m_activeBrowsingContextHandle == handle)
227         .setUrl(page.pageLoadState().activeURL())
228         .setWindowOrigin(WTFMove(originObject))
229         .setWindowSize(WTFMove(sizeObject))
230         .release();
231 }
232
233 // Platform-independent Commands.
234
235 void WebAutomationSession::getNextContext(Ref<WebAutomationSession>&& protectedThis, Vector<Ref<WebPageProxy>>&& pages, Ref<Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>> contexts, Ref<WebAutomationSession::GetBrowsingContextsCallback>&& callback)
236 {
237     if (pages.isEmpty()) {
238         callback->sendSuccess(WTFMove(contexts));
239         return;
240     }
241     auto page = pages.takeLast();
242     auto& webPageProxy = page.get();
243     webPageProxy.getWindowFrameWithCallback([this, protectedThis = WTFMove(protectedThis), callback = WTFMove(callback), pages = WTFMove(pages), contexts = WTFMove(contexts), page = WTFMove(page)](WebCore::FloatRect windowFrame) mutable {
244         contexts->addItem(protectedThis->buildBrowsingContextForPage(page.get(), windowFrame));
245         getNextContext(WTFMove(protectedThis), WTFMove(pages), WTFMove(contexts), WTFMove(callback));
246     });
247 }
248     
249 void WebAutomationSession::getBrowsingContexts(Inspector::ErrorString& errorString, Ref<GetBrowsingContextsCallback>&& callback)
250 {
251     Vector<Ref<WebPageProxy>> pages;
252     for (auto& process : m_processPool->processes()) {
253         for (auto* page : process->pages()) {
254             ASSERT(page);
255             if (!page->isControlledByAutomation())
256                 continue;
257             pages.append(*page);
258         }
259     }
260     
261     getNextContext(makeRef(*this), WTFMove(pages), Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>::create(), WTFMove(callback));
262 }
263
264 void WebAutomationSession::getBrowsingContext(Inspector::ErrorString& errorString, const String& handle, Ref<GetBrowsingContextCallback>&& callback)
265 {
266     WebPageProxy* page = webPageProxyForHandle(handle);
267     if (!page)
268         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
269
270     page->getWindowFrameWithCallback([protectedThis = makeRef(*this), page = makeRef(*page), callback = WTFMove(callback)](WebCore::FloatRect windowFrame) mutable {
271         callback->sendSuccess(protectedThis->buildBrowsingContextForPage(page.get(), windowFrame));
272     });
273 }
274
275 void WebAutomationSession::createBrowsingContext(Inspector::ErrorString& errorString, String* handle)
276 {
277     ASSERT(m_client);
278     if (!m_client)
279         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session could not request a new browsing context.");
280
281     WebPageProxy* page = m_client->didRequestNewWindow(*this);
282     if (!page)
283         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session failed to create a new browsing context.");
284
285     m_activeBrowsingContextHandle = *handle = handleForWebPageProxy(*page);
286 }
287
288 void WebAutomationSession::closeBrowsingContext(Inspector::ErrorString& errorString, const String& handle)
289 {
290     WebPageProxy* page = webPageProxyForHandle(handle);
291     if (!page)
292         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
293
294     if (handle == m_activeBrowsingContextHandle)
295         m_activeBrowsingContextHandle = emptyString();
296
297     page->closePage(false);
298 }
299
300 void WebAutomationSession::switchToBrowsingContext(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle)
301 {
302     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
303     if (!page)
304         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
305
306     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
307     if (!frameID)
308         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
309
310     // FIXME: We don't need to track this in WK2. Remove in a follow up.
311     m_activeBrowsingContextHandle = browsingContextHandle;
312
313     page->setFocus(true);
314     page->process().send(Messages::WebAutomationSessionProxy::FocusFrame(page->pageID(), frameID.value()), 0);
315 }
316
317 void WebAutomationSession::resizeWindowOfBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& sizeObject, Ref<ResizeWindowOfBrowsingContextCallback>&& callback)
318 {
319 #if PLATFORM(IOS)
320     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
321 #else
322     float width;
323     if (!sizeObject.getDouble(WTF::ASCIILiteral("width"), width))
324         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'width' parameter was not found or invalid.");
325
326     float height;
327     if (!sizeObject.getDouble(WTF::ASCIILiteral("height"), height))
328         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'height' parameter was not found or invalid.");
329
330     if (width < 0)
331         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'width' parameter had an invalid value.");
332
333     if (height < 0)
334         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'height' parameter had an invalid value.");
335
336     WebPageProxy* page = webPageProxyForHandle(handle);
337     if (!page)
338         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
339
340     page->getWindowFrameWithCallback([callback = WTFMove(callback), page = makeRef(*page), width, height](WebCore::FloatRect originalFrame) mutable {
341         WebCore::FloatRect newFrame = WebCore::FloatRect(originalFrame.location(), WebCore::FloatSize(width, height));
342         if (newFrame == originalFrame)
343             return callback->sendSuccess();
344
345         page->setWindowFrame(newFrame);
346
347 #if PLATFORM(GTK)
348         callback->sendSuccess();
349 #else
350         // If nothing changed at all, it's probably fair to report that something went wrong.
351         // (We can't assume that the requested frame size will be honored exactly, however.)
352         page->getWindowFrameWithCallback([callback = WTFMove(callback), originalFrame](WebCore::FloatRect updatedFrame) {
353             if (originalFrame == updatedFrame)
354                 callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME_AND_DETAILS(InternalError, "The window size was expected to have changed, but did not."));
355             else
356                 callback->sendSuccess();
357         });
358 #endif
359     });
360 #endif
361 }
362
363 void WebAutomationSession::moveWindowOfBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& positionObject, Ref<MoveWindowOfBrowsingContextCallback>&& callback)
364 {
365 #if PLATFORM(IOS)
366     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
367 #else
368     float x;
369     if (!positionObject.getDouble(WTF::ASCIILiteral("x"), x))
370         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'x' parameter was not found or invalid.");
371
372     float y;
373     if (!positionObject.getDouble(WTF::ASCIILiteral("y"), y))
374         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'y' parameter was not found or invalid.");
375
376     if (x < 0)
377         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'x' parameter had an invalid value.");
378
379     if (y < 0)
380         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'y' parameter had an invalid value.");
381
382     WebPageProxy* page = webPageProxyForHandle(handle);
383     if (!page)
384         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
385
386     WebCore::FloatRect originalFrame;
387     page->getWindowFrameWithCallback([callback = WTFMove(callback), page = makeRef(*page), x, y](WebCore::FloatRect originalFrame) mutable {
388
389         WebCore::FloatRect newFrame = WebCore::FloatRect(WebCore::FloatPoint(x, y), originalFrame.size());
390         if (newFrame == originalFrame)
391             return callback->sendSuccess();
392
393         page->setWindowFrame(newFrame);
394
395 #if PLATFORM(GTK)
396         callback->sendSuccess();
397 #else
398         // If nothing changed at all, it's probably fair to report that something went wrong.
399         // (We can't assume that the requested frame size will be honored exactly, however.)
400         page->getWindowFrameWithCallback([callback = WTFMove(callback), originalFrame](WebCore::FloatRect updatedFrame) {
401             if (originalFrame == updatedFrame)
402                 callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME_AND_DETAILS(InternalError, "The window position was expected to have changed, but did not."));
403             else
404                 callback->sendSuccess();
405         });
406 #endif
407     });
408 #endif
409 }
410
411 static std::optional<Inspector::Protocol::Automation::PageLoadStrategy> pageLoadStrategyFromStringParameter(const String* optionalPageLoadStrategyString)
412 {
413     if (!optionalPageLoadStrategyString)
414         return defaultPageLoadStrategy;
415
416     auto parsedPageLoadStrategy = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::PageLoadStrategy>(*optionalPageLoadStrategyString);
417     if (!parsedPageLoadStrategy)
418         return std::nullopt;
419     return parsedPageLoadStrategy;
420 }
421
422 void WebAutomationSession::waitForNavigationToComplete(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<WaitForNavigationToCompleteCallback>&& callback)
423 {
424     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
425     if (!page)
426         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
427
428     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
429     if (!pageLoadStrategy)
430         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
431     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
432
433     // If page is loading and there's an active JavaScript dialog is probably because the
434     // dialog was started in an onload handler, so in case of normal page load strategy the
435     // load will not finish until the dialog is dismissed. Instead of waiting for the timeout,
436     // we return without waiting since we know it will timeout for sure. We want to check
437     // arguments first, though.
438     bool shouldTimeoutDueToUnexpectedAlert = pageLoadStrategy.value() == Inspector::Protocol::Automation::PageLoadStrategy::Normal
439         && page->pageLoadState().isLoading() && m_client->isShowingJavaScriptDialogOnPage(*this, *page);
440
441     if (optionalFrameHandle && !optionalFrameHandle->isEmpty()) {
442         std::optional<uint64_t> frameID = webFrameIDForHandle(*optionalFrameHandle);
443         if (!frameID)
444             FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
445         WebFrameProxy* frame = page->process().webFrame(frameID.value());
446         if (!frame)
447             FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
448         if (!shouldTimeoutDueToUnexpectedAlert)
449             waitForNavigationToCompleteOnFrame(*frame, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
450     } else {
451         if (!shouldTimeoutDueToUnexpectedAlert)
452             waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
453     }
454
455     if (shouldTimeoutDueToUnexpectedAlert) {
456         // §9 Navigation.
457         // 7. If the previous step completed by the session page load timeout being reached and the browser does not
458         // have an active user prompt, return error with error code timeout.
459         // 8. Return success with data null.
460         // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-wait-for-navigation-to-complete
461         callback->sendSuccess();
462     }
463 }
464
465 void WebAutomationSession::waitForNavigationToCompleteOnPage(WebPageProxy& page, Inspector::Protocol::Automation::PageLoadStrategy loadStrategy, Seconds timeout, Ref<Inspector::BackendDispatcher::CallbackBase>&& callback)
466 {
467     ASSERT(!m_loadTimer.isActive());
468     if (loadStrategy == Inspector::Protocol::Automation::PageLoadStrategy::None || !page.pageLoadState().isLoading()) {
469         callback->sendSuccess(InspectorObject::create());
470         return;
471     }
472
473     m_loadTimer.startOneShot(timeout);
474     switch (loadStrategy) {
475     case Inspector::Protocol::Automation::PageLoadStrategy::Normal:
476         m_pendingNormalNavigationInBrowsingContextCallbacksPerPage.set(page.pageID(), WTFMove(callback));
477         break;
478     case Inspector::Protocol::Automation::PageLoadStrategy::Eager:
479         m_pendingEagerNavigationInBrowsingContextCallbacksPerPage.set(page.pageID(), WTFMove(callback));
480         break;
481     case Inspector::Protocol::Automation::PageLoadStrategy::None:
482         ASSERT_NOT_REACHED();
483     }
484 }
485
486 void WebAutomationSession::waitForNavigationToCompleteOnFrame(WebFrameProxy& frame, Inspector::Protocol::Automation::PageLoadStrategy loadStrategy, Seconds timeout, Ref<Inspector::BackendDispatcher::CallbackBase>&& callback)
487 {
488     ASSERT(!m_loadTimer.isActive());
489     if (loadStrategy == Inspector::Protocol::Automation::PageLoadStrategy::None || frame.frameLoadState().state() == FrameLoadState::State::Finished) {
490         callback->sendSuccess(InspectorObject::create());
491         return;
492     }
493
494     m_loadTimer.startOneShot(timeout);
495     switch (loadStrategy) {
496     case Inspector::Protocol::Automation::PageLoadStrategy::Normal:
497         m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame.set(frame.frameID(), WTFMove(callback));
498         break;
499     case Inspector::Protocol::Automation::PageLoadStrategy::Eager:
500         m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame.set(frame.frameID(), WTFMove(callback));
501         break;
502     case Inspector::Protocol::Automation::PageLoadStrategy::None:
503         ASSERT_NOT_REACHED();
504     }
505 }
506
507 void WebAutomationSession::respondToPendingPageNavigationCallbacksWithTimeout(HashMap<uint64_t, RefPtr<Inspector::BackendDispatcher::CallbackBase>>& map)
508 {
509     Inspector::ErrorString timeoutError = STRING_FOR_PREDEFINED_ERROR_NAME(Timeout);
510     for (auto id : map.keys()) {
511         auto page = WebProcessProxy::webPage(id);
512         auto callback = map.take(id);
513         if (page && m_client->isShowingJavaScriptDialogOnPage(*this, *page))
514             callback->sendSuccess(InspectorObject::create());
515         else
516             callback->sendFailure(timeoutError);
517     }
518 }
519
520 static WebPageProxy* findPageForFrameID(const WebProcessPool& processPool, uint64_t frameID)
521 {
522     for (auto& process : processPool.processes()) {
523         if (auto* frame = process->webFrame(frameID))
524             return frame->page();
525     }
526     return nullptr;
527 }
528
529 void WebAutomationSession::respondToPendingFrameNavigationCallbacksWithTimeout(HashMap<uint64_t, RefPtr<Inspector::BackendDispatcher::CallbackBase>>& map)
530 {
531     Inspector::ErrorString timeoutError = STRING_FOR_PREDEFINED_ERROR_NAME(Timeout);
532     for (auto id : map.keys()) {
533         auto* page = findPageForFrameID(*m_processPool, id);
534         auto callback = map.take(id);
535         if (page && m_client->isShowingJavaScriptDialogOnPage(*this, *page))
536             callback->sendSuccess(InspectorObject::create());
537         else
538             callback->sendFailure(timeoutError);
539     }
540 }
541
542 void WebAutomationSession::loadTimerFired()
543 {
544     respondToPendingFrameNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame);
545     respondToPendingFrameNavigationCallbacksWithTimeout(m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame);
546     respondToPendingPageNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerPage);
547     respondToPendingPageNavigationCallbacksWithTimeout(m_pendingEagerNavigationInBrowsingContextCallbacksPerPage);
548 }
549
550 void WebAutomationSession::willShowJavaScriptDialog(WebPageProxy& page)
551 {
552     // Wait until the next run loop iteration to give time for the client to show the dialog,
553     // then check if page is loading and the dialog is still present. The dialog will block the
554     // load in case of normal strategy, so we want to dispatch all pending navigation callbacks.
555     RunLoop::main().dispatch([this, protectedThis = makeRef(*this), page = makeRef(page)] {
556         if (!page->isValid() || !page->pageLoadState().isLoading() || !m_client || !m_client->isShowingJavaScriptDialogOnPage(*this, page))
557             return;
558
559         respondToPendingFrameNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame);
560         respondToPendingPageNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerPage);
561     });
562 }
563
564 void WebAutomationSession::navigateBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String& url, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<NavigateBrowsingContextCallback>&& callback)
565 {
566     WebPageProxy* page = webPageProxyForHandle(handle);
567     if (!page)
568         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
569
570     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
571     if (!pageLoadStrategy)
572         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
573     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
574
575     page->loadRequest(WebCore::URL(WebCore::URL(), url));
576     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
577 }
578
579 void WebAutomationSession::goBackInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<GoBackInBrowsingContextCallback>&& callback)
580 {
581     WebPageProxy* page = webPageProxyForHandle(handle);
582     if (!page)
583         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
584
585     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
586     if (!pageLoadStrategy)
587         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
588     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
589
590     page->goBack();
591     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
592 }
593
594 void WebAutomationSession::goForwardInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<GoForwardInBrowsingContextCallback>&& callback)
595 {
596     WebPageProxy* page = webPageProxyForHandle(handle);
597     if (!page)
598         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
599
600     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
601     if (!pageLoadStrategy)
602         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
603     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
604
605     page->goForward();
606     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
607 }
608
609 void WebAutomationSession::reloadBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<ReloadBrowsingContextCallback>&& callback)
610 {
611     WebPageProxy* page = webPageProxyForHandle(handle);
612     if (!page)
613         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
614
615     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
616     if (!pageLoadStrategy)
617         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
618     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
619
620     page->reload({ });
621     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
622 }
623
624 void WebAutomationSession::navigationOccurredForFrame(const WebFrameProxy& frame)
625 {
626     if (frame.isMainFrame()) {
627         // New page loaded, clear frame handles previously cached.
628         m_handleWebFrameMap.clear();
629         m_webFrameHandleMap.clear();
630         if (auto callback = m_pendingNormalNavigationInBrowsingContextCallbacksPerPage.take(frame.page()->pageID())) {
631             m_loadTimer.stop();
632             callback->sendSuccess(InspectorObject::create());
633         }
634         m_domainNotifier->browsingContextCleared(handleForWebPageProxy(*frame.page()));
635     } else {
636         if (auto callback = m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame.take(frame.frameID())) {
637             m_loadTimer.stop();
638             callback->sendSuccess(InspectorObject::create());
639         }
640     }
641 }
642
643 void WebAutomationSession::documentLoadedForFrame(const WebFrameProxy& frame)
644 {
645     if (frame.isMainFrame()) {
646         if (auto callback = m_pendingEagerNavigationInBrowsingContextCallbacksPerPage.take(frame.page()->pageID())) {
647             m_loadTimer.stop();
648             callback->sendSuccess(InspectorObject::create());
649         }
650     } else {
651         if (auto callback = m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame.take(frame.frameID())) {
652             m_loadTimer.stop();
653             callback->sendSuccess(InspectorObject::create());
654         }
655     }
656 }
657
658 void WebAutomationSession::inspectorFrontendLoaded(const WebPageProxy& page)
659 {
660     if (auto callback = m_pendingInspectorCallbacksPerPage.take(page.pageID()))
661         callback->sendSuccess(InspectorObject::create());
662 }
663
664 void WebAutomationSession::keyboardEventsFlushedForPage(const WebPageProxy& page)
665 {
666     if (auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID())) {
667         callback->sendSuccess(InspectorObject::create());
668
669         if (m_pendingKeyboardEventsFlushedCallbacksPerPage.isEmpty())
670             m_simulatingUserInteraction = false;
671     }
672 }
673
674 void WebAutomationSession::willClosePage(const WebPageProxy& page)
675 {
676     String handle = handleForWebPageProxy(page);
677     m_domainNotifier->browsingContextCleared(handle);
678 }
679
680 static bool fileCanBeAcceptedForUpload(const String& filename, const HashSet<String>& allowedMIMETypes, const HashSet<String>& allowedFileExtensions)
681 {
682     if (!WebCore::fileExists(filename))
683         return false;
684
685     if (allowedMIMETypes.isEmpty() && allowedFileExtensions.isEmpty())
686         return true;
687
688     // We can't infer a MIME type from a file without an extension, just give up.
689     auto dotOffset = filename.reverseFind('.');
690     if (dotOffset == notFound)
691         return false;
692
693     String extension = filename.substring(dotOffset + 1).convertToASCIILowercase();
694     if (extension.isEmpty())
695         return false;
696
697     if (allowedFileExtensions.contains(extension))
698         return true;
699
700     String mappedMIMEType = WebCore::MIMETypeRegistry::getMIMETypeForExtension(extension).convertToASCIILowercase();
701     if (mappedMIMEType.isEmpty())
702         return false;
703     
704     if (allowedMIMETypes.contains(mappedMIMEType))
705         return true;
706
707     // Fall back to checking for a MIME type wildcard if an exact match is not found.
708     Vector<String> components;
709     mappedMIMEType.split('/', false, components);
710     if (components.size() != 2)
711         return false;
712
713     String wildcardedMIMEType = makeString(components[0], "/*");
714     if (allowedMIMETypes.contains(wildcardedMIMEType))
715         return true;
716
717     return false;
718 }
719
720 void WebAutomationSession::handleRunOpenPanel(const WebPageProxy& page, const WebFrameProxy&, const API::OpenPanelParameters& parameters, WebOpenPanelResultListenerProxy& resultListener)
721 {
722     if (!m_filesToSelectForFileUpload.size()) {
723         resultListener.cancel();
724         m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
725         return;
726     }
727
728     if (m_filesToSelectForFileUpload.size() > 1 && !parameters.allowMultipleFiles()) {
729         resultListener.cancel();
730         m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
731         return;
732     }
733
734     HashSet<String> allowedMIMETypes;
735     auto acceptMIMETypes = parameters.acceptMIMETypes();
736     for (auto type : acceptMIMETypes->elementsOfType<API::String>())
737         allowedMIMETypes.add(type->string());
738
739     HashSet<String> allowedFileExtensions;
740     auto acceptFileExtensions = parameters.acceptFileExtensions();
741     for (auto type : acceptFileExtensions->elementsOfType<API::String>()) {
742         // WebCore vends extensions with leading periods. Strip these to simplify matching later.
743         String extension = type->string();
744         ASSERT(extension.characterAt(0) == '.');
745         allowedFileExtensions.add(extension.substring(1));
746     }
747
748     // Per §14.3.10.5 in the W3C spec, if at least one file cannot be accepted, the command should fail.
749     // The REST API service can tell that this failed by checking the "files" attribute of the input element.
750     for (const String& filename : m_filesToSelectForFileUpload) {
751         if (!fileCanBeAcceptedForUpload(filename, allowedMIMETypes, allowedFileExtensions)) {
752             resultListener.cancel();
753             m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
754             return;
755         }
756     }
757
758     resultListener.chooseFiles(m_filesToSelectForFileUpload);
759     m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, false);
760 }
761
762 void WebAutomationSession::evaluateJavaScriptFunction(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const String& function, const Inspector::InspectorArray& arguments, const bool* optionalExpectsImplicitCallbackArgument, const int* optionalCallbackTimeout, Ref<EvaluateJavaScriptFunctionCallback>&& callback)
763 {
764     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
765     if (!page)
766         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
767
768     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
769     if (!frameID)
770         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
771
772     Vector<String> argumentsVector;
773     argumentsVector.reserveCapacity(arguments.length());
774
775     for (auto& argument : arguments) {
776         String argumentString;
777         argument->asString(argumentString);
778         argumentsVector.uncheckedAppend(argumentString);
779     }
780
781     bool expectsImplicitCallbackArgument = optionalExpectsImplicitCallbackArgument ? *optionalExpectsImplicitCallbackArgument : false;
782     int callbackTimeout = optionalCallbackTimeout ? *optionalCallbackTimeout : 0;
783
784     uint64_t callbackID = m_nextEvaluateJavaScriptCallbackID++;
785     m_evaluateJavaScriptFunctionCallbacks.set(callbackID, WTFMove(callback));
786
787     page->process().send(Messages::WebAutomationSessionProxy::EvaluateJavaScriptFunction(page->pageID(), frameID.value(), function, argumentsVector, expectsImplicitCallbackArgument, callbackTimeout, callbackID), 0);
788 }
789
790 void WebAutomationSession::didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType)
791 {
792     auto callback = m_evaluateJavaScriptFunctionCallbacks.take(callbackID);
793     if (!callback)
794         return;
795
796     if (!errorType.isEmpty())
797         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE_AND_DETAILS(errorType, result));
798     else
799         callback->sendSuccess(result);
800 }
801
802 void WebAutomationSession::resolveChildFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&& callback)
803 {
804     if (!optionalOrdinal && !optionalName && !optionalNodeHandle)
805         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "Command must specify a child frame by ordinal, name, or element handle.");
806
807     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
808     if (!page)
809         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
810
811     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
812     if (!frameID)
813         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
814
815     uint64_t callbackID = m_nextResolveFrameCallbackID++;
816     m_resolveChildFrameHandleCallbacks.set(callbackID, WTFMove(callback));
817
818     if (optionalNodeHandle) {
819         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithNodeHandle(page->pageID(), frameID.value(), *optionalNodeHandle, callbackID), 0);
820         return;
821     }
822
823     if (optionalName) {
824         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithName(page->pageID(), frameID.value(), *optionalName, callbackID), 0);
825         return;
826     }
827
828     if (optionalOrdinal) {
829         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithOrdinal(page->pageID(), frameID.value(), *optionalOrdinal, callbackID), 0);
830         return;
831     }
832
833     ASSERT_NOT_REACHED();
834 }
835
836 void WebAutomationSession::didResolveChildFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
837 {
838     auto callback = m_resolveChildFrameHandleCallbacks.take(callbackID);
839     if (!callback)
840         return;
841
842     if (!errorType.isEmpty())
843         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
844     else
845         callback->sendSuccess(handleForWebFrameID(frameID));
846 }
847
848 void WebAutomationSession::resolveParentFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&& callback)
849 {
850     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
851     if (!page)
852         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
853
854     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
855     if (!frameID)
856         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
857
858     uint64_t callbackID = m_nextResolveParentFrameCallbackID++;
859     m_resolveParentFrameHandleCallbacks.set(callbackID, WTFMove(callback));
860
861     page->process().send(Messages::WebAutomationSessionProxy::ResolveParentFrame(page->pageID(), frameID.value(), callbackID), 0);
862 }
863
864 void WebAutomationSession::didResolveParentFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
865 {
866     auto callback = m_resolveParentFrameHandleCallbacks.take(callbackID);
867     if (!callback)
868         return;
869
870     if (!errorType.isEmpty())
871         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
872     else
873         callback->sendSuccess(handleForWebFrameID(frameID));
874 }
875
876 void WebAutomationSession::computeElementLayout(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* optionalUseViewportCoordinates, Ref<ComputeElementLayoutCallback>&& callback)
877 {
878     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
879     if (!page)
880         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
881
882     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
883     if (!frameID)
884         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
885
886     uint64_t callbackID = m_nextComputeElementLayoutCallbackID++;
887     m_computeElementLayoutCallbacks.set(callbackID, WTFMove(callback));
888
889     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
890     bool useViewportCoordinates = optionalUseViewportCoordinates ? *optionalUseViewportCoordinates : false;
891
892     page->process().send(Messages::WebAutomationSessionProxy::ComputeElementLayout(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, useViewportCoordinates, callbackID), 0);
893 }
894
895 void WebAutomationSession::didComputeElementLayout(uint64_t callbackID, WebCore::IntRect rect, std::optional<WebCore::IntPoint> inViewCenterPoint, bool isObscured, const String& errorType)
896 {
897     auto callback = m_computeElementLayoutCallbacks.take(callbackID);
898     if (!callback)
899         return;
900
901     if (!errorType.isEmpty()) {
902         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
903         return;
904     }
905
906     auto originObject = Inspector::Protocol::Automation::Point::create()
907         .setX(rect.x())
908         .setY(rect.y())
909         .release();
910
911     auto sizeObject = Inspector::Protocol::Automation::Size::create()
912         .setWidth(rect.width())
913         .setHeight(rect.height())
914         .release();
915
916     auto rectObject = Inspector::Protocol::Automation::Rect::create()
917         .setOrigin(WTFMove(originObject))
918         .setSize(WTFMove(sizeObject))
919         .release();
920
921     if (!inViewCenterPoint) {
922         callback->sendSuccess(WTFMove(rectObject), nullptr, isObscured);
923         return;
924     }
925
926     auto inViewCenterPointObject = Inspector::Protocol::Automation::Point::create()
927         .setX(inViewCenterPoint.value().x())
928         .setY(inViewCenterPoint.value().y())
929         .release();
930
931     callback->sendSuccess(WTFMove(rectObject), WTFMove(inViewCenterPointObject), isObscured);
932 }
933
934 void WebAutomationSession::selectOptionElement(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, Ref<SelectOptionElementCallback>&& callback)
935 {
936     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
937     if (!page)
938         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
939
940     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
941     if (!frameID)
942         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
943
944     uint64_t callbackID = m_nextSelectOptionElementCallbackID++;
945     m_selectOptionElementCallbacks.set(callbackID, WTFMove(callback));
946
947     page->process().send(Messages::WebAutomationSessionProxy::SelectOptionElement(page->pageID(), frameID.value(), nodeHandle, callbackID), 0);
948 }
949
950 void WebAutomationSession::didSelectOptionElement(uint64_t callbackID, const String& errorType)
951 {
952     auto callback = m_selectOptionElementCallbacks.take(callbackID);
953     if (!callback)
954         return;
955
956     if (!errorType.isEmpty()) {
957         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
958         return;
959     }
960
961     callback->sendSuccess();
962 }
963
964
965 void WebAutomationSession::isShowingJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, bool* result)
966 {
967     ASSERT(m_client);
968     if (!m_client)
969         FAIL_WITH_PREDEFINED_ERROR(InternalError);
970
971     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
972     if (!page)
973         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
974
975     *result = m_client->isShowingJavaScriptDialogOnPage(*this, *page);
976 }
977
978 void WebAutomationSession::dismissCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
979 {
980     ASSERT(m_client);
981     if (!m_client)
982         FAIL_WITH_PREDEFINED_ERROR(InternalError);
983
984     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
985     if (!page)
986         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
987
988     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
989         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
990
991     m_client->dismissCurrentJavaScriptDialogOnPage(*this, *page);
992 }
993
994 void WebAutomationSession::acceptCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
995 {
996     ASSERT(m_client);
997     if (!m_client)
998         FAIL_WITH_PREDEFINED_ERROR(InternalError);
999
1000     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1001     if (!page)
1002         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1003
1004     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1005         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1006
1007     m_client->acceptCurrentJavaScriptDialogOnPage(*this, *page);
1008 }
1009
1010 void WebAutomationSession::messageOfCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, String* text)
1011 {
1012     ASSERT(m_client);
1013     if (!m_client)
1014         FAIL_WITH_PREDEFINED_ERROR(InternalError);
1015
1016     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1017     if (!page)
1018         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1019
1020     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1021         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1022
1023     *text = m_client->messageOfCurrentJavaScriptDialogOnPage(*this, *page);
1024 }
1025
1026 void WebAutomationSession::setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& promptValue)
1027 {
1028     ASSERT(m_client);
1029     if (!m_client)
1030         FAIL_WITH_PREDEFINED_ERROR(InternalError);
1031
1032     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1033     if (!page)
1034         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1035
1036     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1037         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1038
1039     // §18.4 Send Alert Text.
1040     // https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text
1041     // 3. Run the substeps of the first matching current user prompt:
1042     auto scriptDialogType = m_client->typeOfCurrentJavaScriptDialogOnPage(*this, *page);
1043     ASSERT(scriptDialogType);
1044     switch (scriptDialogType.value()) {
1045     case API::AutomationSessionClient::JavaScriptDialogType::Alert:
1046     case API::AutomationSessionClient::JavaScriptDialogType::Confirm:
1047         // Return error with error code element not interactable.
1048         FAIL_WITH_PREDEFINED_ERROR(ElementNotInteractable);
1049     case API::AutomationSessionClient::JavaScriptDialogType::Prompt:
1050         // Do nothing.
1051         break;
1052     case API::AutomationSessionClient::JavaScriptDialogType::BeforeUnloadConfirm:
1053         // Return error with error code unsupported operation.
1054         FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1055     }
1056
1057     m_client->setUserInputForCurrentJavaScriptPromptOnPage(*this, *page, promptValue);
1058 }
1059
1060 void WebAutomationSession::setFilesToSelectForFileUpload(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorArray& filenames)
1061 {
1062     Vector<String> newFileList;
1063     newFileList.reserveInitialCapacity(filenames.length());
1064
1065     for (auto item : filenames) {
1066         String filename;
1067         if (!item->asString(filename))
1068             FAIL_WITH_PREDEFINED_ERROR(InternalError);
1069
1070         newFileList.append(filename);
1071     }
1072
1073     m_filesToSelectForFileUpload.swap(newFileList);
1074 }
1075
1076 void WebAutomationSession::getAllCookies(ErrorString& errorString, const String& browsingContextHandle, Ref<GetAllCookiesCallback>&& callback)
1077 {
1078     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1079     if (!page)
1080         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1081
1082     // Always send the main frame ID as 0 so it is resolved on the WebProcess side. This avoids a race when page->mainFrame() is null still.
1083     const uint64_t mainFrameID = 0;
1084
1085     uint64_t callbackID = m_nextGetCookiesCallbackID++;
1086     m_getCookieCallbacks.set(callbackID, WTFMove(callback));
1087
1088     page->process().send(Messages::WebAutomationSessionProxy::GetCookiesForFrame(page->pageID(), mainFrameID, callbackID), 0);
1089 }
1090
1091 static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
1092 {
1093     return Inspector::Protocol::Automation::Cookie::create()
1094         .setName(cookie.name)
1095         .setValue(cookie.value)
1096         .setDomain(cookie.domain)
1097         .setPath(cookie.path)
1098         .setExpires(cookie.expires)
1099         .setSize((cookie.name.length() + cookie.value.length()))
1100         .setHttpOnly(cookie.httpOnly)
1101         .setSecure(cookie.secure)
1102         .setSession(cookie.session)
1103         .release();
1104 }
1105
1106 static Ref<Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>> buildArrayForCookies(Vector<WebCore::Cookie>& cookiesList)
1107 {
1108     auto cookies = Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>::create();
1109
1110     for (const auto& cookie : cookiesList)
1111         cookies->addItem(buildObjectForCookie(cookie));
1112
1113     return cookies;
1114 }
1115
1116 void WebAutomationSession::didGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie> cookies, const String& errorType)
1117 {
1118     auto callback = m_getCookieCallbacks.take(callbackID);
1119     if (!callback)
1120         return;
1121
1122     if (!errorType.isEmpty()) {
1123         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1124         return;
1125     }
1126
1127     callback->sendSuccess(buildArrayForCookies(cookies));
1128 }
1129
1130 void WebAutomationSession::deleteSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&& callback)
1131 {
1132     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1133     if (!page)
1134         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1135
1136     // Always send the main frame ID as 0 so it is resolved on the WebProcess side. This avoids a race when page->mainFrame() is null still.
1137     const uint64_t mainFrameID = 0;
1138
1139     uint64_t callbackID = m_nextDeleteCookieCallbackID++;
1140     m_deleteCookieCallbacks.set(callbackID, WTFMove(callback));
1141
1142     page->process().send(Messages::WebAutomationSessionProxy::DeleteCookie(page->pageID(), mainFrameID, cookieName, callbackID), 0);
1143 }
1144
1145 void WebAutomationSession::didDeleteCookie(uint64_t callbackID, const String& errorType)
1146 {
1147     auto callback = m_deleteCookieCallbacks.take(callbackID);
1148     if (!callback)
1149         return;
1150
1151     if (!errorType.isEmpty()) {
1152         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1153         return;
1154     }
1155
1156     callback->sendSuccess();
1157 }
1158
1159 void WebAutomationSession::addSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorObject& cookieObject, Ref<AddSingleCookieCallback>&& callback)
1160 {
1161     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1162     if (!page)
1163         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1164
1165     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
1166     ASSERT(activeURL.isValid());
1167
1168     WebCore::Cookie cookie;
1169
1170     if (!cookieObject.getString(WTF::ASCIILiteral("name"), cookie.name))
1171         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'name' was not found.");
1172
1173     if (!cookieObject.getString(WTF::ASCIILiteral("value"), cookie.value))
1174         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'value' was not found.");
1175
1176     String domain;
1177     if (!cookieObject.getString(WTF::ASCIILiteral("domain"), domain))
1178         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'domain' was not found.");
1179
1180     // Inherit the domain/host from the main frame's URL if it is not explicitly set.
1181     if (domain.isEmpty())
1182         domain = activeURL.host();
1183
1184     cookie.domain = domain;
1185
1186     if (!cookieObject.getString(WTF::ASCIILiteral("path"), cookie.path))
1187         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'path' was not found.");
1188
1189     double expires;
1190     if (!cookieObject.getDouble(WTF::ASCIILiteral("expires"), expires))
1191         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'expires' was not found.");
1192
1193     cookie.expires = expires * 1000.0;
1194
1195     if (!cookieObject.getBoolean(WTF::ASCIILiteral("secure"), cookie.secure))
1196         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'secure' was not found.");
1197
1198     if (!cookieObject.getBoolean(WTF::ASCIILiteral("session"), cookie.session))
1199         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'session' was not found.");
1200
1201     if (!cookieObject.getBoolean(WTF::ASCIILiteral("httpOnly"), cookie.httpOnly))
1202         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'httpOnly' was not found.");
1203
1204     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1205
1206     // FIXME: Using activeURL here twice is basically saying "this is always in the context of the main document"
1207     // which probably isn't accurate.
1208     cookieManager->setCookies(page->websiteDataStore().sessionID(), { cookie }, activeURL, activeURL, [callback = callback.copyRef()](CallbackBase::Error error) {
1209         if (error == CallbackBase::Error::None)
1210             callback->sendSuccess();
1211         else
1212             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1213     });
1214 }
1215
1216 void WebAutomationSession::deleteAllCookies(ErrorString& errorString, const String& browsingContextHandle)
1217 {
1218     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1219     if (!page)
1220         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1221
1222     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
1223     ASSERT(activeURL.isValid());
1224
1225     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1226     cookieManager->deleteCookiesForHostname(page->websiteDataStore().sessionID(), activeURL.host());
1227 }
1228
1229 void WebAutomationSession::getSessionPermissions(ErrorString&, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Automation::SessionPermissionData>>& out_permissions)
1230 {
1231     auto permissionsObjectArray = Inspector::Protocol::Array<Inspector::Protocol::Automation::SessionPermissionData>::create();
1232     auto getUserMediaPermissionObject = Inspector::Protocol::Automation::SessionPermissionData::create()
1233         .setPermission(Inspector::Protocol::Automation::SessionPermission::GetUserMedia)
1234         .setValue(m_permissionForGetUserMedia)
1235         .release();
1236
1237     permissionsObjectArray->addItem(WTFMove(getUserMediaPermissionObject));
1238     out_permissions = WTFMove(permissionsObjectArray);
1239 }
1240
1241 void WebAutomationSession::setSessionPermissions(ErrorString& errorString, const Inspector::InspectorArray& permissions)
1242 {
1243     for (auto it = permissions.begin(); it != permissions.end(); ++it) {
1244         RefPtr<InspectorObject> permission;
1245         if (!it->get()->asObject(permission))
1246             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permissions' is invalid.");
1247
1248         String permissionName;
1249         if (!permission->getString(WTF::ASCIILiteral("permission"), permissionName))
1250             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permission' is missing or invalid.");
1251
1252         auto parsedPermissionName = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::SessionPermission>(permissionName);
1253         if (!parsedPermissionName)
1254             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permission' has an unknown value.");
1255
1256         bool permissionValue;
1257         if (!permission->getBoolean(WTF::ASCIILiteral("value"), permissionValue))
1258             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'value' is missing or invalid.");
1259
1260         switch (parsedPermissionName.value()) {
1261         case Inspector::Protocol::Automation::SessionPermission::GetUserMedia:
1262             m_permissionForGetUserMedia = permissionValue;
1263             break;
1264         }
1265     }
1266 }
1267
1268 bool WebAutomationSession::shouldAllowGetUserMediaForPage(const WebPageProxy&) const
1269 {
1270     return m_permissionForGetUserMedia;
1271 }
1272
1273 #if USE(APPKIT) || PLATFORM(GTK)
1274 static WebEvent::Modifiers protocolModifierToWebEventModifier(Inspector::Protocol::Automation::KeyModifier modifier)
1275 {
1276     switch (modifier) {
1277     case Inspector::Protocol::Automation::KeyModifier::Alt:
1278         return WebEvent::AltKey;
1279     case Inspector::Protocol::Automation::KeyModifier::Meta:
1280         return WebEvent::MetaKey;
1281     case Inspector::Protocol::Automation::KeyModifier::Control:
1282         return WebEvent::ControlKey;
1283     case Inspector::Protocol::Automation::KeyModifier::Shift:
1284         return WebEvent::ShiftKey;
1285     case Inspector::Protocol::Automation::KeyModifier::CapsLock:
1286         return WebEvent::CapsLockKey;
1287     }
1288
1289     RELEASE_ASSERT_NOT_REACHED();
1290 }
1291 #endif // USE(APPKIT) || PLATFORM(GTK)
1292
1293 void WebAutomationSession::performMouseInteraction(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& requestedPositionObject, const String& mouseButtonString, const String& mouseInteractionString, const Inspector::InspectorArray& keyModifierStrings, Ref<PerformMouseInteractionCallback>&& callback)
1294 {
1295 #if !USE(APPKIT) && !PLATFORM(GTK)
1296     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1297 #else
1298     WebPageProxy* page = webPageProxyForHandle(handle);
1299     if (!page)
1300         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1301
1302     float x;
1303     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("x"), x))
1304         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'x' was not found.");
1305
1306     float y;
1307     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("y"), y))
1308         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'y' was not found.");
1309
1310     WebEvent::Modifiers keyModifiers = (WebEvent::Modifiers)0;
1311     for (auto it = keyModifierStrings.begin(); it != keyModifierStrings.end(); ++it) {
1312         String modifierString;
1313         if (!it->get()->asString(modifierString))
1314             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'modifiers' is invalid.");
1315
1316         auto parsedModifier = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyModifier>(modifierString);
1317         if (!parsedModifier)
1318             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A modifier in the 'modifiers' array is invalid.");
1319         WebEvent::Modifiers enumValue = protocolModifierToWebEventModifier(parsedModifier.value());
1320         keyModifiers = (WebEvent::Modifiers)(enumValue | keyModifiers);
1321     }
1322     
1323     page->getWindowFrameWithCallback([this, protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRef(*page), x, y, mouseInteractionString, mouseButtonString, keyModifiers](WebCore::FloatRect windowFrame) mutable {
1324
1325         x = std::min(std::max(0.0f, x), windowFrame.size().width());
1326         y = std::min(std::max(0.0f, y + page->topContentInset()), windowFrame.size().height());
1327
1328         WebCore::IntPoint viewPosition = WebCore::IntPoint(static_cast<int>(x), static_cast<int>(y));
1329
1330         auto parsedInteraction = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseInteraction>(mouseInteractionString);
1331         if (!parsedInteraction)
1332             return callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME_AND_DETAILS(InvalidParameter, "The parameter 'interaction' is invalid."));
1333
1334         auto parsedButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(mouseButtonString);
1335         if (!parsedButton)
1336             return callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME_AND_DETAILS(InvalidParameter, "The parameter 'button' is invalid."));
1337
1338         platformSimulateMouseInteraction(page, viewPosition, parsedInteraction.value(), parsedButton.value(), keyModifiers);
1339
1340         callback->sendSuccess(Inspector::Protocol::Automation::Point::create()
1341             .setX(x)
1342             .setY(y - page->topContentInset())
1343             .release());
1344     });
1345 #endif // USE(APPKIT) || PLATFORM(GTK)
1346 }
1347
1348 void WebAutomationSession::performKeyboardInteractions(ErrorString& errorString, const String& handle, const Inspector::InspectorArray& interactions, Ref<PerformKeyboardInteractionsCallback>&& callback)
1349 {
1350 #if !PLATFORM(COCOA) && !PLATFORM(GTK)
1351     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1352 #else
1353     WebPageProxy* page = webPageProxyForHandle(handle);
1354     if (!page)
1355         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1356
1357     if (!interactions.length())
1358         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interactions' was not found or empty.");
1359
1360     // Validate all of the parameters before performing any interactions with the browsing context under test.
1361     Vector<WTF::Function<void()>> actionsToPerform;
1362     actionsToPerform.reserveCapacity(interactions.length());
1363
1364     for (auto interaction : interactions) {
1365         RefPtr<InspectorObject> interactionObject;
1366         if (!interaction->asObject(interactionObject))
1367             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter was invalid.");
1368
1369         String interactionTypeString;
1370         if (!interactionObject->getString(ASCIILiteral("type"), interactionTypeString))
1371             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter is missing the 'type' key.");
1372         auto interactionType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyboardInteractionType>(interactionTypeString);
1373         if (!interactionType)
1374             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'type' key.");
1375
1376         String virtualKeyString;
1377         bool foundVirtualKey = interactionObject->getString(ASCIILiteral("key"), virtualKeyString);
1378         if (foundVirtualKey) {
1379             auto virtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(virtualKeyString);
1380             if (!virtualKey)
1381                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1382
1383             actionsToPerform.uncheckedAppend([this, page, interactionType, virtualKey] {
1384                 platformSimulateKeyStroke(*page, interactionType.value(), virtualKey.value());
1385             });
1386         }
1387
1388         String keySequence;
1389         bool foundKeySequence = interactionObject->getString(ASCIILiteral("text"), keySequence);
1390         if (foundKeySequence) {
1391             switch (interactionType.value()) {
1392             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress:
1393             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease:
1394                 // 'KeyPress' and 'KeyRelease' are meant for a virtual key and are not supported for a string (sequence of codepoints).
1395                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1396
1397             case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey:
1398                 actionsToPerform.uncheckedAppend([this, page, keySequence] {
1399                     platformSimulateKeySequence(*page, keySequence);
1400                 });
1401                 break;
1402             }
1403         }
1404
1405         if (!foundVirtualKey && !foundKeySequence)
1406             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "An interaction in the 'interactions' parameter is missing both 'key' and 'text'. One must be provided.");
1407     }
1408
1409     ASSERT(actionsToPerform.size());
1410     if (!actionsToPerform.size())
1411         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "No actions to perform.");
1412
1413     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1414     if (callbackInMap)
1415         callbackInMap->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
1416     callbackInMap = WTFMove(callback);
1417
1418     // This is cleared when all keyboard events are flushed.
1419     m_simulatingUserInteraction = true;
1420
1421     for (auto& action : actionsToPerform)
1422         action();
1423 #endif // PLATFORM(COCOA) || PLATFORM(GTK)
1424 }
1425
1426 void WebAutomationSession::takeScreenshot(ErrorString& errorString, const String& handle, const String* optionalFrameHandle, const String* optionalNodeHandle, const bool* optionalScrollIntoViewIfNeeded, Ref<TakeScreenshotCallback>&& callback)
1427 {
1428     WebPageProxy* page = webPageProxyForHandle(handle);
1429     if (!page)
1430         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1431
1432     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
1433     if (!frameID)
1434         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1435
1436     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
1437     String nodeHandle = optionalNodeHandle ? *optionalNodeHandle : emptyString();
1438
1439     uint64_t callbackID = m_nextScreenshotCallbackID++;
1440     m_screenshotCallbacks.set(callbackID, WTFMove(callback));
1441
1442     page->process().send(Messages::WebAutomationSessionProxy::TakeScreenshot(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, callbackID), 0);
1443 }
1444
1445 void WebAutomationSession::didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle& imageDataHandle, const String& errorType)
1446 {
1447     auto callback = m_screenshotCallbacks.take(callbackID);
1448     if (!callback)
1449         return;
1450
1451     if (!errorType.isEmpty()) {
1452         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1453         return;
1454     }
1455
1456     std::optional<String> base64EncodedData = platformGetBase64EncodedPNGData(imageDataHandle);
1457     if (!base64EncodedData) {
1458         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1459         return;
1460     }
1461
1462     callback->sendSuccess(base64EncodedData.value());
1463 }
1464
1465 // Platform-dependent Implementation Stubs.
1466
1467 #if !PLATFORM(MAC) && !PLATFORM(GTK)
1468 void WebAutomationSession::platformSimulateMouseInteraction(WebKit::WebPageProxy&, const WebCore::IntPoint&, Inspector::Protocol::Automation::MouseInteraction, Inspector::Protocol::Automation::MouseButton, WebEvent::Modifiers)
1469 {
1470 }
1471 #endif // !PLATFORM(MAC) && !PLATFORM(GTK)
1472
1473 #if !PLATFORM(COCOA) && !PLATFORM(GTK)
1474 void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy&, Inspector::Protocol::Automation::KeyboardInteractionType, Inspector::Protocol::Automation::VirtualKey)
1475 {
1476 }
1477
1478 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy&, const String&)
1479 {
1480 }
1481 #endif // !PLATFORM(COCOA) && !PLATFORM(GTK)
1482
1483 #if !PLATFORM(COCOA) && !USE(CAIRO)
1484 std::optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&)
1485 {
1486     return std::nullopt;
1487 }
1488 #endif // !PLATFORM(COCOA) && !USE(CAIRO)
1489
1490 } // namespace WebKit