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