mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 11:49:44 +00:00
LibWeb+WebContent+WebDriver: Bring session start and close up to spec
Lots of editorial spec bugs here, but these changes largely affect how the unhandledPromptBehavior capability is handled. We also now set an additional capability for the default User Agent string.
This commit is contained in:
parent
2583996e18
commit
ee649fc13b
Notes:
github-actions[bot]
2025-02-06 14:02:15 +00:00
Author: https://github.com/trflynn89
Commit: ee649fc13b
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3471
Reviewed-by: https://github.com/AtkinsSJ
9 changed files with 290 additions and 131 deletions
|
@ -263,7 +263,7 @@ static bool matches_platform_name(StringView requested_platform_name, StringView
|
|||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-matching-capabilities
|
||||
static JsonValue match_capabilities(JsonObject const& capabilities)
|
||||
static JsonValue match_capabilities(JsonObject const& capabilities, ReadonlySpan<StringView> flags)
|
||||
{
|
||||
static auto browser_name = StringView { BROWSER_NAME, strlen(BROWSER_NAME) }.to_lowercase_string();
|
||||
static auto platform_name = StringView { OS_STRING, strlen(OS_STRING) }.to_lowercase_string();
|
||||
|
@ -284,15 +284,26 @@ static JsonValue match_capabilities(JsonObject const& capabilities)
|
|||
matched_capabilities.set("acceptInsecureCerts"sv, false);
|
||||
// "strictFileInteractability"
|
||||
// Boolean initially set to false, indicating that interactability checks will be applied to <input type=file>.
|
||||
matched_capabilities.set("strictFileInteractability"sv, false);
|
||||
// FIXME: Spec issue: This item likely should have been removed in lieu of step 2.
|
||||
// https://github.com/w3c/webdriver/issues/1879
|
||||
// "setWindowRect"
|
||||
// Boolean indicating whether the remote end supports all of the resizing and positioning commands.
|
||||
matched_capabilities.set("setWindowRect"sv, true);
|
||||
// "userAgent"
|
||||
// String containing the default User-Agent value.
|
||||
matched_capabilities.set("userAgent"sv, Web::default_user_agent);
|
||||
|
||||
// 2. Optionally add extension capabilities as entries to matched capabilities. The values of these may be elided, and there is no requirement that all extension capabilities be added.
|
||||
// 2. If flags contains "http", add the following entries to matched capabilities:
|
||||
if (flags.contains_slow("http"sv)) {
|
||||
// "strictFileInteractability"
|
||||
// Boolean initially set to false, indicating that interactabilty checks will be applied to <input type=file>.
|
||||
matched_capabilities.set("strictFileInteractability"sv, false);
|
||||
}
|
||||
|
||||
// 3. Optionally add extension capabilities as entries to matched capabilities. The values of these may be elided, and there is no requirement that all extension capabilities be added.
|
||||
matched_capabilities.set("serenity:ladybird"sv, default_ladybird_options());
|
||||
|
||||
// 3. For each name and value corresponding to capability’s own properties:
|
||||
// 3. For each name and value corresponding to capabilities's own properties:
|
||||
auto result = capabilities.try_for_each_member([&](auto const& name, auto const& value) -> ErrorOr<void> {
|
||||
// a. Let match value equal value.
|
||||
|
||||
|
@ -318,17 +329,23 @@ static JsonValue match_capabilities(JsonObject const& capabilities)
|
|||
}
|
||||
// -> "acceptInsecureCerts"
|
||||
else if (name == "acceptInsecureCerts"sv) {
|
||||
// If value is true and the endpoint node does not support insecure TLS certificates, return success with data null.
|
||||
// If accept insecure TLS flag is set and not equal to value, return success with data null.
|
||||
if (value.as_bool())
|
||||
return AK::Error::from_string_literal("acceptInsecureCerts");
|
||||
}
|
||||
// -> "proxy"
|
||||
else if (name == "proxy"sv) {
|
||||
// FIXME: If the endpoint node does not allow the proxy it uses to be configured, or if the proxy configuration defined in value is not one that passes the endpoint node’s implementation-specific validity checks, return success with data null.
|
||||
// FIXME: If the has proxy configuration flag is set, or if the proxy configuration defined in value is not one that passes the endpoint node's implementation-specific validity checks, return success with data null.
|
||||
}
|
||||
// -> "unhandledPromptBehavior"
|
||||
else if (name == "unhandledPromptBehavior"sv) {
|
||||
// If check user prompt handler matches with value is false, return success with data null.
|
||||
if (!check_user_prompt_handler_matches(value.as_object()))
|
||||
return AK::Error::from_string_literal("unhandledPromptBehavior");
|
||||
}
|
||||
// -> Otherwise
|
||||
else {
|
||||
// FIXME: If name is the name of an additional WebDriver capability which defines a matched capability serialization algorithm, let match value be the result of running the matched capability serialization algorithm for capability name with argument value.
|
||||
// FIXME: If name is the name of an additional WebDriver capability which defines a matched capability serialization algorithm, let match value be the result of running the matched capability serialization algorithm for capability name with arguments value, and flags.
|
||||
// FIXME: Otherwise, if name is the key of an extension capability, let match value be the result of trying implementation-specific steps to match on name with value. If the match is not successful, return success with data null.
|
||||
|
||||
// https://w3c.github.io/webdriver-bidi/#type-session-CapabilityRequest
|
||||
|
@ -343,8 +360,10 @@ static JsonValue match_capabilities(JsonObject const& capabilities)
|
|||
}
|
||||
}
|
||||
|
||||
// c. Set a property on matched capabilities with name name and value match value.
|
||||
// c. If match value is not null, set a property on matched capabilities with name name and value match value.
|
||||
if (!value.is_null())
|
||||
matched_capabilities.set(name, value);
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
|
@ -358,7 +377,7 @@ static JsonValue match_capabilities(JsonObject const& capabilities)
|
|||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-capabilities-processing
|
||||
Response process_capabilities(JsonValue const& parameters)
|
||||
Response process_capabilities(JsonValue const& parameters, ReadonlySpan<StringView> flags)
|
||||
{
|
||||
if (!parameters.is_object())
|
||||
return Error::from_code(ErrorCode::InvalidArgument, "Session parameters is not an object"sv);
|
||||
|
@ -376,7 +395,9 @@ Response process_capabilities(JsonValue const& parameters)
|
|||
JsonObject required_capabilities;
|
||||
|
||||
if (auto capability = capabilities_request.get("alwaysMatch"sv); capability.has_value()) {
|
||||
// b. Let required capabilities be the result of trying to validate capabilities with argument required capabilities.
|
||||
// b. Let required capabilities be the result of trying to validate capabilities with arguments required capabilities and flag.
|
||||
// FIXME: Spec issue: The "flags" parameter should not be provided to validate_capabilities.
|
||||
// https://github.com/w3c/webdriver/issues/1879
|
||||
required_capabilities = TRY(validate_capabilities(*capability));
|
||||
}
|
||||
|
||||
|
@ -384,23 +405,25 @@ Response process_capabilities(JsonValue const& parameters)
|
|||
JsonArray all_first_match_capabilities;
|
||||
|
||||
if (auto capabilities = capabilities_request.get("firstMatch"sv); capabilities.has_value()) {
|
||||
// b. If all first match capabilities is not a JSON List with one or more entries, return error with error code invalid argument.
|
||||
// b. If all first match capabilities is not a List with one or more entries, return error with error code invalid argument.
|
||||
if (!capabilities->is_array() || capabilities->as_array().is_empty())
|
||||
return Error::from_code(ErrorCode::InvalidArgument, "Capability firstMatch must be an array with at least one entry"sv);
|
||||
|
||||
all_first_match_capabilities = capabilities->as_array();
|
||||
} else {
|
||||
// a. If all first match capabilities is undefined, set the value to a JSON List with a single entry of an empty JSON Object.
|
||||
// a. If all first match capabilities is undefined, set the value to a List with a single entry of an empty JSON Object.
|
||||
all_first_match_capabilities.must_append(JsonObject {});
|
||||
}
|
||||
|
||||
// 4. Let validated first match capabilities be an empty JSON List.
|
||||
// 4. Let validated first match capabilities be an empty List.
|
||||
JsonArray validated_first_match_capabilities;
|
||||
validated_first_match_capabilities.ensure_capacity(all_first_match_capabilities.size());
|
||||
|
||||
// 5. For each first match capabilities corresponding to an indexed property in all first match capabilities:
|
||||
TRY(all_first_match_capabilities.try_for_each([&](auto const& first_match_capabilities) -> ErrorOr<void, Error> {
|
||||
// a. Let validated capabilities be the result of trying to validate capabilities with argument first match capabilities.
|
||||
// a. Let validated capabilities be the result of trying to validate capabilities with arguments first match capabilities and flags.
|
||||
// FIXME: Spec issue: The "flags" parameter should not be provided to validate_capabilities.
|
||||
// https://github.com/w3c/webdriver/issues/1879
|
||||
auto validated_capabilities = TRY(validate_capabilities(first_match_capabilities));
|
||||
|
||||
// b. Append validated capabilities to validated first match capabilities.
|
||||
|
@ -425,7 +448,9 @@ Response process_capabilities(JsonValue const& parameters)
|
|||
// 8. For each capabilities corresponding to an indexed property in merged capabilities:
|
||||
for (auto const& capabilities : merged_capabilities.values()) {
|
||||
// a. Let matched capabilities be the result of trying to match capabilities with capabilities as an argument.
|
||||
auto matched_capabilities = match_capabilities(capabilities.as_object());
|
||||
// FIXME: Spec issue: The "flags" parameter *should* be provided to match_capabilities.
|
||||
// https://github.com/w3c/webdriver/issues/1879
|
||||
auto matched_capabilities = match_capabilities(capabilities.as_object(), flags);
|
||||
|
||||
// b. If matched capabilities is not null, return success with data matched capabilities.
|
||||
if (!matched_capabilities.is_null())
|
||||
|
|
|
@ -42,6 +42,6 @@ struct LadybirdOptions {
|
|||
bool headless { false };
|
||||
};
|
||||
|
||||
Response process_capabilities(JsonValue const& parameters);
|
||||
Response process_capabilities(JsonValue const& parameters, ReadonlySpan<StringView> flags);
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,25 @@ static constexpr PromptHandler prompt_handler_from_string(StringView prompt_hand
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr StringView prompt_type_to_string(PromptType prompt_type)
|
||||
{
|
||||
switch (prompt_type) {
|
||||
case PromptType::Alert:
|
||||
return "alert"sv;
|
||||
case PromptType::BeforeUnload:
|
||||
return "beforeUnload"sv;
|
||||
case PromptType::Confirm:
|
||||
return "confirm"sv;
|
||||
case PromptType::Default:
|
||||
return "default"sv;
|
||||
case PromptType::Prompt:
|
||||
return "prompt"sv;
|
||||
case PromptType::FallbackDefault:
|
||||
return "fallbackDefault"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr PromptType prompt_type_from_string(StringView prompt_type)
|
||||
{
|
||||
if (prompt_type == "alert"sv)
|
||||
|
@ -61,6 +80,24 @@ PromptHandlerConfiguration PromptHandlerConfiguration::deserialize(JsonValue con
|
|||
return { .handler = handler, .notify = notify };
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-serialize-a-prompt-handler-configuration
|
||||
StringView PromptHandlerConfiguration::serialize() const
|
||||
{
|
||||
// 1. Let serialized be configuration's handler.
|
||||
// 2. If «"dismiss", "accept"» contains serialized, and configuration's notify is true, append " and notify" to serialized.
|
||||
// 3. Return serialized.
|
||||
switch (handler) {
|
||||
case PromptHandler::Dismiss:
|
||||
return notify == Notify::Yes ? "dismiss and notify"sv : "dismiss"sv;
|
||||
case PromptHandler::Accept:
|
||||
return notify == Notify::Yes ? "accept and notify"sv : "accept"sv;
|
||||
case PromptHandler::Ignore:
|
||||
return "ignore"sv;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
UserPromptHandler const& user_prompt_handler()
|
||||
{
|
||||
return s_user_prompt_handler;
|
||||
|
@ -147,6 +184,29 @@ Response deserialize_as_an_unhandled_prompt_behavior(JsonValue value)
|
|||
return JsonValue { move(user_prompt_handler) };
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-check-user-prompt-handler-matches
|
||||
bool check_user_prompt_handler_matches(JsonObject const& requested_prompt_handler)
|
||||
{
|
||||
// 1. If the user prompt handler is null, return true.
|
||||
if (!s_user_prompt_handler.has_value())
|
||||
return true;
|
||||
|
||||
// 2. For each request prompt type → request handler in requested prompt handler:
|
||||
auto result = requested_prompt_handler.try_for_each_member([&](ByteString const& request_prompt_type, JsonValue const& request_handler) -> ErrorOr<void> {
|
||||
// 1. If the user prompt handler contains request prompt type:
|
||||
if (auto handler = s_user_prompt_handler->get(prompt_type_from_string(request_prompt_type)); handler.has_value()) {
|
||||
// 1. If the requested prompt handler's handler is not equal to the user prompt handler's handler, return false.
|
||||
if (handler != PromptHandlerConfiguration::deserialize(request_handler))
|
||||
return AK::Error::from_string_literal("Prompt handler mismatch");
|
||||
}
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
// 3. Return true
|
||||
return !result.is_error();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-update-the-user-prompt-handler
|
||||
void update_the_user_prompt_handler(JsonObject const& requested_prompt_handler)
|
||||
{
|
||||
|
@ -163,6 +223,33 @@ void update_the_user_prompt_handler(JsonObject const& requested_prompt_handler)
|
|||
});
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-serialize-the-user-prompt-handler
|
||||
JsonValue serialize_the_user_prompt_handler()
|
||||
{
|
||||
// 1. If the user prompt handler is null, return "dismiss and notify".
|
||||
if (!s_user_prompt_handler.has_value())
|
||||
return "dismiss and notify"sv;
|
||||
|
||||
// 2. If the user prompt handler has size 1, and user prompt handler contains "fallbackDefault", return the result
|
||||
// of serialize a prompt handler configuration with user prompt handler["fallbackDefault"].
|
||||
if (s_user_prompt_handler->size() == 1) {
|
||||
if (auto handler = s_user_prompt_handler->get(PromptType::FallbackDefault); handler.has_value())
|
||||
return handler->serialize();
|
||||
}
|
||||
|
||||
// 3. Let serialized be an empty map.
|
||||
JsonObject serialized;
|
||||
|
||||
// 4. For each key → value of user prompt handler:
|
||||
for (auto const& [key, value] : *s_user_prompt_handler) {
|
||||
// 1. Set serialized[key] to serialize a prompt handler configuration with value.
|
||||
serialized.set(prompt_type_to_string(key), value.serialize());
|
||||
}
|
||||
|
||||
// 5. Return convert an Infra value to a JSON-compatible JavaScript value with serialized.
|
||||
return serialized;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
|
|
|
@ -40,6 +40,9 @@ struct PromptHandlerConfiguration {
|
|||
};
|
||||
|
||||
static PromptHandlerConfiguration deserialize(JsonValue const&);
|
||||
StringView serialize() const;
|
||||
|
||||
bool operator==(PromptHandlerConfiguration const&) const = default;
|
||||
|
||||
PromptHandler handler { PromptHandler::Dismiss };
|
||||
Notify notify { Notify::Yes };
|
||||
|
@ -52,7 +55,9 @@ UserPromptHandler const& user_prompt_handler();
|
|||
void set_user_prompt_handler(UserPromptHandler);
|
||||
|
||||
Response deserialize_as_an_unhandled_prompt_behavior(JsonValue);
|
||||
bool check_user_prompt_handler_matches(JsonObject const&);
|
||||
void update_the_user_prompt_handler(JsonObject const&);
|
||||
JsonValue serialize_the_user_prompt_handler();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -223,9 +223,11 @@ void WebDriverConnection::close_session()
|
|||
// 1. Set the webdriver-active flag to false.
|
||||
set_is_webdriver_active(false);
|
||||
|
||||
// 2. An endpoint node must close any top-level browsing contexts associated with the session, without prompting to unload.
|
||||
if (auto browsing_context = current_top_level_browsing_context())
|
||||
browsing_context->top_level_traversable()->close_top_level_traversable();
|
||||
// 5. Optionally, close all top-level browsing contexts, without prompting to unload.
|
||||
for (auto navigable : Web::HTML::all_navigables()) {
|
||||
if (auto traversable = navigable->top_level_traversable())
|
||||
traversable->close_top_level_traversable();
|
||||
}
|
||||
}
|
||||
|
||||
void WebDriverConnection::set_page_load_strategy(Web::WebDriver::PageLoadStrategy const& page_load_strategy)
|
||||
|
|
|
@ -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-2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
* Copyright (c) 2022-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -13,8 +13,8 @@
|
|||
#include <AK/JsonValue.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
#include <LibWeb/WebDriver/Capabilities.h>
|
||||
#include <LibWeb/WebDriver/UserPrompt.h>
|
||||
#include <WebDriver/Client.h>
|
||||
|
||||
namespace WebDriver {
|
||||
|
@ -50,12 +50,40 @@ ErrorOr<NonnullRefPtr<Session>, Web::WebDriver::Error> Client::find_session_with
|
|||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-close-the-session
|
||||
void Client::close_session(String const& session_id)
|
||||
{
|
||||
// FIXME: 1. If session's HTTP flag is set, remove session from active HTTP sessions.
|
||||
|
||||
// 2. Remove session from active sessions.
|
||||
if (s_sessions.remove(session_id))
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Shut down session {}", session_id);
|
||||
else
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Unable to shut down session {}: Not found", session_id);
|
||||
|
||||
// 3. Perform the following substeps based on the remote end's type:
|
||||
// -> Remote end is an endpoint node
|
||||
// 1. If the list of active sessions is empty:
|
||||
if (s_sessions.is_empty()) {
|
||||
// 1. Set the webdriver-active flag to false
|
||||
// NOTE: This is handled by the WebContent process.
|
||||
|
||||
// 2. Set the user prompt handler to null.
|
||||
Web::WebDriver::set_user_prompt_handler({});
|
||||
|
||||
// FIXME: 3. Unset the accept insecure TLS flag.
|
||||
// FIXME: 4. Reset the has proxy configuration flag to its default value.
|
||||
|
||||
// 5. Optionally, close all top-level browsing contexts, without prompting to unload.
|
||||
// NOTE: This is handled by the WebContent process.
|
||||
}
|
||||
// -> Remote end is an intermediary node
|
||||
// 1. Close the associated session. If this causes an error to occur, complete the remainder of this algorithm
|
||||
// before returning the error.
|
||||
|
||||
// 4. Perform any implementation-specific cleanup steps.
|
||||
|
||||
// 5. If an error has occurred in any of the steps above, return the error, otherwise return success with data null.
|
||||
}
|
||||
|
||||
// 8.1 New Session, https://w3c.github.io/webdriver/#dfn-new-sessions
|
||||
|
@ -64,69 +92,50 @@ Web::WebDriver::Response Client::new_session(Web::WebDriver::Parameters, JsonVal
|
|||
{
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session");
|
||||
|
||||
// FIXME: 1. If the maximum active sessions is equal to the length of the list of active sessions,
|
||||
// return error with error code session not created.
|
||||
// FIXME: 1. If the implementation is an endpoint node, and the list of active HTTP sessions is not empty, or otherwise if
|
||||
// the implementation is unable to start an additional session, return error with error code session not created.
|
||||
|
||||
// FIXME: 2. If the remote end is an intermediary node, take implementation-defined steps that either
|
||||
// result in returning an error with error code session not created, or in returning a
|
||||
// success with data that is isomorphic to that returned by remote ends according to the
|
||||
// rest of this algorithm. If an error is not returned, the intermediary node must retain a
|
||||
// reference to the session created on the upstream node as the associated session such
|
||||
// that commands may be forwarded to this associated session on subsequent commands.
|
||||
// FIXME: 2. If the remote end is an intermediary node, take implementation-defined steps that either result in returning
|
||||
// an error with error code session not created, or in returning a success with data that is isomorphic to that
|
||||
// returned by remote ends according to the rest of this algorithm. If an error is not returned, the intermediary
|
||||
// node must retain a reference to the session created on the upstream node as the associated session such that
|
||||
// commands may be forwarded to this associated session on subsequent commands.
|
||||
|
||||
// FIXME: 3. If the maximum active sessions is equal to the length of the list of active sessions,
|
||||
// return error with error code session not created.
|
||||
// 3. Let flags be a set containing "http".
|
||||
static constexpr Array flags { "http"sv };
|
||||
|
||||
// 4. Let capabilities be the result of trying to process capabilities with parameters as an argument.
|
||||
auto capabilities = TRY(Web::WebDriver::process_capabilities(payload));
|
||||
// 4. Let capabilities be the result of trying to process capabilities with parameters and flags.
|
||||
auto capabilities = TRY(Web::WebDriver::process_capabilities(payload, flags));
|
||||
|
||||
// 5. If capabilities’s is null, return error with error code session not created.
|
||||
// 5. If capabilities's is null, return error with error code session not created.
|
||||
if (capabilities.is_null())
|
||||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::SessionNotCreated, "Could not match capabilities"sv);
|
||||
|
||||
// 6. Let session id be the result of generating a UUID.
|
||||
auto session_id = MUST(Web::Crypto::generate_random_uuid());
|
||||
// 6. Let session be the result of create a session, with capabilities, and flags.
|
||||
auto maybe_session = Session::create(*this, capabilities.as_object(), flags);
|
||||
if (maybe_session.is_error())
|
||||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::SessionNotCreated, ByteString::formatted("Failed to start session: {}", maybe_session.error()));
|
||||
|
||||
// 7. Let session be a new session with the session ID of session id.
|
||||
Web::WebDriver::LadybirdOptions options { capabilities.as_object() };
|
||||
auto session = make_ref_counted<Session>(session_id, *this, move(options));
|
||||
auto session = maybe_session.release_value();
|
||||
s_sessions.set(session->session_id(), session);
|
||||
|
||||
if (auto start_result = session->start(m_callbacks); start_result.is_error())
|
||||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::SessionNotCreated, ByteString::formatted("Failed to start session: {}", start_result.error()));
|
||||
|
||||
auto& web_content_connection = session->web_content_connection();
|
||||
|
||||
// FIXME: 8. Set the current session to session.
|
||||
|
||||
// FIXME: 9. Run any WebDriver new session algorithm defined in external specifications,
|
||||
// with arguments session and capabilities.
|
||||
|
||||
// 10. Append session to active sessions.
|
||||
s_sessions.set(session_id, session);
|
||||
|
||||
// NOTE: We do step 12 before 11 because step 12 mutates the capabilities we set in step 11.
|
||||
|
||||
// 12. Initialize the following from capabilities:
|
||||
session->initialize_from_capabilities(capabilities.as_object());
|
||||
|
||||
// 11. Let body be a JSON Object initialized with:
|
||||
// 7. Let body be a JSON Object initialized with:
|
||||
JsonObject body;
|
||||
// "sessionId"
|
||||
// session id
|
||||
body.set("sessionId", JsonValue { session_id });
|
||||
// session's session ID.
|
||||
body.set("sessionId", JsonValue { session->session_id() });
|
||||
// "capabilities"
|
||||
// capabilities
|
||||
body.set("capabilities", move(capabilities));
|
||||
|
||||
// 13. Set the webdriver-active flag to true.
|
||||
web_content_connection.async_set_is_webdriver_active(true);
|
||||
// 8. Set session' current top-level browsing context to one of the endpoint node's top-level browsing contexts,
|
||||
// preferring the top-level browsing context that has system focus, or otherwise preferring any top-level
|
||||
// browsing context whose visibility state is visible.
|
||||
// NOTE: This happens in the WebContent process.
|
||||
|
||||
// FIXME: 14. Set the current top-level browsing context for session with the top-level browsing context
|
||||
// of the UA’s current browsing context.
|
||||
// FIXME: 9. Set the request queue to a new queue.
|
||||
|
||||
// FIXME: 15. Set the request queue to a new queue.
|
||||
|
||||
// 16. Return success with data body.
|
||||
// 10. Return success with data body.
|
||||
return JsonValue { move(body) };
|
||||
}
|
||||
|
||||
|
|
|
@ -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-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -30,6 +30,7 @@ public:
|
|||
static ErrorOr<NonnullRefPtr<Client>> try_create(NonnullOwnPtr<Core::BufferedTCPSocket>, LaunchBrowserCallbacks, Core::EventReceiver* parent);
|
||||
virtual ~Client() override;
|
||||
|
||||
LaunchBrowserCallbacks const& launch_browser_callbacks() const { return m_callbacks; }
|
||||
void close_session(String const& session_id);
|
||||
|
||||
private:
|
||||
|
|
|
@ -14,16 +14,99 @@
|
|||
#include <LibCore/LocalServer.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
#include <LibWeb/WebDriver/TimeoutsConfiguration.h>
|
||||
#include <LibWeb/WebDriver/UserPrompt.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace WebDriver {
|
||||
|
||||
Session::Session(String session_id, NonnullRefPtr<Client> client, Web::WebDriver::LadybirdOptions options)
|
||||
// https://w3c.github.io/webdriver/#dfn-create-a-session
|
||||
ErrorOr<NonnullRefPtr<Session>> Session::create(NonnullRefPtr<Client> client, JsonObject& capabilities, ReadonlySpan<StringView> flags)
|
||||
{
|
||||
// 1. Let session id be the result of generating a UUID.
|
||||
auto session_id = MUST(Web::Crypto::generate_random_uuid());
|
||||
|
||||
// 2. Let session be a new session with session ID session id, and HTTP flag flags contains "http".
|
||||
auto session = adopt_ref(*new Session(client, capabilities, move(session_id), flags.contains_slow("http"sv)));
|
||||
TRY(session->start(client->launch_browser_callbacks()));
|
||||
|
||||
// FIXME: 3. Let proxy be the result of getting property "proxy" from capabilities and run the substeps of the first matching statement:
|
||||
// -> proxy is a proxy configuration object
|
||||
// Take implementation-defined steps to set the user agent proxy using the extracted proxy configuration. If the defined proxy cannot be configured return error with error code session not created. Otherwise set the has proxy configuration flag to true.
|
||||
// -> Otherwise
|
||||
// Set a property of capabilities with name "proxy" and a value that is a new JSON Object.
|
||||
|
||||
// FIXME: 4. If capabilites has a property named "acceptInsecureCerts", set the endpoint node's accept insecure TLS flag
|
||||
// to the result of getting a property named "acceptInsecureCerts" from capabilities.
|
||||
|
||||
// 5. Let user prompt handler capability be the result of getting property "unhandledPromptBehavior" from capabilities.
|
||||
auto user_prompt_handler_capability = capabilities.get_object("unhandledPromptBehavior"sv);
|
||||
|
||||
// 6. If user prompt handler capability is not undefined, update the user prompt handler with user prompt handler capability.
|
||||
if (user_prompt_handler_capability.has_value())
|
||||
Web::WebDriver::update_the_user_prompt_handler(*user_prompt_handler_capability);
|
||||
|
||||
session->web_content_connection().async_set_user_prompt_handler(Web::WebDriver::user_prompt_handler());
|
||||
|
||||
// 7. Let serialized user prompt handler be serialize the user prompt handler.
|
||||
auto serialized_user_prompt_handler = Web::WebDriver::serialize_the_user_prompt_handler();
|
||||
|
||||
// 8. Set a property on capabilities with the name "unhandledPromptBehavior", and the value serialized user prompt handler.
|
||||
capabilities.set("unhandledPromptBehavior"sv, move(serialized_user_prompt_handler));
|
||||
|
||||
// 9. If flags contains "http":
|
||||
if (flags.contains_slow("http"sv)) {
|
||||
// 1. Let strategy be the result of getting property "pageLoadStrategy" from capabilities. If strategy is a
|
||||
// string, set the session's page loading strategy to strategy. Otherwise, set the page loading strategy to
|
||||
// normal and set a property of capabilities with name "pageLoadStrategy" and value "normal".
|
||||
if (auto strategy = capabilities.get_byte_string("pageLoadStrategy"sv); strategy.has_value()) {
|
||||
session->m_page_load_strategy = Web::WebDriver::page_load_strategy_from_string(*strategy);
|
||||
session->web_content_connection().async_set_page_load_strategy(session->m_page_load_strategy);
|
||||
} else {
|
||||
capabilities.set("pageLoadStrategy"sv, "normal"sv);
|
||||
}
|
||||
|
||||
// 3. Let strictFileInteractability be the result of getting property "strictFileInteractability" from .
|
||||
// capabilities. If strictFileInteractability is a boolean, set session's strict file interactability to
|
||||
// strictFileInteractability.
|
||||
if (auto strict_file_interactiblity = capabilities.get_bool("strictFileInteractability"sv); strict_file_interactiblity.has_value()) {
|
||||
session->m_strict_file_interactiblity = *strict_file_interactiblity;
|
||||
session->web_content_connection().async_set_strict_file_interactability(session->m_strict_file_interactiblity);
|
||||
}
|
||||
|
||||
// 4. Let timeouts be the result of getting a property "timeouts" from capabilities. If timeouts is not
|
||||
// undefined, set session's session timeouts to timeouts.
|
||||
if (auto timeouts = capabilities.get_object("timeouts"sv); timeouts.has_value()) {
|
||||
MUST(session->set_timeouts(*timeouts));
|
||||
}
|
||||
|
||||
// 5. Set a property on capabilities with name "timeouts" and value serialize the timeouts configuration with
|
||||
// session's session timeouts.
|
||||
capabilities.set("timeouts"sv, session->m_timeouts_configuration.value_or_lazy_evaluated([]() {
|
||||
return Web::WebDriver::timeouts_object({});
|
||||
}));
|
||||
}
|
||||
|
||||
// FIXME: 10. Process any extension capabilities in capabilities in an implementation-defined manner.
|
||||
|
||||
// FIXME: 11. Run any WebDriver new session algorithm defined in external specifications, with arguments session, capabilities, and flags.
|
||||
|
||||
// 12. Append session to active sessions.
|
||||
// 13. If flags contains "http", append session to active HTTP sessions.
|
||||
// NOTE: These steps are handled by WebDriver::Client.
|
||||
|
||||
// 14. Set the webdriver-active flag to true.
|
||||
session->web_content_connection().async_set_is_webdriver_active(true);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
Session::Session(NonnullRefPtr<Client> client, JsonObject const& capabilities, String session_id, bool http)
|
||||
: m_client(move(client))
|
||||
, m_options(move(options))
|
||||
, m_options(capabilities)
|
||||
, m_id(move(session_id))
|
||||
, m_http(http)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -53,61 +136,6 @@ Session::~Session()
|
|||
}
|
||||
}
|
||||
|
||||
// Step 12 of https://w3c.github.io/webdriver/#dfn-new-sessions
|
||||
void Session::initialize_from_capabilities(JsonObject& capabilities)
|
||||
{
|
||||
auto& connection = web_content_connection();
|
||||
|
||||
// 1. Let strategy be the result of getting property "pageLoadStrategy" from capabilities.
|
||||
auto strategy = capabilities.get_byte_string("pageLoadStrategy"sv);
|
||||
|
||||
// 2. If strategy is a string, set the current session’s page loading strategy to strategy. Otherwise, set the page loading strategy to normal and set a property of capabilities with name "pageLoadStrategy" and value "normal".
|
||||
if (strategy.has_value()) {
|
||||
m_page_load_strategy = Web::WebDriver::page_load_strategy_from_string(*strategy);
|
||||
connection.async_set_page_load_strategy(m_page_load_strategy);
|
||||
} else {
|
||||
capabilities.set("pageLoadStrategy"sv, "normal"sv);
|
||||
}
|
||||
|
||||
// 3. Let strictFileInteractability be the result of getting property "strictFileInteractability" from capabilities.
|
||||
auto strict_file_interactiblity = capabilities.get_bool("strictFileInteractability"sv);
|
||||
|
||||
// 4. If strictFileInteractability is a boolean, set the current session’s strict file interactability to strictFileInteractability. Otherwise set the current session’s strict file interactability to false.
|
||||
if (strict_file_interactiblity.has_value()) {
|
||||
m_strict_file_interactiblity = *strict_file_interactiblity;
|
||||
connection.async_set_strict_file_interactability(m_strict_file_interactiblity);
|
||||
} else {
|
||||
capabilities.set("strictFileInteractability"sv, false);
|
||||
}
|
||||
|
||||
// FIXME: 5. Let proxy be the result of getting property "proxy" from capabilities and run the substeps of the first matching statement:
|
||||
// FIXME: proxy is a proxy configuration object
|
||||
// FIXME: Take implementation-defined steps to set the user agent proxy using the extracted proxy configuration. If the defined proxy cannot be configured return error with error code session not created.
|
||||
// FIXME: Otherwise
|
||||
// FIXME: Set a property of capabilities with name "proxy" and a value that is a new JSON Object.
|
||||
|
||||
// 6. If capabilities has a property with the key "timeouts":
|
||||
if (auto timeouts = capabilities.get_object("timeouts"sv); timeouts.has_value()) {
|
||||
// a. Let timeouts be the result of trying to JSON deserialize as a timeouts configuration the value of the "timeouts" property.
|
||||
// NOTE: This happens on the remote end.
|
||||
|
||||
// b. Make the session timeouts the new timeouts.
|
||||
MUST(set_timeouts(*timeouts));
|
||||
} else {
|
||||
// 7. Set a property on capabilities with name "timeouts" and value that of the JSON deserialization of the session timeouts.
|
||||
capabilities.set("timeouts"sv, Web::WebDriver::timeouts_object({}));
|
||||
}
|
||||
|
||||
// 8. Apply changes to the user agent for any implementation-defined capabilities selected during the capabilities processing step.
|
||||
if (auto behavior = capabilities.get_object("unhandledPromptBehavior"sv); behavior.has_value()) {
|
||||
Web::WebDriver::update_the_user_prompt_handler(*behavior);
|
||||
} else {
|
||||
capabilities.set("unhandledPromptBehavior"sv, "dismiss and notify"sv);
|
||||
}
|
||||
|
||||
connection.async_set_user_prompt_handler(Web::WebDriver::user_prompt_handler());
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<Core::LocalServer>> Session::create_server(NonnullRefPtr<ServerPromise> promise)
|
||||
{
|
||||
static_assert(IsSame<IPC::Transport, IPC::TransportSocket>, "Need to handle other IPC transports here");
|
||||
|
|
|
@ -29,11 +29,9 @@ struct LaunchBrowserCallbacks;
|
|||
|
||||
class Session : public RefCounted<Session> {
|
||||
public:
|
||||
Session(String session_id, NonnullRefPtr<Client> client, Web::WebDriver::LadybirdOptions options);
|
||||
static ErrorOr<NonnullRefPtr<Session>> create(NonnullRefPtr<Client> client, JsonObject& capabilities, ReadonlySpan<StringView> flags);
|
||||
~Session();
|
||||
|
||||
void initialize_from_capabilities(JsonObject&);
|
||||
|
||||
String session_id() const { return m_id; }
|
||||
|
||||
struct Window {
|
||||
|
@ -56,8 +54,6 @@ public:
|
|||
|
||||
bool has_window_handle(StringView handle) const { return m_windows.contains(handle); }
|
||||
|
||||
ErrorOr<void> start(LaunchBrowserCallbacks const&);
|
||||
|
||||
Web::WebDriver::Response set_timeouts(JsonValue);
|
||||
Web::WebDriver::Response close_window();
|
||||
Web::WebDriver::Response switch_to_window(StringView);
|
||||
|
@ -83,6 +79,10 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
Session(NonnullRefPtr<Client> client, JsonObject const& capabilities, String session_id, bool http);
|
||||
|
||||
ErrorOr<void> start(LaunchBrowserCallbacks const&);
|
||||
|
||||
using ServerPromise = Core::Promise<ErrorOr<void>>;
|
||||
ErrorOr<NonnullRefPtr<Core::LocalServer>> create_server(NonnullRefPtr<ServerPromise> promise);
|
||||
|
||||
|
@ -90,7 +90,9 @@ private:
|
|||
Web::WebDriver::LadybirdOptions m_options;
|
||||
|
||||
bool m_started { false };
|
||||
|
||||
String m_id;
|
||||
bool m_http { false };
|
||||
|
||||
HashMap<String, Window> m_windows;
|
||||
String m_current_window_handle;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue