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