Client.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. /*
  2. * Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
  3. * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
  4. * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
  5. * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
  6. * Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include <AK/Debug.h>
  11. #include <AK/JsonObject.h>
  12. #include <AK/JsonParser.h>
  13. #include <AK/JsonValue.h>
  14. #include <LibCore/DateTime.h>
  15. #include <LibCore/MemoryStream.h>
  16. #include <LibHTTP/HttpRequest.h>
  17. #include <LibHTTP/HttpResponse.h>
  18. #include <LibWeb/WebDriver/TimeoutsConfiguration.h>
  19. #include <WebDriver/Client.h>
  20. #include <WebDriver/Session.h>
  21. namespace WebDriver {
  22. Atomic<unsigned> Client::s_next_session_id;
  23. NonnullOwnPtrVector<Session> Client::s_sessions;
  24. Vector<Client::Route> Client::s_routes = {
  25. { HTTP::HttpRequest::Method::POST, { "session" }, &Client::handle_new_session },
  26. { HTTP::HttpRequest::Method::DELETE, { "session", ":session_id" }, &Client::handle_delete_session },
  27. { HTTP::HttpRequest::Method::GET, { "status" }, &Client::handle_get_status },
  28. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "timeouts" }, &Client::handle_get_timeouts },
  29. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "timeouts" }, &Client::handle_set_timeouts },
  30. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "url" }, &Client::handle_navigate_to },
  31. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "url" }, &Client::handle_get_current_url },
  32. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "back" }, &Client::handle_back },
  33. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "forward" }, &Client::handle_forward },
  34. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "refresh" }, &Client::handle_refresh },
  35. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "title" }, &Client::handle_get_title },
  36. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "window" }, &Client::handle_get_window_handle },
  37. { HTTP::HttpRequest::Method::DELETE, { "session", ":session_id", "window" }, &Client::handle_close_window },
  38. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "window", "handles" }, &Client::handle_get_window_handles },
  39. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "window", "rect" }, &Client::handle_get_window_rect },
  40. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "window", "rect" }, &Client::handle_set_window_rect },
  41. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "window", "maximize" }, &Client::handle_maximize_window },
  42. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "window", "minimize" }, &Client::handle_minimize_window },
  43. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "window", "fullscreen" }, &Client::handle_fullscreen_window },
  44. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "element" }, &Client::handle_find_element },
  45. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "elements" }, &Client::handle_find_elements },
  46. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "element", ":element_id", "element" }, &Client::handle_find_element_from_element },
  47. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "element", ":element_id", "elements" }, &Client::handle_find_elements_from_element },
  48. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "selected" }, &Client::handle_is_element_selected },
  49. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "attribute", ":name" }, &Client::handle_get_element_attribute },
  50. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "property", ":name" }, &Client::handle_get_element_property },
  51. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "css", ":property_name" }, &Client::handle_get_element_css_value },
  52. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "text" }, &Client::handle_get_element_text },
  53. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "name" }, &Client::handle_get_element_tag_name },
  54. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "rect" }, &Client::handle_get_element_rect },
  55. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "enabled" }, &Client::handle_is_element_enabled },
  56. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "source" }, &Client::handle_get_source },
  57. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "execute", "sync" }, &Client::handle_execute_script },
  58. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "execute", "async" }, &Client::handle_execute_async_script },
  59. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "cookie" }, &Client::handle_get_all_cookies },
  60. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "cookie", ":name" }, &Client::handle_get_named_cookie },
  61. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "cookie" }, &Client::handle_add_cookie },
  62. { HTTP::HttpRequest::Method::DELETE, { "session", ":session_id", "cookie", ":name" }, &Client::handle_delete_cookie },
  63. { HTTP::HttpRequest::Method::DELETE, { "session", ":session_id", "cookie" }, &Client::handle_delete_all_cookies },
  64. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "screenshot" }, &Client::handle_take_screenshot },
  65. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "element", ":element_id", "screenshot" }, &Client::handle_take_element_screenshot },
  66. };
  67. Client::Client(NonnullOwnPtr<Core::Stream::BufferedTCPSocket> socket, Core::Object* parent)
  68. : Core::Object(parent)
  69. , m_socket(move(socket))
  70. {
  71. }
  72. void Client::die()
  73. {
  74. m_socket->close();
  75. deferred_invoke([this] { remove_from_parent(); });
  76. }
  77. void Client::start()
  78. {
  79. m_socket->on_ready_to_read = [this] {
  80. StringBuilder builder;
  81. // FIXME: All this should be moved to LibHTTP and be made spec compliant
  82. auto maybe_buffer = ByteBuffer::create_uninitialized(m_socket->buffer_size());
  83. if (maybe_buffer.is_error()) {
  84. warnln("Could not create buffer for client: {}", maybe_buffer.error());
  85. die();
  86. return;
  87. }
  88. auto buffer = maybe_buffer.release_value();
  89. for (;;) {
  90. auto maybe_can_read = m_socket->can_read_without_blocking();
  91. if (maybe_can_read.is_error()) {
  92. warnln("Failed to get the blocking status for the socket: {}", maybe_can_read.error());
  93. die();
  94. return;
  95. }
  96. if (!maybe_can_read.value())
  97. break;
  98. auto maybe_data = m_socket->read(buffer);
  99. if (maybe_data.is_error()) {
  100. warnln("Failed to read data from the request: {}", maybe_data.error());
  101. die();
  102. return;
  103. }
  104. if (m_socket->is_eof()) {
  105. die();
  106. break;
  107. }
  108. builder.append(StringView(maybe_data.value()));
  109. }
  110. auto request = builder.to_byte_buffer();
  111. auto http_request_or_error = HTTP::HttpRequest::from_raw_request(request);
  112. if (!http_request_or_error.has_value())
  113. return;
  114. auto http_request = http_request_or_error.release_value();
  115. auto body_or_error = read_body_as_json(http_request);
  116. if (body_or_error.is_error()) {
  117. warnln("Failed to read the request body: {}", body_or_error.error());
  118. die();
  119. return;
  120. }
  121. auto maybe_did_handle = handle_request(http_request, body_or_error.value());
  122. if (maybe_did_handle.is_error()) {
  123. warnln("Failed to handle the request: {}", maybe_did_handle.error());
  124. }
  125. die();
  126. };
  127. }
  128. ErrorOr<JsonValue> Client::read_body_as_json(HTTP::HttpRequest const& request)
  129. {
  130. // If we received a multipart body here, this would fail badly.
  131. unsigned content_length = 0;
  132. for (auto const& header : request.headers()) {
  133. if (header.name.equals_ignoring_case("Content-Length"sv)) {
  134. content_length = header.value.to_int(TrimWhitespace::Yes).value_or(0);
  135. break;
  136. }
  137. }
  138. if (!content_length)
  139. return JsonValue();
  140. // FIXME: Check the Content-Type is actually application/json
  141. JsonParser json_parser(request.body());
  142. return json_parser.parse();
  143. }
  144. ErrorOr<bool> Client::handle_request(HTTP::HttpRequest const& request, JsonValue const& body)
  145. {
  146. if constexpr (WEBDRIVER_DEBUG) {
  147. dbgln("Got HTTP request: {} {}", request.method_name(), request.resource());
  148. if (!body.is_null())
  149. dbgln("Body: {}", body.to_string());
  150. }
  151. auto routing_result_match = match_route(request.method(), request.resource());
  152. if (routing_result_match.is_error()) {
  153. auto error = routing_result_match.release_error();
  154. dbgln_if(WEBDRIVER_DEBUG, "Failed to match route: {}", error);
  155. TRY(send_error_response(error, request));
  156. return false;
  157. }
  158. auto routing_result = routing_result_match.release_value();
  159. auto result = (this->*routing_result.handler)(routing_result.parameters, body);
  160. if (result.is_error()) {
  161. dbgln_if(WEBDRIVER_DEBUG, "Error in calling route handler: {}", result.error());
  162. TRY(send_error_response(result.release_error(), request));
  163. return false;
  164. }
  165. auto object = result.release_value();
  166. TRY(send_response(object.to_string(), request));
  167. return true;
  168. }
  169. // https://w3c.github.io/webdriver/#dfn-send-a-response
  170. ErrorOr<void> Client::send_response(StringView content, HTTP::HttpRequest const& request)
  171. {
  172. // FIXME: Implement to spec.
  173. StringBuilder builder;
  174. builder.append("HTTP/1.0 200 OK\r\n"sv);
  175. builder.append("Server: WebDriver (SerenityOS)\r\n"sv);
  176. builder.append("X-Frame-Options: SAMEORIGIN\r\n"sv);
  177. builder.append("X-Content-Type-Options: nosniff\r\n"sv);
  178. builder.append("Pragma: no-cache\r\n"sv);
  179. builder.append("Content-Type: application/json; charset=utf-8\r\n"sv);
  180. builder.appendff("Content-Length: {}\r\n", content.length());
  181. builder.append("\r\n"sv);
  182. auto builder_contents = builder.to_byte_buffer();
  183. TRY(m_socket->write(builder_contents));
  184. while (!content.is_empty()) {
  185. auto bytes_sent = TRY(m_socket->write(content.bytes()));
  186. content = content.substring_view(bytes_sent);
  187. }
  188. log_response(200, request);
  189. auto keep_alive = false;
  190. if (auto it = request.headers().find_if([](auto& header) { return header.name.equals_ignoring_case("Connection"sv); }); !it.is_end()) {
  191. if (it->value.trim_whitespace().equals_ignoring_case("keep-alive"sv))
  192. keep_alive = true;
  193. }
  194. if (!keep_alive)
  195. m_socket->close();
  196. return {};
  197. }
  198. // https://w3c.github.io/webdriver/#dfn-send-an-error
  199. ErrorOr<void> Client::send_error_response(Web::WebDriver::Error const& error, HTTP::HttpRequest const& request)
  200. {
  201. // FIXME: Implement to spec.
  202. dbgln("send_error_response: {} {}: {}", error.http_status, error.error, error.message);
  203. auto reason_phrase = HTTP::HttpResponse::reason_phrase_for_code(error.http_status);
  204. auto result = JsonObject();
  205. result.set("error", error.error);
  206. result.set("message", error.message);
  207. result.set("stacktrace", "");
  208. if (error.data.has_value())
  209. result.set("data", *error.data);
  210. StringBuilder content_builder;
  211. result.serialize(content_builder);
  212. StringBuilder header_builder;
  213. header_builder.appendff("HTTP/1.0 {} ", error.http_status);
  214. header_builder.append(reason_phrase);
  215. header_builder.append("\r\n"sv);
  216. header_builder.append("Content-Type: application/json; charset=UTF-8\r\n"sv);
  217. header_builder.appendff("Content-Length: {}\r\n", content_builder.length());
  218. header_builder.append("\r\n"sv);
  219. TRY(m_socket->write(header_builder.to_byte_buffer()));
  220. TRY(m_socket->write(content_builder.to_byte_buffer()));
  221. log_response(error.http_status, request);
  222. return {};
  223. }
  224. void Client::log_response(unsigned code, HTTP::HttpRequest const& request)
  225. {
  226. outln("{} :: {:03d} :: {} {}", Core::DateTime::now().to_string(), code, request.method_name(), request.resource());
  227. }
  228. // https://w3c.github.io/webdriver/#dfn-match-a-request
  229. ErrorOr<Client::RoutingResult, Web::WebDriver::Error> Client::match_route(HTTP::HttpRequest::Method method, String const& resource)
  230. {
  231. // FIXME: Implement to spec.
  232. dbgln_if(WEBDRIVER_DEBUG, "match_route({}, {})", HTTP::to_string(method), resource);
  233. // https://w3c.github.io/webdriver/webdriver-spec.html#routing-requests
  234. if (!resource.starts_with(m_prefix))
  235. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnknownCommand, "The resource doesn't start with the prefix.");
  236. Vector<StringView> resource_split = resource.substring_view(m_prefix.length()).split_view('/', SplitBehavior::KeepEmpty);
  237. Vector<StringView> parameters;
  238. bool matched_path = false;
  239. for (auto const& route : Client::s_routes) {
  240. dbgln_if(WEBDRIVER_DEBUG, "- Checking {} {}", HTTP::to_string(route.method), String::join("/"sv, route.path));
  241. if (resource_split.size() != route.path.size()) {
  242. dbgln_if(WEBDRIVER_DEBUG, "-> Discarding: Wrong length");
  243. continue;
  244. }
  245. bool match = true;
  246. for (size_t i = 0; i < route.path.size(); ++i) {
  247. if (route.path[i].starts_with(':')) {
  248. parameters.append(resource_split[i]);
  249. continue;
  250. }
  251. if (route.path[i] != resource_split[i]) {
  252. match = false;
  253. parameters.clear();
  254. dbgln_if(WEBDRIVER_DEBUG, "-> Discarding: Part `{}` does not match `{}`", route.path[i], resource_split[i]);
  255. break;
  256. }
  257. }
  258. if (match && route.method == method) {
  259. dbgln_if(WEBDRIVER_DEBUG, "-> Matched! :^)");
  260. return RoutingResult { route.handler, parameters };
  261. }
  262. matched_path = true;
  263. }
  264. // Matched a path, but didn't match a known method
  265. if (matched_path) {
  266. dbgln_if(WEBDRIVER_DEBUG, "- A path matched, but method didn't. :^(");
  267. 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.");
  268. }
  269. // Didn't have any match
  270. dbgln_if(WEBDRIVER_DEBUG, "- No matches. :^(");
  271. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnknownCommand, "The command was not recognized.");
  272. }
  273. ErrorOr<Session*, Web::WebDriver::Error> Client::find_session_with_id(StringView session_id)
  274. {
  275. auto session_id_or_error = session_id.to_uint<>();
  276. if (!session_id_or_error.has_value())
  277. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
  278. for (auto& session : Client::s_sessions) {
  279. if (session.session_id() == session_id_or_error.value())
  280. return &session;
  281. }
  282. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
  283. }
  284. ErrorOr<NonnullOwnPtr<Session>, Web::WebDriver::Error> Client::take_session_with_id(StringView session_id)
  285. {
  286. auto session_id_or_error = session_id.to_uint<>();
  287. if (!session_id_or_error.has_value())
  288. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
  289. for (size_t i = 0; i < Client::s_sessions.size(); ++i) {
  290. if (Client::s_sessions[i].session_id() == session_id_or_error.value()) {
  291. return Client::s_sessions.take(i);
  292. }
  293. }
  294. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSessionId, "Invalid session id");
  295. }
  296. void Client::close_session(unsigned session_id)
  297. {
  298. bool found = Client::s_sessions.remove_first_matching([&](auto const& it) {
  299. return it->session_id() == session_id;
  300. });
  301. if (found)
  302. dbgln_if(WEBDRIVER_DEBUG, "Shut down session {}", session_id);
  303. else
  304. dbgln_if(WEBDRIVER_DEBUG, "Unable to shut down session {}: Not found", session_id);
  305. }
  306. JsonValue Client::make_json_value(JsonValue const& value)
  307. {
  308. JsonObject result;
  309. result.set("value", value);
  310. return result;
  311. }
  312. // 8.1 New Session, https://w3c.github.io/webdriver/#dfn-new-sessions
  313. // POST /session
  314. Web::WebDriver::Response Client::handle_new_session(Vector<StringView> const&, JsonValue const&)
  315. {
  316. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session");
  317. // FIXME: 1. If the maximum active sessions is equal to the length of the list of active sessions,
  318. // return error with error code session not created.
  319. // FIXME: 2. If the remote end is an intermediary node, take implementation-defined steps that either
  320. // result in returning an error with error code session not created, or in returning a
  321. // success with data that is isomorphic to that returned by remote ends according to the
  322. // rest of this algorithm. If an error is not returned, the intermediary node must retain a
  323. // reference to the session created on the upstream node as the associated session such
  324. // that commands may be forwarded to this associated session on subsequent commands.
  325. // FIXME: 3. If the maximum active sessions is equal to the length of the list of active sessions,
  326. // return error with error code session not created.
  327. // FIXME: 4. Let capabilities be the result of trying to process capabilities with parameters as an argument.
  328. auto capabilities = JsonObject {};
  329. // FIXME: 5. If capabilities’s is null, return error with error code session not created.
  330. // 6. Let session id be the result of generating a UUID.
  331. // FIXME: Actually create a UUID.
  332. auto session_id = Client::s_next_session_id++;
  333. // 7. Let session be a new session with the session ID of session id.
  334. NonnullOwnPtr<Session> session = make<Session>(session_id, *this);
  335. auto start_result = session->start();
  336. if (start_result.is_error()) {
  337. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::SessionNotCreated, String::formatted("Failed to start session: {}", start_result.error().string_literal()));
  338. }
  339. auto& web_content_connection = session->web_content_connection();
  340. // FIXME: 8. Set the current session to session.
  341. // FIXME: 9. Run any WebDriver new session algorithm defined in external specifications,
  342. // with arguments session and capabilities.
  343. // 10. Append session to active sessions.
  344. Client::s_sessions.append(move(session));
  345. // 11. Let body be a JSON Object initialized with:
  346. JsonObject body;
  347. // "sessionId"
  348. // session id
  349. body.set("sessionId", String::number(session_id));
  350. // "capabilities"
  351. // capabilities
  352. body.set("capabilities", move(capabilities));
  353. // FIXME: 12. Initialize the following from capabilities:
  354. // NOTE: See spec for steps
  355. // 13. Set the webdriver-active flag to true.
  356. web_content_connection.async_set_is_webdriver_active(true);
  357. // FIXME: 14. Set the current top-level browsing context for session with the top-level browsing context
  358. // of the UA’s current browsing context.
  359. // FIXME: 15. Set the request queue to a new queue.
  360. // 16. Return success with data body.
  361. return make_json_value(body);
  362. }
  363. // 8.2 Delete Session, https://w3c.github.io/webdriver/#dfn-delete-session
  364. // DELETE /session/{session id}
  365. Web::WebDriver::Response Client::handle_delete_session(Vector<StringView> const& parameters, JsonValue const&)
  366. {
  367. dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>");
  368. // 1. If the current session is an active session, try to close the session.
  369. auto session = TRY(take_session_with_id(parameters[0]));
  370. TRY(session->stop());
  371. // 2. Return success with data null.
  372. return make_json_value(JsonValue());
  373. }
  374. // 8.3 Status, https://w3c.github.io/webdriver/#dfn-status
  375. // GET /status
  376. Web::WebDriver::Response Client::handle_get_status(Vector<StringView> const&, JsonValue const&)
  377. {
  378. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /status");
  379. // 1. Let body be a new JSON Object with the following properties:
  380. // "ready"
  381. // The remote end’s readiness state.
  382. // "message"
  383. // An implementation-defined string explaining the remote end’s readiness state.
  384. // FIXME: Report if we are somehow not ready.
  385. JsonObject body;
  386. body.set("ready", true);
  387. body.set("message", "Ready to start some sessions!");
  388. // 2. Return success with data body.
  389. return JsonValue { body };
  390. }
  391. // 9.1 Get Timeouts, https://w3c.github.io/webdriver/#dfn-get-timeouts
  392. // GET /session/{session id}/timeouts
  393. Web::WebDriver::Response Client::handle_get_timeouts(Vector<StringView> const& parameters, JsonValue const&)
  394. {
  395. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session id>/timeouts");
  396. auto* session = TRY(find_session_with_id(parameters[0]));
  397. return session->web_content_connection().get_timeouts();
  398. }
  399. // 9.2 Set Timeouts, https://w3c.github.io/webdriver/#dfn-set-timeouts
  400. // POST /session/{session id}/timeouts
  401. Web::WebDriver::Response Client::handle_set_timeouts(Vector<StringView> const& parameters, JsonValue const& payload)
  402. {
  403. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session id>/timeouts");
  404. auto* session = TRY(find_session_with_id(parameters[0]));
  405. return session->web_content_connection().set_timeouts(payload);
  406. }
  407. // 10.1 Navigate To, https://w3c.github.io/webdriver/#dfn-navigate-to
  408. // POST /session/{session id}/url
  409. Web::WebDriver::Response Client::handle_navigate_to(Vector<StringView> const& parameters, JsonValue const& payload)
  410. {
  411. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/url");
  412. auto* session = TRY(find_session_with_id(parameters[0]));
  413. return session->web_content_connection().navigate_to(payload);
  414. }
  415. // 10.2 Get Current URL, https://w3c.github.io/webdriver/#dfn-get-current-url
  416. // GET /session/{session id}/url
  417. Web::WebDriver::Response Client::handle_get_current_url(Vector<StringView> const& parameters, JsonValue const&)
  418. {
  419. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/url");
  420. auto* session = TRY(find_session_with_id(parameters[0]));
  421. return session->web_content_connection().get_current_url();
  422. }
  423. // 10.3 Back, https://w3c.github.io/webdriver/#dfn-back
  424. // POST /session/{session id}/back
  425. Web::WebDriver::Response Client::handle_back(Vector<StringView> const& parameters, JsonValue const&)
  426. {
  427. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/back");
  428. auto* session = TRY(find_session_with_id(parameters[0]));
  429. return session->web_content_connection().back();
  430. }
  431. // 10.4 Forward, https://w3c.github.io/webdriver/#dfn-forward
  432. // POST /session/{session id}/forward
  433. Web::WebDriver::Response Client::handle_forward(Vector<StringView> const& parameters, JsonValue const&)
  434. {
  435. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/forward");
  436. auto* session = TRY(find_session_with_id(parameters[0]));
  437. return session->web_content_connection().forward();
  438. }
  439. // 10.5 Refresh, https://w3c.github.io/webdriver/#dfn-refresh
  440. // POST /session/{session id}/refresh
  441. Web::WebDriver::Response Client::handle_refresh(Vector<StringView> const& parameters, JsonValue const&)
  442. {
  443. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/refresh");
  444. auto* session = TRY(find_session_with_id(parameters[0]));
  445. return session->web_content_connection().refresh();
  446. }
  447. // 10.6 Get Title, https://w3c.github.io/webdriver/#dfn-get-title
  448. // GET /session/{session id}/title
  449. Web::WebDriver::Response Client::handle_get_title(Vector<StringView> const& parameters, JsonValue const&)
  450. {
  451. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/title");
  452. auto* session = TRY(find_session_with_id(parameters[0]));
  453. return session->web_content_connection().get_title();
  454. }
  455. // 11.1 Get Window Handle, https://w3c.github.io/webdriver/#get-window-handle
  456. // GET /session/{session id}/window
  457. Web::WebDriver::Response Client::handle_get_window_handle(Vector<StringView> const& parameters, JsonValue const&)
  458. {
  459. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/window");
  460. auto* session = TRY(find_session_with_id(parameters[0]));
  461. auto result = TRY(session->get_window_handle());
  462. return make_json_value(result);
  463. }
  464. // 11.2 Close Window, https://w3c.github.io/webdriver/#dfn-close-window
  465. // DELETE /session/{session id}/window
  466. Web::WebDriver::Response Client::handle_close_window(Vector<StringView> const& parameters, JsonValue const&)
  467. {
  468. dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>/window");
  469. auto* session = TRY(find_session_with_id(parameters[0]));
  470. TRY(unwrap_result(session->close_window()));
  471. return make_json_value(JsonValue());
  472. }
  473. // 11.4 Get Window Handles, https://w3c.github.io/webdriver/#dfn-get-window-handles
  474. // GET /session/{session id}/window/handles
  475. Web::WebDriver::Response Client::handle_get_window_handles(Vector<StringView> const& parameters, JsonValue const&)
  476. {
  477. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/window/handles");
  478. auto* session = TRY(find_session_with_id(parameters[0]));
  479. auto result = TRY(session->get_window_handles());
  480. return make_json_value(result);
  481. }
  482. // 11.8.1 Get Window Rect, https://w3c.github.io/webdriver/#dfn-get-window-rect
  483. // GET /session/{session id}/window/rect
  484. Web::WebDriver::Response Client::handle_get_window_rect(Vector<StringView> const& parameters, JsonValue const&)
  485. {
  486. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/window/rect");
  487. auto* session = TRY(find_session_with_id(parameters[0]));
  488. return session->web_content_connection().get_window_rect();
  489. }
  490. // 11.8.2 Set Window Rect, https://w3c.github.io/webdriver/#dfn-set-window-rect
  491. // POST /session/{session id}/window/rect
  492. Web::WebDriver::Response Client::handle_set_window_rect(Vector<StringView> const& parameters, JsonValue const& payload)
  493. {
  494. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/window/rect");
  495. auto* session = TRY(find_session_with_id(parameters[0]));
  496. return session->web_content_connection().set_window_rect(payload);
  497. }
  498. // 11.8.3 Maximize Window, https://w3c.github.io/webdriver/#dfn-maximize-window
  499. // POST /session/{session id}/window/maximize
  500. Web::WebDriver::Response Client::handle_maximize_window(Vector<StringView> const& parameters, JsonValue const&)
  501. {
  502. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/window/maximize");
  503. auto* session = TRY(find_session_with_id(parameters[0]));
  504. return session->web_content_connection().maximize_window();
  505. }
  506. // 11.8.4 Minimize Window, https://w3c.github.io/webdriver/#minimize-window
  507. // POST /session/{session id}/window/minimize
  508. Web::WebDriver::Response Client::handle_minimize_window(Vector<StringView> const& parameters, JsonValue const&)
  509. {
  510. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/window/minimize");
  511. auto* session = TRY(find_session_with_id(parameters[0]));
  512. return session->web_content_connection().minimize_window();
  513. }
  514. // 11.8.5 Fullscreen Window, https://w3c.github.io/webdriver/#dfn-fullscreen-window
  515. // POST /session/{session id}/window/fullscreen
  516. Web::WebDriver::Response Client::handle_fullscreen_window(Vector<StringView> const& parameters, JsonValue const&)
  517. {
  518. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/window/fullscreen");
  519. auto* session = TRY(find_session_with_id(parameters[0]));
  520. return session->web_content_connection().fullscreen_window();
  521. }
  522. // 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element
  523. // POST /session/{session id}/element
  524. Web::WebDriver::Response Client::handle_find_element(Vector<StringView> const& parameters, JsonValue const& payload)
  525. {
  526. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element");
  527. auto* session = TRY(find_session_with_id(parameters[0]));
  528. return session->web_content_connection().find_element(payload);
  529. }
  530. // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
  531. // POST /session/{session id}/elements
  532. Web::WebDriver::Response Client::handle_find_elements(Vector<StringView> const& parameters, JsonValue const& payload)
  533. {
  534. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/elements");
  535. auto* session = TRY(find_session_with_id(parameters[0]));
  536. return session->web_content_connection().find_elements(payload);
  537. }
  538. // 12.3.4 Find Element From Element, https://w3c.github.io/webdriver/#dfn-find-element-from-element
  539. // POST /session/{session id}/element/{element id}/element
  540. Web::WebDriver::Response Client::handle_find_element_from_element(Vector<StringView> const& parameters, JsonValue const& payload)
  541. {
  542. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element/<element_id>/element");
  543. auto* session = TRY(find_session_with_id(parameters[0]));
  544. return session->web_content_connection().find_element_from_element(payload, parameters[1]);
  545. }
  546. // 12.3.5 Find Elements From Element, https://w3c.github.io/webdriver/#dfn-find-elements-from-element
  547. // POST /session/{session id}/element/{element id}/elements
  548. Web::WebDriver::Response Client::handle_find_elements_from_element(Vector<StringView> const& parameters, JsonValue const& payload)
  549. {
  550. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/element/<element_id>/elements");
  551. auto* session = TRY(find_session_with_id(parameters[0]));
  552. return session->web_content_connection().find_elements_from_element(payload, parameters[1]);
  553. }
  554. // 12.4.1 Is Element Selected, https://w3c.github.io/webdriver/#dfn-is-element-selected
  555. // GET /session/{session id}/element/{element id}/selected
  556. Web::WebDriver::Response Client::handle_is_element_selected(Vector<StringView> const& parameters, JsonValue const&)
  557. {
  558. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/selected");
  559. auto* session = TRY(find_session_with_id(parameters[0]));
  560. return session->web_content_connection().is_element_selected(parameters[1]);
  561. }
  562. // 12.4.2 Get Element Attribute, https://w3c.github.io/webdriver/#dfn-get-element-attribute
  563. // GET /session/{session id}/element/{element id}/attribute/{name}
  564. Web::WebDriver::Response Client::handle_get_element_attribute(Vector<StringView> const& parameters, JsonValue const&)
  565. {
  566. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/attribute/<name>");
  567. auto* session = TRY(find_session_with_id(parameters[0]));
  568. return session->web_content_connection().get_element_attribute(parameters[1], parameters[2]);
  569. }
  570. // 12.4.3 Get Element Property, https://w3c.github.io/webdriver/#dfn-get-element-property
  571. // GET /session/{session id}/element/{element id}/property/{name}
  572. Web::WebDriver::Response Client::handle_get_element_property(Vector<StringView> const& parameters, JsonValue const&)
  573. {
  574. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/property/<name>");
  575. auto* session = TRY(find_session_with_id(parameters[0]));
  576. return session->web_content_connection().get_element_property(parameters[1], parameters[2]);
  577. }
  578. // 12.4.4 Get Element CSS Value, https://w3c.github.io/webdriver/#dfn-get-element-css-value
  579. // GET /session/{session id}/element/{element id}/css/{property name}
  580. Web::WebDriver::Response Client::handle_get_element_css_value(Vector<StringView> const& parameters, JsonValue const&)
  581. {
  582. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/css/<property_name>");
  583. auto* session = TRY(find_session_with_id(parameters[0]));
  584. return session->web_content_connection().get_element_css_value(parameters[1], parameters[2]);
  585. }
  586. // 12.4.5 Get Element Text, https://w3c.github.io/webdriver/#dfn-get-element-text
  587. // GET /session/{session id}/element/{element id}/text
  588. Web::WebDriver::Response Client::handle_get_element_text(Vector<StringView> const& parameters, JsonValue const&)
  589. {
  590. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/text");
  591. auto* session = TRY(find_session_with_id(parameters[0]));
  592. return session->web_content_connection().get_element_text(parameters[1]);
  593. }
  594. // 12.4.6 Get Element Tag Name, https://w3c.github.io/webdriver/#dfn-get-element-tag-name
  595. // GET /session/{session id}/element/{element id}/name
  596. Web::WebDriver::Response Client::handle_get_element_tag_name(Vector<StringView> const& parameters, JsonValue const&)
  597. {
  598. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/name");
  599. auto* session = TRY(find_session_with_id(parameters[0]));
  600. return session->web_content_connection().get_element_tag_name(parameters[1]);
  601. }
  602. // 12.4.7 Get Element Rect, https://w3c.github.io/webdriver/#dfn-get-element-rect
  603. // GET /session/{session id}/element/{element id}/rect
  604. Web::WebDriver::Response Client::handle_get_element_rect(Vector<StringView> const& parameters, JsonValue const&)
  605. {
  606. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/rect");
  607. auto* session = TRY(find_session_with_id(parameters[0]));
  608. return session->web_content_connection().get_element_rect(parameters[1]);
  609. }
  610. // 12.4.8 Is Element Enabled, https://w3c.github.io/webdriver/#dfn-is-element-enabled
  611. // GET /session/{session id}/element/{element id}/enabled
  612. Web::WebDriver::Response Client::handle_is_element_enabled(Vector<StringView> const& parameters, JsonValue const&)
  613. {
  614. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/enabled");
  615. auto* session = TRY(find_session_with_id(parameters[0]));
  616. return session->web_content_connection().is_element_enabled(parameters[1]);
  617. }
  618. // 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source
  619. // GET /session/{session id}/source
  620. Web::WebDriver::Response Client::handle_get_source(Vector<StringView> const& parameters, JsonValue const&)
  621. {
  622. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/source");
  623. auto* session = TRY(find_session_with_id(parameters[0]));
  624. return session->web_content_connection().get_source();
  625. }
  626. // 13.2.1 Execute Script, https://w3c.github.io/webdriver/#dfn-execute-script
  627. // POST /session/{session id}/execute/sync
  628. Web::WebDriver::Response Client::handle_execute_script(Vector<StringView> const& parameters, JsonValue const& payload)
  629. {
  630. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/execute/sync");
  631. auto* session = TRY(find_session_with_id(parameters[0]));
  632. return session->web_content_connection().execute_script(payload);
  633. }
  634. // 13.2.2 Execute Async Script, https://w3c.github.io/webdriver/#dfn-execute-async-script
  635. // POST /session/{session id}/execute/async
  636. Web::WebDriver::Response Client::handle_execute_async_script(Vector<StringView> const& parameters, JsonValue const& payload)
  637. {
  638. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/execute/async");
  639. auto* session = TRY(find_session_with_id(parameters[0]));
  640. return session->web_content_connection().execute_async_script(payload);
  641. }
  642. // 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies
  643. // GET /session/{session id}/cookie
  644. Web::WebDriver::Response Client::handle_get_all_cookies(Vector<StringView> const& parameters, JsonValue const&)
  645. {
  646. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/cookie");
  647. auto* session = TRY(find_session_with_id(parameters[0]));
  648. return session->web_content_connection().get_all_cookies();
  649. }
  650. // 14.2 Get Named Cookie, https://w3c.github.io/webdriver/#dfn-get-named-cookie
  651. // GET /session/{session id}/cookie/{name}
  652. Web::WebDriver::Response Client::handle_get_named_cookie(Vector<StringView> const& parameters, JsonValue const&)
  653. {
  654. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/cookie/<name>");
  655. auto* session = TRY(find_session_with_id(parameters[0]));
  656. return session->web_content_connection().get_named_cookie(parameters[1]);
  657. }
  658. // 14.3 Add Cookie, https://w3c.github.io/webdriver/#dfn-adding-a-cookie
  659. // POST /session/{session id}/cookie
  660. Web::WebDriver::Response Client::handle_add_cookie(Vector<StringView> const& parameters, JsonValue const& payload)
  661. {
  662. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/cookie");
  663. auto* session = TRY(find_session_with_id(parameters[0]));
  664. return session->web_content_connection().add_cookie(payload);
  665. }
  666. // 14.4 Delete Cookie, https://w3c.github.io/webdriver/#dfn-delete-cookie
  667. // DELETE /session/{session id}/cookie/{name}
  668. Web::WebDriver::Response Client::handle_delete_cookie(Vector<StringView> const& parameters, JsonValue const&)
  669. {
  670. dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>/cookie/<name>");
  671. auto* session = TRY(find_session_with_id(parameters[0]));
  672. return session->web_content_connection().delete_cookie(parameters[1]);
  673. }
  674. // 14.5 Delete All Cookies, https://w3c.github.io/webdriver/#dfn-delete-all-cookies
  675. // DELETE /session/{session id}/cookie
  676. Web::WebDriver::Response Client::handle_delete_all_cookies(Vector<StringView> const& parameters, JsonValue const&)
  677. {
  678. dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>/cookie");
  679. auto* session = TRY(find_session_with_id(parameters[0]));
  680. return session->web_content_connection().delete_all_cookies();
  681. }
  682. // 17.1 Take Screenshot, https://w3c.github.io/webdriver/#take-screenshot
  683. // GET /session/{session id}/screenshot
  684. Web::WebDriver::Response Client::handle_take_screenshot(Vector<StringView> const& parameters, JsonValue const&)
  685. {
  686. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/screenshot");
  687. auto* session = TRY(find_session_with_id(parameters[0]));
  688. return session->web_content_connection().take_screenshot();
  689. }
  690. // 17.2 Take Element Screenshot, https://w3c.github.io/webdriver/#dfn-take-element-screenshot
  691. // GET /session/{session id}/element/{element id}/screenshot
  692. Web::WebDriver::Response Client::handle_take_element_screenshot(Vector<StringView> const& parameters, JsonValue const&)
  693. {
  694. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/element/<element_id>/screenshot");
  695. auto* session = TRY(find_session_with_id(parameters[0]));
  696. return session->web_content_connection().take_element_screenshot(parameters[1]);
  697. }
  698. }