WebDriver: Implement page load strategy
[WebKit-https.git] / Source / WebKit / UIProcess / Automation / WebAutomationSession.cpp
1 /*
2  * Copyright (C) 2016, 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "WebAutomationSession.h"
28
29 #include "APIArray.h"
30 #include "APIAutomationSessionClient.h"
31 #include "APIOpenPanelParameters.h"
32 #include "AutomationProtocolObjects.h"
33 #include "WebAutomationSessionMacros.h"
34 #include "WebAutomationSessionMessages.h"
35 #include "WebAutomationSessionProxyMessages.h"
36 #include "WebCookieManagerProxy.h"
37 #include "WebInspectorProxy.h"
38 #include "WebOpenPanelResultListenerProxy.h"
39 #include "WebProcessPool.h"
40 #include <JavaScriptCore/InspectorBackendDispatcher.h>
41 #include <JavaScriptCore/InspectorFrontendRouter.h>
42 #include <WebCore/MIMETypeRegistry.h>
43 #include <WebCore/URL.h>
44 #include <algorithm>
45 #include <wtf/HashMap.h>
46 #include <wtf/Optional.h>
47 #include <wtf/UUID.h>
48 #include <wtf/text/StringConcatenate.h>
49
50 using namespace Inspector;
51
52 namespace WebKit {
53
54 // §8. Sessions
55 // https://www.w3.org/TR/webdriver/#dfn-session-page-load-timeout
56 static const Seconds defaultPageLoadTimeout = 300_s;
57 // https://www.w3.org/TR/webdriver/#dfn-page-loading-strategy
58 static const Inspector::Protocol::Automation::PageLoadStrategy defaultPageLoadStrategy = Inspector::Protocol::Automation::PageLoadStrategy::Normal;
59
60 WebAutomationSession::WebAutomationSession()
61     : m_client(std::make_unique<API::AutomationSessionClient>())
62     , m_frontendRouter(FrontendRouter::create())
63     , m_backendDispatcher(BackendDispatcher::create(m_frontendRouter.copyRef()))
64     , m_domainDispatcher(AutomationBackendDispatcher::create(m_backendDispatcher, this))
65     , m_domainNotifier(std::make_unique<AutomationFrontendDispatcher>(m_frontendRouter))
66     , m_loadTimer(RunLoop::main(), this, &WebAutomationSession::loadTimerFired)
67 {
68 }
69
70 WebAutomationSession::~WebAutomationSession()
71 {
72     ASSERT(!m_client);
73
74     if (m_processPool)
75         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
76 }
77
78 void WebAutomationSession::setClient(std::unique_ptr<API::AutomationSessionClient>&& client)
79 {
80     m_client = WTFMove(client);
81 }
82
83 void WebAutomationSession::setProcessPool(WebKit::WebProcessPool* processPool)
84 {
85     if (m_processPool)
86         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
87
88     m_processPool = processPool;
89
90     if (m_processPool)
91         m_processPool->addMessageReceiver(Messages::WebAutomationSession::messageReceiverName(), *this);
92 }
93
94 // NOTE: this class could be split at some point to support local and remote automation sessions.
95 // For now, it only works with a remote automation driver over a RemoteInspector connection.
96
97 #if ENABLE(REMOTE_INSPECTOR)
98
99 // Inspector::RemoteAutomationTarget API
100
101 void WebAutomationSession::dispatchMessageFromRemote(const String& message)
102 {
103     m_backendDispatcher->dispatch(message);
104 }
105
106 void WebAutomationSession::connect(Inspector::FrontendChannel* channel, bool isAutomaticConnection, bool immediatelyPause)
107 {
108     UNUSED_PARAM(isAutomaticConnection);
109     UNUSED_PARAM(immediatelyPause);
110
111     m_remoteChannel = channel;
112     m_frontendRouter->connectFrontend(channel);
113
114     setIsPaired(true);
115 }
116
117 void WebAutomationSession::disconnect(Inspector::FrontendChannel* channel)
118 {
119     ASSERT(channel == m_remoteChannel);
120     terminate();
121 }
122
123 #endif // ENABLE(REMOTE_INSPECTOR)
124
125 void WebAutomationSession::terminate()
126 {
127 #if ENABLE(REMOTE_INSPECTOR)
128     if (Inspector::FrontendChannel* channel = m_remoteChannel) {
129         m_remoteChannel = nullptr;
130         m_frontendRouter->disconnectFrontend(channel);
131     }
132
133     setIsPaired(false);
134 #endif
135
136     if (m_client)
137         m_client->didDisconnectFromRemote(*this);
138 }
139
140 WebPageProxy* WebAutomationSession::webPageProxyForHandle(const String& handle)
141 {
142     auto iter = m_handleWebPageMap.find(handle);
143     if (iter == m_handleWebPageMap.end())
144         return nullptr;
145     return WebProcessProxy::webPage(iter->value);
146 }
147
148 String WebAutomationSession::handleForWebPageProxy(const WebPageProxy& webPageProxy)
149 {
150     auto iter = m_webPageHandleMap.find(webPageProxy.pageID());
151     if (iter != m_webPageHandleMap.end())
152         return iter->value;
153
154     String handle = "page-" + createCanonicalUUIDString().convertToASCIIUppercase();
155
156     auto firstAddResult = m_webPageHandleMap.add(webPageProxy.pageID(), handle);
157     RELEASE_ASSERT(firstAddResult.isNewEntry);
158
159     auto secondAddResult = m_handleWebPageMap.add(handle, webPageProxy.pageID());
160     RELEASE_ASSERT(secondAddResult.isNewEntry);
161
162     return handle;
163 }
164
165 std::optional<uint64_t> WebAutomationSession::webFrameIDForHandle(const String& handle)
166 {
167     if (handle.isEmpty())
168         return 0;
169
170     auto iter = m_handleWebFrameMap.find(handle);
171     if (iter == m_handleWebFrameMap.end())
172         return std::nullopt;
173
174     return iter->value;
175 }
176
177 String WebAutomationSession::handleForWebFrameID(uint64_t frameID)
178 {
179     if (!frameID)
180         return emptyString();
181
182     for (auto& process : m_processPool->processes()) {
183         if (WebFrameProxy* frame = process->webFrame(frameID)) {
184             if (frame->isMainFrame())
185                 return emptyString();
186             break;
187         }
188     }
189
190     auto iter = m_webFrameHandleMap.find(frameID);
191     if (iter != m_webFrameHandleMap.end())
192         return iter->value;
193
194     String handle = "frame-" + createCanonicalUUIDString().convertToASCIIUppercase();
195
196     auto firstAddResult = m_webFrameHandleMap.add(frameID, handle);
197     RELEASE_ASSERT(firstAddResult.isNewEntry);
198
199     auto secondAddResult = m_handleWebFrameMap.add(handle, frameID);
200     RELEASE_ASSERT(secondAddResult.isNewEntry);
201
202     return handle;
203 }
204
205 String WebAutomationSession::handleForWebFrameProxy(const WebFrameProxy& webFrameProxy)
206 {
207     return handleForWebFrameID(webFrameProxy.frameID());
208 }
209
210 RefPtr<Inspector::Protocol::Automation::BrowsingContext> WebAutomationSession::buildBrowsingContextForPage(WebPageProxy& page)
211 {
212     WebCore::FloatRect windowFrame;
213     page.getWindowFrame(windowFrame);
214
215     auto originObject = Inspector::Protocol::Automation::Point::create()
216         .setX(windowFrame.x())
217         .setY(windowFrame.y())
218         .release();
219
220     auto sizeObject = Inspector::Protocol::Automation::Size::create()
221         .setWidth(windowFrame.width())
222         .setHeight(windowFrame.height())
223         .release();
224
225     String handle = handleForWebPageProxy(page);
226
227     return Inspector::Protocol::Automation::BrowsingContext::create()
228         .setHandle(handle)
229         .setActive(m_activeBrowsingContextHandle == handle)
230         .setUrl(page.pageLoadState().activeURL())
231         .setWindowOrigin(WTFMove(originObject))
232         .setWindowSize(WTFMove(sizeObject))
233         .release();
234 }
235
236 // Platform-independent Commands.
237
238 void WebAutomationSession::getBrowsingContexts(Inspector::ErrorString& errorString, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>>& contexts)
239 {
240     contexts = Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>::create();
241
242     for (auto& process : m_processPool->processes()) {
243         for (auto* page : process->pages()) {
244             ASSERT(page);
245             if (!page->isControlledByAutomation())
246                 continue;
247
248             contexts->addItem(buildBrowsingContextForPage(*page));
249         }
250     }
251 }
252
253 void WebAutomationSession::getBrowsingContext(Inspector::ErrorString& errorString, const String& handle, RefPtr<Inspector::Protocol::Automation::BrowsingContext>& context)
254 {
255     WebPageProxy* page = webPageProxyForHandle(handle);
256     if (!page)
257         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
258
259     context = buildBrowsingContextForPage(*page);
260 }
261
262 void WebAutomationSession::createBrowsingContext(Inspector::ErrorString& errorString, String* handle)
263 {
264     ASSERT(m_client);
265     if (!m_client)
266         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session could not request a new browsing context.");
267
268     WebPageProxy* page = m_client->didRequestNewWindow(*this);
269     if (!page)
270         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session failed to create a new browsing context.");
271
272     m_activeBrowsingContextHandle = *handle = handleForWebPageProxy(*page);
273 }
274
275 void WebAutomationSession::closeBrowsingContext(Inspector::ErrorString& errorString, const String& handle)
276 {
277     WebPageProxy* page = webPageProxyForHandle(handle);
278     if (!page)
279         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
280
281     if (handle == m_activeBrowsingContextHandle)
282         m_activeBrowsingContextHandle = emptyString();
283
284     page->closePage(false);
285 }
286
287 void WebAutomationSession::switchToBrowsingContext(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle)
288 {
289     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
290     if (!page)
291         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
292
293     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
294     if (!frameID)
295         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
296
297     // FIXME: We don't need to track this in WK2. Remove in a follow up.
298     m_activeBrowsingContextHandle = browsingContextHandle;
299
300     page->setFocus(true);
301     page->process().send(Messages::WebAutomationSessionProxy::FocusFrame(page->pageID(), frameID.value()), 0);
302 }
303
304 void WebAutomationSession::resizeWindowOfBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& sizeObject)
305 {
306 #if PLATFORM(IOS)
307     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
308 #else
309     float width;
310     if (!sizeObject.getDouble(WTF::ASCIILiteral("width"), width))
311         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'width' parameter was not found or invalid.");
312
313     float height;
314     if (!sizeObject.getDouble(WTF::ASCIILiteral("height"), height))
315         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'height' parameter was not found or invalid.");
316
317     if (width < 0)
318         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'width' parameter had an invalid value.");
319
320     if (height < 0)
321         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'height' parameter had an invalid value.");
322
323     WebPageProxy* page = webPageProxyForHandle(handle);
324     if (!page)
325         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
326
327     WebCore::FloatRect originalFrame;
328     page->getWindowFrame(originalFrame);
329
330     WebCore::FloatRect newFrame = WebCore::FloatRect(originalFrame.location(), WebCore::FloatSize(width, height));
331     if (newFrame == originalFrame)
332         return;
333
334     page->setWindowFrame(newFrame);
335
336 #if !PLATFORM(GTK)
337     // If nothing changed at all, it's probably fair to report that something went wrong.
338     // (We can't assume that the requested frame size will be honored exactly, however.)
339     WebCore::FloatRect updatedFrame;
340     page->getWindowFrame(updatedFrame);
341     if (originalFrame == updatedFrame)
342         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The window size was expected to have changed, but did not.");
343 #endif
344 #endif
345 }
346
347 void WebAutomationSession::moveWindowOfBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& positionObject)
348 {
349 #if PLATFORM(IOS)
350     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
351 #else
352     float x;
353     if (!positionObject.getDouble(WTF::ASCIILiteral("x"), x))
354         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'x' parameter was not found or invalid.");
355
356     float y;
357     if (!positionObject.getDouble(WTF::ASCIILiteral("y"), y))
358         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'y' parameter was not found or invalid.");
359
360     if (x < 0)
361         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'x' parameter had an invalid value.");
362
363     if (y < 0)
364         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'y' parameter had an invalid value.");
365
366     WebPageProxy* page = webPageProxyForHandle(handle);
367     if (!page)
368         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
369
370     WebCore::FloatRect originalFrame;
371     page->getWindowFrame(originalFrame);
372
373     WebCore::FloatRect newFrame = WebCore::FloatRect(WebCore::FloatPoint(x, y), originalFrame.size());
374     if (newFrame == originalFrame)
375         return;
376
377     page->setWindowFrame(newFrame);
378
379 #if !PLATFORM(GTK)
380     // If nothing changed at all, it's probably fair to report that something went wrong.
381     // (We can't assume that the requested frame size will be honored exactly, however.)
382     WebCore::FloatRect updatedFrame;
383     page->getWindowFrame(updatedFrame);
384     if (originalFrame == updatedFrame)
385         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The window position was expected to have changed, but did not.");
386 #endif
387 #endif
388 }
389
390 static std::optional<Inspector::Protocol::Automation::PageLoadStrategy> pageLoadStrategyFromStringParameter(const String* optionalPageLoadStrategyString)
391 {
392     if (!optionalPageLoadStrategyString)
393         return defaultPageLoadStrategy;
394
395     auto parsedPageLoadStrategy = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::PageLoadStrategy>(*optionalPageLoadStrategyString);
396     if (!parsedPageLoadStrategy)
397         return std::nullopt;
398     return parsedPageLoadStrategy;
399 }
400
401 void WebAutomationSession::waitForNavigationToComplete(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<WaitForNavigationToCompleteCallback>&& callback)
402 {
403     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
404     if (!page)
405         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
406
407     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
408     if (!pageLoadStrategy)
409         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
410     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
411
412     if (optionalFrameHandle && !optionalFrameHandle->isEmpty()) {
413         std::optional<uint64_t> frameID = webFrameIDForHandle(*optionalFrameHandle);
414         if (!frameID)
415             FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
416         WebFrameProxy* frame = page->process().webFrame(frameID.value());
417         if (!frame)
418             FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
419         waitForNavigationToCompleteOnFrame(*frame, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
420     } else
421         waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
422 }
423
424 void WebAutomationSession::waitForNavigationToCompleteOnPage(WebPageProxy& page, Inspector::Protocol::Automation::PageLoadStrategy loadStrategy, Seconds timeout, Ref<Inspector::BackendDispatcher::CallbackBase>&& callback)
425 {
426     ASSERT(!m_loadTimer.isActive());
427     if (loadStrategy == Inspector::Protocol::Automation::PageLoadStrategy::None || !page.pageLoadState().isLoading()) {
428         callback->sendSuccess(InspectorObject::create());
429         return;
430     }
431
432     m_loadTimer.startOneShot(timeout);
433     switch (loadStrategy) {
434     case Inspector::Protocol::Automation::PageLoadStrategy::Normal:
435         m_pendingNormalNavigationInBrowsingContextCallbacksPerPage.set(page.pageID(), WTFMove(callback));
436         break;
437     case Inspector::Protocol::Automation::PageLoadStrategy::Eager:
438         m_pendingEagerNavigationInBrowsingContextCallbacksPerPage.set(page.pageID(), WTFMove(callback));
439         break;
440     case Inspector::Protocol::Automation::PageLoadStrategy::None:
441         ASSERT_NOT_REACHED();
442     }
443 }
444
445 void WebAutomationSession::waitForNavigationToCompleteOnFrame(WebFrameProxy& frame, Inspector::Protocol::Automation::PageLoadStrategy loadStrategy, Seconds timeout, Ref<Inspector::BackendDispatcher::CallbackBase>&& callback)
446 {
447     ASSERT(!m_loadTimer.isActive());
448     if (loadStrategy == Inspector::Protocol::Automation::PageLoadStrategy::None || frame.frameLoadState().state() == FrameLoadState::State::Finished) {
449         callback->sendSuccess(InspectorObject::create());
450         return;
451     }
452
453     m_loadTimer.startOneShot(timeout);
454     switch (loadStrategy) {
455     case Inspector::Protocol::Automation::PageLoadStrategy::Normal:
456         m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame.set(frame.frameID(), WTFMove(callback));
457         break;
458     case Inspector::Protocol::Automation::PageLoadStrategy::Eager:
459         m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame.set(frame.frameID(), WTFMove(callback));
460         break;
461     case Inspector::Protocol::Automation::PageLoadStrategy::None:
462         ASSERT_NOT_REACHED();
463     }
464 }
465
466 static void respondToPendingNavigationCallbacksWithTimeout(HashMap<uint64_t, RefPtr<Inspector::BackendDispatcher::CallbackBase>>& map)
467 {
468     for (auto id : map.keys()) {
469         auto callback = map.take(id);
470         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
471     }
472 }
473
474 void WebAutomationSession::loadTimerFired()
475 {
476     respondToPendingNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame);
477     respondToPendingNavigationCallbacksWithTimeout(m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame);
478     respondToPendingNavigationCallbacksWithTimeout(m_pendingNormalNavigationInBrowsingContextCallbacksPerPage);
479     respondToPendingNavigationCallbacksWithTimeout(m_pendingEagerNavigationInBrowsingContextCallbacksPerPage);
480 }
481
482 void WebAutomationSession::navigateBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String& url, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<NavigateBrowsingContextCallback>&& callback)
483 {
484     WebPageProxy* page = webPageProxyForHandle(handle);
485     if (!page)
486         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
487
488     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
489     if (!pageLoadStrategy)
490         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
491     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
492
493     page->loadRequest(WebCore::URL(WebCore::URL(), url));
494     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
495 }
496
497 void WebAutomationSession::goBackInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<GoBackInBrowsingContextCallback>&& callback)
498 {
499     WebPageProxy* page = webPageProxyForHandle(handle);
500     if (!page)
501         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
502
503     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
504     if (!pageLoadStrategy)
505         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
506     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
507
508     page->goBack();
509     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
510 }
511
512 void WebAutomationSession::goForwardInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<GoForwardInBrowsingContextCallback>&& callback)
513 {
514     WebPageProxy* page = webPageProxyForHandle(handle);
515     if (!page)
516         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
517
518     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
519     if (!pageLoadStrategy)
520         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
521     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
522
523     page->goForward();
524     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
525 }
526
527 void WebAutomationSession::reloadBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String* optionalPageLoadStrategyString, const int* optionalPageLoadTimeout, Ref<ReloadBrowsingContextCallback>&& callback)
528 {
529     WebPageProxy* page = webPageProxyForHandle(handle);
530     if (!page)
531         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
532
533     auto pageLoadStrategy = pageLoadStrategyFromStringParameter(optionalPageLoadStrategyString);
534     if (!pageLoadStrategy)
535         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'pageLoadStrategy' is invalid.");
536     auto pageLoadTimeout = optionalPageLoadTimeout ? Seconds::fromMilliseconds(*optionalPageLoadTimeout) : defaultPageLoadTimeout;
537
538     page->reload({ });
539     waitForNavigationToCompleteOnPage(*page, pageLoadStrategy.value(), pageLoadTimeout, WTFMove(callback));
540 }
541
542 void WebAutomationSession::navigationOccurredForFrame(const WebFrameProxy& frame)
543 {
544     if (frame.isMainFrame()) {
545         // New page loaded, clear frame handles previously cached.
546         m_handleWebFrameMap.clear();
547         m_webFrameHandleMap.clear();
548         if (auto callback = m_pendingNormalNavigationInBrowsingContextCallbacksPerPage.take(frame.page()->pageID())) {
549             m_loadTimer.stop();
550             callback->sendSuccess(InspectorObject::create());
551         }
552         m_domainNotifier->browsingContextCleared(handleForWebPageProxy(*frame.page()));
553     } else {
554         if (auto callback = m_pendingNormalNavigationInBrowsingContextCallbacksPerFrame.take(frame.frameID())) {
555             m_loadTimer.stop();
556             callback->sendSuccess(InspectorObject::create());
557         }
558     }
559 }
560
561 void WebAutomationSession::documentLoadedForFrame(const WebFrameProxy& frame)
562 {
563     if (frame.isMainFrame()) {
564         if (auto callback = m_pendingEagerNavigationInBrowsingContextCallbacksPerPage.take(frame.page()->pageID())) {
565             m_loadTimer.stop();
566             callback->sendSuccess(InspectorObject::create());
567         }
568     } else {
569         if (auto callback = m_pendingEagerNavigationInBrowsingContextCallbacksPerFrame.take(frame.frameID())) {
570             m_loadTimer.stop();
571             callback->sendSuccess(InspectorObject::create());
572         }
573     }
574 }
575
576 void WebAutomationSession::inspectorFrontendLoaded(const WebPageProxy& page)
577 {
578     if (auto callback = m_pendingInspectorCallbacksPerPage.take(page.pageID()))
579         callback->sendSuccess(InspectorObject::create());
580 }
581
582 void WebAutomationSession::keyboardEventsFlushedForPage(const WebPageProxy& page)
583 {
584     if (auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID()))
585         callback->sendSuccess(InspectorObject::create());
586 }
587
588 void WebAutomationSession::willClosePage(const WebPageProxy& page)
589 {
590     String handle = handleForWebPageProxy(page);
591     m_domainNotifier->browsingContextCleared(handle);
592 }
593
594 static bool fileCanBeAcceptedForUpload(const String& filename, const HashSet<String>& allowedMIMETypes, const HashSet<String>& allowedFileExtensions)
595 {
596     if (!WebCore::fileExists(filename))
597         return false;
598
599     if (allowedMIMETypes.isEmpty() && allowedFileExtensions.isEmpty())
600         return true;
601
602     // We can't infer a MIME type from a file without an extension, just give up.
603     auto dotOffset = filename.reverseFind('.');
604     if (dotOffset == notFound)
605         return false;
606
607     String extension = filename.substring(dotOffset + 1).convertToASCIILowercase();
608     if (extension.isEmpty())
609         return false;
610
611     if (allowedFileExtensions.contains(extension))
612         return true;
613
614     String mappedMIMEType = WebCore::MIMETypeRegistry::getMIMETypeForExtension(extension).convertToASCIILowercase();
615     if (mappedMIMEType.isEmpty())
616         return false;
617     
618     if (allowedMIMETypes.contains(mappedMIMEType))
619         return true;
620
621     // Fall back to checking for a MIME type wildcard if an exact match is not found.
622     Vector<String> components;
623     mappedMIMEType.split('/', false, components);
624     if (components.size() != 2)
625         return false;
626
627     String wildcardedMIMEType = makeString(components[0], "/*");
628     if (allowedMIMETypes.contains(wildcardedMIMEType))
629         return true;
630
631     return false;
632 }
633
634 void WebAutomationSession::handleRunOpenPanel(const WebPageProxy& page, const WebFrameProxy&, const API::OpenPanelParameters& parameters, WebOpenPanelResultListenerProxy& resultListener)
635 {
636     if (!m_filesToSelectForFileUpload.size()) {
637         resultListener.cancel();
638         m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
639         return;
640     }
641
642     if (m_filesToSelectForFileUpload.size() > 1 && !parameters.allowMultipleFiles()) {
643         resultListener.cancel();
644         m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
645         return;
646     }
647
648     HashSet<String> allowedMIMETypes;
649     auto acceptMIMETypes = parameters.acceptMIMETypes();
650     for (auto type : acceptMIMETypes->elementsOfType<API::String>())
651         allowedMIMETypes.add(type->string());
652
653     HashSet<String> allowedFileExtensions;
654     auto acceptFileExtensions = parameters.acceptFileExtensions();
655     for (auto type : acceptFileExtensions->elementsOfType<API::String>()) {
656         // WebCore vends extensions with leading periods. Strip these to simplify matching later.
657         String extension = type->string();
658         ASSERT(extension.characterAt(0) == '.');
659         allowedFileExtensions.add(extension.substring(1));
660     }
661
662     // Per §14.3.10.5 in the W3C spec, if at least one file cannot be accepted, the command should fail.
663     // The REST API service can tell that this failed by checking the "files" attribute of the input element.
664     for (const String& filename : m_filesToSelectForFileUpload) {
665         if (!fileCanBeAcceptedForUpload(filename, allowedMIMETypes, allowedFileExtensions)) {
666             resultListener.cancel();
667             m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, true);
668             return;
669         }
670     }
671
672     resultListener.chooseFiles(m_filesToSelectForFileUpload);
673     m_domainNotifier->fileChooserDismissed(m_activeBrowsingContextHandle, false);
674 }
675
676 void WebAutomationSession::evaluateJavaScriptFunction(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const String& function, const Inspector::InspectorArray& arguments, const bool* optionalExpectsImplicitCallbackArgument, const int* optionalCallbackTimeout, Ref<EvaluateJavaScriptFunctionCallback>&& callback)
677 {
678     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
679     if (!page)
680         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
681
682     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
683     if (!frameID)
684         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
685
686     Vector<String> argumentsVector;
687     argumentsVector.reserveCapacity(arguments.length());
688
689     for (auto& argument : arguments) {
690         String argumentString;
691         argument->asString(argumentString);
692         argumentsVector.uncheckedAppend(argumentString);
693     }
694
695     bool expectsImplicitCallbackArgument = optionalExpectsImplicitCallbackArgument ? *optionalExpectsImplicitCallbackArgument : false;
696     int callbackTimeout = optionalCallbackTimeout ? *optionalCallbackTimeout : 0;
697
698     uint64_t callbackID = m_nextEvaluateJavaScriptCallbackID++;
699     m_evaluateJavaScriptFunctionCallbacks.set(callbackID, WTFMove(callback));
700
701     page->process().send(Messages::WebAutomationSessionProxy::EvaluateJavaScriptFunction(page->pageID(), frameID.value(), function, argumentsVector, expectsImplicitCallbackArgument, callbackTimeout, callbackID), 0);
702 }
703
704 void WebAutomationSession::didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType)
705 {
706     auto callback = m_evaluateJavaScriptFunctionCallbacks.take(callbackID);
707     if (!callback)
708         return;
709
710     if (!errorType.isEmpty())
711         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE_AND_DETAILS(errorType, result));
712     else
713         callback->sendSuccess(result);
714 }
715
716 void WebAutomationSession::resolveChildFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&& callback)
717 {
718     if (!optionalOrdinal && !optionalName && !optionalNodeHandle)
719         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "Command must specify a child frame by ordinal, name, or element handle.");
720
721     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
722     if (!page)
723         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
724
725     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
726     if (!frameID)
727         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
728
729     uint64_t callbackID = m_nextResolveFrameCallbackID++;
730     m_resolveChildFrameHandleCallbacks.set(callbackID, WTFMove(callback));
731
732     if (optionalNodeHandle) {
733         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithNodeHandle(page->pageID(), frameID.value(), *optionalNodeHandle, callbackID), 0);
734         return;
735     }
736
737     if (optionalName) {
738         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithName(page->pageID(), frameID.value(), *optionalName, callbackID), 0);
739         return;
740     }
741
742     if (optionalOrdinal) {
743         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithOrdinal(page->pageID(), frameID.value(), *optionalOrdinal, callbackID), 0);
744         return;
745     }
746
747     ASSERT_NOT_REACHED();
748 }
749
750 void WebAutomationSession::didResolveChildFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
751 {
752     auto callback = m_resolveChildFrameHandleCallbacks.take(callbackID);
753     if (!callback)
754         return;
755
756     if (!errorType.isEmpty())
757         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
758     else
759         callback->sendSuccess(handleForWebFrameID(frameID));
760 }
761
762 void WebAutomationSession::resolveParentFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&& callback)
763 {
764     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
765     if (!page)
766         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
767
768     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
769     if (!frameID)
770         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
771
772     uint64_t callbackID = m_nextResolveParentFrameCallbackID++;
773     m_resolveParentFrameHandleCallbacks.set(callbackID, WTFMove(callback));
774
775     page->process().send(Messages::WebAutomationSessionProxy::ResolveParentFrame(page->pageID(), frameID.value(), callbackID), 0);
776 }
777
778 void WebAutomationSession::didResolveParentFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
779 {
780     auto callback = m_resolveParentFrameHandleCallbacks.take(callbackID);
781     if (!callback)
782         return;
783
784     if (!errorType.isEmpty())
785         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
786     else
787         callback->sendSuccess(handleForWebFrameID(frameID));
788 }
789
790 void WebAutomationSession::computeElementLayout(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* optionalUseViewportCoordinates, Ref<ComputeElementLayoutCallback>&& callback)
791 {
792     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
793     if (!page)
794         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
795
796     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
797     if (!frameID)
798         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
799
800     uint64_t callbackID = m_nextComputeElementLayoutCallbackID++;
801     m_computeElementLayoutCallbacks.set(callbackID, WTFMove(callback));
802
803     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
804     bool useViewportCoordinates = optionalUseViewportCoordinates ? *optionalUseViewportCoordinates : false;
805
806     page->process().send(Messages::WebAutomationSessionProxy::ComputeElementLayout(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, useViewportCoordinates, callbackID), 0);
807 }
808
809 void WebAutomationSession::didComputeElementLayout(uint64_t callbackID, WebCore::IntRect rect, std::optional<WebCore::IntPoint> inViewCenterPoint, bool isObscured, const String& errorType)
810 {
811     auto callback = m_computeElementLayoutCallbacks.take(callbackID);
812     if (!callback)
813         return;
814
815     if (!errorType.isEmpty()) {
816         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
817         return;
818     }
819
820     auto originObject = Inspector::Protocol::Automation::Point::create()
821         .setX(rect.x())
822         .setY(rect.y())
823         .release();
824
825     auto sizeObject = Inspector::Protocol::Automation::Size::create()
826         .setWidth(rect.width())
827         .setHeight(rect.height())
828         .release();
829
830     auto rectObject = Inspector::Protocol::Automation::Rect::create()
831         .setOrigin(WTFMove(originObject))
832         .setSize(WTFMove(sizeObject))
833         .release();
834
835     if (!inViewCenterPoint) {
836         callback->sendSuccess(WTFMove(rectObject), nullptr, isObscured);
837         return;
838     }
839
840     auto inViewCenterPointObject = Inspector::Protocol::Automation::Point::create()
841         .setX(inViewCenterPoint.value().x())
842         .setY(inViewCenterPoint.value().y())
843         .release();
844
845     callback->sendSuccess(WTFMove(rectObject), WTFMove(inViewCenterPointObject), isObscured);
846 }
847
848 void WebAutomationSession::isShowingJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, bool* result)
849 {
850     ASSERT(m_client);
851     if (!m_client)
852         FAIL_WITH_PREDEFINED_ERROR(InternalError);
853
854     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
855     if (!page)
856         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
857
858     *result = m_client->isShowingJavaScriptDialogOnPage(*this, *page);
859 }
860
861 void WebAutomationSession::dismissCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
862 {
863     ASSERT(m_client);
864     if (!m_client)
865         FAIL_WITH_PREDEFINED_ERROR(InternalError);
866
867     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
868     if (!page)
869         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
870
871     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
872         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
873
874     m_client->dismissCurrentJavaScriptDialogOnPage(*this, *page);
875 }
876
877 void WebAutomationSession::acceptCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
878 {
879     ASSERT(m_client);
880     if (!m_client)
881         FAIL_WITH_PREDEFINED_ERROR(InternalError);
882
883     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
884     if (!page)
885         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
886
887     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
888         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
889
890     m_client->acceptCurrentJavaScriptDialogOnPage(*this, *page);
891 }
892
893 void WebAutomationSession::messageOfCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, String* text)
894 {
895     ASSERT(m_client);
896     if (!m_client)
897         FAIL_WITH_PREDEFINED_ERROR(InternalError);
898
899     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
900     if (!page)
901         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
902
903     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
904         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
905
906     *text = m_client->messageOfCurrentJavaScriptDialogOnPage(*this, *page);
907 }
908
909 void WebAutomationSession::setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& promptValue)
910 {
911     ASSERT(m_client);
912     if (!m_client)
913         FAIL_WITH_PREDEFINED_ERROR(InternalError);
914
915     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
916     if (!page)
917         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
918
919     if (!m_client->isShowingJavaScriptDialogOnPage(*this, *page))
920         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
921
922     m_client->setUserInputForCurrentJavaScriptPromptOnPage(*this, *page, promptValue);
923 }
924
925 void WebAutomationSession::setFilesToSelectForFileUpload(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorArray& filenames)
926 {
927     Vector<String> newFileList;
928     newFileList.reserveInitialCapacity(filenames.length());
929
930     for (auto item : filenames) {
931         String filename;
932         if (!item->asString(filename))
933             FAIL_WITH_PREDEFINED_ERROR(InternalError);
934
935         newFileList.append(filename);
936     }
937
938     m_filesToSelectForFileUpload.swap(newFileList);
939 }
940
941 void WebAutomationSession::getAllCookies(ErrorString& errorString, const String& browsingContextHandle, Ref<GetAllCookiesCallback>&& callback)
942 {
943     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
944     if (!page)
945         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
946
947     // 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.
948     const uint64_t mainFrameID = 0;
949
950     uint64_t callbackID = m_nextGetCookiesCallbackID++;
951     m_getCookieCallbacks.set(callbackID, WTFMove(callback));
952
953     page->process().send(Messages::WebAutomationSessionProxy::GetCookiesForFrame(page->pageID(), mainFrameID, callbackID), 0);
954 }
955
956 static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
957 {
958     return Inspector::Protocol::Automation::Cookie::create()
959         .setName(cookie.name)
960         .setValue(cookie.value)
961         .setDomain(cookie.domain)
962         .setPath(cookie.path)
963         .setExpires(cookie.expires)
964         .setSize((cookie.name.length() + cookie.value.length()))
965         .setHttpOnly(cookie.httpOnly)
966         .setSecure(cookie.secure)
967         .setSession(cookie.session)
968         .release();
969 }
970
971 static Ref<Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>> buildArrayForCookies(Vector<WebCore::Cookie>& cookiesList)
972 {
973     auto cookies = Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>::create();
974
975     for (const auto& cookie : cookiesList)
976         cookies->addItem(buildObjectForCookie(cookie));
977
978     return cookies;
979 }
980
981 void WebAutomationSession::didGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie> cookies, const String& errorType)
982 {
983     auto callback = m_getCookieCallbacks.take(callbackID);
984     if (!callback)
985         return;
986
987     if (!errorType.isEmpty()) {
988         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
989         return;
990     }
991
992     callback->sendSuccess(buildArrayForCookies(cookies));
993 }
994
995 void WebAutomationSession::deleteSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&& callback)
996 {
997     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
998     if (!page)
999         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1000
1001     // 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.
1002     const uint64_t mainFrameID = 0;
1003
1004     uint64_t callbackID = m_nextDeleteCookieCallbackID++;
1005     m_deleteCookieCallbacks.set(callbackID, WTFMove(callback));
1006
1007     page->process().send(Messages::WebAutomationSessionProxy::DeleteCookie(page->pageID(), mainFrameID, cookieName, callbackID), 0);
1008 }
1009
1010 void WebAutomationSession::didDeleteCookie(uint64_t callbackID, const String& errorType)
1011 {
1012     auto callback = m_deleteCookieCallbacks.take(callbackID);
1013     if (!callback)
1014         return;
1015
1016     if (!errorType.isEmpty()) {
1017         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1018         return;
1019     }
1020
1021     callback->sendSuccess();
1022 }
1023
1024 void WebAutomationSession::addSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorObject& cookieObject, Ref<AddSingleCookieCallback>&& callback)
1025 {
1026     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1027     if (!page)
1028         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1029
1030     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
1031     ASSERT(activeURL.isValid());
1032
1033     WebCore::Cookie cookie;
1034
1035     if (!cookieObject.getString(WTF::ASCIILiteral("name"), cookie.name))
1036         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'name' was not found.");
1037
1038     if (!cookieObject.getString(WTF::ASCIILiteral("value"), cookie.value))
1039         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'value' was not found.");
1040
1041     String domain;
1042     if (!cookieObject.getString(WTF::ASCIILiteral("domain"), domain))
1043         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'domain' was not found.");
1044
1045     // Inherit the domain/host from the main frame's URL if it is not explicitly set.
1046     if (domain.isEmpty())
1047         domain = activeURL.host();
1048
1049     cookie.domain = domain;
1050
1051     if (!cookieObject.getString(WTF::ASCIILiteral("path"), cookie.path))
1052         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'path' was not found.");
1053
1054     double expires;
1055     if (!cookieObject.getDouble(WTF::ASCIILiteral("expires"), expires))
1056         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'expires' was not found.");
1057
1058     cookie.expires = expires * 1000.0;
1059
1060     if (!cookieObject.getBoolean(WTF::ASCIILiteral("secure"), cookie.secure))
1061         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'secure' was not found.");
1062
1063     if (!cookieObject.getBoolean(WTF::ASCIILiteral("session"), cookie.session))
1064         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'session' was not found.");
1065
1066     if (!cookieObject.getBoolean(WTF::ASCIILiteral("httpOnly"), cookie.httpOnly))
1067         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'httpOnly' was not found.");
1068
1069     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1070
1071     // FIXME: Using activeURL here twice is basically saying "this is always in the context of the main document"
1072     // which probably isn't accurate.
1073     cookieManager->setCookies(page->websiteDataStore().sessionID(), { cookie }, activeURL, activeURL, [callback = callback.copyRef()](CallbackBase::Error error) {
1074         if (error == CallbackBase::Error::None)
1075             callback->sendSuccess();
1076         else
1077             callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1078     });
1079 }
1080
1081 void WebAutomationSession::deleteAllCookies(ErrorString& errorString, const String& browsingContextHandle)
1082 {
1083     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
1084     if (!page)
1085         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1086
1087     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
1088     ASSERT(activeURL.isValid());
1089
1090     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
1091     cookieManager->deleteCookiesForHostname(page->websiteDataStore().sessionID(), activeURL.host());
1092 }
1093
1094 #if USE(APPKIT) || PLATFORM(GTK)
1095 static WebEvent::Modifiers protocolModifierToWebEventModifier(Inspector::Protocol::Automation::KeyModifier modifier)
1096 {
1097     switch (modifier) {
1098     case Inspector::Protocol::Automation::KeyModifier::Alt:
1099         return WebEvent::AltKey;
1100     case Inspector::Protocol::Automation::KeyModifier::Meta:
1101         return WebEvent::MetaKey;
1102     case Inspector::Protocol::Automation::KeyModifier::Control:
1103         return WebEvent::ControlKey;
1104     case Inspector::Protocol::Automation::KeyModifier::Shift:
1105         return WebEvent::ShiftKey;
1106     case Inspector::Protocol::Automation::KeyModifier::CapsLock:
1107         return WebEvent::CapsLockKey;
1108     }
1109
1110     RELEASE_ASSERT_NOT_REACHED();
1111 }
1112 #endif // USE(APPKIT)
1113
1114 void WebAutomationSession::performMouseInteraction(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& requestedPositionObject, const String& mouseButtonString, const String& mouseInteractionString, const Inspector::InspectorArray& keyModifierStrings, RefPtr<Inspector::Protocol::Automation::Point>& updatedPositionObject)
1115 {
1116 #if !USE(APPKIT) && !PLATFORM(GTK)
1117     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1118 #else
1119     WebPageProxy* page = webPageProxyForHandle(handle);
1120     if (!page)
1121         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1122
1123     float x;
1124     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("x"), x))
1125         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'x' was not found.");
1126
1127     float y;
1128     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("y"), y))
1129         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'y' was not found.");
1130
1131     WebCore::FloatRect windowFrame;
1132     page->getWindowFrame(windowFrame);
1133
1134     x = std::min(std::max(0.0f, x), windowFrame.size().width());
1135     y = std::min(std::max(0.0f, y + page->topContentInset()), windowFrame.size().height());
1136
1137     WebCore::IntPoint viewPosition = WebCore::IntPoint(static_cast<int>(x), static_cast<int>(y));
1138
1139     auto parsedInteraction = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseInteraction>(mouseInteractionString);
1140     if (!parsedInteraction)
1141         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interaction' is invalid.");
1142
1143     auto parsedButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(mouseButtonString);
1144     if (!parsedButton)
1145         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'button' is invalid.");
1146
1147     WebEvent::Modifiers keyModifiers = (WebEvent::Modifiers)0;
1148     for (auto it = keyModifierStrings.begin(); it != keyModifierStrings.end(); ++it) {
1149         String modifierString;
1150         if (!it->get()->asString(modifierString))
1151             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'modifiers' is invalid.");
1152
1153         auto parsedModifier = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyModifier>(modifierString);
1154         if (!parsedModifier)
1155             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A modifier in the 'modifiers' array is invalid.");
1156         WebEvent::Modifiers enumValue = protocolModifierToWebEventModifier(parsedModifier.value());
1157         keyModifiers = (WebEvent::Modifiers)(enumValue | keyModifiers);
1158     }
1159
1160     platformSimulateMouseInteraction(*page, viewPosition, parsedInteraction.value(), parsedButton.value(), keyModifiers);
1161
1162     updatedPositionObject = Inspector::Protocol::Automation::Point::create()
1163         .setX(x)
1164         .setY(y - page->topContentInset())
1165         .release();
1166 #endif // USE(APPKIT)
1167 }
1168
1169 void WebAutomationSession::performKeyboardInteractions(ErrorString& errorString, const String& handle, const Inspector::InspectorArray& interactions, Ref<PerformKeyboardInteractionsCallback>&& callback)
1170 {
1171 #if !PLATFORM(COCOA) && !PLATFORM(GTK)
1172     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
1173 #else
1174     WebPageProxy* page = webPageProxyForHandle(handle);
1175     if (!page)
1176         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1177
1178     if (!interactions.length())
1179         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interactions' was not found or empty.");
1180
1181     // Validate all of the parameters before performing any interactions with the browsing context under test.
1182     Vector<WTF::Function<void()>> actionsToPerform;
1183     actionsToPerform.reserveCapacity(interactions.length());
1184
1185     for (auto interaction : interactions) {
1186         RefPtr<InspectorObject> interactionObject;
1187         if (!interaction->asObject(interactionObject))
1188             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter was invalid.");
1189
1190         String interactionTypeString;
1191         if (!interactionObject->getString(ASCIILiteral("type"), interactionTypeString))
1192             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter is missing the 'type' key.");
1193         auto interactionType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyboardInteractionType>(interactionTypeString);
1194         if (!interactionType)
1195             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'type' key.");
1196
1197         String virtualKeyString;
1198         bool foundVirtualKey = interactionObject->getString(ASCIILiteral("key"), virtualKeyString);
1199         if (foundVirtualKey) {
1200             auto virtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(virtualKeyString);
1201             if (!virtualKey)
1202                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1203
1204             actionsToPerform.uncheckedAppend([this, page, interactionType, virtualKey] {
1205                 platformSimulateKeyStroke(*page, interactionType.value(), virtualKey.value());
1206             });
1207         }
1208
1209         String keySequence;
1210         bool foundKeySequence = interactionObject->getString(ASCIILiteral("text"), keySequence);
1211         if (foundKeySequence) {
1212             switch (interactionType.value()) {
1213             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress:
1214             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease:
1215                 // 'KeyPress' and 'KeyRelease' are meant for a virtual key and are not supported for a string (sequence of codepoints).
1216                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
1217
1218             case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey:
1219                 actionsToPerform.uncheckedAppend([this, page, keySequence] {
1220                     platformSimulateKeySequence(*page, keySequence);
1221                 });
1222                 break;
1223             }
1224         }
1225
1226         if (!foundVirtualKey && !foundKeySequence)
1227             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "An interaction in the 'interactions' parameter is missing both 'key' and 'text'. One must be provided.");
1228     }
1229
1230     ASSERT(actionsToPerform.size());
1231     if (!actionsToPerform.size())
1232         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "No actions to perform.");
1233
1234     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
1235     if (callbackInMap)
1236         callbackInMap->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
1237     callbackInMap = WTFMove(callback);
1238
1239     for (auto& action : actionsToPerform)
1240         action();
1241 #endif // PLATFORM(COCOA)
1242 }
1243
1244 void WebAutomationSession::takeScreenshot(ErrorString& errorString, const String& handle, Ref<TakeScreenshotCallback>&& callback)
1245 {
1246     WebPageProxy* page = webPageProxyForHandle(handle);
1247     if (!page)
1248         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
1249
1250     uint64_t callbackID = m_nextScreenshotCallbackID++;
1251     m_screenshotCallbacks.set(callbackID, WTFMove(callback));
1252
1253     page->process().send(Messages::WebAutomationSessionProxy::TakeScreenshot(page->pageID(), callbackID), 0);
1254 }
1255
1256 void WebAutomationSession::didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle& imageDataHandle, const String& errorType)
1257 {
1258     auto callback = m_screenshotCallbacks.take(callbackID);
1259     if (!callback)
1260         return;
1261
1262     if (!errorType.isEmpty()) {
1263         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
1264         return;
1265     }
1266
1267     std::optional<String> base64EncodedData = platformGetBase64EncodedPNGData(imageDataHandle);
1268     if (!base64EncodedData) {
1269         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
1270         return;
1271     }
1272
1273     callback->sendSuccess(base64EncodedData.value());
1274 }
1275
1276 // Platform-dependent Implementation Stubs.
1277
1278 #if !PLATFORM(MAC) && !PLATFORM(GTK)
1279 void WebAutomationSession::platformSimulateMouseInteraction(WebKit::WebPageProxy&, const WebCore::IntPoint&, Inspector::Protocol::Automation::MouseInteraction, Inspector::Protocol::Automation::MouseButton, WebEvent::Modifiers)
1280 {
1281 }
1282 #endif // !PLATFORM(MAC)
1283
1284 #if !PLATFORM(COCOA) && !PLATFORM(GTK)
1285 void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy&, Inspector::Protocol::Automation::KeyboardInteractionType, Inspector::Protocol::Automation::VirtualKey)
1286 {
1287 }
1288
1289 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy&, const String&)
1290 {
1291 }
1292
1293 std::optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&)
1294 {
1295     return String();
1296 }
1297 #endif // !PLATFORM(COCOA)
1298
1299 } // namespace WebKit