/* * Copyright (c) 2022, Florent Castelli * Copyright (c) 2022, Sam Atkins * Copyright (c) 2022, Tobias Christiansen * Copyright (c) 2022, Linus Groh * Copyright (c) 2022, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include namespace WebDriver { Atomic Client::s_next_session_id; NonnullOwnPtrVector Client::s_sessions; Vector 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 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 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 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 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 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::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 resource_split = resource.substring_view(m_prefix.length()).split_view('/', SplitBehavior::KeepEmpty); Vector 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 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, 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 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 = make(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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/"); // 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 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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//elements"); auto* session = TRY(find_session_with_id(parameters[0])); return session->web_content_connection().find_elements(payload); } // 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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//element//element"); auto* session = TRY(find_session_with_id(parameters[0])); return session->web_content_connection().find_element_from_element(payload, parameters[1]); } // 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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//element//elements"); auto* session = TRY(find_session_with_id(parameters[0])); return session->web_content_connection().find_elements_from_element(payload, parameters[1]); } // 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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//selected"); auto* session = TRY(find_session_with_id(parameters[0])); return session->web_content_connection().is_element_selected(parameters[1]); } // 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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//attribute/"); auto* session = TRY(find_session_with_id(parameters[0])); return session->web_content_connection().get_element_attribute(parameters[1], parameters[2]); } // 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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//property/"); auto* session = TRY(find_session_with_id(parameters[0])); return session->web_content_connection().get_element_property(parameters[1], parameters[2]); } // 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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//css/"); auto* session = TRY(find_session_with_id(parameters[0])); return session->web_content_connection().get_element_css_value(parameters[1], parameters[2]); } // 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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//cookie/"); 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 const& parameters, JsonValue const& payload) { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session//cookie/"); 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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//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 const& parameters, JsonValue const&) { dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session//element//screenshot"); auto* session = TRY(find_session_with_id(parameters[0])); auto result = TRY(session->take_element_screenshot(parameters[1])); return make_json_value(result); } }