Selaa lähdekoodia

LibWeb+WebContent: Handle user prompts that open during script execution

If a dialog is opened while a script is executing, we must give control
back to the WebDriver client. The script must also continue executing
though, so once it completes, we ignore its result.
Timothy Flynn 8 kuukautta sitten
vanhempi
commit
1be67faab7

+ 0 - 2
Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp

@@ -254,8 +254,6 @@ JS::ThrowCompletionOr<JS::Value> execute_a_function_body(HTML::BrowsingContext c
 // https://w3c.github.io/webdriver/#dfn-execute-a-function-body
 JS::ThrowCompletionOr<JS::Value> execute_a_function_body(HTML::Window const& window, ByteString const& body, ReadonlySpan<JS::Value> parameters, JS::GCPtr<JS::Object> environment_override_object)
 {
-    // FIXME: If at any point during the algorithm a user prompt appears, immediately return Completion { [[Type]]: normal, [[Value]]: null, [[Target]]: empty }, but continue to run the other steps of this algorithm in parallel.
-
     auto& realm = window.realm();
 
     // 2. Let environment settings be the environment settings object for window.

+ 34 - 41
Userland/Services/WebContent/WebDriverConnection.cpp

@@ -54,7 +54,6 @@
 #include <LibWeb/UIEvents/MouseEvent.h>
 #include <LibWeb/WebDriver/Actions.h>
 #include <LibWeb/WebDriver/ElementReference.h>
-#include <LibWeb/WebDriver/ExecuteScript.h>
 #include <LibWeb/WebDriver/HeapTimer.h>
 #include <LibWeb/WebDriver/InputState.h>
 #include <LibWeb/WebDriver/Properties.h>
@@ -1861,35 +1860,12 @@ 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.
-    Web::WebDriver::execute_script(current_browsing_context(), move(body), move(arguments), timeout_ms, JS::create_heap_function(vm.heap(), [&](Web::WebDriver::ExecuteScriptResultSerialized result) {
+    Web::WebDriver::execute_script(current_browsing_context(), move(body), move(arguments), timeout_ms, JS::create_heap_function(vm.heap(), [this](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:
-            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;
-        case Web::WebDriver::ExecuteScriptResultType::StaleElement:
-            response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Referenced element has become stale", move(result.value));
-            break;
-        }
-
-        async_script_executed(move(response));
+        handle_script_response(move(result));
     }));
 
+    m_has_pending_script_execution = true;
     return JsonValue {};
 }
 
@@ -1914,34 +1890,43 @@ Messages::WebDriverClient::ExecuteAsyncScriptResponse WebDriverConnection::execu
     // This handles steps 5 to 9 and produces the appropriate result type for the following steps.
     Web::WebDriver::execute_async_script(current_browsing_context(), 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;
+        handle_script_response(move(result));
+    }));
 
+    m_has_pending_script_execution = true;
+    return JsonValue {};
+}
+
+void WebDriverConnection::handle_script_response(Web::WebDriver::ExecuteScriptResultSerialized result)
+{
+    if (!m_has_pending_script_execution)
+        return;
+    m_has_pending_script_execution = false;
+
+    auto response = [&]() -> Web::WebDriver::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:
-            response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
-            break;
+            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:
-            response = move(result.value);
-            break;
+            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:
-            response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
-            break;
+            return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
         case Web::WebDriver::ExecuteScriptResultType::BrowsingContextDiscarded:
-            response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Browsing context has been discarded", move(result.value));
-            break;
+            return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Browsing context has been discarded", move(result.value));
         case Web::WebDriver::ExecuteScriptResultType::StaleElement:
-            response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Referenced element has become stale", move(result.value));
-            break;
+            return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Referenced element has become stale", move(result.value));
         }
 
-        async_script_executed(move(response));
-    }));
+        VERIFY_NOT_REACHED();
+    }();
 
-    return JsonValue {};
+    async_script_executed(move(response));
 }
 
 // 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies
@@ -2562,6 +2547,14 @@ void WebDriverConnection::page_did_open_dialog(Badge<PageClient>)
     //              seems to match how other browsers behave.
     if (m_navigation_timer)
         m_navigation_timer->stop_and_fire_timeout_handler();
+
+    // https://w3c.github.io/webdriver/#dfn-execute-a-function-body
+    // If at any point during the algorithm a user prompt appears, immediately return Completion { [[Type]]: normal,
+    // [[Value]]: null, [[Target]]: empty }, but continue to run the other steps of this algorithm in parallel.
+    if (m_has_pending_script_execution) {
+        m_has_pending_script_execution = false;
+        async_script_executed(JsonValue {});
+    }
 }
 
 // https://w3c.github.io/webdriver/#dfn-maximize-the-window

+ 4 - 0
Userland/Services/WebContent/WebDriverConnection.h

@@ -19,6 +19,7 @@
 #include <LibWeb/Forward.h>
 #include <LibWeb/HTML/VisibilityState.h>
 #include <LibWeb/WebDriver/ElementLocationStrategies.h>
+#include <LibWeb/WebDriver/ExecuteScript.h>
 #include <LibWeb/WebDriver/Response.h>
 #include <LibWeb/WebDriver/TimeoutsConfiguration.h>
 #include <WebContent/Forward.h>
@@ -143,6 +144,8 @@ private:
         JS::MarkedVector<JS::Value> arguments;
     };
     ErrorOr<ScriptArguments, Web::WebDriver::Error> extract_the_script_arguments_from_a_request(JS::VM&, JsonValue const& payload);
+    void handle_script_response(Web::WebDriver::ExecuteScriptResultSerialized);
+
     void delete_cookies(Optional<StringView> const& name = {});
 
     // https://w3c.github.io/webdriver/#dfn-page-load-strategy
@@ -167,6 +170,7 @@ private:
     JS::GCPtr<Web::HTML::BrowsingContext> m_current_top_level_browsing_context;
 
     size_t m_pending_window_rect_requests { 0 };
+    bool m_has_pending_script_execution { false };
 
     friend class ElementLocator;
     JS::GCPtr<ElementLocator> m_element_locator;