c25b9517717948b3abcbc0305df7365c29850fa7
[WebKit-https.git] / Source / WebKit / UIProcess / Automation / WebAutomationSession.cpp
1
2 /*
3  * Copyright (C) 2016, 2017 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "WebAutomationSession.h"
29
30 #include "APIArray.h"
31 #include "APIAutomationSessionClient.h"
32 #include "APINavigation.h"
33 #include "APIOpenPanelParameters.h"
34 #include "AutomationProtocolObjects.h"
35 #include "CoordinateSystem.h"
36 #include "WebAutomationSessionMacros.h"
37 #include "WebAutomationSessionMessages.h"
38 #include "WebAutomationSessionProxyMessages.h"
39 #include "WebCookieManagerProxy.h"
40 #include "WebFullScreenManagerProxy.h"
41 #include "WebInspectorProxy.h"
42 #include "WebOpenPanelResultListenerProxy.h"
43 #include "WebProcessPool.h"
44 #include <JavaScriptCore/InspectorBackendDispatcher.h>
45 #include <JavaScriptCore/InspectorFrontendRouter.h>
46 #include <WebCore/MIMETypeRegistry.h>
47 #include <algorithm>
48 #include <wtf/HashMap.h>
49 #include <wtf/Optional.h>
50 #include <wtf/URL.h>
51 #include <wtf/UUID.h>
52 #include <wtf/text/StringConcatenate.h>
53
54 namespace WebKit {
55
56 using namespace Inspector;
57
58 String AutomationCommandError::toProtocolString()
59 {
60     String protocolErrorName = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(type);
61     if (!message.hasValue())
62         return protocolErrorName;
63
64     return makeString(protocolErrorName, errorNameAndDetailsSeparator, message.value());
65 }
66     
67 // §8. Sessions
68 // https://www.w3.org/TR/webdriver/#dfn-session-page-load-timeout
69 static const Seconds defaultPageLoadTimeout = 300_s;
70 // https://www.w3.org/TR/webdriver/#dfn-page-loading-strategy
71 static const Inspector::Protocol::Automation::PageLoadStrategy defaultPageLoadStrategy = Inspector::Protocol::Automation::PageLoadStrategy::Normal;
72
73 WebAutomationSession::WebAutomationSession()
74     : m_client(std::make_unique<API::AutomationSessionClient>())
75     , m_frontendRouter(FrontendRouter::create())
76     , m_backendDispatcher(BackendDispatcher::create(m_frontendRouter.copyRef()))
77     , m_domainDispatcher(AutomationBackendDispatcher::create(m_backendDispatcher, this))
78     , m_domainNotifier(std::make_unique<AutomationFrontendDispatcher>(m_frontendRouter))
79     , m_loadTimer(RunLoop::main(), this, &WebAutomationSession::loadTimerFired)
80 {
81 #if ENABLE(WEBDRIVER_ACTIONS_API)
82     // Set up canonical input sources to be used for 'performInteractionSequence' and 'cancelInteractionSequence'.
83 #if ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
84     m_inputSources.add(SimulatedInputSource::create(SimulatedInputSourceType::Touch));
85 #endif
86 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
87     m_inputSources.add(SimulatedInputSource::create(SimulatedInputSourceType::Mouse));
88 #endif
89 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
90     m_inputSources.add(SimulatedInputSource::create(SimulatedInputSourceType::Keyboard));
91 #endif
92     m_inputSources.add(SimulatedInputSource::create(SimulatedInputSourceType::Null));
93 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
94 }
95
96 WebAutomationSession::~WebAutomationSession()
97 {
98     ASSERT(!m_client);
99     ASSERT(!m_processPool);
100 }
101
102 void WebAutomationSession::setClient(std::unique_ptr<API::AutomationSessionClient>&& client)
103 {
104     m_client = WTFMove(client);
105 }
106
107 void WebAutomationSession::setProcessPool(WebKit::WebProcessPool* processPool)
108 {
109     if (m_processPool)
110         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
111
112     m_processPool = processPool;
113
114     if (m_processPool)
115         m_processPool->addMessageReceiver(Messages::WebAutomationSession::messageReceiverName(), *this);
116 }
117
118 // NOTE: this class could be split at some point to support local and remote automation sessions.
119 // For now, it only works with a remote automation driver over a RemoteInspector connection.
120
121 #if ENABLE(REMOTE_INSPECTOR)
122
123 // Inspector::RemoteAutomationTarget API
124
125 void WebAutomationSession::dispatchMessageFromRemote(const String& message)
126 {
127     m_backendDispatcher->dispatch(message);
128 }
129
130 void WebAutomationSession::connect(Inspector::FrontendChannel& channel, bool isAutomaticConnection, bool immediatelyPause)
131 {
132     UNUSED_PARAM(isAutomaticConnection);
133     UNUSED_PARAM(immediatelyPause);
134
135     m_remoteChannel = &channel;
136     m_frontendRouter->connectFrontend(channel);
137
138     setIsPaired(true);
139 }
140
141 void WebAutomationSession::disconnect(Inspector::FrontendChannel& channel)
142 {
143     ASSERT(&channel == m_remoteChannel);
144     terminate();
145 }
146
147 #endif // ENABLE(REMOTE_INSPECTOR)
148
149 void WebAutomationSession::terminate()
150 {
151 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
152     for (auto& identifier : copyToVector(m_pendingKeyboardEventsFlushedCallbacksPerPage.keys())) {
153         auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(identifier);
154         callback(AUTOMATION_COMMAND_ERROR_WITH_NAME(InternalError));
155     }
156 #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
157
158 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
159     for (auto& identifier : copyToVector(m_pendingMouseEventsFlushedCallbacksPerPage.keys())) {
160         auto callback = m_pendingMouseEventsFlushedCallbacksPerPage.take(identifier);
161         callback(AUTOMATION_COMMAND_ERROR_WITH_NAME(InternalError));
162     }
163 #endif // ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
164
165 #if ENABLE(REMOTE_INSPECTOR)
166     if (Inspector::FrontendChannel* channel = m_remoteChannel) {
167         m_remoteChannel = nullptr;
168         m_frontendRouter->disconnectFrontend(*channel);
169     }
170
171     setIsPaired(false);
172 #endif
173
174     if (m_client)
175         m_client->didDisconnectFromRemote(*this);
176 }
177
178 WebPageProxy* WebAutomationSession::webPageProxyForHandle(const String& handle)
179 {
180     auto iter = m_handleWebPageMap.find(handle);
181     if (iter == m_handleWebPageMap.end())
182         return nullptr;
183     return WebProcessProxy::webPage(iter->value);
184 }
185
186 String WebAutomationSession::handleForWebPageProxy(const WebPageProxy& webPageProxy)
187 {
188     auto iter = m_webPageHandleMap.find(webPageProxy.pageID());
189     if (iter != m_webPageHandleMap.end())
190         return iter->value;
191
192     String handle = "page-" + createCanonicalUUIDString().convertToASCIIUppercase();
193
194     auto firstAddResult = m_webPageHandleMap.add(webPageProxy.pageID(), handle);
195     RELEASE_ASSERT(firstAddResult.isNewEntry);
196
197     auto secondAddResult = m_handleWebPageMap.add(handle, webPageProxy.pageID());
198     RELEASE_ASSERT(secondAddResult.isNewEntry);
199
200     return handle;
201 }
202
203 Optional<uint64_t> WebAutomationSession::webFrameIDForHandle(const String& handle)
204 {
205     if (handle.isEmpty())
206         return 0;
207
208     auto iter = m_handleWebFrameMap.find(handle);
209     if (iter == m_handleWebFrameMap.end())
210         return WTF::nullopt;
211
212     return iter->value;
213 }
214
215 String WebAutomationSession::handleForWebFrameID(uint64_t frameID)
216 {
217     if (!frameID)
218         return emptyString();
219
220     for (auto& process : m_processPool->processes()) {
221         if (WebFrameProxy* frame = process->webFrame(frameID)) {
222             if (frame->isMainFrame())
223                 return emptyString();
224             break;
225         }
226     }
227
228     auto iter = m_webFrameHandleMap.find(frameID);
229     if (iter != m_webFrameHandleMap.end())
230         return iter->value;
231
232     String handle = "frame-" + createCanonicalUUIDString().convertToASCIIUppercase();
233
234     auto firstAddResult = m_webFrameHandleMap.add(frameID, handle);
235     RELEASE_ASSERT(firstAddResult.isNewEntry);
236
237     auto secondAddResult = m_handleWebFrameMap.add(handle, frameID);
238     RELEASE_ASSERT(secondAddResult.isNewEntry);
239
240     return handle;
241 }
242
243 String WebAutomationSession::handleForWebFrameProxy(const WebFrameProxy& webFrameProxy)
244 {
245     return handleForWebFrameID(webFrameProxy.frameID());
246 }
247
248 Ref<Inspector::Protocol::Automation::BrowsingContext> WebAutomationSession::buildBrowsingContextForPage(WebPageProxy& page, WebCore::FloatRect windowFrame)
249 {
250     auto originObject = Inspector::Protocol::Automation::Point::create()
251         .setX(windowFrame.x())
252         .setY(windowFrame.y())
253         .release();
254
255     auto sizeObject = Inspector::Protocol::Automation::Size::create()
256         .setWidth(windowFrame.width())
257         .setHeight(windowFrame.height())
258         .release();
259
260     bool isActive = page.isViewVisible() && page.isViewFocused() && page.isViewWindowActive();
261     String handle = handleForWebPageProxy(page);
262
263     return Inspector::Protocol::Automation::BrowsingContext::create()
264         .setHandle(handle)
265         .setActive(isActive)
266         .setUrl(page.pageLoadState().activeURL())
267         .setWindowOrigin(WTFMove(originObject))
268         .setWindowSize(WTFMove(sizeObject))
269         .release();
270 }
271
272 // Platform-independent Commands.
273
274 void WebAutomationSession::getNextContext(Ref<WebAutomationSession>&& protectedThis, Vector<Ref<WebPageProxy>>&& pages, Ref<JSON::ArrayOf<Inspector::Protocol::Automation::BrowsingContext>> contexts, Ref<WebAutomationSession::GetBrowsingContextsCallback>&& callback)
275 {
276     if (pages.isEmpty()) {
277         callback->sendSuccess(WTFMove(contexts));
278         return;
279     }
280     auto page = pages.takeLast();
281     auto& webPageProxy = page.get();
282     webPageProxy.getWindowFrameWithCallback([this, protectedThis = WTFMove(protectedThis), callback = WTFMove(callback), pages = WTFMove(pages), contexts = WTFMove(contexts), page = WTFMove(page)](WebCore::FloatRect windowFrame) mutable {
283         contexts->addItem(protectedThis->buildBrowsingContextForPage(page.get(), windowFrame));
284         getNextContext(WTFMove(protectedThis), WTFMove(pages), WTFMove(contexts), WTFMove(callback));
285     });
286 }
287     
288 void WebAutomationSession::getBrowsingContexts(Ref<GetBrowsingContextsCallback>&& callback)
289 {
290     Vector<Ref<WebPageProxy>> pages;
291     for (auto& process : m_processPool->processes()) {
292         for (auto* page : process->pages()) {
293             ASSERT(page);
294             if (!page->isControlledByAutomation())
295                 continue;
296             pages.append(*page);
297         }
298     }
299     
300     getNextContext(makeRef(*this), WTFMove(pages), JSON::ArrayOf<Inspector::Protocol::Automation::BrowsingContext>::create(), WTFMove(callback));
301 }
302
303 void WebAutomationSession::getBrowsingContext(const String& handle, Ref<GetBrowsingContextCallback>&& callback)
304 {
305     WebPageProxy* page = webPageProxyForHandle(handle);
306     if (!page)
307         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
308
309     page->getWindowFrameWithCallback([protectedThis = makeRef(*this), page = makeRef(*page), callback = WTFMove(callback)](WebCore::FloatRect windowFrame) mutable {
310         callback->sendSuccess(protectedThis->buildBrowsingContextForPage(page.get(), windowFrame));
311     });
312 }
313
314 static Inspector::Protocol::Automation::BrowsingContextPresentation toProtocol(API::AutomationSessionClient::BrowsingContextPresentation value)
315 {
316     switch (value) {
317     case API::AutomationSessionClient::BrowsingContextPresentation::Tab:
318         return Inspector::Protocol::Automation::BrowsingContextPresentation::Tab;
319     case API::AutomationSessionClient::BrowsingContextPresentation::Window:
320         return Inspector::Protocol::Automation::BrowsingContextPresentation::Window;
321     }
322
323     RELEASE_ASSERT_NOT_REACHED();
324 }
325
326 void WebAutomationSession::createBrowsingContext(const String* optionalPresentationHint, Ref<CreateBrowsingContextCallback>&& callback)
327 {
328     ASSERT(m_client);
329     if (!m_client)
330         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session could not request a new browsing context.");
331
332     uint16_t options = 0;
333
334     if (optionalPresentationHint) {
335         auto parsedPresentationHint = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::BrowsingContextPresentation>(*optionalPresentationHint);
336         if (parsedPresentationHint.hasValue() && parsedPresentationHint.value() == Inspector::Protocol::Automation::BrowsingContextPresentation::Tab)
337             options |= API::AutomationSessionBrowsingContextOptionsPreferNewTab;
338     }
339
340     m_client->requestNewPageWithOptions(*this, static_cast<API::AutomationSessionBrowsingContextOptions>(options), [protectedThis = makeRef(*this), callback = WTFMove(callback)](WebPageProxy* page) {
341         if (!page)
342             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session failed to create a new browsing context.");
343
344         callback->sendSuccess(protectedThis->handleForWebPageProxy(*page), toProtocol(protectedThis->m_client->currentPresentationOfPage(protectedThis.get(), *page)));
345     });
346 }
347
348 void WebAutomationSession::closeBrowsingContext(Inspector::ErrorString& errorString, const String& handle)
349 {
350     WebPageProxy* page = webPageProxyForHandle(handle);
351     if (!page)
352         SYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
353
354     page->closePage(false);
355 }
356
357 void WebAutomationSession::switchToBrowsingContext(const String& browsingContextHandle, const String* optionalFrameHandle, Ref<SwitchToBrowsingContextCallback>&& callback)
358 {
359     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
360     if (!page)
361         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
362
363     Optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
364     if (!frameID)
365         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
366
367
368     m_client->requestSwitchToPage(*this, *page, [frameID, page = makeRef(*page), callback = WTFMove(callback)]() {
369         page->setFocus(true);
370         page->process().send(Messages::WebAutomationSessionProxy::FocusFrame(page->pageID(), frameID.value()), 0);
371
372         callback->sendSuccess();
373     });
374 }
375
376 void WebAutomationSession::setWindowFrameOfBrowsingContext(const String& handle, const JSON::Object* optionalOriginObject, const JSON::Object* optionalSizeObject, Ref<SetWindowFrameOfBrowsingContextCallback>&& callback)
377 {
378     Optional<float> x;
379     Optional<float> y;
380     if (optionalOriginObject) {
381         if (!(x = optionalOriginObject->getNumber<float>("x"_s)))
382             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'x' parameter was not found or invalid.");
383
384         if (!(y = optionalOriginObject->getNumber<float>("y"_s)))
385             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'y' parameter was not found or invalid.");
386
387         if (x.value() < 0)
388             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'x' parameter had an invalid value.");
389
390         if (y.value() < 0)
391             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'y' parameter had an invalid value.");
392     }
393
394     Optional<float> width;
395     Optional<float> height;
396     if (optionalSizeObject) {
397         if (!(width = optionalSizeObject->getNumber<float>("width"_s)))
398             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'width' parameter was not found or invalid.");
399
400         if (!(height = optionalSizeObject->getNumber<float>("height"_s)))
401             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'height' parameter was not found or invalid.");
402
403         if (width.value() < 0)
404             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'width' parameter had an invalid value.");
405
406         if (height.value() < 0)
407             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'height' parameter had an invalid value.");
408     }
409
410     WebPageProxy* page = webPageProxyForHandle(handle);
411     if (!page)
412         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
413
414     exitFullscreenWindowForPage(*page, [this, protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRefPtr(page), width, height, x, y]() mutable {
415         auto& webPage = *page;
416         this->restoreWindowForPage(webPage, [callback = WTFMove(callback), page = WTFMove(page), width, height, x, y]() mutable {
417             auto& webPage = *page;
418             webPage.getWindowFrameWithCallback([callback = WTFMove(callback), page = WTFMove(page), width, height, x, y](WebCore::FloatRect originalFrame) mutable {
419                 WebCore::FloatRect newFrame = WebCore::FloatRect(WebCore::FloatPoint(x.valueOr(originalFrame.location().x()), y.valueOr(originalFrame.location().y())), WebCore::FloatSize(width.valueOr(originalFrame.size().width()), height.valueOr(originalFrame.size().height())));
420                 if (newFrame != originalFrame)
421                     page->setWindowFrame(newFrame);
422                 
423                 callback->sendSuccess();
424             });
425         });
426     });
427 }
428
429 static Optional<Inspector::Protocol::Automation::PageLoadStrategy> pageLoadStrategyFromStringParameter(const String* optionalPageLoadStrategyString)
430 {
431     if (!optionalPageLoadStrategyString)
432         return defaultPageLoadStrategy;
433
434     auto parsedPageLoadStrategy = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::PageLoadStrategy>(*optionalPageLoadStrategyString);
435     if (!parsedPageLoadStrategy)
436         return WTF::nullopt;
437     return parsedPageLoadStrategy;
438 }
439
440 void WebAutomationSession::waitForNavigationToComplete(const String& browsingContextHandle, const String* optionalFrameHandle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<WaitForNavigationToCompleteCallback>&& callback)
441 {
442     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
443     if (!page)
444         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
445
446     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
447     if (!pageLoadStrategy)
448         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
449     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
450
451     // If page is loading and there's an active JavaScript dialog is probably because the
452     // dialog was started in an onload handler, so in case of normal page load strategy the
453     // load will not finish until the dialog is dismissed. Instead of waiting for the timeout,
454     // we return without waiting since we know it will timeout for sure. We want to check
455     // arguments first, though.
456     bool shouldTimeoutDueToUnexpectedAlert = pageLoadStrategy.value() == Inspector::Protocol::Automation::PageLoadStrategy::Normal
457         && page->pageLoadState().isLoading() && m_client->isShowingJavaScriptDialogOnPage(*this, *page);
458
459     if (optionalFrameHandle && !optionalFrameHandle->isEmpty()) {
460         Optional<uint64_t> frameID = webFrameIDForHandle(*optionalFrameHandle);
461         if (!frameID)
462             ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
463         WebFrameProxy* frame = page->process().webFrame(frameID.value());
464         if (!frame)
465             ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
466         if (!shouldTimeoutDueToUnexpectedAlert)
467             waitForNavigationToCompleteOnFrame(*frame, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
468     } else {
469         if (!shouldTimeoutDueToUnexpectedAlert)
470             waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
471     }
472
473     if (shouldTimeoutDueToUnexpectedAlert) {
474         // §9 Navigation.
475         // 7. If the previous step completed by the session page load timeout being reached and the browser does not
476         // have an active user prompt, return error with error code timeout.
477         // 8. Return success with data null.
478         // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-wait-for-navigation-to-complete
479         callback->sendSuccess();
480     }
481 }
482
483 void WebAutomationSession::waitForNavigationToCompleteOnPage(WebPageProxy& page, Inspector::Protocol::Automation::PageLoadStrategy loadStrategy, Seconds timeout, Ref<Inspector::BackendDispatcher::CallbackBase>&& callback)
484 {
485     ASSERT(!m_loadTimer.isActive());
486     if (loadStrategy == Inspector::Protocol::Automation::PageLoadStrategy::None || !page.pageLoadState().isLoading()) {
487         callback->sendSuccess(JSON::Object::create());
488         return;
489     }
490
491     m_loadTimer.startOneShot(timeout);
492     switch (loadStrategy) {
493     case Inspector::Protocol::Automation::PageLoadStrategy::Normal:
494         m_pendingNormalNavigationInBrowsingContextCallbacksPerPage.set(page.pageID(), WTFMove(callback));
495         break;
496     case Inspector::Protocol::Automation::PageLoadStrategy::Eager:
497         m_pendingEagerNavigationInBrowsingContextCallbacksPerPage.set(page.pageID(), WTFMove(callback));
498         break;
499     case Inspector::Protocol::Automation::PageLoadStrategy::None:
500         ASSERT_NOT_REACHED();
501     }
502 }
503
504 void WebAutomationSession::waitForNavigationToCompleteOnFrame(WebFrameProxy& frame, Inspector::Protocol::Automation::PageLoadStrategy loadStrategy, Seconds timeout, Ref<Inspector::BackendDispatcher::CallbackBase>&& callback)
505 {
506     ASSERT(!m_loadTimer.isActive());
507     if (loadStrategy == Inspector::Protocol::Automation::PageLoadStrategy::None || frame.frameLoadState().state() == FrameLoadState::State::Finished) {
508         callback->sendSuccess(JSON::Object::create());
509         return;
510     }
511
512     m_loadTimer.startOneShot(timeout);
513     switch (loadStrategy) {
514     case Inspector::Protocol::Automation::PageLoadStrategy::Normal:
515         m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame.set(frame.frameID(), WTFMove(callback));
516         break;
517     case Inspector::Protocol::Automation::PageLoadStrategy::Eager:
518         m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame.set(frame.frameID(), WTFMove(callback));
519         break;
520     case Inspector::Protocol::Automation::PageLoadStrategy::None:
521         ASSERT_NOT_REACHED();
522     }
523 }
524
525 void WebAutomationSession::respondToPendingPageNavigationCallbacksWithTimeout(HashMap<uint64_t, RefPtr<Inspector::BackendDispatcher::CallbackBase>>& map)
526 {
527     Inspector::ErrorString timeoutError = STRING_FOR_PREDEFINED_ERROR_NAME(Timeout);
528     for (auto id : copyToVector(map.keys())) {
529         auto page = WebProcessProxy::webPage(id);
530         auto callback = map.take(id);
531         if (page && m_client->isShowingJavaScriptDialogOnPage(*this, *page))
532             callback->sendSuccess(JSON::Object::create());
533         else
534             callback->sendFailure(timeoutError);
535     }
536 }
537
538 static WebPageProxy* findPageForFrameID(const WebProcessPool& processPool, uint64_t frameID)
539 {
540     for (auto& process : processPool.processes()) {
541         if (auto* frame = process->webFrame(frameID))
542             return frame->page();
543     }
544     return nullptr;
545 }
546
547 void WebAutomationSession::respondToPendingFrameNavigationCallbacksWithTimeout(HashMap<uint64_t, RefPtr<Inspector::BackendDispatcher::CallbackBase>>& map)
548 {
549     Inspector::ErrorString timeoutError = STRING_FOR_PREDEFINED_ERROR_NAME(Timeout);
550     for (auto id : copyToVector(map.keys())) {
551         auto* page = findPageForFrameID(*m_processPool, id);
552         auto callback = map.take(id);
553         if (page && m_client->isShowingJavaScriptDialogOnPage(*this, *page))
554             callback->sendSuccess(JSON::Object::create());
555         else
556             callback->sendFailure(timeoutError);
557     }
558 }
559
560 void WebAutomationSession::loadTimerFired()
561 {
562     respondToPendingFrameNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame);
563     respondToPendingFrameNavigationCallbacksWithTimeout(m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame);
564     respondToPendingPageNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerPage);
565     respondToPendingPageNavigationCallbacksWithTimeout(m_pendingEagerNavigationInBrowsingContextCallbacksPerPage);
566 }
567
568 void WebAutomationSession::maximizeWindowOfBrowsingContext(const String& browsingContextHandle, Ref<MaximizeWindowOfBrowsingContextCallback>&& callback)
569 {
570     auto* page = webPageProxyForHandle(browsingContextHandle);
571     if (!page)
572         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
573
574     exitFullscreenWindowForPage(*page, [this, protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRefPtr(page)]() mutable {
575         auto& webPage = *page;
576         restoreWindowForPage(webPage, [this, callback = WTFMove(callback), page = WTFMove(page)]() mutable {
577             maximizeWindowForPage(*page, [callback = WTFMove(callback)]() {
578                 callback->sendSuccess();
579             });
580         });
581     });
582 }
583
584 void WebAutomationSession::hideWindowOfBrowsingContext(const String& browsingContextHandle, Ref<HideWindowOfBrowsingContextCallback>&& callback)
585 {
586     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
587     if (!page)
588         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
589     
590     exitFullscreenWindowForPage(*page, [protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRefPtr(page)]() mutable {
591         protectedThis->hideWindowForPage(*page, [callback = WTFMove(callback)]() mutable {
592             callback->sendSuccess();
593         });
594     });
595 }
596
597 void WebAutomationSession::exitFullscreenWindowForPage(WebPageProxy& page, WTF::CompletionHandler<void()>&& completionHandler)
598 {
599 #if ENABLE(FULLSCREEN_API)
600     ASSERT(!m_windowStateTransitionCallback);
601     if (!page.fullScreenManager() || !page.fullScreenManager()->isFullScreen()) {
602         completionHandler();
603         return;
604     }
605     
606     m_windowStateTransitionCallback = WTF::Function<void(WindowTransitionedToState)> { [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](WindowTransitionedToState state) mutable {
607         // If fullscreen exited and we didn't request that, just ignore it.
608         if (state != WindowTransitionedToState::Unfullscreen)
609             return;
610
611         // Keep this callback in scope so completionHandler does not get destroyed before we call it.
612         auto protectedCallback = WTFMove(m_windowStateTransitionCallback);
613         completionHandler();
614     } };
615     
616     page.fullScreenManager()->requestExitFullScreen();
617 #else
618     completionHandler();
619 #endif
620 }
621
622 void WebAutomationSession::restoreWindowForPage(WebPageProxy& page, WTF::CompletionHandler<void()>&& completionHandler)
623 {
624     m_client->requestRestoreWindowOfPage(*this, page, WTFMove(completionHandler));
625 }
626
627 void WebAutomationSession::maximizeWindowForPage(WebPageProxy& page, WTF::CompletionHandler<void()>&& completionHandler)
628 {
629     m_client->requestMaximizeWindowOfPage(*this, page, WTFMove(completionHandler));
630 }
631
632 void WebAutomationSession::hideWindowForPage(WebPageProxy& page, WTF::CompletionHandler<void()>&& completionHandler)
633 {
634     m_client->requestHideWindowOfPage(*this, page, WTFMove(completionHandler));
635 }
636
637 void WebAutomationSession::willShowJavaScriptDialog(WebPageProxy& page)
638 {
639     // Wait until the next run loop iteration to give time for the client to show the dialog,
640     // then check if the dialog is still present. If the page is loading, the dialog will block
641     // the load in case of normal strategy, so we want to dispatch all pending navigation callbacks.
642     // If the dialog was shown during a script execution, we want to finish the evaluateJavaScriptFunction
643     // operation with an unexpected alert open error.
644     RunLoop::main().dispatch([this, protectedThis = makeRef(*this), page = makeRef(page)] {
645         if (!page->hasRunningProcess() || !m_client || !m_client->isShowingJavaScriptDialogOnPage(*this, page))
646             return;
647
648         if (page->pageLoadState().isLoading()) {
649             m_loadTimer.stop();
650             respondToPendingFrameNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame);
651             respondToPendingPageNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerPage);
652         }
653
654         if (!m_evaluateJavaScriptFunctionCallbacks.isEmpty()) {
655             for (auto key : copyToVector(m_evaluateJavaScriptFunctionCallbacks.keys())) {
656                 auto callback = m_evaluateJavaScriptFunctionCallbacks.take(key);
657                 callback->sendSuccess("null"_s);
658             }
659         }
660
661 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
662         if (!m_pendingMouseEventsFlushedCallbacksPerPage.isEmpty()) {
663             for (auto key : copyToVector(m_pendingMouseEventsFlushedCallbacksPerPage.keys())) {
664                 auto callback = m_pendingMouseEventsFlushedCallbacksPerPage.take(key);
665                 callback(WTF::nullopt);
666             }
667         }
668 #endif // ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
669
670 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
671         if (!m_pendingKeyboardEventsFlushedCallbacksPerPage.isEmpty()) {
672             for (auto key : copyToVector(m_pendingKeyboardEventsFlushedCallbacksPerPage.keys())) {
673                 auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(key);
674                 callback(WTF::nullopt);
675             }
676         }
677 #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
678     });
679 }
680     
681 void WebAutomationSession::didEnterFullScreenForPage(const WebPageProxy&)
682 {
683     if (m_windowStateTransitionCallback)
684         m_windowStateTransitionCallback(WindowTransitionedToState::Fullscreen);
685 }
686
687 void WebAutomationSession::didExitFullScreenForPage(const WebPageProxy&)
688 {
689     if (m_windowStateTransitionCallback)
690         m_windowStateTransitionCallback(WindowTransitionedToState::Unfullscreen);
691 }
692
693 void WebAutomationSession::navigateBrowsingContext(const String& handle, const String& url, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<NavigateBrowsingContextCallback>&& callback)
694 {
695     WebPageProxy* page = webPageProxyForHandle(handle);
696     if (!page)
697         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
698
699     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
700     if (!pageLoadStrategy)
701         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
702     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
703
704     page->loadRequest(URL(URL(), url));
705     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
706 }
707
708 void WebAutomationSession::goBackInBrowsingContext(const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<GoBackInBrowsingContextCallback>&& callback)
709 {
710     WebPageProxy* page = webPageProxyForHandle(handle);
711     if (!page)
712         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
713
714     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
715     if (!pageLoadStrategy)
716         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
717     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
718
719     page->goBack();
720     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
721 }
722
723 void WebAutomationSession::goForwardInBrowsingContext(const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<GoForwardInBrowsingContextCallback>&& callback)
724 {
725     WebPageProxy* page = webPageProxyForHandle(handle);
726     if (!page)
727         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
728
729     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
730     if (!pageLoadStrategy)
731         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
732     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
733
734     page->goForward();
735     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
736 }
737
738 void WebAutomationSession::reloadBrowsingContext(const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<ReloadBrowsingContextCallback>&& callback)
739 {
740     WebPageProxy* page = webPageProxyForHandle(handle);
741     if (!page)
742         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
743
744     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
745     if (!pageLoadStrategy)
746         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
747     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
748
749     page->reload({ });
750     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
751 }
752
753 void WebAutomationSession::navigationOccurredForFrame(const WebFrameProxy& frame)
754 {
755     if (frame.isMainFrame()) {
756         // New page loaded, clear frame handles previously cached.
757         m_handleWebFrameMap.clear();
758         m_webFrameHandleMap.clear();
759         if (auto callback = m_pendingNormalNavigationInBrowsingContextCallbacksPerPage.take(frame.page()->pageID())) {
760             m_loadTimer.stop();
761             callback->sendSuccess(JSON::Object::create());
762         }
763         m_domainNotifier->browsingContextCleared(handleForWebPageProxy(*frame.page()));
764     } else {
765         if (auto callback = m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame.take(frame.frameID())) {
766             m_loadTimer.stop();
767             callback->sendSuccess(JSON::Object::create());
768         }
769     }
770 }
771
772 void WebAutomationSession::documentLoadedForFrame(const WebFrameProxy& frame)
773 {
774     if (frame.isMainFrame()) {
775         if (auto callback = m_pendingEagerNavigationInBrowsingContextCallbacksPerPage.take(frame.page()->pageID())) {
776             m_loadTimer.stop();
777             callback->sendSuccess(JSON::Object::create());
778         }
779     } else {
780         if (auto callback = m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame.take(frame.frameID())) {
781             m_loadTimer.stop();
782             callback->sendSuccess(JSON::Object::create());
783         }
784     }
785 }
786
787 void WebAutomationSession::inspectorFrontendLoaded(const WebPageProxy& page)
788 {
789     if (auto callback = m_pendingInspectorCallbacksPerPage.take(page.pageID()))
790         callback->sendSuccess(JSON::Object::create());
791 }
792
793 void WebAutomationSession::mouseEventsFlushedForPage(const WebPageProxy& page)
794 {
795 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
796     if (auto callback = m_pendingMouseEventsFlushedCallbacksPerPage.take(page.pageID()))
797         callback(WTF::nullopt);
798 #else
799     UNUSED_PARAM(page);
800 #endif
801 }
802
803 void WebAutomationSession::keyboardEventsFlushedForPage(const WebPageProxy& page)
804 {
805 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
806     if (auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID()))
807         callback(WTF::nullopt);
808 #else
809     UNUSED_PARAM(page);
810 #endif
811 }
812
813 void WebAutomationSession::willClosePage(const WebPageProxy& page)
814 {
815     String handle = handleForWebPageProxy(page);
816     m_domainNotifier->browsingContextCleared(handle);
817
818     // Cancel pending interactions on this page. By providing an error, this will cause subsequent
819     // actions to be aborted and the SimulatedInputDispatcher::run() call will unwind and fail.
820 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
821     if (auto callback = m_pendingMouseEventsFlushedCallbacksPerPage.take(page.pageID()))
822         callback(AUTOMATION_COMMAND_ERROR_WITH_NAME(WindowNotFound));
823 #endif
824 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
825     if (auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID()))
826         callback(AUTOMATION_COMMAND_ERROR_WITH_NAME(WindowNotFound));
827 #endif
828
829 #if ENABLE(WEBDRIVER_ACTIONS_API)
830     // Then tell the input dispatcher to cancel so timers are stopped, and let it go out of scope.
831     Optional<Ref<SimulatedInputDispatcher>> inputDispatcher = m_inputDispatchersByPage.take(page.pageID());
832     if (inputDispatcher.hasValue())
833         inputDispatcher.value()->cancel();
834 #endif
835 }
836
837 static bool fileCanBeAcceptedForUpload(const String& filename, const HashSet<String>& allowedMIMETypes, const HashSet<String>& allowedFileExtensions)
838 {
839     if (!FileSystem::fileExists(filename))
840         return false;
841
842     if (allowedMIMETypes.isEmpty() && allowedFileExtensions.isEmpty())
843         return true;
844
845     // We can't infer a MIME type from a file without an extension, just give up.
846     auto dotOffset = filename.reverseFind('.');
847     if (dotOffset == notFound)
848         return false;
849
850     String extension = filename.substring(dotOffset + 1).convertToASCIILowercase();
851     if (extension.isEmpty())
852         return false;
853
854     if (allowedFileExtensions.contains(extension))
855         return true;
856
857     String mappedMIMEType = WebCore::MIMETypeRegistry::getMIMETypeForExtension(extension).convertToASCIILowercase();
858     if (mappedMIMEType.isEmpty())
859         return false;
860     
861     if (allowedMIMETypes.contains(mappedMIMEType))
862         return true;
863
864     // Fall back to checking for a MIME type wildcard if an exact match is not found.
865     Vector<String> components = mappedMIMEType.split('/');
866     if (components.size() != 2)
867         return false;
868
869     String wildcardedMIMEType = makeString(components[0], "/*");
870     if (allowedMIMETypes.contains(wildcardedMIMEType))
871         return true;
872
873     return false;
874 }
875
876 void WebAutomationSession::handleRunOpenPanel(const WebPageProxy& page, const WebFrameProxy&, const API::OpenPanelParameters& parameters, WebOpenPanelResultListenerProxy& resultListener)
877 {
878     String browsingContextHandle = handleForWebPageProxy(page);
879     if (!m_filesToSelectForFileUpload.size()) {
880         resultListener.cancel();
881         m_domainNotifier->fileChooserDismissed(browsingContextHandle, true);
882         return;
883     }
884
885     if (m_filesToSelectForFileUpload.size() > 1 && !parameters.allowMultipleFiles()) {
886         resultListener.cancel();
887         m_domainNotifier->fileChooserDismissed(browsingContextHandle, true);
888         return;
889     }
890
891     HashSet<String> allowedMIMETypes;
892     auto acceptMIMETypes = parameters.acceptMIMETypes();
893     for (auto type : acceptMIMETypes->elementsOfType<API::String>())
894         allowedMIMETypes.add(type->string());
895
896     HashSet<String> allowedFileExtensions;
897     auto acceptFileExtensions = parameters.acceptFileExtensions();
898     for (auto type : acceptFileExtensions->elementsOfType<API::String>()) {
899         // WebCore vends extensions with leading periods. Strip these to simplify matching later.
900         String extension = type->string();
901         ASSERT(extension.characterAt(0) == '.');
902         allowedFileExtensions.add(extension.substring(1));
903     }
904
905     // Per §14.3.10.5 in the W3C spec, if at least one file cannot be accepted, the command should fail.
906     // The REST API service can tell that this failed by checking the "files" attribute of the input element.
907     for (const String& filename : m_filesToSelectForFileUpload) {
908         if (!fileCanBeAcceptedForUpload(filename, allowedMIMETypes, allowedFileExtensions)) {
909             resultListener.cancel();
910             m_domainNotifier->fileChooserDismissed(browsingContextHandle, true);
911             return;
912         }
913     }
914
915     resultListener.chooseFiles(m_filesToSelectForFileUpload);
916     m_domainNotifier->fileChooserDismissed(browsingContextHandle, false);
917 }
918
919 void WebAutomationSession::evaluateJavaScriptFunction(const String& browsingContextHandle, const String* optionalFrameHandle, const String& function, const JSON::Array& arguments, const bool* optionalExpectsImplicitCallbackArgument, const int* optionalCallbackTimeout, Ref<EvaluateJavaScriptFunctionCallback>&& callback)
920 {
921     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
922     if (!page)
923         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
924
925     Optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
926     if (!frameID)
927         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
928
929     Vector<String> argumentsVector;
930     argumentsVector.reserveCapacity(arguments.length());
931
932     for (auto& argument : arguments) {
933         String argumentString;
934         argument->asString(argumentString);
935         argumentsVector.uncheckedAppend(argumentString);
936     }
937
938     bool expectsImplicitCallbackArgument = optionalExpectsImplicitCallbackArgument ? *optionalExpectsImplicitCallbackArgument : false;
939     int callbackTimeout = optionalCallbackTimeout ? *optionalCallbackTimeout : 0;
940
941     uint64_t callbackID = m_nextEvaluateJavaScriptCallbackID++;
942     m_evaluateJavaScriptFunctionCallbacks.set(callbackID, WTFMove(callback));
943
944     page->process().send(Messages::WebAutomationSessionProxy::EvaluateJavaScriptFunction(page->pageID(), frameID.value(), function, argumentsVector, expectsImplicitCallbackArgument, callbackTimeout, callbackID), 0);
945 }
946
947 void WebAutomationSession::didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType)
948 {
949     auto callback = m_evaluateJavaScriptFunctionCallbacks.take(callbackID);
950     if (!callback)
951         return;
952
953     if (!errorType.isEmpty())
954         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE_AND_DETAILS(errorType, result));
955     else
956         callback->sendSuccess(result);
957 }
958
959 void WebAutomationSession::resolveChildFrameHandle(const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&& callback)
960 {
961     if (!optionalOrdinal && !optionalName && !optionalNodeHandle)
962         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "Command must specify a child frame by ordinal, name, or element handle.");
963
964     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
965     if (!page)
966         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
967
968     Optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
969     if (!frameID)
970         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
971
972     WTF::CompletionHandler<void(Optional<String>, uint64_t)> completionHandler = [this, protectedThis = makeRef(*this), callback = callback.copyRef()](Optional<String> errorType, uint64_t frameID) mutable {
973         if (errorType) {
974             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(*errorType));
975             return;
976         }
977
978         callback->sendSuccess(handleForWebFrameID(frameID));
979     };
980
981     if (optionalNodeHandle) {
982         page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::ResolveChildFrameWithNodeHandle(page->pageID(), frameID.value(), *optionalNodeHandle), WTFMove(completionHandler));
983         return;
984     }
985
986     if (optionalName) {
987         page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::ResolveChildFrameWithName(page->pageID(), frameID.value(), *optionalName), WTFMove(completionHandler));
988         return;
989     }
990
991     if (optionalOrdinal) {
992         page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::ResolveChildFrameWithOrdinal(page->pageID(), frameID.value(), *optionalOrdinal), WTFMove(completionHandler));
993         return;
994     }
995
996     ASSERT_NOT_REACHED();
997 }
998
999 void WebAutomationSession::resolveParentFrameHandle(const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&& callback)
1000 {
1001     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1002     if (!page)
1003         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1004
1005     Optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
1006     if (!frameID)
1007         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1008
1009     WTF::CompletionHandler<void(Optional<String>, uint64_t)> completionHandler = [this, protectedThis = makeRef(*this), callback = callback.copyRef()](Optional<String> errorType, uint64_t frameID) mutable {
1010         if (errorType) {
1011             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(*errorType));
1012             return;
1013         }
1014
1015         callback->sendSuccess(handleForWebFrameID(frameID));
1016     };
1017
1018     page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::ResolveParentFrame(page->pageID(), frameID.value()), WTFMove(completionHandler));
1019 }
1020
1021 static Optional<CoordinateSystem> protocolStringToCoordinateSystem(const String& coordinateSystemString)
1022 {
1023     if (coordinateSystemString == "Page")
1024         return CoordinateSystem::Page;
1025     if (coordinateSystemString == "LayoutViewport")
1026         return CoordinateSystem::LayoutViewport;
1027     return WTF::nullopt;
1028 }
1029
1030 void WebAutomationSession::computeElementLayout(const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const String& coordinateSystemString, Ref<ComputeElementLayoutCallback>&& callback)
1031 {
1032     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1033     if (!page)
1034         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1035
1036     Optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
1037     if (!frameID)
1038         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1039
1040     Optional<CoordinateSystem> coordinateSystem = protocolStringToCoordinateSystem(coordinateSystemString);
1041     if (!coordinateSystem)
1042         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'coordinateSystem' is invalid.");
1043
1044     WTF::CompletionHandler<void(Optional<String>, WebCore::IntRect, Optional<WebCore::IntPoint>, bool)> completionHandler = [callback = callback.copyRef()](Optional<String> errorType, WebCore::IntRect rect, Optional<WebCore::IntPoint> inViewCenterPoint, bool isObscured) mutable {
1045         if (errorType) {
1046             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(*errorType));
1047             return;
1048         }
1049
1050         auto originObject = Inspector::Protocol::Automation::Point::create()
1051             .setX(rect.x())
1052             .setY(rect.y())
1053             .release();
1054
1055         auto sizeObject = Inspector::Protocol::Automation::Size::create()
1056             .setWidth(rect.width())
1057             .setHeight(rect.height())
1058             .release();
1059
1060         auto rectObject = Inspector::Protocol::Automation::Rect::create()
1061             .setOrigin(WTFMove(originObject))
1062             .setSize(WTFMove(sizeObject))
1063             .release();
1064
1065         if (!inViewCenterPoint) {
1066             callback->sendSuccess(WTFMove(rectObject), nullptr, isObscured);
1067             return;
1068         }
1069
1070         auto inViewCenterPointObject = Inspector::Protocol::Automation::Point::create()
1071             .setX(inViewCenterPoint.value().x())
1072             .setY(inViewCenterPoint.value().y())
1073             .release();
1074
1075         callback->sendSuccess(WTFMove(rectObject), WTFMove(inViewCenterPointObject), isObscured);
1076     };
1077
1078     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
1079     page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::ComputeElementLayout(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, coordinateSystem.value()), WTFMove(completionHandler));
1080 }
1081
1082 void WebAutomationSession::selectOptionElement(const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, Ref<SelectOptionElementCallback>&& callback)
1083 {
1084     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1085     if (!page)
1086         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1087
1088     Optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
1089     if (!frameID)
1090         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1091
1092     WTF::CompletionHandler<void(Optional<String>)> completionHandler = [callback = callback.copyRef()](Optional<String> errorType) mutable {
1093         if (errorType) {
1094             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(*errorType));
1095             return;
1096         }
1097         
1098         callback->sendSuccess();
1099     };
1100
1101     page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::SelectOptionElement(page->pageID(), frameID.value(), nodeHandle), WTFMove(completionHandler));
1102 }
1103
1104 void WebAutomationSession::isShowingJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, bool* result)
1105 {
1106     ASSERT(m_client);
1107     if (!m_client)
1108         SYNC_FAIL_WITH_PREDEFINED_ERROR(InternalError);
1109
1110     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1111     if (!page)
1112         SYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1113
1114     *result = m_client->isShowingJavaScriptDialogOnPage(*this, *page);
1115 }
1116
1117 void WebAutomationSession::dismissCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
1118 {
1119     ASSERT(m_client);
1120     if (!m_client)
1121         SYNC_FAIL_WITH_PREDEFINED_ERROR(InternalError);
1122
1123     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1124     if (!page)
1125         SYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1126
1127     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1128         SYNC_FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1129
1130     m_client->dismissCurrentJavaScriptDialogOnPage(*this, *page);
1131 }
1132
1133 void WebAutomationSession::acceptCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
1134 {
1135     ASSERT(m_client);
1136     if (!m_client)
1137         SYNC_FAIL_WITH_PREDEFINED_ERROR(InternalError);
1138
1139     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1140     if (!page)
1141         SYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1142
1143     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1144         SYNC_FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1145
1146     m_client->acceptCurrentJavaScriptDialogOnPage(*this, *page);
1147 }
1148
1149 void WebAutomationSession::messageOfCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, String* text)
1150 {
1151     ASSERT(m_client);
1152     if (!m_client)
1153         SYNC_FAIL_WITH_PREDEFINED_ERROR(InternalError);
1154
1155     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1156     if (!page)
1157         SYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1158
1159     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1160         SYNC_FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1161
1162     *text = m_client->messageOfCurrentJavaScriptDialogOnPage(*this, *page);
1163 }
1164
1165 void WebAutomationSession::setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& promptValue)
1166 {
1167     ASSERT(m_client);
1168     if (!m_client)
1169         SYNC_FAIL_WITH_PREDEFINED_ERROR(InternalError);
1170
1171     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1172     if (!page)
1173         SYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1174
1175     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
1176         SYNC_FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
1177
1178     // §18.4 Send Alert Text.
1179     // https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text
1180     // 3. Run the substeps of the first matching current user prompt:
1181     auto scriptDialogType = m_client->typeOfCurrentJavaScriptDialogOnPage(*this, *page);
1182     ASSERT(scriptDialogType);
1183     switch (scriptDialogType.value()) {
1184     case API::AutomationSessionClient::JavaScriptDialogType::Alert:
1185     case API::AutomationSessionClient::JavaScriptDialogType::Confirm:
1186         // Return error with error code element not interactable.
1187         SYNC_FAIL_WITH_PREDEFINED_ERROR(ElementNotInteractable);
1188     case API::AutomationSessionClient::JavaScriptDialogType::Prompt:
1189         // Do nothing.
1190         break;
1191     case API::AutomationSessionClient::JavaScriptDialogType::BeforeUnloadConfirm:
1192         // Return error with error code unsupported operation.
1193         SYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1194     }
1195
1196     m_client->setUserInputForCurrentJavaScriptPromptOnPage(*this, *page, promptValue);
1197 }
1198
1199 void WebAutomationSession::setFilesToSelectForFileUpload(ErrorString& errorString, const String& browsingContextHandle, const JSON::Array& filenames, const JSON::Array* fileContents)
1200 {
1201     Vector<String> newFileList;
1202     newFileList.reserveInitialCapacity(filenames.length());
1203
1204     if (fileContents && fileContents->length() != filenames.length())
1205         SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The parameters 'filenames' and 'fileContents' must have equal length.");
1206
1207     for (size_t i = 0; i < filenames.length(); ++i) {
1208         String filename;
1209         if (!filenames.get(i)->asString(filename))
1210             SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The parameter 'filenames' contains a non-string value.");
1211
1212         if (!fileContents) {
1213             newFileList.append(filename);
1214             continue;
1215         }
1216
1217         String fileData;
1218         if (!fileContents->get(i)->asString(fileData))
1219             SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The parameter 'fileContents' contains a non-string value.");
1220
1221         Optional<String> localFilePath = platformGenerateLocalFilePathForRemoteFile(filename, fileData);
1222         if (!localFilePath)
1223             SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote file could not be saved to a local temporary directory.");
1224
1225         newFileList.append(localFilePath.value());
1226     }
1227
1228     m_filesToSelectForFileUpload.swap(newFileList);
1229 }
1230
1231 static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
1232 {
1233     return Inspector::Protocol::Automation::Cookie::create()
1234         .setName(cookie.name)
1235         .setValue(cookie.value)
1236         .setDomain(cookie.domain)
1237         .setPath(cookie.path)
1238         .setExpires(cookie.expires ? cookie.expires / 1000 : 0)
1239         .setSize((cookie.name.length() + cookie.value.length()))
1240         .setHttpOnly(cookie.httpOnly)
1241         .setSecure(cookie.secure)
1242         .setSession(cookie.session)
1243         .release();
1244 }
1245
1246 static Ref<JSON::ArrayOf<Inspector::Protocol::Automation::Cookie>> buildArrayForCookies(Vector<WebCore::Cookie>& cookiesList)
1247 {
1248     auto cookies = JSON::ArrayOf<Inspector::Protocol::Automation::Cookie>::create();
1249
1250     for (const auto& cookie : cookiesList)
1251         cookies->addItem(buildObjectForCookie(cookie));
1252
1253     return cookies;
1254 }
1255
1256 void WebAutomationSession::getAllCookies(const String& browsingContextHandle, Ref<GetAllCookiesCallback>&& callback)
1257 {
1258     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1259     if (!page)
1260         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1261
1262     WTF::CompletionHandler<void(Optional<String>, Vector<WebCore::Cookie>)> completionHandler = [callback = callback.copyRef()](Optional<String> errorType, Vector<WebCore::Cookie> cookies) mutable {
1263         if (errorType) {
1264             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(*errorType));
1265             return;
1266         }
1267
1268         callback->sendSuccess(buildArrayForCookies(cookies));
1269     };
1270
1271     // 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.
1272     const uint64_t mainFrameID = 0;
1273     page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::GetCookiesForFrame(page->pageID(), mainFrameID), WTFMove(completionHandler));
1274 }
1275
1276 void WebAutomationSession::deleteSingleCookie(const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&& callback)
1277 {
1278     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1279     if (!page)
1280         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1281
1282     WTF::CompletionHandler<void(Optional<String>)> completionHandler = [callback = callback.copyRef()](Optional<String> errorType) mutable {
1283         if (errorType) {
1284             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(*errorType));
1285             return;
1286         }
1287
1288         callback->sendSuccess();
1289     };
1290
1291     // 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.
1292     const uint64_t mainFrameID = 0;
1293     page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::DeleteCookie(page->pageID(), mainFrameID, cookieName), WTFMove(completionHandler));
1294 }
1295
1296 static String domainByAddingDotPrefixIfNeeded(String domain)
1297 {
1298     if (domain[0] != '.') {
1299         // RFC 2965: If an explicitly specified value does not start with a dot, the user agent supplies a leading dot.
1300         // Assume that any host that ends with a digit is trying to be an IP address.
1301         if (!URL::hostIsIPAddress(domain))
1302             return makeString('.', domain);
1303     }
1304     
1305     return domain;
1306 }
1307     
1308 void WebAutomationSession::addSingleCookie(const String& browsingContextHandle, const JSON::Object& cookieObject, Ref<AddSingleCookieCallback>&& callback)
1309 {
1310     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1311     if (!page)
1312         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1313
1314     URL activeURL = URL(URL(), page->pageLoadState().activeURL());
1315     ASSERT(activeURL.isValid());
1316
1317     WebCore::Cookie cookie;
1318
1319     if (!cookieObject.getString("name"_s, cookie.name))
1320         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'name' was not found.");
1321
1322     if (!cookieObject.getString("value"_s, cookie.value))
1323         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'value' was not found.");
1324
1325     String domain;
1326     if (!cookieObject.getString("domain"_s, domain))
1327         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'domain' was not found.");
1328
1329     // Inherit the domain/host from the main frame's URL if it is not explicitly set.
1330     if (domain.isEmpty())
1331         cookie.domain = activeURL.host().toString();
1332     else
1333         cookie.domain = domainByAddingDotPrefixIfNeeded(domain);
1334
1335     if (!cookieObject.getString("path"_s, cookie.path))
1336         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'path' was not found.");
1337
1338     double expires;
1339     if (!cookieObject.getDouble("expires"_s, expires))
1340         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'expires' was not found.");
1341
1342     cookie.expires = expires * 1000.0;
1343
1344     if (!cookieObject.getBoolean("secure"_s, cookie.secure))
1345         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'secure' was not found.");
1346
1347     if (!cookieObject.getBoolean("session"_s, cookie.session))
1348         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'session' was not found.");
1349
1350     if (!cookieObject.getBoolean("httpOnly"_s, cookie.httpOnly))
1351         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'httpOnly' was not found.");
1352
1353     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1354     cookieManager->setCookies(page->websiteDataStore().sessionID(), { cookie }, [callback = callback.copyRef()](CallbackBase::Error error) {
1355         if (error == CallbackBase::Error::None)
1356             callback->sendSuccess();
1357         else
1358             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1359     });
1360 }
1361
1362 void WebAutomationSession::deleteAllCookies(ErrorString& errorString, const String& browsingContextHandle)
1363 {
1364     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1365     if (!page)
1366         SYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1367
1368     URL activeURL = URL(URL(), page->pageLoadState().activeURL());
1369     ASSERT(activeURL.isValid());
1370
1371     String host = activeURL.host().toString();
1372
1373     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1374     cookieManager->deleteCookiesForHostnames(page->websiteDataStore().sessionID(), { host, domainByAddingDotPrefixIfNeeded(host) });
1375 }
1376
1377 void WebAutomationSession::getSessionPermissions(ErrorString&, RefPtr<JSON::ArrayOf<Inspector::Protocol::Automation::SessionPermissionData>>& out_permissions)
1378 {
1379     auto permissionsObjectArray = JSON::ArrayOf<Inspector::Protocol::Automation::SessionPermissionData>::create();
1380     auto getUserMediaPermissionObject = Inspector::Protocol::Automation::SessionPermissionData::create()
1381         .setPermission(Inspector::Protocol::Automation::SessionPermission::GetUserMedia)
1382         .setValue(m_permissionForGetUserMedia)
1383         .release();
1384
1385     permissionsObjectArray->addItem(WTFMove(getUserMediaPermissionObject));
1386     out_permissions = WTFMove(permissionsObjectArray);
1387 }
1388
1389 void WebAutomationSession::setSessionPermissions(ErrorString& errorString, const JSON::Array& permissions)
1390 {
1391     for (auto it = permissions.begin(); it != permissions.end(); ++it) {
1392         RefPtr<JSON::Object> permission;
1393         if (!it->get()->asObject(permission))
1394             SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permissions' is invalid.");
1395
1396         String permissionName;
1397         if (!permission->getString("permission"_s, permissionName))
1398             SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permission' is missing or invalid.");
1399
1400         auto parsedPermissionName = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::SessionPermission>(permissionName);
1401         if (!parsedPermissionName)
1402             SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'permission' has an unknown value.");
1403
1404         bool permissionValue;
1405         if (!permission->getBoolean("value"_s, permissionValue))
1406             SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'value' is missing or invalid.");
1407
1408         switch (parsedPermissionName.value()) {
1409         case Inspector::Protocol::Automation::SessionPermission::GetUserMedia:
1410             m_permissionForGetUserMedia = permissionValue;
1411             break;
1412         }
1413     }
1414 }
1415
1416 bool WebAutomationSession::shouldAllowGetUserMediaForPage(const WebPageProxy&) const
1417 {
1418     return m_permissionForGetUserMedia;
1419 }
1420
1421 bool WebAutomationSession::isSimulatingUserInteraction() const
1422 {
1423 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1424     if (!m_pendingMouseEventsFlushedCallbacksPerPage.isEmpty())
1425         return true;
1426 #endif
1427 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1428     if (!m_pendingKeyboardEventsFlushedCallbacksPerPage.isEmpty())
1429         return true;
1430 #endif
1431 #if ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1432     if (m_simulatingTouchInteraction)
1433         return true;
1434 #endif
1435     return false;
1436 }
1437
1438 #if ENABLE(WEBDRIVER_ACTIONS_API)
1439 SimulatedInputDispatcher& WebAutomationSession::inputDispatcherForPage(WebPageProxy& page)
1440 {
1441     return m_inputDispatchersByPage.ensure(page.pageID(), [&] {
1442         return SimulatedInputDispatcher::create(page, *this);
1443     }).iterator->value;
1444 }
1445
1446 SimulatedInputSource* WebAutomationSession::inputSourceForType(SimulatedInputSourceType type) const
1447 {
1448     // FIXME: this should use something like Vector's findMatching().
1449     for (auto& inputSource : m_inputSources) {
1450         if (inputSource->type == type)
1451             return &inputSource.get();
1452     }
1453
1454     return nullptr;
1455 }
1456
1457 // MARK: SimulatedInputDispatcher::Client API
1458 void WebAutomationSession::viewportInViewCenterPointOfElement(WebPageProxy& page, uint64_t frameID, const String& nodeHandle, Function<void (Optional<WebCore::IntPoint>, Optional<AutomationCommandError>)>&& completionHandler)
1459 {
1460     WTF::CompletionHandler<void(Optional<String>, WebCore::IntRect, Optional<WebCore::IntPoint>, bool)> didComputeElementLayoutHandler = [completionHandler = WTFMove(completionHandler)](Optional<String> errorType, WebCore::IntRect, Optional<WebCore::IntPoint> inViewCenterPoint, bool) mutable {
1461         if (errorType) {
1462             completionHandler(WTF::nullopt, AUTOMATION_COMMAND_ERROR_WITH_MESSAGE(*errorType));
1463             return;
1464         }
1465
1466         if (!inViewCenterPoint) {
1467             completionHandler(WTF::nullopt, AUTOMATION_COMMAND_ERROR_WITH_NAME(ElementNotInteractable));
1468             return;
1469         }
1470
1471         completionHandler(inViewCenterPoint, WTF::nullopt);
1472     };
1473
1474     page.process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::ComputeElementLayout(page.pageID(), frameID, nodeHandle, false, CoordinateSystem::LayoutViewport), WTFMove(didComputeElementLayoutHandler));
1475 }
1476
1477 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1478 void WebAutomationSession::simulateMouseInteraction(WebPageProxy& page, MouseInteraction interaction, WebMouseEvent::Button mouseButton, const WebCore::IntPoint& locationInViewport, CompletionHandler<void(Optional<AutomationCommandError>)>&& completionHandler)
1479 {
1480     WebCore::IntPoint locationInView = WebCore::IntPoint(locationInViewport.x(), locationInViewport.y() + page.topContentInset());
1481     page.getWindowFrameWithCallback([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler), page = makeRef(page), interaction, mouseButton, locationInView](WebCore::FloatRect windowFrame) mutable {
1482         auto clippedX = std::min(std::max(0.0f, (float)locationInView.x()), windowFrame.size().width());
1483         auto clippedY = std::min(std::max(0.0f, (float)locationInView.y()), windowFrame.size().height());
1484         if (clippedX != locationInView.x() || clippedY != locationInView.y()) {
1485             completionHandler(AUTOMATION_COMMAND_ERROR_WITH_NAME(TargetOutOfBounds));
1486             return;
1487         }
1488
1489         // Bridge the flushed callback to our command's completion handler.
1490         auto mouseEventsFlushedCallback = [completionHandler = WTFMove(completionHandler)](Optional<AutomationCommandError> error) mutable {
1491             completionHandler(error);
1492         };
1493
1494         auto& callbackInMap = m_pendingMouseEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1495         if (callbackInMap)
1496             callbackInMap(AUTOMATION_COMMAND_ERROR_WITH_NAME(Timeout));
1497         callbackInMap = WTFMove(mouseEventsFlushedCallback);
1498
1499         platformSimulateMouseInteraction(page, interaction, mouseButton, locationInView, OptionSet<WebEvent::Modifier>::fromRaw(m_currentModifiers));
1500
1501         // If the event does not hit test anything in the window, then it may not have been delivered.
1502         if (callbackInMap && !page->isProcessingMouseEvents()) {
1503             auto callbackToCancel = m_pendingMouseEventsFlushedCallbacksPerPage.take(page->pageID());
1504             callbackToCancel(WTF::nullopt);
1505         }
1506
1507         // Otherwise, wait for mouseEventsFlushedCallback to run when all events are handled.
1508     });
1509 }
1510 #endif // ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1511
1512 #if ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1513 void WebAutomationSession::simulateTouchInteraction(WebPageProxy& page, TouchInteraction interaction, const WebCore::IntPoint& locationInViewport, Optional<Seconds> duration, CompletionHandler<void(Optional<AutomationCommandError>)>&& completionHandler)
1514 {
1515 #if PLATFORM(IOS_FAMILY)
1516     if (!page.unobscuredContentRect().contains(locationInViewport)) {
1517         completionHandler(AUTOMATION_COMMAND_ERROR_WITH_NAME(TargetOutOfBounds));
1518         return;
1519     }
1520 #endif
1521
1522     m_simulatingTouchInteraction = true;
1523     platformSimulateTouchInteraction(page, interaction, locationInViewport, duration, [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](Optional<AutomationCommandError> error) mutable {
1524         m_simulatingTouchInteraction = false;
1525         completionHandler(error);
1526     });
1527 }
1528 #endif // ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1529
1530 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1531 void WebAutomationSession::simulateKeyboardInteraction(WebPageProxy& page, KeyboardInteraction interaction, WTF::Variant<VirtualKey, CharKey>&& key, CompletionHandler<void(Optional<AutomationCommandError>)>&& completionHandler)
1532 {
1533     // Bridge the flushed callback to our command's completion handler.
1534     auto keyboardEventsFlushedCallback = [completionHandler = WTFMove(completionHandler)](Optional<AutomationCommandError> error) mutable {
1535         completionHandler(error);
1536     };
1537
1538     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page.pageID(), nullptr).iterator->value;
1539     if (callbackInMap)
1540         callbackInMap(AUTOMATION_COMMAND_ERROR_WITH_NAME(Timeout));
1541     callbackInMap = WTFMove(keyboardEventsFlushedCallback);
1542
1543     platformSimulateKeyboardInteraction(page, interaction, WTFMove(key));
1544
1545     // If the interaction does not generate any events, then do not wait for events to be flushed.
1546     // This happens in some corner cases on macOS, such as releasing a key while Command is pressed.
1547     if (callbackInMap && !page.isProcessingKeyboardEvents()) {
1548         auto callbackToCancel = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID());
1549         callbackToCancel(WTF::nullopt);
1550     }
1551
1552     // Otherwise, wait for keyboardEventsFlushedCallback to run when all events are handled.
1553 }
1554 #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1555 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1556
1557 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1558 static WebEvent::Modifier protocolModifierToWebEventModifier(Inspector::Protocol::Automation::KeyModifier modifier)
1559 {
1560     switch (modifier) {
1561     case Inspector::Protocol::Automation::KeyModifier::Alt:
1562         return WebEvent::Modifier::AltKey;
1563     case Inspector::Protocol::Automation::KeyModifier::Meta:
1564         return WebEvent::Modifier::MetaKey;
1565     case Inspector::Protocol::Automation::KeyModifier::Control:
1566         return WebEvent::Modifier::ControlKey;
1567     case Inspector::Protocol::Automation::KeyModifier::Shift:
1568         return WebEvent::Modifier::ShiftKey;
1569     case Inspector::Protocol::Automation::KeyModifier::CapsLock:
1570         return WebEvent::Modifier::CapsLockKey;
1571     }
1572
1573     RELEASE_ASSERT_NOT_REACHED();
1574 }
1575 #endif // ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1576
1577 #if ENABLE(WEBDRIVER_ACTIONS_API)
1578 static WebMouseEvent::Button protocolMouseButtonToWebMouseEventButton(Inspector::Protocol::Automation::MouseButton button)
1579 {
1580     switch (button) {
1581     case Inspector::Protocol::Automation::MouseButton::None:
1582         return WebMouseEvent::NoButton;
1583     case Inspector::Protocol::Automation::MouseButton::Left:
1584         return WebMouseEvent::LeftButton;
1585     case Inspector::Protocol::Automation::MouseButton::Middle:
1586         return WebMouseEvent::MiddleButton;
1587     case Inspector::Protocol::Automation::MouseButton::Right:
1588         return WebMouseEvent::RightButton;
1589     }
1590
1591     RELEASE_ASSERT_NOT_REACHED();
1592 }
1593 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1594
1595 void WebAutomationSession::performMouseInteraction(const String& handle, const JSON::Object& requestedPositionObject, const String& mouseButtonString, const String& mouseInteractionString, const JSON::Array& keyModifierStrings, Ref<PerformMouseInteractionCallback>&& callback)
1596 {
1597 #if !ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1598     ASYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1599 #else
1600     WebPageProxy* page = webPageProxyForHandle(handle);
1601     if (!page)
1602         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1603
1604     float x;
1605     if (!requestedPositionObject.getDouble("x"_s, x))
1606         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'x' was not found.");
1607
1608     float y;
1609     if (!requestedPositionObject.getDouble("y"_s, y))
1610         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'y' was not found.");
1611
1612     OptionSet<WebEvent::Modifier> keyModifiers;
1613     for (auto it = keyModifierStrings.begin(); it != keyModifierStrings.end(); ++it) {
1614         String modifierString;
1615         if (!it->get()->asString(modifierString))
1616             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'modifiers' is invalid.");
1617
1618         auto parsedModifier = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyModifier>(modifierString);
1619         if (!parsedModifier)
1620             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A modifier in the 'modifiers' array is invalid.");
1621         keyModifiers.add(protocolModifierToWebEventModifier(parsedModifier.value()));
1622     }
1623
1624     page->getWindowFrameWithCallback([this, protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRef(*page), x, y, mouseInteractionString, mouseButtonString, keyModifiers](WebCore::FloatRect windowFrame) mutable {
1625
1626         x = std::min(std::max(0.0f, x), windowFrame.size().width());
1627         y = std::min(std::max(0.0f, y + page->topContentInset()), windowFrame.size().height());
1628
1629         WebCore::IntPoint positionInView = WebCore::IntPoint(static_cast<int>(x), static_cast<int>(y));
1630
1631         auto parsedInteraction = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseInteraction>(mouseInteractionString);
1632         if (!parsedInteraction)
1633             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interaction' is invalid.");
1634
1635         auto parsedButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(mouseButtonString);
1636         if (!parsedButton)
1637             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'button' is invalid.");
1638
1639         auto mouseEventsFlushedCallback = [protectedThis = WTFMove(protectedThis), callback = WTFMove(callback), page = page.copyRef(), x, y](Optional<AutomationCommandError> error) {
1640             if (error)
1641                 callback->sendFailure(error.value().toProtocolString());
1642             else {
1643                 callback->sendSuccess(Inspector::Protocol::Automation::Point::create()
1644                     .setX(x)
1645                     .setY(y - page->topContentInset())
1646                     .release());
1647             }
1648         };
1649
1650         auto& callbackInMap = m_pendingMouseEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1651         if (callbackInMap)
1652             callbackInMap(AUTOMATION_COMMAND_ERROR_WITH_NAME(Timeout));
1653         callbackInMap = WTFMove(mouseEventsFlushedCallback);
1654
1655         platformSimulateMouseInteraction(page, parsedInteraction.value(), protocolMouseButtonToWebMouseEventButton(parsedButton.value()), positionInView, keyModifiers);
1656
1657         // If the event location was previously clipped and does not hit test anything in the window, then it will not be processed.
1658         // For compatibility with pre-W3C driver implementations, don't make this a hard error; just do nothing silently.
1659         // In W3C-only code paths, we can reject any pointer actions whose coordinates are outside the viewport rect.
1660         if (callbackInMap && !page->isProcessingMouseEvents()) {
1661             auto callbackToCancel = m_pendingMouseEventsFlushedCallbacksPerPage.take(page->pageID());
1662             callbackToCancel(WTF::nullopt);
1663         }
1664     });
1665 #endif // ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1666 }
1667
1668 void WebAutomationSession::performKeyboardInteractions(const String& handle, const JSON::Array& interactions, Ref<PerformKeyboardInteractionsCallback>&& callback)
1669 {
1670 #if !ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1671     ASYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1672 #else
1673     WebPageProxy* page = webPageProxyForHandle(handle);
1674     if (!page)
1675         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1676
1677     if (!interactions.length())
1678         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interactions' was not found or empty.");
1679
1680     // Validate all of the parameters before performing any interactions with the browsing context under test.
1681     Vector<WTF::Function<void()>> actionsToPerform;
1682     actionsToPerform.reserveCapacity(interactions.length());
1683
1684     for (const auto& interaction : interactions) {
1685         RefPtr<JSON::Object> interactionObject;
1686         if (!interaction->asObject(interactionObject))
1687             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter was invalid.");
1688
1689         String interactionTypeString;
1690         if (!interactionObject->getString("type"_s, interactionTypeString))
1691             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter is missing the 'type' key.");
1692         auto interactionType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyboardInteractionType>(interactionTypeString);
1693         if (!interactionType)
1694             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'type' key.");
1695
1696         String virtualKeyString;
1697         bool foundVirtualKey = interactionObject->getString("key"_s, virtualKeyString);
1698         if (foundVirtualKey) {
1699             Optional<VirtualKey> virtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(virtualKeyString);
1700             if (!virtualKey)
1701                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1702
1703             actionsToPerform.uncheckedAppend([this, page, interactionType, virtualKey] {
1704                 platformSimulateKeyboardInteraction(*page, interactionType.value(), virtualKey.value());
1705             });
1706         }
1707
1708         String keySequence;
1709         bool foundKeySequence = interactionObject->getString("text"_s, keySequence);
1710         if (foundKeySequence) {
1711             switch (interactionType.value()) {
1712             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress:
1713             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease:
1714                 // 'KeyPress' and 'KeyRelease' are meant for a virtual key and are not supported for a string (sequence of codepoints).
1715                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1716
1717             case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey:
1718                 actionsToPerform.uncheckedAppend([this, page, keySequence] {
1719                     platformSimulateKeySequence(*page, keySequence);
1720                 });
1721                 break;
1722             }
1723         }
1724
1725         if (!foundVirtualKey && !foundKeySequence)
1726             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "An interaction in the 'interactions' parameter is missing both 'key' and 'text'. One must be provided.");
1727     }
1728
1729     ASSERT(actionsToPerform.size());
1730     if (!actionsToPerform.size())
1731         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "No actions to perform.");
1732
1733     auto keyboardEventsFlushedCallback = [protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRef(*page)](Optional<AutomationCommandError> error) {
1734         if (error)
1735             callback->sendFailure(error.value().toProtocolString());
1736         else
1737             callback->sendSuccess();
1738     };
1739
1740     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1741     if (callbackInMap)
1742         callbackInMap(AUTOMATION_COMMAND_ERROR_WITH_NAME(Timeout));
1743     callbackInMap = WTFMove(keyboardEventsFlushedCallback);
1744
1745     for (auto& action : actionsToPerform)
1746         action();
1747 #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1748 }
1749
1750 #if ENABLE(WEBDRIVER_ACTIONS_API)
1751 static SimulatedInputSourceType simulatedInputSourceTypeFromProtocolSourceType(Inspector::Protocol::Automation::InputSourceType protocolType)
1752 {
1753     switch (protocolType) {
1754     case Inspector::Protocol::Automation::InputSourceType::Null:
1755         return SimulatedInputSourceType::Null;
1756     case Inspector::Protocol::Automation::InputSourceType::Keyboard:
1757         return SimulatedInputSourceType::Keyboard;
1758     case Inspector::Protocol::Automation::InputSourceType::Mouse:
1759         return SimulatedInputSourceType::Mouse;
1760     case Inspector::Protocol::Automation::InputSourceType::Touch:
1761         return SimulatedInputSourceType::Touch;
1762     }
1763
1764     RELEASE_ASSERT_NOT_REACHED();
1765 }
1766 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1767
1768 void WebAutomationSession::performInteractionSequence(const String& handle, const String* optionalFrameHandle, const JSON::Array& inputSources, const JSON::Array& steps, Ref<WebAutomationSession::PerformInteractionSequenceCallback>&& callback)
1769 {
1770     // This command implements WebKit support for §17.5 Perform Actions.
1771
1772 #if !ENABLE(WEBDRIVER_ACTIONS_API)
1773     ASYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1774 #else
1775     WebPageProxy* page = webPageProxyForHandle(handle);
1776     if (!page)
1777         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1778
1779     auto frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
1780     if (!frameID)
1781         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1782
1783     HashMap<String, Ref<SimulatedInputSource>> sourceIdToInputSourceMap;
1784     HashMap<SimulatedInputSourceType, String, WTF::IntHash<SimulatedInputSourceType>, WTF::StrongEnumHashTraits<SimulatedInputSourceType>> typeToSourceIdMap;
1785
1786     // Parse and validate Automation protocol arguments. By this point, the driver has
1787     // already performed the steps in §17.3 Processing Actions Requests.
1788     if (!inputSources.length())
1789         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'inputSources' was not found or empty.");
1790
1791     for (const auto& inputSource : inputSources) {
1792         RefPtr<JSON::Object> inputSourceObject;
1793         if (!inputSource->asObject(inputSourceObject))
1794             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An input source in the 'inputSources' parameter was invalid.");
1795         
1796         String sourceId;
1797         if (!inputSourceObject->getString("sourceId"_s, sourceId))
1798             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An input source in the 'inputSources' parameter is missing a 'sourceId'.");
1799
1800         String sourceType;
1801         if (!inputSourceObject->getString("sourceType"_s, sourceType))
1802             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An input source in the 'inputSources' parameter is missing a 'sourceType'.");
1803
1804         auto parsedInputSourceType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::InputSourceType>(sourceType);
1805         if (!parsedInputSourceType)
1806             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An input source in the 'inputSources' parameter has an invalid 'sourceType'.");
1807
1808         SimulatedInputSourceType inputSourceType = simulatedInputSourceTypeFromProtocolSourceType(*parsedInputSourceType);
1809
1810         // Note: iOS does not support mouse input sources, and other platforms do not support touch input sources.
1811         // If a mismatch happens, alias to the supported input source. This works because both Mouse and Touch input sources
1812         // use a MouseButton to indicate the result of interacting (down/up/move), which can be interpreted for touch or mouse.
1813 #if !ENABLE(WEBDRIVER_MOUSE_INTERACTIONS) && ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1814         if (inputSourceType == SimulatedInputSourceType::Mouse)
1815             inputSourceType = SimulatedInputSourceType::Touch;
1816 #endif
1817 #if !ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1818         if (inputSourceType == SimulatedInputSourceType::Mouse)
1819             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(NotImplemented, "Mouse input sources are not yet supported.");
1820 #endif
1821 #if !ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1822         if (inputSourceType == SimulatedInputSourceType::Touch)
1823             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(NotImplemented, "Touch input sources are not yet supported.");
1824 #endif
1825 #if !ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1826         if (inputSourceType == SimulatedInputSourceType::Keyboard)
1827             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(NotImplemented, "Keyboard input sources are not yet supported.");
1828 #endif
1829         if (typeToSourceIdMap.contains(inputSourceType))
1830             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Two input sources with the same type were specified.");
1831         if (sourceIdToInputSourceMap.contains(sourceId))
1832             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Two input sources with the same sourceId were specified.");
1833
1834         typeToSourceIdMap.add(inputSourceType, sourceId);
1835         sourceIdToInputSourceMap.add(sourceId, *inputSourceForType(inputSourceType));
1836     }
1837
1838     Vector<SimulatedInputKeyFrame> keyFrames;
1839
1840     if (!steps.length())
1841         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'steps' was not found or empty.");
1842
1843     for (const auto& step : steps) {
1844         RefPtr<JSON::Object> stepObject;
1845         if (!step->asObject(stepObject))
1846             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A step in the 'steps' parameter was not an object.");
1847
1848         RefPtr<JSON::Array> stepStates;
1849         if (!stepObject->getArray("states"_s, stepStates))
1850             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A step is missing the 'states' property.");
1851
1852         Vector<SimulatedInputKeyFrame::StateEntry> entries;
1853         entries.reserveCapacity(stepStates->length());
1854
1855         for (const auto& state : *stepStates) {
1856             RefPtr<JSON::Object> stateObject;
1857             if (!state->asObject(stateObject))
1858                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Encountered a non-object step state.");
1859
1860             String sourceId;
1861             if (!stateObject->getString("sourceId"_s, sourceId))
1862                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Step state lacks required 'sourceId' property.");
1863
1864             if (!sourceIdToInputSourceMap.contains(sourceId))
1865                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Unknown 'sourceId' specified.");
1866
1867             SimulatedInputSource& inputSource = *sourceIdToInputSourceMap.get(sourceId);
1868             SimulatedInputSourceState sourceState { };
1869
1870             String pressedCharKeyString;
1871             if (stateObject->getString("pressedCharKey"_s, pressedCharKeyString))
1872                 sourceState.pressedCharKey = pressedCharKeyString.characterAt(0);
1873
1874             RefPtr<JSON::Array> pressedVirtualKeysArray;
1875             if (stateObject->getArray("pressedVirtualKeys"_s, pressedVirtualKeysArray)) {
1876                 VirtualKeySet pressedVirtualKeys { };
1877
1878                 for (auto it = pressedVirtualKeysArray->begin(); it != pressedVirtualKeysArray->end(); ++it) {
1879                     String pressedVirtualKeyString;
1880                     if (!(*it)->asString(pressedVirtualKeyString))
1881                         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Encountered a non-string virtual key value.");
1882
1883                     Optional<VirtualKey> parsedVirtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(pressedVirtualKeyString);
1884                     if (!parsedVirtualKey)
1885                         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Encountered an unknown virtual key value.");
1886                     else
1887                         pressedVirtualKeys.add(parsedVirtualKey.value());
1888                 }
1889
1890                 sourceState.pressedVirtualKeys = pressedVirtualKeys;
1891             }
1892
1893             String pressedButtonString;
1894             if (stateObject->getString("pressedButton"_s, pressedButtonString)) {
1895                 auto protocolButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(pressedButtonString);
1896                 sourceState.pressedMouseButton = protocolMouseButtonToWebMouseEventButton(protocolButton.valueOr(Inspector::Protocol::Automation::MouseButton::None));
1897             }
1898
1899             String originString;
1900             if (stateObject->getString("origin"_s, originString))
1901                 sourceState.origin = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseMoveOrigin>(originString);
1902
1903             if (sourceState.origin && sourceState.origin.value() == Inspector::Protocol::Automation::MouseMoveOrigin::Element) {
1904                 String nodeHandleString;
1905                 if (!stateObject->getString("nodeHandle"_s, nodeHandleString))
1906                     ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Node handle not provided for 'Element' origin");
1907                 sourceState.nodeHandle = nodeHandleString;
1908             }
1909
1910             RefPtr<JSON::Object> locationObject;
1911             if (stateObject->getObject("location"_s, locationObject)) {
1912                 int x, y;
1913                 if (locationObject->getInteger("x"_s, x) && locationObject->getInteger("y"_s, y))
1914                     sourceState.location = WebCore::IntPoint(x, y);
1915             }
1916
1917             int parsedDuration;
1918             if (stateObject->getInteger("duration"_s, parsedDuration))
1919                 sourceState.duration = Seconds::fromMilliseconds(parsedDuration);
1920
1921             entries.uncheckedAppend(std::pair<SimulatedInputSource&, SimulatedInputSourceState> { inputSource, sourceState });
1922         }
1923         
1924         keyFrames.append(SimulatedInputKeyFrame(WTFMove(entries)));
1925     }
1926
1927     SimulatedInputDispatcher& inputDispatcher = inputDispatcherForPage(*page);
1928     if (inputDispatcher.isActive()) {
1929         ASSERT_NOT_REACHED();
1930         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "A previous interaction is still underway.");
1931     }
1932
1933     // Delegate the rest of §17.4 Dispatching Actions to the dispatcher.
1934     inputDispatcher.run(frameID.value(), WTFMove(keyFrames), m_inputSources, [protectedThis = makeRef(*this), callback = WTFMove(callback)](Optional<AutomationCommandError> error) {
1935         if (error)
1936             callback->sendFailure(error.value().toProtocolString());
1937         else
1938             callback->sendSuccess();
1939     });
1940 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1941 }
1942
1943 void WebAutomationSession::cancelInteractionSequence(const String& handle, const String* optionalFrameHandle, Ref<CancelInteractionSequenceCallback>&& callback)
1944 {
1945     // This command implements WebKit support for §17.6 Release Actions.
1946
1947 #if !ENABLE(WEBDRIVER_ACTIONS_API)
1948     ASYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1949 #else
1950     WebPageProxy* page = webPageProxyForHandle(handle);
1951     if (!page)
1952         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1953
1954     auto frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
1955     if (!frameID)
1956         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1957
1958     Vector<SimulatedInputKeyFrame> keyFrames({ SimulatedInputKeyFrame::keyFrameToResetInputSources(m_inputSources) });
1959     SimulatedInputDispatcher& inputDispatcher = inputDispatcherForPage(*page);
1960     inputDispatcher.cancel();
1961     
1962     inputDispatcher.run(frameID.value(), WTFMove(keyFrames), m_inputSources, [protectedThis = makeRef(*this), callback = WTFMove(callback)](Optional<AutomationCommandError> error) {
1963         if (error)
1964             callback->sendFailure(error.value().toProtocolString());
1965         else
1966             callback->sendSuccess();
1967     });
1968 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1969 }
1970
1971 void WebAutomationSession::takeScreenshot(const String& handle, const String* optionalFrameHandle, const String* optionalNodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* optionalClipToViewport, Ref<TakeScreenshotCallback>&& callback)
1972 {
1973     WebPageProxy* page = webPageProxyForHandle(handle);
1974     if (!page)
1975         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1976
1977     Optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
1978     if (!frameID)
1979         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1980
1981     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
1982     String nodeHandle = optionalNodeHandle ? *optionalNodeHandle : emptyString();
1983     bool clipToViewport = optionalClipToViewport ? *optionalClipToViewport : false;
1984
1985     uint64_t callbackID = m_nextScreenshotCallbackID++;
1986     m_screenshotCallbacks.set(callbackID, WTFMove(callback));
1987
1988     page->process().send(Messages::WebAutomationSessionProxy::TakeScreenshot(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, clipToViewport, callbackID), 0);
1989 }
1990
1991 void WebAutomationSession::didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle& imageDataHandle, const String& errorType)
1992 {
1993     auto callback = m_screenshotCallbacks.take(callbackID);
1994     if (!callback)
1995         return;
1996
1997     if (!errorType.isEmpty()) {
1998         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1999         return;
2000     }
2001
2002     Optional<String> base64EncodedData = platformGetBase64EncodedPNGData(imageDataHandle);
2003     if (!base64EncodedData)
2004         ASYNC_FAIL_WITH_PREDEFINED_ERROR(InternalError);
2005
2006     callback->sendSuccess(base64EncodedData.value());
2007 }
2008
2009 #if !PLATFORM(COCOA) && !USE(CAIRO)
2010 Optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&)
2011 {
2012     return WTF::nullopt;
2013 }
2014 #endif // !PLATFORM(COCOA) && !USE(CAIRO)
2015
2016 #if !PLATFORM(COCOA)
2017 Optional<String> WebAutomationSession::platformGenerateLocalFilePathForRemoteFile(const String&, const String&)
2018 {
2019     return WTF::nullopt;
2020 }
2021 #endif // !PLATFORM(COCOA)
2022
2023 } // namespace WebKit