Client.cpp 33 KB

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