summaryrefslogtreecommitdiff
path: root/Source/WebKit2/UIProcess/Automation
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebKit2/UIProcess/Automation')
-rw-r--r--Source/WebKit2/UIProcess/Automation/Automation.json480
-rw-r--r--Source/WebKit2/UIProcess/Automation/WebAutomationSession.cpp951
-rw-r--r--Source/WebKit2/UIProcess/Automation/WebAutomationSession.h221
-rw-r--r--Source/WebKit2/UIProcess/Automation/WebAutomationSession.messages.in35
-rw-r--r--Source/WebKit2/UIProcess/Automation/WebAutomationSessionMacros.h52
5 files changed, 1739 insertions, 0 deletions
diff --git a/Source/WebKit2/UIProcess/Automation/Automation.json b/Source/WebKit2/UIProcess/Automation/Automation.json
new file mode 100644
index 000000000..bca27b875
--- /dev/null
+++ b/Source/WebKit2/UIProcess/Automation/Automation.json
@@ -0,0 +1,480 @@
+{
+ "domain": "Automation",
+ "description": "Automation domain exposes commands for automating user interactions with the browser.",
+ "types": [
+ {
+ "id": "Point",
+ "type": "object",
+ "properties": [
+ { "name": "x", "type": "number" },
+ { "name": "y", "type": "number" }
+ ]
+ },
+ {
+ "id": "Size",
+ "type": "object",
+ "properties": [
+ { "name": "width", "type": "number" },
+ { "name": "height", "type": "number" }
+ ]
+ },
+ {
+ "id": "Rect",
+ "type": "object",
+ "properties": [
+ { "name": "origin", "$ref": "Point" },
+ { "name": "size", "$ref": "Size" }
+ ]
+ },
+ {
+ "id": "BrowsingContextHandle",
+ "type": "string",
+ "description": "An opaque identifier for a browsing context."
+ },
+ {
+ "id": "FrameHandle",
+ "type": "string",
+ "description": "An opaque identifier for a frame in a page."
+ },
+ {
+ "id": "NodeHandle",
+ "type": "string",
+ "description": "An opaque identifier for a node in a page."
+ },
+ {
+ "id": "ErrorMessage",
+ "type": "string",
+ "description": "This enum contains predefined error messages that can be used to signal a well-defined error condition, such as a missing implementation, unknown window handle, and so forth. The backend signals one of these errors by using it as a prefix of the commands's error message (the errorString argument in generated C++ backend dispatchers). This will be reported to the frontend as a protocol error with a JSON-RPC error code of 'ServerError'. It is up to the frontend whether and how to deal with errors.",
+ "enum": [
+ "InternalError",
+ "Timeout",
+ "JavaScriptError",
+ "JavaScriptTimeout",
+ "WindowNotFound",
+ "FrameNotFound",
+ "NodeNotFound",
+ "InvalidElementState",
+ "NoJavaScriptDialog",
+ "NotImplemented",
+ "MissingParameter",
+ "InvalidParameter"
+ ]
+ },
+ {
+ "id": "BrowsingContext",
+ "type": "object",
+ "description": "A handle representing an open window or tab in the automation session.",
+ "properties": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "Opaque handle for a browsing context (window or tab). Used as a key for window-related commands." },
+ { "name": "active", "type": "boolean", "description": "Whether the browsing context has focus at the time the command is handled." },
+ { "name": "url", "type": "string", "description": "The URL loaded by the browsing context at the time the command is handled." },
+ { "name": "windowSize", "$ref": "Size", "description": "The current size of the browsing context's window." },
+ { "name": "windowOrigin", "$ref": "Point", "description": "The current (x, y) position of the browsing context's window relative to the top-left of the screen." }
+ ]
+ },
+ {
+ "id": "MouseInteraction",
+ "type": "string",
+ "description": "Enumerates different ways of interacting with a mouse cursor.",
+ "enum": [
+ "Move",
+ "Down",
+ "Up",
+ "SingleClick",
+ "DoubleClick"
+ ]
+ },
+ {
+ "id": "MouseButton",
+ "type": "string",
+ "description": "Enumerates different mouse buttons that can be used.",
+ "enum": [
+ "None",
+ "Left",
+ "Middle",
+ "Right"
+ ]
+ },
+ {
+ "id": "KeyModifier",
+ "type": "string",
+ "description": "Enumerates different key modifiers that can remain pressed during other mouse/touch interactions.",
+ "enum": [
+ "CapsLock",
+ "Control",
+ "Shift",
+ "Meta",
+ "Alt"
+ ]
+ },
+ {
+ "id": "VirtualKey",
+ "type": "string",
+ "description": "Enumerates different platform-independent virtual keys on a physical keyboard whose input via keyboard may or may not produce characters.",
+ "enum": [
+ "Shift",
+ "Control",
+ "Alternate",
+ "Meta",
+ "Command",
+ "Cancel",
+ "Help",
+ "Backspace",
+ "Tab",
+ "Clear",
+ "Enter",
+ "Pause",
+ "Escape",
+ "PageUp",
+ "PageDown",
+ "End",
+ "Home",
+ "LeftArrow",
+ "UpArrow",
+ "RightArrow",
+ "DownArrow",
+ "Insert",
+ "Delete",
+ "Space",
+ "Semicolon",
+ "Equals",
+ "Return",
+ "NumberPad0",
+ "NumberPad1",
+ "NumberPad2",
+ "NumberPad3",
+ "NumberPad4",
+ "NumberPad5",
+ "NumberPad6",
+ "NumberPad7",
+ "NumberPad8",
+ "NumberPad9",
+ "NumberPadMultiply",
+ "NumberPadAdd",
+ "NumberPadSeparator",
+ "NumberPadSubtract",
+ "NumberPadDecimal",
+ "NumberPadDivide",
+ "Function1",
+ "Function2",
+ "Function3",
+ "Function4",
+ "Function5",
+ "Function6",
+ "Function7",
+ "Function8",
+ "Function9",
+ "Function10",
+ "Function11",
+ "Function12"
+ ]
+ },
+ {
+ "id": "KeyboardInteractionType",
+ "type": "string",
+ "description": "Enumerates different ways of interacting with a keyboard device. 'InsertByKey' implies that a separate KeyDown and KeyUp event are produced for each combining character sequence, regardless of the actual keystrokes required to produce the character sequence.",
+ "enum": [
+ "KeyPress",
+ "KeyRelease",
+ "InsertByKey"
+ ]
+ },
+ {
+ "id": "KeyboardInteraction",
+ "type": "object",
+ "description": "A single step in a key sequence. A step can contain a key up/down of a virtual key, or a sequence of Unicode code points that are delivered to the page at grapheme cluster boundaries. Either a 'key' or 'text' property must be specified.",
+ "properties": [
+ { "name": "type", "$ref": "KeyboardInteractionType", "description": "The type of interaction to be performed by a step." },
+ { "name": "key", "$ref": "VirtualKey", "optional": true, "description": "A virtual key to be used to perform the specified interaction." },
+ { "name": "text", "type": "string", "optional": true, "description": "A unicode string to be delivered to the page. The sequence of key events is determined by splitting the string at grapheme cluster boundaries." }
+ ]
+ },
+ {
+ "id": "Cookie",
+ "type": "object",
+ "properties": [
+ { "name": "name", "type": "string", "description": "Cookie name." },
+ { "name": "value", "type": "string", "description": "Cookie value." },
+ { "name": "domain", "type": "string", "description": "Cookie domain. If empty, the domain is inherited from the relevant browsing context." },
+ { "name": "path", "type": "string", "description": "Cookie path." },
+ { "name": "expires", "type": "number", "description": "Cookie expiration in seconds since the UNIX epoch." },
+ { "name": "size", "type": "integer", "description": "Cookie size." },
+ { "name": "httpOnly", "type": "boolean", "description": "True if cookie is http-only." },
+ { "name": "secure", "type": "boolean", "description": "True if cookie is secure." },
+ { "name": "session", "type": "boolean", "description": "True in case of session cookie." }
+ ]
+ }
+ ],
+ "commands": [
+ {
+ "name": "getBrowsingContexts",
+ "description": "Fetches information about all open browsing contexts (windows and tabs) in the automation session.",
+ "returns": [
+ { "name": "contexts", "type": "array", "items": { "$ref": "BrowsingContext" }, "description": "All known browsing contexts available to the session." }
+ ]
+ },
+ {
+ "name": "getBrowsingContext",
+ "description": "Fetches information about the specified browsing context.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." }
+ ],
+ "returns": [
+ { "name": "context", "$ref": "BrowsingContext", "description": "The browsing context available to the session." }
+ ]
+ },
+ {
+ "name": "createBrowsingContext",
+ "description": "Opens a new browsing context in the automation session and makes it the active browsing context for user interaction. This command creates a browsing context in a new window rather than adding a tab to an existing window.",
+ "returns": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "An opaque handle for the newly created browsing context." }
+ ]
+ },
+ {
+ "name": "closeBrowsingContext",
+ "description": "Try to close the specified browsing context. This can trigger an unload prompt and thereafter succeed or fail.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be closed." }
+ ]
+ },
+ {
+ "name": "switchToBrowsingContext",
+ "description": "Activates the specified browsing context and optional frame, which gives them focus (causing the 'focus' DOM event to fire, and 'blur' DOM event to fire for the previous browsing context and frame).",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be made focused." },
+ { "name": "frameHandle", "$ref": "FrameHandle", "optional": true, "description": "The handle for the frame that should be focused. Defaults to the main frame if omitted." }
+ ]
+ },
+ {
+ "name": "resizeWindowOfBrowsingContext",
+ "description": "Resizes the window of the specified browsing context to the specified size.",
+ "platform": "macos",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context to be resized." },
+ { "name": "size", "$ref": "Size", "description": "The new size for the browsing context's window." }
+ ]
+ },
+ {
+ "name": "moveWindowOfBrowsingContext",
+ "description": "Moves the window of the specified browsing context to the specified position.",
+ "platform": "macos",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context to be moved." },
+ { "name": "origin", "$ref": "Point", "description": "The new origin for the browsing context's window. The position is interpreted in screen coordinate space, relative to the upper left corner of the screen." }
+ ]
+ },
+ {
+ "name": "navigateBrowsingContext",
+ "description": "Navigates a browsing context to a specified URL.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be navigated." },
+ { "name": "url", "type": "string", "description": "The URL to load in the browsing context." }
+ ],
+ "async": true
+ },
+ {
+ "name": "goBackInBrowsingContext",
+ "description": "Navigates a browsing context to go back one page in history.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be navigated." }
+ ],
+ "async": true
+ },
+ {
+ "name": "goForwardInBrowsingContext",
+ "description": "Navigates a browsing context to got forward one page in history.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be navigated." }
+ ],
+ "async": true
+ },
+ {
+ "name": "reloadBrowsingContext",
+ "description": "Reloads the current page in a browsing context.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be reloaded." }
+ ],
+ "async": true
+ },
+ {
+ "name": "inspectBrowsingContext",
+ "description": "Inspect the specified browsing context using Web Inspector.",
+ "platform": "macos",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be inspected." },
+ { "name": "enableAutoCapturing", "type": "boolean", "optional": true, "description": "If this option is present and set to true, the Web Inspector will automatically start a timeline recording of the specified browsing context once it is attached. Note that this disables the debugger for the duration of the recording." }
+ ],
+ "async": true
+ },
+ {
+ "name": "evaluateJavaScriptFunction",
+ "description": "Evaluates a script function in a browsing context and calls it with the supplied arguments.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context the script should be evaluated." },
+ { "name": "frameHandle", "$ref": "FrameHandle", "optional": true, "description": "The handle for the frame the script should be evaluated. The main frame is used if this parameter empty string or excluded." },
+ { "name": "function", "type": "string", "description": "The script to evaluate in the browsing context. It must be a function result." },
+ { "name": "arguments", "type": "array", "items": { "type": "string" }, "description": "The arguments to pass to the function when called. They will be parsed as JSON before calling the function." },
+ { "name": "expectsImplicitCallbackArgument", "type": "boolean", "optional": true, "description": "The function expects a callback function as the last argument. It is expected to call this callback with a result." },
+ { "name": "callbackTimeout", "type": "integer", "optional": true, "description": "The timeout in milliseconds that the implicit callback is expected to be called in, otherwise a <code>JavaScriptTimeout</code> error is returned." }
+ ],
+ "returns": [
+ { "name": "result", "type": "string", "description": "The result returned by the called function. It is JSON encoded after the function returns or calls the callback." }
+ ],
+ "async": true
+ },
+ {
+ "name": "performMouseInteraction",
+ "description": "Simulates interaction with a mouse cursor.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The browsing context to be interacted with." },
+ { "name": "position", "$ref": "Point", "description": "The requested position for the mouse cursor, specified in viewport coordinates." },
+ { "name": "button", "$ref": "MouseButton", "description": "The button to use to perform the interaction. This parameter only has an effect for the <code>Down</code>, <code>Up</code>, <code>Click</code>, and <code>DoubleClick</code> mouse interactions." },
+ { "name": "interaction", "$ref": "MouseInteraction", "description": "The type of interaction to be performed with the mouse cursor." },
+ { "name": "modifiers", "type": "array", "items": { "$ref": "KeyModifier" }, "description": "Key modifiers to be active during the mouse interaction." }
+ ],
+ "returns": [
+ { "name": "position", "$ref": "Point", "description": "The updated position of the mouse cursor, specified in viewport coordinates." }
+ ]
+ },
+ {
+ "name": "performKeyboardInteractions",
+ "description": "Simulates delivering the results of pressing one or more keyboard keys together or successively.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context which should receive key." },
+ { "name": "interactions", "type": "array", "items": { "$ref": "KeyboardInteraction" }, "description": "An ordered list of key sequences to be delivered using native key events." }
+ ],
+ "async": true
+ },
+ {
+ "name": "takeScreenshot",
+ "description": "Take a screenshot of the current page in a browsing context.",
+ "parameters": [
+ { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context to take a screenshot of." }
+ ],
+ "returns": [
+ { "name": "data", "type": "string", "description": "Base64-encoded image data (PNG)." }
+ ],
+ "async": true
+ },
+ {
+ "name": "resolveChildFrameHandle",
+ "description": "Determines the <code>FrameHandle</code> based on the ordinal, name or node handle of a child frame.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context in which to search for the frame." },
+ { "name": "frameHandle", "$ref": "FrameHandle", "optional": true, "description": "The handle for the frame in which to search for the frame. The main frame is used if this parameter empty string or excluded." },
+ { "name": "ordinal", "type": "integer", "optional": true, "description": "The ordinal of the child frame to resolve as a <code>FrameHandle</code>. This is analogous to 'window.frames[ordinal]' in JavaScript." },
+ { "name": "name", "type": "string", "optional": true, "description": "The name of the child frame to resolve as a <code>FrameHandle</code>. This is analogous to 'window.frames[name]' in JavaScript." },
+ { "name": "nodeHandle", "$ref": "NodeHandle", "optional": true, "description": "The handle of the child frame owner element to resolve as a <code>FrameHandle</code>." }
+ ],
+ "returns": [
+ { "name": "result", "$ref": "FrameHandle", "description": "The <code>FrameHandle</code> for the requested frame." }
+ ],
+ "async": true
+ },
+ {
+ "name": "resolveParentFrameHandle",
+ "description": "Determines the <code>FrameHandle</code> for the parent frame of the supplied <code>FrameHandle</code>.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context the frame is located." },
+ { "name": "frameHandle", "$ref": "FrameHandle", "description": "The handle for the frame that should resolve its parent frame." }
+ ],
+ "returns": [
+ { "name": "result", "$ref": "FrameHandle", "description": "The <code>FrameHandle</code> for the requested frame." }
+ ],
+ "async": true
+ },
+ {
+ "name": "computeElementLayout",
+ "description": "Computes the layout for an element.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context the frame is located." },
+ { "name": "frameHandle", "$ref": "FrameHandle", "description": "The handle for the frame that contains the element." },
+ { "name": "nodeHandle", "$ref": "NodeHandle", "description": "The handle of the element to use." },
+ { "name": "scrollIntoViewIfNeeded", "optional": true, "type": "boolean", "description": "If the element should be scrolled into view before computing its layout." },
+ { "name": "useViewportCoordinates", "optional": true, "type": "boolean", "description": "If the result coordinates should be represented as viewport coordinates or not. Defaults to false, which means coordinates should be represented as page coordinates." }
+ ],
+ "returns": [
+ { "name": "rect", "$ref": "Rect", "description": "The layout rect for the requested element. Specified in page or viewport coordinates based on the useViewportCoordinates parameter." }
+ ],
+ "async": true
+ },
+ {
+ "name": "isShowingJavaScriptDialog",
+ "description": "Checks if a browsing context is showing a JavaScript alert, confirm, or prompt dialog.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." }
+ ],
+ "returns": [
+ { "name": "result", "type": "boolean", "description": "If the browsing context is showing a JavaScript dialog or not." }
+ ]
+ },
+ {
+ "name": "dismissCurrentJavaScriptDialog",
+ "description": "Dismisses a JavaScript alert, confirm, or prompt dialog currently showing in a browsing context. Equivalent to clicking the 'Cancel' button.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." }
+ ]
+ },
+ {
+ "name": "acceptCurrentJavaScriptDialog",
+ "description": "Accepts a JavaScript alert, confirm, or prompt dialog currently showing in a browsing context. Equivalent to clicking the 'OK' button.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." }
+ ]
+ },
+ {
+ "name": "messageOfCurrentJavaScriptDialog",
+ "description": "Returns the text displayed in a JavaScript alert, confirm, or prompt dialog currently showing in a browsing context.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." }
+ ],
+ "returns": [
+ { "name": "message", "type": "string", "description": "Text being displayed in the current JavaScript dialog." }
+ ]
+ },
+ {
+ "name": "setUserInputForCurrentJavaScriptPrompt",
+ "description": "Sets the user entered value in a JavaScript prompt dialog currently showing in a browsing context.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." },
+ { "name": "userInput", "type": "string", "description": "The text to enter in the prompt." }
+ ]
+ },
+ {
+ "name": "getAllCookies",
+ "description": "Returns all cookies visible to the specified browsing context.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." }
+ ],
+ "returns": [
+ { "name": "cookies", "type": "array", "items": { "$ref": "Cookie" }, "description": "Array of cookie objects." }
+ ],
+ "async": true
+ },
+ {
+ "name": "deleteSingleCookie",
+ "description": "Deletes a cookie with the given name if visible to the specified browsing context.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." },
+ { "name": "cookieName", "type": "string", "description": "Name of the cookie to remove." }
+ ],
+ "async": true
+ },
+ {
+ "name": "addSingleCookie",
+ "description": "Add a cookie to cookie storage for the specified browsing context.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." },
+ { "name": "cookie", "$ref": "Cookie", "description": "The cookie that should be added to storage." }
+ ],
+ "async": true
+ },
+ {
+ "name": "deleteAllCookies",
+ "description": "Delete all cookies that are visible to the specified browsing context.",
+ "parameters": [
+ { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." }
+ ]
+ }
+ ]
+}
diff --git a/Source/WebKit2/UIProcess/Automation/WebAutomationSession.cpp b/Source/WebKit2/UIProcess/Automation/WebAutomationSession.cpp
new file mode 100644
index 000000000..f748eeeee
--- /dev/null
+++ b/Source/WebKit2/UIProcess/Automation/WebAutomationSession.cpp
@@ -0,0 +1,951 @@
+/*
+ * Copyright (C) 2016, 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "WebAutomationSession.h"
+
+#include "APIAutomationSessionClient.h"
+#include "AutomationProtocolObjects.h"
+#include "WebAutomationSessionMacros.h"
+#include "WebAutomationSessionMessages.h"
+#include "WebAutomationSessionProxyMessages.h"
+#include "WebCookieManagerProxy.h"
+#include "WebInspectorProxy.h"
+#include "WebProcessPool.h"
+#include <JavaScriptCore/InspectorBackendDispatcher.h>
+#include <JavaScriptCore/InspectorFrontendRouter.h>
+#include <WebCore/URL.h>
+#include <WebCore/UUID.h>
+#include <algorithm>
+#include <wtf/HashMap.h>
+#include <wtf/Optional.h>
+#include <wtf/text/StringConcatenate.h>
+
+using namespace Inspector;
+
+namespace WebKit {
+
+WebAutomationSession::WebAutomationSession()
+ : m_client(std::make_unique<API::AutomationSessionClient>())
+ , m_frontendRouter(FrontendRouter::create())
+ , m_backendDispatcher(BackendDispatcher::create(m_frontendRouter.copyRef()))
+ , m_domainDispatcher(AutomationBackendDispatcher::create(m_backendDispatcher, this))
+{
+}
+
+WebAutomationSession::~WebAutomationSession()
+{
+ ASSERT(!m_client);
+
+ if (m_processPool)
+ m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
+}
+
+void WebAutomationSession::setClient(std::unique_ptr<API::AutomationSessionClient> client)
+{
+ m_client = WTFMove(client);
+}
+
+void WebAutomationSession::setProcessPool(WebKit::WebProcessPool* processPool)
+{
+ if (m_processPool)
+ m_processPool->removeMessageReceiver(Messages::WebAutomationSession::messageReceiverName());
+
+ m_processPool = processPool;
+
+ if (m_processPool)
+ m_processPool->addMessageReceiver(Messages::WebAutomationSession::messageReceiverName(), *this);
+}
+
+// NOTE: this class could be split at some point to support local and remote automation sessions.
+// For now, it only works with a remote automation driver over a RemoteInspector connection.
+
+#if ENABLE(REMOTE_INSPECTOR)
+
+// Inspector::RemoteAutomationTarget API
+
+void WebAutomationSession::dispatchMessageFromRemote(const String& message)
+{
+ m_backendDispatcher->dispatch(message);
+}
+
+void WebAutomationSession::connect(Inspector::FrontendChannel* channel, bool isAutomaticConnection)
+{
+ UNUSED_PARAM(isAutomaticConnection);
+
+ m_remoteChannel = channel;
+ m_frontendRouter->connectFrontend(channel);
+
+ setIsPaired(true);
+}
+
+void WebAutomationSession::disconnect(Inspector::FrontendChannel* channel)
+{
+ ASSERT(channel == m_remoteChannel);
+ terminate();
+}
+
+#endif // ENABLE(REMOTE_INSPECTOR)
+
+void WebAutomationSession::terminate()
+{
+#if ENABLE(REMOTE_INSPECTOR)
+ if (Inspector::FrontendChannel* channel = m_remoteChannel) {
+ m_remoteChannel = nullptr;
+ m_frontendRouter->disconnectFrontend(channel);
+ }
+
+ setIsPaired(false);
+#endif
+
+ if (m_client)
+ m_client->didDisconnectFromRemote(this);
+}
+
+WebPageProxy* WebAutomationSession::webPageProxyForHandle(const String& handle)
+{
+ auto iter = m_handleWebPageMap.find(handle);
+ if (iter == m_handleWebPageMap.end())
+ return nullptr;
+ return WebProcessProxy::webPage(iter->value);
+}
+
+String WebAutomationSession::handleForWebPageProxy(const WebPageProxy& webPageProxy)
+{
+ auto iter = m_webPageHandleMap.find(webPageProxy.pageID());
+ if (iter != m_webPageHandleMap.end())
+ return iter->value;
+
+ String handle = "page-" + WebCore::createCanonicalUUIDString().convertToASCIIUppercase();
+
+ auto firstAddResult = m_webPageHandleMap.add(webPageProxy.pageID(), handle);
+ RELEASE_ASSERT(firstAddResult.isNewEntry);
+
+ auto secondAddResult = m_handleWebPageMap.add(handle, webPageProxy.pageID());
+ RELEASE_ASSERT(secondAddResult.isNewEntry);
+
+ return handle;
+}
+
+std::optional<uint64_t> WebAutomationSession::webFrameIDForHandle(const String& handle)
+{
+ if (handle.isEmpty())
+ return 0;
+
+ auto iter = m_handleWebFrameMap.find(handle);
+ if (iter == m_handleWebFrameMap.end())
+ return std::nullopt;
+
+ return iter->value;
+}
+
+String WebAutomationSession::handleForWebFrameID(uint64_t frameID)
+{
+ if (!frameID)
+ return emptyString();
+
+ for (auto& process : m_processPool->processes()) {
+ if (WebFrameProxy* frame = process->webFrame(frameID)) {
+ if (frame->isMainFrame())
+ return emptyString();
+ break;
+ }
+ }
+
+ auto iter = m_webFrameHandleMap.find(frameID);
+ if (iter != m_webFrameHandleMap.end())
+ return iter->value;
+
+ String handle = "frame-" + WebCore::createCanonicalUUIDString().convertToASCIIUppercase();
+
+ auto firstAddResult = m_webFrameHandleMap.add(frameID, handle);
+ RELEASE_ASSERT(firstAddResult.isNewEntry);
+
+ auto secondAddResult = m_handleWebFrameMap.add(handle, frameID);
+ RELEASE_ASSERT(secondAddResult.isNewEntry);
+
+ return handle;
+}
+
+String WebAutomationSession::handleForWebFrameProxy(const WebFrameProxy& webFrameProxy)
+{
+ return handleForWebFrameID(webFrameProxy.frameID());
+}
+
+RefPtr<Inspector::Protocol::Automation::BrowsingContext> WebAutomationSession::buildBrowsingContextForPage(WebPageProxy& page)
+{
+ WebCore::FloatRect windowFrame;
+ page.getWindowFrame(windowFrame);
+
+ auto originObject = Inspector::Protocol::Automation::Point::create()
+ .setX(windowFrame.x())
+ .setY(windowFrame.y())
+ .release();
+
+ auto sizeObject = Inspector::Protocol::Automation::Size::create()
+ .setWidth(windowFrame.width())
+ .setHeight(windowFrame.height())
+ .release();
+
+ String handle = handleForWebPageProxy(page);
+
+ return Inspector::Protocol::Automation::BrowsingContext::create()
+ .setHandle(handle)
+ .setActive(m_activeBrowsingContextHandle == handle)
+ .setUrl(page.pageLoadState().activeURL())
+ .setWindowOrigin(WTFMove(originObject))
+ .setWindowSize(WTFMove(sizeObject))
+ .release();
+}
+
+// Platform-independent Commands.
+
+void WebAutomationSession::getBrowsingContexts(Inspector::ErrorString& errorString, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>>& contexts)
+{
+ contexts = Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>::create();
+
+ for (auto& process : m_processPool->processes()) {
+ for (auto* page : process->pages()) {
+ ASSERT(page);
+ if (!page->isControlledByAutomation())
+ continue;
+
+ contexts->addItem(buildBrowsingContextForPage(*page));
+ }
+ }
+}
+
+void WebAutomationSession::getBrowsingContext(Inspector::ErrorString& errorString, const String& handle, RefPtr<Inspector::Protocol::Automation::BrowsingContext>& context)
+{
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ context = buildBrowsingContextForPage(*page);
+}
+
+void WebAutomationSession::createBrowsingContext(Inspector::ErrorString& errorString, String* handle)
+{
+ ASSERT(m_client);
+ if (!m_client)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session could not request a new browsing context.");
+
+ WebPageProxy* page = m_client->didRequestNewWindow(this);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote session failed to create a new browsing context.");
+
+ m_activeBrowsingContextHandle = *handle = handleForWebPageProxy(*page);
+}
+
+void WebAutomationSession::closeBrowsingContext(Inspector::ErrorString& errorString, const String& handle)
+{
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (handle == m_activeBrowsingContextHandle)
+ m_activeBrowsingContextHandle = emptyString();
+
+ page->closePage(false);
+}
+
+void WebAutomationSession::switchToBrowsingContext(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle)
+{
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
+ if (!frameID)
+ FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
+
+ // FIXME: We don't need to track this in WK2. Remove in a follow up.
+ m_activeBrowsingContextHandle = browsingContextHandle;
+
+ page->setFocus(true);
+ page->process().send(Messages::WebAutomationSessionProxy::FocusFrame(page->pageID(), frameID.value()), 0);
+}
+
+void WebAutomationSession::navigateBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const String& url, Ref<NavigateBrowsingContextCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page->pageID()))
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
+ m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page->pageID(), WTFMove(callback));
+
+ page->loadRequest(WebCore::URL(WebCore::URL(), url));
+}
+
+void WebAutomationSession::goBackInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, Ref<GoBackInBrowsingContextCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page->pageID()))
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
+ m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page->pageID(), WTFMove(callback));
+
+ page->goBack();
+}
+
+void WebAutomationSession::goForwardInBrowsingContext(Inspector::ErrorString& errorString, const String& handle, Ref<GoForwardInBrowsingContextCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page->pageID()))
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
+ m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page->pageID(), WTFMove(callback));
+
+ page->goForward();
+}
+
+void WebAutomationSession::reloadBrowsingContext(Inspector::ErrorString& errorString, const String& handle, Ref<ReloadBrowsingContextCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page->pageID()))
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
+ m_pendingNavigationInBrowsingContextCallbacksPerPage.set(page->pageID(), WTFMove(callback));
+
+ const bool reloadFromOrigin = false;
+ page->reload(reloadFromOrigin, { });
+}
+
+void WebAutomationSession::navigationOccurredForPage(const WebPageProxy& page)
+{
+ if (auto callback = m_pendingNavigationInBrowsingContextCallbacksPerPage.take(page.pageID()))
+ callback->sendSuccess(InspectorObject::create());
+}
+
+void WebAutomationSession::inspectorFrontendLoaded(const WebPageProxy& page)
+{
+ if (auto callback = m_pendingInspectorCallbacksPerPage.take(page.pageID()))
+ callback->sendSuccess(InspectorObject::create());
+}
+
+void WebAutomationSession::keyboardEventsFlushedForPage(const WebPageProxy& page)
+{
+ if (auto callback = m_pendingKeyboardEventsFlushedCallbacksPerPage.take(page.pageID()))
+ callback->sendSuccess(InspectorObject::create());
+}
+
+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)
+{
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
+ if (!frameID)
+ FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
+
+ Vector<String> argumentsVector;
+ argumentsVector.reserveCapacity(arguments.length());
+
+ for (auto& argument : arguments) {
+ String argumentString;
+ argument->asString(argumentString);
+ argumentsVector.uncheckedAppend(argumentString);
+ }
+
+ bool expectsImplicitCallbackArgument = optionalExpectsImplicitCallbackArgument ? *optionalExpectsImplicitCallbackArgument : false;
+ int callbackTimeout = optionalCallbackTimeout ? *optionalCallbackTimeout : 0;
+
+ uint64_t callbackID = m_nextEvaluateJavaScriptCallbackID++;
+ m_evaluateJavaScriptFunctionCallbacks.set(callbackID, WTFMove(callback));
+
+ page->process().send(Messages::WebAutomationSessionProxy::EvaluateJavaScriptFunction(page->pageID(), frameID.value(), function, argumentsVector, expectsImplicitCallbackArgument, callbackTimeout, callbackID), 0);
+}
+
+void WebAutomationSession::didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType)
+{
+ auto callback = m_evaluateJavaScriptFunctionCallbacks.take(callbackID);
+ if (!callback)
+ return;
+
+ if (!errorType.isEmpty())
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE_AND_DETAILS(errorType, result));
+ else
+ callback->sendSuccess(result);
+}
+
+void WebAutomationSession::resolveChildFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&& callback)
+{
+ if (!optionalOrdinal && !optionalName && !optionalNodeHandle)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "Command must specify a child frame by ordinal, name, or element handle.");
+
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ std::optional<uint64_t> frameID = webFrameIDForHandle(optionalFrameHandle ? *optionalFrameHandle : emptyString());
+ if (!frameID)
+ FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
+
+ uint64_t callbackID = m_nextResolveFrameCallbackID++;
+ m_resolveChildFrameHandleCallbacks.set(callbackID, WTFMove(callback));
+
+ if (optionalNodeHandle) {
+ page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithNodeHandle(page->pageID(), frameID.value(), *optionalNodeHandle, callbackID), 0);
+ return;
+ }
+
+ if (optionalName) {
+ page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithName(page->pageID(), frameID.value(), *optionalName, callbackID), 0);
+ return;
+ }
+
+ if (optionalOrdinal) {
+ page->process().send(Messages::WebAutomationSessionProxy::ResolveChildFrameWithOrdinal(page->pageID(), frameID.value(), *optionalOrdinal, callbackID), 0);
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void WebAutomationSession::didResolveChildFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
+{
+ auto callback = m_resolveChildFrameHandleCallbacks.take(callbackID);
+ if (!callback)
+ return;
+
+ if (!errorType.isEmpty())
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
+ else
+ callback->sendSuccess(handleForWebFrameID(frameID));
+}
+
+void WebAutomationSession::resolveParentFrameHandle(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
+ if (!frameID)
+ FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
+
+ uint64_t callbackID = m_nextResolveParentFrameCallbackID++;
+ m_resolveParentFrameHandleCallbacks.set(callbackID, WTFMove(callback));
+
+ page->process().send(Messages::WebAutomationSessionProxy::ResolveParentFrame(page->pageID(), frameID.value(), callbackID), 0);
+}
+
+void WebAutomationSession::didResolveParentFrame(uint64_t callbackID, uint64_t frameID, const String& errorType)
+{
+ auto callback = m_resolveParentFrameHandleCallbacks.take(callbackID);
+ if (!callback)
+ return;
+
+ if (!errorType.isEmpty())
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
+ else
+ callback->sendSuccess(handleForWebFrameID(frameID));
+}
+
+void WebAutomationSession::computeElementLayout(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* optionalUseViewportCoordinates, Ref<ComputeElementLayoutCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
+ if (!frameID)
+ FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
+
+ uint64_t callbackID = m_nextComputeElementLayoutCallbackID++;
+ m_computeElementLayoutCallbacks.set(callbackID, WTFMove(callback));
+
+ bool scrollIntoViewIfNeeded = optionalScrollIntoViewIfNeeded ? *optionalScrollIntoViewIfNeeded : false;
+ bool useViewportCoordinates = optionalUseViewportCoordinates ? *optionalUseViewportCoordinates : false;
+
+ page->process().send(Messages::WebAutomationSessionProxy::ComputeElementLayout(page->pageID(), frameID.value(), nodeHandle, scrollIntoViewIfNeeded, useViewportCoordinates, callbackID), 0);
+}
+
+void WebAutomationSession::didComputeElementLayout(uint64_t callbackID, WebCore::IntRect rect, const String& errorType)
+{
+ auto callback = m_computeElementLayoutCallbacks.take(callbackID);
+ if (!callback)
+ return;
+
+ if (!errorType.isEmpty()) {
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
+ return;
+ }
+
+ auto originObject = Inspector::Protocol::Automation::Point::create()
+ .setX(rect.x())
+ .setY(rect.y())
+ .release();
+
+ auto sizeObject = Inspector::Protocol::Automation::Size::create()
+ .setWidth(rect.width())
+ .setHeight(rect.height())
+ .release();
+
+ auto rectObject = Inspector::Protocol::Automation::Rect::create()
+ .setOrigin(WTFMove(originObject))
+ .setSize(WTFMove(sizeObject))
+ .release();
+
+ callback->sendSuccess(WTFMove(rectObject));
+}
+
+void WebAutomationSession::isShowingJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, bool* result)
+{
+ ASSERT(m_client);
+ if (!m_client)
+ FAIL_WITH_PREDEFINED_ERROR(InternalError);
+
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ *result = m_client->isShowingJavaScriptDialogOnPage(this, page);
+}
+
+void WebAutomationSession::dismissCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
+{
+ ASSERT(m_client);
+ if (!m_client)
+ FAIL_WITH_PREDEFINED_ERROR(InternalError);
+
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (!m_client->isShowingJavaScriptDialogOnPage(this, page))
+ FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
+
+ m_client->dismissCurrentJavaScriptDialogOnPage(this, page);
+}
+
+void WebAutomationSession::acceptCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle)
+{
+ ASSERT(m_client);
+ if (!m_client)
+ FAIL_WITH_PREDEFINED_ERROR(InternalError);
+
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (!m_client->isShowingJavaScriptDialogOnPage(this, page))
+ FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
+
+ m_client->acceptCurrentJavaScriptDialogOnPage(this, page);
+}
+
+void WebAutomationSession::messageOfCurrentJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, String* text)
+{
+ ASSERT(m_client);
+ if (!m_client)
+ FAIL_WITH_PREDEFINED_ERROR(InternalError);
+
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (!m_client->isShowingJavaScriptDialogOnPage(this, page))
+ FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
+
+ *text = m_client->messageOfCurrentJavaScriptDialogOnPage(this, page);
+}
+
+void WebAutomationSession::setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& promptValue)
+{
+ ASSERT(m_client);
+ if (!m_client)
+ FAIL_WITH_PREDEFINED_ERROR(InternalError);
+
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (!m_client->isShowingJavaScriptDialogOnPage(this, page))
+ FAIL_WITH_PREDEFINED_ERROR(NoJavaScriptDialog);
+
+ m_client->setUserInputForCurrentJavaScriptPromptOnPage(this, page, promptValue);
+}
+
+void WebAutomationSession::getAllCookies(ErrorString& errorString, const String& browsingContextHandle, Ref<GetAllCookiesCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ // 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.
+ const uint64_t mainFrameID = 0;
+
+ uint64_t callbackID = m_nextGetCookiesCallbackID++;
+ m_getCookieCallbacks.set(callbackID, WTFMove(callback));
+
+ page->process().send(Messages::WebAutomationSessionProxy::GetCookiesForFrame(page->pageID(), mainFrameID, callbackID), 0);
+}
+
+static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
+{
+ return Inspector::Protocol::Automation::Cookie::create()
+ .setName(cookie.name)
+ .setValue(cookie.value)
+ .setDomain(cookie.domain)
+ .setPath(cookie.path)
+ .setExpires(cookie.expires)
+ .setSize((cookie.name.length() + cookie.value.length()))
+ .setHttpOnly(cookie.httpOnly)
+ .setSecure(cookie.secure)
+ .setSession(cookie.session)
+ .release();
+}
+
+static Ref<Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>> buildArrayForCookies(Vector<WebCore::Cookie>& cookiesList)
+{
+ auto cookies = Inspector::Protocol::Array<Inspector::Protocol::Automation::Cookie>::create();
+
+ for (const auto& cookie : cookiesList)
+ cookies->addItem(buildObjectForCookie(cookie));
+
+ return cookies;
+}
+
+void WebAutomationSession::didGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie> cookies, const String& errorType)
+{
+ auto callback = m_getCookieCallbacks.take(callbackID);
+ if (!callback)
+ return;
+
+ if (!errorType.isEmpty()) {
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
+ return;
+ }
+
+ callback->sendSuccess(buildArrayForCookies(cookies));
+}
+
+void WebAutomationSession::deleteSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ // 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.
+ const uint64_t mainFrameID = 0;
+
+ uint64_t callbackID = m_nextDeleteCookieCallbackID++;
+ m_deleteCookieCallbacks.set(callbackID, WTFMove(callback));
+
+ page->process().send(Messages::WebAutomationSessionProxy::DeleteCookie(page->pageID(), mainFrameID, cookieName, callbackID), 0);
+}
+
+void WebAutomationSession::didDeleteCookie(uint64_t callbackID, const String& errorType)
+{
+ auto callback = m_deleteCookieCallbacks.take(callbackID);
+ if (!callback)
+ return;
+
+ if (!errorType.isEmpty()) {
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
+ return;
+ }
+
+ callback->sendSuccess();
+}
+
+void WebAutomationSession::addSingleCookie(ErrorString& errorString, const String& browsingContextHandle, const Inspector::InspectorObject& cookieObject, Ref<AddSingleCookieCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
+ ASSERT(activeURL.isValid());
+
+ WebCore::Cookie cookie;
+
+ if (!cookieObject.getString(WTF::ASCIILiteral("name"), cookie.name))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'name' was not found.");
+
+ if (!cookieObject.getString(WTF::ASCIILiteral("value"), cookie.value))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'value' was not found.");
+
+ String domain;
+ if (!cookieObject.getString(WTF::ASCIILiteral("domain"), domain))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'domain' was not found.");
+
+ // Inherit the domain/host from the main frame's URL if it is not explicitly set.
+ if (domain.isEmpty())
+ domain = activeURL.host();
+
+ cookie.domain = domain;
+
+ if (!cookieObject.getString(WTF::ASCIILiteral("path"), cookie.path))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'path' was not found.");
+
+ double expires;
+ if (!cookieObject.getDouble(WTF::ASCIILiteral("expires"), expires))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'expires' was not found.");
+
+ cookie.expires = expires * 1000.0;
+
+ if (!cookieObject.getBoolean(WTF::ASCIILiteral("secure"), cookie.secure))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'secure' was not found.");
+
+ if (!cookieObject.getBoolean(WTF::ASCIILiteral("session"), cookie.session))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'session' was not found.");
+
+ if (!cookieObject.getBoolean(WTF::ASCIILiteral("httpOnly"), cookie.httpOnly))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'httpOnly' was not found.");
+
+ WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
+ cookieManager->addCookie(WebCore::SessionID::defaultSessionID(), cookie, activeURL.host());
+
+ callback->sendSuccess();
+}
+
+void WebAutomationSession::deleteAllCookies(ErrorString& errorString, const String& browsingContextHandle)
+{
+ WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ WebCore::URL activeURL = WebCore::URL(WebCore::URL(), page->pageLoadState().activeURL());
+ ASSERT(activeURL.isValid());
+
+ WebCookieManagerProxy* cookieManager = m_processPool->supplement<WebCookieManagerProxy>();
+ cookieManager->deleteCookiesForHostname(WebCore::SessionID::defaultSessionID(), activeURL.host());
+}
+
+#if USE(APPKIT)
+static WebEvent::Modifiers protocolModifierToWebEventModifier(Inspector::Protocol::Automation::KeyModifier modifier)
+{
+ switch (modifier) {
+ case Inspector::Protocol::Automation::KeyModifier::Alt:
+ return WebEvent::AltKey;
+ case Inspector::Protocol::Automation::KeyModifier::Meta:
+ return WebEvent::MetaKey;
+ case Inspector::Protocol::Automation::KeyModifier::Control:
+ return WebEvent::ControlKey;
+ case Inspector::Protocol::Automation::KeyModifier::Shift:
+ return WebEvent::ShiftKey;
+ case Inspector::Protocol::Automation::KeyModifier::CapsLock:
+ return WebEvent::CapsLockKey;
+ }
+}
+#endif // USE(APPKIT)
+
+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)
+{
+#if !USE(APPKIT)
+ FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
+#else
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ float x;
+ if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("x"), x))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'x' was not found.");
+
+ float y;
+ if (!requestedPositionObject.getDouble(WTF::ASCIILiteral("y"), y))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The parameter 'y' was not found.");
+
+ WebCore::FloatRect windowFrame;
+ page->getWindowFrame(windowFrame);
+
+ x = std::min(std::max(0.0f, x), windowFrame.size().width());
+ y = std::min(std::max(0.0f, y + page->topContentInset()), windowFrame.size().height());
+
+ WebCore::IntPoint viewPosition = WebCore::IntPoint(static_cast<int>(x), static_cast<int>(y));
+
+ auto parsedInteraction = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseInteraction>(mouseInteractionString);
+ if (!parsedInteraction)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interaction' is invalid.");
+
+ auto parsedButton = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::MouseButton>(mouseButtonString);
+ if (!parsedButton)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'button' is invalid.");
+
+ WebEvent::Modifiers keyModifiers = (WebEvent::Modifiers)0;
+ for (auto it = keyModifierStrings.begin(); it != keyModifierStrings.end(); ++it) {
+ String modifierString;
+ if (!it->get()->asString(modifierString))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'modifiers' is invalid.");
+
+ auto parsedModifier = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyModifier>(modifierString);
+ if (!parsedModifier)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "A modifier in the 'modifiers' array is invalid.");
+ WebEvent::Modifiers enumValue = protocolModifierToWebEventModifier(parsedModifier.value());
+ keyModifiers = (WebEvent::Modifiers)(enumValue | keyModifiers);
+ }
+
+ platformSimulateMouseInteraction(*page, viewPosition, parsedInteraction.value(), parsedButton.value(), keyModifiers);
+
+ updatedPositionObject = Inspector::Protocol::Automation::Point::create()
+ .setX(x)
+ .setY(y - page->topContentInset())
+ .release();
+#endif // USE(APPKIT)
+}
+
+void WebAutomationSession::performKeyboardInteractions(ErrorString& errorString, const String& handle, const Inspector::InspectorArray& interactions, Ref<PerformKeyboardInteractionsCallback>&& callback)
+{
+#if !USE(APPKIT)
+ FAIL_WITH_PREDEFINED_ERROR(NotImplemented);
+#else
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ if (!interactions.length())
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The parameter 'interactions' was not found or empty.");
+
+ // Validate all of the parameters before performing any interactions with the browsing context under test.
+ Vector<std::function<void()>> actionsToPerform;
+ actionsToPerform.reserveCapacity(interactions.length());
+
+ for (auto interaction : interactions) {
+ RefPtr<InspectorObject> interactionObject;
+ if (!interaction->asObject(interactionObject))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter was invalid.");
+
+ String interactionTypeString;
+ if (!interactionObject->getString(ASCIILiteral("type"), interactionTypeString))
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter is missing the 'type' key.");
+ auto interactionType = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::KeyboardInteractionType>(interactionTypeString);
+ if (!interactionType)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'type' key.");
+
+ String virtualKeyString;
+ bool foundVirtualKey = interactionObject->getString(ASCIILiteral("key"), virtualKeyString);
+ if (foundVirtualKey) {
+ auto virtualKey = Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::VirtualKey>(virtualKeyString);
+ if (!virtualKey)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
+
+ actionsToPerform.uncheckedAppend([this, page, interactionType, virtualKey] {
+ platformSimulateKeyStroke(*page, interactionType.value(), virtualKey.value());
+ });
+ }
+
+ String keySequence;
+ bool foundKeySequence = interactionObject->getString(ASCIILiteral("text"), keySequence);
+ if (foundKeySequence) {
+ switch (interactionType.value()) {
+ case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress:
+ case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease:
+ // 'KeyPress' and 'KeyRelease' are meant for a virtual key and are not supported for a string (sequence of codepoints).
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "An interaction in the 'interactions' parameter has an invalid 'key' value.");
+
+ case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey:
+ actionsToPerform.uncheckedAppend([this, page, keySequence] {
+ platformSimulateKeySequence(*page, keySequence);
+ });
+ break;
+ }
+ }
+
+ if (!foundVirtualKey && !foundKeySequence)
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "An interaction in the 'interactions' parameter is missing both 'key' and 'text'. One must be provided.");
+ }
+
+ ASSERT(actionsToPerform.size());
+ if (!actionsToPerform.size())
+ FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "No actions to perform.");
+
+ auto& callbackInMap = m_pendingKeyboardEventsFlushedCallbacksPerPage.add(page->pageID(), nullptr).iterator->value;
+ if (callbackInMap)
+ callbackInMap->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
+ callbackInMap = WTFMove(callback);
+
+ for (auto& action : actionsToPerform)
+ action();
+#endif // USE(APPKIT)
+}
+
+void WebAutomationSession::takeScreenshot(ErrorString& errorString, const String& handle, Ref<TakeScreenshotCallback>&& callback)
+{
+ WebPageProxy* page = webPageProxyForHandle(handle);
+ if (!page)
+ FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+ uint64_t callbackID = m_nextScreenshotCallbackID++;
+ m_screenshotCallbacks.set(callbackID, WTFMove(callback));
+
+ page->process().send(Messages::WebAutomationSessionProxy::TakeScreenshot(page->pageID(), callbackID), 0);
+}
+
+void WebAutomationSession::didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle& imageDataHandle, const String& errorType)
+{
+ auto callback = m_screenshotCallbacks.take(callbackID);
+ if (!callback)
+ return;
+
+ if (!errorType.isEmpty()) {
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
+ return;
+ }
+
+ std::optional<String> base64EncodedData = platformGetBase64EncodedPNGData(imageDataHandle);
+ if (!base64EncodedData) {
+ callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(InternalError));
+ return;
+ }
+
+ callback->sendSuccess(base64EncodedData.value());
+}
+
+// Platform-dependent Implementation Stubs.
+
+#if !PLATFORM(MAC)
+void WebAutomationSession::platformSimulateMouseInteraction(WebKit::WebPageProxy&, const WebCore::IntPoint&, Inspector::Protocol::Automation::MouseInteraction, Inspector::Protocol::Automation::MouseButton, WebEvent::Modifiers)
+{
+}
+
+void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy&, Inspector::Protocol::Automation::KeyboardInteractionType, Inspector::Protocol::Automation::VirtualKey)
+{
+}
+
+void WebAutomationSession::platformSimulateKeySequence(WebPageProxy&, const String&)
+{
+}
+#endif // !PLATFORM(MAC)
+
+#if !PLATFORM(COCOA)
+std::optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&)
+{
+ return String();
+}
+#endif // !PLATFORM(COCOA)
+
+} // namespace WebKit
diff --git a/Source/WebKit2/UIProcess/Automation/WebAutomationSession.h b/Source/WebKit2/UIProcess/Automation/WebAutomationSession.h
new file mode 100644
index 000000000..cd2d0b8af
--- /dev/null
+++ b/Source/WebKit2/UIProcess/Automation/WebAutomationSession.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2016, 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "APIObject.h"
+#include "AutomationBackendDispatchers.h"
+#include "Connection.h"
+#include "ShareableBitmap.h"
+#include "WebEvent.h"
+#include <wtf/Forward.h>
+
+#if ENABLE(REMOTE_INSPECTOR)
+#include <JavaScriptCore/RemoteAutomationTarget.h>
+#endif
+
+namespace API {
+class AutomationSessionClient;
+}
+
+namespace Inspector {
+class BackendDispatcher;
+class FrontendRouter;
+}
+
+namespace WebCore {
+class IntPoint;
+class IntRect;
+
+struct Cookie;
+}
+
+#if USE(APPKIT)
+OBJC_CLASS NSArray;
+OBJC_CLASS NSEvent;
+#endif
+
+namespace WebKit {
+
+class WebAutomationSessionClient;
+class WebFrameProxy;
+class WebPageProxy;
+class WebProcessPool;
+
+class WebAutomationSession final : public API::ObjectImpl<API::Object::Type::AutomationSession>, public IPC::MessageReceiver
+#if ENABLE(REMOTE_INSPECTOR)
+ , public Inspector::RemoteAutomationTarget
+#endif
+ , public Inspector::AutomationBackendDispatcherHandler
+{
+public:
+ WebAutomationSession();
+ ~WebAutomationSession();
+
+ void setClient(std::unique_ptr<API::AutomationSessionClient>);
+
+ void setSessionIdentifier(const String& sessionIdentifier) { m_sessionIdentifier = sessionIdentifier; }
+ String sessionIdentifier() const { return m_sessionIdentifier; }
+
+ WebProcessPool* processPool() const { return m_processPool; }
+ void setProcessPool(WebProcessPool*);
+
+ void navigationOccurredForPage(const WebPageProxy&);
+ void inspectorFrontendLoaded(const WebPageProxy&);
+ void keyboardEventsFlushedForPage(const WebPageProxy&);
+
+#if ENABLE(REMOTE_INSPECTOR)
+ // Inspector::RemoteAutomationTarget API
+ String name() const override { return m_sessionIdentifier; }
+ void dispatchMessageFromRemote(const String& message) override;
+ void connect(Inspector::FrontendChannel*, bool isAutomaticConnection = false) override;
+ void disconnect(Inspector::FrontendChannel*) override;
+#endif
+ void terminate();
+
+ // Inspector::AutomationBackendDispatcherHandler API
+ // NOTE: the set of declarations included in this interface depend on the "platform" property in Automation.json
+ // and the --platform argument passed to the protocol bindings generator.
+
+ // Platform: Generic
+ void getBrowsingContexts(Inspector::ErrorString&, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Automation::BrowsingContext>>&) override;
+ void getBrowsingContext(Inspector::ErrorString&, const String&, RefPtr<Inspector::Protocol::Automation::BrowsingContext>&) override;
+ void createBrowsingContext(Inspector::ErrorString&, String*) override;
+ void closeBrowsingContext(Inspector::ErrorString&, const String&) override;
+ void switchToBrowsingContext(Inspector::ErrorString&, const String& browsingContextHandle, const String* optionalFrameHandle) override;
+ void navigateBrowsingContext(Inspector::ErrorString&, const String& handle, const String& url, Ref<NavigateBrowsingContextCallback>&&) override;
+ void goBackInBrowsingContext(Inspector::ErrorString&, const String&, Ref<GoBackInBrowsingContextCallback>&&) override;
+ void goForwardInBrowsingContext(Inspector::ErrorString&, const String&, Ref<GoForwardInBrowsingContextCallback>&&) override;
+ void reloadBrowsingContext(Inspector::ErrorString&, const String&, Ref<ReloadBrowsingContextCallback>&&) override;
+ void evaluateJavaScriptFunction(Inspector::ErrorString&, const String& browsingContextHandle, const String* optionalFrameHandle, const String& function, const Inspector::InspectorArray& arguments, const bool* optionalExpectsImplicitCallbackArgument, const int* optionalCallbackTimeout, Ref<Inspector::AutomationBackendDispatcherHandler::EvaluateJavaScriptFunctionCallback>&&) override;
+ void performMouseInteraction(Inspector::ErrorString&, const String& handle, const Inspector::InspectorObject& requestedPosition, const String& mouseButton, const String& mouseInteraction, const Inspector::InspectorArray& keyModifiers, RefPtr<Inspector::Protocol::Automation::Point>& updatedPosition) override;
+ void performKeyboardInteractions(Inspector::ErrorString&, const String& handle, const Inspector::InspectorArray& interactions, Ref<PerformKeyboardInteractionsCallback>&&) override;
+ void takeScreenshot(Inspector::ErrorString&, const String& handle, Ref<Inspector::AutomationBackendDispatcherHandler::TakeScreenshotCallback>&&) override;
+ void resolveChildFrameHandle(Inspector::ErrorString&, const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&&) override;
+ void resolveParentFrameHandle(Inspector::ErrorString&, const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&&) override;
+ void computeElementLayout(Inspector::ErrorString&, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* useViewportCoordinates, Ref<Inspector::AutomationBackendDispatcherHandler::ComputeElementLayoutCallback>&&) override;
+ void isShowingJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle, bool* result) override;
+ void dismissCurrentJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle) override;
+ void acceptCurrentJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle) override;
+ void messageOfCurrentJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle, String* text) override;
+ void setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString&, const String& browsingContextHandle, const String& text) override;
+ void getAllCookies(Inspector::ErrorString&, const String& browsingContextHandle, Ref<GetAllCookiesCallback>&&) override;
+ void deleteSingleCookie(Inspector::ErrorString&, const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&&) override;
+ void addSingleCookie(Inspector::ErrorString&, const String& browsingContextHandle, const Inspector::InspectorObject& cookie, Ref<AddSingleCookieCallback>&&) override;
+ void deleteAllCookies(Inspector::ErrorString&, const String& browsingContextHandle) override;
+
+ // Platform: macOS
+#if PLATFORM(MAC)
+ void resizeWindowOfBrowsingContext(Inspector::ErrorString&, const String& handle, const Inspector::InspectorObject& size) override;
+ void moveWindowOfBrowsingContext(Inspector::ErrorString&, const String& handle, const Inspector::InspectorObject& position) override;
+ void inspectBrowsingContext(Inspector::ErrorString&, const String&, const bool* optionalEnableAutoCapturing, Ref<InspectBrowsingContextCallback>&&) override;
+#endif
+
+ // Event Simulation Support.
+#if PLATFORM(MAC)
+ bool wasEventSynthesizedForAutomation(NSEvent *);
+ void markEventAsSynthesizedForAutomation(NSEvent *);
+#endif
+
+private:
+ WebPageProxy* webPageProxyForHandle(const String&);
+ String handleForWebPageProxy(const WebPageProxy&);
+ RefPtr<Inspector::Protocol::Automation::BrowsingContext> buildBrowsingContextForPage(WebPageProxy&);
+
+ std::optional<uint64_t> webFrameIDForHandle(const String&);
+ String handleForWebFrameID(uint64_t frameID);
+ String handleForWebFrameProxy(const WebFrameProxy&);
+
+ // Implemented in generated WebAutomationSessionMessageReceiver.cpp.
+ void didReceiveMessage(IPC::Connection&, IPC::Decoder&) override;
+
+ // Called by WebAutomationSession messages.
+ void didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType);
+ void didResolveChildFrame(uint64_t callbackID, uint64_t frameID, const String& errorType);
+ void didResolveParentFrame(uint64_t callbackID, uint64_t frameID, const String& errorType);
+ void didComputeElementLayout(uint64_t callbackID, WebCore::IntRect, const String& errorType);
+ void didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle&, const String& errorType);
+ void didGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie>, const String& errorType);
+ void didDeleteCookie(uint64_t callbackID, const String& errorType);
+
+ // Platform-dependent implementations.
+ void platformSimulateMouseInteraction(WebPageProxy&, const WebCore::IntPoint& viewPosition, Inspector::Protocol::Automation::MouseInteraction, Inspector::Protocol::Automation::MouseButton, WebEvent::Modifiers);
+ // Simulates a single virtual key being pressed, such as Control, F-keys, Numpad keys, etc. as allowed by the protocol.
+ void platformSimulateKeyStroke(WebPageProxy&, Inspector::Protocol::Automation::KeyboardInteractionType, Inspector::Protocol::Automation::VirtualKey);
+ // Simulates key presses to produce the codepoints in a string. One or more code points are delivered atomically at grapheme cluster boundaries.
+ void platformSimulateKeySequence(WebPageProxy&, const String&);
+ // Get base64 encoded PNG data from a bitmap.
+ std::optional<String> platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&);
+
+#if PLATFORM(MAC)
+ void sendSynthesizedEventsToPage(WebPageProxy&, NSArray *eventsToSend);
+#endif
+
+ WebProcessPool* m_processPool { nullptr };
+
+ std::unique_ptr<API::AutomationSessionClient> m_client;
+ String m_sessionIdentifier { ASCIILiteral("Untitled Session") };
+ Ref<Inspector::FrontendRouter> m_frontendRouter;
+ Ref<Inspector::BackendDispatcher> m_backendDispatcher;
+ Ref<Inspector::AutomationBackendDispatcher> m_domainDispatcher;
+
+ HashMap<uint64_t, String> m_webPageHandleMap;
+ HashMap<String, uint64_t> m_handleWebPageMap;
+ String m_activeBrowsingContextHandle;
+
+ HashMap<uint64_t, String> m_webFrameHandleMap;
+ HashMap<String, uint64_t> m_handleWebFrameMap;
+
+ HashMap<uint64_t, RefPtr<Inspector::BackendDispatcher::CallbackBase>> m_pendingNavigationInBrowsingContextCallbacksPerPage;
+ HashMap<uint64_t, RefPtr<Inspector::BackendDispatcher::CallbackBase>> m_pendingInspectorCallbacksPerPage;
+ HashMap<uint64_t, RefPtr<Inspector::BackendDispatcher::CallbackBase>> m_pendingKeyboardEventsFlushedCallbacksPerPage;
+
+ uint64_t m_nextEvaluateJavaScriptCallbackID { 1 };
+ HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::EvaluateJavaScriptFunctionCallback>> m_evaluateJavaScriptFunctionCallbacks;
+
+ uint64_t m_nextResolveFrameCallbackID { 1 };
+ HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::ResolveChildFrameHandleCallback>> m_resolveChildFrameHandleCallbacks;
+
+ uint64_t m_nextResolveParentFrameCallbackID { 1 };
+ HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::ResolveParentFrameHandleCallback>> m_resolveParentFrameHandleCallbacks;
+
+ uint64_t m_nextComputeElementLayoutCallbackID { 1 };
+ HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::ComputeElementLayoutCallback>> m_computeElementLayoutCallbacks;
+
+ uint64_t m_nextScreenshotCallbackID { 1 };
+ HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::TakeScreenshotCallback>> m_screenshotCallbacks;
+
+ uint64_t m_nextGetCookiesCallbackID { 1 };
+ HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::GetAllCookiesCallback>> m_getCookieCallbacks;
+
+ uint64_t m_nextDeleteCookieCallbackID { 1 };
+ HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::DeleteSingleCookieCallback>> m_deleteCookieCallbacks;
+
+#if ENABLE(REMOTE_INSPECTOR)
+ Inspector::FrontendChannel* m_remoteChannel { nullptr };
+#endif
+};
+
+} // namespace WebKit
diff --git a/Source/WebKit2/UIProcess/Automation/WebAutomationSession.messages.in b/Source/WebKit2/UIProcess/Automation/WebAutomationSession.messages.in
new file mode 100644
index 000000000..cb4d56288
--- /dev/null
+++ b/Source/WebKit2/UIProcess/Automation/WebAutomationSession.messages.in
@@ -0,0 +1,35 @@
+# Copyright (C) 2016 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+messages -> WebAutomationSession {
+ DidEvaluateJavaScriptFunction(uint64_t callbackID, String result, String errorType)
+
+ DidResolveChildFrame(uint64_t callbackID, uint64_t frameID, String errorType)
+ DidResolveParentFrame(uint64_t callbackID, uint64_t frameID, String errorType)
+
+ DidComputeElementLayout(uint64_t callbackID, WebCore::IntRect rect, String errorType)
+
+ DidTakeScreenshot(uint64_t callbackID, WebKit::ShareableBitmap::Handle imageDataHandle, String errorType)
+
+ DidGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie> cookies, String errorType)
+ DidDeleteCookie(uint64_t callbackID, String errorType)
+}
diff --git a/Source/WebKit2/UIProcess/Automation/WebAutomationSessionMacros.h b/Source/WebKit2/UIProcess/Automation/WebAutomationSessionMacros.h
new file mode 100644
index 000000000..d6398769c
--- /dev/null
+++ b/Source/WebKit2/UIProcess/Automation/WebAutomationSessionMacros.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define errorNameAndDetailsSeparator ";"
+
+// Make sure the predefined error name is valid, otherwise use InternalError.
+#define VALIDATED_ERROR_MESSAGE(errorString) Inspector::Protocol::AutomationHelpers::parseEnumValueFromString<Inspector::Protocol::Automation::ErrorMessage>(errorString).value_or(Inspector::Protocol::Automation::ErrorMessage::InternalError)
+
+// If the error name is incorrect for these macros, it will be a compile-time error.
+#define STRING_FOR_PREDEFINED_ERROR_NAME(errorName) Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::errorName)
+#define STRING_FOR_PREDEFINED_ERROR_NAME_AND_DETAILS(errorName, detailsString) makeString(Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::errorName), errorNameAndDetailsSeparator, detailsString)
+
+// If the error message is not a predefined error, InternalError will be used instead.
+#define STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorMessage) Inspector::Protocol::AutomationHelpers::getEnumConstantValue(VALIDATED_ERROR_MESSAGE(errorMessage))
+#define STRING_FOR_PREDEFINED_ERROR_MESSAGE_AND_DETAILS(errorMessage, detailsString) makeString(Inspector::Protocol::AutomationHelpers::getEnumConstantValue(VALIDATED_ERROR_MESSAGE(errorMessage)), errorNameAndDetailsSeparator, detailsString)
+
+// Convenience macros for filling in the error string of synchronous commands in bailout branches.
+#define FAIL_WITH_PREDEFINED_ERROR(errorName) \
+do { \
+ errorString = STRING_FOR_PREDEFINED_ERROR_NAME(errorName); \
+ return; \
+} while (false)
+
+#define FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(errorName, detailsString) \
+do { \
+ errorString = STRING_FOR_PREDEFINED_ERROR_NAME_AND_DETAILS(errorName, detailsString); \
+ return; \
+} while (false)