Use ObjectIdentifier<FrameIdentifierType> for frameIDs
[WebKit.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(std::make_unique<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(std::make_unique<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.pageID());
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.pageID(), handle);
197     RELEASE_ASSERT(firstAddResult.isNewEntry);
198
199     auto secondAddResult = m_handleWebPageMap.add(handle, webPageProxy.pageID());
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->pageID(), 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.pageID(), WTFMove(callback));
501         break;
502     case Inspector::Protocol::Automation::PageLoadStrategy::Eager:
503         m_pendingEagerNavigationInBrowsingContextCallbacksPerPage.set(page.pageID(), 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<PageIdentifier, 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()->pageID())) {
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()->pageID())) {
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.pageID()))
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.pageID()))
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.pageID()))
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.pageID()))
828         callback(AUTOMATION_COMMAND_ERROR_WITH_NAME(WindowNotFound));
829 #endif
830 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
831     if (auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID()))
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.pageID());
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->pageID(), 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->pageID(), frameID, *optionalNodeHandle), WTFMove(completionHandler));
991         return;
992     }
993
994     if (optionalName) {
995         page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::ResolveChildFrameWithName(page->pageID(), frameID, *optionalName), WTFMove(completionHandler));
996         return;
997     }
998
999     if (optionalOrdinal) {
1000         page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::ResolveChildFrameWithOrdinal(page->pageID(), 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->pageID(), 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->pageID(), 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->pageID(), 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->pageID(), 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->pageID(), 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.pageID(), [&] {
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, 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.pageID(), frameID, nodeHandle, false, CoordinateSystem::LayoutViewport), WTFMove(didComputeElementLayoutHandler));
1482 }
1483
1484 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1485 void WebAutomationSession::simulateMouseInteraction(WebPageProxy& page, MouseInteraction interaction, WebMouseEvent::Button mouseButton, const WebCore::IntPoint& locationInViewport, CompletionHandler<void(Optional<AutomationCommandError>)>&& completionHandler)
1486 {
1487     page.getWindowFrameWithCallback([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler), page = makeRef(page), interaction, mouseButton, locationInViewport](WebCore::FloatRect windowFrame) mutable {
1488         auto clippedX = std::min(std::max(0.0f, (float)locationInViewport.x()), windowFrame.size().width());
1489         auto clippedY = std::min(std::max(0.0f, (float)locationInViewport.y()), windowFrame.size().height());
1490         if (clippedX != locationInViewport.x() || clippedY != locationInViewport.y()) {
1491             completionHandler(AUTOMATION_COMMAND_ERROR_WITH_NAME(TargetOutOfBounds));
1492             return;
1493         }
1494
1495         // Bridge the flushed callback to our command's completion handler.
1496         auto mouseEventsFlushedCallback = [completionHandler = WTFMove(completionHandler)](Optional<AutomationCommandError> error) mutable {
1497             completionHandler(error);
1498         };
1499
1500         auto& callbackInMap = m_pendingMouseEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1501         if (callbackInMap)
1502             callbackInMap(AUTOMATION_COMMAND_ERROR_WITH_NAME(Timeout));
1503         callbackInMap = WTFMove(mouseEventsFlushedCallback);
1504
1505         platformSimulateMouseInteraction(page, interaction, mouseButton, locationInViewport, OptionSet<WebEvent::Modifier>::fromRaw(m_currentModifiers));
1506
1507         // If the event does not hit test anything in the window, then it may not have been delivered.
1508         if (callbackInMap && !page->isProcessingMouseEvents()) {
1509             auto callbackToCancel = m_pendingMouseEventsFlushedCallbacksPerPage.take(page->pageID());
1510             callbackToCancel(WTF::nullopt);
1511         }
1512
1513         // Otherwise, wait for mouseEventsFlushedCallback to run when all events are handled.
1514     });
1515 }
1516 #endif // ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1517
1518 #if ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1519 void WebAutomationSession::simulateTouchInteraction(WebPageProxy& page, TouchInteraction interaction, const WebCore::IntPoint& locationInViewport, Optional<Seconds> duration, CompletionHandler<void(Optional<AutomationCommandError>)>&& completionHandler)
1520 {
1521 #if PLATFORM(IOS_FAMILY)
1522     WebCore::FloatRect visualViewportBounds = WebCore::FloatRect({ }, page.unobscuredContentRect().size());
1523     if (!visualViewportBounds.contains(locationInViewport)) {
1524         completionHandler(AUTOMATION_COMMAND_ERROR_WITH_NAME(TargetOutOfBounds));
1525         return;
1526     }
1527 #endif
1528
1529     m_simulatingTouchInteraction = true;
1530     platformSimulateTouchInteraction(page, interaction, locationInViewport, duration, [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](Optional<AutomationCommandError> error) mutable {
1531         m_simulatingTouchInteraction = false;
1532         completionHandler(error);
1533     });
1534 }
1535 #endif // ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1536
1537 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1538 void WebAutomationSession::simulateKeyboardInteraction(WebPageProxy& page, KeyboardInteraction interaction, WTF::Variant<VirtualKey, CharKey>&& key, CompletionHandler<void(Optional<AutomationCommandError>)>&& completionHandler)
1539 {
1540     // Bridge the flushed callback to our command's completion handler.
1541     auto keyboardEventsFlushedCallback = [completionHandler = WTFMove(completionHandler)](Optional<AutomationCommandError> error) mutable {
1542         completionHandler(error);
1543     };
1544
1545     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page.pageID(), nullptr).iterator->value;
1546     if (callbackInMap)
1547         callbackInMap(AUTOMATION_COMMAND_ERROR_WITH_NAME(Timeout));
1548     callbackInMap = WTFMove(keyboardEventsFlushedCallback);
1549
1550     platformSimulateKeyboardInteraction(page, interaction, WTFMove(key));
1551
1552     // If the interaction does not generate any events, then do not wait for events to be flushed.
1553     // This happens in some corner cases on macOS, such as releasing a key while Command is pressed.
1554     if (callbackInMap && !page.isProcessingKeyboardEvents()) {
1555         auto callbackToCancel = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID());
1556         callbackToCancel(WTF::nullopt);
1557     }
1558
1559     // Otherwise, wait for keyboardEventsFlushedCallback to run when all events are handled.
1560 }
1561 #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1562 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1563
1564 #if ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1565 static WebEvent::Modifier protocolModifierToWebEventModifier(Inspector::Protocol::Automation::KeyModifier modifier)
1566 {
1567     switch (modifier) {
1568     case Inspector::Protocol::Automation::KeyModifier::Alt:
1569         return WebEvent::Modifier::AltKey;
1570     case Inspector::Protocol::Automation::KeyModifier::Meta:
1571         return WebEvent::Modifier::MetaKey;
1572     case Inspector::Protocol::Automation::KeyModifier::Control:
1573         return WebEvent::Modifier::ControlKey;
1574     case Inspector::Protocol::Automation::KeyModifier::Shift:
1575         return WebEvent::Modifier::ShiftKey;
1576     case Inspector::Protocol::Automation::KeyModifier::CapsLock:
1577         return WebEvent::Modifier::CapsLockKey;
1578     }
1579
1580     RELEASE_ASSERT_NOT_REACHED();
1581 }
1582 #endif // ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1583
1584 #if ENABLE(WEBDRIVER_ACTIONS_API)
1585 static WebMouseEvent::Button protocolMouseButtonToWebMouseEventButton(Inspector::Protocol::Automation::MouseButton button)
1586 {
1587     switch (button) {
1588     case Inspector::Protocol::Automation::MouseButton::None:
1589         return WebMouseEvent::NoButton;
1590     case Inspector::Protocol::Automation::MouseButton::Left:
1591         return WebMouseEvent::LeftButton;
1592     case Inspector::Protocol::Automation::MouseButton::Middle:
1593         return WebMouseEvent::MiddleButton;
1594     case Inspector::Protocol::Automation::MouseButton::Right:
1595         return WebMouseEvent::RightButton;
1596     }
1597
1598     RELEASE_ASSERT_NOT_REACHED();
1599 }
1600 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1601
1602 void WebAutomationSession::performMouseInteraction(const String& handle, const JSON::Object& requestedPositionObject, const String& mouseButtonString, const String& mouseInteractionString, const JSON::Array& keyModifierStrings, Ref<PerformMouseInteractionCallback>&& callback)
1603 {
1604 #if !ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1605     ASYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1606 #else
1607     WebPageProxy* page = webPageProxyForHandle(handle);
1608     if (!page)
1609         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1610
1611     float x;
1612     if (!requestedPositionObject.getDouble("x"_s, x))
1613         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'x' was not found.");
1614
1615     float y;
1616     if (!requestedPositionObject.getDouble("y"_s, y))
1617         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'y' was not found.");
1618
1619     OptionSet<WebEvent::Modifier> keyModifiers;
1620     for (auto it = keyModifierStrings.begin(); it != keyModifierStrings.end(); ++it) {
1621         String modifierString;
1622         if (!it->get()->asString(modifierString))
1623             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'modifiers' is invalid.");
1624
1625         auto parsedModifier = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyModifier>(modifierString);
1626         if (!parsedModifier)
1627             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A modifier in the 'modifiers' array is invalid.");
1628         keyModifiers.add(protocolModifierToWebEventModifier(parsedModifier.value()));
1629     }
1630
1631     page->getWindowFrameWithCallback([this, protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRef(*page), x, y, mouseInteractionString, mouseButtonString, keyModifiers](WebCore::FloatRect windowFrame) mutable {
1632
1633         x = std::min(std::max(0.0f, x), windowFrame.size().width());
1634         y = std::min(std::max(0.0f, y), windowFrame.size().height());
1635
1636         WebCore::IntPoint locationInViewport = WebCore::IntPoint(static_cast<int>(x), static_cast<int>(y));
1637
1638         auto parsedInteraction = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseInteraction>(mouseInteractionString);
1639         if (!parsedInteraction)
1640             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interaction' is invalid.");
1641
1642         auto parsedButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(mouseButtonString);
1643         if (!parsedButton)
1644             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'button' is invalid.");
1645
1646         auto mouseEventsFlushedCallback = [protectedThis = WTFMove(protectedThis), callback = WTFMove(callback), page = page.copyRef(), x, y](Optional<AutomationCommandError> error) {
1647             if (error)
1648                 callback->sendFailure(error.value().toProtocolString());
1649             else {
1650                 callback->sendSuccess(Inspector::Protocol::Automation::Point::create()
1651                     .setX(x)
1652                     .setY(y - page->topContentInset())
1653                     .release());
1654             }
1655         };
1656
1657         auto& callbackInMap = m_pendingMouseEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1658         if (callbackInMap)
1659             callbackInMap(AUTOMATION_COMMAND_ERROR_WITH_NAME(Timeout));
1660         callbackInMap = WTFMove(mouseEventsFlushedCallback);
1661
1662         platformSimulateMouseInteraction(page, parsedInteraction.value(), protocolMouseButtonToWebMouseEventButton(parsedButton.value()), locationInViewport, keyModifiers);
1663
1664         // If the event location was previously clipped and does not hit test anything in the window, then it will not be processed.
1665         // For compatibility with pre-W3C driver implementations, don't make this a hard error; just do nothing silently.
1666         // In W3C-only code paths, we can reject any pointer actions whose coordinates are outside the viewport rect.
1667         if (callbackInMap && !page->isProcessingMouseEvents()) {
1668             auto callbackToCancel = m_pendingMouseEventsFlushedCallbacksPerPage.take(page->pageID());
1669             callbackToCancel(WTF::nullopt);
1670         }
1671     });
1672 #endif // ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1673 }
1674
1675 void WebAutomationSession::performKeyboardInteractions(const String& handle, const JSON::Array& interactions, Ref<PerformKeyboardInteractionsCallback>&& callback)
1676 {
1677 #if !ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1678     ASYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1679 #else
1680     WebPageProxy* page = webPageProxyForHandle(handle);
1681     if (!page)
1682         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1683
1684     if (!interactions.length())
1685         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interactions' was not found or empty.");
1686
1687     // Validate all of the parameters before performing any interactions with the browsing context under test.
1688     Vector<WTF::Function<void()>> actionsToPerform;
1689     actionsToPerform.reserveCapacity(interactions.length());
1690
1691     for (const auto& interaction : interactions) {
1692         RefPtr<JSON::Object> interactionObject;
1693         if (!interaction->asObject(interactionObject))
1694             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter was invalid.");
1695
1696         String interactionTypeString;
1697         if (!interactionObject->getString("type"_s, interactionTypeString))
1698             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter is missing the 'type' key.");
1699         auto interactionType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyboardInteractionType>(interactionTypeString);
1700         if (!interactionType)
1701             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'type' key.");
1702
1703         String virtualKeyString;
1704         bool foundVirtualKey = interactionObject->getString("key"_s, virtualKeyString);
1705         if (foundVirtualKey) {
1706             Optional<VirtualKey> virtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(virtualKeyString);
1707             if (!virtualKey)
1708                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1709
1710             actionsToPerform.uncheckedAppend([this, page, interactionType, virtualKey] {
1711                 platformSimulateKeyboardInteraction(*page, interactionType.value(), virtualKey.value());
1712             });
1713         }
1714
1715         String keySequence;
1716         bool foundKeySequence = interactionObject->getString("text"_s, keySequence);
1717         if (foundKeySequence) {
1718             switch (interactionType.value()) {
1719             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress:
1720             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease:
1721                 // 'KeyPress' and 'KeyRelease' are meant for a virtual key and are not supported for a string (sequence of codepoints).
1722                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1723
1724             case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey:
1725                 actionsToPerform.uncheckedAppend([this, page, keySequence] {
1726                     platformSimulateKeySequence(*page, keySequence);
1727                 });
1728                 break;
1729             }
1730         }
1731
1732         if (!foundVirtualKey && !foundKeySequence)
1733             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "An interaction in the 'interactions' parameter is missing both 'key' and 'text'. One must be provided.");
1734     }
1735
1736     ASSERT(actionsToPerform.size());
1737     if (!actionsToPerform.size())
1738         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "No actions to perform.");
1739
1740     auto keyboardEventsFlushedCallback = [protectedThis = makeRef(*this), callback = WTFMove(callback), page = makeRef(*page)](Optional<AutomationCommandError> error) {
1741         if (error)
1742             callback->sendFailure(error.value().toProtocolString());
1743         else
1744             callback->sendSuccess();
1745     };
1746
1747     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1748     if (callbackInMap)
1749         callbackInMap(AUTOMATION_COMMAND_ERROR_WITH_NAME(Timeout));
1750     callbackInMap = WTFMove(keyboardEventsFlushedCallback);
1751
1752     for (auto& action : actionsToPerform)
1753         action();
1754 #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1755 }
1756
1757 #if ENABLE(WEBDRIVER_ACTIONS_API)
1758 static SimulatedInputSourceType simulatedInputSourceTypeFromProtocolSourceType(Inspector::Protocol::Automation::InputSourceType protocolType)
1759 {
1760     switch (protocolType) {
1761     case Inspector::Protocol::Automation::InputSourceType::Null:
1762         return SimulatedInputSourceType::Null;
1763     case Inspector::Protocol::Automation::InputSourceType::Keyboard:
1764         return SimulatedInputSourceType::Keyboard;
1765     case Inspector::Protocol::Automation::InputSourceType::Mouse:
1766         return SimulatedInputSourceType::Mouse;
1767     case Inspector::Protocol::Automation::InputSourceType::Touch:
1768         return SimulatedInputSourceType::Touch;
1769     }
1770
1771     RELEASE_ASSERT_NOT_REACHED();
1772 }
1773 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1774
1775 void WebAutomationSession::performInteractionSequence(const String& handle, const String* optionalFrameHandle, const JSON::Array& inputSources, const JSON::Array& steps, Ref<WebAutomationSession::PerformInteractionSequenceCallback>&& callback)
1776 {
1777     // This command implements WebKit support for §17.5 Perform Actions.
1778
1779 #if !ENABLE(WEBDRIVER_ACTIONS_API)
1780     ASYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1781 #else
1782     WebPageProxy* page = webPageProxyForHandle(handle);
1783     if (!page)
1784         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1785
1786     bool frameNotFound = false;
1787     auto frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString(), frameNotFound);
1788     if (frameNotFound)
1789         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1790
1791     HashMap<String, Ref<SimulatedInputSource>> sourceIdToInputSourceMap;
1792     HashMap<SimulatedInputSourceType, String, WTF::IntHash<SimulatedInputSourceType>, WTF::StrongEnumHashTraits<SimulatedInputSourceType>> typeToSourceIdMap;
1793
1794     // Parse and validate Automation protocol arguments. By this point, the driver has
1795     // already performed the steps in §17.3 Processing Actions Requests.
1796     if (!inputSources.length())
1797         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'inputSources' was not found or empty.");
1798
1799     for (const auto& inputSource : inputSources) {
1800         RefPtr<JSON::Object> inputSourceObject;
1801         if (!inputSource->asObject(inputSourceObject))
1802             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An input source in the 'inputSources' parameter was invalid.");
1803         
1804         String sourceId;
1805         if (!inputSourceObject->getString("sourceId"_s, sourceId))
1806             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An input source in the 'inputSources' parameter is missing a 'sourceId'.");
1807
1808         String sourceType;
1809         if (!inputSourceObject->getString("sourceType"_s, sourceType))
1810             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An input source in the 'inputSources' parameter is missing a 'sourceType'.");
1811
1812         auto parsedInputSourceType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::InputSourceType>(sourceType);
1813         if (!parsedInputSourceType)
1814             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An input source in the 'inputSources' parameter has an invalid 'sourceType'.");
1815
1816         SimulatedInputSourceType inputSourceType = simulatedInputSourceTypeFromProtocolSourceType(*parsedInputSourceType);
1817
1818         // Note: iOS does not support mouse input sources, and other platforms do not support touch input sources.
1819         // If a mismatch happens, alias to the supported input source. This works because both Mouse and Touch input sources
1820         // use a MouseButton to indicate the result of interacting (down/up/move), which can be interpreted for touch or mouse.
1821 #if !ENABLE(WEBDRIVER_MOUSE_INTERACTIONS) && ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1822         if (inputSourceType == SimulatedInputSourceType::Mouse)
1823             inputSourceType = SimulatedInputSourceType::Touch;
1824 #endif
1825 #if !ENABLE(WEBDRIVER_MOUSE_INTERACTIONS)
1826         if (inputSourceType == SimulatedInputSourceType::Mouse)
1827             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(NotImplemented, "Mouse input sources are not yet supported.");
1828 #endif
1829 #if !ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
1830         if (inputSourceType == SimulatedInputSourceType::Touch)
1831             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(NotImplemented, "Touch input sources are not yet supported.");
1832 #endif
1833 #if !ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
1834         if (inputSourceType == SimulatedInputSourceType::Keyboard)
1835             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(NotImplemented, "Keyboard input sources are not yet supported.");
1836 #endif
1837         if (typeToSourceIdMap.contains(inputSourceType))
1838             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Two input sources with the same type were specified.");
1839         if (sourceIdToInputSourceMap.contains(sourceId))
1840             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Two input sources with the same sourceId were specified.");
1841
1842         typeToSourceIdMap.add(inputSourceType, sourceId);
1843         sourceIdToInputSourceMap.add(sourceId, *inputSourceForType(inputSourceType));
1844     }
1845
1846     Vector<SimulatedInputKeyFrame> keyFrames;
1847
1848     if (!steps.length())
1849         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'steps' was not found or empty.");
1850
1851     for (const auto& step : steps) {
1852         RefPtr<JSON::Object> stepObject;
1853         if (!step->asObject(stepObject))
1854             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A step in the 'steps' parameter was not an object.");
1855
1856         RefPtr<JSON::Array> stepStates;
1857         if (!stepObject->getArray("states"_s, stepStates))
1858             ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A step is missing the 'states' property.");
1859
1860         Vector<SimulatedInputKeyFrame::StateEntry> entries;
1861         entries.reserveCapacity(stepStates->length());
1862
1863         for (const auto& state : *stepStates) {
1864             RefPtr<JSON::Object> stateObject;
1865             if (!state->asObject(stateObject))
1866                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Encountered a non-object step state.");
1867
1868             String sourceId;
1869             if (!stateObject->getString("sourceId"_s, sourceId))
1870                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Step state lacks required 'sourceId' property.");
1871
1872             if (!sourceIdToInputSourceMap.contains(sourceId))
1873                 ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Unknown 'sourceId' specified.");
1874
1875             SimulatedInputSource& inputSource = *sourceIdToInputSourceMap.get(sourceId);
1876             SimulatedInputSourceState sourceState { };
1877
1878             String pressedCharKeyString;
1879             if (stateObject->getString("pressedCharKey"_s, pressedCharKeyString))
1880                 sourceState.pressedCharKey = pressedCharKeyString.characterAt(0);
1881
1882             RefPtr<JSON::Array> pressedVirtualKeysArray;
1883             if (stateObject->getArray("pressedVirtualKeys"_s, pressedVirtualKeysArray)) {
1884                 VirtualKeySet pressedVirtualKeys { };
1885
1886                 for (auto it = pressedVirtualKeysArray->begin(); it != pressedVirtualKeysArray->end(); ++it) {
1887                     String pressedVirtualKeyString;
1888                     if (!(*it)->asString(pressedVirtualKeyString))
1889                         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Encountered a non-string virtual key value.");
1890
1891                     Optional<VirtualKey> parsedVirtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(pressedVirtualKeyString);
1892                     if (!parsedVirtualKey)
1893                         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Encountered an unknown virtual key value.");
1894                     else
1895                         pressedVirtualKeys.add(parsedVirtualKey.value());
1896                 }
1897
1898                 sourceState.pressedVirtualKeys = pressedVirtualKeys;
1899             }
1900
1901             String pressedButtonString;
1902             if (stateObject->getString("pressedButton"_s, pressedButtonString)) {
1903                 auto protocolButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(pressedButtonString);
1904                 sourceState.pressedMouseButton = protocolMouseButtonToWebMouseEventButton(protocolButton.valueOr(Inspector::Protocol::Automation::MouseButton::None));
1905             }
1906
1907             String originString;
1908             if (stateObject->getString("origin"_s, originString))
1909                 sourceState.origin = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseMoveOrigin>(originString);
1910
1911             if (sourceState.origin && sourceState.origin.value() == Inspector::Protocol::Automation::MouseMoveOrigin::Element) {
1912                 String nodeHandleString;
1913                 if (!stateObject->getString("nodeHandle"_s, nodeHandleString))
1914                     ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "Node handle not provided for 'Element' origin");
1915                 sourceState.nodeHandle = nodeHandleString;
1916             }
1917
1918             RefPtr<JSON::Object> locationObject;
1919             if (stateObject->getObject("location"_s, locationObject)) {
1920                 int x, y;
1921                 if (locationObject->getInteger("x"_s, x) && locationObject->getInteger("y"_s, y))
1922                     sourceState.location = WebCore::IntPoint(x, y);
1923             }
1924
1925             int parsedDuration;
1926             if (stateObject->getInteger("duration"_s, parsedDuration))
1927                 sourceState.duration = Seconds::fromMilliseconds(parsedDuration);
1928
1929             entries.uncheckedAppend(std::pair<SimulatedInputSource&, SimulatedInputSourceState> { inputSource, sourceState });
1930         }
1931         
1932         keyFrames.append(SimulatedInputKeyFrame(WTFMove(entries)));
1933     }
1934
1935     SimulatedInputDispatcher& inputDispatcher = inputDispatcherForPage(*page);
1936     if (inputDispatcher.isActive()) {
1937         ASSERT_NOT_REACHED();
1938         ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "A previous interaction is still underway.");
1939     }
1940
1941     // Delegate the rest of §17.4 Dispatching Actions to the dispatcher.
1942     inputDispatcher.run(frameID.value(), WTFMove(keyFrames), m_inputSources, [protectedThis = makeRef(*this), callback = WTFMove(callback)](Optional<AutomationCommandError> error) {
1943         if (error)
1944             callback->sendFailure(error.value().toProtocolString());
1945         else
1946             callback->sendSuccess();
1947     });
1948 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1949 }
1950
1951 void WebAutomationSession::cancelInteractionSequence(const String& handle, const String* optionalFrameHandle, Ref<CancelInteractionSequenceCallback>&& callback)
1952 {
1953     // This command implements WebKit support for §17.6 Release Actions.
1954
1955 #if !ENABLE(WEBDRIVER_ACTIONS_API)
1956     ASYNC_FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1957 #else
1958     WebPageProxy* page = webPageProxyForHandle(handle);
1959     if (!page)
1960         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1961
1962     bool frameNotFound = false;
1963     auto frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString(), frameNotFound);
1964     if (frameNotFound)
1965         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1966
1967     Vector<SimulatedInputKeyFrame> keyFrames({ SimulatedInputKeyFrame::keyFrameToResetInputSources(m_inputSources) });
1968     SimulatedInputDispatcher& inputDispatcher = inputDispatcherForPage(*page);
1969     inputDispatcher.cancel();
1970     
1971     inputDispatcher.run(frameID.value(), WTFMove(keyFrames), m_inputSources, [protectedThis = makeRef(*this), callback = WTFMove(callback)](Optional<AutomationCommandError> error) {
1972         if (error)
1973             callback->sendFailure(error.value().toProtocolString());
1974         else
1975             callback->sendSuccess();
1976     });
1977 #endif // ENABLE(WEBDRIVER_ACTIONS_API)
1978 }
1979
1980 void WebAutomationSession::takeScreenshot(const String& handle, const String* optionalFrameHandle, const String* optionalNodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* optionalClipToViewport, Ref<TakeScreenshotCallback>&& callback)
1981 {
1982     WebPageProxy* page = webPageProxyForHandle(handle);
1983     if (!page)
1984         ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1985
1986     bool frameNotFound = false;
1987     auto frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString(), frameNotFound);
1988     if (frameNotFound)
1989         ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
1990
1991     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
1992     String nodeHandle = optionalNodeHandle ? *optionalNodeHandle : emptyString();
1993     bool clipToViewport = optionalClipToViewport ? *optionalClipToViewport : false;
1994
1995     uint64_t callbackID = m_nextScreenshotCallbackID++;
1996     m_screenshotCallbacks.set(callbackID, WTFMove(callback));
1997
1998     page->process().send(Messages::WebAutomationSessionProxy::TakeScreenshot(page->pageID(), frameID, nodeHandle, scrollIntoViewIfNeeded, clipToViewport, callbackID), 0);
1999 }
2000
2001 void WebAutomationSession::didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle& imageDataHandle, const String& errorType)
2002 {
2003     auto callback = m_screenshotCallbacks.take(callbackID);
2004     if (!callback)
2005         return;
2006
2007     if (!errorType.isEmpty()) {
2008         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
2009         return;
2010     }
2011
2012     Optional<String> base64EncodedData = platformGetBase64EncodedPNGData(imageDataHandle);
2013     if (!base64EncodedData)
2014         ASYNC_FAIL_WITH_PREDEFINED_ERROR(InternalError);
2015
2016     callback->sendSuccess(base64EncodedData.value());
2017 }
2018
2019 #if !PLATFORM(COCOA) && !USE(CAIRO)
2020 Optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&)
2021 {
2022     return WTF::nullopt;
2023 }
2024 #endif // !PLATFORM(COCOA) && !USE(CAIRO)
2025
2026 #if !PLATFORM(COCOA)
2027 Optional<String> WebAutomationSession::platformGenerateLocalFilePathForRemoteFile(const String&, const String&)
2028 {
2029     return WTF::nullopt;
2030 }
2031 #endif // !PLATFORM(COCOA)
2032
2033 } // namespace WebKit