Bladeren bron

WebContent+WebDriver: Move Find Element to WebContent

Note that this does nothing to "fix" how element references are created.
We continue to return the element ID because, otherwise, all other
element WebDriver endpoints would break.

On the bright side, we avoid several IPC round trips now that we perform
the entire 'find' operation in the WebContent process; and we are able
to work directly on DOM nodes.
Timothy Flynn 2 jaren geleden
bovenliggende
commit
61de50c7fd

+ 1 - 0
Userland/Services/WebContent/WebDriverClient.ipc

@@ -9,4 +9,5 @@ endpoint WebDriverClient {
     set_window_rect(JsonValue payload) => (Web::WebDriver::Response response)
     set_window_rect(JsonValue payload) => (Web::WebDriver::Response response)
     maximize_window() => (Web::WebDriver::Response response)
     maximize_window() => (Web::WebDriver::Response response)
     minimize_window() => (Web::WebDriver::Response response)
     minimize_window() => (Web::WebDriver::Response response)
+    find_element(JsonValue payload) => (Web::WebDriver::Response response)
 }
 }

+ 114 - 0
Userland/Services/WebContent/WebDriverConnection.cpp

@@ -50,6 +50,51 @@ static Gfx::IntRect compute_window_rect(Web::Page const& page)
     };
     };
 }
 }
 
 
+// https://w3c.github.io/webdriver/#dfn-get-or-create-a-web-element-reference
+static String get_or_create_a_web_element_reference(Web::DOM::Node const& element)
+{
+    // FIXME: 1. For each known element of the current browsing context’s list of known elements:
+    // FIXME:     1. If known element equals element, return success with known element’s web element reference.
+    // FIXME: 2. Add element to the list of known elements of the current browsing context.
+    // FIXME: 3. Return success with the element’s web element reference.
+
+    return String::number(element.id());
+}
+
+// https://w3c.github.io/webdriver/#dfn-web-element-reference-object
+static JsonObject web_element_reference_object(Web::DOM::Node const& element)
+{
+    // https://w3c.github.io/webdriver/#dfn-web-element-identifier
+    static String const web_element_identifier = "element-6066-11e4-a52e-4f735466cecf"sv;
+
+    // 1. Let identifier be the web element identifier.
+    auto identifier = web_element_identifier;
+
+    // 2. Let reference be the result of get or create a web element reference given element.
+    auto reference = get_or_create_a_web_element_reference(element);
+
+    // 3. Return a JSON Object initialized with a property with name identifier and value reference.
+    JsonObject object;
+    object.set("name"sv, identifier);
+    object.set("value"sv, reference);
+    return object;
+}
+
+static ErrorOr<String, Web::WebDriver::Error> get_property(JsonValue const& payload, StringView key)
+{
+    if (!payload.is_object())
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
+
+    auto const* property = payload.as_object().get_ptr(key);
+
+    if (!property)
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("No property called '{}' present", key));
+    if (!property->is_string())
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Property '{}' is not a String", key));
+
+    return property->as_string();
+}
+
 ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(ConnectionFromClient& web_content_client, PageHost& page_host, String const& webdriver_ipc_path)
 ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(ConnectionFromClient& web_content_client, PageHost& page_host, String const& webdriver_ipc_path)
 {
 {
     dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path);
     dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path);
@@ -261,6 +306,43 @@ Messages::WebDriverClient::MinimizeWindowResponse WebDriverConnection::minimize_
     return serialize_rect(window_rect);
     return serialize_rect(window_rect);
 }
 }
 
 
+// 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element
+Messages::WebDriverClient::FindElementResponse WebDriverConnection::find_element(JsonValue const& payload)
+{
+    // 1. Let location strategy be the result of getting a property called "using".
+    auto location_strategy_string = TRY(get_property(payload, "using"sv));
+    auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
+
+    // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
+    if (!location_strategy.has_value())
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Location strategy '{}' is invalid", location_strategy_string));
+
+    // 3. Let selector be the result of getting a property called "value".
+    // 4. If selector is undefined, return error with error code invalid argument.
+    auto selector = TRY(get_property(payload, "value"sv));
+
+    // 5. If the current browsing context is no longer open, return error with error code no such window.
+    TRY(ensure_open_top_level_browsing_context());
+
+    // FIXME: 6. Handle any user prompts and return its value if it is an error.
+
+    // 7. Let start node be the current browsing context’s document element.
+    auto* start_node = m_page_host.page().top_level_browsing_context().active_document();
+
+    // 8. If start node is null, return error with error code no such element.
+    if (!start_node)
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
+
+    // 9. Let result be the result of trying to Find with start node, location strategy, and selector.
+    auto result = TRY(find(*start_node, *location_strategy, selector));
+
+    // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
+    if (result.is_empty())
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
+
+    return make_success_response(result.at(0));
+}
+
 // https://w3c.github.io/webdriver/#dfn-no-longer-open
 // https://w3c.github.io/webdriver/#dfn-no-longer-open
 ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::ensure_open_top_level_browsing_context()
 ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::ensure_open_top_level_browsing_context()
 {
 {
@@ -310,4 +392,36 @@ Gfx::IntRect WebDriverConnection::iconify_the_window()
     return rect;
     return rect;
 }
 }
 
 
+// https://w3c.github.io/webdriver/#dfn-find
+ErrorOr<JsonArray, Web::WebDriver::Error> WebDriverConnection::find(Web::DOM::ParentNode& start_node, Web::WebDriver::LocationStrategy using_, StringView value)
+{
+    // FIXME: 1. Let end time be the current time plus the session implicit wait timeout.
+
+    // 2. Let location strategy be equal to using.
+    auto location_strategy = using_;
+
+    // 3. Let selector be equal to value.
+    auto selector = value;
+
+    // 4. Let elements returned be the result of trying to call the relevant element location strategy with arguments start node, and selector.
+    auto elements = Web::WebDriver::invoke_location_strategy(location_strategy, start_node, selector);
+
+    // 5. If a DOMException, SyntaxError, XPathException, or other error occurs during the execution of the element location strategy, return error invalid selector.
+    if (elements.is_error())
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSelector, String::formatted("The location strategy could not finish: {}", elements.error().message));
+
+    // FIXME: 6. If elements returned is empty and the current time is less than end time return to step 4. Otherwise, continue to the next step.
+
+    // 7. Let result be an empty JSON List.
+    JsonArray result;
+    result.ensure_capacity(elements.value()->length());
+
+    // 8. For each element in elements returned, append the web element reference object for element, to result.
+    for (size_t i = 0; i < elements.value()->length(); ++i)
+        result.append(web_element_reference_object(*elements.value()->item(i)));
+
+    // 9. Return success with data result.
+    return result;
+}
+
 }
 }

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

@@ -9,6 +9,8 @@
 #pragma once
 #pragma once
 
 
 #include <LibIPC/ConnectionToServer.h>
 #include <LibIPC/ConnectionToServer.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/WebDriver/ElementLocationStrategies.h>
 #include <LibWeb/WebDriver/Response.h>
 #include <LibWeb/WebDriver/Response.h>
 #include <WebContent/Forward.h>
 #include <WebContent/Forward.h>
 #include <WebContent/WebDriverClientEndpoint.h>
 #include <WebContent/WebDriverClientEndpoint.h>
@@ -37,11 +39,13 @@ private:
     virtual Messages::WebDriverClient::SetWindowRectResponse set_window_rect(JsonValue const& payload) override;
     virtual Messages::WebDriverClient::SetWindowRectResponse set_window_rect(JsonValue const& payload) override;
     virtual Messages::WebDriverClient::MaximizeWindowResponse maximize_window() override;
     virtual Messages::WebDriverClient::MaximizeWindowResponse maximize_window() override;
     virtual Messages::WebDriverClient::MinimizeWindowResponse minimize_window() override;
     virtual Messages::WebDriverClient::MinimizeWindowResponse minimize_window() override;
+    virtual Messages::WebDriverClient::FindElementResponse find_element(JsonValue const& payload) override;
 
 
     ErrorOr<void, Web::WebDriver::Error> ensure_open_top_level_browsing_context();
     ErrorOr<void, Web::WebDriver::Error> ensure_open_top_level_browsing_context();
     void restore_the_window();
     void restore_the_window();
     Gfx::IntRect maximize_the_window();
     Gfx::IntRect maximize_the_window();
     Gfx::IntRect iconify_the_window();
     Gfx::IntRect iconify_the_window();
+    ErrorOr<JsonArray, Web::WebDriver::Error> find(Web::DOM::ParentNode& start_node, Web::WebDriver::LocationStrategy using_, StringView value);
 
 
     ConnectionFromClient& m_web_content_client;
     ConnectionFromClient& m_web_content_client;
     PageHost& m_page_host;
     PageHost& m_page_host;

+ 1 - 2
Userland/Services/WebDriver/Client.cpp

@@ -618,8 +618,7 @@ Web::WebDriver::Response Client::handle_find_element(Vector<StringView> const& p
 {
 {
     dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element");
     dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element");
     auto* session = TRY(find_session_with_id(parameters[0]));
     auto* session = TRY(find_session_with_id(parameters[0]));
-    auto result = TRY(session->find_element(payload));
-    return make_json_value(result);
+    return session->web_content_connection().find_element(payload);
 }
 }
 
 
 // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
 // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements

+ 0 - 55
Userland/Services/WebDriver/Session.cpp

@@ -447,61 +447,6 @@ ErrorOr<Vector<Session::LocalElement>, Web::WebDriver::Error> Session::locator_s
     return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Not implemented: locator strategy XPath");
     return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Not implemented: locator strategy XPath");
 }
 }
 
 
-// 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element
-Web::WebDriver::Response Session::find_element(JsonValue const& payload)
-{
-    if (!payload.is_object())
-        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
-
-    auto const& properties = payload.as_object();
-    // 1. Let location strategy be the result of getting a property called "using".
-    if (!properties.has("using"sv))
-        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "No property called 'using' present");
-    auto const& maybe_location_strategy = properties.get("using"sv);
-    if (!maybe_location_strategy.is_string())
-        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Property 'using' is not a String");
-
-    auto location_strategy = maybe_location_strategy.to_string();
-
-    // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
-    if (!s_locator_strategies.first_matching([&](LocatorStrategy const& match) { return match.name == location_strategy; }).has_value())
-        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "No valid location strategy");
-
-    // 3. Let selector be the result of getting a property called "value".
-    // 4. If selector is undefined, return error with error code invalid argument.
-    if (!properties.has("value"sv))
-        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "No property called 'value' present");
-    auto const& maybe_selector = properties.get("value"sv);
-    if (!maybe_selector.is_string())
-        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Property 'value' is not a String");
-
-    auto selector = maybe_selector.to_string();
-
-    // 5. 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: 6. Handle any user prompts and return its value if it is an error.
-
-    // 7. Let start node be the current browsing context’s document element.
-    auto maybe_start_node_id = m_browser_connection->get_document_element();
-
-    // 8. If start node is null, return error with error code no such element.
-    if (!maybe_start_node_id.has_value())
-        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist");
-
-    auto start_node_id = maybe_start_node_id.release_value();
-    LocalElement start_node = { start_node_id };
-
-    // 9. Let result be the result of trying to Find with start node, location strategy, and selector.
-    auto result = TRY(find(start_node, location_strategy, selector));
-
-    // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
-    if (result.is_empty())
-        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist");
-
-    return JsonValue(result.at(0));
-}
-
 // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
 // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
 Web::WebDriver::Response Session::find_elements(JsonValue const& payload)
 Web::WebDriver::Response Session::find_elements(JsonValue const& payload)
 {
 {

+ 0 - 1
Userland/Services/WebDriver/Session.h

@@ -58,7 +58,6 @@ public:
     Web::WebDriver::Response get_window_handle();
     Web::WebDriver::Response get_window_handle();
     ErrorOr<void, Variant<Web::WebDriver::Error, Error>> close_window();
     ErrorOr<void, Variant<Web::WebDriver::Error, Error>> close_window();
     Web::WebDriver::Response get_window_handles() const;
     Web::WebDriver::Response get_window_handles() const;
-    Web::WebDriver::Response find_element(JsonValue const& payload);
     Web::WebDriver::Response find_elements(JsonValue const& payload);
     Web::WebDriver::Response find_elements(JsonValue const& payload);
     Web::WebDriver::Response find_element_from_element(JsonValue const& payload, StringView parameter_element_id);
     Web::WebDriver::Response find_element_from_element(JsonValue const& payload, StringView parameter_element_id);
     Web::WebDriver::Response find_elements_from_element(JsonValue const& payload, StringView parameter_element_id);
     Web::WebDriver::Response find_elements_from_element(JsonValue const& payload, StringView parameter_element_id);