diff --git a/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp b/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp index 988b52de915..18ee357b5d5 100644 --- a/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp +++ b/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp @@ -283,7 +283,63 @@ static JS::ThrowCompletionOr execute_a_function_body(Web::Page& page, return completion; } -ExecuteScriptResultSerialized execute_script(Web::Page& page, ByteString const& body, JS::MarkedVector arguments, Optional const& timeout_ms) +class HeapTimer : public JS::Cell { + JS_CELL(HeapTimer, JS::Cell); + JS_DECLARE_ALLOCATOR(HeapTimer); + +public: + explicit HeapTimer() + : m_timer(Core::Timer::create()) + { + } + + virtual ~HeapTimer() override = default; + + void start(u64 timeout_ms, JS::NonnullGCPtr on_timeout) + { + m_on_timeout = on_timeout; + + m_timer->on_timeout = [this]() { + m_timed_out = true; + + if (m_on_timeout) { + auto error_object = JsonObject {}; + error_object.set("name", "Error"); + error_object.set("message", "Script Timeout"); + + m_on_timeout->function()({ ExecuteScriptResultType::Timeout, move(error_object) }); + m_on_timeout = nullptr; + } + }; + + m_timer->set_interval(static_cast(timeout_ms)); + m_timer->set_single_shot(true); + m_timer->start(); + } + + void stop() + { + m_on_timeout = nullptr; + m_timer->stop(); + } + + bool is_timed_out() const { return m_timed_out; } + +private: + virtual void visit_edges(JS::Cell::Visitor& visitor) override + { + Base::visit_edges(visitor); + visitor.visit(m_on_timeout); + } + + NonnullRefPtr m_timer; + JS::GCPtr m_on_timeout; + bool m_timed_out { false }; +}; + +JS_DEFINE_ALLOCATOR(HeapTimer); + +void execute_script(Web::Page& page, ByteString body, JS::MarkedVector arguments, Optional const& timeout_ms, JS::NonnullGCPtr on_complete) { auto* document = page.top_level_browsing_context().active_document(); auto* window = page.top_level_browsing_context().active_window(); @@ -291,28 +347,25 @@ ExecuteScriptResultSerialized execute_script(Web::Page& page, ByteString const& auto& vm = window->vm(); // 5. Let timer be a new timer. - auto timeout_flag = false; - auto timer = Core::Timer::create(); + auto timer = vm.heap().allocate(realm); // 6. If timeout is not null: if (timeout_ms.has_value()) { // 1. Start the timer with timer and timeout. - timer->on_timeout = [&]() { - timeout_flag = true; - }; - timer->set_interval(timeout_ms.value()); - timer->set_single_shot(true); + timer->start(timeout_ms.value(), on_complete); } // AD-HOC: An execution context is required for Promise creation hooks. - HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object() }; + HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; // 7. Let promise be a new Promise. auto promise_capability = WebIDL::create_promise(realm); JS::NonnullGCPtr promise { verify_cast(*promise_capability->promise()) }; // 8. Run the following substeps in parallel: - Platform::EventLoopPlugin::the().deferred_invoke([&realm, &page, promise_capability, promise, body = move(body), arguments = move(arguments)]() mutable { + Platform::EventLoopPlugin::the().deferred_invoke([&realm, &page, promise_capability, document, promise, body = move(body), arguments = move(arguments)]() mutable { + HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object() }; + // 1. Let scriptPromise be the result of promise-calling execute a function body, with arguments body and arguments. auto script_result = execute_a_function_body(page, body, move(arguments)); @@ -328,40 +381,40 @@ ExecuteScriptResultSerialized execute_script(Web::Page& page, ByteString const& }); // 9. Wait until promise is resolved, or timer's timeout fired flag is set, whichever occurs first. - vm.custom_data()->spin_event_loop_until([&] { - return timeout_flag || promise->state() != JS::Promise::State::Pending; + auto reaction_steps = JS::create_heap_function(vm.heap(), [&realm, promise, timer, on_complete](JS::Value) -> WebIDL::ExceptionOr { + if (timer->is_timed_out()) + return JS::js_undefined(); + timer->stop(); + + auto json_value_or_error = json_clone(realm, promise->result()); + if (json_value_or_error.is_error()) { + auto error_object = JsonObject {}; + error_object.set("name", "Error"); + error_object.set("message", "Could not clone result value"); + + on_complete->function()({ ExecuteScriptResultType::JavaScriptError, move(error_object) }); + } + + // 10. If promise is still pending and timer's timeout fired flag is set, return error with error code script timeout. + // NOTE: This is handled by the HeapTimer. + + // 11. If promise is fulfilled with value v, let result be JSON clone with session and v, and return success with data result. + else if (promise->state() == JS::Promise::State::Fulfilled) { + on_complete->function()({ ExecuteScriptResultType::PromiseResolved, json_value_or_error.release_value() }); + } + + // 12. If promise is rejected with reason r, let result be JSON clone with session and r, and return error with error code javascript error and data result. + else if (promise->state() == JS::Promise::State::Rejected) { + on_complete->function()({ ExecuteScriptResultType::PromiseRejected, json_value_or_error.release_value() }); + } + + return JS::js_undefined(); }); - // 10. If promise is still pending and timer's timeout fired flag is set, return error with error code script timeout. - if (timeout_flag && promise->state() == JS::Promise::State::Pending) { - auto error_object = JsonObject {}; - error_object.set("name", "Error"); - error_object.set("message", "Script Timeout"); - return { ExecuteScriptResultType::Timeout, move(error_object) }; - } - - auto json_value_or_error = json_clone(realm, promise->result()); - if (json_value_or_error.is_error()) { - auto error_object = JsonObject {}; - error_object.set("name", "Error"); - error_object.set("message", "Could not clone result value"); - return { ExecuteScriptResultType::JavaScriptError, move(error_object) }; - } - - // 11. If promise is fulfilled with value v, let result be JSON clone with session and v, and return success with data result. - if (promise->state() == JS::Promise::State::Fulfilled) { - return { ExecuteScriptResultType::PromiseResolved, json_value_or_error.release_value() }; - } - - // 12. If promise is rejected with reason r, let result be JSON clone with session and r, and return error with error code javascript error and data result. - if (promise->state() == JS::Promise::State::Rejected) { - return { ExecuteScriptResultType::PromiseRejected, json_value_or_error.release_value() }; - } - - VERIFY_NOT_REACHED(); + WebIDL::react_to_promise(promise_capability, reaction_steps, reaction_steps); } -ExecuteScriptResultSerialized execute_async_script(Web::Page& page, ByteString const& body, JS::MarkedVector arguments, Optional const& timeout_ms) +void execute_async_script(Web::Page& page, ByteString body, JS::MarkedVector arguments, Optional const& timeout_ms, JS::NonnullGCPtr on_complete) { auto* document = page.top_level_browsing_context().active_document(); auto* window = page.top_level_browsing_context().active_window(); @@ -369,28 +422,25 @@ ExecuteScriptResultSerialized execute_async_script(Web::Page& page, ByteString c auto& vm = window->vm(); // 5. Let timer be a new timer. - IGNORE_USE_IN_ESCAPING_LAMBDA auto timeout_flag = false; - auto timer = Core::Timer::create(); + auto timer = vm.heap().allocate(realm); // 6. If timeout is not null: if (timeout_ms.has_value()) { // 1. Start the timer with timer and timeout. - timer->on_timeout = [&]() { - timeout_flag = true; - }; - timer->set_interval(timeout_ms.value()); - timer->set_single_shot(true); + timer->start(timeout_ms.value(), on_complete); } // AD-HOC: An execution context is required for Promise creation hooks. - HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object() }; + HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; // 7. Let promise be a new Promise. auto promise_capability = WebIDL::create_promise(realm); JS::NonnullGCPtr promise { verify_cast(*promise_capability->promise()) }; // 8. Run the following substeps in parallel: - Platform::EventLoopPlugin::the().deferred_invoke([&vm, &realm, &page, &timeout_flag, promise_capability, promise, body = move(body), arguments = move(arguments)]() mutable { + Platform::EventLoopPlugin::the().deferred_invoke([&vm, &realm, &page, timer, document, promise_capability, promise, body = move(body), arguments = move(arguments)]() mutable { + HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object() }; + // 1. Let resolvingFunctions be CreateResolvingFunctions(promise). auto resolving_functions = promise->create_resolving_functions(); @@ -434,7 +484,7 @@ ExecuteScriptResultSerialized execute_async_script(Web::Page& page, ByteString c auto& script_promise = static_cast(*script_promise_or_error.value()); vm.custom_data()->spin_event_loop_until([&] { - return timeout_flag || script_promise.state() != JS::Promise::State::Pending; + return timer->is_timed_out() || script_promise.state() != JS::Promise::State::Pending; }); // 10. Upon fulfillment of scriptPromise with value v, resolve promise with value v. @@ -447,37 +497,37 @@ ExecuteScriptResultSerialized execute_async_script(Web::Page& page, ByteString c }); // 9. Wait until promise is resolved, or timer's timeout fired flag is set, whichever occurs first. - vm.custom_data()->spin_event_loop_until([&] { - return timeout_flag || promise->state() != JS::Promise::State::Pending; + auto reaction_steps = JS::create_heap_function(vm.heap(), [&realm, promise, timer, on_complete](JS::Value) -> WebIDL::ExceptionOr { + if (timer->is_timed_out()) + return JS::js_undefined(); + timer->stop(); + + auto json_value_or_error = json_clone(realm, promise->result()); + if (json_value_or_error.is_error()) { + auto error_object = JsonObject {}; + error_object.set("name", "Error"); + error_object.set("message", "Could not clone result value"); + + on_complete->function()({ ExecuteScriptResultType::JavaScriptError, move(error_object) }); + } + + // 10. If promise is still pending and timer's timeout fired flag is set, return error with error code script timeout. + // NOTE: This is handled by the HeapTimer. + + // 11. If promise is fulfilled with value v, let result be JSON clone with session and v, and return success with data result. + else if (promise->state() == JS::Promise::State::Fulfilled) { + on_complete->function()({ ExecuteScriptResultType::PromiseResolved, json_value_or_error.release_value() }); + } + + // 12. If promise is rejected with reason r, let result be JSON clone with session and r, and return error with error code javascript error and data result. + else if (promise->state() == JS::Promise::State::Rejected) { + on_complete->function()({ ExecuteScriptResultType::PromiseRejected, json_value_or_error.release_value() }); + } + + return JS::js_undefined(); }); - // 10. If promise is still pending and timer's timeout fired flag is set, return error with error code script - if (timeout_flag && promise->state() == JS::Promise::State::Pending) { - auto error_object = JsonObject {}; - error_object.set("name", "Error"); - error_object.set("message", "Script Timeout"); - return { ExecuteScriptResultType::Timeout, move(error_object) }; - } - - auto json_value_or_error = json_clone(realm, promise->result()); - if (json_value_or_error.is_error()) { - auto error_object = JsonObject {}; - error_object.set("name", "Error"); - error_object.set("message", "Could not clone result value"); - return { ExecuteScriptResultType::JavaScriptError, move(error_object) }; - } - - // 11. If promise is fulfilled with value v, let result be JSON clone with session and v, and return success with data result. - if (promise->state() == JS::Promise::State::Fulfilled) { - return { ExecuteScriptResultType::PromiseResolved, json_value_or_error.release_value() }; - } - - // 12. If promise is rejected with reason r, let result be JSON clone with session and r, and return error with error code javascript error and data result. - if (promise->state() == JS::Promise::State::Rejected) { - return { ExecuteScriptResultType::PromiseRejected, json_value_or_error.release_value() }; - } - - VERIFY_NOT_REACHED(); + WebIDL::react_to_promise(promise_capability, reaction_steps, reaction_steps); } } diff --git a/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.h b/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.h index 7bd1970325b..f14308beeb0 100644 --- a/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.h +++ b/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -32,7 +33,9 @@ struct ExecuteScriptResultSerialized { JsonValue value; }; -ExecuteScriptResultSerialized execute_script(Page& page, ByteString const& body, JS::MarkedVector arguments, Optional const& timeout_ms); -ExecuteScriptResultSerialized execute_async_script(Page& page, ByteString const& body, JS::MarkedVector arguments, Optional const& timeout_ms); +using OnScriptComplete = JS::HeapFunction; + +void execute_script(Page& page, ByteString body, JS::MarkedVector arguments, Optional const& timeout_ms, JS::NonnullGCPtr on_complete); +void execute_async_script(Page& page, ByteString body, JS::MarkedVector arguments, Optional const& timeout_ms, JS::NonnullGCPtr on_complete); } diff --git a/Userland/Services/WebContent/WebDriverConnection.cpp b/Userland/Services/WebContent/WebDriverConnection.cpp index 3c5e04b7734..89d5aa30d4f 100644 --- a/Userland/Services/WebContent/WebDriverConnection.cpp +++ b/Userland/Services/WebContent/WebDriverConnection.cpp @@ -1845,8 +1845,11 @@ Messages::WebDriverClient::GetSourceResponse WebDriverConnection::get_source() // 13.2.1 Execute Script, https://w3c.github.io/webdriver/#dfn-execute-script Messages::WebDriverClient::ExecuteScriptResponse WebDriverConnection::execute_script(JsonValue const& payload) { + auto* window = m_page_client->page().top_level_browsing_context().active_window(); + auto& vm = window->vm(); + // 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)); + auto [body, arguments] = TRY(extract_the_script_arguments_from_a_request(vm, payload)); // 2. If the current browsing context is no longer open, return error with error code no such window. TRY(ensure_open_top_level_browsing_context()); @@ -1858,32 +1861,43 @@ Messages::WebDriverClient::ExecuteScriptResponse WebDriverConnection::execute_sc auto timeout_ms = m_timeouts_configuration.script_timeout; // This handles steps 5 to 9 and produces the appropriate result type for the following steps. - auto result = Web::WebDriver::execute_script(m_page_client->page(), body, move(arguments), timeout_ms); - dbgln_if(WEBDRIVER_DEBUG, "Executing script returned: {}", result.value); + Web::WebDriver::execute_script(m_page_client->page(), move(body), move(arguments), timeout_ms, JS::create_heap_function(vm.heap(), [&](Web::WebDriver::ExecuteScriptResultSerialized result) { + dbgln_if(WEBDRIVER_DEBUG, "Executing script returned: {}", result.value); + Web::WebDriver::Response response; - switch (result.type) { - // 10. 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 Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out"); - // 11. 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 move(result.value); - // 12. 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 Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value)); - case Web::WebDriver::ExecuteScriptResultType::BrowsingContextDiscarded: - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Browsing context has been discarded", move(result.value)); - } + switch (result.type) { + // 10. If promise is still pending and the session script timeout is reached, return error with error code script timeout. + case Web::WebDriver::ExecuteScriptResultType::Timeout: + response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out"); + break; + // 11. 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: + response = move(result.value); + break; + // 12. 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: + response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value)); + break; + case Web::WebDriver::ExecuteScriptResultType::BrowsingContextDiscarded: + response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Browsing context has been discarded", move(result.value)); + break; + } - VERIFY_NOT_REACHED(); + async_script_executed(move(response)); + })); + + return JsonValue {}; } // 13.2.2 Execute Async Script, https://w3c.github.io/webdriver/#dfn-execute-async-script Messages::WebDriverClient::ExecuteAsyncScriptResponse WebDriverConnection::execute_async_script(JsonValue const& payload) { + auto* window = m_page_client->page().top_level_browsing_context().active_window(); + auto& vm = window->vm(); + // 1. Let body and arguments by 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)); + auto [body, arguments] = TRY(extract_the_script_arguments_from_a_request(vm, payload)); // 2. If the current browsing context is no longer open, return error with error code no such window. TRY(ensure_open_top_level_browsing_context()); @@ -1895,25 +1909,33 @@ Messages::WebDriverClient::ExecuteAsyncScriptResponse WebDriverConnection::execu auto timeout_ms = m_timeouts_configuration.script_timeout; // This handles steps 5 to 9 and produces the appropriate result type for the following steps. - auto result = Web::WebDriver::execute_async_script(m_page_client->page(), body, move(arguments), timeout_ms); - dbgln_if(WEBDRIVER_DEBUG, "Executing async script returned: {}", result.value); + Web::WebDriver::execute_async_script(m_page_client->page(), move(body), move(arguments), timeout_ms, JS::create_heap_function(vm.heap(), [&](Web::WebDriver::ExecuteScriptResultSerialized result) { + dbgln_if(WEBDRIVER_DEBUG, "Executing async script returned: {}", result.value); + Web::WebDriver::Response response; - switch (result.type) { - // 10. 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 Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out"); - // 11. 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 move(result.value); - // 12. 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 Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value)); - case Web::WebDriver::ExecuteScriptResultType::BrowsingContextDiscarded: - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Browsing context has been discarded", move(result.value)); - } + switch (result.type) { + // 10. If promise is still pending and the session script timeout is reached, return error with error code script timeout. + case Web::WebDriver::ExecuteScriptResultType::Timeout: + response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out"); + break; + // 11. 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: + response = move(result.value); + break; + // 12. 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: + response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value)); + break; + case Web::WebDriver::ExecuteScriptResultType::BrowsingContextDiscarded: + response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Browsing context has been discarded", move(result.value)); + break; + } - VERIFY_NOT_REACHED(); + async_script_executed(move(response)); + })); + + return JsonValue {}; } // 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies @@ -2487,11 +2509,8 @@ ErrorOr WebDriverConnection::find(StartNodeGet } // https://w3c.github.io/webdriver/#dfn-extract-the-script-arguments-from-a-request -ErrorOr WebDriverConnection::extract_the_script_arguments_from_a_request(JsonValue const& payload) +ErrorOr WebDriverConnection::extract_the_script_arguments_from_a_request(JS::VM& vm, JsonValue const& payload) { - auto* window = m_page_client->page().top_level_browsing_context().active_window(); - auto& vm = window->vm(); - // 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. auto script = TRY(get_property(payload, "script"sv)); diff --git a/Userland/Services/WebContent/WebDriverConnection.h b/Userland/Services/WebContent/WebDriverConnection.h index 976b1c17a06..83e8bc7a0bb 100644 --- a/Userland/Services/WebContent/WebDriverConnection.h +++ b/Userland/Services/WebContent/WebDriverConnection.h @@ -120,7 +120,7 @@ private: ByteString script; JS::MarkedVector arguments; }; - ErrorOr extract_the_script_arguments_from_a_request(JsonValue const& payload); + static ErrorOr extract_the_script_arguments_from_a_request(JS::VM&, JsonValue const& payload); void delete_cookies(Optional const& name = {}); JS::NonnullGCPtr m_page_client; diff --git a/Userland/Services/WebContent/WebDriverServer.ipc b/Userland/Services/WebContent/WebDriverServer.ipc index 9305610d5a0..9dd79b81468 100644 --- a/Userland/Services/WebContent/WebDriverServer.ipc +++ b/Userland/Services/WebContent/WebDriverServer.ipc @@ -1,2 +1,5 @@ +#include + endpoint WebDriverServer { + script_executed(Web::WebDriver::Response response) =| } diff --git a/Userland/Services/WebDriver/Client.cpp b/Userland/Services/WebDriver/Client.cpp index eb31329c272..253aeacc8e7 100644 --- a/Userland/Services/WebDriver/Client.cpp +++ b/Userland/Services/WebDriver/Client.cpp @@ -620,7 +620,7 @@ Web::WebDriver::Response Client::execute_script(Web::WebDriver::Parameters param { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//execute/sync"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().execute_script(payload); + return session->execute_script(move(payload), Session::ScriptMode::Sync); } // 13.2.2 Execute Async Script, https://w3c.github.io/webdriver/#dfn-execute-async-script @@ -629,7 +629,7 @@ Web::WebDriver::Response Client::execute_async_script(Web::WebDriver::Parameters { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//execute/async"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().execute_async_script(payload); + return session->execute_script(move(payload), Session::ScriptMode::Async); } // 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies diff --git a/Userland/Services/WebDriver/Session.cpp b/Userland/Services/WebDriver/Session.cpp index d3dc2c147f8..65ae64f2cc0 100644 --- a/Userland/Services/WebDriver/Session.cpp +++ b/Userland/Services/WebDriver/Session.cpp @@ -12,6 +12,7 @@ #include "Client.h" #include #include +#include #include #include #include @@ -168,4 +169,29 @@ Web::WebDriver::Response Session::get_window_handles() const return JsonValue { move(handles) }; } +Web::WebDriver::Response Session::execute_script(JsonValue payload, ScriptMode mode) const +{ + ScopeGuard guard { [&]() { web_content_connection().on_script_executed = nullptr; } }; + + Optional response; + web_content_connection().on_script_executed = [&](auto result) { + response = move(result); + }; + + switch (mode) { + case ScriptMode::Sync: + TRY(web_content_connection().execute_script(move(payload))); + break; + case ScriptMode::Async: + TRY(web_content_connection().execute_async_script(move(payload))); + break; + } + + 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 8e983c3d36e..fa0988f9647 100644 --- a/Userland/Services/WebDriver/Session.h +++ b/Userland/Services/WebDriver/Session.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include #include @@ -53,6 +54,12 @@ public: Web::WebDriver::Response switch_to_window(StringView); Web::WebDriver::Response get_window_handles() const; + enum class ScriptMode { + Sync, + Async, + }; + Web::WebDriver::Response execute_script(JsonValue, ScriptMode) 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 8b6aeb2f85e..d756536892e 100644 --- a/Userland/Services/WebDriver/WebContentConnection.cpp +++ b/Userland/Services/WebDriver/WebContentConnection.cpp @@ -20,4 +20,10 @@ void WebContentConnection::die() on_close(); } +void WebContentConnection::script_executed(Web::WebDriver::Response const& response) +{ + if (on_script_executed) + on_script_executed(response); +} + } diff --git a/Userland/Services/WebDriver/WebContentConnection.h b/Userland/Services/WebDriver/WebContentConnection.h index 6c31a45344b..9613e346847 100644 --- a/Userland/Services/WebDriver/WebContentConnection.h +++ b/Userland/Services/WebDriver/WebContentConnection.h @@ -21,8 +21,12 @@ public: WebContentConnection(NonnullOwnPtr socket); Function on_close; + Function on_script_executed; +private: virtual void die() override; + + virtual void script_executed(Web::WebDriver::Response const&) override; }; }