8494d884f09763674f3be634ab5f063b4f867b49
[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
670 void WebAutomationSession::willClosePage(const WebPageProxy& page)
671 {
672     String handle = handleForWebPageProxy(page);
673     m_domainNotifier->browsingContextCleared(handle);
674 }
675
676 static bool fileCanBeAcceptedForUpload(const String& filename, const HashSet<String>& allowedMIMETypes, const HashSet<String>& allowedFileExtensions)
677 {
678     if (!WebCore::fileExists(filename))
679         return false;
680
681     if (allowedMIMETypes.isEmpty() && allowedFileExtensions.isEmpty())
682         return true;
683
684     // We can't infer a MIME type from a file without an extension, just give up.
685     auto dotOffset = filename.reverseFind('.');
686     if (dotOffset == notFound)
687         return false;
688
689     String extension = filename.substring(dotOffset + 1).convertToASCIILowercase();
690     if (extension.isEmpty())
691         return false;
692
693     if (allowedFileExtensions.contains(extension))
694         return true;
695
696     String mappedMIMEType = WebCore::MIMETypeRegistry::getMIMETypeForExtension(extension).convertToASCIILowercase();
697     if (mappedMIMEType.isEmpty())
698         return false;
699     
700     if (allowedMIMETypes.contains(mappedMIMEType))
701         return true;
702
703     // Fall back to checking for a MIME type wildcard if an exact match is not found.
704     Vector<String> components;
705     mappedMIMEType.split('/', false, components);
706     if (components.size() != 2)
707         return false;
708
709     String wildcardedMIMEType = makeString(components[0], "/*");
710     if (allowedMIMETypes.contains(wildcardedMIMEType))
711         return true;
712
713     return false;
714 }
715
716 void WebAutomationSession::handleRunOpenPanel(const WebPageProxy& page, const WebFrameProxy&, const API::OpenPanelParameters& parameters, WebOpenPanelResultListenerProxy& resultListener)
717 {
718     if (!m_filesToSelectForFileUpload.size()) {
719         resultListener.cancel();
720         m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
721         return;
722     }
723
724     if (m_filesToSelectForFileUpload.size() > 1 && !parameters.allowMultipleFiles()) {
725         resultListener.cancel();
726         m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
727         return;
728     }
729
730     HashSet<String> allowedMIMETypes;
731     auto acceptMIMETypes = parameters.acceptMIMETypes();
732     for (auto type : acceptMIMETypes->elementsOfType<API::String>())
733         allowedMIMETypes.add(type->string());
734
735     HashSet<String> allowedFileExtensions;
736     auto acceptFileExtensions = parameters.acceptFileExtensions();
737     for (auto type : acceptFileExtensions->elementsOfType<API::String>()) {
738         // WebCore vends extensions with leading periods. Strip these to simplify matching later.
739         String extension = type->string();
740         ASSERT(extension.characterAt(0) == '.');
741         allowedFileExtensions.add(extension.substring(1));
742     }
743
744     // Per §14.3.10.5 in the W3C spec, if at least one file cannot be accepted, the command should fail.
745     // The REST API service can tell that this failed by checking the "files" attribute of the input element.
746     for (const String& filename : m_filesToSelectForFileUpload) {
747         if (!fileCanBeAcceptedForUpload(filename, allowedMIMETypes, allowedFileExtensions)) {
748             resultListener.cancel();
749             m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
750             return;
751         }
752     }
753
754     resultListener.chooseFiles(m_filesToSelectForFileUpload);
755     m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, false);
756 }
757
758 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)
759 {
760     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
761     if (!page)
762         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
763
764     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
765     if (!frameID)
766         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
767
768     Vector<String> argumentsVector;
769     argumentsVector.reserveCapacity(arguments.length());
770
771     for (auto& argument : arguments) {
772         String argumentString;
773         argument->asString(argumentString);
774         argumentsVector.uncheckedAppend(argumentString);
775     }
776
777     bool expectsImplicitCallbackArgument = optionalExpectsImplicitCallbackArgument ? *optionalExpectsImplicitCallbackArgument : false;
778     int callbackTimeout = optionalCallbackTimeout ? *optionalCallbackTimeout : 0;
779
780     uint64_t callbackID = m_nextEvaluateJavaScriptCallbackID++;
781     m_evaluateJavaScriptFunctionCallbacks.set(callbackID, WTFMove(callback));
782
783     page->process().send(Messages::WebAutomationSessionProxy::EvaluateJavaScriptFunction(page->pageID(), frameID.value(), function, argumentsVector, expectsImplicitCallbackArgument, callbackTimeout, callbackID), 0);
784 }
785
786 void WebAutomationSession::didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType)
787 {
788     auto callback = m_evaluateJavaScriptFunctionCallbacks.take(callbackID);
789     if (!callback)
790         return;
791
792     if (!errorType.isEmpty())
793         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE_AND_DETAILS(errorType, result));
794     else
795         callback->sendSuccess(result);
796 }
797
798 void WebAutomationSession::resolveChildFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&& callback)
799 {
800     if (!optionalOrdinal && !optionalName && !optionalNodeHandle)
801         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "Command must specify a child frame by ordinal, name, or element handle.");
802
803     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
804     if (!page)
805         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
806
807     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
808     if (!frameID)
809         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
810
811     uint64_t callbackID = m_nextResolveFrameCallbackID++;
812     m_resolveChildFrameHandleCallbacks.set(callbackID, WTFMove(callback));
813
814     if (optionalNodeHandle) {
815         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithNodeHandle(page->pageID(), frameID.value(), *optionalNodeHandle, callbackID), 0);
816         return;
817     }
818
819     if (optionalName) {
820         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithName(page->pageID(), frameID.value(), *optionalName, callbackID), 0);
821         return;
822     }
823
824     if (optionalOrdinal) {
825         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithOrdinal(page->pageID(), frameID.value(), *optionalOrdinal, callbackID), 0);
826         return;
827     }
828
829     ASSERT_NOT_REACHED();
830 }
831
832 void WebAutomationSession::didResolveChildFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
833 {
834     auto callback = m_resolveChildFrameHandleCallbacks.take(callbackID);
835     if (!callback)
836         return;
837
838     if (!errorType.isEmpty())
839         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
840     else
841         callback->sendSuccess(handleForWebFrameID(frameID));
842 }
843
844 void WebAutomationSession::resolveParentFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&& callback)
845 {
846     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
847     if (!page)
848         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
849
850     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
851     if (!frameID)
852         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
853
854     uint64_t callbackID = m_nextResolveParentFrameCallbackID++;
855     m_resolveParentFrameHandleCallbacks.set(callbackID, WTFMove(callback));
856
857     page->process().send(Messages::WebAutomationSessionProxy::ResolveParentFrame(page->pageID(), frameID.value(), callbackID), 0);
858 }
859
860 void WebAutomationSession::didResolveParentFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
861 {
862     auto callback = m_resolveParentFrameHandleCallbacks.take(callbackID);
863     if (!callback)
864         return;
865
866     if (!errorType.isEmpty())
867         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
868     else
869         callback->sendSuccess(handleForWebFrameID(frameID));
870 }
871
872 void WebAutomationSession::computeElementLayout(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* optionalUseViewportCoordinates, Ref<ComputeElementLayoutCallback>&& callback)
873 {
874     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
875     if (!page)
876         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
877
878     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
879     if (!frameID)
880         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
881
882     uint64_t callbackID = m_nextComputeElementLayoutCallbackID++;
883     m_computeElementLayoutCallbacks.set(callbackID, WTFMove(callback));
884
885     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
886     bool useViewportCoordinates = optionalUseViewportCoordinates ? *optionalUseViewportCoordinates : false;
887
888     page->process().send(Messages::WebAutomationSessionProxy::ComputeElementLayout(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, useViewportCoordinates, callbackID), 0);
889 }
890
891 void WebAutomationSession::didComputeElementLayout(uint64_t callbackID, WebCore::IntRect rect, std::optional<WebCore::IntPoint> inViewCenterPoint, bool isObscured, const String& errorType)
892 {
893     auto callback = m_computeElementLayoutCallbacks.take(callbackID);
894     if (!callback)
895         return;
896
897     if (!errorType.isEmpty()) {
898         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
899         return;
900     }
901
902     auto originObject = Inspector::Protocol::Automation::Point::create()
903         .setX(rect.x())
904         .setY(rect.y())
905         .release();
906
907     auto sizeObject = Inspector::Protocol::Automation::Size::create()
908         .setWidth(rect.width())
909         .setHeight(rect.height())
910         .release();
911
912     auto rectObject = Inspector::Protocol::Automation::Rect::create()
913         .setOrigin(WTFMove(originObject))
914         .setSize(WTFMove(sizeObject))
915         .release();
916
917     if (!inViewCenterPoint) {
918         callback->sendSuccess(WTFMove(rectObject), nullptr, isObscured);
919         return;
920     }
921
922     auto inViewCenterPointObject = Inspector::Protocol::Automation::Point::create()
923         .setX(inViewCenterPoint.value().x())
924         .setY(inViewCenterPoint.value().y())
925         .release();
926
927     callback->sendSuccess(WTFMove(rectObject), WTFMove(inViewCenterPointObject), isObscured);
928 }
929
930 void WebAutomationSession::selectOptionElement(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, Ref<SelectOptionElementCallback>&& callback)
931 {
932     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
933     if (!page)
934         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
935
936     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
937     if (!frameID)
938         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
939
940     uint64_t callbackID = m_nextSelectOptionElementCallbackID++;
941     m_selectOptionElementCallbacks.set(callbackID, WTFMove(callback));
942
943     page->process().send(Messages::WebAutomationSessionProxy::SelectOptionElement(page->pageID(), frameID.value(), nodeHandle, callbackID), 0);
944 }
945
946 void WebAutomationSession::didSelectOptionElement(uint64_t callbackID, const String& errorType)
947 {
948     auto callback = m_selectOptionElementCallbacks.take(callbackID);
949     if (!callback)
950         return;
951
952     if (!errorType.isEmpty()) {
953         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
954         return;
955     }
956
957     callback->sendSuccess();
958 }
959
960
961 void WebAutomationSession::isShowingJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, bool* result)
962 {
963     ASSERT(m_client);
964     if (!m_client)
965         FAIL_WITH_PREDEFINED_ERROR(InternalError);
966
967     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
968     if (!page)
969         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
970
971     *result = m_client->isShowingJavaScriptDialogOnPage(*this, *page);
972 }
973
974 void WebAutomationSession::dismissCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
975 {
976     ASSERT(m_client);
977     if (!m_client)
978         FAIL_WITH_PREDEFINED_ERROR(InternalError);
979
980     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
981     if (!page)
982         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
983
984     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
985         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
986
987     m_client->dismissCurrentJavaScriptDialogOnPage(*this, *page);
988 }
989
990 void WebAutomationSession::acceptCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
991 {
992     ASSERT(m_client);
993     if (!m_client)
994         FAIL_WITH_PREDEFINED_ERROR(InternalError);
995
996     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
997     if (!page)
998         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
999
1000     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1001         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1002
1003     m_client->acceptCurrentJavaScriptDialogOnPage(*this, *page);
1004 }
1005
1006 void WebAutomationSession::messageOfCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, String* text)
1007 {
1008     ASSERT(m_client);
1009     if (!m_client)
1010         FAIL_WITH_PREDEFINED_ERROR(InternalError);
1011
1012     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1013     if (!page)
1014         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1015
1016     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1017         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1018
1019     *text = m_client->messageOfCurrentJavaScriptDialogOnPage(*this, *page);
1020 }
1021
1022 void WebAutomationSession::setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& promptValue)
1023 {
1024     ASSERT(m_client);
1025     if (!m_client)
1026         FAIL_WITH_PREDEFINED_ERROR(InternalError);
1027
1028     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1029     if (!page)
1030         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1031
1032     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1033         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1034
1035     // §18.4 Send Alert Text.
1036     // https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text
1037     // 3. Run the substeps of the first matching current user prompt:
1038     auto scriptDialogType = m_client->typeOfCurrentJavaScriptDialogOnPage(*this, *page);
1039     ASSERT(scriptDialogType);
1040     switch (scriptDialogType.value()) {
1041     case API::AutomationSessionClient::JavaScriptDialogType::Alert:
1042     case API::AutomationSessionClient::JavaScriptDialogType::Confirm:
1043         // Return error with error code element not interactable.
1044         FAIL_WITH_PREDEFINED_ERROR(ElementNotInteractable);
1045     case API::AutomationSessionClient::JavaScriptDialogType::Prompt:
1046         // Do nothing.
1047         break;
1048     case API::AutomationSessionClient::JavaScriptDialogType::BeforeUnloadConfirm:
1049         // Return error with error code unsupported operation.
1050         FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1051     }
1052
1053     m_client->setUserInputForCurrentJavaScriptPromptOnPage(*this, *page, promptValue);
1054 }
1055
1056 void WebAutomationSession::setFilesToSelectForFileUpload(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorArray& filenames)
1057 {
1058     Vector<String> newFileList;
1059     newFileList.reserveInitialCapacity(filenames.length());
1060
1061     for (auto item : filenames) {
1062         String filename;
1063         if (!item->asString(filename))
1064             FAIL_WITH_PREDEFINED_ERROR(InternalError);
1065
1066         newFileList.append(filename);
1067     }
1068
1069     m_filesToSelectForFileUpload.swap(newFileList);
1070 }
1071
1072 void WebAutomationSession::getAllCookies(ErrorString& errorString, const String& browsingContextHandle, Ref<GetAllCookiesCallback>&& callback)
1073 {
1074     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1075     if (!page)
1076         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1077
1078     // 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.
1079     const uint64_t mainFrameID = 0;
1080
1081     uint64_t callbackID = m_nextGetCookiesCallbackID++;
1082     m_getCookieCallbacks.set(callbackID, WTFMove(callback));
1083
1084     page->process().send(Messages::WebAutomationSessionProxy::GetCookiesForFrame(page->pageID(), mainFrameID, callbackID), 0);
1085 }
1086
1087 static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
1088 {
1089     return Inspector::Protocol::Automation::Cookie::create()
1090         .setName(cookie.name)
1091         .setValue(cookie.value)
1092         .setDomain(cookie.domain)
1093         .setPath(cookie.path)
1094         .setExpires(cookie.expires)
1095         .setSize((cookie.name.length() + cookie.value.length()))
1096         .setHttpOnly(cookie.httpOnly)
1097         .setSecure(cookie.secure)
1098         .setSession(cookie.session)
1099         .release();
1100 }
1101
1102 static Ref<Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>> buildArrayForCookies(Vector<WebCore::Cookie>& cookiesList)
1103 {
1104     auto cookies = Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>::create();
1105
1106     for (const auto& cookie : cookiesList)
1107         cookies->addItem(buildObjectForCookie(cookie));
1108
1109     return cookies;
1110 }
1111
1112 void WebAutomationSession::didGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie> cookies, const String& errorType)
1113 {
1114     auto callback = m_getCookieCallbacks.take(callbackID);
1115     if (!callback)
1116         return;
1117
1118     if (!errorType.isEmpty()) {
1119         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1120         return;
1121     }
1122
1123     callback->sendSuccess(buildArrayForCookies(cookies));
1124 }
1125
1126 void WebAutomationSession::deleteSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&& callback)
1127 {
1128     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1129     if (!page)
1130         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1131
1132     // 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.
1133     const uint64_t mainFrameID = 0;
1134
1135     uint64_t callbackID = m_nextDeleteCookieCallbackID++;
1136     m_deleteCookieCallbacks.set(callbackID, WTFMove(callback));
1137
1138     page->process().send(Messages::WebAutomationSessionProxy::DeleteCookie(page->pageID(), mainFrameID, cookieName, callbackID), 0);
1139 }
1140
1141 void WebAutomationSession::didDeleteCookie(uint64_t callbackID, const String& errorType)
1142 {
1143     auto callback = m_deleteCookieCallbacks.take(callbackID);
1144     if (!callback)
1145         return;
1146
1147     if (!errorType.isEmpty()) {
1148         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1149         return;
1150     }
1151
1152     callback->sendSuccess();
1153 }
1154
1155 void WebAutomationSession::addSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorObject& cookieObject, Ref<AddSingleCookieCallback>&& callback)
1156 {
1157     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1158     if (!page)
1159         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1160
1161     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
1162     ASSERT(activeURL.isValid());
1163
1164     WebCore::Cookie cookie;
1165
1166     if (!cookieObject.getString(WTF::ASCIILiteral("name"), cookie.name))
1167         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'name' was not found.");
1168
1169     if (!cookieObject.getString(WTF::ASCIILiteral("value"), cookie.value))
1170         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'value' was not found.");
1171
1172     String domain;
1173     if (!cookieObject.getString(WTF::ASCIILiteral("domain"), domain))
1174         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'domain' was not found.");
1175
1176     // Inherit the domain/host from the main frame's URL if it is not explicitly set.
1177     if (domain.isEmpty())
1178         domain = activeURL.host();
1179
1180     cookie.domain = domain;
1181
1182     if (!cookieObject.getString(WTF::ASCIILiteral("path"), cookie.path))
1183         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'path' was not found.");
1184
1185     double expires;
1186     if (!cookieObject.getDouble(WTF::ASCIILiteral("expires"), expires))
1187         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'expires' was not found.");
1188
1189     cookie.expires = expires * 1000.0;
1190
1191     if (!cookieObject.getBoolean(WTF::ASCIILiteral("secure"), cookie.secure))
1192         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'secure' was not found.");
1193
1194     if (!cookieObject.getBoolean(WTF::ASCIILiteral("session"), cookie.session))
1195         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'session' was not found.");
1196
1197     if (!cookieObject.getBoolean(WTF::ASCIILiteral("httpOnly"), cookie.httpOnly))
1198         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'httpOnly' was not found.");
1199
1200     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1201
1202     // FIXME: Using activeURL here twice is basically saying "this is always in the context of the main document"
1203     // which probably isn't accurate.
1204     cookieManager->setCookies(page->websiteDataStore().sessionID(), { cookie }, activeURL, activeURL, [callback = callback.copyRef()](CallbackBase::Error error) {
1205         if (error == CallbackBase::Error::None)
1206             callback->sendSuccess();
1207         else
1208             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1209     });
1210 }
1211
1212 void WebAutomationSession::deleteAllCookies(ErrorString& errorString, const String& browsingContextHandle)
1213 {
1214     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1215     if (!page)
1216         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1217
1218     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
1219     ASSERT(activeURL.isValid());
1220
1221     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1222     cookieManager->deleteCookiesForHostname(page->websiteDataStore().sessionID(), activeURL.host());
1223 }
1224
1225 void WebAutomationSession::getSessionPermissions(ErrorString&, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Automation::SessionPermissionData>>& out_permissions)
1226 {
1227     auto permissionsObjectArray = Inspector::Protocol::Array<Inspector::Protocol::Automation::SessionPermissionData>::create();
1228     auto getUserMediaPermissionObject = Inspector::Protocol::Automation::SessionPermissionData::create()
1229         .setPermission(Inspector::Protocol::Automation::SessionPermission::GetUserMedia)
1230         .setValue(m_permissionForGetUserMedia)
1231         .release();
1232
1233     permissionsObjectArray->addItem(WTFMove(getUserMediaPermissionObject));
1234     out_permissions = WTFMove(permissionsObjectArray);
1235 }
1236
1237 void WebAutomationSession::setSessionPermissions(ErrorString& errorString, const Inspector::InspectorArray& permissions)
1238 {
1239     for (auto it = permissions.begin(); it != permissions.end(); ++it) {
1240         RefPtr<InspectorObject> permission;
1241         if (!it->get()->asObject(permission))
1242             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permissions' is invalid.");
1243
1244         String permissionName;
1245         if (!permission->getString(WTF::ASCIILiteral("permission"), permissionName))
1246             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permission' is missing or invalid.");
1247
1248         auto parsedPermissionName = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::SessionPermission>(permissionName);
1249         if (!parsedPermissionName)
1250             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permission' has an unknown value.");
1251
1252         bool permissionValue;
1253         if (!permission->getBoolean(WTF::ASCIILiteral("value"), permissionValue))
1254             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'value' is missing or invalid.");
1255
1256         switch (parsedPermissionName.value()) {
1257         case Inspector::Protocol::Automation::SessionPermission::GetUserMedia:
1258             m_permissionForGetUserMedia = permissionValue;
1259             break;
1260         }
1261     }
1262 }
1263
1264 bool WebAutomationSession::shouldAllowGetUserMediaForPage(const WebPageProxy&) const
1265 {
1266     return m_permissionForGetUserMedia;
1267 }
1268
1269 #if USE(APPKIT) || PLATFORM(GTK)
1270 static WebEvent::Modifiers protocolModifierToWebEventModifier(Inspector::Protocol::Automation::KeyModifier modifier)
1271 {
1272     switch (modifier) {
1273     case Inspector::Protocol::Automation::KeyModifier::Alt:
1274         return WebEvent::AltKey;
1275     case Inspector::Protocol::Automation::KeyModifier::Meta:
1276         return WebEvent::MetaKey;
1277     case Inspector::Protocol::Automation::KeyModifier::Control:
1278         return WebEvent::ControlKey;
1279     case Inspector::Protocol::Automation::KeyModifier::Shift:
1280         return WebEvent::ShiftKey;
1281     case Inspector::Protocol::Automation::KeyModifier::CapsLock:
1282         return WebEvent::CapsLockKey;
1283     }
1284
1285     RELEASE_ASSERT_NOT_REACHED();
1286 }
1287 #endif // USE(APPKIT)
1288
1289 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)
1290 {
1291 #if !USE(APPKIT) && !PLATFORM(GTK)
1292     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1293 #else
1294     WebPageProxy* page = webPageProxyForHandle(handle);
1295     if (!page)
1296         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1297
1298     float x;
1299     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("x"), x))
1300         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'x' was not found.");
1301
1302     float y;
1303     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("y"), y))
1304         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'y' was not found.");
1305
1306     WebEvent::Modifiers keyModifiers = (WebEvent::Modifiers)0;
1307     for (auto it = keyModifierStrings.begin(); it != keyModifierStrings.end(); ++it) {
1308         String modifierString;
1309         if (!it->get()->asString(modifierString))
1310             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'modifiers' is invalid.");
1311
1312         auto parsedModifier = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyModifier>(modifierString);
1313         if (!parsedModifier)
1314             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A modifier in the 'modifiers' array is invalid.");
1315         WebEvent::Modifiers enumValue = protocolModifierToWebEventModifier(parsedModifier.value());
1316         keyModifiers = (WebEvent::Modifiers)(enumValue | keyModifiers);
1317     }
1318     
1319     page->getWindowFrameWithCallback([this, protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRef(*page), x, y, mouseInteractionString, mouseButtonString, keyModifiers](WebCore::FloatRect windowFrame) mutable {
1320
1321         x = std::min(std::max(0.0f, x), windowFrame.size().width());
1322         y = std::min(std::max(0.0f, y + page->topContentInset()), windowFrame.size().height());
1323
1324         WebCore::IntPoint viewPosition = WebCore::IntPoint(static_cast<int>(x), static_cast<int>(y));
1325
1326         auto parsedInteraction = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseInteraction>(mouseInteractionString);
1327         if (!parsedInteraction)
1328             return callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME_AND_DETAILS(InvalidParameter, "The parameter 'interaction' is invalid."));
1329
1330         auto parsedButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(mouseButtonString);
1331         if (!parsedButton)
1332             return callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME_AND_DETAILS(InvalidParameter, "The parameter 'button' is invalid."));
1333
1334         platformSimulateMouseInteraction(page, viewPosition, parsedInteraction.value(), parsedButton.value(), keyModifiers);
1335
1336         callback->sendSuccess(Inspector::Protocol::Automation::Point::create()
1337             .setX(x)
1338             .setY(y - page->topContentInset())
1339             .release());
1340     });
1341 #endif // USE(APPKIT)
1342 }
1343
1344 void WebAutomationSession::performKeyboardInteractions(ErrorString& errorString, const String& handle, const Inspector::InspectorArray& interactions, Ref<PerformKeyboardInteractionsCallback>&& callback)
1345 {
1346 #if !PLATFORM(COCOA) && !PLATFORM(GTK)
1347     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1348 #else
1349     WebPageProxy* page = webPageProxyForHandle(handle);
1350     if (!page)
1351         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1352
1353     if (!interactions.length())
1354         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interactions' was not found or empty.");
1355
1356     // Validate all of the parameters before performing any interactions with the browsing context under test.
1357     Vector<WTF::Function<void()>> actionsToPerform;
1358     actionsToPerform.reserveCapacity(interactions.length());
1359
1360     for (auto interaction : interactions) {
1361         RefPtr<InspectorObject> interactionObject;
1362         if (!interaction->asObject(interactionObject))
1363             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter was invalid.");
1364
1365         String interactionTypeString;
1366         if (!interactionObject->getString(ASCIILiteral("type"), interactionTypeString))
1367             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter is missing the 'type' key.");
1368         auto interactionType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyboardInteractionType>(interactionTypeString);
1369         if (!interactionType)
1370             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'type' key.");
1371
1372         String virtualKeyString;
1373         bool foundVirtualKey = interactionObject->getString(ASCIILiteral("key"), virtualKeyString);
1374         if (foundVirtualKey) {
1375             auto virtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(virtualKeyString);
1376             if (!virtualKey)
1377                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1378
1379             actionsToPerform.uncheckedAppend([this, page, interactionType, virtualKey] {
1380                 platformSimulateKeyStroke(*page, interactionType.value(), virtualKey.value());
1381             });
1382         }
1383
1384         String keySequence;
1385         bool foundKeySequence = interactionObject->getString(ASCIILiteral("text"), keySequence);
1386         if (foundKeySequence) {
1387             switch (interactionType.value()) {
1388             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress:
1389             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease:
1390                 // 'KeyPress' and 'KeyRelease' are meant for a virtual key and are not supported for a string (sequence of codepoints).
1391                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1392
1393             case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey:
1394                 actionsToPerform.uncheckedAppend([this, page, keySequence] {
1395                     platformSimulateKeySequence(*page, keySequence);
1396                 });
1397                 break;
1398             }
1399         }
1400
1401         if (!foundVirtualKey && !foundKeySequence)
1402             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "An interaction in the 'interactions' parameter is missing both 'key' and 'text'. One must be provided.");
1403     }
1404
1405     ASSERT(actionsToPerform.size());
1406     if (!actionsToPerform.size())
1407         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "No actions to perform.");
1408
1409     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1410     if (callbackInMap)
1411         callbackInMap->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
1412     callbackInMap = WTFMove(callback);
1413
1414     for (auto& action : actionsToPerform)
1415         action();
1416 #endif // PLATFORM(COCOA)
1417 }
1418
1419 void WebAutomationSession::takeScreenshot(ErrorString& errorString, const String& handle, const String* optionalFrameHandle, const String* optionalNodeHandle, const bool* optionalScrollIntoViewIfNeeded, Ref<TakeScreenshotCallback>&& callback)
1420 {
1421     WebPageProxy* page = webPageProxyForHandle(handle);
1422     if (!page)
1423         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1424
1425     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
1426     if (!frameID)
1427         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1428
1429     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
1430     String nodeHandle = optionalNodeHandle ? *optionalNodeHandle : emptyString();
1431
1432     uint64_t callbackID = m_nextScreenshotCallbackID++;
1433     m_screenshotCallbacks.set(callbackID, WTFMove(callback));
1434
1435     page->process().send(Messages::WebAutomationSessionProxy::TakeScreenshot(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, callbackID), 0);
1436 }
1437
1438 void WebAutomationSession::didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle& imageDataHandle, const String& errorType)
1439 {
1440     auto callback = m_screenshotCallbacks.take(callbackID);
1441     if (!callback)
1442         return;
1443
1444     if (!errorType.isEmpty()) {
1445         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1446         return;
1447     }
1448
1449     std::optional<String> base64EncodedData = platformGetBase64EncodedPNGData(imageDataHandle);
1450     if (!base64EncodedData) {
1451         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1452         return;
1453     }
1454
1455     callback->sendSuccess(base64EncodedData.value());
1456 }
1457
1458 // Platform-dependent Implementation Stubs.
1459
1460 #if !PLATFORM(MAC) && !PLATFORM(GTK)
1461 void WebAutomationSession::platformSimulateMouseInteraction(WebKit::WebPageProxy&, const WebCore::IntPoint&, Inspector::Protocol::Automation::MouseInteraction, Inspector::Protocol::Automation::MouseButton, WebEvent::Modifiers)
1462 {
1463 }
1464 #endif // !PLATFORM(MAC) && !PLATFORM(GTK)
1465
1466 #if !PLATFORM(COCOA) && !PLATFORM(GTK)
1467 void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy&, Inspector::Protocol::Automation::KeyboardInteractionType, Inspector::Protocol::Automation::VirtualKey)
1468 {
1469 }
1470
1471 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy&, const String&)
1472 {
1473 }
1474 #endif // !PLATFORM(COCOA) && !PLATFORM(GTK)
1475
1476 #if !PLATFORM(COCOA) && !USE(CAIRO)
1477 std::optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&)
1478 {
1479     return std::nullopt;
1480 }
1481 #endif // !PLATFORM(COCOA) && !USE(CAIRO)
1482
1483 } // namespace WebKit