From f64ba82944cc30ab146796982cfd165ba91ca7f1 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 27 Jan 2023 13:25:34 +0000 Subject: [PATCH] WebContent: Bring existing WebDriver element-click code closer to spec The main change here is to implement and use the "container for element" algorithm. But also, adjust the errors we return. Errors thrown by `scroll_element_into_view()` are not related to the scrolling itself, so should not claim to be. `UnsupportedOperation` is more accurate than `InvalidArgument` when we're expressing that the operation isn't fully implemented. --- .../WebContent/WebDriverConnection.cpp | 61 +++++++++++++++++-- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/Userland/Services/WebContent/WebDriverConnection.cpp b/Userland/Services/WebContent/WebDriverConnection.cpp index a4e411decd..21abc311af 100644 --- a/Userland/Services/WebContent/WebDriverConnection.cpp +++ b/Userland/Services/WebContent/WebDriverConnection.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, Florent Castelli - * Copyright (c) 2022, Sam Atkins + * Copyright (c) 2022-2023, Sam Atkins * Copyright (c) 2022, Tobias Christiansen * Copyright (c) 2022, Linus Groh * Copyright (c) 2022, Tim Flynn @@ -20,13 +20,18 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include +#include #include +#include #include #include #include @@ -253,6 +258,49 @@ static ErrorOr get_property(JsonValue const } } +// https://w3c.github.io/webdriver/#dfn-container +static Optional container_for_element(Web::DOM::Element& element) +{ + auto first_element_reached_by_traversing_the_tree_in_reverse_order = [](Web::DOM::Element& element, auto filter) -> Optional { + auto node_iterator = element.document().create_node_iterator(element, to_underlying(Web::DOM::NodeFilter::WhatToShow::SHOW_ALL), nullptr); + + auto current_node = node_iterator->previous_node(); + while (current_node.has_value() && current_node.value() != nullptr && current_node.value()->is_element()) { + if (filter(current_node.value())) + return static_cast(*current_node.release_value()); + } + + return {}; + }; + + // An element’s container is: + // -> option element in a valid element context + // -> optgroup element in a valid element context + // FIXME: Determine if the element is in a valid element context. (https://html.spec.whatwg.org/#concept-element-contexts) + if (is(element) || is(element)) { + // The element’s element context, which is determined by: + // 1. Let datalist parent be the first datalist element reached by traversing the tree in reverse order from element, or undefined if the root of the tree is reached. + auto datalist_parent = first_element_reached_by_traversing_the_tree_in_reverse_order(element, [](auto& node) { return is(*node); }); + + // 2. Let select parent be the first select element reached by traversing the tree in reverse order from element, or undefined if the root of the tree is reached. + auto select_parent = first_element_reached_by_traversing_the_tree_in_reverse_order(element, [](auto& node) { return is(*node); }); + + // 3. If datalist parent is undefined, the element context is select parent. Otherwise, the element context is datalist parent. + if (!datalist_parent.has_value()) + return select_parent; + return datalist_parent; + } + // -> option element in an invalid element context + else if (is(element)) { + // The element does not have a container. + return {}; + } + // -> Otherwise + else { + // The container is the element itself. + return element; + } +} ErrorOr> WebDriverConnection::connect(Web::PageClient& page_client, DeprecatedString const& webdriver_ipc_path) { dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path); @@ -1214,11 +1262,12 @@ Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_cli } // 5. Scroll into view the element’s container. - auto scroll_or_error = scroll_element_into_view(*element); - - // 6. If element’s container is still not in view, return error with error code element not interactable. + auto element_container = container_for_element(*element); + auto scroll_or_error = scroll_element_into_view(*element_container); if (scroll_or_error.is_error()) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Element’s container is still not in view after scrolling"sv); + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnknownError, scroll_or_error.error().string_literal()); + + // FIXME: 6. If element’s container is still not in view, return error with error code element not interactable. // FIXME: 7. If element’s container is obscured by another element, return error with error code element click intercepted. @@ -1232,7 +1281,7 @@ Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_cli // FIXME: 12. Try to run the post-navigation checks. // FIXME: 13. Return success with data null. - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Click not implemented"sv); + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Click not implemented"sv); } // 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source