Session.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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-2024, Tim Flynn <trflynn89@ladybird.org>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include "Session.h"
  11. #include "Client.h"
  12. #include <AK/JsonObject.h>
  13. #include <AK/ScopeGuard.h>
  14. #include <LibCore/EventLoop.h>
  15. #include <LibCore/LocalServer.h>
  16. #include <LibCore/StandardPaths.h>
  17. #include <LibCore/System.h>
  18. #include <unistd.h>
  19. namespace WebDriver {
  20. Session::Session(unsigned session_id, NonnullRefPtr<Client> client, Web::WebDriver::LadybirdOptions options)
  21. : m_client(move(client))
  22. , m_options(move(options))
  23. , m_id(session_id)
  24. {
  25. }
  26. // https://w3c.github.io/webdriver/#dfn-close-the-session
  27. Session::~Session()
  28. {
  29. if (!m_started)
  30. return;
  31. // 1. Perform the following substeps based on the remote end’s type:
  32. // NOTE: We perform the "Remote end is an endpoint node" steps in the WebContent process.
  33. for (auto& it : m_windows) {
  34. it.value.web_content_connection->close_session();
  35. }
  36. // 2. Remove the current session from active sessions.
  37. // NOTE: We are in a session destruction which means it is already removed
  38. // from active sessions
  39. // 3. Perform any implementation-specific cleanup steps.
  40. if (m_browser_pid.has_value()) {
  41. MUST(Core::System::kill(*m_browser_pid, SIGTERM));
  42. m_browser_pid = {};
  43. }
  44. if (m_web_content_socket_path.has_value()) {
  45. MUST(Core::System::unlink(*m_web_content_socket_path));
  46. m_web_content_socket_path = {};
  47. }
  48. }
  49. ErrorOr<NonnullRefPtr<Core::LocalServer>> Session::create_server(NonnullRefPtr<ServerPromise> promise)
  50. {
  51. static_assert(IsSame<IPC::Transport, IPC::TransportSocket>, "Need to handle other IPC transports here");
  52. dbgln("Listening for WebDriver connection on {}", *m_web_content_socket_path);
  53. (void)Core::System::unlink(*m_web_content_socket_path);
  54. auto server = TRY(Core::LocalServer::try_create());
  55. server->listen(*m_web_content_socket_path);
  56. server->on_accept = [this, promise](auto client_socket) {
  57. auto maybe_connection = adopt_nonnull_ref_or_enomem(new (nothrow) WebContentConnection(IPC::Transport(move(client_socket))));
  58. if (maybe_connection.is_error()) {
  59. promise->resolve(maybe_connection.release_error());
  60. return;
  61. }
  62. dbgln("WebDriver is connected to WebContent socket");
  63. auto web_content_connection = maybe_connection.release_value();
  64. auto maybe_window_handle = web_content_connection->get_window_handle();
  65. if (maybe_window_handle.is_error()) {
  66. promise->reject(Error::from_string_literal("Window was closed immediately"));
  67. return;
  68. }
  69. auto window_handle = MUST(String::from_byte_string(maybe_window_handle.value().as_string()));
  70. web_content_connection->on_close = [this, window_handle]() {
  71. dbgln_if(WEBDRIVER_DEBUG, "Window {} was closed remotely.", window_handle);
  72. m_windows.remove(window_handle);
  73. if (m_windows.is_empty())
  74. m_client->close_session(session_id());
  75. };
  76. m_windows.set(window_handle, Session::Window { window_handle, move(web_content_connection) });
  77. if (m_current_window_handle.is_empty())
  78. m_current_window_handle = window_handle;
  79. promise->resolve({});
  80. };
  81. server->on_accept_error = [promise](auto error) {
  82. promise->resolve(move(error));
  83. };
  84. return server;
  85. }
  86. ErrorOr<void> Session::start(LaunchBrowserCallbacks const& callbacks)
  87. {
  88. auto promise = TRY(ServerPromise::try_create());
  89. m_web_content_socket_path = ByteString::formatted("{}/webdriver/session_{}_{}", TRY(Core::StandardPaths::runtime_directory()), getpid(), m_id);
  90. m_web_content_server = TRY(create_server(promise));
  91. if (m_options.headless)
  92. m_browser_pid = TRY(callbacks.launch_headless_browser(*m_web_content_socket_path));
  93. else
  94. m_browser_pid = TRY(callbacks.launch_browser(*m_web_content_socket_path));
  95. // FIXME: Allow this to be more asynchronous. For now, this at least allows us to propagate
  96. // errors received while accepting the Browser and WebContent sockets.
  97. TRY(TRY(promise->await()));
  98. m_started = true;
  99. return {};
  100. }
  101. // 11.2 Close Window, https://w3c.github.io/webdriver/#dfn-close-window
  102. Web::WebDriver::Response Session::close_window()
  103. {
  104. {
  105. // Defer removing the window handle from this session until after we know we are done with its connection.
  106. ScopeGuard guard { [this] { m_windows.remove(m_current_window_handle); m_current_window_handle = "NoSuchWindowPleaseSelectANewOne"_string; } };
  107. // 3. Close the current top-level browsing context.
  108. TRY(web_content_connection().close_window());
  109. // 4. If there are no more open top-level browsing contexts, then close the session.
  110. if (m_windows.size() == 1)
  111. m_client->close_session(session_id());
  112. }
  113. // 5. Return the result of running the remote end steps for the Get Window Handles command.
  114. return get_window_handles();
  115. }
  116. // 11.3 Switch to Window, https://w3c.github.io/webdriver/#dfn-switch-to-window
  117. Web::WebDriver::Response Session::switch_to_window(StringView handle)
  118. {
  119. // 4. If handle is equal to the associated window handle for some top-level browsing context in the
  120. // current session, let context be the that browsing context, and set the current top-level
  121. // browsing context with context.
  122. // Otherwise, return error with error code no such window.
  123. if (auto it = m_windows.find(handle); it != m_windows.end())
  124. m_current_window_handle = it->key;
  125. else
  126. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found");
  127. // 5. Update any implementation-specific state that would result from the user selecting the current
  128. // browsing context for interaction, without altering OS-level focus.
  129. TRY(web_content_connection().switch_to_window(m_current_window_handle));
  130. // 6. Return success with data null.
  131. return JsonValue {};
  132. }
  133. // 11.4 Get Window Handles, https://w3c.github.io/webdriver/#dfn-get-window-handles
  134. Web::WebDriver::Response Session::get_window_handles() const
  135. {
  136. // 1. Let handles be a JSON List.
  137. JsonArray handles {};
  138. // 2. For each top-level browsing context in the remote end, push the associated window handle onto handles.
  139. for (auto const& window_handle : m_windows.keys()) {
  140. handles.must_append(JsonValue(window_handle));
  141. }
  142. // 3. Return success with data handles.
  143. return JsonValue { move(handles) };
  144. }
  145. ErrorOr<void, Web::WebDriver::Error> Session::ensure_current_window_handle_is_valid() const
  146. {
  147. if (auto current_window = m_windows.get(m_current_window_handle); current_window.has_value())
  148. return {};
  149. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found"sv);
  150. }
  151. template<typename Handler, typename Action>
  152. static Web::WebDriver::Response perform_async_action(Handler& handler, Action&& action)
  153. {
  154. Optional<Web::WebDriver::Response> response;
  155. ScopeGuard guard { [&]() { handler = nullptr; } };
  156. handler = [&](auto result) { response = move(result); };
  157. TRY(action());
  158. Core::EventLoop::current().spin_until([&]() {
  159. return response.has_value();
  160. });
  161. return response.release_value();
  162. }
  163. Web::WebDriver::Response Session::navigate_to(JsonValue payload) const
  164. {
  165. return perform_async_action(web_content_connection().on_navigation_complete, [&]() {
  166. return web_content_connection().navigate_to(move(payload));
  167. });
  168. }
  169. Web::WebDriver::Response Session::set_window_rect(JsonValue payload) const
  170. {
  171. return perform_async_action(web_content_connection().on_window_rect_updated, [&]() {
  172. return web_content_connection().set_window_rect(move(payload));
  173. });
  174. }
  175. Web::WebDriver::Response Session::maximize_window() const
  176. {
  177. return perform_async_action(web_content_connection().on_window_rect_updated, [&]() {
  178. return web_content_connection().maximize_window();
  179. });
  180. }
  181. Web::WebDriver::Response Session::minimize_window() const
  182. {
  183. return perform_async_action(web_content_connection().on_window_rect_updated, [&]() {
  184. return web_content_connection().minimize_window();
  185. });
  186. }
  187. Web::WebDriver::Response Session::fullscreen_window() const
  188. {
  189. return perform_async_action(web_content_connection().on_window_rect_updated, [&]() {
  190. return web_content_connection().fullscreen_window();
  191. });
  192. }
  193. Web::WebDriver::Response Session::execute_script(JsonValue payload, ScriptMode mode) const
  194. {
  195. return perform_async_action(web_content_connection().on_script_executed, [&]() {
  196. switch (mode) {
  197. case ScriptMode::Sync:
  198. return web_content_connection().execute_script(move(payload));
  199. case ScriptMode::Async:
  200. return web_content_connection().execute_async_script(move(payload));
  201. }
  202. VERIFY_NOT_REACHED();
  203. });
  204. }
  205. Web::WebDriver::Response Session::element_click(String element_id) const
  206. {
  207. return perform_async_action(web_content_connection().on_actions_performed, [&]() {
  208. return web_content_connection().element_click(move(element_id));
  209. });
  210. }
  211. Web::WebDriver::Response Session::element_send_keys(String element_id, JsonValue payload) const
  212. {
  213. return perform_async_action(web_content_connection().on_actions_performed, [&]() {
  214. return web_content_connection().element_send_keys(move(element_id), move(payload));
  215. });
  216. }
  217. Web::WebDriver::Response Session::perform_actions(JsonValue payload) const
  218. {
  219. return perform_async_action(web_content_connection().on_actions_performed, [&]() {
  220. return web_content_connection().perform_actions(move(payload));
  221. });
  222. }
  223. Web::WebDriver::Response Session::dismiss_alert() const
  224. {
  225. return perform_async_action(web_content_connection().on_dialog_closed, [&]() {
  226. return web_content_connection().dismiss_alert();
  227. });
  228. }
  229. Web::WebDriver::Response Session::accept_alert() const
  230. {
  231. return perform_async_action(web_content_connection().on_dialog_closed, [&]() {
  232. return web_content_connection().accept_alert();
  233. });
  234. }
  235. }