mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-25 12:32:53 +00:00
Note that this does nothing to "fix" how element references are created. We continue to return the element ID because, otherwise, all other element WebDriver endpoints would break. On the bright side, we avoid several IPC round trips now that we perform the entire 'find' operation in the WebContent process; and we are able to work directly on DOM nodes.
834 lines
38 KiB
C++
834 lines
38 KiB
C++
/*
|
||
* Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
|
||
* 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>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <AK/Debug.h>
|
||
#include <AK/JsonParser.h>
|
||
#include <LibCore/DateTime.h>
|
||
#include <LibCore/MemoryStream.h>
|
||
#include <LibHTTP/HttpRequest.h>
|
||
#include <LibHTTP/HttpResponse.h>
|
||
#include <WebDriver/Client.h>
|
||
#include <WebDriver/Session.h>
|
||
#include <WebDriver/TimeoutsConfiguration.h>
|
||
|
||
namespace WebDriver {
|
||
|
||
Atomic<unsigned> Client::s_next_session_id;
|
||
NonnullOwnPtrVector<Session> Client::s_sessions;
|
||
Vector<Client::Route> Client::s_routes = {
|
||
{ HTTP::HttpRequest::Method::POST, { "session" }, &Client::handle_new_session },
|
||
{ HTTP::HttpRequest::Method::DELETE, { "session", ":session_id" }, &Client::handle_delete_session },
|
||
{ HTTP::HttpRequest::Method::GET, { "status" }, &Client::handle_get_status },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "timeouts" }, &Client::handle_get_timeouts },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "timeouts" }, &Client::handle_set_timeouts },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "url" }, &Client::handle_navigate_to },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "url" }, &Client::handle_get_current_url },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "back" }, &Client::handle_back },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "forward" }, &Client::handle_forward },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "refresh" }, &Client::handle_refresh },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "title" }, &Client::handle_get_title },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "window" }, &Client::handle_get_window_handle },
|
||
{ HTTP::HttpRequest::Method::DELETE, { "session", ":session_id", "window" }, &Client::handle_close_window },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "window", "handles" }, &Client::handle_get_window_handles },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "window", "rect" }, &Client::handle_get_window_rect },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "window", "rect" }, &Client::handle_set_window_rect },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "window", "maximize" }, &Client::handle_maximize_window },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "window", "minimize" }, &Client::handle_minimize_window },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "element" }, &Client::handle_find_element },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "elements" }, &Client::handle_find_elements },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "element", ":element_id", "element" }, &Client::handle_find_element_from_element },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "element", ":element_id", "elements" }, &Client::handle_find_elements_from_element },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "selected" }, &Client::handle_is_element_selected },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "attribute", ":name" }, &Client::handle_get_element_attribute },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "property", ":name" }, &Client::handle_get_element_property },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "css", ":property_name" }, &Client::handle_get_element_css_value },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "text" }, &Client::handle_get_element_text },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "name" }, &Client::handle_get_element_tag_name },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "rect" }, &Client::handle_get_element_rect },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "enabled" }, &Client::handle_is_element_enabled },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "source" }, &Client::handle_get_source },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "execute", "sync" }, &Client::handle_execute_script },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "execute", "async" }, &Client::handle_execute_async_script },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "cookie" }, &Client::handle_get_all_cookies },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "cookie", ":name" }, &Client::handle_get_named_cookie },
|
||
{ HTTP::HttpRequest::Method::POST, { "session", ":session_id", "cookie" }, &Client::handle_add_cookie },
|
||
{ HTTP::HttpRequest::Method::DELETE, { "session", ":session_id", "cookie", ":name" }, &Client::handle_delete_cookie },
|
||
{ HTTP::HttpRequest::Method::DELETE, { "session", ":session_id", "cookie" }, &Client::handle_delete_all_cookies },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "screenshot" }, &Client::handle_take_screenshot },
|
||
{ HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "screenshot" }, &Client::handle_take_element_screenshot },
|
||
};
|
||
|
||
Client::Client(NonnullOwnPtr<Core::Stream::BufferedTCPSocket> socket, Core::Object* parent)
|
||
: Core::Object(parent)
|
||
, m_socket(move(socket))
|
||
{
|
||
}
|
||
|
||
void Client::die()
|
||
{
|
||
m_socket->close();
|
||
deferred_invoke([this] { remove_from_parent(); });
|
||
}
|
||
|
||
void Client::start()
|
||
{
|
||
m_socket->on_ready_to_read = [this] {
|
||
StringBuilder builder;
|
||
|
||
// FIXME: All this should be moved to LibHTTP and be made spec compliant
|
||
auto maybe_buffer = ByteBuffer::create_uninitialized(m_socket->buffer_size());
|
||
if (maybe_buffer.is_error()) {
|
||
warnln("Could not create buffer for client: {}", maybe_buffer.error());
|
||
die();
|
||
return;
|
||
}
|
||
|
||
auto buffer = maybe_buffer.release_value();
|
||
for (;;) {
|
||
auto maybe_can_read = m_socket->can_read_without_blocking();
|
||
if (maybe_can_read.is_error()) {
|
||
warnln("Failed to get the blocking status for the socket: {}", maybe_can_read.error());
|
||
die();
|
||
return;
|
||
}
|
||
|
||
if (!maybe_can_read.value())
|
||
break;
|
||
|
||
auto maybe_data = m_socket->read(buffer);
|
||
if (maybe_data.is_error()) {
|
||
warnln("Failed to read data from the request: {}", maybe_data.error());
|
||
die();
|
||
return;
|
||
}
|
||
|
||
if (m_socket->is_eof()) {
|
||
die();
|
||
break;
|
||
}
|
||
|
||
builder.append(StringView(maybe_data.value()));
|
||
}
|
||
|
||
auto request = builder.to_byte_buffer();
|
||
auto http_request_or_error = HTTP::HttpRequest::from_raw_request(request);
|
||
if (!http_request_or_error.has_value())
|
||
return;
|
||
|
||
auto http_request = http_request_or_error.release_value();
|
||
|
||
auto body_or_error = read_body_as_json(http_request);
|
||
if (body_or_error.is_error()) {
|
||
warnln("Failed to read the request body: {}", body_or_error.error());
|
||
die();
|
||
return;
|
||
}
|
||
|
||
auto maybe_did_handle = handle_request(http_request, body_or_error.value());
|
||
if (maybe_did_handle.is_error()) {
|
||
warnln("Failed to handle the request: {}", maybe_did_handle.error());
|
||
}
|
||
|
||
die();
|
||
};
|
||
}
|
||
|
||
ErrorOr<JsonValue> Client::read_body_as_json(HTTP::HttpRequest const& request)
|
||
{
|
||
// If we received a multipart body here, this would fail badly.
|
||
unsigned content_length = 0;
|
||
for (auto const& header : request.headers()) {
|
||
if (header.name.equals_ignoring_case("Content-Length"sv)) {
|
||
content_length = header.value.to_int(TrimWhitespace::Yes).value_or(0);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!content_length)
|
||
return JsonValue();
|
||
|
||
// FIXME: Check the Content-Type is actually application/json
|
||
JsonParser json_parser(request.body());
|
||
return json_parser.parse();
|
||
}
|
||
|
||
ErrorOr<bool> Client::handle_request(HTTP::HttpRequest const& request, JsonValue const& body)
|
||
{
|
||
if constexpr (WEBDRIVER_DEBUG) {
|
||
dbgln("Got HTTP request: {} {}", request.method_name(), request.resource());
|
||
if (!body.is_null())
|
||
dbgln("Body: {}", body.to_string());
|
||
}
|
||
|
||
auto routing_result_match = match_route(request.method(), request.resource());
|
||
if (routing_result_match.is_error()) {
|
||
auto error = routing_result_match.release_error();
|
||
dbgln_if(WEBDRIVER_DEBUG, "Failed to match route: {}", error);
|
||
TRY(send_error_response(error, request));
|
||
return false;
|
||
}
|
||
|
||
auto routing_result = routing_result_match.release_value();
|
||
auto result = (this->*routing_result.handler)(routing_result.parameters, body);
|
||
if (result.is_error()) {
|
||
dbgln_if(WEBDRIVER_DEBUG, "Error in calling route handler: {}", result.error());
|
||
TRY(send_error_response(result.release_error(), request));
|
||
return false;
|
||
}
|
||
|
||
auto object = result.release_value();
|
||
TRY(send_response(object.to_string(), request));
|
||
|
||
return true;
|
||
}
|
||
|
||
// https://w3c.github.io/webdriver/#dfn-send-a-response
|
||
ErrorOr<void> Client::send_response(StringView content, HTTP::HttpRequest const& request)
|
||
{
|
||
// FIXME: Implement to spec.
|
||
|
||
StringBuilder builder;
|
||
builder.append("HTTP/1.0 200 OK\r\n"sv);
|
||
builder.append("Server: WebDriver (SerenityOS)\r\n"sv);
|
||
builder.append("X-Frame-Options: SAMEORIGIN\r\n"sv);
|
||
builder.append("X-Content-Type-Options: nosniff\r\n"sv);
|
||
builder.append("Pragma: no-cache\r\n"sv);
|
||
builder.append("Content-Type: application/json; charset=utf-8\r\n"sv);
|
||
builder.appendff("Content-Length: {}\r\n", content.length());
|
||
builder.append("\r\n"sv);
|
||
|
||
auto builder_contents = builder.to_byte_buffer();
|
||
TRY(m_socket->write(builder_contents));
|
||
|
||
while (!content.is_empty()) {
|
||
auto bytes_sent = TRY(m_socket->write(content.bytes()));
|
||
content = content.substring_view(bytes_sent);
|
||
}
|
||
|
||
log_response(200, request);
|
||
|
||
auto keep_alive = false;
|
||
if (auto it = request.headers().find_if([](auto& header) { return header.name.equals_ignoring_case("Connection"sv); }); !it.is_end()) {
|
||
if (it->value.trim_whitespace().equals_ignoring_case("keep-alive"sv))
|
||
keep_alive = true;
|
||
}
|
||
if (!keep_alive)
|
||
m_socket->close();
|
||
|
||
return {};
|
||
}
|
||
|
||
// https://w3c.github.io/webdriver/#dfn-send-an-error
|
||
ErrorOr<void> Client::send_error_response(Web::WebDriver::Error const& error, HTTP::HttpRequest const& request)
|
||
{
|
||
// FIXME: Implement to spec.
|
||
|
||
dbgln("send_error_response: {} {}: {}", error.http_status, error.error, error.message);
|
||
auto reason_phrase = HTTP::HttpResponse::reason_phrase_for_code(error.http_status);
|
||
|
||
auto result = JsonObject();
|
||
result.set("error", error.error);
|
||
result.set("message", error.message);
|
||
result.set("stacktrace", "");
|
||
if (error.data.has_value())
|
||
result.set("data", *error.data);
|
||
|
||
StringBuilder content_builder;
|
||
result.serialize(content_builder);
|
||
|
||
StringBuilder header_builder;
|
||
header_builder.appendff("HTTP/1.0 {} ", error.http_status);
|
||
header_builder.append(reason_phrase);
|
||
header_builder.append("\r\n"sv);
|
||
header_builder.append("Content-Type: application/json; charset=UTF-8\r\n"sv);
|
||
header_builder.appendff("Content-Length: {}\r\n", content_builder.length());
|
||
header_builder.append("\r\n"sv);
|
||
TRY(m_socket->write(header_builder.to_byte_buffer()));
|
||
TRY(m_socket->write(content_builder.to_byte_buffer()));
|
||
|
||
log_response(error.http_status, request);
|
||
return {};
|
||
}
|
||
|
||
void Client::log_response(unsigned code, HTTP::HttpRequest const& request)
|
||
{
|
||
outln("{} :: {:03d} :: {} {}", Core::DateTime::now().to_string(), code, request.method_name(), request.resource());
|
||
}
|
||
|
||
// https://w3c.github.io/webdriver/#dfn-match-a-request
|
||
ErrorOr<Client::RoutingResult, Web::WebDriver::Error> Client::match_route(HTTP::HttpRequest::Method method, String const& resource)
|
||
{
|
||
// FIXME: Implement to spec.
|
||
|
||
dbgln_if(WEBDRIVER_DEBUG, "match_route({}, {})", HTTP::to_string(method), resource);
|
||
|
||
// https://w3c.github.io/webdriver/webdriver-spec.html#routing-requests
|
||
if (!resource.starts_with(m_prefix))
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnknownCommand, "The resource doesn't start with the prefix.");
|
||
|
||
Vector<StringView> resource_split = resource.substring_view(m_prefix.length()).split_view('/', SplitBehavior::KeepEmpty);
|
||
Vector<StringView> parameters;
|
||
|
||
bool matched_path = false;
|
||
|
||
for (auto const& route : Client::s_routes) {
|
||
dbgln_if(WEBDRIVER_DEBUG, "- Checking {} {}", HTTP::to_string(route.method), String::join("/"sv, route.path));
|
||
if (resource_split.size() != route.path.size()) {
|
||
dbgln_if(WEBDRIVER_DEBUG, "-> Discarding: Wrong length");
|
||
continue;
|
||
}
|
||
|
||
bool match = true;
|
||
for (size_t i = 0; i < route.path.size(); ++i) {
|
||
if (route.path[i].starts_with(':')) {
|
||
parameters.append(resource_split[i]);
|
||
continue;
|
||
}
|
||
|
||
if (route.path[i] != resource_split[i]) {
|
||
match = false;
|
||
parameters.clear();
|
||
dbgln_if(WEBDRIVER_DEBUG, "-> Discarding: Part `{}` does not match `{}`", route.path[i], resource_split[i]);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (match && route.method == method) {
|
||
dbgln_if(WEBDRIVER_DEBUG, "-> Matched! :^)");
|
||
return RoutingResult { route.handler, parameters };
|
||
}
|
||
matched_path = true;
|
||
}
|
||
|
||
// Matched a path, but didn't match a known method
|
||
if (matched_path) {
|
||
dbgln_if(WEBDRIVER_DEBUG, "- A path matched, but method didn't. :^(");
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnknownMethod, "The command matched a known URL but did not match a method for that URL.");
|
||
}
|
||
|
||
// Didn't have any match
|
||
dbgln_if(WEBDRIVER_DEBUG, "- No matches. :^(");
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnknownCommand, "The command was not recognized.");
|
||
}
|
||
|
||
ErrorOr<Session*, Web::WebDriver::Error> Client::find_session_with_id(StringView session_id)
|
||
{
|
||
auto session_id_or_error = session_id.to_uint<>();
|
||
if (!session_id_or_error.has_value())
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
|
||
|
||
for (auto& session : Client::s_sessions) {
|
||
if (session.session_id() == session_id_or_error.value())
|
||
return &session;
|
||
}
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
|
||
}
|
||
|
||
ErrorOr<NonnullOwnPtr<Session>, Web::WebDriver::Error> Client::take_session_with_id(StringView session_id)
|
||
{
|
||
auto session_id_or_error = session_id.to_uint<>();
|
||
if (!session_id_or_error.has_value())
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
|
||
|
||
for (size_t i = 0; i < Client::s_sessions.size(); ++i) {
|
||
if (Client::s_sessions[i].session_id() == session_id_or_error.value()) {
|
||
return Client::s_sessions.take(i);
|
||
}
|
||
}
|
||
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
|
||
}
|
||
|
||
void Client::close_session(unsigned session_id)
|
||
{
|
||
bool found = Client::s_sessions.remove_first_matching([&](auto const& it) {
|
||
return it->session_id() == session_id;
|
||
});
|
||
|
||
if (found)
|
||
dbgln_if(WEBDRIVER_DEBUG, "Shut down session {}", session_id);
|
||
else
|
||
dbgln_if(WEBDRIVER_DEBUG, "Unable to shut down session {}: Not found", session_id);
|
||
}
|
||
|
||
JsonValue Client::make_json_value(JsonValue const& value)
|
||
{
|
||
JsonObject result;
|
||
result.set("value", value);
|
||
return result;
|
||
}
|
||
|
||
// 8.1 New Session, https://w3c.github.io/webdriver/#dfn-new-sessions
|
||
// POST /session
|
||
Web::WebDriver::Response Client::handle_new_session(Vector<StringView> const&, JsonValue const&)
|
||
{
|
||
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: 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.
|
||
|
||
// FIXME: 4. Let capabilities be the result of trying to process capabilities with parameters as an argument.
|
||
auto capabilities = JsonObject {};
|
||
|
||
// FIXME: 5. If capabilities’s is null, return error with error code session not created.
|
||
|
||
// 6. Let session id be the result of generating a UUID.
|
||
// FIXME: Actually create a UUID.
|
||
auto session_id = Client::s_next_session_id++;
|
||
|
||
// 7. Let session be a new session with the session ID of session id.
|
||
NonnullOwnPtr<Session> session = make<Session>(session_id, *this);
|
||
auto start_result = session->start();
|
||
if (start_result.is_error()) {
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::SessionNotCreated, String::formatted("Failed to start session: {}", start_result.error().string_literal()));
|
||
}
|
||
|
||
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.
|
||
Client::s_sessions.append(move(session));
|
||
|
||
// 11. Let body be a JSON Object initialized with:
|
||
JsonObject body;
|
||
// "sessionId"
|
||
// session id
|
||
body.set("sessionId", String::number(session_id));
|
||
// "capabilities"
|
||
// capabilities
|
||
body.set("capabilities", move(capabilities));
|
||
|
||
// FIXME: 12. Initialize the following from capabilities:
|
||
// NOTE: See spec for steps
|
||
|
||
// 13. Set the webdriver-active flag to true.
|
||
web_content_connection.async_set_is_webdriver_active(true);
|
||
|
||
// 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: 15. Set the request queue to a new queue.
|
||
|
||
// 16. Return success with data body.
|
||
return make_json_value(body);
|
||
}
|
||
|
||
// 8.2 Delete Session, https://w3c.github.io/webdriver/#dfn-delete-session
|
||
// DELETE /session/{session id}
|
||
Web::WebDriver::Response Client::handle_delete_session(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>");
|
||
|
||
// 1. If the current session is an active session, try to close the session.
|
||
auto session = TRY(take_session_with_id(parameters[0]));
|
||
TRY(session->stop());
|
||
|
||
// 2. Return success with data null.
|
||
return make_json_value(JsonValue());
|
||
}
|
||
|
||
// 8.3 Status, https://w3c.github.io/webdriver/#dfn-status
|
||
// GET /status
|
||
Web::WebDriver::Response Client::handle_get_status(Vector<StringView> const&, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /status");
|
||
|
||
// 1. Let body be a new JSON Object with the following properties:
|
||
// "ready"
|
||
// The remote end’s readiness state.
|
||
// "message"
|
||
// An implementation-defined string explaining the remote end’s readiness state.
|
||
// FIXME: Report if we are somehow not ready.
|
||
JsonObject body;
|
||
body.set("ready", true);
|
||
body.set("message", "Ready to start some sessions!");
|
||
|
||
// 2. Return success with data body.
|
||
return JsonValue { body };
|
||
}
|
||
|
||
// 9.1 Get Timeouts, https://w3c.github.io/webdriver/#dfn-get-timeouts
|
||
// GET /session/{session id}/timeouts
|
||
Web::WebDriver::Response Client::handle_get_timeouts(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session id>/timeouts");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = session->get_timeouts();
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 9.2 Set Timeouts, https://w3c.github.io/webdriver/#dfn-set-timeouts
|
||
// POST /session/{session id}/timeouts
|
||
Web::WebDriver::Response Client::handle_set_timeouts(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session id>/timeouts");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->set_timeouts(payload));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 10.1 Navigate To, https://w3c.github.io/webdriver/#dfn-navigate-to
|
||
// POST /session/{session id}/url
|
||
Web::WebDriver::Response Client::handle_navigate_to(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/url");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
return session->web_content_connection().navigate_to(payload);
|
||
}
|
||
|
||
// 10.2 Get Current URL, https://w3c.github.io/webdriver/#dfn-get-current-url
|
||
// GET /session/{session id}/url
|
||
Web::WebDriver::Response Client::handle_get_current_url(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/url");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
return session->web_content_connection().get_current_url();
|
||
}
|
||
|
||
// 10.3 Back, https://w3c.github.io/webdriver/#dfn-back
|
||
// POST /session/{session id}/back
|
||
Web::WebDriver::Response Client::handle_back(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/back");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->back());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 10.4 Forward, https://w3c.github.io/webdriver/#dfn-forward
|
||
// POST /session/{session id}/forward
|
||
Web::WebDriver::Response Client::handle_forward(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/forward");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->forward());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 10.5 Refresh, https://w3c.github.io/webdriver/#dfn-refresh
|
||
// POST /session/{session id}/refresh
|
||
Web::WebDriver::Response Client::handle_refresh(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/refresh");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->refresh());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 10.6 Get Title, https://w3c.github.io/webdriver/#dfn-get-title
|
||
// GET /session/{session id}/title
|
||
Web::WebDriver::Response Client::handle_get_title(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/title");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_title());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 11.1 Get Window Handle, https://w3c.github.io/webdriver/#get-window-handle
|
||
// GET /session/{session id}/window
|
||
Web::WebDriver::Response Client::handle_get_window_handle(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/window");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_window_handle());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 11.2 Close Window, https://w3c.github.io/webdriver/#dfn-close-window
|
||
// DELETE /session/{session id}/window
|
||
Web::WebDriver::Response Client::handle_close_window(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>/window");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
TRY(unwrap_result(session->close_window()));
|
||
return make_json_value(JsonValue());
|
||
}
|
||
|
||
// 11.4 Get Window Handles, https://w3c.github.io/webdriver/#dfn-get-window-handles
|
||
// GET /session/{session id}/window/handles
|
||
Web::WebDriver::Response Client::handle_get_window_handles(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/window/handles");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_window_handles());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 11.8.1 Get Window Rect, https://w3c.github.io/webdriver/#dfn-get-window-rect
|
||
// GET /session/{session id}/window/rect
|
||
Web::WebDriver::Response Client::handle_get_window_rect(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/window/rect");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
return session->web_content_connection().get_window_rect();
|
||
}
|
||
|
||
// 11.8.2 Set Window Rect, https://w3c.github.io/webdriver/#dfn-set-window-rect
|
||
// POST /session/{session id}/window/rect
|
||
Web::WebDriver::Response Client::handle_set_window_rect(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/window/rect");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
return session->web_content_connection().set_window_rect(payload);
|
||
}
|
||
|
||
// 11.8.3 Maximize Window, https://w3c.github.io/webdriver/#dfn-maximize-window
|
||
// POST /session/{session id}/window/maximize
|
||
Web::WebDriver::Response Client::handle_maximize_window(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/window/maximize");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
return session->web_content_connection().maximize_window();
|
||
}
|
||
|
||
// 11.8.4 Minimize Window, https://w3c.github.io/webdriver/#minimize-window
|
||
// POST /session/{session id}/window/minimize
|
||
Web::WebDriver::Response Client::handle_minimize_window(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/window/minimize");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
return session->web_content_connection().minimize_window();
|
||
}
|
||
|
||
// 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element
|
||
// POST /session/{session id}/element
|
||
Web::WebDriver::Response Client::handle_find_element(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
return session->web_content_connection().find_element(payload);
|
||
}
|
||
|
||
// 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
|
||
// POST /session/{session id}/elements
|
||
Web::WebDriver::Response Client::handle_find_elements(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/elements");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->find_elements(payload));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.3.4 Find Element From Element, https://w3c.github.io/webdriver/#dfn-find-element-from-element
|
||
// POST /session/{session id}/element/{element id}/element
|
||
Web::WebDriver::Response Client::handle_find_element_from_element(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element/<element_id>/element");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->find_element_from_element(payload, parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.3.5 Find Elements From Element, https://w3c.github.io/webdriver/#dfn-find-elements-from-element
|
||
// POST /session/{session id}/element/{element id}/elements
|
||
Web::WebDriver::Response Client::handle_find_elements_from_element(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element/<element_id>/elements");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->find_elements_from_element(payload, parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.4.1 Is Element Selected, https://w3c.github.io/webdriver/#dfn-is-element-selected
|
||
// GET /session/{session id}/element/{element id}/selected
|
||
Web::WebDriver::Response Client::handle_is_element_selected(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/selected");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->is_element_selected(parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.4.2 Get Element Attribute, https://w3c.github.io/webdriver/#dfn-get-element-attribute
|
||
// GET /session/{session id}/element/{element id}/attribute/{name}
|
||
Web::WebDriver::Response Client::handle_get_element_attribute(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/attribute/<name>");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_element_attribute(payload, parameters[1], parameters[2]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.4.3 Get Element Property, https://w3c.github.io/webdriver/#dfn-get-element-property
|
||
// GET /session/{session id}/element/{element id}/property/{name}
|
||
Web::WebDriver::Response Client::handle_get_element_property(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/property/<name>");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_element_property(payload, parameters[1], parameters[2]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.4.4 Get Element CSS Value, https://w3c.github.io/webdriver/#dfn-get-element-css-value
|
||
// GET /session/{session id}/element/{element id}/css/{property name}
|
||
Web::WebDriver::Response Client::handle_get_element_css_value(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/css/<property_name>");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_element_css_value(payload, parameters[1], parameters[2]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.4.5 Get Element Text, https://w3c.github.io/webdriver/#dfn-get-element-text
|
||
// GET /session/{session id}/element/{element id}/text
|
||
Web::WebDriver::Response Client::handle_get_element_text(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/text");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_element_text(payload, parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.4.6 Get Element Tag Name, https://w3c.github.io/webdriver/#dfn-get-element-tag-name
|
||
// GET /session/{session id}/element/{element id}/name
|
||
Web::WebDriver::Response Client::handle_get_element_tag_name(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/name");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_element_tag_name(payload, parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.4.7 Get Element Rect, https://w3c.github.io/webdriver/#dfn-get-element-rect
|
||
// GET /session/{session id}/element/{element id}/rect
|
||
Web::WebDriver::Response Client::handle_get_element_rect(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/rect");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_element_rect(parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 12.4.8 Is Element Enabled, https://w3c.github.io/webdriver/#dfn-is-element-enabled
|
||
// GET /session/{session id}/element/{element id}/enabled
|
||
Web::WebDriver::Response Client::handle_is_element_enabled(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/enabled");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->is_element_enabled(parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source
|
||
// GET /session/{session id}/source
|
||
Web::WebDriver::Response Client::handle_get_source(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/source");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->get_source());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 13.2.1 Execute Script, https://w3c.github.io/webdriver/#dfn-execute-script
|
||
// POST /session/{session id}/execute/sync
|
||
Web::WebDriver::Response Client::handle_execute_script(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/execute/sync");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->execute_script(payload));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 13.2.2 Execute Async Script, https://w3c.github.io/webdriver/#dfn-execute-async-script
|
||
// POST /session/{session id}/execute/async
|
||
Web::WebDriver::Response Client::handle_execute_async_script(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/execute/async");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->execute_async_script(payload));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies
|
||
// GET /session/{session id}/cookie
|
||
Web::WebDriver::Response Client::handle_get_all_cookies(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/cookie");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto cookies = TRY(session->get_all_cookies());
|
||
return make_json_value(cookies);
|
||
}
|
||
|
||
// 14.2 Get Named Cookie, https://w3c.github.io/webdriver/#dfn-get-named-cookie
|
||
// GET /session/{session id}/cookie/{name}
|
||
Web::WebDriver::Response Client::handle_get_named_cookie(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/cookie/<name>");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto cookies = TRY(session->get_named_cookie(parameters[1]));
|
||
return make_json_value(cookies);
|
||
}
|
||
|
||
// 14.3 Add Cookie, https://w3c.github.io/webdriver/#dfn-adding-a-cookie
|
||
// POST /session/{session id}/cookie
|
||
Web::WebDriver::Response Client::handle_add_cookie(Vector<StringView> const& parameters, JsonValue const& payload)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/cookie");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->add_cookie(payload));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 14.4 Delete Cookie, https://w3c.github.io/webdriver/#dfn-delete-cookie
|
||
// DELETE /session/{session id}/cookie/{name}
|
||
Web::WebDriver::Response Client::handle_delete_cookie(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>/cookie/<name>");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->delete_cookie(parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 14.5 Delete All Cookies, https://w3c.github.io/webdriver/#dfn-delete-all-cookies
|
||
// DELETE /session/{session id}/cookie
|
||
Web::WebDriver::Response Client::handle_delete_all_cookies(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>/cookie");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->delete_all_cookies());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 17.1 Take Screenshot, https://w3c.github.io/webdriver/#take-screenshot
|
||
// GET /session/{session id}/screenshot
|
||
Web::WebDriver::Response Client::handle_take_screenshot(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/screenshot");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->take_screenshot());
|
||
return make_json_value(result);
|
||
}
|
||
|
||
// 17.2 Take Element Screenshot, https://w3c.github.io/webdriver/#dfn-take-element-screenshot
|
||
// GET /session/{session id}/element/{element id}/screenshot
|
||
Web::WebDriver::Response Client::handle_take_element_screenshot(Vector<StringView> const& parameters, JsonValue const&)
|
||
{
|
||
dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/screenshot");
|
||
auto* session = TRY(find_session_with_id(parameters[0]));
|
||
auto result = TRY(session->take_element_screenshot(parameters[1]));
|
||
return make_json_value(result);
|
||
}
|
||
|
||
}
|