WebDriverConnection.cpp 85 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762
  1. /*
  2. * Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
  3. * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
  4. * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
  5. * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
  6. * Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include <AK/JsonObject.h>
  11. #include <AK/JsonValue.h>
  12. #include <AK/Vector.h>
  13. #include <LibJS/Runtime/JSONObject.h>
  14. #include <LibJS/Runtime/Value.h>
  15. #include <LibWeb/CSS/PropertyID.h>
  16. #include <LibWeb/CSS/StyleProperties.h>
  17. #include <LibWeb/CSS/StyleValue.h>
  18. #include <LibWeb/Cookie/Cookie.h>
  19. #include <LibWeb/Cookie/ParsedCookie.h>
  20. #include <LibWeb/DOM/Document.h>
  21. #include <LibWeb/DOM/Element.h>
  22. #include <LibWeb/DOM/ShadowRoot.h>
  23. #include <LibWeb/Geometry/DOMRect.h>
  24. #include <LibWeb/HTML/AttributeNames.h>
  25. #include <LibWeb/HTML/BrowsingContext.h>
  26. #include <LibWeb/HTML/FormAssociatedElement.h>
  27. #include <LibWeb/HTML/HTMLInputElement.h>
  28. #include <LibWeb/HTML/HTMLOptionElement.h>
  29. #include <LibWeb/Page/Page.h>
  30. #include <LibWeb/Platform/EventLoopPlugin.h>
  31. #include <LibWeb/Platform/Timer.h>
  32. #include <LibWeb/WebDriver/ExecuteScript.h>
  33. #include <LibWeb/WebDriver/Screenshot.h>
  34. #include <WebContent/WebDriverConnection.h>
  35. namespace WebContent {
  36. // https://w3c.github.io/webdriver/#dfn-serialized-cookie
  37. static JsonValue serialize_cookie(Web::Cookie::Cookie const& cookie)
  38. {
  39. JsonObject serialized_cookie;
  40. serialized_cookie.set("name"sv, cookie.name);
  41. serialized_cookie.set("value"sv, cookie.value);
  42. serialized_cookie.set("path"sv, cookie.path);
  43. serialized_cookie.set("domain"sv, cookie.domain);
  44. serialized_cookie.set("secure"sv, cookie.secure);
  45. serialized_cookie.set("httpOnly"sv, cookie.http_only);
  46. serialized_cookie.set("expiry"sv, cookie.expiry_time.timestamp());
  47. serialized_cookie.set("sameSite"sv, Web::Cookie::same_site_to_string(cookie.same_site));
  48. return serialized_cookie;
  49. }
  50. static JsonValue serialize_rect(Gfx::IntRect const& rect)
  51. {
  52. JsonObject serialized_rect = {};
  53. serialized_rect.set("x", rect.x());
  54. serialized_rect.set("y", rect.y());
  55. serialized_rect.set("width", rect.width());
  56. serialized_rect.set("height", rect.height());
  57. return serialized_rect;
  58. }
  59. static Gfx::IntRect compute_window_rect(Web::Page const& page)
  60. {
  61. return {
  62. page.window_position().x(),
  63. page.window_position().y(),
  64. page.window_size().width(),
  65. page.window_size().height()
  66. };
  67. }
  68. // https://w3c.github.io/webdriver/#dfn-calculate-the-absolute-position
  69. static Gfx::IntPoint calculate_absolute_position_of_element(Web::Page const& page, JS::NonnullGCPtr<Web::Geometry::DOMRect> rect)
  70. {
  71. // 1. Let rect be the value returned by calling getBoundingClientRect().
  72. // 2. Let window be the associated window of current top-level browsing context.
  73. auto const* window = page.top_level_browsing_context().active_window();
  74. // 3. Let x be (scrollX of window + rect’s x coordinate).
  75. auto x = (window ? static_cast<int>(window->scroll_x()) : 0) + static_cast<int>(rect->x());
  76. // 4. Let y be (scrollY of window + rect’s y coordinate).
  77. auto y = (window ? static_cast<int>(window->scroll_y()) : 0) + static_cast<int>(rect->y());
  78. // 5. Return a pair of (x, y).
  79. return { x, y };
  80. }
  81. static Gfx::IntRect calculate_absolute_rect_of_element(Web::Page const& page, Web::DOM::Element const& element)
  82. {
  83. auto bounding_rect = element.get_bounding_client_rect();
  84. auto coordinates = calculate_absolute_position_of_element(page, bounding_rect);
  85. return {
  86. coordinates.x(),
  87. coordinates.y(),
  88. static_cast<int>(bounding_rect->width()),
  89. static_cast<int>(bounding_rect->height())
  90. };
  91. }
  92. // https://w3c.github.io/webdriver/#dfn-get-or-create-a-web-element-reference
  93. static DeprecatedString get_or_create_a_web_element_reference(Web::DOM::Node const& element)
  94. {
  95. // FIXME: 1. For each known element of the current browsing context’s list of known elements:
  96. // FIXME: 1. If known element equals element, return success with known element’s web element reference.
  97. // FIXME: 2. Add element to the list of known elements of the current browsing context.
  98. // FIXME: 3. Return success with the element’s web element reference.
  99. return DeprecatedString::number(element.id());
  100. }
  101. // https://w3c.github.io/webdriver/#dfn-web-element-reference-object
  102. static JsonObject web_element_reference_object(Web::DOM::Node const& element)
  103. {
  104. // https://w3c.github.io/webdriver/#dfn-web-element-identifier
  105. static DeprecatedString const web_element_identifier = "element-6066-11e4-a52e-4f735466cecf"sv;
  106. // 1. Let identifier be the web element identifier.
  107. auto identifier = web_element_identifier;
  108. // 2. Let reference be the result of get or create a web element reference given element.
  109. auto reference = get_or_create_a_web_element_reference(element);
  110. // 3. Return a JSON Object initialized with a property with name identifier and value reference.
  111. JsonObject object;
  112. object.set("name"sv, identifier);
  113. object.set("value"sv, reference);
  114. return object;
  115. }
  116. // https://w3c.github.io/webdriver/#dfn-get-a-known-connected-element
  117. static ErrorOr<Web::DOM::Element*, Web::WebDriver::Error> get_known_connected_element(StringView element_id)
  118. {
  119. // NOTE: The whole concept of "connected elements" is not implemented yet. See get_or_create_a_web_element_reference().
  120. // For now the element is only represented by its ID.
  121. auto element = element_id.to_int();
  122. if (!element.has_value())
  123. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Element ID is not an integer");
  124. auto* node = Web::DOM::Node::from_id(*element);
  125. if (!node || !node->is_element())
  126. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, DeprecatedString::formatted("Could not find element with ID: {}", element_id));
  127. return static_cast<Web::DOM::Element*>(node);
  128. }
  129. // https://w3c.github.io/webdriver/#dfn-get-or-create-a-shadow-root-reference
  130. static DeprecatedString get_or_create_a_shadow_root_reference(Web::DOM::ShadowRoot const& shadow_root)
  131. {
  132. // FIXME: 1. For each known shadow root of the current browsing context’s list of known shadow roots:
  133. // FIXME: 1. If known shadow root equals shadow root, return success with known shadow root’s shadow root reference.
  134. // FIXME: 2. Add shadow to the list of known shadow roots of the current browsing context.
  135. // FIXME: 3. Return success with the shadow’s shadow root reference.
  136. return DeprecatedString::number(shadow_root.id());
  137. }
  138. // https://w3c.github.io/webdriver/#dfn-shadow-root-reference-object
  139. static JsonObject shadow_root_reference_object(Web::DOM::ShadowRoot const& shadow_root)
  140. {
  141. // https://w3c.github.io/webdriver/#dfn-shadow-root-identifier
  142. static DeprecatedString const shadow_root_identifier = "shadow-6066-11e4-a52e-4f735466cecf"sv;
  143. // 1. Let identifier be the shadow root identifier.
  144. auto identifier = shadow_root_identifier;
  145. // 2. Let reference be the result of get or create a shadow root reference given shadow root.
  146. auto reference = get_or_create_a_shadow_root_reference(shadow_root);
  147. // 3. Return a JSON Object initialized with a property with name identifier and value reference.
  148. JsonObject object;
  149. object.set("name"sv, move(identifier));
  150. object.set("value"sv, move(reference));
  151. return object;
  152. }
  153. // https://w3c.github.io/webdriver/#dfn-get-a-known-shadow-root
  154. static ErrorOr<Web::DOM::ShadowRoot*, Web::WebDriver::Error> get_known_shadow_root(StringView shadow_id)
  155. {
  156. // NOTE: The whole concept of "known shadow roots" is not implemented yet. See get_or_create_a_shadow_root_reference().
  157. // For now the shadow root is only represented by its ID.
  158. auto shadow_root = shadow_id.to_int();
  159. if (!shadow_root.has_value())
  160. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Shadow ID is not an integer");
  161. auto* node = Web::DOM::Node::from_id(*shadow_root);
  162. if (!node || !node->is_shadow_root())
  163. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, DeprecatedString::formatted("Could not find shadow root with ID: {}", shadow_id));
  164. return static_cast<Web::DOM::ShadowRoot*>(node);
  165. }
  166. // https://w3c.github.io/webdriver/#dfn-scrolls-into-view
  167. static void scroll_element_into_view(Web::DOM::Element& element)
  168. {
  169. // 1. Let options be the following ScrollIntoViewOptions:
  170. Web::DOM::ScrollIntoViewOptions options {};
  171. // Logical scroll position "block"
  172. // "end"
  173. options.block = Web::Bindings::ScrollLogicalPosition::End;
  174. // Logical scroll position "inline"
  175. // "nearest"
  176. options.inline_ = Web::Bindings::ScrollLogicalPosition::Nearest;
  177. // 2. Run Function.[[Call]](scrollIntoView, options) with element as the this value.
  178. element.scroll_into_view(options);
  179. }
  180. template<typename PropertyType = DeprecatedString>
  181. static ErrorOr<PropertyType, Web::WebDriver::Error> get_property(JsonValue const& payload, StringView key)
  182. {
  183. if (!payload.is_object())
  184. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
  185. auto const* property = payload.as_object().get_ptr(key);
  186. if (!property)
  187. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("No property called '{}' present", key));
  188. if constexpr (IsSame<PropertyType, DeprecatedString>) {
  189. if (!property->is_string())
  190. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not a String", key));
  191. return property->as_string();
  192. } else if constexpr (IsSame<PropertyType, bool>) {
  193. if (!property->is_bool())
  194. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not a Boolean", key));
  195. return property->as_bool();
  196. } else if constexpr (IsSame<PropertyType, u32>) {
  197. if (!property->is_u32())
  198. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not a Number", key));
  199. return property->as_u32();
  200. } else if constexpr (IsSame<PropertyType, JsonArray const*>) {
  201. if (!property->is_array())
  202. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not an Array", key));
  203. return &property->as_array();
  204. } else if constexpr (IsSame<PropertyType, JsonObject const*>) {
  205. if (!property->is_object())
  206. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not an Object", key));
  207. return &property->as_object();
  208. } else {
  209. static_assert(DependentFalse<PropertyType>, "get_property invoked with unknown property type");
  210. VERIFY_NOT_REACHED();
  211. }
  212. }
  213. ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(Web::PageClient& page_client, DeprecatedString const& webdriver_ipc_path)
  214. {
  215. dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path);
  216. auto socket = TRY(Core::Stream::LocalSocket::connect(webdriver_ipc_path));
  217. dbgln_if(WEBDRIVER_DEBUG, "Connected to WebDriver");
  218. return adopt_nonnull_ref_or_enomem(new (nothrow) WebDriverConnection(move(socket), page_client));
  219. }
  220. WebDriverConnection::WebDriverConnection(NonnullOwnPtr<Core::Stream::LocalSocket> socket, Web::PageClient& page_client)
  221. : IPC::ConnectionToServer<WebDriverClientEndpoint, WebDriverServerEndpoint>(*this, move(socket))
  222. , m_page_client(page_client)
  223. , m_current_window_handle("main"sv)
  224. {
  225. m_windows.set(m_current_window_handle, { m_current_window_handle, true });
  226. }
  227. // https://w3c.github.io/webdriver/#dfn-close-the-session
  228. void WebDriverConnection::close_session()
  229. {
  230. // 1. Set the webdriver-active flag to false.
  231. set_is_webdriver_active(false);
  232. // 2. An endpoint node must close any top-level browsing contexts associated with the session, without prompting to unload.
  233. if (!m_page_client.page().top_level_browsing_context().has_been_discarded())
  234. m_page_client.page().top_level_browsing_context().close();
  235. }
  236. void WebDriverConnection::set_page_load_strategy(Web::WebDriver::PageLoadStrategy const& page_load_strategy)
  237. {
  238. m_page_load_strategy = page_load_strategy;
  239. }
  240. void WebDriverConnection::set_unhandled_prompt_behavior(Web::WebDriver::UnhandledPromptBehavior const& unhandled_prompt_behavior)
  241. {
  242. m_unhandled_prompt_behavior = unhandled_prompt_behavior;
  243. }
  244. void WebDriverConnection::set_strict_file_interactability(bool strict_file_interactability)
  245. {
  246. m_strict_file_interactability = strict_file_interactability;
  247. }
  248. void WebDriverConnection::set_is_webdriver_active(bool is_webdriver_active)
  249. {
  250. m_page_client.page().set_is_webdriver_active(is_webdriver_active);
  251. }
  252. // 9.1 Get Timeouts, https://w3c.github.io/webdriver/#dfn-get-timeouts
  253. Messages::WebDriverClient::GetTimeoutsResponse WebDriverConnection::get_timeouts()
  254. {
  255. // 1. Let timeouts be the timeouts object for session’s timeouts configuration
  256. auto timeouts = Web::WebDriver::timeouts_object(m_timeouts_configuration);
  257. // 2. Return success with data timeouts.
  258. return timeouts;
  259. }
  260. // 9.2 Set Timeouts, https://w3c.github.io/webdriver/#dfn-set-timeouts
  261. Messages::WebDriverClient::SetTimeoutsResponse WebDriverConnection::set_timeouts(JsonValue const& payload)
  262. {
  263. // 1. Let timeouts be the result of trying to JSON deserialize as a timeouts configuration the request’s parameters.
  264. auto timeouts = TRY(Web::WebDriver::json_deserialize_as_a_timeouts_configuration(payload));
  265. // 2. Make the session timeouts the new timeouts.
  266. m_timeouts_configuration = move(timeouts);
  267. // 3. Return success with data null.
  268. return JsonValue {};
  269. }
  270. // 10.1 Navigate To, https://w3c.github.io/webdriver/#navigate-to
  271. Messages::WebDriverClient::NavigateToResponse WebDriverConnection::navigate_to(JsonValue const& payload)
  272. {
  273. dbgln_if(WEBDRIVER_DEBUG, "WebDriverConnection::navigate_to {}", payload);
  274. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  275. TRY(ensure_open_top_level_browsing_context());
  276. // 2. Let url be the result of getting the property url from the parameters argument.
  277. if (!payload.is_object() || !payload.as_object().has_string("url"sv))
  278. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload doesn't have a string `url`"sv);
  279. URL url(payload.as_object().get_ptr("url"sv)->as_string());
  280. // 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.
  281. // 4. Handle any user prompts and return its value if it is an error.
  282. TRY(handle_any_user_prompts());
  283. // FIXME: 5. Let current URL be the current top-level browsing context’s active document’s URL.
  284. // FIXME: 6. If current URL and url do not have the same absolute URL:
  285. // 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.
  286. // 7. Navigate the current top-level browsing context to url.
  287. m_page_client.page().load(url);
  288. // FIXME: 8. If url is special except for file and current URL and URL do not have the same absolute URL:
  289. // FIXME: a. Try to wait for navigation to complete.
  290. // FIXME: b. Try to run the post-navigation checks.
  291. // FIXME: 9. Set the current browsing context with the current top-level browsing context.
  292. // 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.
  293. // 11. Return success with data null.
  294. return JsonValue {};
  295. }
  296. // 10.2 Get Current URL, https://w3c.github.io/webdriver/#get-current-url
  297. Messages::WebDriverClient::GetCurrentUrlResponse WebDriverConnection::get_current_url()
  298. {
  299. dbgln_if(WEBDRIVER_DEBUG, "WebDriverConnection::get_current_url");
  300. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  301. TRY(ensure_open_top_level_browsing_context());
  302. // 2. Handle any user prompts and return its value if it is an error.
  303. TRY(handle_any_user_prompts());
  304. // 3. Let url be the serialization of the current top-level browsing context’s active document’s document URL.
  305. auto url = m_page_client.page().top_level_browsing_context().active_document()->url().to_deprecated_string();
  306. // 4. Return success with data url.
  307. return url;
  308. }
  309. // 10.3 Back, https://w3c.github.io/webdriver/#dfn-back
  310. Messages::WebDriverClient::BackResponse WebDriverConnection::back()
  311. {
  312. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  313. TRY(ensure_open_top_level_browsing_context());
  314. // 2. Handle any user prompts and return its value if it is an error.
  315. TRY(handle_any_user_prompts());
  316. // 3. Traverse the history by a delta –1 for the current browsing context.
  317. m_page_client.page_did_request_navigate_back();
  318. // 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.
  319. // 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.
  320. // 6. Return success with data null.
  321. return JsonValue {};
  322. }
  323. // 10.4 Forward, https://w3c.github.io/webdriver/#dfn-forward
  324. Messages::WebDriverClient::ForwardResponse WebDriverConnection::forward()
  325. {
  326. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  327. TRY(ensure_open_top_level_browsing_context());
  328. // 2. Handle any user prompts and return its value if it is an error.
  329. TRY(handle_any_user_prompts());
  330. // 3. Traverse the history by a delta 1 for the current browsing context.
  331. m_page_client.page_did_request_navigate_forward();
  332. // 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.
  333. // 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.
  334. // 6. Return success with data null.
  335. return JsonValue {};
  336. }
  337. // 10.5 Refresh, https://w3c.github.io/webdriver/#dfn-refresh
  338. Messages::WebDriverClient::RefreshResponse WebDriverConnection::refresh()
  339. {
  340. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  341. TRY(ensure_open_top_level_browsing_context());
  342. // 2. Handle any user prompts and return its value if it is an error.
  343. TRY(handle_any_user_prompts());
  344. // 3. Initiate an overridden reload of the current top-level browsing context’s active document.
  345. m_page_client.page_did_request_refresh();
  346. // FIXME: 4. If url is special except for file:
  347. // FIXME: 1. Try to wait for navigation to complete.
  348. // FIXME: 2. Try to run the post-navigation checks.
  349. // FIXME: 5. Set the current browsing context with current top-level browsing context.
  350. // 6. Return success with data null.
  351. return JsonValue {};
  352. }
  353. // 10.6 Get Title, https://w3c.github.io/webdriver/#dfn-get-title
  354. Messages::WebDriverClient::GetTitleResponse WebDriverConnection::get_title()
  355. {
  356. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  357. TRY(ensure_open_top_level_browsing_context());
  358. // 2. Handle any user prompts and return its value if it is an error.
  359. TRY(handle_any_user_prompts());
  360. // 3. Let title be the initial value of the title IDL attribute of the current top-level browsing context's active document.
  361. auto title = m_page_client.page().top_level_browsing_context().active_document()->title();
  362. // 4. Return success with data title.
  363. return title;
  364. }
  365. // 11.1 Get Window Handle, https://w3c.github.io/webdriver/#get-window-handle
  366. Messages::WebDriverClient::GetWindowHandleResponse WebDriverConnection::get_window_handle()
  367. {
  368. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  369. TRY(ensure_open_top_level_browsing_context());
  370. // 2. Return success with data being the window handle associated with the current top-level browsing context.
  371. return m_current_window_handle;
  372. }
  373. // 11.2 Close Window, https://w3c.github.io/webdriver/#dfn-close-window
  374. Messages::WebDriverClient::CloseWindowResponse WebDriverConnection::close_window()
  375. {
  376. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  377. TRY(ensure_open_top_level_browsing_context());
  378. // 2. Handle any user prompts and return its value if it is an error.
  379. TRY(handle_any_user_prompts());
  380. // 3. Close the current top-level browsing context.
  381. m_page_client.page().top_level_browsing_context().close();
  382. m_windows.remove(m_current_window_handle);
  383. // 4. If there are no more open top-level browsing contexts, then close the session.
  384. if (m_windows.is_empty())
  385. close_session();
  386. // 5. Return the result of running the remote end steps for the Get Window Handles command.
  387. return get_window_handles().take_response();
  388. }
  389. // 11.3 Switch to Window, https://w3c.github.io/webdriver/#dfn-switch-to-window
  390. Messages::WebDriverClient::SwitchToWindowResponse WebDriverConnection::switch_to_window(JsonValue const& payload)
  391. {
  392. // 1. Let handle be the result of getting the property "handle" from the parameters argument.
  393. // 2. If handle is undefined, return error with error code invalid argument.
  394. auto handle = TRY(get_property(payload, "handle"sv));
  395. // 3. If there is an active user prompt, that prevents the focusing of another top-level browsing
  396. // context, return error with error code unexpected alert open.
  397. if (m_page_client.page().has_pending_dialog())
  398. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
  399. // 4. If handle is equal to the associated window handle for some top-level browsing context in the
  400. // current session, let context be the that browsing context, and set the current top-level
  401. // browsing context with context.
  402. // Otherwise, return error with error code no such window.
  403. auto const& maybe_window = m_windows.get(handle);
  404. if (!maybe_window.has_value())
  405. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found");
  406. m_current_window_handle = handle;
  407. // FIXME: 5. Update any implementation-specific state that would result from the user selecting the current
  408. // browsing context for interaction, without altering OS-level focus.
  409. // 6. Return success with data null.
  410. return JsonValue {};
  411. }
  412. // 11.4 Get Window Handles, https://w3c.github.io/webdriver/#dfn-get-window-handles
  413. Messages::WebDriverClient::GetWindowHandlesResponse WebDriverConnection::get_window_handles()
  414. {
  415. // 1. Let handles be a JSON List.
  416. JsonArray handles {};
  417. // 2. For each top-level browsing context in the remote end, push the associated window handle onto handles.
  418. for (auto const& window_handle : m_windows.keys())
  419. handles.append(window_handle);
  420. // 3. Return success with data handles.
  421. return handles;
  422. }
  423. // 11.8.1 Get Window Rect, https://w3c.github.io/webdriver/#dfn-get-window-rect
  424. Messages::WebDriverClient::GetWindowRectResponse WebDriverConnection::get_window_rect()
  425. {
  426. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  427. TRY(ensure_open_top_level_browsing_context());
  428. // 2. Handle any user prompts and return its value if it is an error.
  429. TRY(handle_any_user_prompts());
  430. // 3. Return success with data set to the WindowRect object for the current top-level browsing context.
  431. return serialize_rect(compute_window_rect(m_page_client.page()));
  432. }
  433. // 11.8.2 Set Window Rect, https://w3c.github.io/webdriver/#dfn-set-window-rect
  434. Messages::WebDriverClient::SetWindowRectResponse WebDriverConnection::set_window_rect(JsonValue const& payload)
  435. {
  436. if (!payload.is_object())
  437. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
  438. auto const& properties = payload.as_object();
  439. auto resolve_property = [](auto name, auto const* property, auto min, auto max) -> ErrorOr<Optional<i32>, Web::WebDriver::Error> {
  440. if (!property)
  441. return Optional<i32> {};
  442. if (!property->is_number())
  443. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' is not a Number", name));
  444. auto number = property->template to_number<i64>();
  445. if (number < min)
  446. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' value {} exceeds the minimum allowed value {}", name, number, min));
  447. if (number > max)
  448. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Property '{}' value {} exceeds the maximum allowed value {}", name, number, max));
  449. return static_cast<i32>(number);
  450. };
  451. // 1. Let width be the result of getting a property named width from the parameters argument, else let it be null.
  452. auto const* width_property = properties.get_ptr("width"sv);
  453. // 2. Let height be the result of getting a property named height from the parameters argument, else let it be null.
  454. auto const* height_property = properties.get_ptr("height"sv);
  455. // 3. Let x be the result of getting a property named x from the parameters argument, else let it be null.
  456. auto const* x_property = properties.get_ptr("x"sv);
  457. // 4. Let y be the result of getting a property named y from the parameters argument, else let it be null.
  458. auto const* y_property = properties.get_ptr("y"sv);
  459. // 5. If width or height is neither null nor a Number from 0 to 2^31 − 1, return error with error code invalid argument.
  460. auto width = TRY(resolve_property("width"sv, width_property, 0, NumericLimits<i32>::max()));
  461. auto height = TRY(resolve_property("height"sv, height_property, 0, NumericLimits<i32>::max()));
  462. // 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.
  463. auto x = TRY(resolve_property("x"sv, x_property, NumericLimits<i32>::min(), NumericLimits<i32>::max()));
  464. auto y = TRY(resolve_property("y"sv, y_property, NumericLimits<i32>::min(), NumericLimits<i32>::max()));
  465. // 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.
  466. // 8. If the current top-level browsing context is no longer open, return error with error code no such window.
  467. TRY(ensure_open_top_level_browsing_context());
  468. // 9. Handle any user prompts and return its value if it is an error.
  469. TRY(handle_any_user_prompts());
  470. // FIXME: 10. Fully exit fullscreen.
  471. // 11. Restore the window.
  472. restore_the_window();
  473. Gfx::IntRect window_rect;
  474. // 11. If width and height are not null:
  475. if (width.has_value() && height.has_value()) {
  476. // 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.
  477. // 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.
  478. auto size = m_page_client.page_did_request_resize_window({ *width, *height });
  479. window_rect.set_size(size);
  480. } else {
  481. window_rect.set_size(m_page_client.page().window_size());
  482. }
  483. // 12. If x and y are not null:
  484. if (x.has_value() && y.has_value()) {
  485. // 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.
  486. auto position = m_page_client.page_did_request_reposition_window({ *x, *y });
  487. window_rect.set_location(position);
  488. } else {
  489. window_rect.set_location(m_page_client.page().window_position());
  490. }
  491. // 14. Return success with data set to the WindowRect object for the current top-level browsing context.
  492. return serialize_rect(window_rect);
  493. }
  494. // 11.8.3 Maximize Window, https://w3c.github.io/webdriver/#dfn-maximize-window
  495. Messages::WebDriverClient::MaximizeWindowResponse WebDriverConnection::maximize_window()
  496. {
  497. // 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.
  498. // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
  499. TRY(ensure_open_top_level_browsing_context());
  500. // 3. Handle any user prompts and return its value if it is an error.
  501. TRY(handle_any_user_prompts());
  502. // FIXME: 4. Fully exit fullscreen.
  503. // 5. Restore the window.
  504. restore_the_window();
  505. // 6. Maximize the window of the current top-level browsing context.
  506. auto window_rect = maximize_the_window();
  507. // 7. Return success with data set to the WindowRect object for the current top-level browsing context.
  508. return serialize_rect(window_rect);
  509. }
  510. // 11.8.4 Minimize Window, https://w3c.github.io/webdriver/#minimize-window
  511. Messages::WebDriverClient::MinimizeWindowResponse WebDriverConnection::minimize_window()
  512. {
  513. // 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.
  514. // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
  515. TRY(ensure_open_top_level_browsing_context());
  516. // 3. Handle any user prompts and return its value if it is an error.
  517. TRY(handle_any_user_prompts());
  518. // FIXME: 4. Fully exit fullscreen.
  519. // 5. Iconify the window.
  520. auto window_rect = iconify_the_window();
  521. // 6. Return success with data set to the WindowRect object for the current top-level browsing context.
  522. return serialize_rect(window_rect);
  523. }
  524. // 11.8.5 Fullscreen Window, https://w3c.github.io/webdriver/#dfn-fullscreen-window
  525. Messages::WebDriverClient::FullscreenWindowResponse WebDriverConnection::fullscreen_window()
  526. {
  527. // 1. If the remote end does not support fullscreen return error with error code unsupported operation.
  528. // 2. 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. // 3. Handle any user prompts and return its value if it is an error.
  531. TRY(handle_any_user_prompts());
  532. // 4. Restore the window.
  533. restore_the_window();
  534. // 5. FIXME: Call fullscreen an element with the current top-level browsing context’s active document’s document element.
  535. // As described in https://fullscreen.spec.whatwg.org/#fullscreen-an-element
  536. // NOTE: What we do here is basically `requestFullscreen(options)` with options["navigationUI"]="show"
  537. auto rect = m_page_client.page_did_request_fullscreen_window();
  538. // 6. Return success with data set to the WindowRect object for the current top-level browsing context.
  539. return serialize_rect(rect);
  540. }
  541. // 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element
  542. Messages::WebDriverClient::FindElementResponse WebDriverConnection::find_element(JsonValue const& payload)
  543. {
  544. // 1. Let location strategy be the result of getting a property called "using".
  545. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  546. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  547. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  548. if (!location_strategy.has_value())
  549. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  550. // 3. Let selector be the result of getting a property called "value".
  551. // 4. If selector is undefined, return error with error code invalid argument.
  552. auto selector = TRY(get_property(payload, "value"sv));
  553. // 5. If the current browsing context is no longer open, return error with error code no such window.
  554. TRY(ensure_open_top_level_browsing_context());
  555. // 6. Handle any user prompts and return its value if it is an error.
  556. TRY(handle_any_user_prompts());
  557. auto start_node_getter = [this]() -> StartNodeGetter::ReturnType {
  558. // 7. Let start node be the current browsing context’s document element.
  559. auto* start_node = m_page_client.page().top_level_browsing_context().active_document();
  560. // 8. If start node is null, return error with error code no such element.
  561. if (!start_node)
  562. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
  563. return start_node;
  564. };
  565. // 9. Let result be the result of trying to Find with start node, location strategy, and selector.
  566. auto result = TRY(find(move(start_node_getter), *location_strategy, selector));
  567. // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
  568. if (result.is_empty())
  569. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
  570. return result.take(0);
  571. }
  572. // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
  573. Messages::WebDriverClient::FindElementsResponse WebDriverConnection::find_elements(JsonValue const& payload)
  574. {
  575. // 1. Let location strategy be the result of getting a property called "using".
  576. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  577. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  578. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  579. if (!location_strategy.has_value())
  580. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  581. // 3. Let selector be the result of getting a property called "value".
  582. // 4. If selector is undefined, return error with error code invalid argument.
  583. auto selector = TRY(get_property(payload, "value"sv));
  584. // 5. If the current browsing context is no longer open, return error with error code no such window.
  585. TRY(ensure_open_top_level_browsing_context());
  586. // 6. Handle any user prompts and return its value if it is an error.
  587. TRY(handle_any_user_prompts());
  588. auto start_node_getter = [this]() -> StartNodeGetter::ReturnType {
  589. // 7. Let start node be the current browsing context’s document element.
  590. auto* start_node = m_page_client.page().top_level_browsing_context().active_document();
  591. // 8. If start node is null, return error with error code no such element.
  592. if (!start_node)
  593. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
  594. return start_node;
  595. };
  596. // 9. Return the result of trying to Find with start node, location strategy, and selector.
  597. return TRY(find(move(start_node_getter), *location_strategy, selector));
  598. }
  599. // 12.3.4 Find Element From Element, https://w3c.github.io/webdriver/#dfn-find-element-from-element
  600. Messages::WebDriverClient::FindElementFromElementResponse WebDriverConnection::find_element_from_element(JsonValue const& payload, DeprecatedString const& element_id)
  601. {
  602. // 1. Let location strategy be the result of getting a property called "using".
  603. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  604. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  605. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  606. if (!location_strategy.has_value())
  607. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  608. // 3. Let selector be the result of getting a property called "value".
  609. // 4. If selector is undefined, return error with error code invalid argument.
  610. auto selector = TRY(get_property(payload, "value"sv));
  611. // 5. If the current browsing context is no longer open, return error with error code no such window.
  612. TRY(ensure_open_top_level_browsing_context());
  613. // 6. Handle any user prompts and return its value if it is an error.
  614. TRY(handle_any_user_prompts());
  615. auto start_node_getter = [&]() -> StartNodeGetter::ReturnType {
  616. // 7. Let start node be the result of trying to get a known connected element with url variable element id.
  617. return TRY(get_known_connected_element(element_id));
  618. };
  619. // 8. Let result be the value of trying to Find with start node, location strategy, and selector.
  620. auto result = TRY(find(move(start_node_getter), *location_strategy, selector));
  621. // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
  622. if (result.is_empty())
  623. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
  624. return result.take(0);
  625. }
  626. // 12.3.5 Find Elements From Element, https://w3c.github.io/webdriver/#dfn-find-elements-from-element
  627. Messages::WebDriverClient::FindElementsFromElementResponse WebDriverConnection::find_elements_from_element(JsonValue const& payload, DeprecatedString const& element_id)
  628. {
  629. // 1. Let location strategy be the result of getting a property called "using".
  630. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  631. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  632. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  633. if (!location_strategy.has_value())
  634. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  635. // 3. Let selector be the result of getting a property called "value".
  636. // 4. If selector is undefined, return error with error code invalid argument.
  637. auto selector = TRY(get_property(payload, "value"sv));
  638. // 5. If the current browsing context is no longer open, return error with error code no such window.
  639. TRY(ensure_open_top_level_browsing_context());
  640. // 6. Handle any user prompts and return its value if it is an error.
  641. TRY(handle_any_user_prompts());
  642. auto start_node_getter = [&]() -> StartNodeGetter::ReturnType {
  643. // 7. Let start node be the result of trying to get a known connected element with url variable element id.
  644. return TRY(get_known_connected_element(element_id));
  645. };
  646. // 8. Return the result of trying to Find with start node, location strategy, and selector.
  647. return TRY(find(move(start_node_getter), *location_strategy, selector));
  648. }
  649. // 12.3.6 Find Element From Shadow Root, https://w3c.github.io/webdriver/#find-element-from-shadow-root
  650. Messages::WebDriverClient::FindElementFromShadowRootResponse WebDriverConnection::find_element_from_shadow_root(JsonValue const& payload, DeprecatedString const& shadow_id)
  651. {
  652. // 1. Let location strategy be the result of getting a property called "using".
  653. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  654. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  655. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  656. if (!location_strategy.has_value())
  657. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  658. // 3. Let selector be the result of getting a property called "value".
  659. // 4. If selector is undefined, return error with error code invalid argument.
  660. auto selector = TRY(get_property(payload, "value"sv));
  661. // 5. If the current browsing context is no longer open, return error with error code no such window.
  662. TRY(ensure_open_top_level_browsing_context());
  663. // 6. Handle any user prompts and return its value if it is an error.
  664. TRY(handle_any_user_prompts());
  665. auto start_node_getter = [&]() -> StartNodeGetter::ReturnType {
  666. // 7. Let start node be the result of trying to get a known shadow root with url variable shadow id.
  667. return TRY(get_known_shadow_root(shadow_id));
  668. };
  669. // 8. Let result be the value of trying to Find with start node, location strategy, and selector.
  670. auto result = TRY(find(move(start_node_getter), *location_strategy, selector));
  671. // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
  672. if (result.is_empty())
  673. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
  674. return result.take(0);
  675. }
  676. // 12.3.7 Find Elements From Shadow Root, https://w3c.github.io/webdriver/#find-elements-from-shadow-root
  677. Messages::WebDriverClient::FindElementsFromShadowRootResponse WebDriverConnection::find_elements_from_shadow_root(JsonValue const& payload, DeprecatedString const& shadow_id)
  678. {
  679. // 1. Let location strategy be the result of getting a property called "using".
  680. auto location_strategy_string = TRY(get_property(payload, "using"sv));
  681. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  682. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  683. if (!location_strategy.has_value())
  684. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, DeprecatedString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  685. // 3. Let selector be the result of getting a property called "value".
  686. // 4. If selector is undefined, return error with error code invalid argument.
  687. auto selector = TRY(get_property(payload, "value"sv));
  688. // 5. If the current browsing context is no longer open, return error with error code no such window.
  689. TRY(ensure_open_top_level_browsing_context());
  690. // 6. Handle any user prompts and return its value if it is an error.
  691. TRY(handle_any_user_prompts());
  692. auto start_node_getter = [&]() -> StartNodeGetter::ReturnType {
  693. // 7. Let start node be the result of trying to get a known shadow root with url variable shadow id.
  694. return TRY(get_known_shadow_root(shadow_id));
  695. };
  696. // 8. Return the result of trying to Find with start node, location strategy, and selector.
  697. return TRY(find(move(start_node_getter), *location_strategy, selector));
  698. }
  699. // 12.3.8 Get Active Element, https://w3c.github.io/webdriver/#get-active-element
  700. Messages::WebDriverClient::GetActiveElementResponse WebDriverConnection::get_active_element()
  701. {
  702. // 1. If the current browsing context is no longer open, return error with error code no such window.
  703. TRY(ensure_open_top_level_browsing_context());
  704. // 2. Handle any user prompts and return its value if it is an error.
  705. TRY(handle_any_user_prompts());
  706. // 3. Let active element be the active element of the current browsing context’s document element.
  707. auto* active_element = m_page_client.page().top_level_browsing_context().active_document()->active_element();
  708. // 4. If active element is a non-null element, return success with data set to web element reference object for active element.
  709. // Otherwise, return error with error code no such element.
  710. if (active_element)
  711. return DeprecatedString::number(active_element->id());
  712. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The current document does not have an active element"sv);
  713. }
  714. // 12.3.9 Get Element Shadow Root, https://w3c.github.io/webdriver/#get-element-shadow-root
  715. Messages::WebDriverClient::GetElementShadowRootResponse WebDriverConnection::get_element_shadow_root(DeprecatedString const& element_id)
  716. {
  717. // 1. If the current browsing context is no longer open, return error with error code no such window.
  718. TRY(ensure_open_top_level_browsing_context());
  719. // 2. Handle any user prompts and return its value if it is an error.
  720. TRY(handle_any_user_prompts());
  721. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  722. auto* element = TRY(get_known_connected_element(element_id));
  723. // 4. Let shadow root be element's shadow root.
  724. auto* shadow_root = element->shadow_root();
  725. // 5. If shadow root is null, return error with error code no such shadow root.
  726. if (!shadow_root)
  727. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchShadowRoot, DeprecatedString::formatted("Element with ID '{}' does not have a shadow root", element_id));
  728. // 6. Let serialized be the shadow root reference object for shadow root.
  729. auto serialized = shadow_root_reference_object(*shadow_root);
  730. // 7. Return success with data serialized.
  731. return serialized;
  732. }
  733. // 12.4.1 Is Element Selected, https://w3c.github.io/webdriver/#dfn-is-element-selected
  734. Messages::WebDriverClient::IsElementSelectedResponse WebDriverConnection::is_element_selected(DeprecatedString const& element_id)
  735. {
  736. // 1. If the current browsing context is no longer open, return error with error code no such window.
  737. TRY(ensure_open_top_level_browsing_context());
  738. // 2. Handle any user prompts and return its value if it is an error.
  739. TRY(handle_any_user_prompts());
  740. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  741. auto* element = TRY(get_known_connected_element(element_id));
  742. // 4. Let selected be the value corresponding to the first matching statement:
  743. bool selected = false;
  744. // element is an input element with a type attribute in the Checkbox- or Radio Button state
  745. if (is<Web::HTML::HTMLInputElement>(*element)) {
  746. // -> The result of element’s checkedness.
  747. auto& input = static_cast<Web::HTML::HTMLInputElement&>(*element);
  748. using enum Web::HTML::HTMLInputElement::TypeAttributeState;
  749. if (input.type_state() == Checkbox || input.type_state() == RadioButton)
  750. selected = input.checked();
  751. }
  752. // element is an option element
  753. else if (is<Web::HTML::HTMLOptionElement>(*element)) {
  754. // -> The result of element’s selectedness.
  755. selected = static_cast<Web::HTML::HTMLOptionElement&>(*element).selected();
  756. }
  757. // Otherwise
  758. // -> False.
  759. // 5. Return success with data selected.
  760. return selected;
  761. }
  762. // 12.4.2 Get Element Attribute, https://w3c.github.io/webdriver/#dfn-get-element-attribute
  763. Messages::WebDriverClient::GetElementAttributeResponse WebDriverConnection::get_element_attribute(DeprecatedString const& element_id, DeprecatedString const& name)
  764. {
  765. // 1. If the current browsing context is no longer open, return error with error code no such window.
  766. TRY(ensure_open_top_level_browsing_context());
  767. // 2. Handle any user prompts and return its value if it is an error.
  768. TRY(handle_any_user_prompts());
  769. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  770. auto* element = TRY(get_known_connected_element(element_id));
  771. // 4. Let result be the result of the first matching condition:
  772. Optional<DeprecatedString> result;
  773. // -> If name is a boolean attribute
  774. if (Web::HTML::is_boolean_attribute(name)) {
  775. // "true" (string) if the element has the attribute, otherwise null.
  776. if (element->has_attribute(name))
  777. result = "true"sv;
  778. }
  779. // -> Otherwise
  780. else {
  781. // The result of getting an attribute by name name.
  782. result = element->get_attribute(name);
  783. }
  784. // 5. Return success with data result.
  785. if (result.has_value())
  786. return result.release_value();
  787. return JsonValue {};
  788. }
  789. // 12.4.3 Get Element Property, https://w3c.github.io/webdriver/#dfn-get-element-property
  790. Messages::WebDriverClient::GetElementPropertyResponse WebDriverConnection::get_element_property(DeprecatedString const& element_id, DeprecatedString const& name)
  791. {
  792. // 1. If the current browsing context is no longer open, return error with error code no such window.
  793. TRY(ensure_open_top_level_browsing_context());
  794. // 2. Handle any user prompts and return its value if it is an error.
  795. TRY(handle_any_user_prompts());
  796. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  797. auto* element = TRY(get_known_connected_element(element_id));
  798. Optional<DeprecatedString> result;
  799. // 4. Let property be the result of calling the Object.[[GetProperty]](name) on element.
  800. if (auto property_or_error = element->get(name); !property_or_error.is_throw_completion()) {
  801. auto property = property_or_error.release_value();
  802. // 5. Let result be the value of property if not undefined, or null.
  803. if (!property.is_undefined()) {
  804. if (auto string_or_error = property.to_string(element->vm()); !string_or_error.is_error())
  805. result = string_or_error.release_value();
  806. }
  807. }
  808. // 6. Return success with data result.
  809. if (result.has_value())
  810. return result.release_value();
  811. return JsonValue {};
  812. }
  813. // 12.4.4 Get Element CSS Value, https://w3c.github.io/webdriver/#dfn-get-element-css-value
  814. Messages::WebDriverClient::GetElementCssValueResponse WebDriverConnection::get_element_css_value(DeprecatedString const& element_id, DeprecatedString const& name)
  815. {
  816. // 1. If the current browsing context is no longer open, return error with error code no such window.
  817. TRY(ensure_open_top_level_browsing_context());
  818. // 2. Handle any user prompts and return its value if it is an error.
  819. TRY(handle_any_user_prompts());
  820. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  821. auto* element = TRY(get_known_connected_element(element_id));
  822. // 4. Let computed value be the result of the first matching condition:
  823. DeprecatedString computed_value;
  824. // -> current browsing context’s active document’s type is not "xml"
  825. if (!m_page_client.page().top_level_browsing_context().active_document()->is_xml_document()) {
  826. // computed value of parameter property name from element’s style declarations. property name is obtained from url variables.
  827. auto property = Web::CSS::property_id_from_string(name);
  828. if (auto* computed_values = element->computed_css_values())
  829. computed_value = computed_values->property(property)->to_deprecated_string();
  830. }
  831. // -> Otherwise
  832. else {
  833. // "" (empty string)
  834. computed_value = DeprecatedString::empty();
  835. }
  836. // 5. Return success with data computed value.
  837. return computed_value;
  838. }
  839. // 12.4.5 Get Element Text, https://w3c.github.io/webdriver/#dfn-get-element-text
  840. Messages::WebDriverClient::GetElementTextResponse WebDriverConnection::get_element_text(DeprecatedString const& element_id)
  841. {
  842. // 1. If the current browsing context is no longer open, return error with error code no such window.
  843. TRY(ensure_open_top_level_browsing_context());
  844. // 2. Handle any user prompts and return its value if it is an error.
  845. TRY(handle_any_user_prompts());
  846. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  847. auto* element = TRY(get_known_connected_element(element_id));
  848. // 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.
  849. auto rendered_text = element->text_content();
  850. // 5. Return success with data rendered text.
  851. return rendered_text;
  852. }
  853. // 12.4.6 Get Element Tag Name, https://w3c.github.io/webdriver/#dfn-get-element-tag-name
  854. Messages::WebDriverClient::GetElementTagNameResponse WebDriverConnection::get_element_tag_name(DeprecatedString const& element_id)
  855. {
  856. // 1. If the current browsing context is no longer open, return error with error code no such window.
  857. TRY(ensure_open_top_level_browsing_context());
  858. // 2. Handle any user prompts and return its value if it is an error.
  859. TRY(handle_any_user_prompts());
  860. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  861. auto* element = TRY(get_known_connected_element(element_id));
  862. // 4. Let qualified name be the result of getting element’s tagName IDL attribute.
  863. auto qualified_name = element->tag_name();
  864. // 5. Return success with data qualified name.
  865. return qualified_name;
  866. }
  867. // 12.4.7 Get Element Rect, https://w3c.github.io/webdriver/#dfn-get-element-rect
  868. Messages::WebDriverClient::GetElementRectResponse WebDriverConnection::get_element_rect(DeprecatedString const& element_id)
  869. {
  870. // 1. If the current browsing context is no longer open, return error with error code no such window.
  871. TRY(ensure_open_top_level_browsing_context());
  872. // 2. Handle any user prompts and return its value if it is an error.
  873. TRY(handle_any_user_prompts());
  874. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  875. auto* element = TRY(get_known_connected_element(element_id));
  876. // 4. Calculate the absolute position of element and let it be coordinates.
  877. // 5. Let rect be element’s bounding rectangle.
  878. auto rect = calculate_absolute_rect_of_element(m_page_client.page(), *element);
  879. // 6. Let body be a new JSON Object initialized with:
  880. // "x"
  881. // The first value of coordinates.
  882. // "y"
  883. // The second value of coordinates.
  884. // "width"
  885. // Value of rect’s width dimension.
  886. // "height"
  887. // Value of rect’s height dimension.
  888. auto body = serialize_rect(rect);
  889. // 7. Return success with data body.
  890. return body;
  891. }
  892. // 12.4.8 Is Element Enabled, https://w3c.github.io/webdriver/#dfn-is-element-enabled
  893. Messages::WebDriverClient::IsElementEnabledResponse WebDriverConnection::is_element_enabled(DeprecatedString const& element_id)
  894. {
  895. // 1. 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. // 2. Handle any user prompts and return its value if it is an error.
  898. TRY(handle_any_user_prompts());
  899. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  900. auto* element = TRY(get_known_connected_element(element_id));
  901. // 4. Let enabled be a boolean initially set to true if the current browsing context’s active document’s type is not "xml".
  902. // 5. Otherwise, let enabled to false and jump to the last step of this algorithm.
  903. bool enabled = !m_page_client.page().top_level_browsing_context().active_document()->is_xml_document();
  904. // 6. Set enabled to false if a form control is disabled.
  905. if (enabled && is<Web::HTML::FormAssociatedElement>(*element)) {
  906. auto& form_associated_element = dynamic_cast<Web::HTML::FormAssociatedElement&>(*element);
  907. enabled = form_associated_element.enabled();
  908. }
  909. // 7. Return success with data enabled.
  910. return enabled;
  911. }
  912. // 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source
  913. Messages::WebDriverClient::GetSourceResponse WebDriverConnection::get_source()
  914. {
  915. // 1. If the current browsing context is no longer open, return error with error code no such window.
  916. TRY(ensure_open_top_level_browsing_context());
  917. // 2. Handle any user prompts and return its value if it is an error.
  918. TRY(handle_any_user_prompts());
  919. auto* document = m_page_client.page().top_level_browsing_context().active_document();
  920. Optional<DeprecatedString> source;
  921. // 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.
  922. if (auto result = document->serialize_fragment(Web::DOMParsing::RequireWellFormed::Yes); !result.is_error())
  923. source = result.release_value();
  924. // 4. Let source be the result of serializing to string the current browsing context active document, if source is null.
  925. if (!source.has_value())
  926. source = MUST(document->serialize_fragment(Web::DOMParsing::RequireWellFormed::No));
  927. // 5. Return success with data source.
  928. return source.release_value();
  929. }
  930. // 13.2.1 Execute Script, https://w3c.github.io/webdriver/#dfn-execute-script
  931. Messages::WebDriverClient::ExecuteScriptResponse WebDriverConnection::execute_script(JsonValue const& payload)
  932. {
  933. // 1. Let body and arguments be the result of trying to extract the script arguments from a request with argument parameters.
  934. auto const& [body, arguments] = TRY(extract_the_script_arguments_from_a_request(payload));
  935. // 2. If the current browsing context is no longer open, return error with error code no such window.
  936. TRY(ensure_open_top_level_browsing_context());
  937. // 3. Handle any user prompts, and return its value if it is an error.
  938. TRY(handle_any_user_prompts());
  939. // 4., 5.1-5.3.
  940. auto result = Web::WebDriver::execute_script(m_page_client.page(), body, move(arguments), m_timeouts_configuration.script_timeout);
  941. dbgln_if(WEBDRIVER_DEBUG, "Executing script returned: {}", result.value);
  942. switch (result.type) {
  943. // 6. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
  944. case Web::WebDriver::ExecuteScriptResultType::Timeout:
  945. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
  946. // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
  947. case Web::WebDriver::ExecuteScriptResultType::PromiseResolved:
  948. return move(result.value);
  949. // 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.
  950. case Web::WebDriver::ExecuteScriptResultType::PromiseRejected:
  951. case Web::WebDriver::ExecuteScriptResultType::JavaScriptError:
  952. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
  953. }
  954. VERIFY_NOT_REACHED();
  955. }
  956. // 13.2.2 Execute Async Script, https://w3c.github.io/webdriver/#dfn-execute-async-script
  957. Messages::WebDriverClient::ExecuteAsyncScriptResponse WebDriverConnection::execute_async_script(JsonValue const& payload)
  958. {
  959. // 1. Let body and arguments by the result of trying to extract the script arguments from a request with argument parameters.
  960. auto const& [body, arguments] = TRY(extract_the_script_arguments_from_a_request(payload));
  961. // 2. If the current browsing context is no longer open, return error with error code no such window.
  962. TRY(ensure_open_top_level_browsing_context());
  963. // 3. Handle any user prompts, and return its value if it is an error.
  964. TRY(handle_any_user_prompts());
  965. // 4., 5.1-5.11.
  966. auto result = Web::WebDriver::execute_async_script(m_page_client.page(), body, move(arguments), m_timeouts_configuration.script_timeout);
  967. dbgln_if(WEBDRIVER_DEBUG, "Executing async script returned: {}", result.value);
  968. switch (result.type) {
  969. // 6. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
  970. case Web::WebDriver::ExecuteScriptResultType::Timeout:
  971. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
  972. // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
  973. case Web::WebDriver::ExecuteScriptResultType::PromiseResolved:
  974. return move(result.value);
  975. // 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.
  976. case Web::WebDriver::ExecuteScriptResultType::PromiseRejected:
  977. case Web::WebDriver::ExecuteScriptResultType::JavaScriptError:
  978. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
  979. }
  980. VERIFY_NOT_REACHED();
  981. }
  982. // 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies
  983. Messages::WebDriverClient::GetAllCookiesResponse WebDriverConnection::get_all_cookies()
  984. {
  985. // 1. If the current browsing context is no longer open, return error with error code no such window.
  986. TRY(ensure_open_top_level_browsing_context());
  987. // 2. Handle any user prompts, and return its value if it is an error.
  988. TRY(handle_any_user_prompts());
  989. // 3. Let cookies be a new JSON List.
  990. JsonArray cookies;
  991. // 4. For each cookie in all associated cookies of the current browsing context’s active document:
  992. auto* document = m_page_client.page().top_level_browsing_context().active_document();
  993. for (auto const& cookie : m_page_client.page_did_request_all_cookies(document->url())) {
  994. // 1. Let serialized cookie be the result of serializing cookie.
  995. auto serialized_cookie = serialize_cookie(cookie);
  996. // 2. Append serialized cookie to cookies
  997. cookies.append(move(serialized_cookie));
  998. }
  999. // 5. Return success with data cookies.
  1000. return cookies;
  1001. }
  1002. // 14.2 Get Named Cookie, https://w3c.github.io/webdriver/#dfn-get-named-cookie
  1003. Messages::WebDriverClient::GetNamedCookieResponse WebDriverConnection::get_named_cookie(DeprecatedString const& name)
  1004. {
  1005. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1006. TRY(ensure_open_top_level_browsing_context());
  1007. // 2. Handle any user prompts, and return its value if it is an error.
  1008. TRY(handle_any_user_prompts());
  1009. // 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.
  1010. auto* document = m_page_client.page().top_level_browsing_context().active_document();
  1011. if (auto cookie = m_page_client.page_did_request_named_cookie(document->url(), name); cookie.has_value()) {
  1012. auto serialized_cookie = serialize_cookie(*cookie);
  1013. return serialized_cookie;
  1014. }
  1015. // 4. Otherwise, return error with error code no such cookie.
  1016. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchCookie, DeprecatedString::formatted("Cookie '{}' not found", name));
  1017. }
  1018. // 14.3 Add Cookie, https://w3c.github.io/webdriver/#dfn-adding-a-cookie
  1019. Messages::WebDriverClient::AddCookieResponse WebDriverConnection::add_cookie(JsonValue const& payload)
  1020. {
  1021. // 1. Let data be the result of getting a property named cookie from the parameters argument.
  1022. auto const& data = *TRY(get_property<JsonObject const*>(payload, "cookie"sv));
  1023. // 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.
  1024. // NOTE: This validation is performed in subsequent steps.
  1025. // 3. If the current browsing context is no longer open, return error with error code no such window.
  1026. TRY(ensure_open_top_level_browsing_context());
  1027. // 4. Handle any user prompts, and return its value if it is an error.
  1028. TRY(handle_any_user_prompts());
  1029. // FIXME: 5. If the current browsing context’s document element is a cookie-averse Document object, return error with error code invalid cookie domain.
  1030. // 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.
  1031. // NOTE: This validation is either performed in subsequent steps, or is performed by the CookieJar (namely domain matching).
  1032. // 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:
  1033. Web::Cookie::ParsedCookie cookie {};
  1034. cookie.name = TRY(get_property(data, "name"sv));
  1035. cookie.value = TRY(get_property(data, "value"sv));
  1036. // Cookie path
  1037. // The value if the entry exists, otherwise "/".
  1038. if (data.has("path"sv))
  1039. cookie.path = TRY(get_property(data, "path"sv));
  1040. else
  1041. cookie.path = "/";
  1042. // Cookie domain
  1043. // The value if the entry exists, otherwise the current browsing context’s active document’s URL domain.
  1044. // NOTE: The otherwise case is handled by the CookieJar
  1045. if (data.has("domain"sv))
  1046. cookie.domain = TRY(get_property(data, "domain"sv));
  1047. // Cookie secure only
  1048. // The value if the entry exists, otherwise false.
  1049. if (data.has("secure"sv))
  1050. cookie.secure_attribute_present = TRY(get_property<bool>(data, "secure"sv));
  1051. // Cookie HTTP only
  1052. // The value if the entry exists, otherwise false.
  1053. if (data.has("httpOnly"sv))
  1054. cookie.http_only_attribute_present = TRY(get_property<bool>(data, "httpOnly"sv));
  1055. // Cookie expiry time
  1056. // The value if the entry exists, otherwise leave unset to indicate that this is a session cookie.
  1057. if (data.has("expiry"sv)) {
  1058. // NOTE: less than 0 or greater than safe integer are handled by the JSON parser
  1059. auto expiry = TRY(get_property<u32>(data, "expiry"sv));
  1060. cookie.expiry_time_from_expires_attribute = Core::DateTime::from_timestamp(expiry);
  1061. }
  1062. // Cookie same site
  1063. // The value if the entry exists, otherwise leave unset to indicate that no same site policy is defined.
  1064. if (data.has("sameSite"sv)) {
  1065. auto same_site = TRY(get_property(data, "sameSite"sv));
  1066. cookie.same_site_attribute = Web::Cookie::same_site_from_string(same_site);
  1067. }
  1068. auto* document = m_page_client.page().top_level_browsing_context().active_document();
  1069. m_page_client.page_did_set_cookie(document->url(), cookie, Web::Cookie::Source::Http);
  1070. // If there is an error during this step, return error with error code unable to set cookie.
  1071. // NOTE: This probably should only apply to the actual setting of the cookie in the Browser, which cannot fail in our case.
  1072. // 8. Return success with data null.
  1073. return JsonValue {};
  1074. }
  1075. // 14.4 Delete Cookie, https://w3c.github.io/webdriver/#dfn-delete-cookie
  1076. Messages::WebDriverClient::DeleteCookieResponse WebDriverConnection::delete_cookie(DeprecatedString const& name)
  1077. {
  1078. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1079. TRY(ensure_open_top_level_browsing_context());
  1080. // 2. Handle any user prompts, and return its value if it is an error.
  1081. TRY(handle_any_user_prompts());
  1082. // 3. Delete cookies using the url variable name parameter as the filter argument.
  1083. delete_cookies(name);
  1084. // 4. Return success with data null.
  1085. return JsonValue {};
  1086. }
  1087. // 14.5 Delete All Cookies, https://w3c.github.io/webdriver/#dfn-delete-all-cookies
  1088. Messages::WebDriverClient::DeleteAllCookiesResponse WebDriverConnection::delete_all_cookies()
  1089. {
  1090. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1091. TRY(ensure_open_top_level_browsing_context());
  1092. // 2. Handle any user prompts, and return its value if it is an error.
  1093. TRY(handle_any_user_prompts());
  1094. // 3. Delete cookies, giving no filtering argument.
  1095. delete_cookies();
  1096. // 4. Return success with data null.
  1097. return JsonValue {};
  1098. }
  1099. // 16.1 Dismiss Alert, https://w3c.github.io/webdriver/#dismiss-alert
  1100. Messages::WebDriverClient::DismissAlertResponse WebDriverConnection::dismiss_alert()
  1101. {
  1102. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1103. TRY(ensure_open_top_level_browsing_context());
  1104. // 2. If there is no current user prompt, return error with error code no such alert.
  1105. if (!m_page_client.page().has_pending_dialog())
  1106. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
  1107. // 3. Dismiss the current user prompt.
  1108. m_page_client.page().dismiss_dialog();
  1109. // 4. Return success with data null.
  1110. return JsonValue {};
  1111. }
  1112. // 16.2 Accept Alert, https://w3c.github.io/webdriver/#accept-alert
  1113. Messages::WebDriverClient::AcceptAlertResponse WebDriverConnection::accept_alert()
  1114. {
  1115. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1116. TRY(ensure_open_top_level_browsing_context());
  1117. // 2. If there is no current user prompt, return error with error code no such alert.
  1118. if (!m_page_client.page().has_pending_dialog())
  1119. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
  1120. // 3. Accept the current user prompt.
  1121. m_page_client.page().accept_dialog();
  1122. // 4. Return success with data null.
  1123. return JsonValue {};
  1124. }
  1125. // 16.3 Get Alert Text, https://w3c.github.io/webdriver/#get-alert-text
  1126. Messages::WebDriverClient::GetAlertTextResponse WebDriverConnection::get_alert_text()
  1127. {
  1128. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1129. TRY(ensure_open_top_level_browsing_context());
  1130. // 2. If there is no current user prompt, return error with error code no such alert.
  1131. if (!m_page_client.page().has_pending_dialog())
  1132. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
  1133. // 3. Let message be the text message associated with the current user prompt, or otherwise be null.
  1134. auto const& message = m_page_client.page().pending_dialog_text();
  1135. // 4. Return success with data message.
  1136. if (message.has_value())
  1137. return *message;
  1138. return JsonValue {};
  1139. }
  1140. // 16.4 Send Alert Text, https://w3c.github.io/webdriver/#send-alert-text
  1141. Messages::WebDriverClient::SendAlertTextResponse WebDriverConnection::send_alert_text(JsonValue const& payload)
  1142. {
  1143. // 1. Let text be the result of getting the property "text" from parameters.
  1144. // 2. If text is not a String, return error with error code invalid argument.
  1145. auto text = TRY(get_property(payload, "text"sv));
  1146. // 3. If the current top-level browsing context is no longer open, return error with error code no such window.
  1147. TRY(ensure_open_top_level_browsing_context());
  1148. // 4. If there is no current user prompt, return error with error code no such alert.
  1149. if (!m_page_client.page().has_pending_dialog())
  1150. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
  1151. // 5. Run the substeps of the first matching current user prompt:
  1152. switch (m_page_client.page().pending_dialog()) {
  1153. // -> alert
  1154. // -> confirm
  1155. case Web::Page::PendingDialog::Alert:
  1156. case Web::Page::PendingDialog::Confirm:
  1157. // Return error with error code element not interactable.
  1158. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Only prompt dialogs may receive text"sv);
  1159. // -> prompt
  1160. case Web::Page::PendingDialog::Prompt:
  1161. // Do nothing.
  1162. break;
  1163. // -> Otherwise
  1164. default:
  1165. // Return error with error code unsupported operation.
  1166. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Unknown dialog type"sv);
  1167. }
  1168. // 6. Perform user agent dependent steps to set the value of current user prompt’s text field to text.
  1169. m_page_client.page_did_request_set_prompt_text(move(text));
  1170. // 7. Return success with data null.
  1171. return JsonValue {};
  1172. }
  1173. // 17.1 Take Screenshot, https://w3c.github.io/webdriver/#take-screenshot
  1174. Messages::WebDriverClient::TakeScreenshotResponse WebDriverConnection::take_screenshot()
  1175. {
  1176. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1177. TRY(ensure_open_top_level_browsing_context());
  1178. // 2. When the user agent is next to run the animation frame callbacks:
  1179. // a. Let root rect be the current top-level browsing context’s document element’s rectangle.
  1180. // b. Let screenshot result be the result of trying to call draw a bounding box from the framebuffer, given root rect as an argument.
  1181. // c. Let canvas be a canvas element of screenshot result’s data.
  1182. // d. Let encoding result be the result of trying encoding a canvas as Base64 canvas.
  1183. // e. Let encoded string be encoding result’s data.
  1184. auto* document = m_page_client.page().top_level_browsing_context().active_document();
  1185. auto root_rect = calculate_absolute_rect_of_element(m_page_client.page(), *document->document_element());
  1186. auto encoded_string = TRY(Web::WebDriver::capture_element_screenshot(
  1187. [&](auto const& rect, auto& bitmap) { m_page_client.paint(rect.template to_type<Web::DevicePixels>(), bitmap); },
  1188. m_page_client.page(),
  1189. *document->document_element(),
  1190. root_rect));
  1191. // 3. Return success with data encoded string.
  1192. return encoded_string;
  1193. }
  1194. // 17.2 Take Element Screenshot, https://w3c.github.io/webdriver/#dfn-take-element-screenshot
  1195. Messages::WebDriverClient::TakeElementScreenshotResponse WebDriverConnection::take_element_screenshot(DeprecatedString const& element_id)
  1196. {
  1197. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1198. TRY(ensure_open_top_level_browsing_context());
  1199. // 2. Handle any user prompts and return its value if it is an error.
  1200. TRY(handle_any_user_prompts());
  1201. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  1202. auto* element = TRY(get_known_connected_element(element_id));
  1203. // 4. Scroll into view the element.
  1204. scroll_element_into_view(*element);
  1205. // 5. When the user agent is next to run the animation frame callbacks:
  1206. // a. Let element rect be element’s rectangle.
  1207. // b. Let screenshot result be the result of trying to call draw a bounding box from the framebuffer, given element rect as an argument.
  1208. // c. Let canvas be a canvas element of screenshot result’s data.
  1209. // d. Let encoding result be the result of trying encoding a canvas as Base64 canvas.
  1210. // e. Let encoded string be encoding result’s data.
  1211. auto element_rect = calculate_absolute_rect_of_element(m_page_client.page(), *element);
  1212. auto encoded_string = TRY(Web::WebDriver::capture_element_screenshot(
  1213. [&](auto const& rect, auto& bitmap) { m_page_client.paint(rect.template to_type<Web::DevicePixels>(), bitmap); },
  1214. m_page_client.page(),
  1215. *element,
  1216. element_rect));
  1217. // 6. Return success with data encoded string.
  1218. return encoded_string;
  1219. }
  1220. // 18.1 Print Page, https://w3c.github.io/webdriver/#dfn-print-page
  1221. Messages::WebDriverClient::PrintPageResponse WebDriverConnection::print_page()
  1222. {
  1223. // FIXME: Actually implement this :^)
  1224. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Print not implemented"sv);
  1225. }
  1226. // https://w3c.github.io/webdriver/#dfn-no-longer-open
  1227. ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::ensure_open_top_level_browsing_context()
  1228. {
  1229. // A browsing context is said to be no longer open if it has been discarded.
  1230. if (m_page_client.page().top_level_browsing_context().has_been_discarded())
  1231. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found"sv);
  1232. return {};
  1233. }
  1234. // https://w3c.github.io/webdriver/#dfn-handle-any-user-prompts
  1235. ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::handle_any_user_prompts()
  1236. {
  1237. // 1. If there is no current user prompt, abort these steps and return success.
  1238. if (!m_page_client.page().has_pending_dialog())
  1239. return {};
  1240. // 2. Perform the following substeps based on the current session’s user prompt handler:
  1241. switch (m_unhandled_prompt_behavior) {
  1242. // -> dismiss state
  1243. case Web::WebDriver::UnhandledPromptBehavior::Dismiss:
  1244. // Dismiss the current user prompt.
  1245. m_page_client.page().dismiss_dialog();
  1246. break;
  1247. // -> accept state
  1248. case Web::WebDriver::UnhandledPromptBehavior::Accept:
  1249. // Accept the current user prompt.
  1250. m_page_client.page().accept_dialog();
  1251. break;
  1252. // -> dismiss and notify state
  1253. case Web::WebDriver::UnhandledPromptBehavior::DismissAndNotify:
  1254. // Dismiss the current user prompt.
  1255. m_page_client.page().dismiss_dialog();
  1256. // Return an annotated unexpected alert open error.
  1257. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
  1258. // -> accept and notify state
  1259. case Web::WebDriver::UnhandledPromptBehavior::AcceptAndNotify:
  1260. // Accept the current user prompt.
  1261. m_page_client.page().accept_dialog();
  1262. // Return an annotated unexpected alert open error.
  1263. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
  1264. // -> ignore state
  1265. case Web::WebDriver::UnhandledPromptBehavior::Ignore:
  1266. // Return an annotated unexpected alert open error.
  1267. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
  1268. }
  1269. // 3. Return success.
  1270. return {};
  1271. }
  1272. // https://w3c.github.io/webdriver/#dfn-restore-the-window
  1273. void WebDriverConnection::restore_the_window()
  1274. {
  1275. // 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.
  1276. m_page_client.page_did_request_restore_window();
  1277. // 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.
  1278. // FIXME: Implement timeouts.
  1279. Web::Platform::EventLoopPlugin::the().spin_until([this]() {
  1280. auto state = m_page_client.page().top_level_browsing_context().system_visibility_state();
  1281. return state == Web::HTML::VisibilityState::Visible;
  1282. });
  1283. }
  1284. // https://w3c.github.io/webdriver/#dfn-maximize-the-window
  1285. Gfx::IntRect WebDriverConnection::maximize_the_window()
  1286. {
  1287. // 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.
  1288. auto rect = m_page_client.page_did_request_maximize_window();
  1289. // Return when the window has completed the transition, or within an implementation-defined timeout.
  1290. return rect;
  1291. }
  1292. // https://w3c.github.io/webdriver/#dfn-iconify-the-window
  1293. Gfx::IntRect WebDriverConnection::iconify_the_window()
  1294. {
  1295. // 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.
  1296. auto rect = m_page_client.page_did_request_minimize_window();
  1297. // 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.
  1298. // FIXME: Implement timeouts.
  1299. Web::Platform::EventLoopPlugin::the().spin_until([this]() {
  1300. auto state = m_page_client.page().top_level_browsing_context().system_visibility_state();
  1301. return state == Web::HTML::VisibilityState::Hidden;
  1302. });
  1303. return rect;
  1304. }
  1305. // https://w3c.github.io/webdriver/#dfn-find
  1306. ErrorOr<JsonArray, Web::WebDriver::Error> WebDriverConnection::find(StartNodeGetter&& start_node_getter, Web::WebDriver::LocationStrategy using_, StringView value)
  1307. {
  1308. // 1. Let end time be the current time plus the session implicit wait timeout.
  1309. auto end_time = Time::now_monotonic() + Time::from_milliseconds(static_cast<i64>(m_timeouts_configuration.implicit_wait_timeout));
  1310. // 2. Let location strategy be equal to using.
  1311. auto location_strategy = using_;
  1312. // 3. Let selector be equal to value.
  1313. auto selector = value;
  1314. ErrorOr<JS::GCPtr<Web::DOM::NodeList>, Web::WebDriver::Error> maybe_elements { nullptr };
  1315. auto try_to_find_element = [&]() -> decltype(maybe_elements) {
  1316. // 4. Let elements returned be the result of trying to call the relevant element location strategy with arguments start node, and selector.
  1317. auto elements = Web::WebDriver::invoke_location_strategy(location_strategy, *TRY(start_node_getter()), selector);
  1318. // 5. If a DOMException, SyntaxError, XPathException, or other error occurs during the execution of the element location strategy, return error invalid selector.
  1319. if (elements.is_error())
  1320. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSelector, DeprecatedString::formatted("The location strategy could not finish: {}", elements.error().message));
  1321. return elements.release_value();
  1322. };
  1323. Web::Platform::EventLoopPlugin::the().spin_until([&]() {
  1324. maybe_elements = try_to_find_element();
  1325. if (maybe_elements.is_error())
  1326. return true;
  1327. // 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.
  1328. return maybe_elements.value()->length() != 0 || Time::now_monotonic() >= end_time;
  1329. });
  1330. auto elements = TRY(maybe_elements);
  1331. VERIFY(elements);
  1332. // 7. Let result be an empty JSON List.
  1333. JsonArray result;
  1334. result.ensure_capacity(elements->length());
  1335. // 8. For each element in elements returned, append the web element reference object for element, to result.
  1336. for (size_t i = 0; i < elements->length(); ++i)
  1337. result.append(web_element_reference_object(*elements->item(i)));
  1338. // 9. Return success with data result.
  1339. return result;
  1340. }
  1341. // https://w3c.github.io/webdriver/#dfn-extract-the-script-arguments-from-a-request
  1342. ErrorOr<WebDriverConnection::ScriptArguments, Web::WebDriver::Error> WebDriverConnection::extract_the_script_arguments_from_a_request(JsonValue const& payload)
  1343. {
  1344. auto* window = m_page_client.page().top_level_browsing_context().active_window();
  1345. auto& vm = window->vm();
  1346. // 1. Let script be the result of getting a property named script from the parameters.
  1347. // 2. If script is not a String, return error with error code invalid argument.
  1348. auto script = TRY(get_property(payload, "script"sv));
  1349. // 3. Let args be the result of getting a property named args from the parameters.
  1350. // 4. If args is not an Array return error with error code invalid argument.
  1351. auto const& args = *TRY(get_property<JsonArray const*>(payload, "args"sv));
  1352. // 5. Let arguments be the result of calling the JSON deserialize algorithm with arguments args.
  1353. auto arguments = JS::MarkedVector<JS::Value> { vm.heap() };
  1354. args.for_each([&](auto const& arg) {
  1355. arguments.append(JS::JSONObject::parse_json_value(vm, arg));
  1356. });
  1357. // 6. Return success with data script and arguments.
  1358. return ScriptArguments { move(script), move(arguments) };
  1359. }
  1360. // https://w3c.github.io/webdriver/#dfn-delete-cookies
  1361. void WebDriverConnection::delete_cookies(Optional<StringView> const& name)
  1362. {
  1363. // For each cookie among all associated cookies of the current browsing context’s active document, un the substeps of the first matching condition:
  1364. auto* document = m_page_client.page().top_level_browsing_context().active_document();
  1365. for (auto& cookie : m_page_client.page_did_request_all_cookies(document->url())) {
  1366. // -> name is undefined
  1367. // -> name is equal to cookie name
  1368. if (!name.has_value() || name.value() == cookie.name) {
  1369. // Set the cookie expiry time to a Unix timestamp in the past.
  1370. cookie.expiry_time = Core::DateTime::from_timestamp(0);
  1371. m_page_client.page_did_update_cookie(move(cookie));
  1372. }
  1373. // -> Otherwise
  1374. // Do nothing.
  1375. }
  1376. }
  1377. }