diff --git a/Userland/Libraries/LibWeb/WebDriver/ElementReference.cpp b/Userland/Libraries/LibWeb/WebDriver/ElementReference.cpp index 22e68998366..cc7bd5a67e7 100644 --- a/Userland/Libraries/LibWeb/WebDriver/ElementReference.cpp +++ b/Userland/Libraries/LibWeb/WebDriver/ElementReference.cpp @@ -77,6 +77,18 @@ bool represents_a_web_element(JsonValue const& value) return value.as_object().has_string(web_element_identifier); } +// https://w3c.github.io/webdriver/#dfn-get-a-webelement-origin +ErrorOr, Web::WebDriver::Error> get_web_element_origin(StringView origin) +{ + // 1. Assert: browsing context is the current browsing context. + + // 2. Let element be equal to the result of trying to get a known element with session and origin. + auto* element = TRY(get_known_connected_element(origin)); + + // 3. Return success with data element. + return *element; +} + // https://w3c.github.io/webdriver/#dfn-get-a-known-element ErrorOr get_known_connected_element(StringView element_id) { diff --git a/Userland/Libraries/LibWeb/WebDriver/ElementReference.h b/Userland/Libraries/LibWeb/WebDriver/ElementReference.h index e7744319dbe..7d9a215bc38 100644 --- a/Userland/Libraries/LibWeb/WebDriver/ElementReference.h +++ b/Userland/Libraries/LibWeb/WebDriver/ElementReference.h @@ -21,6 +21,7 @@ JsonObject web_element_reference_object(Web::DOM::Node const& element); ErrorOr, WebDriver::Error> deserialize_web_element(JsonObject const&); ByteString extract_web_element_reference(JsonObject const&); bool represents_a_web_element(JsonValue const& value); +ErrorOr, Web::WebDriver::Error> get_web_element_origin(StringView origin); ErrorOr get_known_connected_element(StringView element_id); bool is_element_stale(Web::DOM::Node const& element); diff --git a/Userland/Services/WebContent/WebDriverConnection.cpp b/Userland/Services/WebContent/WebDriverConnection.cpp index 1af05e21202..4900250c1d7 100644 --- a/Userland/Services/WebContent/WebDriverConnection.cpp +++ b/Userland/Services/WebContent/WebDriverConnection.cpp @@ -44,8 +44,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -1887,17 +1889,38 @@ Messages::WebDriverClient::DeleteAllCookiesResponse WebDriverConnection::delete_ // 15.7 Perform Actions, https://w3c.github.io/webdriver/#perform-actions Messages::WebDriverClient::PerformActionsResponse WebDriverConnection::perform_actions(JsonValue const& payload) { - dbgln("FIXME: WebDriverConnection::perform_actions({})", payload); + // 4. If session's current browsing context is no longer open, return error with error code no such window. + // NOTE: We do this first so we can assume the current top-level browsing context below is non-null. + TRY(ensure_current_browsing_context_is_open()); - // FIXME: 1. Let input state be the result of get the input state with session and session's current top-level browsing context. - // FIXME: 2. Let actions options be a new actions options with the is element origin steps set to represents a web element, and the get element origin steps set to get a WebElement origin. - // FIXME: 3. Let actions by tick be the result of trying to extract an action sequence with input state, parameters, and actions options. - // FIXME: 4. If session's current browsing context is no longer open, return error with error code no such window. - // FIXME: 5. Try to handle any user prompts with session. - // FIXME: 6. Dispatch actions with input state, actions by tick, current browsing context, and actions options. If this results in an error return that error. - // FIXME: 7. Return success with data null. + // 1. Let input state be the result of get the input state with session and session's current top-level browsing context. + auto& input_state = Web::WebDriver::get_input_state(*current_top_level_browsing_context()); - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "perform actions not implemented"sv); + // 2. Let actions options be a new actions options with the is element origin steps set to represents a web element, + // and the get element origin steps set to get a WebElement origin. + Web::WebDriver::ActionsOptions actions_options { + .is_element_origin = &Web::WebDriver::represents_a_web_element, + .get_element_origin = &Web::WebDriver::get_web_element_origin, + }; + + // 3. Let actions by tick be the result of trying to extract an action sequence with input state, parameters, and + // actions options. + auto actions_by_tick = TRY(Web::WebDriver::extract_an_action_sequence(input_state, payload, actions_options)); + + // 5. Try to handle any user prompts with session. + TRY(handle_any_user_prompts()); + + // 6. Dispatch actions with input state, actions by tick, current browsing context, and actions options. If this + // results in an error return that error. + auto on_complete = JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) { + m_action_executor = nullptr; + async_actions_performed(move(result)); + }); + + m_action_executor = Web::WebDriver::dispatch_actions(input_state, move(actions_by_tick), current_browsing_context(), move(actions_options), on_complete); + + // 7. Return success with data null. + return JsonValue {}; } // 15.8 Release Actions, https://w3c.github.io/webdriver/#release-actions diff --git a/Userland/Services/WebContent/WebDriverConnection.h b/Userland/Services/WebContent/WebDriverConnection.h index 25e3e5f550b..910c80b49fc 100644 --- a/Userland/Services/WebContent/WebDriverConnection.h +++ b/Userland/Services/WebContent/WebDriverConnection.h @@ -1,7 +1,7 @@ /* * Copyright (c) 2022, Florent Castelli * Copyright (c) 2022, Linus Groh - * Copyright (c) 2022-2024, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -145,6 +145,8 @@ private: // https://w3c.github.io/webdriver/#dfn-current-browsing-context JS::Handle m_current_browsing_context; + + JS::Handle m_action_executor; }; } diff --git a/Userland/Services/WebContent/WebDriverServer.ipc b/Userland/Services/WebContent/WebDriverServer.ipc index 9dd79b81468..753461970b4 100644 --- a/Userland/Services/WebContent/WebDriverServer.ipc +++ b/Userland/Services/WebContent/WebDriverServer.ipc @@ -2,4 +2,5 @@ endpoint WebDriverServer { script_executed(Web::WebDriver::Response response) =| + actions_performed(Web::WebDriver::Response response) =| } diff --git a/Userland/Services/WebDriver/Client.cpp b/Userland/Services/WebDriver/Client.cpp index 9613432387d..248eaac14fd 100644 --- a/Userland/Services/WebDriver/Client.cpp +++ b/Userland/Services/WebDriver/Client.cpp @@ -3,7 +3,7 @@ * Copyright (c) 2022, Sam Atkins * Copyright (c) 2022, Tobias Christiansen * Copyright (c) 2022, Linus Groh - * Copyright (c) 2022-2023, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -687,7 +687,7 @@ Web::WebDriver::Response Client::perform_actions(Web::WebDriver::Parameters para { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//actions"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().perform_actions(move(payload)); + return session->perform_actions(move(payload)); } // 15.8 Release Actions, https://w3c.github.io/webdriver/#release-actions diff --git a/Userland/Services/WebDriver/Session.cpp b/Userland/Services/WebDriver/Session.cpp index 3612d097d67..daf94d9ee4d 100644 --- a/Userland/Services/WebDriver/Session.cpp +++ b/Userland/Services/WebDriver/Session.cpp @@ -3,7 +3,7 @@ * Copyright (c) 2022, Sam Atkins * Copyright (c) 2022, Tobias Christiansen * Copyright (c) 2022, Linus Groh - * Copyright (c) 2022, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -201,4 +201,22 @@ Web::WebDriver::Response Session::execute_script(JsonValue payload, ScriptMode m return response.release_value(); } +Web::WebDriver::Response Session::perform_actions(JsonValue payload) const +{ + ScopeGuard guard { [&]() { web_content_connection().on_actions_performed = nullptr; } }; + + Optional response; + web_content_connection().on_actions_performed = [&](auto result) { + response = move(result); + }; + + TRY(web_content_connection().perform_actions(move(payload))); + + Core::EventLoop::current().spin_until([&]() { + return response.has_value(); + }); + + return response.release_value(); +} + } diff --git a/Userland/Services/WebDriver/Session.h b/Userland/Services/WebDriver/Session.h index 173dca2e6fe..846d8f3d387 100644 --- a/Userland/Services/WebDriver/Session.h +++ b/Userland/Services/WebDriver/Session.h @@ -1,7 +1,7 @@ /* * Copyright (c) 2022, Florent Castelli * Copyright (c) 2022, Linus Groh - * Copyright (c) 2022, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -61,6 +61,8 @@ public: }; Web::WebDriver::Response execute_script(JsonValue, ScriptMode) const; + Web::WebDriver::Response perform_actions(JsonValue) const; + private: using ServerPromise = Core::Promise>; ErrorOr> create_server(NonnullRefPtr promise); diff --git a/Userland/Services/WebDriver/WebContentConnection.cpp b/Userland/Services/WebDriver/WebContentConnection.cpp index d756536892e..2d84edcc2d0 100644 --- a/Userland/Services/WebDriver/WebContentConnection.cpp +++ b/Userland/Services/WebDriver/WebContentConnection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -26,4 +26,10 @@ void WebContentConnection::script_executed(Web::WebDriver::Response const& respo on_script_executed(response); } +void WebContentConnection::actions_performed(Web::WebDriver::Response const& response) +{ + if (on_actions_performed) + on_actions_performed(response); +} + } diff --git a/Userland/Services/WebDriver/WebContentConnection.h b/Userland/Services/WebDriver/WebContentConnection.h index 9613e346847..01b4b201fb0 100644 --- a/Userland/Services/WebDriver/WebContentConnection.h +++ b/Userland/Services/WebDriver/WebContentConnection.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -22,11 +22,13 @@ public: Function on_close; Function on_script_executed; + Function on_actions_performed; private: virtual void die() override; virtual void script_executed(Web::WebDriver::Response const&) override; + virtual void actions_performed(Web::WebDriver::Response const&) override; }; }