diff --git a/Userland/Services/WebContent/WebDriverConnection.cpp b/Userland/Services/WebContent/WebDriverConnection.cpp index 661860b12d5..d260506c36a 100644 --- a/Userland/Services/WebContent/WebDriverConnection.cpp +++ b/Userland/Services/WebContent/WebDriverConnection.cpp @@ -203,6 +203,7 @@ void WebDriverConnection::visit_edges(JS::Cell::Visitor& visitor) visitor.visit(m_current_browsing_context); visitor.visit(m_current_parent_browsing_context); visitor.visit(m_current_top_level_browsing_context); + visitor.visit(m_element_locator); visitor.visit(m_action_executor); visitor.visit(m_document_observer); visitor.visit(m_navigation_observer); @@ -810,29 +811,41 @@ Messages::WebDriverClient::ConsumeUserActivationResponse WebDriverConnection::co return consume; } +static Web::WebDriver::Response extract_first_element(Web::WebDriver::Response result) +{ + auto array = TRY(result); + VERIFY(array.is_array()); + + if (!array.as_array().is_empty()) + return array.as_array().take(0); + + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv); +} + // 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". + // 1. Let location strategy be the result of getting a property named "using" from parameters. auto location_strategy_string = TRY(Web::WebDriver::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. + // 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, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string)); - // 3. Let selector be the result of getting a property called "value". + // 3. Let selector be the result of getting a property named "value" from parameters. // 4. If selector is undefined, return error with error code invalid argument. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv)); - // 5. If the current browsing context is no longer open, return error with error code no such window. + // 5. If session's current browsing context is no longer open, return error with error code no such window. TRY(ensure_current_browsing_context_is_open()); - // 6. Handle any user prompts and return its value if it is an error. + // 6. Try to handle any user prompts with session. TRY(handle_any_user_prompts()); - auto start_node_getter = [this]() -> StartNodeGetter::ReturnType { - // 7. Let start node be the current browsing context’s document element. + auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this]() -> ErrorOr, Web::WebDriver::Error> { + // 7. Let start node be session's current browsing context's document element. auto* start_node = current_browsing_context().active_document(); // 8. If start node is null, return error with error code no such element. @@ -840,41 +853,41 @@ Messages::WebDriverClient::FindElementResponse WebDriverConnection::find_element return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv); return *start_node; - }; + }); - // 9. Let result be the result of trying to Find with start node, location strategy, and selector. - auto result = TRY(find(move(start_node_getter), *location_strategy, selector)); + // 9. Let result be the result of trying to Find with session, start node, location strategy, and selector. + find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) { + // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result. + async_find_elements_complete(extract_first_element(move(result))); + })); - // 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 result.take(0); + return JsonValue {}; } // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements Messages::WebDriverClient::FindElementsResponse WebDriverConnection::find_elements(JsonValue const& payload) { - // 1. Let location strategy be the result of getting a property called "using". + // 1. Let location strategy be the result of getting a property named "using" from parameters. auto location_strategy_string = TRY(Web::WebDriver::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. + // 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, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string)); - // 3. Let selector be the result of getting a property called "value". + // 3. Let selector be the result of getting a property named "value" from parameters. // 4. If selector is undefined, return error with error code invalid argument. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv)); - // 5. If the current browsing context is no longer open, return error with error code no such window. + // 5. If session's current browsing context is no longer open, return error with error code no such window. TRY(ensure_current_browsing_context_is_open()); - // 6. Handle any user prompts and return its value if it is an error. + // 6. Try to handle any user prompts with session. TRY(handle_any_user_prompts()); - auto start_node_getter = [this]() -> StartNodeGetter::ReturnType { - // 7. Let start node be the current browsing context’s document element. + auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this]() -> ErrorOr, Web::WebDriver::Error> { + // 7. Let start node be session's current browsing context's document element. auto* start_node = current_browsing_context().active_document(); // 8. If start node is null, return error with error code no such element. @@ -882,16 +895,20 @@ Messages::WebDriverClient::FindElementsResponse WebDriverConnection::find_elemen return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv); return *start_node; - }; + }); - // 9. Return the result of trying to Find with start node, location strategy, and selector. - return TRY(find(move(start_node_getter), *location_strategy, selector)); + // 9. Return the result of trying to Find with session, start node, location strategy, and selector. + find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) { + async_find_elements_complete(move(result)); + })); + + return JsonValue {}; } // 12.3.4 Find Element From Element, https://w3c.github.io/webdriver/#dfn-find-element-from-element Messages::WebDriverClient::FindElementFromElementResponse WebDriverConnection::find_element_from_element(JsonValue const& payload, String const& element_id) { - // 1. Let location strategy be the result of getting a property called "using". + // 1. Let location strategy be the result of getting a property named "using" from parameters. auto location_strategy_string = TRY(Web::WebDriver::get_property(payload, "using"sv)); auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string); @@ -899,35 +916,34 @@ Messages::WebDriverClient::FindElementFromElementResponse WebDriverConnection::f if (!location_strategy.has_value()) return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string)); - // 3. Let selector be the result of getting a property called "value". + // 3. Let selector be the result of getting a property named "value" from parameters. // 4. If selector is undefined, return error with error code invalid argument. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv)); - // 5. If the current browsing context is no longer open, return error with error code no such window. + // 5. If session's current browsing context is no longer open, return error with error code no such window. TRY(ensure_current_browsing_context_is_open()); - // 6. Handle any user prompts and return its value if it is an error. + // 6. Try to handle any user prompts with session. TRY(handle_any_user_prompts()); - auto start_node_getter = [&]() -> StartNodeGetter::ReturnType { - // 7. Let start node be the result of trying to get a known connected element with url variable element id. - return TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id)); - }; + auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this, element_id]() -> ErrorOr, Web::WebDriver::Error> { + // 7. Let start node be the result of trying to get a known element with session and URL variables["element id"]. + return Web::WebDriver::get_known_element(current_browsing_context(), element_id); + }); - // 8. Let result be the value of trying to Find with start node, location strategy, and selector. - auto result = TRY(find(move(start_node_getter), *location_strategy, selector)); + // 8. Let result be the value of trying to Find with session, start node, location strategy, and selector. + find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) { + // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result. + async_find_elements_complete(extract_first_element(move(result))); + })); - // 9. 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 result.take(0); + return JsonValue {}; } // 12.3.5 Find Elements From Element, https://w3c.github.io/webdriver/#dfn-find-elements-from-element Messages::WebDriverClient::FindElementsFromElementResponse WebDriverConnection::find_elements_from_element(JsonValue const& payload, String const& element_id) { - // 1. Let location strategy be the result of getting a property called "using". + // 1. Let location strategy be the result of getting a property named "using" from parameters. auto location_strategy_string = TRY(Web::WebDriver::get_property(payload, "using"sv)); auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string); @@ -935,23 +951,27 @@ Messages::WebDriverClient::FindElementsFromElementResponse WebDriverConnection:: if (!location_strategy.has_value()) return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string)); - // 3. Let selector be the result of getting a property called "value". + // 3. Let selector be the result of getting a property named "value" from parameters. // 4. If selector is undefined, return error with error code invalid argument. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv)); - // 5. If the current browsing context is no longer open, return error with error code no such window. + // 5. If session's current browsing context is no longer open, return error with error code no such window. TRY(ensure_current_browsing_context_is_open()); - // 6. Handle any user prompts and return its value if it is an error. + // 6. Try to handle any user prompts with session. TRY(handle_any_user_prompts()); - auto start_node_getter = [&]() -> StartNodeGetter::ReturnType { - // 7. Let start node be the result of trying to get a known connected element with url variable element id. - return TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id)); - }; + auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this, element_id]() -> ErrorOr, Web::WebDriver::Error> { + // 7. Let start node be the result of trying to get a known element with session and URL variables["element id"]. + return Web::WebDriver::get_known_element(current_browsing_context(), element_id); + }); - // 8. Return the result of trying to Find with start node, location strategy, and selector. - return TRY(find(move(start_node_getter), *location_strategy, selector)); + // 8. Return the result of trying to Find with session, start node, location strategy, and selector. + find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) { + async_find_elements_complete(move(result)); + })); + + return JsonValue {}; } // 12.3.6 Find Element From Shadow Root, https://w3c.github.io/webdriver/#find-element-from-shadow-root @@ -969,25 +989,24 @@ Messages::WebDriverClient::FindElementFromShadowRootResponse WebDriverConnection // 4. If selector is undefined, return error with error code invalid argument. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv)); - // 5. If the current browsing context is no longer open, return error with error code no such window. + // 5. If the ssession's current browsing context is no longer open, return error with error code no such window. TRY(ensure_current_browsing_context_is_open()); // 6. Handle any user prompts and return its value if it is an error. TRY(handle_any_user_prompts()); - auto start_node_getter = [&]() -> StartNodeGetter::ReturnType { - // 7. Let start node be the result of trying to get a known shadow root with url variable shadow id. - return TRY(Web::WebDriver::get_known_shadow_root(shadow_id)); - }; + auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this, shadow_id]() -> ErrorOr, Web::WebDriver::Error> { + // 7. Let start node be the result of trying to get a known shadow root with session and URL variables["shadow id"]. + return Web::WebDriver::get_known_shadow_root(current_browsing_context(), shadow_id); + }); - // 8. Let result be the value of trying to Find with start node, location strategy, and selector. - auto result = TRY(find(move(start_node_getter), *location_strategy, selector)); + // 8. Let result be the value of trying to Find with session, start node, location strategy, and selector. + find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) { + // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result. + async_find_elements_complete(extract_first_element(move(result))); + })); - // 9. 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 result.take(0); + return JsonValue {}; } // 12.3.7 Find Elements From Shadow Root, https://w3c.github.io/webdriver/#find-elements-from-shadow-root @@ -1005,19 +1024,23 @@ Messages::WebDriverClient::FindElementsFromShadowRootResponse WebDriverConnectio // 4. If selector is undefined, return error with error code invalid argument. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv)); - // 5. If the current browsing context is no longer open, return error with error code no such window. + // 5. If session's current browsing context is no longer open, return error with error code no such window. TRY(ensure_current_browsing_context_is_open()); // 6. Handle any user prompts and return its value if it is an error. TRY(handle_any_user_prompts()); - auto start_node_getter = [&]() -> StartNodeGetter::ReturnType { - // 7. Let start node be the result of trying to get a known shadow root with url variable shadow id. - return TRY(Web::WebDriver::get_known_shadow_root(shadow_id)); - }; + auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this, shadow_id]() -> ErrorOr, Web::WebDriver::Error> { + // 7. Let start node be the result of trying to get a known shadow root with session and URL variables["shadow id"]. + return Web::WebDriver::get_known_shadow_root(current_browsing_context(), shadow_id); + }); - // 8. Return the result of trying to Find with start node, location strategy, and selector. - return TRY(find(move(start_node_getter), *location_strategy, selector)); + // 8. Return the result of trying to Find with session, start node, location strategy, and selector. + find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) { + async_find_elements_complete(move(result)); + })); + + return JsonValue {}; } // 12.3.8 Get Active Element, https://w3c.github.io/webdriver/#get-active-element @@ -1043,13 +1066,13 @@ Messages::WebDriverClient::GetActiveElementResponse WebDriverConnection::get_act // 12.3.9 Get Element Shadow Root, https://w3c.github.io/webdriver/#get-element-shadow-root Messages::WebDriverClient::GetElementShadowRootResponse WebDriverConnection::get_element_shadow_root(String const& element_id) { - // 1. If the current browsing context is no longer open, return error with error code no such window. + // 1. If session's current browsing context is no longer open, return error with error code no such window. TRY(ensure_current_browsing_context_is_open()); // 2. Handle any user prompts and return its value if it is an error. TRY(handle_any_user_prompts()); - // 3. Let element be the result of trying to get a known connected element with url variable element id. + // 3. Let element be the result of trying to get a known element with session and URL variables[element id]. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id)); // 4. Let shadow root be element's shadow root. @@ -1059,8 +1082,8 @@ Messages::WebDriverClient::GetElementShadowRootResponse WebDriverConnection::get if (!shadow_root) return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchShadowRoot, ByteString::formatted("Element with ID '{}' does not have a shadow root", element_id)); - // 6. Let serialized be the shadow root reference object for shadow root. - auto serialized = Web::WebDriver::shadow_root_reference_object(*shadow_root); + // 6. Let serialized be the shadow root reference object for session and shadow root. + auto serialized = Web::WebDriver::shadow_root_reference_object(current_browsing_context(), *shadow_root); // 7. Return success with data serialized. return serialized; @@ -2587,54 +2610,126 @@ void WebDriverConnection::wait_for_visibility_state(JS::NonnullGCPtr WebDriverConnection::find(StartNodeGetter&& start_node_getter, Web::WebDriver::LocationStrategy using_, StringView value) -{ - // 1. Let end time be the current time plus the session implicit wait timeout. - auto end_time = MonotonicTime::now() + AK::Duration::from_milliseconds(static_cast(m_timeouts_configuration.implicit_wait_timeout.value_or(0))); +class ElementLocator final : public JS::Cell { + JS_CELL(ElementLocator, JS::Cell); + JS_DECLARE_ALLOCATOR(ElementLocator); - // 2. Let location strategy be equal to using. - auto location_strategy = using_; +public: + ElementLocator( + Web::HTML::BrowsingContext const& browsing_context, + Web::WebDriver::LocationStrategy location_strategy, + ByteString selector, + WebDriverConnection::GetStartNode get_start_node, + WebDriverConnection::OnFindComplete on_complete, + JS::NonnullGCPtr timer) + : m_browsing_context(browsing_context) + , m_location_strategy(location_strategy) + , m_selector(move(selector)) + , m_get_start_node(get_start_node) + , m_on_complete(on_complete) + , m_timer(timer) + { + } - // 3. Let selector be equal to value. - auto selector = value; + void search_for_element() + { + if (auto result = perform_search(); result.has_value()) { + m_on_complete->function()(result.release_value()); + return; + } - ErrorOr, Web::WebDriver::Error> maybe_elements { nullptr }; + if (m_timer->is_timed_out()) + return; - auto try_to_find_element = [&]() -> decltype(maybe_elements) { - // 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, *TRY(start_node_getter()), selector); + Web::HTML::queue_a_task(Web::HTML::Task::Source::Unspecified, nullptr, nullptr, JS::create_heap_function(heap(), [this]() { + search_for_element(); + })); + } - // 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, ByteString::formatted("The location strategy could not finish: {}", elements.error().message)); +private: + Optional perform_search() + { + // 1. Set elements returned to the result of trying to call the relevant element location strategy with arguments + // start node, and selector. + auto maybe_elements = Web::WebDriver::invoke_location_strategy(m_location_strategy, TRY(m_get_start_node->function()()), m_selector); - return elements.release_value(); - }; - - Web::Platform::EventLoopPlugin::the().spin_until(JS::create_heap_function(current_top_level_browsing_context()->heap(), [&]() { - maybe_elements = try_to_find_element(); + // 2. If a DOMException, SyntaxError, XPathException, or other error occurs during the execution of the element + // location strategy, return error invalid selector. if (maybe_elements.is_error()) - return true; + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSelector, ByteString::formatted("The location strategy could not finish: {}", maybe_elements.error().message)); - // 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. - return maybe_elements.value()->length() != 0 || MonotonicTime::now() >= end_time; - })); + if (auto elements = maybe_elements.release_value(); elements->length() > 0) { + // 8. Let result be an empty List. + JsonArray result; + result.ensure_capacity(elements->length()); - auto elements = TRY(maybe_elements); - VERIFY(elements); + // 9. For each element in elements returned, append the web element reference object for session and element, + // to result. + for (size_t i = 0; i < elements->length(); ++i) + result.must_append(Web::WebDriver::web_element_reference_object(m_browsing_context, *elements->item(i))); - // 7. Let result be an empty JSON List. - JsonArray result; - result.ensure_capacity(elements->length()); + // 10. Return success with data result. + return JsonValue { move(result) }; + } - // 8. For each element in elements returned, append the web element reference object for element, to result. - for (size_t i = 0; i < elements->length(); ++i) - TRY(result.append(Web::WebDriver::web_element_reference_object(current_browsing_context(), *elements->item(i)))); + return {}; + } - // 9. Return success with data result. - return result; + virtual void visit_edges(Cell::Visitor& visitor) override + { + Base::visit_edges(visitor); + visitor.visit(m_browsing_context); + visitor.visit(m_get_start_node); + visitor.visit(m_on_complete); + visitor.visit(m_timer); + } + + JS::NonnullGCPtr m_browsing_context; + + Web::WebDriver::LocationStrategy m_location_strategy; + ByteString m_selector; + + WebDriverConnection::GetStartNode m_get_start_node; + WebDriverConnection::OnFindComplete m_on_complete; + + JS::NonnullGCPtr m_timer; +}; + +JS_DEFINE_ALLOCATOR(ElementLocator); + +// https://w3c.github.io/webdriver/#dfn-find +void WebDriverConnection::find(Web::WebDriver::LocationStrategy location_strategy, ByteString selector, GetStartNode get_start_node, OnFindComplete on_complete) +{ + auto& realm = current_browsing_context().active_document()->realm(); + + // 1. Let location strategy be equal to using. + // 2. Let selector be equal to value. + + // 3. Let timeout be session's session timeouts' implicit wait timeout. + auto timeout = m_timeouts_configuration.implicit_wait_timeout; + + // 4. Let timer be a new timer. + auto timer = realm.heap().allocate(realm); + + auto wrapped_on_complete = JS::create_heap_function(realm.heap(), [this, on_complete, timer](Web::WebDriver::Response result) { + m_element_locator = nullptr; + timer->stop(); + + on_complete->function()(move(result)); + }); + + // 5. If timeout is not null: + if (timeout.has_value()) { + // 1. Start the timer with timer and timeout. + timer->start(*timeout, JS::create_heap_function(realm.heap(), [wrapped_on_complete]() { + wrapped_on_complete->function()({ JsonArray {} }); + })); + } + + // 6. Let elements returned be an empty List. + // 7. While elements returned is empty and timer's timeout fired flag is not set: + m_element_locator = realm.heap().allocate(realm, current_browsing_context(), location_strategy, move(selector), get_start_node, wrapped_on_complete, timer); + m_element_locator->search_for_element(); } // https://w3c.github.io/webdriver/#dfn-json-deserialize diff --git a/Userland/Services/WebContent/WebDriverConnection.h b/Userland/Services/WebContent/WebDriverConnection.h index e4aad4ced75..7b38701eada 100644 --- a/Userland/Services/WebContent/WebDriverConnection.h +++ b/Userland/Services/WebContent/WebDriverConnection.h @@ -9,7 +9,6 @@ #pragma once #include -#include #include #include #include @@ -28,6 +27,8 @@ namespace WebContent { +class ElementLocator; + class WebDriverConnection final : public IPC::ConnectionToServer { C_OBJECT_ABSTRACT(WebDriverConnection) @@ -133,8 +134,9 @@ private: Gfx::IntPoint calculate_absolute_position_of_element(JS::NonnullGCPtr rect); Gfx::IntRect calculate_absolute_rect_of_element(Web::DOM::Element const& element); - using StartNodeGetter = Function, Web::WebDriver::Error>()>; - ErrorOr find(StartNodeGetter&& start_node_getter, Web::WebDriver::LocationStrategy using_, StringView value); + using GetStartNode = JS::NonnullGCPtr, Web::WebDriver::Error>()>>; + using OnFindComplete = JS::NonnullGCPtr>; + void find(Web::WebDriver::LocationStrategy, ByteString, GetStartNode, OnFindComplete); struct ScriptArguments { ByteString script; @@ -166,6 +168,9 @@ private: size_t m_pending_window_rect_requests { 0 }; + friend class ElementLocator; + JS::GCPtr m_element_locator; + JS::GCPtr m_action_executor; JS::GCPtr m_document_observer; diff --git a/Userland/Services/WebContent/WebDriverServer.ipc b/Userland/Services/WebContent/WebDriverServer.ipc index f376b375493..96ae5de461b 100644 --- a/Userland/Services/WebContent/WebDriverServer.ipc +++ b/Userland/Services/WebContent/WebDriverServer.ipc @@ -3,6 +3,7 @@ endpoint WebDriverServer { navigation_complete(Web::WebDriver::Response response) =| window_rect_updated(Web::WebDriver::Response response) =| + find_elements_complete(Web::WebDriver::Response response) =| script_executed(Web::WebDriver::Response response) =| actions_performed(Web::WebDriver::Response response) =| dialog_closed(Web::WebDriver::Response response) =| diff --git a/Userland/Services/WebDriver/Client.cpp b/Userland/Services/WebDriver/Client.cpp index c050a205e83..f6fe03bfacd 100644 --- a/Userland/Services/WebDriver/Client.cpp +++ b/Userland/Services/WebDriver/Client.cpp @@ -442,7 +442,7 @@ Web::WebDriver::Response Client::find_element(Web::WebDriver::Parameters paramet { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//element"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().find_element(payload); + return session->find_element(payload); } // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements @@ -451,7 +451,7 @@ Web::WebDriver::Response Client::find_elements(Web::WebDriver::Parameters parame { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//elements"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().find_elements(payload); + return session->find_elements(payload); } // 12.3.4 Find Element From Element, https://w3c.github.io/webdriver/#dfn-find-element-from-element @@ -460,7 +460,7 @@ Web::WebDriver::Response Client::find_element_from_element(Web::WebDriver::Param { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//element//element"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().find_element_from_element(payload, move(parameters[1])); + return session->find_element_from_element(move(parameters[1]), move(payload)); } // 12.3.5 Find Elements From Element, https://w3c.github.io/webdriver/#dfn-find-elements-from-element @@ -469,7 +469,7 @@ Web::WebDriver::Response Client::find_elements_from_element(Web::WebDriver::Para { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//element//elements"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().find_elements_from_element(payload, move(parameters[1])); + return session->find_elements_from_element(move(parameters[1]), move(payload)); } // 12.3.6 Find Element From Shadow Root, https://w3c.github.io/webdriver/#find-element-from-shadow-root @@ -478,7 +478,7 @@ Web::WebDriver::Response Client::find_element_from_shadow_root(Web::WebDriver::P { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//shadow//element"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().find_element_from_shadow_root(payload, move(parameters[1])); + return session->find_element_from_shadow_root(move(parameters[1]), move(payload)); } // 12.3.7 Find Elements From Shadow Root, https://w3c.github.io/webdriver/#find-elements-from-shadow-root @@ -487,7 +487,7 @@ Web::WebDriver::Response Client::find_elements_from_shadow_root(Web::WebDriver:: { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//shadow//elements"); auto session = TRY(find_session_with_id(parameters[0])); - return session->web_content_connection().find_elements_from_shadow_root(payload, move(parameters[1])); + return session->find_elements_from_shadow_root(move(parameters[1]), move(payload)); } // 12.3.8 Get Active Element, https://w3c.github.io/webdriver/#get-active-element diff --git a/Userland/Services/WebDriver/Session.cpp b/Userland/Services/WebDriver/Session.cpp index 9e36d5ca988..35a612e57a4 100644 --- a/Userland/Services/WebDriver/Session.cpp +++ b/Userland/Services/WebDriver/Session.cpp @@ -237,6 +237,48 @@ Web::WebDriver::Response Session::fullscreen_window() const }); } +Web::WebDriver::Response Session::find_element(JsonValue payload) const +{ + return perform_async_action(web_content_connection().on_find_elements_complete, [&]() { + return web_content_connection().find_element(move(payload)); + }); +} + +Web::WebDriver::Response Session::find_elements(JsonValue payload) const +{ + return perform_async_action(web_content_connection().on_find_elements_complete, [&]() { + return web_content_connection().find_elements(move(payload)); + }); +} + +Web::WebDriver::Response Session::find_element_from_element(String element_id, JsonValue payload) const +{ + return perform_async_action(web_content_connection().on_find_elements_complete, [&]() { + return web_content_connection().find_element_from_element(move(payload), move(element_id)); + }); +} + +Web::WebDriver::Response Session::find_elements_from_element(String element_id, JsonValue payload) const +{ + return perform_async_action(web_content_connection().on_find_elements_complete, [&]() { + return web_content_connection().find_elements_from_element(move(payload), move(element_id)); + }); +} + +Web::WebDriver::Response Session::find_element_from_shadow_root(String shadow_id, JsonValue payload) const +{ + return perform_async_action(web_content_connection().on_find_elements_complete, [&]() { + return web_content_connection().find_element_from_shadow_root(move(payload), move(shadow_id)); + }); +} + +Web::WebDriver::Response Session::find_elements_from_shadow_root(String shadow_id, JsonValue payload) const +{ + return perform_async_action(web_content_connection().on_find_elements_complete, [&]() { + return web_content_connection().find_elements_from_shadow_root(move(payload), move(shadow_id)); + }); +} + Web::WebDriver::Response Session::execute_script(JsonValue payload, ScriptMode mode) const { return perform_async_action(web_content_connection().on_script_executed, [&]() { diff --git a/Userland/Services/WebDriver/Session.h b/Userland/Services/WebDriver/Session.h index 17f926ca98d..06f234cffab 100644 --- a/Userland/Services/WebDriver/Session.h +++ b/Userland/Services/WebDriver/Session.h @@ -70,6 +70,13 @@ public: Web::WebDriver::Response minimize_window() const; Web::WebDriver::Response fullscreen_window() const; + Web::WebDriver::Response find_element(JsonValue) const; + Web::WebDriver::Response find_elements(JsonValue) const; + Web::WebDriver::Response find_element_from_element(String, JsonValue) const; + Web::WebDriver::Response find_elements_from_element(String, JsonValue) const; + Web::WebDriver::Response find_element_from_shadow_root(String, JsonValue) const; + Web::WebDriver::Response find_elements_from_shadow_root(String, JsonValue) const; + Web::WebDriver::Response element_click(String) const; Web::WebDriver::Response element_send_keys(String, JsonValue) const; Web::WebDriver::Response perform_actions(JsonValue) const; diff --git a/Userland/Services/WebDriver/WebContentConnection.cpp b/Userland/Services/WebDriver/WebContentConnection.cpp index 58819785130..0d7a7686420 100644 --- a/Userland/Services/WebDriver/WebContentConnection.cpp +++ b/Userland/Services/WebDriver/WebContentConnection.cpp @@ -32,6 +32,12 @@ void WebContentConnection::window_rect_updated(Web::WebDriver::Response const& r on_window_rect_updated(response); } +void WebContentConnection::find_elements_complete(Web::WebDriver::Response const& response) +{ + if (on_find_elements_complete) + on_find_elements_complete(response); +} + void WebContentConnection::script_executed(Web::WebDriver::Response const& response) { if (on_script_executed) diff --git a/Userland/Services/WebDriver/WebContentConnection.h b/Userland/Services/WebDriver/WebContentConnection.h index 39a69478174..0fc6f123d65 100644 --- a/Userland/Services/WebDriver/WebContentConnection.h +++ b/Userland/Services/WebDriver/WebContentConnection.h @@ -24,6 +24,7 @@ public: Function on_close; Function on_navigation_complete; Function on_window_rect_updated; + Function on_find_elements_complete; Function on_script_executed; Function on_actions_performed; Function on_dialog_closed; @@ -33,6 +34,7 @@ private: virtual void navigation_complete(Web::WebDriver::Response const&) override; virtual void window_rect_updated(Web::WebDriver::Response const&) override; + virtual void find_elements_complete(Web::WebDriver::Response const&) override; virtual void script_executed(Web::WebDriver::Response const&) override; virtual void actions_performed(Web::WebDriver::Response const&) override; virtual void dialog_closed(Web::WebDriver::Response const&) override;