mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 15:40:19 +00:00
LibWeb+WebDriver+WebContent: Implement the Element Click endpoint
This commit is contained in:
parent
95bf6c9877
commit
5aa50bff8b
Notes:
github-actions[bot]
2024-10-06 00:43:52 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/5aa50bff8b2 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1644 Reviewed-by: https://github.com/shannonbooth ✅
8 changed files with 146 additions and 26 deletions
|
@ -1014,4 +1014,16 @@ ErrorOr<void, WebDriver::Error> dispatch_tick_actions(InputState& input_state, R
|
|||
return {};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-dispatch-a-list-of-actions
|
||||
JS::NonnullGCPtr<JS::Cell> dispatch_list_of_actions(InputState& input_state, Vector<ActionObject> actions, HTML::BrowsingContext& browsing_context, ActionsOptions actions_options, OnActionsComplete on_complete)
|
||||
{
|
||||
// 1. Let tick actions be the list «actions»
|
||||
// 2. Let actions by tick be the list «tick actions».
|
||||
Vector<Vector<ActionObject>> actions_by_tick;
|
||||
actions_by_tick.append(move(actions));
|
||||
|
||||
// 3. Return the result of dispatch actions with input state, actions by tick, browsing context, and actions options.
|
||||
return dispatch_actions(input_state, move(actions_by_tick), browsing_context, move(actions_options), on_complete);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -128,5 +128,6 @@ ErrorOr<Vector<Vector<ActionObject>>, WebDriver::Error> extract_an_action_sequen
|
|||
|
||||
JS::NonnullGCPtr<JS::Cell> dispatch_actions(InputState&, Vector<Vector<ActionObject>>, HTML::BrowsingContext&, ActionsOptions, OnActionsComplete);
|
||||
ErrorOr<void, WebDriver::Error> dispatch_tick_actions(InputState&, ReadonlySpan<ActionObject>, AK::Duration, HTML::BrowsingContext&, ActionsOptions const&);
|
||||
JS::NonnullGCPtr<JS::Cell> dispatch_list_of_actions(InputState&, Vector<ActionObject>, HTML::BrowsingContext&, ActionsOptions, OnActionsComplete);
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/WebDriver/Actions.h>
|
||||
#include <LibWeb/WebDriver/InputSource.h>
|
||||
#include <LibWeb/WebDriver/InputState.h>
|
||||
|
||||
|
@ -128,6 +129,32 @@ InputSource create_input_source(InputState const& input_state, InputSourceType t
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-remove-an-input-source
|
||||
void add_input_source(InputState& input_state, String id, InputSource source)
|
||||
{
|
||||
// 1. Let input state map be input state's input state map.
|
||||
// 2. Set input state map[input id] to source.
|
||||
input_state.input_state_map.set(move(id), move(source));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-remove-an-input-source
|
||||
void remove_input_source(InputState& input_state, StringView id)
|
||||
{
|
||||
// 1. Assert: None of the items in input state's input cancel list has id equal to input id.
|
||||
// FIXME: Spec issue: This assertion cannot be correct. For example, when Element Click is executed, the initial
|
||||
// pointer down action will append a pointer up action to the input cancel list, and the input cancel list
|
||||
// is never subsequently cleared. So instead of performing this assertion, we remove any action from the
|
||||
// input cancel list with the provided input ID.
|
||||
// https://github.com/w3c/webdriver/issues/1809
|
||||
input_state.input_cancel_list.remove_all_matching([&](ActionObject const& action) {
|
||||
return action.id == id;
|
||||
});
|
||||
|
||||
// 2. Let input state map be input state's input state map.
|
||||
// 3. Remove input state map[input id].
|
||||
input_state.input_state_map.remove(id);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-get-an-input-source
|
||||
Optional<InputSource&> get_input_source(InputState& input_state, StringView id)
|
||||
{
|
||||
|
|
|
@ -77,6 +77,8 @@ Optional<InputSourceType> input_source_type_from_string(StringView);
|
|||
Optional<PointerInputSource::Subtype> pointer_input_source_subtype_from_string(StringView);
|
||||
|
||||
InputSource create_input_source(InputState const&, InputSourceType, Optional<PointerInputSource::Subtype>);
|
||||
void add_input_source(InputState&, String id, InputSource);
|
||||
void remove_input_source(InputState&, StringView id);
|
||||
Optional<InputSource&> get_input_source(InputState&, StringView id);
|
||||
ErrorOr<InputSource*, WebDriver::Error> get_or_create_input_source(InputState&, InputSourceType, StringView id, Optional<PointerInputSource::Subtype>);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <LibWeb/CSS/StyleProperties.h>
|
||||
#include <LibWeb/Cookie/Cookie.h>
|
||||
#include <LibWeb/Cookie/ParsedCookie.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
|
@ -1318,6 +1319,24 @@ Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_cli
|
|||
// 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.
|
||||
|
||||
auto on_complete = JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
|
||||
// 9. Wait until the user agent event loop has spun enough times to process the DOM events generated by the
|
||||
// previous step.
|
||||
m_action_executor = nullptr;
|
||||
|
||||
// FIXME: 10. Perform implementation-defined steps to allow any navigations triggered by the click to start.
|
||||
|
||||
// 11. Try to wait for navigation to complete.
|
||||
if (auto navigation_result = wait_for_navigation_to_complete(); navigation_result.is_error()) {
|
||||
async_actions_performed(navigation_result.release_error());
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: 12. Try to run the post-navigation checks.
|
||||
|
||||
async_actions_performed(move(result));
|
||||
});
|
||||
|
||||
// 8. Matching on element:
|
||||
// -> option element
|
||||
if (is<Web::HTML::HTMLOptionElement>(*element)) {
|
||||
|
@ -1361,42 +1380,82 @@ Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_cli
|
|||
fire_an_event<Web::DOM::Event>(Web::HTML::EventNames::change, parent_node);
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Fire a mouseUp event at parent node.
|
||||
fire_an_event<Web::UIEvents::MouseEvent>(Web::UIEvents::EventNames::mouseup, parent_node);
|
||||
|
||||
// 8. Fire a click event at parent node.
|
||||
fire_an_event<Web::UIEvents::MouseEvent>(Web::UIEvents::EventNames::click, parent_node);
|
||||
|
||||
Web::HTML::queue_a_task(Web::HTML::Task::Source::Unspecified, nullptr, nullptr, JS::create_heap_function(current_browsing_context().heap(), [on_complete]() {
|
||||
on_complete->function()(JsonValue {});
|
||||
}));
|
||||
}
|
||||
// -> Otherwise
|
||||
else {
|
||||
dbgln("FIXME: WebDriverConnection::element_click({})", element->class_name());
|
||||
// 1. Let input state be the result of get the input state given current session and current top-level
|
||||
// browsing context.
|
||||
auto& input_state = Web::WebDriver::get_input_state(*current_top_level_browsing_context());
|
||||
|
||||
// FIXME: 1. Let input state be the result of get the input state given current session and current top-level browsing context.
|
||||
// FIXME: 2. Let actions options be a new actions options with the is element origin steps set to represents a web element, and the get element origin steps set to get a WebElement origin.
|
||||
// FIXME: 3. Let input id be a the result of generating a UUID.
|
||||
// FIXME: 4. Let source be the result of create an input source with input state, and "pointer".
|
||||
// FIXME: 5. Add an input source with input state, input id and source.
|
||||
// FIXME: 6. Let click point be the element’s in-view center point.
|
||||
// FIXME: 7. Let pointer move action be an action object constructed with arguments input id, "pointer", and "pointerMove".
|
||||
// FIXME: 8. Set a property x to 0 on pointer move action.
|
||||
// FIXME: 9. Set a property y to 0 on pointer move action.
|
||||
// FIXME: 10. Set a property origin to element on pointer move action.
|
||||
// FIXME: 11. Let pointer down action be an action object constructed with arguments input id, "pointer", and "pointerDown".
|
||||
// FIXME: 12. Set a property button to 0 on pointer down action.
|
||||
// FIXME: 13. Let pointer up action be an action object constructed with arguments input id, "mouse", and "pointerUp" as arguments.
|
||||
// FIXME: 14. Set a property button to 0 on pointer up action.
|
||||
// FIXME: 15. Let actions be the list «pointer move action, pointer down action, pointer move action».
|
||||
// FIXME: 16. Dispatch a list of actions with input state, actions, current browsing context, and actions options.
|
||||
// FIXME: 17. Remove an input source with input state and input id.
|
||||
// 2. Let actions options be a new actions options with the is element origin steps set to represents a web
|
||||
// element, and the get element origin steps set to get a WebElement origin.
|
||||
Web::WebDriver::ActionsOptions actions_options {
|
||||
.is_element_origin = &Web::WebDriver::represents_a_web_element,
|
||||
.get_element_origin = &Web::WebDriver::get_web_element_origin,
|
||||
};
|
||||
|
||||
// 3. Let input id be a the result of generating a UUID.
|
||||
auto input_id = MUST(Web::Crypto::generate_random_uuid());
|
||||
|
||||
// 4. Let source be the result of create an input source with input state, and "pointer".
|
||||
auto source = Web::WebDriver::create_input_source(input_state, Web::WebDriver::InputSourceType::Pointer, Web::WebDriver::PointerInputSource::Subtype::Mouse);
|
||||
|
||||
// 5. Add an input source with input state, input id and source.
|
||||
Web::WebDriver::add_input_source(input_state, input_id, move(source));
|
||||
|
||||
// 6. Let click point be the element’s in-view center point.
|
||||
// FIXME: Spec-issue: This parameter is unused. Note that it would not correct to set the mouse move action
|
||||
// position to this click point. The [0,0] specified below is ultimately interpreted as an offset from
|
||||
// the element's center position.
|
||||
// https://github.com/w3c/webdriver/issues/1563
|
||||
|
||||
// 7. Let pointer move action be an action object constructed with arguments input id, "pointer", and "pointerMove".
|
||||
Web::WebDriver::ActionObject pointer_move_action { input_id, Web::WebDriver::InputSourceType::Pointer, Web::WebDriver::ActionObject::Subtype::PointerMove };
|
||||
|
||||
// 8. Set a property x to 0 on pointer move action.
|
||||
// 9. Set a property y to 0 on pointer move action.
|
||||
pointer_move_action.pointer_move_fields().position = { 0, 0 };
|
||||
|
||||
// 10. Set a property origin to element on pointer move action.
|
||||
auto origin = Web::WebDriver::get_or_create_a_web_element_reference(*element);
|
||||
pointer_move_action.pointer_move_fields().origin = MUST(String::from_byte_string(origin));
|
||||
|
||||
// 11. Let pointer down action be an action object constructed with arguments input id, "pointer", and "pointerDown".
|
||||
Web::WebDriver::ActionObject pointer_down_action { input_id, Web::WebDriver::InputSourceType::Pointer, Web::WebDriver::ActionObject::Subtype::PointerDown };
|
||||
|
||||
// 12. Set a property button to 0 on pointer down action.
|
||||
pointer_down_action.pointer_up_down_fields().button = Web::UIEvents::button_code_to_mouse_button(0);
|
||||
|
||||
// 13. Let pointer up action be an action object constructed with arguments input id, "pointer", and "pointerUp" as arguments.
|
||||
Web::WebDriver::ActionObject pointer_up_action { input_id, Web::WebDriver::InputSourceType::Pointer, Web::WebDriver::ActionObject::Subtype::PointerUp };
|
||||
|
||||
// 14. Set a property button to 0 on pointer up action.
|
||||
pointer_up_action.pointer_up_down_fields().button = Web::UIEvents::button_code_to_mouse_button(0);
|
||||
|
||||
// 15. Let actions be the list «pointer move action, pointer down action, pointer up action».
|
||||
Vector actions { move(pointer_move_action), move(pointer_down_action), move(pointer_up_action) };
|
||||
|
||||
// 16. Dispatch a list of actions with input state, actions, current browsing context, and actions options.
|
||||
m_action_executor = Web::WebDriver::dispatch_list_of_actions(input_state, move(actions), current_browsing_context(), move(actions_options), JS::create_heap_function(current_browsing_context().heap(), [on_complete, &input_state, input_id = move(input_id)](Web::WebDriver::Response result) {
|
||||
// 17. Remove an input source with input state and input id.
|
||||
Web::WebDriver::remove_input_source(input_state, input_id);
|
||||
|
||||
on_complete->function()(move(result));
|
||||
}));
|
||||
}
|
||||
|
||||
// FIXME: 9. Wait until the user agent event loop has spun enough times to process the DOM events generated by the previous step.
|
||||
// FIXME: 10. Perform implementation-defined steps to allow any navigations triggered by the click to start.
|
||||
// FIXME: 11. Try to wait for navigation to complete.
|
||||
// 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::UnsupportedOperation, "Click not implemented"sv);
|
||||
// 13. Return success with data null.
|
||||
return JsonValue {};
|
||||
}
|
||||
|
||||
// 12.5.2 Element Clear, https://w3c.github.io/webdriver/#dfn-element-clear
|
||||
|
|
|
@ -588,7 +588,7 @@ Web::WebDriver::Response Client::element_click(Web::WebDriver::Parameters parame
|
|||
{
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element/<element_id>/click");
|
||||
auto session = TRY(find_session_with_id(parameters[0]));
|
||||
return session->web_content_connection().element_click(move(parameters[1]));
|
||||
return session->element_click(move(parameters[1]));
|
||||
}
|
||||
|
||||
// 12.5.2 Element Clear, https://w3c.github.io/webdriver/#dfn-element-clear
|
||||
|
|
|
@ -201,6 +201,24 @@ Web::WebDriver::Response Session::execute_script(JsonValue payload, ScriptMode m
|
|||
return response.release_value();
|
||||
}
|
||||
|
||||
Web::WebDriver::Response Session::element_click(String element_id) const
|
||||
{
|
||||
ScopeGuard guard { [&]() { web_content_connection().on_actions_performed = nullptr; } };
|
||||
|
||||
Optional<Web::WebDriver::Response> response;
|
||||
web_content_connection().on_actions_performed = [&](auto result) {
|
||||
response = move(result);
|
||||
};
|
||||
|
||||
TRY(web_content_connection().element_click(move(element_id)));
|
||||
|
||||
Core::EventLoop::current().spin_until([&]() {
|
||||
return response.has_value();
|
||||
});
|
||||
|
||||
return response.release_value();
|
||||
}
|
||||
|
||||
Web::WebDriver::Response Session::perform_actions(JsonValue payload) const
|
||||
{
|
||||
ScopeGuard guard { [&]() { web_content_connection().on_actions_performed = nullptr; } };
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
};
|
||||
Web::WebDriver::Response execute_script(JsonValue, ScriptMode) const;
|
||||
|
||||
Web::WebDriver::Response element_click(String) const;
|
||||
Web::WebDriver::Response perform_actions(JsonValue) const;
|
||||
|
||||
private:
|
||||
|
|
Loading…
Reference in a new issue