Serenity Operating System
1/*
2 * Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
3 * Copyright (c) 2022-2023, 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-2023, Tim Flynn <trflynn89@serenityos.org>
7 *
8 * SPDX-License-Identifier: BSD-2-Clause
9 */
10
11#include <AK/JsonObject.h>
12#include <AK/JsonValue.h>
13#include <AK/Time.h>
14#include <AK/Vector.h>
15#include <LibJS/Runtime/JSONObject.h>
16#include <LibJS/Runtime/Value.h>
17#include <LibWeb/CSS/PropertyID.h>
18#include <LibWeb/CSS/StyleProperties.h>
19#include <LibWeb/CSS/StyleValue.h>
20#include <LibWeb/Cookie/Cookie.h>
21#include <LibWeb/Cookie/ParsedCookie.h>
22#include <LibWeb/DOM/Document.h>
23#include <LibWeb/DOM/Element.h>
24#include <LibWeb/DOM/Event.h>
25#include <LibWeb/DOM/NodeFilter.h>
26#include <LibWeb/DOM/NodeIterator.h>
27#include <LibWeb/DOM/ShadowRoot.h>
28#include <LibWeb/Geometry/DOMRect.h>
29#include <LibWeb/HTML/AttributeNames.h>
30#include <LibWeb/HTML/BrowsingContext.h>
31#include <LibWeb/HTML/Focus.h>
32#include <LibWeb/HTML/FormAssociatedElement.h>
33#include <LibWeb/HTML/HTMLDataListElement.h>
34#include <LibWeb/HTML/HTMLInputElement.h>
35#include <LibWeb/HTML/HTMLOptGroupElement.h>
36#include <LibWeb/HTML/HTMLOptionElement.h>
37#include <LibWeb/HTML/HTMLSelectElement.h>
38#include <LibWeb/Page/Page.h>
39#include <LibWeb/Platform/EventLoopPlugin.h>
40#include <LibWeb/Platform/Timer.h>
41#include <LibWeb/UIEvents/MouseEvent.h>
42#include <LibWeb/WebDriver/ExecuteScript.h>
43#include <LibWeb/WebDriver/Screenshot.h>
44#include <WebContent/WebDriverConnection.h>
45
46namespace WebContent {
47
48// https://w3c.github.io/webdriver/#dfn-serialized-cookie
49static JsonValue serialize_cookie(Web::Cookie::Cookie const& cookie)
50{
51 JsonObject serialized_cookie;
52 serialized_cookie.set("name"sv, cookie.name);
53 serialized_cookie.set("value"sv, cookie.value);
54 serialized_cookie.set("path"sv, cookie.path);
55 serialized_cookie.set("domain"sv, cookie.domain);
56 serialized_cookie.set("secure"sv, cookie.secure);
57 serialized_cookie.set("httpOnly"sv, cookie.http_only);
58 serialized_cookie.set("expiry"sv, cookie.expiry_time.to_seconds());
59 serialized_cookie.set("sameSite"sv, Web::Cookie::same_site_to_string(cookie.same_site));
60
61 return serialized_cookie;
62}
63
64static JsonValue serialize_rect(Gfx::IntRect const& rect)
65{
66 JsonObject serialized_rect = {};
67 serialized_rect.set("x", rect.x());
68 serialized_rect.set("y", rect.y());
69 serialized_rect.set("width", rect.width());
70 serialized_rect.set("height", rect.height());
71
72 return serialized_rect;
73}
74
75static Gfx::IntRect compute_window_rect(Web::Page const& page)
76{
77 return {
78 page.window_position().x(),
79 page.window_position().y(),
80 page.window_size().width(),
81 page.window_size().height()
82 };
83}
84
85// https://w3c.github.io/webdriver/#dfn-calculate-the-absolute-position
86static Gfx::IntPoint calculate_absolute_position_of_element(Web::Page const& page, JS::NonnullGCPtr<Web::Geometry::DOMRect> rect)
87{
88 // 1. Let rect be the value returned by calling getBoundingClientRect().
89
90 // 2. Let window be the associated window of current top-level browsing context.
91 auto const* window = page.top_level_browsing_context().active_window();
92
93 // 3. Let x be (scrollX of window + rect’s x coordinate).
94 auto x = (window ? static_cast<int>(window->scroll_x()) : 0) + static_cast<int>(rect->x());
95
96 // 4. Let y be (scrollY of window + rect’s y coordinate).
97 auto y = (window ? static_cast<int>(window->scroll_y()) : 0) + static_cast<int>(rect->y());
98
99 // 5. Return a pair of (x, y).
100 return { x, y };
101}
102
103static Gfx::IntRect calculate_absolute_rect_of_element(Web::Page const& page, Web::DOM::Element const& element)
104{
105 auto bounding_rect = element.get_bounding_client_rect();
106 auto coordinates = calculate_absolute_position_of_element(page, bounding_rect);
107
108 return {
109 coordinates.x(),
110 coordinates.y(),
111 static_cast<int>(bounding_rect->width()),
112 static_cast<int>(bounding_rect->height())
113 };
114}
115
116// https://w3c.github.io/webdriver/#dfn-get-or-create-a-web-element-reference
117static DeprecatedString get_or_create_a_web_element_reference(Web::DOM::Node const& element)
118{
119 // FIXME: 1. For each known element of the current browsing context’s list of known elements:
120 // FIXME: 1. If known element equals element, return success with known element’s web element reference.
121 // FIXME: 2. Add element to the list of known elements of the current browsing context.
122 // FIXME: 3. Return success with the element’s web element reference.
123
124 return DeprecatedString::number(element.id());
125}
126
127// https://w3c.github.io/webdriver/#dfn-web-element-reference-object
128static JsonObject web_element_reference_object(Web::DOM::Node const& element)
129{
130 // https://w3c.github.io/webdriver/#dfn-web-element-identifier
131 static DeprecatedString const web_element_identifier = "element-6066-11e4-a52e-4f735466cecf"sv;
132
133 // 1. Let identifier be the web element identifier.
134 auto identifier = web_element_identifier;
135
136 // 2. Let reference be the result of get or create a web element reference given element.
137 auto reference = get_or_create_a_web_element_reference(element);
138
139 // 3. Return a JSON Object initialized with a property with name identifier and value reference.
140 JsonObject object;
141 object.set("name"sv, identifier);
142 object.set("value"sv, reference);
143 return object;
144}
145
146// https://w3c.github.io/webdriver/#dfn-get-a-known-connected-element
147static ErrorOr<Web::DOM::Element*, Web::WebDriver::Error> get_known_connected_element(StringView element_id)
148{
149 // NOTE: The whole concept of "connected elements" is not implemented yet. See get_or_create_a_web_element_reference().
150 // For now the element is only represented by its ID.
151 auto element = element_id.to_int();
152 if (!element.has_value())
153 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Element ID is not an integer");
154
155 auto* node = Web::DOM::Node::from_id(*element);
156
157 if (!node || !node->is_element())
158 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, DeprecatedString::formatted("Could not find element with ID: {}", element_id));
159
160 return static_cast<Web::DOM::Element*>(node);
161}
162
163// https://w3c.github.io/webdriver/#dfn-get-or-create-a-shadow-root-reference
164static DeprecatedString get_or_create_a_shadow_root_reference(Web::DOM::ShadowRoot const& shadow_root)
165{
166 // FIXME: 1. For each known shadow root of the current browsing context’s list of known shadow roots:
167 // FIXME: 1. If known shadow root equals shadow root, return success with known shadow root’s shadow root reference.
168 // FIXME: 2. Add shadow to the list of known shadow roots of the current browsing context.
169 // FIXME: 3. Return success with the shadow’s shadow root reference.
170
171 return DeprecatedString::number(shadow_root.id());
172}
173
174// https://w3c.github.io/webdriver/#dfn-shadow-root-reference-object
175static JsonObject shadow_root_reference_object(Web::DOM::ShadowRoot const& shadow_root)
176{
177 // https://w3c.github.io/webdriver/#dfn-shadow-root-identifier
178 static DeprecatedString const shadow_root_identifier = "shadow-6066-11e4-a52e-4f735466cecf"sv;
179
180 // 1. Let identifier be the shadow root identifier.
181 auto identifier = shadow_root_identifier;
182
183 // 2. Let reference be the result of get or create a shadow root reference given shadow root.
184 auto reference = get_or_create_a_shadow_root_reference(shadow_root);
185
186 // 3. Return a JSON Object initialized with a property with name identifier and value reference.
187 JsonObject object;
188 object.set("name"sv, move(identifier));
189 object.set("value"sv, move(reference));
190 return object;
191}
192
193// https://w3c.github.io/webdriver/#dfn-get-a-known-shadow-root
194static ErrorOr<Web::DOM::ShadowRoot*, Web::WebDriver::Error> get_known_shadow_root(StringView shadow_id)
195{
196 // NOTE: The whole concept of "known shadow roots" is not implemented yet. See get_or_create_a_shadow_root_reference().
197 // For now the shadow root is only represented by its ID.
198 auto shadow_root = shadow_id.to_int();
199 if (!shadow_root.has_value())
200 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Shadow ID is not an integer");
201
202 auto* node = Web::DOM::Node::from_id(*shadow_root);
203
204 if (!node || !node->is_shadow_root())
205 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, DeprecatedString::formatted("Could not find shadow root with ID: {}", shadow_id));
206
207 return static_cast<Web::DOM::ShadowRoot*>(node);
208}
209
210// https://w3c.github.io/webdriver/#dfn-scrolls-into-view
211static ErrorOr<void> scroll_element_into_view(Web::DOM::Element& element)
212{
213 // 1. Let options be the following ScrollIntoViewOptions:
214 Web::DOM::ScrollIntoViewOptions options {};
215 // Logical scroll position "block"
216 // "end"
217 options.block = Web::Bindings::ScrollLogicalPosition::End;
218 // Logical scroll position "inline"
219 // "nearest"
220 options.inline_ = Web::Bindings::ScrollLogicalPosition::Nearest;
221
222 // 2. Run Function.[[Call]](scrollIntoView, options) with element as the this value.
223 TRY(element.scroll_into_view(options));
224
225 return {};
226}
227
228template<typename PropertyType = DeprecatedString>
229static ErrorOr<PropertyType, Web::WebDriver::Error> get_property(JsonValue const& payload, StringView key)
230{
231 if (!payload.is_object())
232 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
233
234 auto property = payload.as_object().get(key);
235
236 if (!property.has_value())
237 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("No property called '{}' present", key));
238
239 if constexpr (IsSame<PropertyType, DeprecatedString>) {
240 if (!property->is_string())
241 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not a String", key));
242 return property->as_string();
243 } else if constexpr (IsSame<PropertyType, bool>) {
244 if (!property->is_bool())
245 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not a Boolean", key));
246 return property->as_bool();
247 } else if constexpr (IsSame<PropertyType, u32>) {
248 if (!property->is_u32())
249 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not a Number", key));
250 return property->as_u32();
251 } else if constexpr (IsSame<PropertyType, JsonArray const*>) {
252 if (!property->is_array())
253 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not an Array", key));
254 return &property->as_array();
255 } else if constexpr (IsSame<PropertyType, JsonObject const*>) {
256 if (!property->is_object())
257 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not an Object", key));
258 return &property->as_object();
259 } else {
260 static_assert(DependentFalse<PropertyType>, "get_property invoked with unknown property type");
261 VERIFY_NOT_REACHED();
262 }
263}
264
265// https://w3c.github.io/webdriver/#dfn-container
266static Optional<Web::DOM::Element&> container_for_element(Web::DOM::Element& element)
267{
268 auto first_element_reached_by_traversing_the_tree_in_reverse_order = [](Web::DOM::Element& element, auto filter) -> Optional<Web::DOM::Element&> {
269 auto node_iterator = element.document().create_node_iterator(element, to_underlying(Web::DOM::NodeFilter::WhatToShow::SHOW_ALL), nullptr);
270
271 auto current_node = node_iterator->previous_node();
272 while (current_node.has_value() && current_node.value() != nullptr && current_node.value()->is_element()) {
273 if (filter(current_node.value()))
274 return static_cast<Web::DOM::Element&>(*current_node.release_value());
275 }
276
277 return {};
278 };
279
280 // An element’s container is:
281 // -> option element in a valid element context
282 // -> optgroup element in a valid element context
283 // FIXME: Determine if the element is in a valid element context. (https://html.spec.whatwg.org/#concept-element-contexts)
284 if (is<Web::HTML::HTMLOptionElement>(element) || is<Web::HTML::HTMLOptGroupElement>(element)) {
285 // The element’s element context, which is determined by:
286 // 1. Let datalist parent be the first datalist element reached by traversing the tree in reverse order from element, or undefined if the root of the tree is reached.
287 auto datalist_parent = first_element_reached_by_traversing_the_tree_in_reverse_order(element, [](auto& node) { return is<Web::HTML::HTMLDataListElement>(*node); });
288
289 // 2. Let select parent be the first select element reached by traversing the tree in reverse order from element, or undefined if the root of the tree is reached.
290 auto select_parent = first_element_reached_by_traversing_the_tree_in_reverse_order(element, [](auto& node) { return is<Web::HTML::HTMLSelectElement>(*node); });
291
292 // 3. If datalist parent is undefined, the element context is select parent. Otherwise, the element context is datalist parent.
293 if (!datalist_parent.has_value())
294 return select_parent;
295 return datalist_parent;
296 }
297 // -> option element in an invalid element context
298 else if (is<Web::HTML::HTMLOptionElement>(element)) {
299 // The element does not have a container.
300 return {};
301 }
302 // -> Otherwise
303 else {
304 // The container is the element itself.
305 return element;
306 }
307}
308
309template<typename T>
310static bool fire_an_event(DeprecatedString name, Optional<Web::DOM::Element&> target)
311{
312 // FIXME: This is supposed to call the https://dom.spec.whatwg.org/#concept-event-fire DOM algorithm,
313 // but that doesn't seem to be implemented elsewhere. So, we'll ad-hack it for now. :^)
314
315 if (!target.has_value())
316 return false;
317
318 auto event = T::create(target->realm(), name).release_value_but_fixme_should_propagate_errors();
319 return target->dispatch_event(event);
320}
321
322ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(Web::PageClient& page_client, DeprecatedString const& webdriver_ipc_path)
323{
324 dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path);
325 auto socket = TRY(Core::LocalSocket::connect(webdriver_ipc_path));
326
327 dbgln_if(WEBDRIVER_DEBUG, "Connected to WebDriver");
328 return adopt_nonnull_ref_or_enomem(new (nothrow) WebDriverConnection(move(socket), page_client));
329}
330
331WebDriverConnection::WebDriverConnection(NonnullOwnPtr<Core::LocalSocket> socket, Web::PageClient& page_client)
332 : IPC::ConnectionToServer<WebDriverClientEndpoint, WebDriverServerEndpoint>(*this, move(socket))
333 , m_page_client(page_client)
334{
335}
336
337// https://w3c.github.io/webdriver/#dfn-close-the-session
338void WebDriverConnection::close_session()
339{
340 // 1. Set the webdriver-active flag to false.
341 set_is_webdriver_active(false);
342
343 // 2. An endpoint node must close any top-level browsing contexts associated with the session, without prompting to unload.
344 if (!m_page_client.page().top_level_browsing_context().has_been_discarded())
345 m_page_client.page().top_level_browsing_context().close();
346}
347
348void WebDriverConnection::set_page_load_strategy(Web::WebDriver::PageLoadStrategy const& page_load_strategy)
349{
350 m_page_load_strategy = page_load_strategy;
351}
352
353void WebDriverConnection::set_unhandled_prompt_behavior(Web::WebDriver::UnhandledPromptBehavior const& unhandled_prompt_behavior)
354{
355 m_unhandled_prompt_behavior = unhandled_prompt_behavior;
356}
357
358void WebDriverConnection::set_strict_file_interactability(bool strict_file_interactability)
359{
360 m_strict_file_interactability = strict_file_interactability;
361}
362
363void WebDriverConnection::set_is_webdriver_active(bool is_webdriver_active)
364{
365 m_page_client.page().set_is_webdriver_active(is_webdriver_active);
366}
367
368// 9.1 Get Timeouts, https://w3c.github.io/webdriver/#dfn-get-timeouts
369Messages::WebDriverClient::GetTimeoutsResponse WebDriverConnection::get_timeouts()
370{
371 // 1. Let timeouts be the timeouts object for session’s timeouts configuration
372 auto timeouts = Web::WebDriver::timeouts_object(m_timeouts_configuration);
373
374 // 2. Return success with data timeouts.
375 return timeouts;
376}
377
378// 9.2 Set Timeouts, https://w3c.github.io/webdriver/#dfn-set-timeouts
379Messages::WebDriverClient::SetTimeoutsResponse WebDriverConnection::set_timeouts(JsonValue const& payload)
380{
381 // 1. Let timeouts be the result of trying to JSON deserialize as a timeouts configuration the request’s parameters.
382 auto timeouts = TRY(Web::WebDriver::json_deserialize_as_a_timeouts_configuration(payload));
383
384 // 2. Make the session timeouts the new timeouts.
385 m_timeouts_configuration = move(timeouts);
386
387 // 3. Return success with data null.
388 return JsonValue {};
389}
390
391// 10.1 Navigate To, https://w3c.github.io/webdriver/#navigate-to
392Messages::WebDriverClient::NavigateToResponse WebDriverConnection::navigate_to(JsonValue const& payload)
393{
394 dbgln_if(WEBDRIVER_DEBUG, "WebDriverConnection::navigate_to {}", payload);
395
396 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
397 TRY(ensure_open_top_level_browsing_context());
398
399 // 2. Let url be the result of getting the property url from the parameters argument.
400 if (!payload.is_object() || !payload.as_object().has_string("url"sv))
401 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload doesn't have a string `url`"sv);
402 URL url(payload.as_object().get_deprecated_string("url"sv).value());
403
404 // 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.
405
406 // 4. Handle any user prompts and return its value if it is an error.
407 TRY(handle_any_user_prompts());
408
409 // 5. Let current URL be the current top-level browsing context’s active document’s URL.
410 auto const& current_url = m_page_client.page().top_level_browsing_context().active_document()->url();
411 // FIXME: 6. If current URL and url do not have the same absolute URL:
412 // 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.
413
414 // 7. Navigate the current top-level browsing context to url.
415 m_page_client.page().load(url);
416
417 // 8. If url is special except for file and current URL and URL do not have the same absolute URL:
418 if (url.is_special() && url.scheme() != "file"sv && current_url != url) {
419 // a. Try to wait for navigation to complete.
420 TRY(wait_for_navigation_to_complete());
421
422 // FIXME: b. Try to run the post-navigation checks.
423 }
424
425 // FIXME: 9. Set the current browsing context with the current top-level browsing context.
426 // 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.
427
428 // 11. Return success with data null.
429 return JsonValue {};
430}
431
432// 10.2 Get Current URL, https://w3c.github.io/webdriver/#get-current-url
433Messages::WebDriverClient::GetCurrentUrlResponse WebDriverConnection::get_current_url()
434{
435 dbgln_if(WEBDRIVER_DEBUG, "WebDriverConnection::get_current_url");
436
437 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
438 TRY(ensure_open_top_level_browsing_context());
439
440 // 2. Handle any user prompts and return its value if it is an error.
441 TRY(handle_any_user_prompts());
442
443 // 3. Let url be the serialization of the current top-level browsing context’s active document’s document URL.
444 auto url = m_page_client.page().top_level_browsing_context().active_document()->url().to_deprecated_string();
445
446 // 4. Return success with data url.
447 return url;
448}
449
450// 10.3 Back, https://w3c.github.io/webdriver/#dfn-back
451Messages::WebDriverClient::BackResponse WebDriverConnection::back()
452{
453 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
454 TRY(ensure_open_top_level_browsing_context());
455
456 // 2. Handle any user prompts and return its value if it is an error.
457 TRY(handle_any_user_prompts());
458
459 // 3. Traverse the history by a delta –1 for the current browsing context.
460 m_page_client.page_did_request_navigate_back();
461
462 // FIXME: 4. If the previous step completed results in a pageHide event firing, wait until pageShow event fires or for the session page load timeout milliseconds to pass, whichever occurs sooner.
463 // FIXME: 5. If the previous step completed by the session page load timeout being reached, and user prompts have been handled, return error with error code timeout.
464
465 // 6. Return success with data null.
466 return JsonValue {};
467}
468
469// 10.4 Forward, https://w3c.github.io/webdriver/#dfn-forward
470Messages::WebDriverClient::ForwardResponse WebDriverConnection::forward()
471{
472 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
473 TRY(ensure_open_top_level_browsing_context());
474
475 // 2. Handle any user prompts and return its value if it is an error.
476 TRY(handle_any_user_prompts());
477
478 // 3. Traverse the history by a delta 1 for the current browsing context.
479 m_page_client.page_did_request_navigate_forward();
480
481 // FIXME: 4. If the previous step completed results in a pageHide event firing, wait until pageShow event fires or for the session page load timeout milliseconds to pass, whichever occurs sooner.
482 // FIXME: 5. If the previous step completed by the session page load timeout being reached, and user prompts have been handled, return error with error code timeout.
483
484 // 6. Return success with data null.
485 return JsonValue {};
486}
487
488// 10.5 Refresh, https://w3c.github.io/webdriver/#dfn-refresh
489Messages::WebDriverClient::RefreshResponse WebDriverConnection::refresh()
490{
491 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
492 TRY(ensure_open_top_level_browsing_context());
493
494 // 2. Handle any user prompts and return its value if it is an error.
495 TRY(handle_any_user_prompts());
496
497 // 3. Initiate an overridden reload of the current top-level browsing context’s active document.
498 m_page_client.page_did_request_refresh();
499
500 // FIXME: 4. If url is special except for file:
501 // FIXME: 1. Try to wait for navigation to complete.
502 // FIXME: 2. Try to run the post-navigation checks.
503 // FIXME: 5. Set the current browsing context with current top-level browsing context.
504
505 // 6. Return success with data null.
506 return JsonValue {};
507}
508
509// 10.6 Get Title, https://w3c.github.io/webdriver/#dfn-get-title
510Messages::WebDriverClient::GetTitleResponse WebDriverConnection::get_title()
511{
512 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
513 TRY(ensure_open_top_level_browsing_context());
514
515 // 2. Handle any user prompts and return its value if it is an error.
516 TRY(handle_any_user_prompts());
517
518 // 3. Let title be the initial value of the title IDL attribute of the current top-level browsing context's active document.
519 auto title = m_page_client.page().top_level_browsing_context().active_document()->title();
520
521 // 4. Return success with data title.
522 return title;
523}
524
525// 11.2 Close Window, https://w3c.github.io/webdriver/#dfn-close-window
526Messages::WebDriverClient::CloseWindowResponse WebDriverConnection::close_window()
527{
528 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
529 TRY(ensure_open_top_level_browsing_context());
530
531 // 2. Handle any user prompts and return its value if it is an error.
532 TRY(handle_any_user_prompts());
533
534 // 3. Close the current top-level browsing context.
535 m_page_client.page().top_level_browsing_context().close();
536 return JsonValue {};
537}
538
539// 11.8.1 Get Window Rect, https://w3c.github.io/webdriver/#dfn-get-window-rect
540Messages::WebDriverClient::GetWindowRectResponse WebDriverConnection::get_window_rect()
541{
542 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
543 TRY(ensure_open_top_level_browsing_context());
544
545 // 2. Handle any user prompts and return its value if it is an error.
546 TRY(handle_any_user_prompts());
547
548 // 3. Return success with data set to the WindowRect object for the current top-level browsing context.
549 return serialize_rect(compute_window_rect(m_page_client.page()));
550}
551
552// 11.8.2 Set Window Rect, https://w3c.github.io/webdriver/#dfn-set-window-rect
553Messages::WebDriverClient::SetWindowRectResponse WebDriverConnection::set_window_rect(JsonValue const& payload)
554{
555 if (!payload.is_object())
556 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
557
558 auto const& properties = payload.as_object();
559
560 auto resolve_property = [](auto name, auto const& property, auto min, auto max) -> ErrorOr<Optional<i32>, Web::WebDriver::Error> {
561 if (property.is_null())
562 return Optional<i32> {};
563 if (!property.is_number())
564 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not a Number", name));
565
566 auto number = property.template to_number<i64>();
567
568 if (number < min)
569 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' value {} exceeds the minimum allowed value {}", name, number, min));
570 if (number > max)
571 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' value {} exceeds the maximum allowed value {}", name, number, max));
572
573 return static_cast<i32>(number);
574 };
575
576 // 1. Let width be the result of getting a property named width from the parameters argument, else let it be null.
577 auto width_property = properties.get("width"sv).value_or(JsonValue());
578
579 // 2. Let height be the result of getting a property named height from the parameters argument, else let it be null.
580 auto height_property = properties.get("height"sv).value_or(JsonValue());
581
582 // 3. Let x be the result of getting a property named x from the parameters argument, else let it be null.
583 auto x_property = properties.get("x"sv).value_or(JsonValue());
584
585 // 4. Let y be the result of getting a property named y from the parameters argument, else let it be null.
586 auto y_property = properties.get("y"sv).value_or(JsonValue());
587
588 // 5. If width or height is neither null nor a Number from 0 to 2^31 − 1, return error with error code invalid argument.
589 auto width = TRY(resolve_property("width"sv, width_property, 0, NumericLimits<i32>::max()));
590 auto height = TRY(resolve_property("height"sv, height_property, 0, NumericLimits<i32>::max()));
591
592 // 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.
593 auto x = TRY(resolve_property("x"sv, x_property, NumericLimits<i32>::min(), NumericLimits<i32>::max()));
594 auto y = TRY(resolve_property("y"sv, y_property, NumericLimits<i32>::min(), NumericLimits<i32>::max()));
595
596 // 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.
597
598 // 8. If the current top-level browsing context is no longer open, return error with error code no such window.
599 TRY(ensure_open_top_level_browsing_context());
600
601 // 9. Handle any user prompts and return its value if it is an error.
602 TRY(handle_any_user_prompts());
603
604 // FIXME: 10. Fully exit fullscreen.
605
606 // 11. Restore the window.
607 restore_the_window();
608
609 Gfx::IntRect window_rect;
610
611 // 11. If width and height are not null:
612 if (width.has_value() && height.has_value()) {
613 // 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.
614 // 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.
615 auto size = m_page_client.page_did_request_resize_window({ *width, *height });
616 window_rect.set_size(size);
617 } else {
618 window_rect.set_size(m_page_client.page().window_size().to_type<int>());
619 }
620
621 // 12. If x and y are not null:
622 if (x.has_value() && y.has_value()) {
623 // 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.
624 auto position = m_page_client.page_did_request_reposition_window({ *x, *y });
625 window_rect.set_location(position);
626 } else {
627 window_rect.set_location(m_page_client.page().window_position().to_type<int>());
628 }
629
630 // 14. Return success with data set to the WindowRect object for the current top-level browsing context.
631 return serialize_rect(window_rect);
632}
633
634// 11.8.3 Maximize Window, https://w3c.github.io/webdriver/#dfn-maximize-window
635Messages::WebDriverClient::MaximizeWindowResponse WebDriverConnection::maximize_window()
636{
637 // 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.
638
639 // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
640 TRY(ensure_open_top_level_browsing_context());
641
642 // 3. Handle any user prompts and return its value if it is an error.
643 TRY(handle_any_user_prompts());
644
645 // FIXME: 4. Fully exit fullscreen.
646
647 // 5. Restore the window.
648 restore_the_window();
649
650 // 6. Maximize the window of the current top-level browsing context.
651 auto window_rect = maximize_the_window();
652
653 // 7. Return success with data set to the WindowRect object for the current top-level browsing context.
654 return serialize_rect(window_rect);
655}
656
657// 11.8.4 Minimize Window, https://w3c.github.io/webdriver/#minimize-window
658Messages::WebDriverClient::MinimizeWindowResponse WebDriverConnection::minimize_window()
659{
660 // 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.
661
662 // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
663 TRY(ensure_open_top_level_browsing_context());
664
665 // 3. Handle any user prompts and return its value if it is an error.
666 TRY(handle_any_user_prompts());
667
668 // FIXME: 4. Fully exit fullscreen.
669
670 // 5. Iconify the window.
671 auto window_rect = iconify_the_window();
672
673 // 6. Return success with data set to the WindowRect object for the current top-level browsing context.
674 return serialize_rect(window_rect);
675}
676
677// 11.8.5 Fullscreen Window, https://w3c.github.io/webdriver/#dfn-fullscreen-window
678Messages::WebDriverClient::FullscreenWindowResponse WebDriverConnection::fullscreen_window()
679{
680 // 1. If the remote end does not support fullscreen return error with error code unsupported operation.
681
682 // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
683 TRY(ensure_open_top_level_browsing_context());
684
685 // 3. Handle any user prompts and return its value if it is an error.
686 TRY(handle_any_user_prompts());
687
688 // 4. Restore the window.
689 restore_the_window();
690
691 // 5. FIXME: Call fullscreen an element with the current top-level browsing context’s active document’s document element.
692 // As described in https://fullscreen.spec.whatwg.org/#fullscreen-an-element
693 // NOTE: What we do here is basically `requestFullscreen(options)` with options["navigationUI"]="show"
694 auto rect = m_page_client.page_did_request_fullscreen_window();
695
696 // 6. Return success with data set to the WindowRect object for the current top-level browsing context.
697 return serialize_rect(rect);
698}
699
700// 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element
701Messages::WebDriverClient::FindElementResponse WebDriverConnection::find_element(JsonValue const& payload)
702{
703 // 1. Let location strategy be the result of getting a property called "using".
704 auto location_strategy_string = TRY(get_property(payload, "using"sv));
705 auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
706
707 // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
708 if (!location_strategy.has_value())
709 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
710
711 // 3. Let selector be the result of getting a property called "value".
712 // 4. If selector is undefined, return error with error code invalid argument.
713 auto selector = TRY(get_property(payload, "value"sv));
714
715 // 5. If the current browsing context is no longer open, return error with error code no such window.
716 TRY(ensure_open_top_level_browsing_context());
717
718 // 6. Handle any user prompts and return its value if it is an error.
719 TRY(handle_any_user_prompts());
720
721 auto start_node_getter = [this]() -> StartNodeGetter::ReturnType {
722 // 7. Let start node be the current browsing context’s document element.
723 auto* start_node = m_page_client.page().top_level_browsing_context().active_document();
724
725 // 8. If start node is null, return error with error code no such element.
726 if (!start_node)
727 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
728
729 return start_node;
730 };
731
732 // 9. Let result be the result of trying to Find with start node, location strategy, and selector.
733 auto result = TRY(find(move(start_node_getter), *location_strategy, selector));
734
735 // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
736 if (result.is_empty())
737 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
738
739 return result.take(0);
740}
741
742// 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
743Messages::WebDriverClient::FindElementsResponse WebDriverConnection::find_elements(JsonValue const& payload)
744{
745 // 1. Let location strategy be the result of getting a property called "using".
746 auto location_strategy_string = TRY(get_property(payload, "using"sv));
747 auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
748
749 // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
750 if (!location_strategy.has_value())
751 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
752
753 // 3. Let selector be the result of getting a property called "value".
754 // 4. If selector is undefined, return error with error code invalid argument.
755 auto selector = TRY(get_property(payload, "value"sv));
756
757 // 5. If the current browsing context is no longer open, return error with error code no such window.
758 TRY(ensure_open_top_level_browsing_context());
759
760 // 6. Handle any user prompts and return its value if it is an error.
761 TRY(handle_any_user_prompts());
762
763 auto start_node_getter = [this]() -> StartNodeGetter::ReturnType {
764 // 7. Let start node be the current browsing context’s document element.
765 auto* start_node = m_page_client.page().top_level_browsing_context().active_document();
766
767 // 8. If start node is null, return error with error code no such element.
768 if (!start_node)
769 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
770
771 return start_node;
772 };
773
774 // 9. Return the result of trying to Find with start node, location strategy, and selector.
775 return TRY(find(move(start_node_getter), *location_strategy, selector));
776}
777
778// 12.3.4 Find Element From Element, https://w3c.github.io/webdriver/#dfn-find-element-from-element
779Messages::WebDriverClient::FindElementFromElementResponse WebDriverConnection::find_element_from_element(JsonValue const& payload, String const& element_id)
780{
781 // 1. Let location strategy be the result of getting a property called "using".
782 auto location_strategy_string = TRY(get_property(payload, "using"sv));
783 auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
784
785 // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
786 if (!location_strategy.has_value())
787 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
788
789 // 3. Let selector be the result of getting a property called "value".
790 // 4. If selector is undefined, return error with error code invalid argument.
791 auto selector = TRY(get_property(payload, "value"sv));
792
793 // 5. If the current browsing context is no longer open, return error with error code no such window.
794 TRY(ensure_open_top_level_browsing_context());
795
796 // 6. Handle any user prompts and return its value if it is an error.
797 TRY(handle_any_user_prompts());
798
799 auto start_node_getter = [&]() -> StartNodeGetter::ReturnType {
800 // 7. Let start node be the result of trying to get a known connected element with url variable element id.
801 return TRY(get_known_connected_element(element_id));
802 };
803
804 // 8. Let result be the value of trying to Find with start node, location strategy, and selector.
805 auto result = TRY(find(move(start_node_getter), *location_strategy, selector));
806
807 // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
808 if (result.is_empty())
809 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
810
811 return result.take(0);
812}
813
814// 12.3.5 Find Elements From Element, https://w3c.github.io/webdriver/#dfn-find-elements-from-element
815Messages::WebDriverClient::FindElementsFromElementResponse WebDriverConnection::find_elements_from_element(JsonValue const& payload, String const& element_id)
816{
817 // 1. Let location strategy be the result of getting a property called "using".
818 auto location_strategy_string = TRY(get_property(payload, "using"sv));
819 auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
820
821 // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
822 if (!location_strategy.has_value())
823 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
824
825 // 3. Let selector be the result of getting a property called "value".
826 // 4. If selector is undefined, return error with error code invalid argument.
827 auto selector = TRY(get_property(payload, "value"sv));
828
829 // 5. If the current browsing context is no longer open, return error with error code no such window.
830 TRY(ensure_open_top_level_browsing_context());
831
832 // 6. Handle any user prompts and return its value if it is an error.
833 TRY(handle_any_user_prompts());
834
835 auto start_node_getter = [&]() -> StartNodeGetter::ReturnType {
836 // 7. Let start node be the result of trying to get a known connected element with url variable element id.
837 return TRY(get_known_connected_element(element_id));
838 };
839
840 // 8. Return the result of trying to Find with start node, location strategy, and selector.
841 return TRY(find(move(start_node_getter), *location_strategy, selector));
842}
843
844// 12.3.6 Find Element From Shadow Root, https://w3c.github.io/webdriver/#find-element-from-shadow-root
845Messages::WebDriverClient::FindElementFromShadowRootResponse WebDriverConnection::find_element_from_shadow_root(JsonValue const& payload, String const& shadow_id)
846{
847 // 1. Let location strategy be the result of getting a property called "using".
848 auto location_strategy_string = TRY(get_property(payload, "using"sv));
849 auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
850
851 // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
852 if (!location_strategy.has_value())
853 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
854
855 // 3. Let selector be the result of getting a property called "value".
856 // 4. If selector is undefined, return error with error code invalid argument.
857 auto selector = TRY(get_property(payload, "value"sv));
858
859 // 5. If the current browsing context is no longer open, return error with error code no such window.
860 TRY(ensure_open_top_level_browsing_context());
861
862 // 6. Handle any user prompts and return its value if it is an error.
863 TRY(handle_any_user_prompts());
864
865 auto start_node_getter = [&]() -> StartNodeGetter::ReturnType {
866 // 7. Let start node be the result of trying to get a known shadow root with url variable shadow id.
867 return TRY(get_known_shadow_root(shadow_id));
868 };
869
870 // 8. Let result be the value of trying to Find with start node, location strategy, and selector.
871 auto result = TRY(find(move(start_node_getter), *location_strategy, selector));
872
873 // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
874 if (result.is_empty())
875 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
876
877 return result.take(0);
878}
879
880// 12.3.7 Find Elements From Shadow Root, https://w3c.github.io/webdriver/#find-elements-from-shadow-root
881Messages::WebDriverClient::FindElementsFromShadowRootResponse WebDriverConnection::find_elements_from_shadow_root(JsonValue const& payload, String const& shadow_id)
882{
883 // 1. Let location strategy be the result of getting a property called "using".
884 auto location_strategy_string = TRY(get_property(payload, "using"sv));
885 auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
886
887 // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
888 if (!location_strategy.has_value())
889 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
890
891 // 3. Let selector be the result of getting a property called "value".
892 // 4. If selector is undefined, return error with error code invalid argument.
893 auto selector = TRY(get_property(payload, "value"sv));
894
895 // 5. If the current browsing context is no longer open, return error with error code no such window.
896 TRY(ensure_open_top_level_browsing_context());
897
898 // 6. Handle any user prompts and return its value if it is an error.
899 TRY(handle_any_user_prompts());
900
901 auto start_node_getter = [&]() -> StartNodeGetter::ReturnType {
902 // 7. Let start node be the result of trying to get a known shadow root with url variable shadow id.
903 return TRY(get_known_shadow_root(shadow_id));
904 };
905
906 // 8. Return the result of trying to Find with start node, location strategy, and selector.
907 return TRY(find(move(start_node_getter), *location_strategy, selector));
908}
909
910// 12.3.8 Get Active Element, https://w3c.github.io/webdriver/#get-active-element
911Messages::WebDriverClient::GetActiveElementResponse WebDriverConnection::get_active_element()
912{
913 // 1. If the current browsing context is no longer open, return error with error code no such window.
914 TRY(ensure_open_top_level_browsing_context());
915
916 // 2. Handle any user prompts and return its value if it is an error.
917 TRY(handle_any_user_prompts());
918
919 // 3. Let active element be the active element of the current browsing context’s document element.
920 auto* active_element = m_page_client.page().top_level_browsing_context().active_document()->active_element();
921
922 // 4. If active element is a non-null element, return success with data set to web element reference object for active element.
923 // Otherwise, return error with error code no such element.
924 if (active_element)
925 return DeprecatedString::number(active_element->id());
926
927 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The current document does not have an active element"sv);
928}
929
930// 12.3.9 Get Element Shadow Root, https://w3c.github.io/webdriver/#get-element-shadow-root
931Messages::WebDriverClient::GetElementShadowRootResponse WebDriverConnection::get_element_shadow_root(String const& element_id)
932{
933 // 1. If the current browsing context is no longer open, return error with error code no such window.
934 TRY(ensure_open_top_level_browsing_context());
935
936 // 2. Handle any user prompts and return its value if it is an error.
937 TRY(handle_any_user_prompts());
938
939 // 3. Let element be the result of trying to get a known connected element with url variable element id.
940 auto* element = TRY(get_known_connected_element(element_id));
941
942 // 4. Let shadow root be element's shadow root.
943 auto* shadow_root = element->shadow_root_internal();
944
945 // 5. If shadow root is null, return error with error code no such shadow root.
946 if (!shadow_root)
947 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchShadowRoot, DeprecatedString::formatted("Element with ID '{}' does not have a shadow root", element_id));
948
949 // 6. Let serialized be the shadow root reference object for shadow root.
950 auto serialized = shadow_root_reference_object(*shadow_root);
951
952 // 7. Return success with data serialized.
953 return serialized;
954}
955
956// 12.4.1 Is Element Selected, https://w3c.github.io/webdriver/#dfn-is-element-selected
957Messages::WebDriverClient::IsElementSelectedResponse WebDriverConnection::is_element_selected(String const& element_id)
958{
959 // 1. If the current browsing context is no longer open, return error with error code no such window.
960 TRY(ensure_open_top_level_browsing_context());
961
962 // 2. Handle any user prompts and return its value if it is an error.
963 TRY(handle_any_user_prompts());
964
965 // 3. Let element be the result of trying to get a known connected element with url variable element id.
966 auto* element = TRY(get_known_connected_element(element_id));
967
968 // 4. Let selected be the value corresponding to the first matching statement:
969 bool selected = false;
970
971 // element is an input element with a type attribute in the Checkbox- or Radio Button state
972 if (is<Web::HTML::HTMLInputElement>(*element)) {
973 // -> The result of element’s checkedness.
974 auto& input = static_cast<Web::HTML::HTMLInputElement&>(*element);
975 using enum Web::HTML::HTMLInputElement::TypeAttributeState;
976
977 if (input.type_state() == Checkbox || input.type_state() == RadioButton)
978 selected = input.checked();
979 }
980 // element is an option element
981 else if (is<Web::HTML::HTMLOptionElement>(*element)) {
982 // -> The result of element’s selectedness.
983 selected = static_cast<Web::HTML::HTMLOptionElement&>(*element).selected();
984 }
985 // Otherwise
986 // -> False.
987
988 // 5. Return success with data selected.
989 return selected;
990}
991
992// 12.4.2 Get Element Attribute, https://w3c.github.io/webdriver/#dfn-get-element-attribute
993Messages::WebDriverClient::GetElementAttributeResponse WebDriverConnection::get_element_attribute(String const& element_id, String const& name)
994{
995 // 1. If the current browsing context is no longer open, return error with error code no such window.
996 TRY(ensure_open_top_level_browsing_context());
997
998 // 2. Handle any user prompts and return its value if it is an error.
999 TRY(handle_any_user_prompts());
1000
1001 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1002 auto* element = TRY(get_known_connected_element(element_id));
1003
1004 // 4. Let result be the result of the first matching condition:
1005 Optional<DeprecatedString> result;
1006
1007 auto deprecated_name = name.to_deprecated_string();
1008
1009 // -> If name is a boolean attribute
1010 if (Web::HTML::is_boolean_attribute(deprecated_name)) {
1011 // "true" (string) if the element has the attribute, otherwise null.
1012 if (element->has_attribute(deprecated_name))
1013 result = "true"sv;
1014 }
1015 // -> Otherwise
1016 else {
1017 // The result of getting an attribute by name name.
1018 result = element->get_attribute(deprecated_name);
1019 }
1020
1021 // 5. Return success with data result.
1022 if (result.has_value())
1023 return result.release_value();
1024 return JsonValue {};
1025}
1026
1027// 12.4.3 Get Element Property, https://w3c.github.io/webdriver/#dfn-get-element-property
1028Messages::WebDriverClient::GetElementPropertyResponse WebDriverConnection::get_element_property(String const& element_id, String const& name)
1029{
1030 // 1. If the current browsing context is no longer open, return error with error code no such window.
1031 TRY(ensure_open_top_level_browsing_context());
1032
1033 // 2. Handle any user prompts and return its value if it is an error.
1034 TRY(handle_any_user_prompts());
1035
1036 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1037 auto* element = TRY(get_known_connected_element(element_id));
1038
1039 Optional<DeprecatedString> result;
1040
1041 // 4. Let property be the result of calling the Object.[[GetProperty]](name) on element.
1042 if (auto property_or_error = element->get(name.to_deprecated_string()); !property_or_error.is_throw_completion()) {
1043 auto property = property_or_error.release_value();
1044
1045 // 5. Let result be the value of property if not undefined, or null.
1046 if (!property.is_undefined()) {
1047 if (auto string_or_error = property.to_deprecated_string(element->vm()); !string_or_error.is_error())
1048 result = string_or_error.release_value();
1049 }
1050 }
1051
1052 // 6. Return success with data result.
1053 if (result.has_value())
1054 return result.release_value();
1055 return JsonValue {};
1056}
1057
1058// 12.4.4 Get Element CSS Value, https://w3c.github.io/webdriver/#dfn-get-element-css-value
1059Messages::WebDriverClient::GetElementCssValueResponse WebDriverConnection::get_element_css_value(String const& element_id, String const& name)
1060{
1061 // 1. If the current browsing context is no longer open, return error with error code no such window.
1062 TRY(ensure_open_top_level_browsing_context());
1063
1064 // 2. Handle any user prompts and return its value if it is an error.
1065 TRY(handle_any_user_prompts());
1066
1067 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1068 auto* element = TRY(get_known_connected_element(element_id));
1069
1070 // 4. Let computed value be the result of the first matching condition:
1071 DeprecatedString computed_value;
1072
1073 // -> current browsing context’s active document’s type is not "xml"
1074 if (!m_page_client.page().top_level_browsing_context().active_document()->is_xml_document()) {
1075 // computed value of parameter property name from element’s style declarations. property name is obtained from url variables.
1076 auto property = Web::CSS::property_id_from_string(name);
1077
1078 if (auto* computed_values = element->computed_css_values())
1079 computed_value = computed_values->property(property)->to_string().release_value_but_fixme_should_propagate_errors().to_deprecated_string();
1080 }
1081 // -> Otherwise
1082 else {
1083 // "" (empty string)
1084 computed_value = DeprecatedString::empty();
1085 }
1086
1087 // 5. Return success with data computed value.
1088 return computed_value;
1089}
1090
1091// 12.4.5 Get Element Text, https://w3c.github.io/webdriver/#dfn-get-element-text
1092Messages::WebDriverClient::GetElementTextResponse WebDriverConnection::get_element_text(String const& element_id)
1093{
1094 // 1. If the current browsing context is no longer open, return error with error code no such window.
1095 TRY(ensure_open_top_level_browsing_context());
1096
1097 // 2. Handle any user prompts and return its value if it is an error.
1098 TRY(handle_any_user_prompts());
1099
1100 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1101 auto* element = TRY(get_known_connected_element(element_id));
1102
1103 // 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.
1104 auto rendered_text = element->text_content();
1105
1106 // 5. Return success with data rendered text.
1107 return rendered_text;
1108}
1109
1110// 12.4.6 Get Element Tag Name, https://w3c.github.io/webdriver/#dfn-get-element-tag-name
1111Messages::WebDriverClient::GetElementTagNameResponse WebDriverConnection::get_element_tag_name(String const& element_id)
1112{
1113 // 1. If the current browsing context is no longer open, return error with error code no such window.
1114 TRY(ensure_open_top_level_browsing_context());
1115
1116 // 2. Handle any user prompts and return its value if it is an error.
1117 TRY(handle_any_user_prompts());
1118
1119 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1120 auto* element = TRY(get_known_connected_element(element_id));
1121
1122 // 4. Let qualified name be the result of getting element’s tagName IDL attribute.
1123 auto qualified_name = element->tag_name();
1124
1125 // 5. Return success with data qualified name.
1126 return qualified_name;
1127}
1128
1129// 12.4.7 Get Element Rect, https://w3c.github.io/webdriver/#dfn-get-element-rect
1130Messages::WebDriverClient::GetElementRectResponse WebDriverConnection::get_element_rect(String const& element_id)
1131{
1132 // 1. If the current browsing context is no longer open, return error with error code no such window.
1133 TRY(ensure_open_top_level_browsing_context());
1134
1135 // 2. Handle any user prompts and return its value if it is an error.
1136 TRY(handle_any_user_prompts());
1137
1138 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1139 auto* element = TRY(get_known_connected_element(element_id));
1140
1141 // 4. Calculate the absolute position of element and let it be coordinates.
1142 // 5. Let rect be element’s bounding rectangle.
1143 auto rect = calculate_absolute_rect_of_element(m_page_client.page(), *element);
1144
1145 // 6. Let body be a new JSON Object initialized with:
1146 // "x"
1147 // The first value of coordinates.
1148 // "y"
1149 // The second value of coordinates.
1150 // "width"
1151 // Value of rect’s width dimension.
1152 // "height"
1153 // Value of rect’s height dimension.
1154 auto body = serialize_rect(rect);
1155
1156 // 7. Return success with data body.
1157 return body;
1158}
1159
1160// 12.4.8 Is Element Enabled, https://w3c.github.io/webdriver/#dfn-is-element-enabled
1161Messages::WebDriverClient::IsElementEnabledResponse WebDriverConnection::is_element_enabled(String const& element_id)
1162{
1163 // 1. If the current browsing context is no longer open, return error with error code no such window.
1164 TRY(ensure_open_top_level_browsing_context());
1165
1166 // 2. Handle any user prompts and return its value if it is an error.
1167 TRY(handle_any_user_prompts());
1168
1169 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1170 auto* element = TRY(get_known_connected_element(element_id));
1171
1172 // 4. Let enabled be a boolean initially set to true if the current browsing context’s active document’s type is not "xml".
1173 // 5. Otherwise, let enabled to false and jump to the last step of this algorithm.
1174 bool enabled = !m_page_client.page().top_level_browsing_context().active_document()->is_xml_document();
1175
1176 // 6. Set enabled to false if a form control is disabled.
1177 if (enabled && is<Web::HTML::FormAssociatedElement>(*element)) {
1178 auto& form_associated_element = dynamic_cast<Web::HTML::FormAssociatedElement&>(*element);
1179 enabled = form_associated_element.enabled();
1180 }
1181
1182 // 7. Return success with data enabled.
1183 return enabled;
1184}
1185
1186// 12.4.9 Get Computed Role, https://w3c.github.io/webdriver/#dfn-get-computed-role
1187Messages::WebDriverClient::GetComputedRoleResponse WebDriverConnection::get_computed_role(String const& element_id)
1188{
1189 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
1190 TRY(ensure_open_top_level_browsing_context());
1191
1192 // 2. Handle any user prompts and return its value if it is an error.
1193 TRY(handle_any_user_prompts());
1194
1195 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1196 auto* element = TRY(get_known_connected_element(element_id));
1197
1198 // 4. Let role be the result of computing the WAI-ARIA role of element.
1199 auto role = element->role_or_default();
1200
1201 // 5. Return success with data role.
1202 if (role.has_value())
1203 return Web::ARIA::role_name(*role);
1204 return ""sv;
1205}
1206
1207// 12.4.10 Get Computed Label, https://w3c.github.io/webdriver/#get-computed-label
1208Messages::WebDriverClient::GetComputedLabelResponse WebDriverConnection::get_computed_label(String const& element_id)
1209{
1210 // 1. If the current browsing context is no longer open, return error with error code no such window.
1211 TRY(ensure_open_top_level_browsing_context());
1212
1213 // 2. Handle any user prompts and return its value if it is an error.
1214 TRY(handle_any_user_prompts());
1215
1216 // 3. Let element be the result of trying to get a known element with url variable element id.
1217 auto* element = TRY(get_known_connected_element(element_id));
1218
1219 // 4. Let label be the result of a Accessible Name and Description Computation for the Accessible Name of the element.
1220 auto label = element->accessible_name(element->document()).release_value_but_fixme_should_propagate_errors();
1221
1222 // 5. Return success with data label.
1223 return label.to_deprecated_string();
1224}
1225
1226// 12.5.1 Element Click, https://w3c.github.io/webdriver/#element-click
1227Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_click(String const& element_id)
1228{
1229 // 1. If the current browsing context is no longer open, return error with error code no such window.
1230 TRY(ensure_open_top_level_browsing_context());
1231
1232 // 2. Handle any user prompts and return its value if it is an error.
1233 TRY(handle_any_user_prompts());
1234
1235 // 3. Let element be the result of trying to get a known element with element id.
1236 auto* element = TRY(get_known_connected_element(element_id));
1237
1238 // 4. If the element is an input element in the file upload state return error with error code invalid argument.
1239 if (is<Web::HTML::HTMLInputElement>(*element)) {
1240 // -> The result of element’s checkedness.
1241 auto& input = static_cast<Web::HTML::HTMLInputElement&>(*element);
1242 using enum Web::HTML::HTMLInputElement::TypeAttributeState;
1243
1244 if (input.type_state() == FileUpload)
1245 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Clicking on an input element in the file upload state is not supported"sv);
1246 }
1247
1248 // 5. Scroll into view the element’s container.
1249 auto element_container = container_for_element(*element);
1250 auto scroll_or_error = scroll_element_into_view(*element_container);
1251 if (scroll_or_error.is_error())
1252 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnknownError, scroll_or_error.error().string_literal());
1253
1254 // FIXME: 6. If element’s container is still not in view, return error with error code element not interactable.
1255
1256 // FIXME: 7. If element’s container is obscured by another element, return error with error code element click intercepted.
1257
1258 // 8. Matching on element:
1259 // -> option element
1260 if (is<Web::HTML::HTMLOptionElement>(*element)) {
1261 auto& option_element = static_cast<Web::HTML::HTMLOptionElement&>(*element);
1262
1263 // 1. Let parent node be the element’s container.
1264 auto parent_node = element_container;
1265
1266 // 2. Fire a mouseOver event at parent node.
1267 fire_an_event<Web::UIEvents::MouseEvent>("mouseOver", parent_node);
1268
1269 // 3. Fire a mouseMove event at parent node.
1270 fire_an_event<Web::UIEvents::MouseEvent>("mouseMove", parent_node);
1271
1272 // 4. Fire a mouseDown event at parent node.
1273 fire_an_event<Web::UIEvents::MouseEvent>("mouseDown", parent_node);
1274
1275 // 5. Run the focusing steps on parent node.
1276 Web::HTML::run_focusing_steps(parent_node.has_value() ? &*parent_node : nullptr);
1277
1278 // 6. If element is not disabled:
1279 if (!option_element.is_actually_disabled()) {
1280 // 1. Fire an input event at parent node.
1281 fire_an_event<Web::DOM::Event>("input", parent_node);
1282
1283 // 2. Let previous selectedness be equal to element selectedness.
1284 auto previous_selectedness = option_element.selected();
1285
1286 // 3. If element’s container has the multiple attribute, toggle the element’s selectedness state
1287 // by setting it to the opposite value of its current selectedness.
1288 if (parent_node.has_value() && parent_node->has_attribute("multiple")) {
1289 option_element.set_selected(!option_element.selected());
1290 }
1291 // Otherwise, set the element’s selectedness state to true.
1292 else {
1293 option_element.set_selected(true);
1294 }
1295
1296 // 4. If previous selectedness is false, fire a change event at parent node.
1297 if (!previous_selectedness) {
1298 fire_an_event<Web::DOM::Event>("change", parent_node);
1299 }
1300 }
1301 // 7. Fire a mouseUp event at parent node.
1302 fire_an_event<Web::UIEvents::MouseEvent>("mouseUp", parent_node);
1303
1304 // 8. Fire a click event at parent node.
1305 fire_an_event<Web::UIEvents::MouseEvent>("click", parent_node);
1306 }
1307 // -> Otherwise
1308 else {
1309 // FIXME: 1. Let input state be the result of get the input state given current session and current top-level browsing context.
1310
1311 // FIXME: 2. Let actions options be a new actions options with the is element origin steps set to represents a web element, and the get element origin steps set to get a WebElement origin.
1312
1313 // FIXME: 3. Let input id be a the result of generating a UUID.
1314
1315 // FIXME: 4. Let source be the result of create an input source with input state, and "pointer".
1316
1317 // FIXME: 5. Add an input source with input state, input id and source.
1318
1319 // FIXME: 6. Let click point be the element’s in-view center point.
1320
1321 // FIXME: 7. Let pointer move action be an action object constructed with arguments input id, "pointer", and "pointerMove".
1322
1323 // FIXME: 8. Set a property x to 0 on pointer move action.
1324
1325 // FIXME: 9. Set a property y to 0 on pointer move action.
1326
1327 // FIXME: 10. Set a property origin to element on pointer move action.
1328
1329 // FIXME: 11. Let pointer down action be an action object constructed with arguments input id, "pointer", and "pointerDown".
1330
1331 // FIXME: 12. Set a property button to 0 on pointer down action.
1332
1333 // FIXME: 13. Let pointer up action be an action object constructed with arguments input id, "mouse", and "pointerUp" as arguments.
1334
1335 // FIXME: 14. Set a property button to 0 on pointer up action.
1336
1337 // FIXME: 15. Let actions be the list «pointer move action, pointer down action, pointer move action».
1338
1339 // FIXME: 16. Dispatch a list of actions with input state, actions, current browsing context, and actions options.
1340
1341 // FIXME: 17. Remove an input source with input state and input id.
1342 }
1343
1344 // FIXME: 9. Wait until the user agent event loop has spun enough times to process the DOM events generated by the previous step.
1345 // FIXME: 10. Perform implementation-defined steps to allow any navigations triggered by the click to start.
1346 // FIXME: 11. Try to wait for navigation to complete.
1347 // FIXME: 12. Try to run the post-navigation checks.
1348 // FIXME: 13. Return success with data null.
1349
1350 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Click not implemented"sv);
1351}
1352
1353// 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source
1354Messages::WebDriverClient::GetSourceResponse WebDriverConnection::get_source()
1355{
1356 // 1. If the current browsing context is no longer open, return error with error code no such window.
1357 TRY(ensure_open_top_level_browsing_context());
1358
1359 // 2. Handle any user prompts and return its value if it is an error.
1360 TRY(handle_any_user_prompts());
1361
1362 auto* document = m_page_client.page().top_level_browsing_context().active_document();
1363 Optional<DeprecatedString> source;
1364
1365 // 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.
1366 if (auto result = document->serialize_fragment(Web::DOMParsing::RequireWellFormed::Yes); !result.is_error())
1367 source = result.release_value();
1368
1369 // 4. Let source be the result of serializing to string the current browsing context active document, if source is null.
1370 if (!source.has_value())
1371 source = MUST(document->serialize_fragment(Web::DOMParsing::RequireWellFormed::No));
1372
1373 // 5. Return success with data source.
1374 return source.release_value();
1375}
1376
1377// 13.2.1 Execute Script, https://w3c.github.io/webdriver/#dfn-execute-script
1378Messages::WebDriverClient::ExecuteScriptResponse WebDriverConnection::execute_script(JsonValue const& payload)
1379{
1380 // 1. Let body and arguments be the result of trying to extract the script arguments from a request with argument parameters.
1381 auto const& [body, arguments] = TRY(extract_the_script_arguments_from_a_request(payload));
1382
1383 // 2. If the current browsing context is no longer open, return error with error code no such window.
1384 TRY(ensure_open_top_level_browsing_context());
1385
1386 // 3. Handle any user prompts, and return its value if it is an error.
1387 TRY(handle_any_user_prompts());
1388
1389 // 4., 5.1-5.3.
1390 auto result = Web::WebDriver::execute_script(m_page_client.page(), body, move(arguments), m_timeouts_configuration.script_timeout);
1391 dbgln_if(WEBDRIVER_DEBUG, "Executing script returned: {}", result.value);
1392
1393 switch (result.type) {
1394 // 6. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
1395 case Web::WebDriver::ExecuteScriptResultType::Timeout:
1396 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
1397 // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
1398 case Web::WebDriver::ExecuteScriptResultType::PromiseResolved:
1399 return move(result.value);
1400 // 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.
1401 case Web::WebDriver::ExecuteScriptResultType::PromiseRejected:
1402 case Web::WebDriver::ExecuteScriptResultType::JavaScriptError:
1403 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
1404 }
1405
1406 VERIFY_NOT_REACHED();
1407}
1408
1409// 13.2.2 Execute Async Script, https://w3c.github.io/webdriver/#dfn-execute-async-script
1410Messages::WebDriverClient::ExecuteAsyncScriptResponse WebDriverConnection::execute_async_script(JsonValue const& payload)
1411{
1412 // 1. Let body and arguments by the result of trying to extract the script arguments from a request with argument parameters.
1413 auto const& [body, arguments] = TRY(extract_the_script_arguments_from_a_request(payload));
1414
1415 // 2. If the current browsing context is no longer open, return error with error code no such window.
1416 TRY(ensure_open_top_level_browsing_context());
1417
1418 // 3. Handle any user prompts, and return its value if it is an error.
1419 TRY(handle_any_user_prompts());
1420
1421 // 4., 5.1-5.11.
1422 auto result = Web::WebDriver::execute_async_script(m_page_client.page(), body, move(arguments), m_timeouts_configuration.script_timeout);
1423 dbgln_if(WEBDRIVER_DEBUG, "Executing async script returned: {}", result.value);
1424
1425 switch (result.type) {
1426 // 6. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
1427 case Web::WebDriver::ExecuteScriptResultType::Timeout:
1428 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
1429 // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
1430 case Web::WebDriver::ExecuteScriptResultType::PromiseResolved:
1431 return move(result.value);
1432 // 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.
1433 case Web::WebDriver::ExecuteScriptResultType::PromiseRejected:
1434 case Web::WebDriver::ExecuteScriptResultType::JavaScriptError:
1435 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
1436 }
1437
1438 VERIFY_NOT_REACHED();
1439}
1440
1441// 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies
1442Messages::WebDriverClient::GetAllCookiesResponse WebDriverConnection::get_all_cookies()
1443{
1444 // 1. If the current browsing context is no longer open, return error with error code no such window.
1445 TRY(ensure_open_top_level_browsing_context());
1446
1447 // 2. Handle any user prompts, and return its value if it is an error.
1448 TRY(handle_any_user_prompts());
1449
1450 // 3. Let cookies be a new JSON List.
1451 JsonArray cookies;
1452
1453 // 4. For each cookie in all associated cookies of the current browsing context’s active document:
1454 auto* document = m_page_client.page().top_level_browsing_context().active_document();
1455
1456 for (auto const& cookie : m_page_client.page_did_request_all_cookies(document->url())) {
1457 // 1. Let serialized cookie be the result of serializing cookie.
1458 auto serialized_cookie = serialize_cookie(cookie);
1459
1460 // 2. Append serialized cookie to cookies
1461 cookies.append(move(serialized_cookie));
1462 }
1463
1464 // 5. Return success with data cookies.
1465 return cookies;
1466}
1467
1468// 14.2 Get Named Cookie, https://w3c.github.io/webdriver/#dfn-get-named-cookie
1469Messages::WebDriverClient::GetNamedCookieResponse WebDriverConnection::get_named_cookie(String const& name)
1470{
1471 // 1. If the current browsing context is no longer open, return error with error code no such window.
1472 TRY(ensure_open_top_level_browsing_context());
1473
1474 // 2. Handle any user prompts, and return its value if it is an error.
1475 TRY(handle_any_user_prompts());
1476
1477 // 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.
1478 auto* document = m_page_client.page().top_level_browsing_context().active_document();
1479
1480 if (auto cookie = m_page_client.page_did_request_named_cookie(document->url(), name.to_deprecated_string()); cookie.has_value()) {
1481 auto serialized_cookie = serialize_cookie(*cookie);
1482 return serialized_cookie;
1483 }
1484
1485 // 4. Otherwise, return error with error code no such cookie.
1486 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchCookie, DeprecatedString::formatted("Cookie '{}' not found", name));
1487}
1488
1489// 14.3 Add Cookie, https://w3c.github.io/webdriver/#dfn-adding-a-cookie
1490Messages::WebDriverClient::AddCookieResponse WebDriverConnection::add_cookie(JsonValue const& payload)
1491{
1492 // 1. Let data be the result of getting a property named cookie from the parameters argument.
1493 auto const& data = *TRY(get_property<JsonObject const*>(payload, "cookie"sv));
1494
1495 // 2. If data is not a JSON Object with all the required (non-optional) JSON keys listed in the table for cookie conversion, return error with error code invalid argument.
1496 // NOTE: This validation is performed in subsequent steps.
1497
1498 // 3. If the current browsing context is no longer open, return error with error code no such window.
1499 TRY(ensure_open_top_level_browsing_context());
1500
1501 // 4. Handle any user prompts, and return its value if it is an error.
1502 TRY(handle_any_user_prompts());
1503
1504 // FIXME: 5. If the current browsing context’s document element is a cookie-averse Document object, return error with error code invalid cookie domain.
1505
1506 // 6. If cookie name or cookie value is null, cookie domain is not equal to the current browsing context’s active document’s domain, cookie secure only or cookie HTTP only are not boolean types, or cookie expiry time is not an integer type, or it less than 0 or greater than the maximum safe integer, return error with error code invalid argument.
1507 // NOTE: This validation is either performed in subsequent steps, or is performed by the CookieJar (namely domain matching).
1508
1509 // 7. Create a cookie in the cookie store associated with the active document’s address using cookie name name, cookie value value, and an attribute-value list of the following cookie concepts listed in the table for cookie conversion from data:
1510 Web::Cookie::ParsedCookie cookie {};
1511 cookie.name = TRY(get_property(data, "name"sv));
1512 cookie.value = TRY(get_property(data, "value"sv));
1513
1514 // Cookie path
1515 // The value if the entry exists, otherwise "/".
1516 if (data.has("path"sv))
1517 cookie.path = TRY(get_property(data, "path"sv));
1518 else
1519 cookie.path = "/";
1520
1521 // Cookie domain
1522 // The value if the entry exists, otherwise the current browsing context’s active document’s URL domain.
1523 // NOTE: The otherwise case is handled by the CookieJar
1524 if (data.has("domain"sv))
1525 cookie.domain = TRY(get_property(data, "domain"sv));
1526
1527 // Cookie secure only
1528 // The value if the entry exists, otherwise false.
1529 if (data.has("secure"sv))
1530 cookie.secure_attribute_present = TRY(get_property<bool>(data, "secure"sv));
1531
1532 // Cookie HTTP only
1533 // The value if the entry exists, otherwise false.
1534 if (data.has("httpOnly"sv))
1535 cookie.http_only_attribute_present = TRY(get_property<bool>(data, "httpOnly"sv));
1536
1537 // Cookie expiry time
1538 // The value if the entry exists, otherwise leave unset to indicate that this is a session cookie.
1539 if (data.has("expiry"sv)) {
1540 // NOTE: less than 0 or greater than safe integer are handled by the JSON parser
1541 auto expiry = TRY(get_property<u32>(data, "expiry"sv));
1542 cookie.expiry_time_from_expires_attribute = Time::from_seconds(expiry);
1543 }
1544
1545 // Cookie same site
1546 // The value if the entry exists, otherwise leave unset to indicate that no same site policy is defined.
1547 if (data.has("sameSite"sv)) {
1548 auto same_site = TRY(get_property(data, "sameSite"sv));
1549 cookie.same_site_attribute = Web::Cookie::same_site_from_string(same_site);
1550 }
1551
1552 auto* document = m_page_client.page().top_level_browsing_context().active_document();
1553 m_page_client.page_did_set_cookie(document->url(), cookie, Web::Cookie::Source::Http);
1554
1555 // If there is an error during this step, return error with error code unable to set cookie.
1556 // NOTE: This probably should only apply to the actual setting of the cookie in the Browser, which cannot fail in our case.
1557
1558 // 8. Return success with data null.
1559 return JsonValue {};
1560}
1561
1562// 14.4 Delete Cookie, https://w3c.github.io/webdriver/#dfn-delete-cookie
1563Messages::WebDriverClient::DeleteCookieResponse WebDriverConnection::delete_cookie(String const& name)
1564{
1565 // 1. If the current browsing context is no longer open, return error with error code no such window.
1566 TRY(ensure_open_top_level_browsing_context());
1567
1568 // 2. Handle any user prompts, and return its value if it is an error.
1569 TRY(handle_any_user_prompts());
1570
1571 // 3. Delete cookies using the url variable name parameter as the filter argument.
1572 delete_cookies(name);
1573
1574 // 4. Return success with data null.
1575 return JsonValue {};
1576}
1577
1578// 14.5 Delete All Cookies, https://w3c.github.io/webdriver/#dfn-delete-all-cookies
1579Messages::WebDriverClient::DeleteAllCookiesResponse WebDriverConnection::delete_all_cookies()
1580{
1581 // 1. If the current browsing context is no longer open, return error with error code no such window.
1582 TRY(ensure_open_top_level_browsing_context());
1583
1584 // 2. Handle any user prompts, and return its value if it is an error.
1585 TRY(handle_any_user_prompts());
1586
1587 // 3. Delete cookies, giving no filtering argument.
1588 delete_cookies();
1589
1590 // 4. Return success with data null.
1591 return JsonValue {};
1592}
1593
1594// 16.1 Dismiss Alert, https://w3c.github.io/webdriver/#dismiss-alert
1595Messages::WebDriverClient::DismissAlertResponse WebDriverConnection::dismiss_alert()
1596{
1597 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
1598 TRY(ensure_open_top_level_browsing_context());
1599
1600 // 2. If there is no current user prompt, return error with error code no such alert.
1601 if (!m_page_client.page().has_pending_dialog())
1602 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
1603
1604 // 3. Dismiss the current user prompt.
1605 m_page_client.page().dismiss_dialog();
1606
1607 // 4. Return success with data null.
1608 return JsonValue {};
1609}
1610
1611// 16.2 Accept Alert, https://w3c.github.io/webdriver/#accept-alert
1612Messages::WebDriverClient::AcceptAlertResponse WebDriverConnection::accept_alert()
1613{
1614 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
1615 TRY(ensure_open_top_level_browsing_context());
1616
1617 // 2. If there is no current user prompt, return error with error code no such alert.
1618 if (!m_page_client.page().has_pending_dialog())
1619 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
1620
1621 // 3. Accept the current user prompt.
1622 m_page_client.page().accept_dialog();
1623
1624 // 4. Return success with data null.
1625 return JsonValue {};
1626}
1627
1628// 16.3 Get Alert Text, https://w3c.github.io/webdriver/#get-alert-text
1629Messages::WebDriverClient::GetAlertTextResponse WebDriverConnection::get_alert_text()
1630{
1631 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
1632 TRY(ensure_open_top_level_browsing_context());
1633
1634 // 2. If there is no current user prompt, return error with error code no such alert.
1635 if (!m_page_client.page().has_pending_dialog())
1636 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
1637
1638 // 3. Let message be the text message associated with the current user prompt, or otherwise be null.
1639 auto const& message = m_page_client.page().pending_dialog_text();
1640
1641 // 4. Return success with data message.
1642 if (message.has_value())
1643 return *message;
1644 return JsonValue {};
1645}
1646
1647// 16.4 Send Alert Text, https://w3c.github.io/webdriver/#send-alert-text
1648Messages::WebDriverClient::SendAlertTextResponse WebDriverConnection::send_alert_text(JsonValue const& payload)
1649{
1650 // 1. Let text be the result of getting the property "text" from parameters.
1651 // 2. If text is not a String, return error with error code invalid argument.
1652 auto text = TRY(get_property(payload, "text"sv));
1653
1654 // 3. If the current top-level browsing context is no longer open, return error with error code no such window.
1655 TRY(ensure_open_top_level_browsing_context());
1656
1657 // 4. If there is no current user prompt, return error with error code no such alert.
1658 if (!m_page_client.page().has_pending_dialog())
1659 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
1660
1661 // 5. Run the substeps of the first matching current user prompt:
1662 switch (m_page_client.page().pending_dialog()) {
1663 // -> alert
1664 // -> confirm
1665 case Web::Page::PendingDialog::Alert:
1666 case Web::Page::PendingDialog::Confirm:
1667 // Return error with error code element not interactable.
1668 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Only prompt dialogs may receive text"sv);
1669
1670 // -> prompt
1671 case Web::Page::PendingDialog::Prompt:
1672 // Do nothing.
1673 break;
1674
1675 // -> Otherwise
1676 default:
1677 // Return error with error code unsupported operation.
1678 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Unknown dialog type"sv);
1679 }
1680
1681 // 6. Perform user agent dependent steps to set the value of current user prompt’s text field to text.
1682 m_page_client.page_did_request_set_prompt_text(move(text));
1683
1684 // 7. Return success with data null.
1685 return JsonValue {};
1686}
1687
1688// 17.1 Take Screenshot, https://w3c.github.io/webdriver/#take-screenshot
1689Messages::WebDriverClient::TakeScreenshotResponse WebDriverConnection::take_screenshot()
1690{
1691 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
1692 TRY(ensure_open_top_level_browsing_context());
1693
1694 // 2. When the user agent is next to run the animation frame callbacks:
1695 // a. Let root rect be the current top-level browsing context’s document element’s rectangle.
1696 // b. Let screenshot result be the result of trying to call draw a bounding box from the framebuffer, given root rect as an argument.
1697 // c. Let canvas be a canvas element of screenshot result’s data.
1698 // d. Let encoding result be the result of trying encoding a canvas as Base64 canvas.
1699 // e. Let encoded string be encoding result’s data.
1700 auto* document = m_page_client.page().top_level_browsing_context().active_document();
1701 auto root_rect = calculate_absolute_rect_of_element(m_page_client.page(), *document->document_element());
1702
1703 auto encoded_string = TRY(Web::WebDriver::capture_element_screenshot(
1704 [&](auto const& rect, auto& bitmap) { m_page_client.paint(rect.template to_type<Web::DevicePixels>(), bitmap); },
1705 m_page_client.page(),
1706 *document->document_element(),
1707 root_rect));
1708
1709 // 3. Return success with data encoded string.
1710 return encoded_string;
1711}
1712
1713// 17.2 Take Element Screenshot, https://w3c.github.io/webdriver/#dfn-take-element-screenshot
1714Messages::WebDriverClient::TakeElementScreenshotResponse WebDriverConnection::take_element_screenshot(String const& element_id)
1715{
1716 // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
1717 TRY(ensure_open_top_level_browsing_context());
1718
1719 // 2. Handle any user prompts and return its value if it is an error.
1720 TRY(handle_any_user_prompts());
1721
1722 // 3. Let element be the result of trying to get a known connected element with url variable element id.
1723 auto* element = TRY(get_known_connected_element(element_id));
1724
1725 // 4. Scroll into view the element.
1726 (void)scroll_element_into_view(*element);
1727
1728 // 5. When the user agent is next to run the animation frame callbacks:
1729 // a. Let element rect be element’s rectangle.
1730 // b. Let screenshot result be the result of trying to call draw a bounding box from the framebuffer, given element rect as an argument.
1731 // c. Let canvas be a canvas element of screenshot result’s data.
1732 // d. Let encoding result be the result of trying encoding a canvas as Base64 canvas.
1733 // e. Let encoded string be encoding result’s data.
1734 auto element_rect = calculate_absolute_rect_of_element(m_page_client.page(), *element);
1735
1736 auto encoded_string = TRY(Web::WebDriver::capture_element_screenshot(
1737 [&](auto const& rect, auto& bitmap) { m_page_client.paint(rect.template to_type<Web::DevicePixels>(), bitmap); },
1738 m_page_client.page(),
1739 *element,
1740 element_rect));
1741
1742 // 6. Return success with data encoded string.
1743 return encoded_string;
1744}
1745
1746// 18.1 Print Page, https://w3c.github.io/webdriver/#dfn-print-page
1747Messages::WebDriverClient::PrintPageResponse WebDriverConnection::print_page()
1748{
1749 // FIXME: Actually implement this :^)
1750 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Print not implemented"sv);
1751}
1752
1753// https://w3c.github.io/webdriver/#dfn-no-longer-open
1754Messages::WebDriverClient::EnsureTopLevelBrowsingContextIsOpenResponse WebDriverConnection::ensure_top_level_browsing_context_is_open()
1755{
1756 // A browsing context is said to be no longer open if it has been discarded.
1757 if (m_page_client.page().top_level_browsing_context().has_been_discarded())
1758 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found"sv);
1759 return JsonValue {};
1760}
1761
1762// https://w3c.github.io/webdriver/#dfn-no-longer-open
1763ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::ensure_open_top_level_browsing_context()
1764{
1765 TRY(ensure_top_level_browsing_context_is_open().take_response());
1766 return {};
1767}
1768
1769// https://w3c.github.io/webdriver/#dfn-handle-any-user-prompts
1770ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::handle_any_user_prompts()
1771{
1772 // 1. If there is no current user prompt, abort these steps and return success.
1773 if (!m_page_client.page().has_pending_dialog())
1774 return {};
1775
1776 // 2. Perform the following substeps based on the current session’s user prompt handler:
1777 switch (m_unhandled_prompt_behavior) {
1778 // -> dismiss state
1779 case Web::WebDriver::UnhandledPromptBehavior::Dismiss:
1780 // Dismiss the current user prompt.
1781 m_page_client.page().dismiss_dialog();
1782 break;
1783
1784 // -> accept state
1785 case Web::WebDriver::UnhandledPromptBehavior::Accept:
1786 // Accept the current user prompt.
1787 m_page_client.page().accept_dialog();
1788 break;
1789
1790 // -> dismiss and notify state
1791 case Web::WebDriver::UnhandledPromptBehavior::DismissAndNotify:
1792 // Dismiss the current user prompt.
1793 m_page_client.page().dismiss_dialog();
1794
1795 // Return an annotated unexpected alert open error.
1796 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
1797
1798 // -> accept and notify state
1799 case Web::WebDriver::UnhandledPromptBehavior::AcceptAndNotify:
1800 // Accept the current user prompt.
1801 m_page_client.page().accept_dialog();
1802
1803 // Return an annotated unexpected alert open error.
1804 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
1805
1806 // -> ignore state
1807 case Web::WebDriver::UnhandledPromptBehavior::Ignore:
1808 // Return an annotated unexpected alert open error.
1809 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
1810 }
1811
1812 // 3. Return success.
1813 return {};
1814}
1815
1816// https://w3c.github.io/webdriver/#dfn-waiting-for-the-navigation-to-complete
1817ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::wait_for_navigation_to_complete()
1818{
1819 // 1. If the current session has a page loading strategy of none, return success with data null.
1820 if (m_page_load_strategy == Web::WebDriver::PageLoadStrategy::None)
1821 return {};
1822
1823 // 2. If the current browsing context is no longer open, return success with data null.
1824 if (m_page_client.page().top_level_browsing_context().has_been_discarded())
1825 return {};
1826
1827 // FIXME: 3. 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.
1828 // FIXME: 4. If there is an ongoing attempt to navigate the current browsing context that has not yet matured, wait for navigation to mature.
1829
1830 // 5. Let readiness target be the document readiness state associated with the current session’s page loading strategy, which can be found in the table of page load strategies.
1831 auto readiness_target = [this]() {
1832 switch (m_page_load_strategy) {
1833 case Web::WebDriver::PageLoadStrategy::Normal:
1834 return Web::HTML::DocumentReadyState::Complete;
1835 case Web::WebDriver::PageLoadStrategy::Eager:
1836 return Web::HTML::DocumentReadyState::Interactive;
1837 default:
1838 VERIFY_NOT_REACHED();
1839 };
1840 }();
1841
1842 // 6. Wait for the current browsing context’s document readiness state to reach readiness target,
1843 // FIXME: or for the session page load timeout to pass, whichever occurs sooner.
1844 Web::Platform::EventLoopPlugin::the().spin_until([&]() {
1845 return m_page_client.page().top_level_browsing_context().active_document()->readiness() == readiness_target;
1846 });
1847
1848 // FIXME: 7. If the previous step completed by the session page load timeout being reached and the browser does not have an active user prompt, return error with error code timeout.
1849
1850 // 8. Return success with data null.
1851 return {};
1852}
1853
1854// https://w3c.github.io/webdriver/#dfn-restore-the-window
1855void WebDriverConnection::restore_the_window()
1856{
1857 // 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.
1858 m_page_client.page_did_request_restore_window();
1859
1860 // 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.
1861 // FIXME: Implement timeouts.
1862 Web::Platform::EventLoopPlugin::the().spin_until([this]() {
1863 auto state = m_page_client.page().top_level_browsing_context().system_visibility_state();
1864 return state == Web::HTML::VisibilityState::Visible;
1865 });
1866}
1867
1868// https://w3c.github.io/webdriver/#dfn-maximize-the-window
1869Gfx::IntRect WebDriverConnection::maximize_the_window()
1870{
1871 // 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.
1872 auto rect = m_page_client.page_did_request_maximize_window();
1873
1874 // Return when the window has completed the transition, or within an implementation-defined timeout.
1875 return rect;
1876}
1877
1878// https://w3c.github.io/webdriver/#dfn-iconify-the-window
1879Gfx::IntRect WebDriverConnection::iconify_the_window()
1880{
1881 // 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.
1882 auto rect = m_page_client.page_did_request_minimize_window();
1883
1884 // 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.
1885 // FIXME: Implement timeouts.
1886 Web::Platform::EventLoopPlugin::the().spin_until([this]() {
1887 auto state = m_page_client.page().top_level_browsing_context().system_visibility_state();
1888 return state == Web::HTML::VisibilityState::Hidden;
1889 });
1890
1891 return rect;
1892}
1893
1894// https://w3c.github.io/webdriver/#dfn-find
1895ErrorOr<JsonArray, Web::WebDriver::Error> WebDriverConnection::find(StartNodeGetter&& start_node_getter, Web::WebDriver::LocationStrategy using_, StringView value)
1896{
1897 // 1. Let end time be the current time plus the session implicit wait timeout.
1898 auto end_time = Time::now_monotonic() + Time::from_milliseconds(static_cast<i64>(m_timeouts_configuration.implicit_wait_timeout));
1899
1900 // 2. Let location strategy be equal to using.
1901 auto location_strategy = using_;
1902
1903 // 3. Let selector be equal to value.
1904 auto selector = value;
1905
1906 ErrorOr<JS::GCPtr<Web::DOM::NodeList>, Web::WebDriver::Error> maybe_elements { nullptr };
1907
1908 auto try_to_find_element = [&]() -> decltype(maybe_elements) {
1909 // 4. Let elements returned be the result of trying to call the relevant element location strategy with arguments start node, and selector.
1910 auto elements = Web::WebDriver::invoke_location_strategy(location_strategy, *TRY(start_node_getter()), selector);
1911
1912 // 5. If a DOMException, SyntaxError, XPathException, or other error occurs during the execution of the element location strategy, return error invalid selector.
1913 if (elements.is_error())
1914 return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSelector, DeprecatedString::formatted("The location strategy could not finish: {}", elements.error().message));
1915
1916 return elements.release_value();
1917 };
1918
1919 Web::Platform::EventLoopPlugin::the().spin_until([&]() {
1920 maybe_elements = try_to_find_element();
1921 if (maybe_elements.is_error())
1922 return true;
1923
1924 // 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.
1925 return maybe_elements.value()->length() != 0 || Time::now_monotonic() >= end_time;
1926 });
1927
1928 auto elements = TRY(maybe_elements);
1929 VERIFY(elements);
1930
1931 // 7. Let result be an empty JSON List.
1932 JsonArray result;
1933 result.ensure_capacity(elements->length());
1934
1935 // 8. For each element in elements returned, append the web element reference object for element, to result.
1936 for (size_t i = 0; i < elements->length(); ++i)
1937 result.append(web_element_reference_object(*elements->item(i)));
1938
1939 // 9. Return success with data result.
1940 return result;
1941}
1942
1943// https://w3c.github.io/webdriver/#dfn-extract-the-script-arguments-from-a-request
1944ErrorOr<WebDriverConnection::ScriptArguments, Web::WebDriver::Error> WebDriverConnection::extract_the_script_arguments_from_a_request(JsonValue const& payload)
1945{
1946 auto* window = m_page_client.page().top_level_browsing_context().active_window();
1947 auto& vm = window->vm();
1948
1949 // 1. Let script be the result of getting a property named script from the parameters.
1950 // 2. If script is not a String, return error with error code invalid argument.
1951 auto script = TRY(get_property(payload, "script"sv));
1952
1953 // 3. Let args be the result of getting a property named args from the parameters.
1954 // 4. If args is not an Array return error with error code invalid argument.
1955 auto const& args = *TRY(get_property<JsonArray const*>(payload, "args"sv));
1956
1957 // 5. Let arguments be the result of calling the JSON deserialize algorithm with arguments args.
1958 auto arguments = JS::MarkedVector<JS::Value> { vm.heap() };
1959
1960 args.for_each([&](auto const& arg) {
1961 arguments.append(JS::JSONObject::parse_json_value(vm, arg));
1962 });
1963
1964 // 6. Return success with data script and arguments.
1965 return ScriptArguments { move(script), move(arguments) };
1966}
1967
1968// https://w3c.github.io/webdriver/#dfn-delete-cookies
1969void WebDriverConnection::delete_cookies(Optional<StringView> const& name)
1970{
1971 // For each cookie among all associated cookies of the current browsing context’s active document, un the substeps of the first matching condition:
1972 auto* document = m_page_client.page().top_level_browsing_context().active_document();
1973
1974 for (auto& cookie : m_page_client.page_did_request_all_cookies(document->url())) {
1975 // -> name is undefined
1976 // -> name is equal to cookie name
1977 if (!name.has_value() || name.value() == cookie.name) {
1978 // Set the cookie expiry time to a Unix timestamp in the past.
1979 cookie.expiry_time = Time::from_seconds(0);
1980 m_page_client.page_did_update_cookie(move(cookie));
1981 }
1982 // -> Otherwise
1983 // Do nothing.
1984 }
1985}
1986
1987}