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