5f0314650df5333224c1e601a1da6120bb2facaa
[WebKit-https.git] / Source / WebKit2 / 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 "APIAutomationSessionClient.h"
30 #include "AutomationProtocolObjects.h"
31 #include "WebAutomationSessionMacros.h"
32 #include "WebAutomationSessionMessages.h"
33 #include "WebAutomationSessionProxyMessages.h"
34 #include "WebCookieManagerProxy.h"
35 #include "WebInspectorProxy.h"
36 #include "WebProcessPool.h"
37 #include <JavaScriptCore/InspectorBackendDispatcher.h>
38 #include <JavaScriptCore/InspectorFrontendRouter.h>
39 #include <WebCore/URL.h>
40 #include <WebCore/UUID.h>
41 #include <algorithm>
42 #include <wtf/HashMap.h>
43 #include <wtf/Optional.h>
44 #include <wtf/text/StringConcatenate.h>
45
46 using namespace Inspector;
47
48 namespace WebKit {
49
50 WebAutomationSession::WebAutomationSession()
51     : m_client(std::make_unique<API::AutomationSessionClient>())
52     , m_frontendRouter(FrontendRouter::create())
53     , m_backendDispatcher(BackendDispatcher::create(m_frontendRouter.copyRef()))
54     , m_domainDispatcher(AutomationBackendDispatcher::create(m_backendDispatcher, this))
55 {
56 }
57
58 WebAutomationSession::~WebAutomationSession()
59 {
60     ASSERT(!m_client);
61
62     if (m_processPool)
63         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
64 }
65
66 void WebAutomationSession::setClient(std::unique_ptr<API::AutomationSessionClient> client)
67 {
68     m_client = WTFMove(client);
69 }
70
71 void WebAutomationSession::setProcessPool(WebKit::WebProcessPool* processPool)
72 {
73     if (m_processPool)
74         m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
75
76     m_processPool = processPool;
77
78     if (m_processPool)
79         m_processPool->addMessageReceiver(Messages::WebAutomationSession::messageReceiverName(), *this);
80 }
81
82 // NOTE: this class could be split at some point to support local and remote automation sessions.
83 // For now, it only works with a remote automation driver over a RemoteInspector connection.
84
85 #if ENABLE(REMOTE_INSPECTOR)
86
87 // Inspector::RemoteAutomationTarget API
88
89 void WebAutomationSession::dispatchMessageFromRemote(const String& message)
90 {
91     m_backendDispatcher->dispatch(message);
92 }
93
94 void WebAutomationSession::connect(Inspector::FrontendChannel* channel, bool isAutomaticConnection)
95 {
96     UNUSED_PARAM(isAutomaticConnection);
97
98     m_remoteChannel = channel;
99     m_frontendRouter->connectFrontend(channel);
100
101     setIsPaired(true);
102 }
103
104 void WebAutomationSession::disconnect(Inspector::FrontendChannel* channel)
105 {
106     ASSERT(channel == m_remoteChannel);
107     terminate();
108 }
109
110 #endif // ENABLE(REMOTE_INSPECTOR)
111
112 void WebAutomationSession::terminate()
113 {
114 #if ENABLE(REMOTE_INSPECTOR)
115     if (Inspector::FrontendChannel* channel = m_remoteChannel) {
116         m_remoteChannel = nullptr;
117         m_frontendRouter->disconnectFrontend(channel);
118     }
119
120     setIsPaired(false);
121 #endif
122
123     if (m_client)
124         m_client->didDisconnectFromRemote(this);
125 }
126
127 WebPageProxy* WebAutomationSession::webPageProxyForHandle(const String& handle)
128 {
129     auto iter = m_handleWebPageMap.find(handle);
130     if (iter == m_handleWebPageMap.end())
131         return nullptr;
132     return WebProcessProxy::webPage(iter->value);
133 }
134
135 String WebAutomationSession::handleForWebPageProxy(const WebPageProxy& webPageProxy)
136 {
137     auto iter = m_webPageHandleMap.find(webPageProxy.pageID());
138     if (iter != m_webPageHandleMap.end())
139         return iter->value;
140
141     String handle = "page-" + WebCore::createCanonicalUUIDString().convertToASCIIUppercase();
142
143     auto firstAddResult = m_webPageHandleMap.add(webPageProxy.pageID(), handle);
144     RELEASE_ASSERT(firstAddResult.isNewEntry);
145
146     auto secondAddResult = m_handleWebPageMap.add(handle, webPageProxy.pageID());
147     RELEASE_ASSERT(secondAddResult.isNewEntry);
148
149     return handle;
150 }
151
152 std::optional<uint64_t> WebAutomationSession::webFrameIDForHandle(const String& handle)
153 {
154     if (handle.isEmpty())
155         return 0;
156
157     auto iter = m_handleWebFrameMap.find(handle);
158     if (iter == m_handleWebFrameMap.end())
159         return std::nullopt;
160
161     return iter->value;
162 }
163
164 String WebAutomationSession::handleForWebFrameID(uint64_t frameID)
165 {
166     if (!frameID)
167         return emptyString();
168
169     for (auto& process : m_processPool->processes()) {
170         if (WebFrameProxy* frame = process->webFrame(frameID)) {
171             if (frame->isMainFrame())
172                 return emptyString();
173             break;
174         }
175     }
176
177     auto iter = m_webFrameHandleMap.find(frameID);
178     if (iter != m_webFrameHandleMap.end())
179         return iter->value;
180
181     String handle = "frame-" + WebCore::createCanonicalUUIDString().convertToASCIIUppercase();
182
183     auto firstAddResult = m_webFrameHandleMap.add(frameID, handle);
184     RELEASE_ASSERT(firstAddResult.isNewEntry);
185
186     auto secondAddResult = m_handleWebFrameMap.add(handle, frameID);
187     RELEASE_ASSERT(secondAddResult.isNewEntry);
188
189     return handle;
190 }
191
192 String WebAutomationSession::handleForWebFrameProxy(const WebFrameProxy& webFrameProxy)
193 {
194     return handleForWebFrameID(webFrameProxy.frameID());
195 }
196
197 RefPtr<Inspector::Protocol::Automation::BrowsingContext> WebAutomationSession::buildBrowsingContextForPage(WebPageProxy& page)
198 {
199     WebCore::FloatRect windowFrame;
200     page.getWindowFrame(windowFrame);
201
202     auto originObject = Inspector::Protocol::Automation::Point::create()
203         .setX(windowFrame.x())
204         .setY(windowFrame.y())
205         .release();
206
207     auto sizeObject = Inspector::Protocol::Automation::Size::create()
208         .setWidth(windowFrame.width())
209         .setHeight(windowFrame.height())
210         .release();
211
212     String handle = handleForWebPageProxy(page);
213
214     return Inspector::Protocol::Automation::BrowsingContext::create()
215         .setHandle(handle)
216         .setActive(m_activeBrowsingContextHandle == handle)
217         .setUrl(page.pageLoadState().activeURL())
218         .setWindowOrigin(WTFMove(originObject))
219         .setWindowSize(WTFMove(sizeObject))
220         .release();
221 }
222
223 // Platform-independent Commands.
224
225 void WebAutomationSession::getBrowsingContexts(Inspector::ErrorString& errorString, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>>& contexts)
226 {
227     contexts = Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>::create();
228
229     for (auto& process : m_processPool->processes()) {
230         for (auto* page : process->pages()) {
231             ASSERT(page);
232             if (!page->isControlledByAutomation())
233                 continue;
234
235             contexts->addItem(buildBrowsingContextForPage(*page));
236         }
237     }
238 }
239
240 void WebAutomationSession::getBrowsingContext(Inspector::ErrorString& errorString, const String& handle, RefPtr<Inspector::Protocol::Automation::BrowsingContext>& context)
241 {
242     WebPageProxy* page = webPageProxyForHandle(handle);
243     if (!page)
244         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
245
246     context = buildBrowsingContextForPage(*page);
247 }
248
249 void WebAutomationSession::createBrowsingContext(Inspector::ErrorString& errorString, String* handle)
250 {
251     ASSERT(m_client);
252     if (!m_client)
253         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session could not request a new browsing context.");
254
255     WebPageProxy* page = m_client->didRequestNewWindow(this);
256     if (!page)
257         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session failed to create a new browsing context.");
258
259     m_activeBrowsingContextHandle = *handle = handleForWebPageProxy(*page);
260 }
261
262 void WebAutomationSession::closeBrowsingContext(Inspector::ErrorString& errorString, const String& handle)
263 {
264     WebPageProxy* page = webPageProxyForHandle(handle);
265     if (!page)
266         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
267
268     if (handle == m_activeBrowsingContextHandle)
269         m_activeBrowsingContextHandle = emptyString();
270
271     page->closePage(false);
272 }
273
274 void WebAutomationSession::switchToBrowsingContext(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle)
275 {
276     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
277     if (!page)
278         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
279
280     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
281     if (!frameID)
282         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
283
284     // FIXME: We don't need to track this in WK2. Remove in a follow up.
285     m_activeBrowsingContextHandle = browsingContextHandle;
286
287     page->setFocus(true);
288     page->process().send(Messages::WebAutomationSessionProxy::FocusFrame(page->pageID(), frameID.value()), 0);
289 }
290
291 void WebAutomationSession::navigateBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String& url, Ref<NavigateBrowsingContextCallback>&& callback)
292 {
293     WebPageProxy* page = webPageProxyForHandle(handle);
294     if (!page)
295         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
296
297     if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page->pageID()))
298         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
299     m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page->pageID(), WTFMove(callback));
300
301     page->loadRequest(WebCore::URL(WebCore::URL(), url));
302 }
303
304 void WebAutomationSession::goBackInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, Ref<GoBackInBrowsingContextCallback>&& callback)
305 {
306     WebPageProxy* page = webPageProxyForHandle(handle);
307     if (!page)
308         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
309
310     if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page->pageID()))
311         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
312     m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page->pageID(), WTFMove(callback));
313
314     page->goBack();
315 }
316
317 void WebAutomationSession::goForwardInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, Ref<GoForwardInBrowsingContextCallback>&& callback)
318 {
319     WebPageProxy* page = webPageProxyForHandle(handle);
320     if (!page)
321         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
322
323     if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page->pageID()))
324         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
325     m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page->pageID(), WTFMove(callback));
326
327     page->goForward();
328 }
329
330 void WebAutomationSession::reloadBrowsingContext(Inspector::ErrorString& errorString, const String& handle, Ref<ReloadBrowsingContextCallback>&& callback)
331 {
332     WebPageProxy* page = webPageProxyForHandle(handle);
333     if (!page)
334         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
335
336     if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page->pageID()))
337         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
338     m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page->pageID(), WTFMove(callback));
339
340     const bool reloadFromOrigin = false;
341     page->reload(reloadFromOrigin, { });
342 }
343
344 void WebAutomationSession::navigationOccurredForPage(const WebPageProxy& page)
345 {
346     if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page.pageID()))
347         callback->sendSuccess(InspectorObject::create());
348 }
349
350 void WebAutomationSession::inspectorFrontendLoaded(const WebPageProxy& page)
351 {
352     if (auto callback = m_pendingInspectorCallbacksPerPage.take(page.pageID()))
353         callback->sendSuccess(InspectorObject::create());
354 }
355
356 void WebAutomationSession::keyboardEventsFlushedForPage(const WebPageProxy& page)
357 {
358     if (auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID()))
359         callback->sendSuccess(InspectorObject::create());
360 }
361
362 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)
363 {
364     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
365     if (!page)
366         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
367
368     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
369     if (!frameID)
370         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
371
372     Vector<String> argumentsVector;
373     argumentsVector.reserveCapacity(arguments.length());
374
375     for (auto& argument : arguments) {
376         String argumentString;
377         argument->asString(argumentString);
378         argumentsVector.uncheckedAppend(argumentString);
379     }
380
381     bool expectsImplicitCallbackArgument = optionalExpectsImplicitCallbackArgument ? *optionalExpectsImplicitCallbackArgument : false;
382     int callbackTimeout = optionalCallbackTimeout ? *optionalCallbackTimeout : 0;
383
384     uint64_t callbackID = m_nextEvaluateJavaScriptCallbackID++;
385     m_evaluateJavaScriptFunctionCallbacks.set(callbackID, WTFMove(callback));
386
387     page->process().send(Messages::WebAutomationSessionProxy::EvaluateJavaScriptFunction(page->pageID(), frameID.value(), function, argumentsVector, expectsImplicitCallbackArgument, callbackTimeout, callbackID), 0);
388 }
389
390 void WebAutomationSession::didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType)
391 {
392     auto callback = m_evaluateJavaScriptFunctionCallbacks.take(callbackID);
393     if (!callback)
394         return;
395
396     if (!errorType.isEmpty())
397         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE_AND_DETAILS(errorType, result));
398     else
399         callback->sendSuccess(result);
400 }
401
402 void WebAutomationSession::resolveChildFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&& callback)
403 {
404     if (!optionalOrdinal && !optionalName && !optionalNodeHandle)
405         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "Command must specify a child frame by ordinal, name, or element handle.");
406
407     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
408     if (!page)
409         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
410
411     std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
412     if (!frameID)
413         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
414
415     uint64_t callbackID = m_nextResolveFrameCallbackID++;
416     m_resolveChildFrameHandleCallbacks.set(callbackID, WTFMove(callback));
417
418     if (optionalNodeHandle) {
419         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithNodeHandle(page->pageID(), frameID.value(), *optionalNodeHandle, callbackID), 0);
420         return;
421     }
422
423     if (optionalName) {
424         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithName(page->pageID(), frameID.value(), *optionalName, callbackID), 0);
425         return;
426     }
427
428     if (optionalOrdinal) {
429         page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithOrdinal(page->pageID(), frameID.value(), *optionalOrdinal, callbackID), 0);
430         return;
431     }
432
433     ASSERT_NOT_REACHED();
434 }
435
436 void WebAutomationSession::didResolveChildFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
437 {
438     auto callback = m_resolveChildFrameHandleCallbacks.take(callbackID);
439     if (!callback)
440         return;
441
442     if (!errorType.isEmpty())
443         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
444     else
445         callback->sendSuccess(handleForWebFrameID(frameID));
446 }
447
448 void WebAutomationSession::resolveParentFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&& callback)
449 {
450     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
451     if (!page)
452         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
453
454     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
455     if (!frameID)
456         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
457
458     uint64_t callbackID = m_nextResolveParentFrameCallbackID++;
459     m_resolveParentFrameHandleCallbacks.set(callbackID, WTFMove(callback));
460
461     page->process().send(Messages::WebAutomationSessionProxy::ResolveParentFrame(page->pageID(), frameID.value(), callbackID), 0);
462 }
463
464 void WebAutomationSession::didResolveParentFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
465 {
466     auto callback = m_resolveParentFrameHandleCallbacks.take(callbackID);
467     if (!callback)
468         return;
469
470     if (!errorType.isEmpty())
471         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
472     else
473         callback->sendSuccess(handleForWebFrameID(frameID));
474 }
475
476 void WebAutomationSession::computeElementLayout(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* optionalUseViewportCoordinates, Ref<ComputeElementLayoutCallback>&& callback)
477 {
478     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
479     if (!page)
480         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
481
482     std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
483     if (!frameID)
484         FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
485
486     uint64_t callbackID = m_nextComputeElementLayoutCallbackID++;
487     m_computeElementLayoutCallbacks.set(callbackID, WTFMove(callback));
488
489     bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
490     bool useViewportCoordinates = optionalUseViewportCoordinates ? *optionalUseViewportCoordinates : false;
491
492     page->process().send(Messages::WebAutomationSessionProxy::ComputeElementLayout(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, useViewportCoordinates, callbackID), 0);
493 }
494
495 void WebAutomationSession::didComputeElementLayout(uint64_t callbackID, WebCore::IntRect rect, const String& errorType)
496 {
497     auto callback = m_computeElementLayoutCallbacks.take(callbackID);
498     if (!callback)
499         return;
500
501     if (!errorType.isEmpty()) {
502         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
503         return;
504     }
505
506     auto originObject = Inspector::Protocol::Automation::Point::create()
507         .setX(rect.x())
508         .setY(rect.y())
509         .release();
510
511     auto sizeObject = Inspector::Protocol::Automation::Size::create()
512         .setWidth(rect.width())
513         .setHeight(rect.height())
514         .release();
515
516     auto rectObject = Inspector::Protocol::Automation::Rect::create()
517         .setOrigin(WTFMove(originObject))
518         .setSize(WTFMove(sizeObject))
519         .release();
520
521     callback->sendSuccess(WTFMove(rectObject));
522 }
523
524 void WebAutomationSession::isShowingJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, bool* result)
525 {
526     ASSERT(m_client);
527     if (!m_client)
528         FAIL_WITH_PREDEFINED_ERROR(InternalError);
529
530     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
531     if (!page)
532         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
533
534     *result = m_client->isShowingJavaScriptDialogOnPage(this, page);
535 }
536
537 void WebAutomationSession::dismissCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
538 {
539     ASSERT(m_client);
540     if (!m_client)
541         FAIL_WITH_PREDEFINED_ERROR(InternalError);
542
543     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
544     if (!page)
545         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
546
547     if (!m_client->isShowingJavaScriptDialogOnPage(this, page))
548         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
549
550     m_client->dismissCurrentJavaScriptDialogOnPage(this, page);
551 }
552
553 void WebAutomationSession::acceptCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
554 {
555     ASSERT(m_client);
556     if (!m_client)
557         FAIL_WITH_PREDEFINED_ERROR(InternalError);
558
559     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
560     if (!page)
561         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
562
563     if (!m_client->isShowingJavaScriptDialogOnPage(this, page))
564         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
565
566     m_client->acceptCurrentJavaScriptDialogOnPage(this, page);
567 }
568
569 void WebAutomationSession::messageOfCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, String* text)
570 {
571     ASSERT(m_client);
572     if (!m_client)
573         FAIL_WITH_PREDEFINED_ERROR(InternalError);
574
575     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
576     if (!page)
577         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
578
579     if (!m_client->isShowingJavaScriptDialogOnPage(this, page))
580         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
581
582     *text = m_client->messageOfCurrentJavaScriptDialogOnPage(this, page);
583 }
584
585 void WebAutomationSession::setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& promptValue)
586 {
587     ASSERT(m_client);
588     if (!m_client)
589         FAIL_WITH_PREDEFINED_ERROR(InternalError);
590
591     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
592     if (!page)
593         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
594
595     if (!m_client->isShowingJavaScriptDialogOnPage(this, page))
596         FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
597
598     m_client->setUserInputForCurrentJavaScriptPromptOnPage(this, page, promptValue);
599 }
600
601 void WebAutomationSession::getAllCookies(ErrorString& errorString, const String& browsingContextHandle, Ref<GetAllCookiesCallback>&& callback)
602 {
603     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
604     if (!page)
605         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
606
607     // 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.
608     const uint64_t mainFrameID = 0;
609
610     uint64_t callbackID = m_nextGetCookiesCallbackID++;
611     m_getCookieCallbacks.set(callbackID, WTFMove(callback));
612
613     page->process().send(Messages::WebAutomationSessionProxy::GetCookiesForFrame(page->pageID(), mainFrameID, callbackID), 0);
614 }
615
616 static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
617 {
618     return Inspector::Protocol::Automation::Cookie::create()
619         .setName(cookie.name)
620         .setValue(cookie.value)
621         .setDomain(cookie.domain)
622         .setPath(cookie.path)
623         .setExpires(cookie.expires)
624         .setSize((cookie.name.length() + cookie.value.length()))
625         .setHttpOnly(cookie.httpOnly)
626         .setSecure(cookie.secure)
627         .setSession(cookie.session)
628         .release();
629 }
630
631 static Ref<Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>> buildArrayForCookies(Vector<WebCore::Cookie>& cookiesList)
632 {
633     auto cookies = Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>::create();
634
635     for (const auto& cookie : cookiesList)
636         cookies->addItem(buildObjectForCookie(cookie));
637
638     return cookies;
639 }
640
641 void WebAutomationSession::didGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie> cookies, const String& errorType)
642 {
643     auto callback = m_getCookieCallbacks.take(callbackID);
644     if (!callback)
645         return;
646
647     if (!errorType.isEmpty()) {
648         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
649         return;
650     }
651
652     callback->sendSuccess(buildArrayForCookies(cookies));
653 }
654
655 void WebAutomationSession::deleteSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&& callback)
656 {
657     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
658     if (!page)
659         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
660
661     // 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.
662     const uint64_t mainFrameID = 0;
663
664     uint64_t callbackID = m_nextDeleteCookieCallbackID++;
665     m_deleteCookieCallbacks.set(callbackID, WTFMove(callback));
666
667     page->process().send(Messages::WebAutomationSessionProxy::DeleteCookie(page->pageID(), mainFrameID, cookieName, callbackID), 0);
668 }
669
670 void WebAutomationSession::didDeleteCookie(uint64_t callbackID, const String& errorType)
671 {
672     auto callback = m_deleteCookieCallbacks.take(callbackID);
673     if (!callback)
674         return;
675
676     if (!errorType.isEmpty()) {
677         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
678         return;
679     }
680
681     callback->sendSuccess();
682 }
683
684 void WebAutomationSession::addSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorObject& cookieObject, Ref<AddSingleCookieCallback>&& callback)
685 {
686     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
687     if (!page)
688         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
689
690     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
691     ASSERT(activeURL.isValid());
692
693     WebCore::Cookie cookie;
694
695     if (!cookieObject.getString(WTF::ASCIILiteral("name"), cookie.name))
696         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'name' was not found.");
697
698     if (!cookieObject.getString(WTF::ASCIILiteral("value"), cookie.value))
699         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'value' was not found.");
700
701     String domain;
702     if (!cookieObject.getString(WTF::ASCIILiteral("domain"), domain))
703         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'domain' was not found.");
704
705     // Inherit the domain/host from the main frame's URL if it is not explicitly set.
706     if (domain.isEmpty())
707         domain = activeURL.host();
708
709     cookie.domain = domain;
710
711     if (!cookieObject.getString(WTF::ASCIILiteral("path"), cookie.path))
712         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'path' was not found.");
713
714     double expires;
715     if (!cookieObject.getDouble(WTF::ASCIILiteral("expires"), expires))
716         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'expires' was not found.");
717
718     cookie.expires = expires * 1000.0;
719
720     if (!cookieObject.getBoolean(WTF::ASCIILiteral("secure"), cookie.secure))
721         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'secure' was not found.");
722
723     if (!cookieObject.getBoolean(WTF::ASCIILiteral("session"), cookie.session))
724         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'session' was not found.");
725
726     if (!cookieObject.getBoolean(WTF::ASCIILiteral("httpOnly"), cookie.httpOnly))
727         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'httpOnly' was not found.");
728
729     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
730     cookieManager->addCookie(cookie, activeURL.host());
731
732     callback->sendSuccess();
733 }
734
735 void WebAutomationSession::deleteAllCookies(ErrorString& errorString, const String& browsingContextHandle)
736 {
737     WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
738     if (!page)
739         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
740
741     WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
742     ASSERT(activeURL.isValid());
743
744     WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
745     cookieManager->deleteCookiesForHostname(activeURL.host());
746 }
747
748 #if USE(APPKIT)
749 static WebEvent::Modifiers protocolModifierToWebEventModifier(Inspector::Protocol::Automation::KeyModifier modifier)
750 {
751     switch (modifier) {
752     case Inspector::Protocol::Automation::KeyModifier::Alt:
753         return WebEvent::AltKey;
754     case Inspector::Protocol::Automation::KeyModifier::Meta:
755         return WebEvent::MetaKey;
756     case Inspector::Protocol::Automation::KeyModifier::Control:
757         return WebEvent::ControlKey;
758     case Inspector::Protocol::Automation::KeyModifier::Shift:
759         return WebEvent::ShiftKey;
760     case Inspector::Protocol::Automation::KeyModifier::CapsLock:
761         return WebEvent::CapsLockKey;
762     }
763 }
764 #endif // USE(APPKIT)
765
766 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)
767 {
768 #if !USE(APPKIT)
769     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
770 #else
771     WebPageProxy* page = webPageProxyForHandle(handle);
772     if (!page)
773         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
774
775     float x;
776     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("x"), x))
777         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'x' was not found.");
778
779     float y;
780     if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("y"), y))
781         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'y' was not found.");
782
783     WebCore::FloatRect windowFrame;
784     page->getWindowFrame(windowFrame);
785
786     x = std::min(std::max(0.0f, x), windowFrame.size().width());
787     y = std::min(std::max(0.0f, y + page->topContentInset()), windowFrame.size().height());
788
789     WebCore::IntPoint viewPosition = WebCore::IntPoint(static_cast<int>(x), static_cast<int>(y));
790
791     auto parsedInteraction = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseInteraction>(mouseInteractionString);
792     if (!parsedInteraction)
793         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interaction' is invalid.");
794
795     auto parsedButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(mouseButtonString);
796     if (!parsedButton)
797         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'button' is invalid.");
798
799     WebEvent::Modifiers keyModifiers = (WebEvent::Modifiers)0;
800     for (auto it = keyModifierStrings.begin(); it != keyModifierStrings.end(); ++it) {
801         String modifierString;
802         if (!it->get()->asString(modifierString))
803             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'modifiers' is invalid.");
804
805         auto parsedModifier = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyModifier>(modifierString);
806         if (!parsedModifier)
807             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A modifier in the 'modifiers' array is invalid.");
808         WebEvent::Modifiers enumValue = protocolModifierToWebEventModifier(parsedModifier.value());
809         keyModifiers = (WebEvent::Modifiers)(enumValue | keyModifiers);
810     }
811
812     platformSimulateMouseInteraction(*page, viewPosition, parsedInteraction.value(), parsedButton.value(), keyModifiers);
813
814     updatedPositionObject = Inspector::Protocol::Automation::Point::create()
815         .setX(x)
816         .setY(y - page->topContentInset())
817         .release();
818 #endif // USE(APPKIT)
819 }
820
821 void WebAutomationSession::performKeyboardInteractions(ErrorString& errorString, const String& handle, const Inspector::InspectorArray& interactions, Ref<PerformKeyboardInteractionsCallback>&& callback)
822 {
823 #if !USE(APPKIT)
824     FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
825 #else
826     WebPageProxy* page = webPageProxyForHandle(handle);
827     if (!page)
828         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
829
830     if (!interactions.length())
831         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interactions' was not found or empty.");
832
833     // Validate all of the parameters before performing any interactions with the browsing context under test.
834     Vector<std::function<void()>> actionsToPerform;
835     actionsToPerform.reserveCapacity(interactions.length());
836
837     for (auto interaction : interactions) {
838         RefPtr<InspectorObject> interactionObject;
839         if (!interaction->asObject(interactionObject))
840             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter was invalid.");
841
842         String interactionTypeString;
843         if (!interactionObject->getString(ASCIILiteral("type"), interactionTypeString))
844             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter is missing the 'type' key.");
845         auto interactionType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyboardInteractionType>(interactionTypeString);
846         if (!interactionType)
847             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'type' key.");
848
849         String virtualKeyString;
850         bool foundVirtualKey = interactionObject->getString(ASCIILiteral("key"), virtualKeyString);
851         if (foundVirtualKey) {
852             auto virtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(virtualKeyString);
853             if (!virtualKey)
854                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
855
856             actionsToPerform.uncheckedAppend([this, page, interactionType, virtualKey] {
857                 platformSimulateKeyStroke(*page, interactionType.value(), virtualKey.value());
858             });
859         }
860
861         String keySequence;
862         bool foundKeySequence = interactionObject->getString(ASCIILiteral("text"), keySequence);
863         if (foundKeySequence) {
864             switch (interactionType.value()) {
865             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress:
866             case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease:
867                 // 'KeyPress' and 'KeyRelease' are meant for a virtual key and are not supported for a string (sequence of codepoints).
868                 FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
869
870             case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey:
871                 actionsToPerform.uncheckedAppend([this, page, keySequence] {
872                     platformSimulateKeySequence(*page, keySequence);
873                 });
874                 break;
875             }
876         }
877
878         if (!foundVirtualKey && !foundKeySequence)
879             FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "An interaction in the 'interactions' parameter is missing both 'key' and 'text'. One must be provided.");
880     }
881
882     ASSERT(actionsToPerform.size());
883     if (!actionsToPerform.size())
884         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "No actions to perform.");
885
886     auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
887     if (callbackInMap)
888         callbackInMap->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
889     callbackInMap = WTFMove(callback);
890
891     for (auto& action : actionsToPerform)
892         action();
893 #endif // USE(APPKIT)
894 }
895
896 void WebAutomationSession::takeScreenshot(ErrorString& errorString, const String& handle, Ref<TakeScreenshotCallback>&& callback)
897 {
898     WebPageProxy* page = webPageProxyForHandle(handle);
899     if (!page)
900         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
901
902     uint64_t callbackID = m_nextScreenshotCallbackID++;
903     m_screenshotCallbacks.set(callbackID, WTFMove(callback));
904
905     page->process().send(Messages::WebAutomationSessionProxy::TakeScreenshot(page->pageID(), callbackID), 0);
906 }
907
908 void WebAutomationSession::didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle& imageDataHandle, const String& errorType)
909 {
910     auto callback = m_screenshotCallbacks.take(callbackID);
911     if (!callback)
912         return;
913
914     if (!errorType.isEmpty()) {
915         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
916         return;
917     }
918
919     String base64EncodedData = platformGetBase64EncodedPNGData(imageDataHandle);
920     if (base64EncodedData.isEmpty()) {
921         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
922         return;
923     }
924
925     callback->sendSuccess(base64EncodedData);
926 }
927
928 // Platform-dependent Implementation Stubs.
929
930 #if !PLATFORM(MAC)
931 void WebAutomationSession::platformSimulateMouseInteraction(WebKit::WebPageProxy&, const WebCore::IntPoint&, Inspector::Protocol::Automation::MouseInteraction, Inspector::Protocol::Automation::MouseButton, WebEvent::Modifiers)
932 {
933 }
934
935 void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy&, Inspector::Protocol::Automation::KeyboardInteractionType, Inspector::Protocol::Automation::VirtualKey)
936 {
937 }
938
939 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy&, const String&)
940 {
941 }
942 #endif // !PLATFORM(MAC)
943
944 #if !PLATFORM(COCOA)
945 String WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&)
946 {
947     return String();
948 }
949 #endif // !PLATFORM(COCOA)
950
951 } // namespace WebKit