WebDriverConnection.cpp 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058
  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/JsonObject.h>
  11. #include <AK/JsonValue.h>
  12. #include <AK/Vector.h>
  13. #include <LibJS/Runtime/JSONObject.h>
  14. #include <LibJS/Runtime/Value.h>
  15. #include <LibWeb/CSS/PropertyID.h>
  16. #include <LibWeb/CSS/StyleProperties.h>
  17. #include <LibWeb/CSS/StyleValue.h>
  18. #include <LibWeb/Cookie/Cookie.h>
  19. #include <LibWeb/DOM/Document.h>
  20. #include <LibWeb/DOM/Element.h>
  21. #include <LibWeb/Geometry/DOMRect.h>
  22. #include <LibWeb/HTML/AttributeNames.h>
  23. #include <LibWeb/HTML/BrowsingContext.h>
  24. #include <LibWeb/HTML/FormAssociatedElement.h>
  25. #include <LibWeb/HTML/HTMLInputElement.h>
  26. #include <LibWeb/HTML/HTMLOptionElement.h>
  27. #include <LibWeb/Page/Page.h>
  28. #include <LibWeb/Platform/EventLoopPlugin.h>
  29. #include <LibWeb/Platform/Timer.h>
  30. #include <LibWeb/WebDriver/Screenshot.h>
  31. #include <WebContent/ConnectionFromClient.h>
  32. #include <WebContent/PageHost.h>
  33. #include <WebContent/WebDriverConnection.h>
  34. namespace WebContent {
  35. static JsonValue make_success_response(JsonValue value)
  36. {
  37. JsonObject result;
  38. result.set("value", move(value));
  39. return result;
  40. }
  41. // https://w3c.github.io/webdriver/#dfn-serialized-cookie
  42. static JsonValue serialize_cookie(Web::Cookie::Cookie const& cookie)
  43. {
  44. JsonObject serialized_cookie;
  45. serialized_cookie.set("name"sv, cookie.name);
  46. serialized_cookie.set("value"sv, cookie.value);
  47. serialized_cookie.set("path"sv, cookie.path);
  48. serialized_cookie.set("domain"sv, cookie.domain);
  49. serialized_cookie.set("secure"sv, cookie.secure);
  50. serialized_cookie.set("httpOnly"sv, cookie.http_only);
  51. serialized_cookie.set("expiry"sv, cookie.expiry_time.timestamp());
  52. serialized_cookie.set("sameSite"sv, Web::Cookie::same_site_to_string(cookie.same_site));
  53. return serialized_cookie;
  54. }
  55. static JsonValue serialize_rect(Gfx::IntRect const& rect)
  56. {
  57. JsonObject serialized_rect = {};
  58. serialized_rect.set("x", rect.x());
  59. serialized_rect.set("y", rect.y());
  60. serialized_rect.set("width", rect.width());
  61. serialized_rect.set("height", rect.height());
  62. return make_success_response(move(serialized_rect));
  63. }
  64. static Gfx::IntRect compute_window_rect(Web::Page const& page)
  65. {
  66. return {
  67. page.window_position().x(),
  68. page.window_position().y(),
  69. page.window_size().width(),
  70. page.window_size().height()
  71. };
  72. }
  73. // https://w3c.github.io/webdriver/#dfn-calculate-the-absolute-position
  74. static Gfx::IntPoint calculate_absolute_position_of_element(Web::Page const& page, JS::NonnullGCPtr<Web::Geometry::DOMRect> rect)
  75. {
  76. // 1. Let rect be the value returned by calling getBoundingClientRect().
  77. // 2. Let window be the associated window of current top-level browsing context.
  78. auto const* window = page.top_level_browsing_context().active_window();
  79. // 3. Let x be (scrollX of window + rect’s x coordinate).
  80. auto x = (window ? static_cast<int>(window->scroll_x()) : 0) + static_cast<int>(rect->x());
  81. // 4. Let y be (scrollY of window + rect’s y coordinate).
  82. auto y = (window ? static_cast<int>(window->scroll_y()) : 0) + static_cast<int>(rect->y());
  83. // 5. Return a pair of (x, y).
  84. return { x, y };
  85. }
  86. static Gfx::IntRect calculate_absolute_rect_of_element(Web::Page const& page, Web::DOM::Element const& element)
  87. {
  88. auto bounding_rect = element.get_bounding_client_rect();
  89. auto coordinates = calculate_absolute_position_of_element(page, bounding_rect);
  90. return {
  91. coordinates.x(),
  92. coordinates.y(),
  93. static_cast<int>(bounding_rect->width()),
  94. static_cast<int>(bounding_rect->height())
  95. };
  96. }
  97. // https://w3c.github.io/webdriver/#dfn-get-or-create-a-web-element-reference
  98. static String get_or_create_a_web_element_reference(Web::DOM::Node const& element)
  99. {
  100. // FIXME: 1. For each known element of the current browsing context’s list of known elements:
  101. // FIXME: 1. If known element equals element, return success with known element’s web element reference.
  102. // FIXME: 2. Add element to the list of known elements of the current browsing context.
  103. // FIXME: 3. Return success with the element’s web element reference.
  104. return String::number(element.id());
  105. }
  106. // https://w3c.github.io/webdriver/#dfn-web-element-reference-object
  107. static JsonObject web_element_reference_object(Web::DOM::Node const& element)
  108. {
  109. // https://w3c.github.io/webdriver/#dfn-web-element-identifier
  110. static String const web_element_identifier = "element-6066-11e4-a52e-4f735466cecf"sv;
  111. // 1. Let identifier be the web element identifier.
  112. auto identifier = web_element_identifier;
  113. // 2. Let reference be the result of get or create a web element reference given element.
  114. auto reference = get_or_create_a_web_element_reference(element);
  115. // 3. Return a JSON Object initialized with a property with name identifier and value reference.
  116. JsonObject object;
  117. object.set("name"sv, identifier);
  118. object.set("value"sv, reference);
  119. return object;
  120. }
  121. // https://w3c.github.io/webdriver/#dfn-get-a-known-connected-element
  122. static ErrorOr<Web::DOM::Element*, Web::WebDriver::Error> get_known_connected_element(StringView element_id)
  123. {
  124. // NOTE: The whole concept of "connected elements" is not implemented yet. See get_or_create_a_web_element_reference().
  125. // For now the element is only represented by its ID.
  126. auto element = element_id.to_int();
  127. if (!element.has_value())
  128. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Element ID is not an integer");
  129. auto* node = Web::DOM::Node::from_id(*element);
  130. if (!node || !node->is_element())
  131. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, String::formatted("Could not find element with ID: {}", element_id));
  132. return static_cast<Web::DOM::Element*>(node);
  133. }
  134. // https://w3c.github.io/webdriver/#dfn-scrolls-into-view
  135. static void scroll_element_into_view(Web::DOM::Element& element)
  136. {
  137. // 1. Let options be the following ScrollIntoViewOptions:
  138. Web::DOM::ScrollIntoViewOptions options {};
  139. // Logical scroll position "block"
  140. // "end"
  141. options.block = Web::Bindings::ScrollLogicalPosition::End;
  142. // Logical scroll position "inline"
  143. // "nearest"
  144. options.inline_ = Web::Bindings::ScrollLogicalPosition::Nearest;
  145. // 2. Run Function.[[Call]](scrollIntoView, options) with element as the this value.
  146. element.scroll_into_view(options);
  147. }
  148. template<typename PropertyType = String>
  149. static ErrorOr<PropertyType, Web::WebDriver::Error> get_property(JsonValue const& payload, StringView key)
  150. {
  151. if (!payload.is_object())
  152. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
  153. auto const* property = payload.as_object().get_ptr(key);
  154. if (!property)
  155. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("No property called '{}' present", key));
  156. if constexpr (IsSame<PropertyType, String>) {
  157. if (!property->is_string())
  158. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Property '{}' is not a String", key));
  159. return property->as_string();
  160. } else if constexpr (IsSame<PropertyType, JsonArray const*>) {
  161. if (!property->is_array())
  162. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Property '{}' is not an Array", key));
  163. return &property->as_array();
  164. } else {
  165. static_assert(DependentFalse<PropertyType>, "get_property invoked with unknown property type");
  166. VERIFY_NOT_REACHED();
  167. }
  168. }
  169. ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(ConnectionFromClient& web_content_client, PageHost& page_host, String const& webdriver_ipc_path)
  170. {
  171. dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path);
  172. auto socket = TRY(Core::Stream::LocalSocket::connect(webdriver_ipc_path));
  173. dbgln_if(WEBDRIVER_DEBUG, "Connected to WebDriver");
  174. return adopt_nonnull_ref_or_enomem(new (nothrow) WebDriverConnection(move(socket), web_content_client, page_host));
  175. }
  176. WebDriverConnection::WebDriverConnection(NonnullOwnPtr<Core::Stream::LocalSocket> socket, ConnectionFromClient& web_content_client, PageHost& page_host)
  177. : IPC::ConnectionToServer<WebDriverClientEndpoint, WebDriverServerEndpoint>(*this, move(socket))
  178. , m_web_content_client(web_content_client)
  179. , m_page_host(page_host)
  180. {
  181. }
  182. // https://w3c.github.io/webdriver/#dfn-close-the-session
  183. void WebDriverConnection::close_session()
  184. {
  185. // 1. Set the webdriver-active flag to false.
  186. set_is_webdriver_active(false);
  187. // 2. An endpoint node must close any top-level browsing contexts associated with the session, without prompting to unload.
  188. m_page_host.page().top_level_browsing_context().close();
  189. }
  190. void WebDriverConnection::set_is_webdriver_active(bool is_webdriver_active)
  191. {
  192. m_page_host.set_is_webdriver_active(is_webdriver_active);
  193. }
  194. // 10.1 Navigate To, https://w3c.github.io/webdriver/#navigate-to
  195. Messages::WebDriverClient::NavigateToResponse WebDriverConnection::navigate_to(JsonValue const& payload)
  196. {
  197. dbgln_if(WEBDRIVER_DEBUG, "WebDriverConnection::navigate_to {}", payload);
  198. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  199. TRY(ensure_open_top_level_browsing_context());
  200. // 2. Let url be the result of getting the property url from the parameters argument.
  201. if (!payload.is_object() || !payload.as_object().has_string("url"sv))
  202. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload doesn't have a string `url`"sv);
  203. URL url(payload.as_object().get_ptr("url"sv)->as_string());
  204. // FIXME: 3. If url is not an absolute URL or is not an absolute URL with fragment or not a local scheme, return error with error code invalid argument.
  205. // FIXME: 4. Handle any user prompts and return its value if it is an error.
  206. // FIXME: 5. Let current URL be the current top-level browsing context’s active document’s URL.
  207. // FIXME: 6. If current URL and url do not have the same absolute URL:
  208. // FIXME: a. If timer has not been started, start a timer. If this algorithm has not completed before timer reaches the session’s session page load timeout in milliseconds, return an error with error code timeout.
  209. // 7. Navigate the current top-level browsing context to url.
  210. m_page_host.page().load(url);
  211. // FIXME: 8. If url is special except for file and current URL and URL do not have the same absolute URL:
  212. // FIXME: a. Try to wait for navigation to complete.
  213. // FIXME: b. Try to run the post-navigation checks.
  214. // FIXME: 9. Set the current browsing context with the current top-level browsing context.
  215. // FIXME: 10. If the current top-level browsing context contains a refresh state pragma directive of time 1 second or less, wait until the refresh timeout has elapsed, a new navigate has begun, and return to the first step of this algorithm.
  216. // 11. Return success with data null.
  217. return make_success_response({});
  218. }
  219. // 10.2 Get Current URL, https://w3c.github.io/webdriver/#get-current-url
  220. Messages::WebDriverClient::GetCurrentUrlResponse WebDriverConnection::get_current_url()
  221. {
  222. dbgln_if(WEBDRIVER_DEBUG, "WebDriverConnection::get_current_url");
  223. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  224. TRY(ensure_open_top_level_browsing_context());
  225. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  226. // 3. Let url be the serialization of the current top-level browsing context’s active document’s document URL.
  227. auto url = m_page_host.page().top_level_browsing_context().active_document()->url().to_string();
  228. // 4. Return success with data url.
  229. return make_success_response(url);
  230. }
  231. // 11.8.1 Get Window Rect, https://w3c.github.io/webdriver/#dfn-get-window-rect
  232. Messages::WebDriverClient::GetWindowRectResponse WebDriverConnection::get_window_rect()
  233. {
  234. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  235. TRY(ensure_open_top_level_browsing_context());
  236. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  237. // 3. Return success with data set to the WindowRect object for the current top-level browsing context.
  238. return serialize_rect(compute_window_rect(m_page_host.page()));
  239. }
  240. // 11.8.2 Set Window Rect, https://w3c.github.io/webdriver/#dfn-set-window-rect
  241. Messages::WebDriverClient::SetWindowRectResponse WebDriverConnection::set_window_rect(JsonValue const& payload)
  242. {
  243. if (!payload.is_object())
  244. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
  245. auto const& properties = payload.as_object();
  246. auto resolve_property = [](auto name, auto const* property, auto min, auto max) -> ErrorOr<Optional<i32>, Web::WebDriver::Error> {
  247. if (!property)
  248. return Optional<i32> {};
  249. if (!property->is_number())
  250. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Property '{}' is not a Number", name));
  251. auto number = property->template to_number<i64>();
  252. if (number < min)
  253. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Property '{}' value {} exceeds the minimum allowed value {}", name, number, min));
  254. if (number > max)
  255. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Property '{}' value {} exceeds the maximum allowed value {}", name, number, max));
  256. return static_cast<i32>(number);
  257. };
  258. // 1. Let width be the result of getting a property named width from the parameters argument, else let it be null.
  259. auto const* width_property = properties.get_ptr("width"sv);
  260. // 2. Let height be the result of getting a property named height from the parameters argument, else let it be null.
  261. auto const* height_property = properties.get_ptr("height"sv);
  262. // 3. Let x be the result of getting a property named x from the parameters argument, else let it be null.
  263. auto const* x_property = properties.get_ptr("x"sv);
  264. // 4. Let y be the result of getting a property named y from the parameters argument, else let it be null.
  265. auto const* y_property = properties.get_ptr("y"sv);
  266. // 5. If width or height is neither null nor a Number from 0 to 2^31 − 1, return error with error code invalid argument.
  267. auto width = TRY(resolve_property("width"sv, width_property, 0, NumericLimits<i32>::max()));
  268. auto height = TRY(resolve_property("height"sv, height_property, 0, NumericLimits<i32>::max()));
  269. // 6. If x or y is neither null nor a Number from −(2^31) to 2^31 − 1, return error with error code invalid argument.
  270. auto x = TRY(resolve_property("x"sv, x_property, NumericLimits<i32>::min(), NumericLimits<i32>::max()));
  271. auto y = TRY(resolve_property("y"sv, y_property, NumericLimits<i32>::min(), NumericLimits<i32>::max()));
  272. // 7. If the remote end does not support the Set Window Rect command for the current top-level browsing context for any reason, return error with error code unsupported operation.
  273. // 8. If the current top-level browsing context is no longer open, return error with error code no such window.
  274. TRY(ensure_open_top_level_browsing_context());
  275. // FIXME: 9. Handle any user prompts and return its value if it is an error.
  276. // FIXME: 10. Fully exit fullscreen.
  277. // 11. Restore the window.
  278. restore_the_window();
  279. Gfx::IntRect window_rect;
  280. // 11. If width and height are not null:
  281. if (width.has_value() && height.has_value()) {
  282. // a. Set the width, in CSS pixels, of the operating system window containing the current top-level browsing context, including any browser chrome and externally drawn window decorations to a value that is as close as possible to width.
  283. // b. Set the height, in CSS pixels, of the operating system window containing the current top-level browsing context, including any browser chrome and externally drawn window decorations to a value that is as close as possible to height.
  284. auto size = m_web_content_client.did_request_resize_window({ *width, *height });
  285. window_rect.set_size(size);
  286. } else {
  287. window_rect.set_size(m_page_host.page().window_size());
  288. }
  289. // 12. If x and y are not null:
  290. if (x.has_value() && y.has_value()) {
  291. // a. Run the implementation-specific steps to set the position of the operating system level window containing the current top-level browsing context to the position given by the x and y coordinates.
  292. auto position = m_web_content_client.did_request_reposition_window({ *x, *y });
  293. window_rect.set_location(position);
  294. } else {
  295. window_rect.set_location(m_page_host.page().window_position());
  296. }
  297. // 14. Return success with data set to the WindowRect object for the current top-level browsing context.
  298. return serialize_rect(window_rect);
  299. }
  300. // 11.8.3 Maximize Window, https://w3c.github.io/webdriver/#dfn-maximize-window
  301. Messages::WebDriverClient::MaximizeWindowResponse WebDriverConnection::maximize_window()
  302. {
  303. // 1. If the remote end does not support the Maximize Window command for the current top-level browsing context for any reason, return error with error code unsupported operation.
  304. // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
  305. TRY(ensure_open_top_level_browsing_context());
  306. // FIXME: 3. Handle any user prompts and return its value if it is an error.
  307. // FIXME: 4. Fully exit fullscreen.
  308. // 5. Restore the window.
  309. restore_the_window();
  310. // 6. Maximize the window of the current top-level browsing context.
  311. auto window_rect = maximize_the_window();
  312. // 7. Return success with data set to the WindowRect object for the current top-level browsing context.
  313. return serialize_rect(window_rect);
  314. }
  315. // 11.8.4 Minimize Window, https://w3c.github.io/webdriver/#minimize-window
  316. Messages::WebDriverClient::MinimizeWindowResponse WebDriverConnection::minimize_window()
  317. {
  318. // 1. If the remote end does not support the Minimize Window command for the current top-level browsing context for any reason, return error with error code unsupported operation.
  319. // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
  320. TRY(ensure_open_top_level_browsing_context());
  321. // FIXME: 3. Handle any user prompts and return its value if it is an error.
  322. // FIXME: 4. Fully exit fullscreen.
  323. // 5. Iconify the window.
  324. auto window_rect = iconify_the_window();
  325. // 6. Return success with data set to the WindowRect object for the current top-level browsing context.
  326. return serialize_rect(window_rect);
  327. }
  328. // 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element
  329. Messages::WebDriverClient::FindElementResponse WebDriverConnection::find_element(JsonValue const& payload)
  330. {
  331. // 1. Let location strategy be the result of getting a property called "using".
  332. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  333. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  334. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  335. if (!location_strategy.has_value())
  336. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Location strategy '{}' is invalid", location_strategy_string));
  337. // 3. Let selector be the result of getting a property called "value".
  338. // 4. If selector is undefined, return error with error code invalid argument.
  339. auto selector = TRY(get_property(payload, "value"sv));
  340. // 5. If the current browsing context is no longer open, return error with error code no such window.
  341. TRY(ensure_open_top_level_browsing_context());
  342. // FIXME: 6. Handle any user prompts and return its value if it is an error.
  343. // 7. Let start node be the current browsing context’s document element.
  344. auto* start_node = m_page_host.page().top_level_browsing_context().active_document();
  345. // 8. If start node is null, return error with error code no such element.
  346. if (!start_node)
  347. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
  348. // 9. Let result be the result of trying to Find with start node, location strategy, and selector.
  349. auto result = TRY(find(*start_node, *location_strategy, selector));
  350. // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
  351. if (result.is_empty())
  352. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
  353. return make_success_response(result.at(0));
  354. }
  355. // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
  356. Messages::WebDriverClient::FindElementsResponse WebDriverConnection::find_elements(JsonValue const& payload)
  357. {
  358. // 1. Let location strategy be the result of getting a property called "using".
  359. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  360. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  361. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  362. if (!location_strategy.has_value())
  363. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Location strategy '{}' is invalid", location_strategy_string));
  364. // 3. Let selector be the result of getting a property called "value".
  365. // 4. If selector is undefined, return error with error code invalid argument.
  366. auto selector = TRY(get_property(payload, "value"sv));
  367. // 5. If the current browsing context is no longer open, return error with error code no such window.
  368. TRY(ensure_open_top_level_browsing_context());
  369. // FIXME: 6. Handle any user prompts and return its value if it is an error.
  370. // 7. Let start node be the current browsing context’s document element.
  371. auto* start_node = m_page_host.page().top_level_browsing_context().active_document();
  372. // 8. If start node is null, return error with error code no such element.
  373. if (!start_node)
  374. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
  375. // 9. Return the result of trying to Find with start node, location strategy, and selector.
  376. auto result = TRY(find(*start_node, *location_strategy, selector));
  377. return make_success_response(move(result));
  378. }
  379. // 12.3.4 Find Element From Element, https://w3c.github.io/webdriver/#dfn-find-element-from-element
  380. Messages::WebDriverClient::FindElementFromElementResponse WebDriverConnection::find_element_from_element(JsonValue const& payload, String const& element_id)
  381. {
  382. // 1. Let location strategy be the result of getting a property called "using".
  383. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  384. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  385. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  386. if (!location_strategy.has_value())
  387. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Location strategy '{}' is invalid", location_strategy_string));
  388. // 3. Let selector be the result of getting a property called "value".
  389. // 4. If selector is undefined, return error with error code invalid argument.
  390. auto selector = TRY(get_property(payload, "value"sv));
  391. // 5. If the current browsing context is no longer open, return error with error code no such window.
  392. TRY(ensure_open_top_level_browsing_context());
  393. // FIXME: 6. Handle any user prompts and return its value if it is an error.
  394. // 7. Let start node be the result of trying to get a known connected element with url variable element id.
  395. auto* start_node = TRY(get_known_connected_element(element_id));
  396. // 8. Let result be the value of trying to Find with start node, location strategy, and selector.
  397. auto result = TRY(find(*start_node, *location_strategy, selector));
  398. // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
  399. if (result.is_empty())
  400. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
  401. return make_success_response(result.at(0));
  402. }
  403. // 12.3.5 Find Elements From Element, https://w3c.github.io/webdriver/#dfn-find-elements-from-element
  404. Messages::WebDriverClient::FindElementsFromElementResponse WebDriverConnection::find_elements_from_element(JsonValue const& payload, String const& element_id)
  405. {
  406. // 1. Let location strategy be the result of getting a property called "using".
  407. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  408. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  409. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  410. if (!location_strategy.has_value())
  411. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Location strategy '{}' is invalid", location_strategy_string));
  412. // 3. Let selector be the result of getting a property called "value".
  413. // 4. If selector is undefined, return error with error code invalid argument.
  414. auto selector = TRY(get_property(payload, "value"sv));
  415. // 5. If the current browsing context is no longer open, return error with error code no such window.
  416. TRY(ensure_open_top_level_browsing_context());
  417. // FIXME: 6. Handle any user prompts and return its value if it is an error.
  418. // 7. Let start node be the result of trying to get a known connected element with url variable element id.
  419. auto* start_node = TRY(get_known_connected_element(element_id));
  420. // 8. Return the result of trying to Find with start node, location strategy, and selector.
  421. auto result = TRY(find(*start_node, *location_strategy, selector));
  422. return make_success_response(move(result));
  423. }
  424. // 12.4.1 Is Element Selected, https://w3c.github.io/webdriver/#dfn-is-element-selected
  425. Messages::WebDriverClient::IsElementSelectedResponse WebDriverConnection::is_element_selected(String const& element_id)
  426. {
  427. // 1. If the current browsing context is no longer open, return error with error code no such window.
  428. TRY(ensure_open_top_level_browsing_context());
  429. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  430. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  431. auto* element = TRY(get_known_connected_element(element_id));
  432. // 4. Let selected be the value corresponding to the first matching statement:
  433. bool selected = false;
  434. // element is an input element with a type attribute in the Checkbox- or Radio Button state
  435. if (is<Web::HTML::HTMLInputElement>(*element)) {
  436. // -> The result of element’s checkedness.
  437. auto& input = static_cast<Web::HTML::HTMLInputElement&>(*element);
  438. using enum Web::HTML::HTMLInputElement::TypeAttributeState;
  439. if (input.type_state() == Checkbox || input.type_state() == RadioButton)
  440. selected = input.checked();
  441. }
  442. // element is an option element
  443. else if (is<Web::HTML::HTMLOptionElement>(*element)) {
  444. // -> The result of element’s selectedness.
  445. selected = static_cast<Web::HTML::HTMLOptionElement&>(*element).selected();
  446. }
  447. // Otherwise
  448. // -> False.
  449. // 5. Return success with data selected.
  450. return make_success_response(selected);
  451. }
  452. // 12.4.2 Get Element Attribute, https://w3c.github.io/webdriver/#dfn-get-element-attribute
  453. Messages::WebDriverClient::GetElementAttributeResponse WebDriverConnection::get_element_attribute(String const& element_id, String const& name)
  454. {
  455. // 1. If the current browsing context is no longer open, return error with error code no such window.
  456. TRY(ensure_open_top_level_browsing_context());
  457. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  458. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  459. auto* element = TRY(get_known_connected_element(element_id));
  460. // 4. Let result be the result of the first matching condition:
  461. Optional<String> result;
  462. // -> If name is a boolean attribute
  463. if (Web::HTML::is_boolean_attribute(name)) {
  464. // "true" (string) if the element has the attribute, otherwise null.
  465. if (element->has_attribute(name))
  466. result = "true"sv;
  467. }
  468. // -> Otherwise
  469. else {
  470. // The result of getting an attribute by name name.
  471. result = element->get_attribute(name);
  472. }
  473. // 5. Return success with data result.
  474. if (result.has_value())
  475. return make_success_response(result.release_value());
  476. return make_success_response({});
  477. }
  478. // 12.4.3 Get Element Property, https://w3c.github.io/webdriver/#dfn-get-element-property
  479. Messages::WebDriverClient::GetElementPropertyResponse WebDriverConnection::get_element_property(String const& element_id, String const& name)
  480. {
  481. // 1. If the current browsing context is no longer open, return error with error code no such window.
  482. TRY(ensure_open_top_level_browsing_context());
  483. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  484. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  485. auto* element = TRY(get_known_connected_element(element_id));
  486. Optional<String> result;
  487. // 4. Let property be the result of calling the Object.[[GetProperty]](name) on element.
  488. if (auto property_or_error = element->get(name); !property_or_error.is_throw_completion()) {
  489. auto property = property_or_error.release_value();
  490. // 5. Let result be the value of property if not undefined, or null.
  491. if (!property.is_undefined()) {
  492. if (auto string_or_error = property.to_string(element->vm()); !string_or_error.is_error())
  493. result = string_or_error.release_value();
  494. }
  495. }
  496. // 6. Return success with data result.
  497. if (result.has_value())
  498. return make_success_response(result.release_value());
  499. return make_success_response({});
  500. }
  501. // 12.4.4 Get Element CSS Value, https://w3c.github.io/webdriver/#dfn-get-element-css-value
  502. Messages::WebDriverClient::GetElementCssValueResponse WebDriverConnection::get_element_css_value(String const& element_id, String const& name)
  503. {
  504. // 1. If the current browsing context is no longer open, return error with error code no such window.
  505. TRY(ensure_open_top_level_browsing_context());
  506. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  507. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  508. auto* element = TRY(get_known_connected_element(element_id));
  509. // 4. Let computed value be the result of the first matching condition:
  510. String computed_value;
  511. // -> current browsing context’s active document’s type is not "xml"
  512. if (!m_page_host.page().top_level_browsing_context().active_document()->is_xml_document()) {
  513. // computed value of parameter property name from element’s style declarations. property name is obtained from url variables.
  514. auto property = Web::CSS::property_id_from_string(name);
  515. if (auto* computed_values = element->computed_css_values())
  516. computed_value = computed_values->property(property)->to_string();
  517. }
  518. // -> Otherwise
  519. else {
  520. // "" (empty string)
  521. computed_value = String::empty();
  522. }
  523. // 5. Return success with data computed value.
  524. return make_success_response(move(computed_value));
  525. }
  526. // 12.4.5 Get Element Text, https://w3c.github.io/webdriver/#dfn-get-element-text
  527. Messages::WebDriverClient::GetElementTextResponse WebDriverConnection::get_element_text(String const& element_id)
  528. {
  529. // 1. If the current browsing context is no longer open, return error with error code no such window.
  530. TRY(ensure_open_top_level_browsing_context());
  531. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  532. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  533. auto* element = TRY(get_known_connected_element(element_id));
  534. // 4. Let rendered text be the result of performing implementation-specific steps whose result is exactly the same as the result of a Function.[[Call]](null, element) with bot.dom.getVisibleText as the this value.
  535. auto rendered_text = element->text_content();
  536. // 5. Return success with data rendered text.
  537. return make_success_response(move(rendered_text));
  538. }
  539. // 12.4.6 Get Element Tag Name, https://w3c.github.io/webdriver/#dfn-get-element-tag-name
  540. Messages::WebDriverClient::GetElementTagNameResponse WebDriverConnection::get_element_tag_name(String const& element_id)
  541. {
  542. // 1. If the current browsing context is no longer open, return error with error code no such window.
  543. TRY(ensure_open_top_level_browsing_context());
  544. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  545. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  546. auto* element = TRY(get_known_connected_element(element_id));
  547. // 4. Let qualified name be the result of getting element’s tagName IDL attribute.
  548. auto qualified_name = element->tag_name();
  549. // 5. Return success with data qualified name.
  550. return make_success_response(move(qualified_name));
  551. }
  552. // 12.4.7 Get Element Rect, https://w3c.github.io/webdriver/#dfn-get-element-rect
  553. Messages::WebDriverClient::GetElementRectResponse WebDriverConnection::get_element_rect(String const& element_id)
  554. {
  555. // 1. If the current browsing context is no longer open, return error with error code no such window.
  556. TRY(ensure_open_top_level_browsing_context());
  557. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  558. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  559. auto* element = TRY(get_known_connected_element(element_id));
  560. // 4. Calculate the absolute position of element and let it be coordinates.
  561. // 5. Let rect be element’s bounding rectangle.
  562. auto rect = calculate_absolute_rect_of_element(m_page_host.page(), *element);
  563. // 6. Let body be a new JSON Object initialized with:
  564. // "x"
  565. // The first value of coordinates.
  566. // "y"
  567. // The second value of coordinates.
  568. // "width"
  569. // Value of rect’s width dimension.
  570. // "height"
  571. // Value of rect’s height dimension.
  572. auto body = serialize_rect(rect);
  573. // 7. Return success with data body.
  574. return body;
  575. }
  576. // 12.4.8 Is Element Enabled, https://w3c.github.io/webdriver/#dfn-is-element-enabled
  577. Messages::WebDriverClient::IsElementEnabledResponse WebDriverConnection::is_element_enabled(String const& element_id)
  578. {
  579. // 1. If the current browsing context is no longer open, return error with error code no such window.
  580. TRY(ensure_open_top_level_browsing_context());
  581. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  582. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  583. auto* element = TRY(get_known_connected_element(element_id));
  584. // 4. Let enabled be a boolean initially set to true if the current browsing context’s active document’s type is not "xml".
  585. // 5. Otherwise, let enabled to false and jump to the last step of this algorithm.
  586. bool enabled = !m_page_host.page().top_level_browsing_context().active_document()->is_xml_document();
  587. // 6. Set enabled to false if a form control is disabled.
  588. if (enabled && is<Web::HTML::FormAssociatedElement>(*element)) {
  589. auto& form_associated_element = dynamic_cast<Web::HTML::FormAssociatedElement&>(*element);
  590. enabled = form_associated_element.enabled();
  591. }
  592. // 7. Return success with data enabled.
  593. return make_success_response(enabled);
  594. }
  595. // 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source
  596. Messages::WebDriverClient::GetSourceResponse WebDriverConnection::get_source()
  597. {
  598. // 1. If the current browsing context is no longer open, return error with error code no such window.
  599. TRY(ensure_open_top_level_browsing_context());
  600. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  601. auto* document = m_page_host.page().top_level_browsing_context().active_document();
  602. Optional<String> source;
  603. // 3. Let source be the result of invoking the fragment serializing algorithm on a fictional node whose only child is the document element providing true for the require well-formed flag. If this causes an exception to be thrown, let source be null.
  604. if (auto result = document->serialize_fragment(Web::DOMParsing::RequireWellFormed::Yes); !result.is_error())
  605. source = result.release_value();
  606. // 4. Let source be the result of serializing to string the current browsing context active document, if source is null.
  607. if (!source.has_value())
  608. source = MUST(document->serialize_fragment(Web::DOMParsing::RequireWellFormed::No));
  609. // 5. Return success with data source.
  610. return make_success_response(source.release_value());
  611. }
  612. // 13.2.1 Execute Script, https://w3c.github.io/webdriver/#dfn-execute-script
  613. Messages::WebDriverClient::ExecuteScriptResponse WebDriverConnection::execute_script(JsonValue const& payload)
  614. {
  615. // 1. Let body and arguments be the result of trying to extract the script arguments from a request with argument parameters.
  616. auto const& [body, arguments] = TRY(extract_the_script_arguments_from_a_request(payload));
  617. // 2. If the current browsing context is no longer open, return error with error code no such window.
  618. TRY(ensure_open_top_level_browsing_context());
  619. // FIXME: 3. Handle any user prompts, and return its value if it is an error.
  620. // 4., 5.1-5.3.
  621. // FIXME: Move timeouts from WebDriver to WebContent and pass the script timeout through here.
  622. auto result = Web::WebDriver::execute_script(m_page_host.page(), body, move(arguments), {});
  623. dbgln_if(WEBDRIVER_DEBUG, "Executing script returned: {}", result.value);
  624. switch (result.type) {
  625. // 6. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
  626. case Web::WebDriver::ExecuteScriptResultType::Timeout:
  627. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
  628. // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
  629. case Web::WebDriver::ExecuteScriptResultType::PromiseResolved:
  630. return make_success_response(move(result.value));
  631. // 8. Upon rejection of promise with reason r, let result be a JSON clone of r, and return error with error code javascript error and data result.
  632. case Web::WebDriver::ExecuteScriptResultType::PromiseRejected:
  633. case Web::WebDriver::ExecuteScriptResultType::JavaScriptError:
  634. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
  635. }
  636. VERIFY_NOT_REACHED();
  637. }
  638. // 13.2.2 Execute Async Script, https://w3c.github.io/webdriver/#dfn-execute-async-script
  639. Messages::WebDriverClient::ExecuteAsyncScriptResponse WebDriverConnection::execute_async_script(JsonValue const& payload)
  640. {
  641. // 1. Let body and arguments by the result of trying to extract the script arguments from a request with argument parameters.
  642. auto const& [body, arguments] = TRY(extract_the_script_arguments_from_a_request(payload));
  643. // 2. If the current browsing context is no longer open, return error with error code no such window.
  644. TRY(ensure_open_top_level_browsing_context());
  645. // FIXME: 3. Handle any user prompts, and return its value if it is an error.
  646. // 4., 5.1-5.11.
  647. // FIXME: Move timeouts from WebDriver to WebContent and pass the script timeout through here.
  648. auto result = Web::WebDriver::execute_async_script(m_page_host.page(), body, move(arguments), {});
  649. dbgln_if(WEBDRIVER_DEBUG, "Executing async script returned: {}", result.value);
  650. switch (result.type) {
  651. // 6. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
  652. case Web::WebDriver::ExecuteScriptResultType::Timeout:
  653. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
  654. // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
  655. case Web::WebDriver::ExecuteScriptResultType::PromiseResolved:
  656. return make_success_response(move(result.value));
  657. // 8. Upon rejection of promise with reason r, let result be a JSON clone of r, and return error with error code javascript error and data result.
  658. case Web::WebDriver::ExecuteScriptResultType::PromiseRejected:
  659. case Web::WebDriver::ExecuteScriptResultType::JavaScriptError:
  660. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
  661. }
  662. VERIFY_NOT_REACHED();
  663. }
  664. // 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies
  665. Messages::WebDriverClient::GetAllCookiesResponse WebDriverConnection::get_all_cookies()
  666. {
  667. // 1. If the current browsing context is no longer open, return error with error code no such window.
  668. TRY(ensure_open_top_level_browsing_context());
  669. // FIXME: 2. Handle any user prompts, and return its value if it is an error.
  670. // 3. Let cookies be a new JSON List.
  671. JsonArray cookies;
  672. // 4. For each cookie in all associated cookies of the current browsing context’s active document:
  673. auto* document = m_page_host.page().top_level_browsing_context().active_document();
  674. for (auto const& cookie : m_web_content_client.did_request_all_cookies(document->url())) {
  675. // 1. Let serialized cookie be the result of serializing cookie.
  676. auto serialized_cookie = serialize_cookie(cookie);
  677. // 2. Append serialized cookie to cookies
  678. cookies.append(move(serialized_cookie));
  679. }
  680. // 5. Return success with data cookies.
  681. return make_success_response(move(cookies));
  682. }
  683. // 14.2 Get Named Cookie, https://w3c.github.io/webdriver/#dfn-get-named-cookie
  684. Messages::WebDriverClient::GetNamedCookieResponse WebDriverConnection::get_named_cookie(String const& name)
  685. {
  686. // 1. If the current browsing context is no longer open, return error with error code no such window.
  687. TRY(ensure_open_top_level_browsing_context());
  688. // FIXME: 2. Handle any user prompts, and return its value if it is an error.
  689. // 3. If the url variable name is equal to a cookie’s cookie name amongst all associated cookies of the current browsing context’s active document, return success with the serialized cookie as data.
  690. auto* document = m_page_host.page().top_level_browsing_context().active_document();
  691. if (auto cookie = m_web_content_client.did_request_named_cookie(document->url(), name); cookie.has_value()) {
  692. auto serialized_cookie = serialize_cookie(*cookie);
  693. return make_success_response(move(serialized_cookie));
  694. }
  695. // 4. Otherwise, return error with error code no such cookie.
  696. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchCookie, String::formatted("Cookie '{}' not found", name));
  697. }
  698. // 17.1 Take Screenshot, https://w3c.github.io/webdriver/#take-screenshot
  699. Messages::WebDriverClient::TakeScreenshotResponse WebDriverConnection::take_screenshot()
  700. {
  701. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  702. TRY(ensure_open_top_level_browsing_context());
  703. // 2. When the user agent is next to run the animation frame callbacks:
  704. // a. Let root rect be the current top-level browsing context’s document element’s rectangle.
  705. // b. Let screenshot result be the result of trying to call draw a bounding box from the framebuffer, given root rect as an argument.
  706. // c. Let canvas be a canvas element of screenshot result’s data.
  707. // d. Let encoding result be the result of trying encoding a canvas as Base64 canvas.
  708. // e. Let encoded string be encoding result’s data.
  709. auto* document = m_page_host.page().top_level_browsing_context().active_document();
  710. auto root_rect = calculate_absolute_rect_of_element(m_page_host.page(), *document->document_element());
  711. auto encoded_string = TRY(Web::WebDriver::capture_element_screenshot(
  712. [&](auto const& rect, auto& bitmap) { m_page_host.paint(rect, bitmap); },
  713. m_page_host.page(),
  714. *document->document_element(),
  715. root_rect));
  716. // 3. Return success with data encoded string.
  717. return make_success_response(move(encoded_string));
  718. }
  719. // 17.2 Take Element Screenshot, https://w3c.github.io/webdriver/#dfn-take-element-screenshot
  720. Messages::WebDriverClient::TakeElementScreenshotResponse WebDriverConnection::take_element_screenshot(String const& element_id)
  721. {
  722. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  723. TRY(ensure_open_top_level_browsing_context());
  724. // FIXME: 2. Handle any user prompts and return its value if it is an error.
  725. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  726. auto* element = TRY(get_known_connected_element(element_id));
  727. // 4. Scroll into view the element.
  728. scroll_element_into_view(*element);
  729. // 5. When the user agent is next to run the animation frame callbacks:
  730. // a. Let element rect be element’s rectangle.
  731. // b. Let screenshot result be the result of trying to call draw a bounding box from the framebuffer, given element rect as an argument.
  732. // c. Let canvas be a canvas element of screenshot result’s data.
  733. // d. Let encoding result be the result of trying encoding a canvas as Base64 canvas.
  734. // e. Let encoded string be encoding result’s data.
  735. auto element_rect = calculate_absolute_rect_of_element(m_page_host.page(), *element);
  736. auto encoded_string = TRY(Web::WebDriver::capture_element_screenshot(
  737. [&](auto const& rect, auto& bitmap) { m_page_host.paint(rect, bitmap); },
  738. m_page_host.page(),
  739. *element,
  740. element_rect));
  741. // 6. Return success with data encoded string.
  742. return make_success_response(move(encoded_string));
  743. }
  744. // https://w3c.github.io/webdriver/#dfn-no-longer-open
  745. ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::ensure_open_top_level_browsing_context()
  746. {
  747. // A browsing context is said to be no longer open if it has been discarded.
  748. if (m_page_host.page().top_level_browsing_context().has_been_discarded())
  749. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found"sv);
  750. return {};
  751. }
  752. // https://w3c.github.io/webdriver/#dfn-restore-the-window
  753. void WebDriverConnection::restore_the_window()
  754. {
  755. // To restore the window, given an operating system level window with an associated top-level browsing context, run implementation-specific steps to restore or unhide the window to the visible screen.
  756. m_web_content_client.async_did_request_restore_window();
  757. // Do not return from this operation until the visibility state of the top-level browsing context’s active document has reached the visible state, or until the operation times out.
  758. // FIXME: Implement timeouts.
  759. Web::Platform::EventLoopPlugin::the().spin_until([this]() {
  760. auto state = m_page_host.page().top_level_browsing_context().system_visibility_state();
  761. return state == Web::HTML::VisibilityState::Visible;
  762. });
  763. }
  764. // https://w3c.github.io/webdriver/#dfn-maximize-the-window
  765. Gfx::IntRect WebDriverConnection::maximize_the_window()
  766. {
  767. // To maximize the window, given an operating system level window with an associated top-level browsing context, run the implementation-specific steps to transition the operating system level window into the maximized window state.
  768. auto rect = m_web_content_client.did_request_maximize_window();
  769. // Return when the window has completed the transition, or within an implementation-defined timeout.
  770. return rect;
  771. }
  772. // https://w3c.github.io/webdriver/#dfn-iconify-the-window
  773. Gfx::IntRect WebDriverConnection::iconify_the_window()
  774. {
  775. // To iconify the window, given an operating system level window with an associated top-level browsing context, run implementation-specific steps to iconify, minimize, or hide the window from the visible screen.
  776. auto rect = m_web_content_client.did_request_minimize_window();
  777. // Do not return from this operation until the visibility state of the top-level browsing context’s active document has reached the hidden state, or until the operation times out.
  778. // FIXME: Implement timeouts.
  779. Web::Platform::EventLoopPlugin::the().spin_until([this]() {
  780. auto state = m_page_host.page().top_level_browsing_context().system_visibility_state();
  781. return state == Web::HTML::VisibilityState::Hidden;
  782. });
  783. return rect;
  784. }
  785. // https://w3c.github.io/webdriver/#dfn-find
  786. ErrorOr<JsonArray, Web::WebDriver::Error> WebDriverConnection::find(Web::DOM::ParentNode& start_node, Web::WebDriver::LocationStrategy using_, StringView value)
  787. {
  788. // FIXME: 1. Let end time be the current time plus the session implicit wait timeout.
  789. // 2. Let location strategy be equal to using.
  790. auto location_strategy = using_;
  791. // 3. Let selector be equal to value.
  792. auto selector = value;
  793. // 4. Let elements returned be the result of trying to call the relevant element location strategy with arguments start node, and selector.
  794. auto elements = Web::WebDriver::invoke_location_strategy(location_strategy, start_node, selector);
  795. // 5. If a DOMException, SyntaxError, XPathException, or other error occurs during the execution of the element location strategy, return error invalid selector.
  796. if (elements.is_error())
  797. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSelector, String::formatted("The location strategy could not finish: {}", elements.error().message));
  798. // FIXME: 6. If elements returned is empty and the current time is less than end time return to step 4. Otherwise, continue to the next step.
  799. // 7. Let result be an empty JSON List.
  800. JsonArray result;
  801. result.ensure_capacity(elements.value()->length());
  802. // 8. For each element in elements returned, append the web element reference object for element, to result.
  803. for (size_t i = 0; i < elements.value()->length(); ++i)
  804. result.append(web_element_reference_object(*elements.value()->item(i)));
  805. // 9. Return success with data result.
  806. return result;
  807. }
  808. // https://w3c.github.io/webdriver/#dfn-extract-the-script-arguments-from-a-request
  809. ErrorOr<WebDriverConnection::ScriptArguments, Web::WebDriver::Error> WebDriverConnection::extract_the_script_arguments_from_a_request(JsonValue const& payload)
  810. {
  811. auto* window = m_page_host.page().top_level_browsing_context().active_window();
  812. auto& vm = window->vm();
  813. // 1. Let script be the result of getting a property named script from the parameters.
  814. // 2. If script is not a String, return error with error code invalid argument.
  815. auto script = TRY(get_property(payload, "script"sv));
  816. // 3. Let args be the result of getting a property named args from the parameters.
  817. // 4. If args is not an Array return error with error code invalid argument.
  818. auto const& args = *TRY(get_property<JsonArray const*>(payload, "args"sv));
  819. // 5. Let arguments be the result of calling the JSON deserialize algorithm with arguments args.
  820. auto arguments = JS::MarkedVector<JS::Value> { vm.heap() };
  821. args.for_each([&](auto const& arg) {
  822. arguments.append(JS::JSONObject::parse_json_value(vm, arg));
  823. });
  824. // 6. Return success with data script and arguments.
  825. return ScriptArguments { move(script), move(arguments) };
  826. }
  827. }