Client.cpp 27 KB

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