From f88a0c51a329435f6a706c65a25c9cd959e26168 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Wed, 2 Nov 2022 18:11:04 +0000 Subject: WebDriver: Implement `POST /session/{session id}/execute/sync` endpoint --- Userland/Services/WebDriver/Client.cpp | 11 +++++ Userland/Services/WebDriver/Client.h | 1 + Userland/Services/WebDriver/Session.cpp | 74 +++++++++++++++++++++++++++++++++ Userland/Services/WebDriver/Session.h | 1 + 4 files changed, 87 insertions(+) (limited to 'Userland/Services') diff --git a/Userland/Services/WebDriver/Client.cpp b/Userland/Services/WebDriver/Client.cpp index d6f477b3be..ea20088864 100644 --- a/Userland/Services/WebDriver/Client.cpp +++ b/Userland/Services/WebDriver/Client.cpp @@ -49,6 +49,7 @@ Vector Client::s_routes = { { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "css", ":property_name" }, &Client::handle_get_element_css_value }, { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "text" }, &Client::handle_get_element_text }, { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "name" }, &Client::handle_get_element_tag_name }, + { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "execute", "sync" }, &Client::handle_execute_script }, { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "cookie" }, &Client::handle_get_all_cookies }, { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "cookie", ":name" }, &Client::handle_get_named_cookie }, { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "cookie" }, &Client::handle_add_cookie }, @@ -685,6 +686,16 @@ ErrorOr Client::handle_get_element_tag_name(Vector Client::handle_execute_script(Vector const& parameters, JsonValue const& payload) +{ + dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//execute/sync"); + auto* session = TRY(find_session_with_id(parameters[0])); + auto result = TRY(session->execute_script(payload)); + return make_json_value(result); +} + // 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies // GET /session/{session id}/cookie ErrorOr Client::handle_get_all_cookies(Vector const& parameters, JsonValue const&) diff --git a/Userland/Services/WebDriver/Client.h b/Userland/Services/WebDriver/Client.h index 195f59323a..33239087e1 100644 --- a/Userland/Services/WebDriver/Client.h +++ b/Userland/Services/WebDriver/Client.h @@ -74,6 +74,7 @@ private: ErrorOr handle_get_element_css_value(Vector const&, JsonValue const& payload); ErrorOr handle_get_element_text(Vector const&, JsonValue const& payload); ErrorOr handle_get_element_tag_name(Vector const&, JsonValue const& payload); + ErrorOr handle_execute_script(Vector const&, JsonValue const& payload); ErrorOr handle_get_all_cookies(Vector const&, JsonValue const& payload); ErrorOr handle_get_named_cookie(Vector const&, JsonValue const& payload); ErrorOr handle_add_cookie(Vector const&, JsonValue const& payload); diff --git a/Userland/Services/WebDriver/Session.cpp b/Userland/Services/WebDriver/Session.cpp index 197a61f77b..53ab8bdbd2 100644 --- a/Userland/Services/WebDriver/Session.cpp +++ b/Userland/Services/WebDriver/Session.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace WebDriver { @@ -899,6 +900,79 @@ ErrorOr Session::get_element_tag_name(JsonValue const return JsonValue(qualified_name); } +struct ScriptArguments { + String script; + JsonArray const& arguments; +}; + +// https://w3c.github.io/webdriver/#dfn-extract-the-script-arguments-from-a-request +static ErrorOr extract_the_script_arguments_from_a_request(JsonValue const& payload) +{ + if (!payload.is_object()) + return WebDriverError::from_code(ErrorCode::InvalidArgument, "Payload is not a JSON object"); + + auto const& properties = payload.as_object(); + + // 1. Let script be the result of getting a property named script from the parameters. + // 2. If script is not a String, return error with error code invalid argument. + if (!properties.has_string("script"sv)) + return WebDriverError::from_code(ErrorCode::InvalidArgument, "Payload doesn't have a 'script' string property"); + auto script = properties.get("script"sv).as_string(); + + // 3. Let args be the result of getting a property named args from the parameters. + // 4. If args is not an Array return error with error code invalid argument. + if (!properties.has_array("args"sv)) + return WebDriverError::from_code(ErrorCode::InvalidArgument, "Payload doesn't have an 'args' string property"); + auto const& args = properties.get("args"sv).as_array(); + + // 5. Let arguments be the result of calling the JSON deserialize algorithm with arguments args. + // NOTE: We forward the JSON array to the Browser and then WebContent process over IPC, so this is not necessary. + + // 6. Return success with data script and arguments. + return ScriptArguments { script, args }; +} + +// 13.2.1 Execute Script, https://w3c.github.io/webdriver/#dfn-execute-script +ErrorOr Session::execute_script(JsonValue const& payload) +{ + // 1. Let body and arguments be the result of trying to extract the script arguments from a request with argument parameters. + auto const& [body, arguments] = TRY(extract_the_script_arguments_from_a_request(payload)); + + // 2. If the current browsing context is no longer open, return error with error code no such window. + TRY(check_for_open_top_level_browsing_context_or_return_error()); + + // FIXME: 3. Handle any user prompts, and return its value if it is an error. + + // 4., 5.1-5.3. + Vector json_arguments; + arguments.for_each([&](JsonValue const& json_value) { + // NOTE: serialized() instead of to_string() ensures proper quoting. + json_arguments.append(json_value.serialized()); + }); + + dbgln("Executing script with 'args': [{}] / 'body':\n{}", String::join(", "sv, json_arguments), body); + auto execute_script_response = m_browser_connection->execute_script(body, json_arguments, m_timeouts_configuration.script_timeout, false); + dbgln("Executing script returned: {}", execute_script_response.json_result()); + + // NOTE: This is assumed to be a valid JSON value. + auto result = MUST(JsonValue::from_string(execute_script_response.json_result())); + + switch (execute_script_response.result_type()) { + // 6. If promise is still pending and the session script timeout is reached, return error with error code script timeout. + case Web::WebDriver::ExecuteScriptResultType::Timeout: + return WebDriverError::from_code(ErrorCode::ScriptTimeoutError, "Script timed out"); + // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result. + case Web::WebDriver::ExecuteScriptResultType::PromiseResolved: + return result; + // 8. Upon rejection of promise with reason r, let result be a JSON clone of r, and return error with error code javascript error and data result. + case Web::WebDriver::ExecuteScriptResultType::PromiseRejected: + case Web::WebDriver::ExecuteScriptResultType::JavaScriptError: + return WebDriverError::from_code(ErrorCode::JavascriptError, "Script returned an error", move(result)); + default: + VERIFY_NOT_REACHED(); + } +} + // https://w3c.github.io/webdriver/#dfn-serialized-cookie static JsonObject serialize_cookie(Web::Cookie::Cookie const& cookie) { diff --git a/Userland/Services/WebDriver/Session.h b/Userland/Services/WebDriver/Session.h index b0982e9ea4..9dc8943d59 100644 --- a/Userland/Services/WebDriver/Session.h +++ b/Userland/Services/WebDriver/Session.h @@ -63,6 +63,7 @@ public: ErrorOr get_element_css_value(JsonValue const& payload, StringView element_id, StringView property_name); ErrorOr get_element_text(JsonValue const& payload, StringView element_id); ErrorOr get_element_tag_name(JsonValue const& payload, StringView element_id); + ErrorOr execute_script(JsonValue const& payload); ErrorOr get_all_cookies(); ErrorOr get_named_cookie(String const& name); ErrorOr add_cookie(JsonValue const& payload); -- cgit v1.2.3