LibWeb+WebContent+WebDriver: Implement the Perform Actions endpoint

Similar to script execution, this spins the WebDriver process until the
action is complete (rather than spinning the WebContent process, which
we've seen result in deadlocks).
This commit is contained in:
Timothy Flynn 2024-09-29 09:40:17 -04:00 committed by Andreas Kling
parent 8000837f78
commit 709deeb482
Notes: github-actions[bot] 2024-10-01 09:03:18 +00:00
10 changed files with 83 additions and 16 deletions

View file

@ -77,6 +77,18 @@ bool represents_a_web_element(JsonValue const& value)
return value.as_object().has_string(web_element_identifier);
}
// https://w3c.github.io/webdriver/#dfn-get-a-webelement-origin
ErrorOr<JS::NonnullGCPtr<Web::DOM::Element>, Web::WebDriver::Error> get_web_element_origin(StringView origin)
{
// 1. Assert: browsing context is the current browsing context.
// 2. Let element be equal to the result of trying to get a known element with session and origin.
auto* element = TRY(get_known_connected_element(origin));
// 3. Return success with data element.
return *element;
}
// https://w3c.github.io/webdriver/#dfn-get-a-known-element
ErrorOr<Web::DOM::Element*, Web::WebDriver::Error> get_known_connected_element(StringView element_id)
{

View file

@ -21,6 +21,7 @@ JsonObject web_element_reference_object(Web::DOM::Node const& element);
ErrorOr<JS::NonnullGCPtr<Web::DOM::Element>, WebDriver::Error> deserialize_web_element(JsonObject const&);
ByteString extract_web_element_reference(JsonObject const&);
bool represents_a_web_element(JsonValue const& value);
ErrorOr<JS::NonnullGCPtr<Web::DOM::Element>, Web::WebDriver::Error> get_web_element_origin(StringView origin);
ErrorOr<Web::DOM::Element*, Web::WebDriver::Error> get_known_connected_element(StringView element_id);
bool is_element_stale(Web::DOM::Node const& element);

View file

@ -44,8 +44,10 @@
#include <LibWeb/Platform/Timer.h>
#include <LibWeb/UIEvents/EventNames.h>
#include <LibWeb/UIEvents/MouseEvent.h>
#include <LibWeb/WebDriver/Actions.h>
#include <LibWeb/WebDriver/ElementReference.h>
#include <LibWeb/WebDriver/ExecuteScript.h>
#include <LibWeb/WebDriver/InputState.h>
#include <LibWeb/WebDriver/Properties.h>
#include <LibWeb/WebDriver/Screenshot.h>
#include <WebContent/WebDriverConnection.h>
@ -1887,17 +1889,38 @@ Messages::WebDriverClient::DeleteAllCookiesResponse WebDriverConnection::delete_
// 15.7 Perform Actions, https://w3c.github.io/webdriver/#perform-actions
Messages::WebDriverClient::PerformActionsResponse WebDriverConnection::perform_actions(JsonValue const& payload)
{
dbgln("FIXME: WebDriverConnection::perform_actions({})", payload);
// 4. If session's current browsing context is no longer open, return error with error code no such window.
// NOTE: We do this first so we can assume the current top-level browsing context below is non-null.
TRY(ensure_current_browsing_context_is_open());
// FIXME: 1. Let input state be the result of get the input state with session and session's 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 actions by tick be the result of trying to extract an action sequence with input state, parameters, and actions options.
// FIXME: 4. If session's current browsing context is no longer open, return error with error code no such window.
// FIXME: 5. Try to handle any user prompts with session.
// FIXME: 6. Dispatch actions with input state, actions by tick, current browsing context, and actions options. If this results in an error return that error.
// FIXME: 7. Return success with data null.
// 1. Let input state be the result of get the input state with session and session's current top-level browsing context.
auto& input_state = Web::WebDriver::get_input_state(*current_top_level_browsing_context());
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "perform actions not implemented"sv);
// 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 actions by tick be the result of trying to extract an action sequence with input state, parameters, and
// actions options.
auto actions_by_tick = TRY(Web::WebDriver::extract_an_action_sequence(input_state, payload, actions_options));
// 5. Try to handle any user prompts with session.
TRY(handle_any_user_prompts());
// 6. Dispatch actions with input state, actions by tick, current browsing context, and actions options. If this
// results in an error return that error.
auto on_complete = JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
m_action_executor = nullptr;
async_actions_performed(move(result));
});
m_action_executor = Web::WebDriver::dispatch_actions(input_state, move(actions_by_tick), current_browsing_context(), move(actions_options), on_complete);
// 7. Return success with data null.
return JsonValue {};
}
// 15.8 Release Actions, https://w3c.github.io/webdriver/#release-actions

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -145,6 +145,8 @@ private:
// https://w3c.github.io/webdriver/#dfn-current-browsing-context
JS::Handle<Web::HTML::BrowsingContext> m_current_browsing_context;
JS::Handle<JS::Cell> m_action_executor;
};
}

View file

@ -2,4 +2,5 @@
endpoint WebDriverServer {
script_executed(Web::WebDriver::Response response) =|
actions_performed(Web::WebDriver::Response response) =|
}

View file

@ -3,7 +3,7 @@
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -687,7 +687,7 @@ Web::WebDriver::Response Client::perform_actions(Web::WebDriver::Parameters para
{
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/actions");
auto session = TRY(find_session_with_id(parameters[0]));
return session->web_content_connection().perform_actions(move(payload));
return session->perform_actions(move(payload));
}
// 15.8 Release Actions, https://w3c.github.io/webdriver/#release-actions

View file

@ -3,7 +3,7 @@
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -201,4 +201,22 @@ Web::WebDriver::Response Session::execute_script(JsonValue payload, ScriptMode m
return response.release_value();
}
Web::WebDriver::Response Session::perform_actions(JsonValue payload) 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().perform_actions(move(payload)));
Core::EventLoop::current().spin_until([&]() {
return response.has_value();
});
return response.release_value();
}
}

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -61,6 +61,8 @@ public:
};
Web::WebDriver::Response execute_script(JsonValue, ScriptMode) const;
Web::WebDriver::Response perform_actions(JsonValue) const;
private:
using ServerPromise = Core::Promise<ErrorOr<void>>;
ErrorOr<NonnullRefPtr<Core::LocalServer>> create_server(NonnullRefPtr<ServerPromise> promise);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -26,4 +26,10 @@ void WebContentConnection::script_executed(Web::WebDriver::Response const& respo
on_script_executed(response);
}
void WebContentConnection::actions_performed(Web::WebDriver::Response const& response)
{
if (on_actions_performed)
on_actions_performed(response);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -22,11 +22,13 @@ public:
Function<void()> on_close;
Function<void(Web::WebDriver::Response)> on_script_executed;
Function<void(Web::WebDriver::Response)> on_actions_performed;
private:
virtual void die() override;
virtual void script_executed(Web::WebDriver::Response const&) override;
virtual void actions_performed(Web::WebDriver::Response const&) override;
};
}