7f6d6cd1218a3711b89d4be593f2b3cccc47ca15
[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
58 WebAutomationSession::WebAutomationSession()
59     : m_client(std::make_unique<API::AutomationSessionClient>())
60     , m_frontendRouter(FrontendRouter::create())
61     , m_backendDispatcher(BackendDispatcher::create(m_frontendRouter.copyRef()))
62     , m_domainDispatcher(AutomationBackendDispatcher::create(m_backendDispatcher, this))
63     , m_domainNotifier(std::make_unique<AutomationFrontendDispatcher>(m_frontendRouter))
64     , m_loadTimer(RunLoop::main(), this, &WebAutomationSession::loadTimerFired)
65 {
66 }
67
68 WebAutomationSession::~WebAutomationSession()
69 {
70     ASSERT(!m_client);
71
72     if (m_processPool)
73         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
74 }
75
76 void WebAutomationSession::setClient(std::unique_ptr<API::AutomationSessionClient>&& client)
77 {
78     m_client = WTFMove(client);
79 }
80
81 void WebAutomationSession::setProcessPool(WebKit::WebProcessPool* processPool)
82 {
83     if (m_processPool)
84         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
85
86     m_processPool = processPool;
87
88     if (m_processPool)
89         m_processPool->addMessageReceiver(Messages::WebAutomationSession::messageReceiverName(), *this);
90 }
91
92 // NOTE: this class could be split at some point to support local and remote automation sessions.
93 // For now, it only works with a remote automation driver over a RemoteInspector connection.
94
95 #if ENABLE(REMOTE_INSPECTOR)
96
97 // Inspector::RemoteAutomationTarget API
98
99 void WebAutomationSession::dispatchMessageFromRemote(const String& message)
100 {
101     m_backendDispatcher->dispatch(message);
102 }
103
104 void WebAutomationSession::connect(Inspector::FrontendChannel* channel, bool isAutomaticConnection, bool immediatelyPause)
105 {
106     UNUSED_PARAM(isAutomaticConnection);
107     UNUSED_PARAM(immediatelyPause);
108
109     m_remoteChannel = channel;
110     m_frontendRouter->connectFrontend(channel);
111
112     setIsPaired(true);
113 }
114
115 void WebAutomationSession::disconnect(Inspector::FrontendChannel* channel)
116 {
117     ASSERT(channel == m_remoteChannel);
118     terminate();
119 }
120
121 #endif // ENABLE(REMOTE_INSPECTOR)
122
123 void WebAutomationSession::terminate()
124 {
125 #if ENABLE(REMOTE_INSPECTOR)
126     if (Inspector::FrontendChannel* channel = m_remoteChannel) {
127         m_remoteChannel = nullptr;
128         m_frontendRouter->disconnectFrontend(channel);
129     }
130
131     setIsPaired(false);
132 #endif
133
134     if (m_client)
135         m_client->didDisconnectFromRemote(*this);
136 }
137
138 WebPageProxy* WebAutomationSession::webPageProxyForHandle(const String& handle)
139 {
140     auto iter = m_handleWebPageMap.find(handle);
141     if (iter == m_handleWebPageMap.end())
142         return nullptr;
143     return WebProcessProxy::webPage(iter->value);
144 }
145
146 String WebAutomationSession::handleForWebPageProxy(const WebPageProxy& webPageProxy)
147 {
148     auto iter = m_webPageHandleMap.find(webPageProxy.pageID());
149     if (iter != m_webPageHandleMap.end())
150         return iter->value;
151
152     String handle = "page-" + createCanonicalUUIDString().convertToASCIIUppercase();
153
154     auto firstAddResult = m_webPageHandleMap.add(webPageProxy.pageID(), handle);
155     RELEASE_ASSERT(firstAddResult.isNewEntry);
156
157     auto secondAddResult = m_handleWebPageMap.add(handle, webPageProxy.pageID());
158     RELEASE_ASSERT(secondAddResult.isNewEntry);
159
160     return handle;
161 }
162
163 std::optional<uint64_t> WebAutomationSession::webFrameIDForHandle(const String& handle)
164 {
165     if (handle.isEmpty())
166         return 0;
167
168     auto iter = m_handleWebFrameMap.find(handle);
169     if (iter == m_handleWebFrameMap.end())
170         return std::nullopt;
171
172     return iter->value;
173 }
174
175 String WebAutomationSession::handleForWebFrameID(uint64_t frameID)
176 {
177     if (!frameID)
178         return emptyString();
179
180     for (auto& process : m_processPool->processes()) {
181         if (WebFrameProxy* frame = process->webFrame(frameID)) {
182             if (frame->isMainFrame())
183                 return emptyString();
184             break;
185         }
186     }
187
188     auto iter = m_webFrameHandleMap.find(frameID);
189     if (iter != m_webFrameHandleMap.end())
190         return iter->value;
191
192     String handle = "frame-" + createCanonicalUUIDString().convertToASCIIUppercase();
193
194     auto firstAddResult = m_webFrameHandleMap.add(frameID, handle);
195     RELEASE_ASSERT(firstAddResult.isNewEntry);
196
197     auto secondAddResult = m_handleWebFrameMap.add(handle, frameID);
198     RELEASE_ASSERT(secondAddResult.isNewEntry);
199
200     return handle;
201 }
202
203 String WebAutomationSession::handleForWebFrameProxy(const WebFrameProxy& webFrameProxy)
204 {
205     return handleForWebFrameID(webFrameProxy.frameID());
206 }
207
208 RefPtr<Inspector::Protocol::Automation::BrowsingContext> WebAutomationSession::buildBrowsingContextForPage(WebPageProxy& page)
209 {
210     WebCore::FloatRect windowFrame;
211     page.getWindowFrame(windowFrame);
212
213     auto originObject = Inspector::Protocol::Automation::Point::create()
214         .setX(windowFrame.x())
215         .setY(windowFrame.y())
216         .release();
217
218     auto sizeObject = Inspector::Protocol::Automation::Size::create()
219         .setWidth(windowFrame.width())
220         .setHeight(windowFrame.height())
221         .release();
222
223     String handle = handleForWebPageProxy(page);
224
225     return Inspector::Protocol::Automation::BrowsingContext::create()
226         .setHandle(handle)
227         .setActive(m_activeBrowsingContextHandle == handle)
228         .setUrl(page.pageLoadState().activeURL())
229         .setWindowOrigin(WTFMove(originObject))
230         .setWindowSize(WTFMove(sizeObject))
231         .release();
232 }
233
234 // Platform-independent Commands.
235
236 void WebAutomationSession::getBrowsingContexts(Inspector::ErrorString& errorString, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>>& contexts)
237 {
238     contexts = Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>::create();
239
240     for (auto& process : m_processPool->processes()) {
241         for (auto* page : process->pages()) {
242             ASSERT(page);
243             if (!page->isControlledByAutomation())
244                 continue;
245
246             contexts->addItem(buildBrowsingContextForPage(*page));
247         }
248     }
249 }
250
251 void WebAutomationSession::getBrowsingContext(Inspector::ErrorString& errorString, const String& handle, RefPtr<Inspector::Protocol::Automation::BrowsingContext>& context)
252 {
253     WebPageProxy* page = webPageProxyForHandle(handle);
254     if (!page)
255         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
256
257     context = buildBrowsingContextForPage(*page);
258 }
259
260 void WebAutomationSession::createBrowsingContext(Inspector::ErrorString& errorString, String* handle)
261 {
262     ASSERT(m_client);
263     if (!m_client)
264         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session could not request a new browsing context.");
265
266     WebPageProxy* page = m_client->didRequestNewWindow(*this);
267     if (!page)
268         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session failed to create a new browsing context.");
269
270     m_activeBrowsingContextHandle = *handle = handleForWebPageProxy(*page);
271 }
272
273 void WebAutomationSession::closeBrowsingContext(Inspector::ErrorString& errorString, const String& handle)
274 {
275     WebPageProxy* page = webPageProxyForHandle(handle);
276     if (!page)
277         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
278
279     if (handle == m_activeBrowsingContextHandle)
280         m_activeBrowsingContextHandle = emptyString();
281
282     page->closePage(false);
283 }
284
285 void WebAutomationSession::switchToBrowsingContext(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle)
286 {
287     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
288     if (!page)
289         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
290
291     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
292     if (!frameID)
293         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
294
295     // FIXME: We don't need to track this in WK2. Remove in a follow up.
296     m_activeBrowsingContextHandle = browsingContextHandle;
297
298     page->setFocus(true);
299     page->process().send(Messages::WebAutomationSessionProxy::FocusFrame(page->pageID(), frameID.value()), 0);
300 }
301
302 void WebAutomationSession::resizeWindowOfBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& sizeObject)
303 {
304 #if PLATFORM(IOS)
305     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
306 #else
307     float width;
308     if (!sizeObject.getDouble(WTF::ASCIILiteral("width"), width))
309         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'width' parameter was not found or invalid.");
310
311     float height;
312     if (!sizeObject.getDouble(WTF::ASCIILiteral("height"), height))
313         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'height' parameter was not found or invalid.");
314
315     if (width < 0)
316         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'width' parameter had an invalid value.");
317
318     if (height < 0)
319         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'height' parameter had an invalid value.");
320
321     WebPageProxy* page = webPageProxyForHandle(handle);
322     if (!page)
323         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
324
325     WebCore::FloatRect originalFrame;
326     page->getWindowFrame(originalFrame);
327
328     WebCore::FloatRect newFrame = WebCore::FloatRect(originalFrame.location(), WebCore::FloatSize(width, height));
329     if (newFrame == originalFrame)
330         return;
331
332     page->setWindowFrame(newFrame);
333
334 #if !PLATFORM(GTK)
335     // If nothing changed at all, it's probably fair to report that something went wrong.
336     // (We can't assume that the requested frame size will be honored exactly, however.)
337     WebCore::FloatRect updatedFrame;
338     page->getWindowFrame(updatedFrame);
339     if (originalFrame == updatedFrame)
340         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The window size was expected to have changed, but did not.");
341 #endif
342 #endif
343 }
344
345 void WebAutomationSession::moveWindowOfBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& positionObject)
346 {
347 #if PLATFORM(IOS)
348     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
349 #else
350     float x;
351     if (!positionObject.getDouble(WTF::ASCIILiteral("x"), x))
352         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'x' parameter was not found or invalid.");
353
354     float y;
355     if (!positionObject.getDouble(WTF::ASCIILiteral("y"), y))
356         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'y' parameter was not found or invalid.");
357
358     if (x < 0)
359         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'x' parameter had an invalid value.");
360
361     if (y < 0)
362         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'y' parameter had an invalid value.");
363
364     WebPageProxy* page = webPageProxyForHandle(handle);
365     if (!page)
366         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
367
368     WebCore::FloatRect originalFrame;
369     page->getWindowFrame(originalFrame);
370
371     WebCore::FloatRect newFrame = WebCore::FloatRect(WebCore::FloatPoint(x, y), originalFrame.size());
372     if (newFrame == originalFrame)
373         return;
374
375     page->setWindowFrame(newFrame);
376
377 #if !PLATFORM(GTK)
378     // If nothing changed at all, it's probably fair to report that something went wrong.
379     // (We can't assume that the requested frame size will be honored exactly, however.)
380     WebCore::FloatRect updatedFrame;
381     page->getWindowFrame(updatedFrame);
382     if (originalFrame == updatedFrame)
383         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The window position was expected to have changed, but did not.");
384 #endif
385 #endif
386 }
387
388 void WebAutomationSession::waitForNavigationToComplete(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<WaitForNavigationToCompleteCallback>&& callback)
389 {
390     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
391     if (!page)
392         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
393
394     // FIXME: Implement page load strategy.
395
396     Seconds pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
397
398     if (optionalFrameHandle && !optionalFrameHandle->isEmpty()) {
399         std::optional<uint64_t> frameID = webFrameIDForHandle(*optionalFrameHandle);
400         if (!frameID)
401             FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
402         WebFrameProxy* frame = page->process().webFrame(frameID.value());
403         if (!frame)
404             FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
405         waitForNavigationToCompleteOnFrame(*frame, pageLoadTimeout, WTFMove(callback));
406     } else
407         waitForNavigationToCompleteOnPage(*page, pageLoadTimeout, WTFMove(callback));
408 }
409
410 void WebAutomationSession::waitForNavigationToCompleteOnPage(WebPageProxy& page, Seconds timeout, Ref<Inspector::BackendDispatcher::CallbackBase>&& callback)
411 {
412     ASSERT(!m_loadTimer.isActive());
413     if (!page.pageLoadState().isLoading()) {
414         callback->sendSuccess(InspectorObject::create());
415         return;
416     }
417
418     m_loadTimer.startOneShot(timeout);
419     m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page.pageID(), WTFMove(callback));
420 }
421
422 void WebAutomationSession::waitForNavigationToCompleteOnFrame(WebFrameProxy& frame, Seconds timeout, Ref<Inspector::BackendDispatcher::CallbackBase>&& callback)
423 {
424     ASSERT(!m_loadTimer.isActive());
425     if (frame.frameLoadState().state() == FrameLoadState::State::Finished) {
426         callback->sendSuccess(InspectorObject::create());
427         return;
428     }
429
430     m_loadTimer.startOneShot(timeout);
431     m_pendingNavigationInBrowsingContextCallbacksPerFrame.set(frame.frameID(), WTFMove(callback));
432 }
433
434 void WebAutomationSession::loadTimerFired()
435 {
436     for (auto frameID : m_pendingNavigationInBrowsingContextCallbacksPerFrame.keys()) {
437         auto callback = m_pendingNavigationInBrowsingContextCallbacksPerFrame.take(frameID);
438         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
439     }
440     for (auto pageID : m_pendingNavigationInBrowsingContextCallbacksPerPage.keys()) {
441         auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(pageID);
442         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
443     }
444 }
445
446 void WebAutomationSession::navigateBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String& url, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<NavigateBrowsingContextCallback>&& callback)
447 {
448     WebPageProxy* page = webPageProxyForHandle(handle);
449     if (!page)
450         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
451
452     // FIXME: Implement page load strategy.
453
454     page->loadRequest(WebCore::URL(WebCore::URL(), url));
455     waitForNavigationToCompleteOnPage(*page, optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout, WTFMove(callback));
456 }
457
458 void WebAutomationSession::goBackInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<GoBackInBrowsingContextCallback>&& callback)
459 {
460     WebPageProxy* page = webPageProxyForHandle(handle);
461     if (!page)
462         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
463
464     // FIXME: Implement page load strategy.
465
466     page->goBack();
467     waitForNavigationToCompleteOnPage(*page, optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout, WTFMove(callback));
468 }
469
470 void WebAutomationSession::goForwardInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<GoForwardInBrowsingContextCallback>&& callback)
471 {
472     WebPageProxy* page = webPageProxyForHandle(handle);
473     if (!page)
474         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
475
476     // FIXME: Implement page load strategy.
477
478     page->goForward();
479     waitForNavigationToCompleteOnPage(*page, optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout, WTFMove(callback));
480 }
481
482 void WebAutomationSession::reloadBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<ReloadBrowsingContextCallback>&& callback)
483 {
484     WebPageProxy* page = webPageProxyForHandle(handle);
485     if (!page)
486         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
487
488     // FIXME: Implement page load strategy.
489
490     page->reload({ });
491     waitForNavigationToCompleteOnPage(*page, optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout, WTFMove(callback));
492 }
493
494 void WebAutomationSession::navigationOccurredForFrame(const WebFrameProxy& frame)
495 {
496     if (frame.isMainFrame()) {
497         // New page loaded, clear frame handles previously cached.
498         m_handleWebFrameMap.clear();
499         m_webFrameHandleMap.clear();
500         if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(frame.page()->pageID())) {
501             m_loadTimer.stop();
502             callback->sendSuccess(InspectorObject::create());
503         }
504         m_domainNotifier->browsingContextCleared(handleForWebPageProxy(*frame.page()));
505     } else {
506         if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerFrame.take(frame.frameID())) {
507             m_loadTimer.stop();
508             callback->sendSuccess(InspectorObject::create());
509         }
510     }
511 }
512
513 void WebAutomationSession::inspectorFrontendLoaded(const WebPageProxy& page)
514 {
515     if (auto callback = m_pendingInspectorCallbacksPerPage.take(page.pageID()))
516         callback->sendSuccess(InspectorObject::create());
517 }
518
519 void WebAutomationSession::keyboardEventsFlushedForPage(const WebPageProxy& page)
520 {
521     if (auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID()))
522         callback->sendSuccess(InspectorObject::create());
523 }
524
525 void WebAutomationSession::willClosePage(const WebPageProxy& page)
526 {
527     String handle = handleForWebPageProxy(page);
528     m_domainNotifier->browsingContextCleared(handle);
529 }
530
531 static bool fileCanBeAcceptedForUpload(const String& filename, const HashSet<String>& allowedMIMETypes, const HashSet<String>& allowedFileExtensions)
532 {
533     if (!WebCore::fileExists(filename))
534         return false;
535
536     if (allowedMIMETypes.isEmpty() && allowedFileExtensions.isEmpty())
537         return true;
538
539     // We can't infer a MIME type from a file without an extension, just give up.
540     auto dotOffset = filename.reverseFind('.');
541     if (dotOffset == notFound)
542         return false;
543
544     String extension = filename.substring(dotOffset + 1).convertToASCIILowercase();
545     if (extension.isEmpty())
546         return false;
547
548     if (allowedFileExtensions.contains(extension))
549         return true;
550
551     String mappedMIMEType = WebCore::MIMETypeRegistry::getMIMETypeForExtension(extension).convertToASCIILowercase();
552     if (mappedMIMEType.isEmpty())
553         return false;
554     
555     if (allowedMIMETypes.contains(mappedMIMEType))
556         return true;
557
558     // Fall back to checking for a MIME type wildcard if an exact match is not found.
559     Vector<String> components;
560     mappedMIMEType.split('/', false, components);
561     if (components.size() != 2)
562         return false;
563
564     String wildcardedMIMEType = makeString(components[0], "/*");
565     if (allowedMIMETypes.contains(wildcardedMIMEType))
566         return true;
567
568     return false;
569 }
570
571 void WebAutomationSession::handleRunOpenPanel(const WebPageProxy& page, const WebFrameProxy&, const API::OpenPanelParameters& parameters, WebOpenPanelResultListenerProxy& resultListener)
572 {
573     if (!m_filesToSelectForFileUpload.size()) {
574         resultListener.cancel();
575         m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
576         return;
577     }
578
579     if (m_filesToSelectForFileUpload.size() > 1 && !parameters.allowMultipleFiles()) {
580         resultListener.cancel();
581         m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
582         return;
583     }
584
585     HashSet<String> allowedMIMETypes;
586     auto acceptMIMETypes = parameters.acceptMIMETypes();
587     for (auto type : acceptMIMETypes->elementsOfType<API::String>())
588         allowedMIMETypes.add(type->string());
589
590     HashSet<String> allowedFileExtensions;
591     auto acceptFileExtensions = parameters.acceptFileExtensions();
592     for (auto type : acceptFileExtensions->elementsOfType<API::String>()) {
593         // WebCore vends extensions with leading periods. Strip these to simplify matching later.
594         String extension = type->string();
595         ASSERT(extension.characterAt(0) == '.');
596         allowedFileExtensions.add(extension.substring(1));
597     }
598
599     // Per §14.3.10.5 in the W3C spec, if at least one file cannot be accepted, the command should fail.
600     // The REST API service can tell that this failed by checking the "files" attribute of the input element.
601     for (const String& filename : m_filesToSelectForFileUpload) {
602         if (!fileCanBeAcceptedForUpload(filename, allowedMIMETypes, allowedFileExtensions)) {
603             resultListener.cancel();
604             m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
605             return;
606         }
607     }
608
609     resultListener.chooseFiles(m_filesToSelectForFileUpload);
610     m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, false);
611 }
612
613 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)
614 {
615     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
616     if (!page)
617         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
618
619     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
620     if (!frameID)
621         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
622
623     Vector<String> argumentsVector;
624     argumentsVector.reserveCapacity(arguments.length());
625
626     for (auto& argument : arguments) {
627         String argumentString;
628         argument->asString(argumentString);
629         argumentsVector.uncheckedAppend(argumentString);
630     }
631
632     bool expectsImplicitCallbackArgument = optionalExpectsImplicitCallbackArgument ? *optionalExpectsImplicitCallbackArgument : false;
633     int callbackTimeout = optionalCallbackTimeout ? *optionalCallbackTimeout : 0;
634
635     uint64_t callbackID = m_nextEvaluateJavaScriptCallbackID++;
636     m_evaluateJavaScriptFunctionCallbacks.set(callbackID, WTFMove(callback));
637
638     page->process().send(Messages::WebAutomationSessionProxy::EvaluateJavaScriptFunction(page->pageID(), frameID.value(), function, argumentsVector, expectsImplicitCallbackArgument, callbackTimeout, callbackID), 0);
639 }
640
641 void WebAutomationSession::didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType)
642 {
643     auto callback = m_evaluateJavaScriptFunctionCallbacks.take(callbackID);
644     if (!callback)
645         return;
646
647     if (!errorType.isEmpty())
648         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE_AND_DETAILS(errorType, result));
649     else
650         callback->sendSuccess(result);
651 }
652
653 void WebAutomationSession::resolveChildFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&& callback)
654 {
655     if (!optionalOrdinal && !optionalName && !optionalNodeHandle)
656         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "Command must specify a child frame by ordinal, name, or element handle.");
657
658     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
659     if (!page)
660         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
661
662     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
663     if (!frameID)
664         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
665
666     uint64_t callbackID = m_nextResolveFrameCallbackID++;
667     m_resolveChildFrameHandleCallbacks.set(callbackID, WTFMove(callback));
668
669     if (optionalNodeHandle) {
670         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithNodeHandle(page->pageID(), frameID.value(), *optionalNodeHandle, callbackID), 0);
671         return;
672     }
673
674     if (optionalName) {
675         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithName(page->pageID(), frameID.value(), *optionalName, callbackID), 0);
676         return;
677     }
678
679     if (optionalOrdinal) {
680         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithOrdinal(page->pageID(), frameID.value(), *optionalOrdinal, callbackID), 0);
681         return;
682     }
683
684     ASSERT_NOT_REACHED();
685 }
686
687 void WebAutomationSession::didResolveChildFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
688 {
689     auto callback = m_resolveChildFrameHandleCallbacks.take(callbackID);
690     if (!callback)
691         return;
692
693     if (!errorType.isEmpty())
694         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
695     else
696         callback->sendSuccess(handleForWebFrameID(frameID));
697 }
698
699 void WebAutomationSession::resolveParentFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&& callback)
700 {
701     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
702     if (!page)
703         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
704
705     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
706     if (!frameID)
707         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
708
709     uint64_t callbackID = m_nextResolveParentFrameCallbackID++;
710     m_resolveParentFrameHandleCallbacks.set(callbackID, WTFMove(callback));
711
712     page->process().send(Messages::WebAutomationSessionProxy::ResolveParentFrame(page->pageID(), frameID.value(), callbackID), 0);
713 }
714
715 void WebAutomationSession::didResolveParentFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
716 {
717     auto callback = m_resolveParentFrameHandleCallbacks.take(callbackID);
718     if (!callback)
719         return;
720
721     if (!errorType.isEmpty())
722         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
723     else
724         callback->sendSuccess(handleForWebFrameID(frameID));
725 }
726
727 void WebAutomationSession::computeElementLayout(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* optionalUseViewportCoordinates, Ref<ComputeElementLayoutCallback>&& callback)
728 {
729     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
730     if (!page)
731         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
732
733     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
734     if (!frameID)
735         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
736
737     uint64_t callbackID = m_nextComputeElementLayoutCallbackID++;
738     m_computeElementLayoutCallbacks.set(callbackID, WTFMove(callback));
739
740     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
741     bool useViewportCoordinates = optionalUseViewportCoordinates ? *optionalUseViewportCoordinates : false;
742
743     page->process().send(Messages::WebAutomationSessionProxy::ComputeElementLayout(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, useViewportCoordinates, callbackID), 0);
744 }
745
746 void WebAutomationSession::didComputeElementLayout(uint64_t callbackID, WebCore::IntRect rect, std::optional<WebCore::IntPoint> inViewCenterPoint, bool isObscured, const String& errorType)
747 {
748     auto callback = m_computeElementLayoutCallbacks.take(callbackID);
749     if (!callback)
750         return;
751
752     if (!errorType.isEmpty()) {
753         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
754         return;
755     }
756
757     auto originObject = Inspector::Protocol::Automation::Point::create()
758         .setX(rect.x())
759         .setY(rect.y())
760         .release();
761
762     auto sizeObject = Inspector::Protocol::Automation::Size::create()
763         .setWidth(rect.width())
764         .setHeight(rect.height())
765         .release();
766
767     auto rectObject = Inspector::Protocol::Automation::Rect::create()
768         .setOrigin(WTFMove(originObject))
769         .setSize(WTFMove(sizeObject))
770         .release();
771
772     if (!inViewCenterPoint) {
773         callback->sendSuccess(WTFMove(rectObject), nullptr, isObscured);
774         return;
775     }
776
777     auto inViewCenterPointObject = Inspector::Protocol::Automation::Point::create()
778         .setX(inViewCenterPoint.value().x())
779         .setY(inViewCenterPoint.value().y())
780         .release();
781
782     callback->sendSuccess(WTFMove(rectObject), WTFMove(inViewCenterPointObject), isObscured);
783 }
784
785 void WebAutomationSession::isShowingJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, bool* result)
786 {
787     ASSERT(m_client);
788     if (!m_client)
789         FAIL_WITH_PREDEFINED_ERROR(InternalError);
790
791     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
792     if (!page)
793         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
794
795     *result = m_client->isShowingJavaScriptDialogOnPage(*this, *page);
796 }
797
798 void WebAutomationSession::dismissCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
799 {
800     ASSERT(m_client);
801     if (!m_client)
802         FAIL_WITH_PREDEFINED_ERROR(InternalError);
803
804     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
805     if (!page)
806         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
807
808     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
809         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
810
811     m_client->dismissCurrentJavaScriptDialogOnPage(*this, *page);
812 }
813
814 void WebAutomationSession::acceptCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
815 {
816     ASSERT(m_client);
817     if (!m_client)
818         FAIL_WITH_PREDEFINED_ERROR(InternalError);
819
820     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
821     if (!page)
822         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
823
824     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
825         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
826
827     m_client->acceptCurrentJavaScriptDialogOnPage(*this, *page);
828 }
829
830 void WebAutomationSession::messageOfCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, String* text)
831 {
832     ASSERT(m_client);
833     if (!m_client)
834         FAIL_WITH_PREDEFINED_ERROR(InternalError);
835
836     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
837     if (!page)
838         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
839
840     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
841         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
842
843     *text = m_client->messageOfCurrentJavaScriptDialogOnPage(*this, *page);
844 }
845
846 void WebAutomationSession::setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& promptValue)
847 {
848     ASSERT(m_client);
849     if (!m_client)
850         FAIL_WITH_PREDEFINED_ERROR(InternalError);
851
852     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
853     if (!page)
854         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
855
856     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
857         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
858
859     m_client->setUserInputForCurrentJavaScriptPromptOnPage(*this, *page, promptValue);
860 }
861
862 void WebAutomationSession::setFilesToSelectForFileUpload(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorArray& filenames)
863 {
864     Vector<String> newFileList;
865     newFileList.reserveInitialCapacity(filenames.length());
866
867     for (auto item : filenames) {
868         String filename;
869         if (!item->asString(filename))
870             FAIL_WITH_PREDEFINED_ERROR(InternalError);
871
872         newFileList.append(filename);
873     }
874
875     m_filesToSelectForFileUpload.swap(newFileList);
876 }
877
878 void WebAutomationSession::getAllCookies(ErrorString& errorString, const String& browsingContextHandle, Ref<GetAllCookiesCallback>&& callback)
879 {
880     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
881     if (!page)
882         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
883
884     // 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.
885     const uint64_t mainFrameID = 0;
886
887     uint64_t callbackID = m_nextGetCookiesCallbackID++;
888     m_getCookieCallbacks.set(callbackID, WTFMove(callback));
889
890     page->process().send(Messages::WebAutomationSessionProxy::GetCookiesForFrame(page->pageID(), mainFrameID, callbackID), 0);
891 }
892
893 static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
894 {
895     return Inspector::Protocol::Automation::Cookie::create()
896         .setName(cookie.name)
897         .setValue(cookie.value)
898         .setDomain(cookie.domain)
899         .setPath(cookie.path)
900         .setExpires(cookie.expires)
901         .setSize((cookie.name.length() + cookie.value.length()))
902         .setHttpOnly(cookie.httpOnly)
903         .setSecure(cookie.secure)
904         .setSession(cookie.session)
905         .release();
906 }
907
908 static Ref<Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>> buildArrayForCookies(Vector<WebCore::Cookie>& cookiesList)
909 {
910     auto cookies = Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>::create();
911
912     for (const auto& cookie : cookiesList)
913         cookies->addItem(buildObjectForCookie(cookie));
914
915     return cookies;
916 }
917
918 void WebAutomationSession::didGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie> cookies, const String& errorType)
919 {
920     auto callback = m_getCookieCallbacks.take(callbackID);
921     if (!callback)
922         return;
923
924     if (!errorType.isEmpty()) {
925         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
926         return;
927     }
928
929     callback->sendSuccess(buildArrayForCookies(cookies));
930 }
931
932 void WebAutomationSession::deleteSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&& callback)
933 {
934     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
935     if (!page)
936         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
937
938     // 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.
939     const uint64_t mainFrameID = 0;
940
941     uint64_t callbackID = m_nextDeleteCookieCallbackID++;
942     m_deleteCookieCallbacks.set(callbackID, WTFMove(callback));
943
944     page->process().send(Messages::WebAutomationSessionProxy::DeleteCookie(page->pageID(), mainFrameID, cookieName, callbackID), 0);
945 }
946
947 void WebAutomationSession::didDeleteCookie(uint64_t callbackID, const String& errorType)
948 {
949     auto callback = m_deleteCookieCallbacks.take(callbackID);
950     if (!callback)
951         return;
952
953     if (!errorType.isEmpty()) {
954         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
955         return;
956     }
957
958     callback->sendSuccess();
959 }
960
961 void WebAutomationSession::addSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorObject& cookieObject, Ref<AddSingleCookieCallback>&& callback)
962 {
963     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
964     if (!page)
965         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
966
967     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
968     ASSERT(activeURL.isValid());
969
970     WebCore::Cookie cookie;
971
972     if (!cookieObject.getString(WTF::ASCIILiteral("name"), cookie.name))
973         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'name' was not found.");
974
975     if (!cookieObject.getString(WTF::ASCIILiteral("value"), cookie.value))
976         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'value' was not found.");
977
978     String domain;
979     if (!cookieObject.getString(WTF::ASCIILiteral("domain"), domain))
980         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'domain' was not found.");
981
982     // Inherit the domain/host from the main frame's URL if it is not explicitly set.
983     if (domain.isEmpty())
984         domain = activeURL.host();
985
986     cookie.domain = domain;
987
988     if (!cookieObject.getString(WTF::ASCIILiteral("path"), cookie.path))
989         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'path' was not found.");
990
991     double expires;
992     if (!cookieObject.getDouble(WTF::ASCIILiteral("expires"), expires))
993         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'expires' was not found.");
994
995     cookie.expires = expires * 1000.0;
996
997     if (!cookieObject.getBoolean(WTF::ASCIILiteral("secure"), cookie.secure))
998         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'secure' was not found.");
999
1000     if (!cookieObject.getBoolean(WTF::ASCIILiteral("session"), cookie.session))
1001         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'session' was not found.");
1002
1003     if (!cookieObject.getBoolean(WTF::ASCIILiteral("httpOnly"), cookie.httpOnly))
1004         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'httpOnly' was not found.");
1005
1006     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1007
1008     // FIXME: Using activeURL here twice is basically saying "this is always in the context of the main document"
1009     // which probably isn't accurate.
1010     cookieManager->setCookies(page->websiteDataStore().sessionID(), { cookie }, activeURL, activeURL, [callback = callback.copyRef()](CallbackBase::Error error) {
1011         if (error == CallbackBase::Error::None)
1012             callback->sendSuccess();
1013         else
1014             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1015     });
1016 }
1017
1018 void WebAutomationSession::deleteAllCookies(ErrorString& errorString, const String& browsingContextHandle)
1019 {
1020     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1021     if (!page)
1022         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1023
1024     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
1025     ASSERT(activeURL.isValid());
1026
1027     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1028     cookieManager->deleteCookiesForHostname(page->websiteDataStore().sessionID(), activeURL.host());
1029 }
1030
1031 #if USE(APPKIT) || PLATFORM(GTK)
1032 static WebEvent::Modifiers protocolModifierToWebEventModifier(Inspector::Protocol::Automation::KeyModifier modifier)
1033 {
1034     switch (modifier) {
1035     case Inspector::Protocol::Automation::KeyModifier::Alt:
1036         return WebEvent::AltKey;
1037     case Inspector::Protocol::Automation::KeyModifier::Meta:
1038         return WebEvent::MetaKey;
1039     case Inspector::Protocol::Automation::KeyModifier::Control:
1040         return WebEvent::ControlKey;
1041     case Inspector::Protocol::Automation::KeyModifier::Shift:
1042         return WebEvent::ShiftKey;
1043     case Inspector::Protocol::Automation::KeyModifier::CapsLock:
1044         return WebEvent::CapsLockKey;
1045     }
1046
1047     RELEASE_ASSERT_NOT_REACHED();
1048 }
1049 #endif // USE(APPKIT)
1050
1051 void WebAutomationSession::performMouseInteraction(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& requestedPositionObject, const String& mouseButtonString, const String& mouseInteractionString, const Inspector::InspectorArray& keyModifierStrings, RefPtr<Inspector::Protocol::Automation::Point>& updatedPositionObject)
1052 {
1053 #if !USE(APPKIT) && !PLATFORM(GTK)
1054     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1055 #else
1056     WebPageProxy* page = webPageProxyForHandle(handle);
1057     if (!page)
1058         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1059
1060     float x;
1061     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("x"), x))
1062         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'x' was not found.");
1063
1064     float y;
1065     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("y"), y))
1066         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'y' was not found.");
1067
1068     WebCore::FloatRect windowFrame;
1069     page->getWindowFrame(windowFrame);
1070
1071     x = std::min(std::max(0.0f, x), windowFrame.size().width());
1072     y = std::min(std::max(0.0f, y + page->topContentInset()), windowFrame.size().height());
1073
1074     WebCore::IntPoint viewPosition = WebCore::IntPoint(static_cast<int>(x), static_cast<int>(y));
1075
1076     auto parsedInteraction = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseInteraction>(mouseInteractionString);
1077     if (!parsedInteraction)
1078         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interaction' is invalid.");
1079
1080     auto parsedButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(mouseButtonString);
1081     if (!parsedButton)
1082         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'button' is invalid.");
1083
1084     WebEvent::Modifiers keyModifiers = (WebEvent::Modifiers)0;
1085     for (auto it = keyModifierStrings.begin(); it != keyModifierStrings.end(); ++it) {
1086         String modifierString;
1087         if (!it->get()->asString(modifierString))
1088             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'modifiers' is invalid.");
1089
1090         auto parsedModifier = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyModifier>(modifierString);
1091         if (!parsedModifier)
1092             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A modifier in the 'modifiers' array is invalid.");
1093         WebEvent::Modifiers enumValue = protocolModifierToWebEventModifier(parsedModifier.value());
1094         keyModifiers = (WebEvent::Modifiers)(enumValue | keyModifiers);
1095     }
1096
1097     platformSimulateMouseInteraction(*page, viewPosition, parsedInteraction.value(), parsedButton.value(), keyModifiers);
1098
1099     updatedPositionObject = Inspector::Protocol::Automation::Point::create()
1100         .setX(x)
1101         .setY(y - page->topContentInset())
1102         .release();
1103 #endif // USE(APPKIT)
1104 }
1105
1106 void WebAutomationSession::performKeyboardInteractions(ErrorString& errorString, const String& handle, const Inspector::InspectorArray& interactions, Ref<PerformKeyboardInteractionsCallback>&& callback)
1107 {
1108 #if !PLATFORM(COCOA) && !PLATFORM(GTK)
1109     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1110 #else
1111     WebPageProxy* page = webPageProxyForHandle(handle);
1112     if (!page)
1113         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1114
1115     if (!interactions.length())
1116         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interactions' was not found or empty.");
1117
1118     // Validate all of the parameters before performing any interactions with the browsing context under test.
1119     Vector<WTF::Function<void()>> actionsToPerform;
1120     actionsToPerform.reserveCapacity(interactions.length());
1121
1122     for (auto interaction : interactions) {
1123         RefPtr<InspectorObject> interactionObject;
1124         if (!interaction->asObject(interactionObject))
1125             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter was invalid.");
1126
1127         String interactionTypeString;
1128         if (!interactionObject->getString(ASCIILiteral("type"), interactionTypeString))
1129             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter is missing the 'type' key.");
1130         auto interactionType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyboardInteractionType>(interactionTypeString);
1131         if (!interactionType)
1132             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'type' key.");
1133
1134         String virtualKeyString;
1135         bool foundVirtualKey = interactionObject->getString(ASCIILiteral("key"), virtualKeyString);
1136         if (foundVirtualKey) {
1137             auto virtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(virtualKeyString);
1138             if (!virtualKey)
1139                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1140
1141             actionsToPerform.uncheckedAppend([this, page, interactionType, virtualKey] {
1142                 platformSimulateKeyStroke(*page, interactionType.value(), virtualKey.value());
1143             });
1144         }
1145
1146         String keySequence;
1147         bool foundKeySequence = interactionObject->getString(ASCIILiteral("text"), keySequence);
1148         if (foundKeySequence) {
1149             switch (interactionType.value()) {
1150             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress:
1151             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease:
1152                 // 'KeyPress' and 'KeyRelease' are meant for a virtual key and are not supported for a string (sequence of codepoints).
1153                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1154
1155             case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey:
1156                 actionsToPerform.uncheckedAppend([this, page, keySequence] {
1157                     platformSimulateKeySequence(*page, keySequence);
1158                 });
1159                 break;
1160             }
1161         }
1162
1163         if (!foundVirtualKey && !foundKeySequence)
1164             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "An interaction in the 'interactions' parameter is missing both 'key' and 'text'. One must be provided.");
1165     }
1166
1167     ASSERT(actionsToPerform.size());
1168     if (!actionsToPerform.size())
1169         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "No actions to perform.");
1170
1171     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1172     if (callbackInMap)
1173         callbackInMap->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
1174     callbackInMap = WTFMove(callback);
1175
1176     for (auto& action : actionsToPerform)
1177         action();
1178 #endif // PLATFORM(COCOA)
1179 }
1180
1181 void WebAutomationSession::takeScreenshot(ErrorString& errorString, const String& handle, Ref<TakeScreenshotCallback>&& callback)
1182 {
1183     WebPageProxy* page = webPageProxyForHandle(handle);
1184     if (!page)
1185         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1186
1187     uint64_t callbackID = m_nextScreenshotCallbackID++;
1188     m_screenshotCallbacks.set(callbackID, WTFMove(callback));
1189
1190     page->process().send(Messages::WebAutomationSessionProxy::TakeScreenshot(page->pageID(), callbackID), 0);
1191 }
1192
1193 void WebAutomationSession::didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle& imageDataHandle, const String& errorType)
1194 {
1195     auto callback = m_screenshotCallbacks.take(callbackID);
1196     if (!callback)
1197         return;
1198
1199     if (!errorType.isEmpty()) {
1200         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1201         return;
1202     }
1203
1204     std::optional<String> base64EncodedData = platformGetBase64EncodedPNGData(imageDataHandle);
1205     if (!base64EncodedData) {
1206         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1207         return;
1208     }
1209
1210     callback->sendSuccess(base64EncodedData.value());
1211 }
1212
1213 // Platform-dependent Implementation Stubs.
1214
1215 #if !PLATFORM(MAC) && !PLATFORM(GTK)
1216 void WebAutomationSession::platformSimulateMouseInteraction(WebKit::WebPageProxy&, const WebCore::IntPoint&, Inspector::Protocol::Automation::MouseInteraction, Inspector::Protocol::Automation::MouseButton, WebEvent::Modifiers)
1217 {
1218 }
1219 #endif // !PLATFORM(MAC)
1220
1221 #if !PLATFORM(COCOA) && !PLATFORM(GTK)
1222 void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy&, Inspector::Protocol::Automation::KeyboardInteractionType, Inspector::Protocol::Automation::VirtualKey)
1223 {
1224 }
1225
1226 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy&, const String&)
1227 {
1228 }
1229
1230 std::optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&)
1231 {
1232     return String();
1233 }
1234 #endif // !PLATFORM(COCOA)
1235
1236 } // namespace WebKit