WebDriverConnection.cpp 143 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862
  1. /*
  2. * Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
  3. * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
  4. * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
  5. * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
  6. * Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include <AK/JsonObject.h>
  11. #include <AK/JsonValue.h>
  12. #include <AK/LexicalPath.h>
  13. #include <AK/Time.h>
  14. #include <AK/Vector.h>
  15. #include <LibCore/File.h>
  16. #include <LibJS/Runtime/JSONObject.h>
  17. #include <LibJS/Runtime/Value.h>
  18. #include <LibWeb/CSS/CSSStyleValue.h>
  19. #include <LibWeb/CSS/PropertyID.h>
  20. #include <LibWeb/CSS/StyleProperties.h>
  21. #include <LibWeb/Cookie/Cookie.h>
  22. #include <LibWeb/Cookie/ParsedCookie.h>
  23. #include <LibWeb/Crypto/Crypto.h>
  24. #include <LibWeb/DOM/Document.h>
  25. #include <LibWeb/DOM/DocumentObserver.h>
  26. #include <LibWeb/DOM/Element.h>
  27. #include <LibWeb/DOM/Event.h>
  28. #include <LibWeb/DOM/Position.h>
  29. #include <LibWeb/DOM/ShadowRoot.h>
  30. #include <LibWeb/Geometry/DOMRect.h>
  31. #include <LibWeb/HTML/AnimationFrameCallbackDriver.h>
  32. #include <LibWeb/HTML/AttributeNames.h>
  33. #include <LibWeb/HTML/BrowsingContext.h>
  34. #include <LibWeb/HTML/Focus.h>
  35. #include <LibWeb/HTML/FormAssociatedElement.h>
  36. #include <LibWeb/HTML/HTMLDataListElement.h>
  37. #include <LibWeb/HTML/HTMLFrameElement.h>
  38. #include <LibWeb/HTML/HTMLIFrameElement.h>
  39. #include <LibWeb/HTML/HTMLInputElement.h>
  40. #include <LibWeb/HTML/HTMLOptGroupElement.h>
  41. #include <LibWeb/HTML/HTMLOptionElement.h>
  42. #include <LibWeb/HTML/HTMLSelectElement.h>
  43. #include <LibWeb/HTML/HTMLTextAreaElement.h>
  44. #include <LibWeb/HTML/NavigationObserver.h>
  45. #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
  46. #include <LibWeb/HTML/SelectedFile.h>
  47. #include <LibWeb/HTML/TraversableNavigable.h>
  48. #include <LibWeb/HTML/WindowProxy.h>
  49. #include <LibWeb/Page/Page.h>
  50. #include <LibWeb/Platform/EventLoopPlugin.h>
  51. #include <LibWeb/Platform/Timer.h>
  52. #include <LibWeb/UIEvents/EventNames.h>
  53. #include <LibWeb/UIEvents/MouseEvent.h>
  54. #include <LibWeb/WebDriver/Actions.h>
  55. #include <LibWeb/WebDriver/ElementReference.h>
  56. #include <LibWeb/WebDriver/ExecuteScript.h>
  57. #include <LibWeb/WebDriver/HeapTimer.h>
  58. #include <LibWeb/WebDriver/InputState.h>
  59. #include <LibWeb/WebDriver/Properties.h>
  60. #include <LibWeb/WebDriver/Screenshot.h>
  61. #include <WebContent/WebDriverConnection.h>
  62. namespace WebContent {
  63. // https://w3c.github.io/webdriver/#dfn-serialized-cookie
  64. static JsonValue serialize_cookie(Web::Cookie::Cookie const& cookie)
  65. {
  66. JsonObject serialized_cookie;
  67. serialized_cookie.set("name"sv, cookie.name.to_byte_string());
  68. serialized_cookie.set("value"sv, cookie.value.to_byte_string());
  69. serialized_cookie.set("path"sv, cookie.path.to_byte_string());
  70. serialized_cookie.set("domain"sv, cookie.domain.to_byte_string());
  71. serialized_cookie.set("secure"sv, cookie.secure);
  72. serialized_cookie.set("httpOnly"sv, cookie.http_only);
  73. serialized_cookie.set("expiry"sv, cookie.expiry_time.seconds_since_epoch());
  74. serialized_cookie.set("sameSite"sv, Web::Cookie::same_site_to_string(cookie.same_site));
  75. return serialized_cookie;
  76. }
  77. static JsonValue serialize_rect(Gfx::IntRect const& rect)
  78. {
  79. JsonObject serialized_rect = {};
  80. serialized_rect.set("x", rect.x());
  81. serialized_rect.set("y", rect.y());
  82. serialized_rect.set("width", rect.width());
  83. serialized_rect.set("height", rect.height());
  84. return serialized_rect;
  85. }
  86. static Gfx::IntRect compute_window_rect(Web::Page const& page)
  87. {
  88. return {
  89. page.window_position().x(),
  90. page.window_position().y(),
  91. page.window_size().width(),
  92. page.window_size().height()
  93. };
  94. }
  95. // https://w3c.github.io/webdriver/#dfn-no-longer-open
  96. static ErrorOr<void, Web::WebDriver::Error> ensure_browsing_context_is_open(JS::GCPtr<Web::HTML::BrowsingContext> browsing_context)
  97. {
  98. // A browsing context is said to be no longer open if its navigable has been destroyed.
  99. if (!browsing_context || browsing_context->has_navigable_been_destroyed())
  100. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found"sv);
  101. return {};
  102. }
  103. // https://w3c.github.io/webdriver/#dfn-scrolls-into-view
  104. static void scroll_element_into_view(Web::DOM::Element& element)
  105. {
  106. // 1. Let options be the following ScrollIntoViewOptions:
  107. Web::DOM::ScrollIntoViewOptions options {};
  108. // Logical scroll position "block"
  109. // "end"
  110. options.block = Web::Bindings::ScrollLogicalPosition::End;
  111. // Logical scroll position "inline"
  112. // "nearest"
  113. options.inline_ = Web::Bindings::ScrollLogicalPosition::Nearest;
  114. // 2. Run Function.[[Call]](scrollIntoView, options) with element as the this value.
  115. (void)element.scroll_into_view(options);
  116. }
  117. // https://w3c.github.io/webdriver/#dfn-container
  118. static Optional<Web::DOM::Element&> container_for_element(Web::DOM::Element& element)
  119. {
  120. auto first_element_reached_by_traversing_the_tree_in_reverse_order = [](Web::DOM::Element& element, auto filter) -> Optional<Web::DOM::Element&> {
  121. for (auto* current = element.previous_element_in_pre_order(); current; current = current->previous_element_in_pre_order()) {
  122. if (filter(*current))
  123. return *current;
  124. }
  125. return {};
  126. };
  127. // An element’s container is:
  128. // -> option element in a valid element context
  129. // -> optgroup element in a valid element context
  130. // FIXME: Determine if the element is in a valid element context. (https://html.spec.whatwg.org/#concept-element-contexts)
  131. if (is<Web::HTML::HTMLOptionElement>(element) || is<Web::HTML::HTMLOptGroupElement>(element)) {
  132. // The element’s element context, which is determined by:
  133. // 1. Let datalist parent be the first datalist element reached by traversing the tree in reverse order from element, or undefined if the root of the tree is reached.
  134. auto datalist_parent = first_element_reached_by_traversing_the_tree_in_reverse_order(element, [](auto& node) { return is<Web::HTML::HTMLDataListElement>(node); });
  135. // 2. Let select parent be the first select element reached by traversing the tree in reverse order from element, or undefined if the root of the tree is reached.
  136. auto select_parent = first_element_reached_by_traversing_the_tree_in_reverse_order(element, [](auto& node) { return is<Web::HTML::HTMLSelectElement>(node); });
  137. // 3. If datalist parent is undefined, the element context is select parent. Otherwise, the element context is datalist parent.
  138. if (!datalist_parent.has_value())
  139. return select_parent;
  140. return datalist_parent;
  141. }
  142. // -> option element in an invalid element context
  143. else if (is<Web::HTML::HTMLOptionElement>(element)) {
  144. // The element does not have a container.
  145. return {};
  146. }
  147. // -> Otherwise
  148. else {
  149. // The container is the element itself.
  150. return element;
  151. }
  152. }
  153. template<typename T>
  154. static bool fire_an_event(FlyString name, Optional<Web::DOM::Element&> target)
  155. {
  156. // FIXME: This is supposed to call the https://dom.spec.whatwg.org/#concept-event-fire DOM algorithm,
  157. // but that doesn't seem to be implemented elsewhere. So, we'll ad-hack it for now. :^)
  158. if (!target.has_value())
  159. return false;
  160. auto event = T::create(target->realm(), name);
  161. return target->dispatch_event(event);
  162. }
  163. ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(Web::PageClient& page_client, ByteString const& webdriver_ipc_path)
  164. {
  165. static_assert(IsSame<IPC::Transport, IPC::TransportSocket>, "Need to handle other IPC transports here");
  166. dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path);
  167. auto socket = TRY(Core::LocalSocket::connect(webdriver_ipc_path));
  168. // Allow pop-ups, or otherwise /window/new won't be able to open a new tab.
  169. page_client.page().set_should_block_pop_ups(false);
  170. dbgln_if(WEBDRIVER_DEBUG, "Connected to WebDriver");
  171. return adopt_nonnull_ref_or_enomem(new (nothrow) WebDriverConnection(IPC::Transport(move(socket)), page_client));
  172. }
  173. WebDriverConnection::WebDriverConnection(IPC::Transport transport, Web::PageClient& page_client)
  174. : IPC::ConnectionToServer<WebDriverClientEndpoint, WebDriverServerEndpoint>(*this, move(transport))
  175. {
  176. set_current_top_level_browsing_context(page_client.page().top_level_browsing_context());
  177. }
  178. void WebDriverConnection::visit_edges(JS::Cell::Visitor& visitor)
  179. {
  180. visitor.visit(m_current_browsing_context);
  181. visitor.visit(m_current_parent_browsing_context);
  182. visitor.visit(m_current_top_level_browsing_context);
  183. visitor.visit(m_element_locator);
  184. visitor.visit(m_action_executor);
  185. visitor.visit(m_document_observer);
  186. visitor.visit(m_navigation_observer);
  187. visitor.visit(m_navigation_timer);
  188. }
  189. // https://w3c.github.io/webdriver/#dfn-close-the-session
  190. void WebDriverConnection::close_session()
  191. {
  192. // 1. Set the webdriver-active flag to false.
  193. set_is_webdriver_active(false);
  194. // 2. An endpoint node must close any top-level browsing contexts associated with the session, without prompting to unload.
  195. if (auto browsing_context = current_top_level_browsing_context())
  196. browsing_context->top_level_traversable()->close_top_level_traversable();
  197. }
  198. void WebDriverConnection::set_page_load_strategy(Web::WebDriver::PageLoadStrategy const& page_load_strategy)
  199. {
  200. m_page_load_strategy = page_load_strategy;
  201. }
  202. void WebDriverConnection::set_unhandled_prompt_behavior(Web::WebDriver::UnhandledPromptBehavior const& unhandled_prompt_behavior)
  203. {
  204. m_unhandled_prompt_behavior = unhandled_prompt_behavior;
  205. }
  206. void WebDriverConnection::set_strict_file_interactability(bool strict_file_interactability)
  207. {
  208. m_strict_file_interactability = strict_file_interactability;
  209. }
  210. void WebDriverConnection::set_is_webdriver_active(bool is_webdriver_active)
  211. {
  212. current_browsing_context().page().set_is_webdriver_active(is_webdriver_active);
  213. }
  214. // 9.1 Get Timeouts, https://w3c.github.io/webdriver/#dfn-get-timeouts
  215. Messages::WebDriverClient::GetTimeoutsResponse WebDriverConnection::get_timeouts()
  216. {
  217. // 1. Let timeouts be the timeouts object for session’s timeouts configuration
  218. auto timeouts = Web::WebDriver::timeouts_object(m_timeouts_configuration);
  219. // 2. Return success with data timeouts.
  220. return timeouts;
  221. }
  222. // 9.2 Set Timeouts, https://w3c.github.io/webdriver/#dfn-set-timeouts
  223. Messages::WebDriverClient::SetTimeoutsResponse WebDriverConnection::set_timeouts(JsonValue const& payload)
  224. {
  225. // FIXME: Spec issue: As written, the spec replaces the timeouts configuration with the newly provided values. But
  226. // all other implementations update the existing configuration with any new values instead. WPT relies on
  227. // this behavior, and sends us one timeout value at time.
  228. // https://github.com/w3c/webdriver/issues/1596
  229. // 1. Let timeouts be the result of trying to JSON deserialize as a timeouts configuration the request’s parameters.
  230. TRY(Web::WebDriver::json_deserialize_as_a_timeouts_configuration_into(payload, m_timeouts_configuration));
  231. // 2. Make the session timeouts the new timeouts.
  232. // 3. Return success with data null.
  233. return JsonValue {};
  234. }
  235. // 10.1 Navigate To, https://w3c.github.io/webdriver/#navigate-to
  236. Messages::WebDriverClient::NavigateToResponse WebDriverConnection::navigate_to(JsonValue const& payload)
  237. {
  238. dbgln_if(WEBDRIVER_DEBUG, "WebDriverConnection::navigate_to {}", payload);
  239. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  240. TRY(ensure_current_top_level_browsing_context_is_open());
  241. // 2. Let url be the result of getting the property url from the parameters argument.
  242. if (!payload.is_object() || !payload.as_object().has_string("url"sv))
  243. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload doesn't have a string `url`"sv);
  244. URL::URL url(payload.as_object().get_byte_string("url"sv).value());
  245. // 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.
  246. // 4. Handle any user prompts and return its value if it is an error.
  247. TRY(handle_any_user_prompts());
  248. // 5. Let current URL be the current top-level browsing context’s active document’s URL.
  249. auto const& current_url = current_top_level_browsing_context()->active_document()->url();
  250. // FIXME: 6. If current URL and url do not have the same absolute URL:
  251. // 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.
  252. // 7. Navigate the current top-level browsing context to url.
  253. current_top_level_browsing_context()->page().load(url);
  254. auto navigation_complete = JS::create_heap_function(current_top_level_browsing_context()->heap(), [this](Web::WebDriver::Response result) {
  255. // 9. Set the current browsing context with the current top-level browsing context.
  256. set_current_browsing_context(*current_top_level_browsing_context());
  257. // 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.
  258. async_navigation_complete(move(result));
  259. });
  260. // 8. If url is special except for file and current URL and URL do not have the same absolute URL:
  261. // AD-HOC: We wait for the navigation to complete regardless of whether the current URL differs from the provided
  262. // URL. Even if they're the same, the navigation queues a tasks that we must await, otherwise subsequent
  263. // endpoint invocations will attempt to operate on the wrong page.
  264. if (url.is_special() && url.scheme() != "file"sv) {
  265. // a. Try to wait for navigation to complete.
  266. wait_for_navigation_to_complete(navigation_complete);
  267. // FIXME: b. Try to run the post-navigation checks.
  268. } else {
  269. navigation_complete->function()(JsonValue {});
  270. }
  271. // 11. Return success with data null.
  272. return JsonValue {};
  273. }
  274. // 10.2 Get Current URL, https://w3c.github.io/webdriver/#get-current-url
  275. Messages::WebDriverClient::GetCurrentUrlResponse WebDriverConnection::get_current_url()
  276. {
  277. dbgln_if(WEBDRIVER_DEBUG, "WebDriverConnection::get_current_url");
  278. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  279. TRY(ensure_current_top_level_browsing_context_is_open());
  280. // 2. Handle any user prompts and return its value if it is an error.
  281. TRY(handle_any_user_prompts());
  282. // 3. Let url be the serialization of the current top-level browsing context’s active document’s document URL.
  283. auto url = current_top_level_browsing_context()->active_document()->url().to_byte_string();
  284. // 4. Return success with data url.
  285. return url;
  286. }
  287. // 10.3 Back, https://w3c.github.io/webdriver/#dfn-back
  288. Messages::WebDriverClient::BackResponse WebDriverConnection::back()
  289. {
  290. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  291. TRY(ensure_current_top_level_browsing_context_is_open());
  292. // 2. Handle any user prompts and return its value if it is an error.
  293. TRY(handle_any_user_prompts());
  294. // 3. Traverse the history by a delta –1 for the current browsing context.
  295. current_browsing_context().page().client().page_did_request_navigate_back();
  296. // 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.
  297. // 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.
  298. // 6. Return success with data null.
  299. return JsonValue {};
  300. }
  301. // 10.4 Forward, https://w3c.github.io/webdriver/#dfn-forward
  302. Messages::WebDriverClient::ForwardResponse WebDriverConnection::forward()
  303. {
  304. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  305. TRY(ensure_current_top_level_browsing_context_is_open());
  306. // 2. Handle any user prompts and return its value if it is an error.
  307. TRY(handle_any_user_prompts());
  308. // 3. Traverse the history by a delta 1 for the current browsing context.
  309. current_browsing_context().page().client().page_did_request_navigate_forward();
  310. // 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.
  311. // 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.
  312. // 6. Return success with data null.
  313. return JsonValue {};
  314. }
  315. // 10.5 Refresh, https://w3c.github.io/webdriver/#dfn-refresh
  316. Messages::WebDriverClient::RefreshResponse WebDriverConnection::refresh()
  317. {
  318. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  319. TRY(ensure_current_top_level_browsing_context_is_open());
  320. // 2. Handle any user prompts and return its value if it is an error.
  321. TRY(handle_any_user_prompts());
  322. // 3. Initiate an overridden reload of the current top-level browsing context’s active document.
  323. current_top_level_browsing_context()->page().client().page_did_request_refresh();
  324. // FIXME: 4. If url is special except for file:
  325. // FIXME: 1. Try to wait for navigation to complete.
  326. // FIXME: 2. Try to run the post-navigation checks.
  327. // 5. Set the current browsing context with current top-level browsing context.
  328. set_current_browsing_context(*current_top_level_browsing_context());
  329. // 6. Return success with data null.
  330. return JsonValue {};
  331. }
  332. // 10.6 Get Title, https://w3c.github.io/webdriver/#dfn-get-title
  333. Messages::WebDriverClient::GetTitleResponse WebDriverConnection::get_title()
  334. {
  335. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  336. TRY(ensure_current_top_level_browsing_context_is_open());
  337. // 2. Handle any user prompts and return its value if it is an error.
  338. TRY(handle_any_user_prompts());
  339. // 3. Let title be the initial value of the title IDL attribute of the current top-level browsing context's active document.
  340. auto title = current_top_level_browsing_context()->active_document()->title();
  341. // 4. Return success with data title.
  342. return title.to_byte_string();
  343. }
  344. // 11.1 Get Window Handle, https://w3c.github.io/webdriver/#get-window-handle
  345. Messages::WebDriverClient::GetWindowHandleResponse WebDriverConnection::get_window_handle()
  346. {
  347. // 1. If session's current top-level browsing context is no longer open, return error with error code no such window.
  348. TRY(ensure_current_top_level_browsing_context_is_open());
  349. // 2. Return success with data being the window handle associated with session's current top-level browsing context.
  350. return JsonValue { current_top_level_browsing_context()->top_level_traversable()->window_handle() };
  351. }
  352. // 11.2 Close Window, https://w3c.github.io/webdriver/#dfn-close-window
  353. Messages::WebDriverClient::CloseWindowResponse WebDriverConnection::close_window()
  354. {
  355. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  356. TRY(ensure_current_top_level_browsing_context_is_open());
  357. // 2. Handle any user prompts and return its value if it is an error.
  358. TRY(handle_any_user_prompts());
  359. // 3. Close the current top-level browsing context.
  360. current_top_level_browsing_context()->top_level_traversable()->close_top_level_traversable();
  361. return JsonValue {};
  362. }
  363. // 11.3 Switch to Window, https://w3c.github.io/webdriver/#dfn-switch-to-window
  364. Messages::WebDriverClient::SwitchToWindowResponse WebDriverConnection::switch_to_window(String const& handle)
  365. {
  366. // 4. If handle is equal to the associated window handle for some top-level browsing context in the
  367. // current session, let context be the that browsing context, and set the current top-level
  368. // browsing context with context.
  369. // Otherwise, return error with error code no such window.
  370. bool found_matching_context = false;
  371. for (auto* navigable : Web::HTML::all_navigables()) {
  372. auto traversable = navigable->top_level_traversable();
  373. if (!traversable || !traversable->active_browsing_context())
  374. continue;
  375. if (handle == traversable->window_handle()) {
  376. set_current_top_level_browsing_context(*traversable->active_browsing_context());
  377. found_matching_context = true;
  378. break;
  379. }
  380. }
  381. if (!found_matching_context)
  382. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found");
  383. // 5. Update any implementation-specific state that would result from the user selecting the current
  384. // browsing context for interaction, without altering OS-level focus.
  385. current_browsing_context().page().client().page_did_request_activate_tab();
  386. return JsonValue {};
  387. }
  388. // 11.5 New Window, https://w3c.github.io/webdriver/#dfn-new-window
  389. Messages::WebDriverClient::NewWindowResponse WebDriverConnection::new_window(JsonValue const& payload)
  390. {
  391. // 1. If the implementation does not support creating new top-level browsing contexts, return error with error code unsupported operation.
  392. // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
  393. TRY(ensure_current_top_level_browsing_context_is_open());
  394. // 3. Handle any user prompts and return its value if it is an error.
  395. TRY(handle_any_user_prompts());
  396. // 4. Let type hint be the result of getting the property "type" from the parameters argument.
  397. if (!payload.is_object())
  398. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
  399. // FIXME: Actually use this value to decide between an OS window or tab.
  400. auto type_hint = payload.as_object().get("type"sv);
  401. if (type_hint.has_value() && !type_hint->is_null() && !type_hint->is_string())
  402. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload property `type` is not null or a string"sv);
  403. // 5. Create a new top-level browsing context by running the window open steps with url set to "about:blank",
  404. // target set to the empty string, and features set to "noopener" and the user agent configured to create a new
  405. // browsing context. This must be done without invoking the focusing steps for the created browsing context. If
  406. // type hint has the value "tab", and the implementation supports multiple browsing context in the same OS
  407. // window, the new browsing context should share an OS window with the current browsing context. If type hint
  408. // is "window", and the implementation supports multiple browsing contexts in separate OS windows, the
  409. // created browsing context should be in a new OS window. In all other cases the details of how the browsing
  410. // context is presented to the user are implementation defined.
  411. auto* active_window = current_browsing_context().active_window();
  412. VERIFY(active_window);
  413. Web::HTML::TemporaryExecutionContext execution_context { active_window->document()->realm() };
  414. auto [target_navigable, no_opener, window_type] = MUST(active_window->window_open_steps_internal("about:blank"sv, ""sv, "noopener"sv));
  415. // 6. Let handle be the associated window handle of the newly created window.
  416. auto handle = target_navigable->traversable_navigable()->window_handle();
  417. // 7. Let type be "tab" if the newly created window shares an OS-level window with the current browsing context, or "window" otherwise.
  418. auto type = "tab"sv;
  419. // 8. Let result be a new JSON Object initialized with:
  420. JsonObject result;
  421. result.set("handle"sv, JsonValue { handle });
  422. result.set("type"sv, JsonValue { type });
  423. // 9. Return success with data result.
  424. return result;
  425. }
  426. // 11.6 Switch To Frame, https://w3c.github.io/webdriver/#dfn-switch-to-frame
  427. Messages::WebDriverClient::SwitchToFrameResponse WebDriverConnection::switch_to_frame(JsonValue const& payload)
  428. {
  429. // 1. Let id be the result of getting the property "id" from parameters.
  430. if (!payload.is_object() || !payload.as_object().has("id"sv))
  431. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload doesn't have property `id`"sv);
  432. auto id = payload.as_object().get("id"sv).release_value();
  433. // 2. If id is not null, a Number object, or an Object that represents a web element, return error with error code invalid argument.
  434. if (!id.is_null() && !id.is_number() && !Web::WebDriver::represents_a_web_element(id))
  435. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload property `id` is not null, a number, or a web element"sv);
  436. // 3. Run the substeps of the first matching condition:
  437. // -> id is null
  438. if (id.is_null()) {
  439. // 1. If session's current top-level browsing context is no longer open, return error with error code no such window.
  440. TRY(ensure_current_top_level_browsing_context_is_open());
  441. // 2. Try to handle any user prompts with session.
  442. TRY(handle_any_user_prompts());
  443. // 3. Set the current browsing context with session and session's current top-level browsing context.
  444. set_current_browsing_context(*current_top_level_browsing_context());
  445. }
  446. // -> id is a Number object
  447. else if (id.is_number()) {
  448. // FIXME: 1. If id is less than 0 or greater than 216 – 1, return error with error code invalid argument.
  449. // FIXME: 2. If session's current browsing context is no longer open, return error with error code no such window.
  450. // FIXME: 3. Try to handle any user prompts with session.
  451. // FIXME: 4. Let window be the associated window of session's current browsing context's active document.
  452. // FIXME: 5. If id is not a supported property index of window, return error with error code no such frame.
  453. // FIXME: 6. Let child window be the WindowProxy object obtained by calling window.[[GetOwnProperty]] (id).
  454. // FIXME: 7. Set the current browsing context with session and child window's browsing context.
  455. dbgln("FIXME: WebDriverConnection::switch_to_frame(id={})", id);
  456. }
  457. // -> id represents a web element
  458. else if (id.is_object()) {
  459. auto element_id = Web::WebDriver::extract_web_element_reference(id.as_object());
  460. // 1. If session's current browsing context is no longer open, return error with error code no such window.
  461. TRY(ensure_current_browsing_context_is_open());
  462. // 2. Try to handle any user prompts with session.
  463. TRY(handle_any_user_prompts());
  464. // 3. Let element be the result of trying to get a known element with session and id.
  465. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  466. // 4. If element is not a frame or iframe element, return error with error code no such frame.
  467. bool is_frame = is<Web::HTML::HTMLFrameElement>(*element);
  468. bool is_iframe = is<Web::HTML::HTMLIFrameElement>(*element);
  469. if (!is_frame && !is_iframe)
  470. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchFrame, "element is not a frame"sv);
  471. // 5. Set the current browsing context with session and element's content navigable's active browsing context.
  472. if (is_frame) {
  473. // FIXME: Should HTMLFrameElement also be a NavigableContainer?
  474. set_current_browsing_context(*element->navigable()->active_browsing_context());
  475. } else {
  476. auto& navigable_container = static_cast<Web::HTML::NavigableContainer&>(*element);
  477. set_current_browsing_context(*navigable_container.content_navigable()->active_browsing_context());
  478. }
  479. }
  480. // FIXME: 4. Update any implementation-specific state that would result from the user selecting session's current browsing context for interaction, without altering OS-level focus.
  481. // 5. Return success with data null
  482. return JsonValue {};
  483. }
  484. // 11.7 Switch To Parent Frame, https://w3c.github.io/webdriver/#dfn-switch-to-parent-frame
  485. Messages::WebDriverClient::SwitchToParentFrameResponse WebDriverConnection::switch_to_parent_frame(JsonValue const&)
  486. {
  487. // 1. If session's current browsing context is already the top-level browsing context:
  488. if (&current_browsing_context() == current_top_level_browsing_context()) {
  489. // 1. If session's current browsing context is no longer open, return error with error code no such window.
  490. TRY(ensure_current_browsing_context_is_open());
  491. // 2. Return success with data null.
  492. return JsonValue {};
  493. }
  494. auto parent_browsing_context = current_parent_browsing_context();
  495. // 2. If session's current parent browsing context is no longer open, return error with error code no such window.
  496. TRY(ensure_browsing_context_is_open(parent_browsing_context));
  497. // 3. Try to handle any user prompts with session.
  498. TRY(handle_any_user_prompts());
  499. // 4. If session's current parent browsing context is not null, set the current browsing context with session and
  500. // current parent browsing context.
  501. if (parent_browsing_context)
  502. set_current_browsing_context(*current_parent_browsing_context());
  503. // FIXME: 5. Update any implementation-specific state that would result from the user selecting session's current browsing context for interaction, without altering OS-level focus.
  504. // 6. Return success with data null.
  505. return JsonValue {};
  506. }
  507. // 11.8.1 Get Window Rect, https://w3c.github.io/webdriver/#dfn-get-window-rect
  508. Messages::WebDriverClient::GetWindowRectResponse WebDriverConnection::get_window_rect()
  509. {
  510. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  511. TRY(ensure_current_top_level_browsing_context_is_open());
  512. // 2. Handle any user prompts and return its value if it is an error.
  513. TRY(handle_any_user_prompts());
  514. // 3. Return success with data set to the WindowRect object for the current top-level browsing context.
  515. return serialize_rect(compute_window_rect(current_top_level_browsing_context()->page()));
  516. }
  517. // 11.8.2 Set Window Rect, https://w3c.github.io/webdriver/#dfn-set-window-rect
  518. Messages::WebDriverClient::SetWindowRectResponse WebDriverConnection::set_window_rect(JsonValue const& payload)
  519. {
  520. if (!payload.is_object())
  521. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object");
  522. auto const& properties = payload.as_object();
  523. auto resolve_property = [](auto name, auto const& property, double min, double max) -> ErrorOr<Optional<double>, Web::WebDriver::Error> {
  524. if (property.is_null())
  525. return OptionalNone {};
  526. auto value = property.get_double_with_precision_loss();
  527. if (!value.has_value())
  528. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not a Number", name));
  529. if (*value < min)
  530. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' value {} exceeds the minimum allowed value {}", name, *value, min));
  531. if (*value > max)
  532. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' value {} exceeds the maximum allowed value {}", name, *value, max));
  533. return value;
  534. };
  535. // 1. Let width be the result of getting a property named width from the parameters argument, else let it be null.
  536. auto width_property = properties.get("width"sv).value_or(JsonValue());
  537. // 2. Let height be the result of getting a property named height from the parameters argument, else let it be null.
  538. auto height_property = properties.get("height"sv).value_or(JsonValue());
  539. // 3. Let x be the result of getting a property named x from the parameters argument, else let it be null.
  540. auto x_property = properties.get("x"sv).value_or(JsonValue());
  541. // 4. Let y be the result of getting a property named y from the parameters argument, else let it be null.
  542. auto y_property = properties.get("y"sv).value_or(JsonValue());
  543. // 5. If width or height is neither null nor a Number from 0 to 2^31 − 1, return error with error code invalid argument.
  544. auto width = TRY(resolve_property("width"sv, width_property, 0, NumericLimits<i32>::max()));
  545. auto height = TRY(resolve_property("height"sv, height_property, 0, NumericLimits<i32>::max()));
  546. // 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.
  547. auto x = TRY(resolve_property("x"sv, x_property, NumericLimits<i32>::min(), NumericLimits<i32>::max()));
  548. auto y = TRY(resolve_property("y"sv, y_property, NumericLimits<i32>::min(), NumericLimits<i32>::max()));
  549. // 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.
  550. // 8. If the current top-level browsing context is no longer open, return error with error code no such window.
  551. TRY(ensure_current_top_level_browsing_context_is_open());
  552. // 9. Handle any user prompts and return its value if it is an error.
  553. TRY(handle_any_user_prompts());
  554. // FIXME: 10. Fully exit fullscreen.
  555. // 11. Restore the window.
  556. restore_the_window(JS::create_heap_function(current_top_level_browsing_context()->heap(), [this, x, y, width, height]() {
  557. auto& page = current_top_level_browsing_context()->page();
  558. // 11. If width and height are not null:
  559. if (width.has_value() && height.has_value()) {
  560. // 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.
  561. // 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.
  562. page.client().page_did_request_resize_window({ *width, *height });
  563. ++m_pending_window_rect_requests;
  564. }
  565. // 12. If x and y are not null:
  566. if (x.has_value() && y.has_value()) {
  567. // 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.
  568. page.client().page_did_request_reposition_window({ *x, *y });
  569. ++m_pending_window_rect_requests;
  570. }
  571. if (m_pending_window_rect_requests == 0)
  572. async_window_rect_updated(serialize_rect(compute_window_rect(page)));
  573. }));
  574. // 14. Return success with data set to the WindowRect object for the current top-level browsing context.
  575. return JsonValue {};
  576. }
  577. // 11.8.3 Maximize Window, https://w3c.github.io/webdriver/#dfn-maximize-window
  578. Messages::WebDriverClient::MaximizeWindowResponse WebDriverConnection::maximize_window()
  579. {
  580. // 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.
  581. // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
  582. TRY(ensure_current_top_level_browsing_context_is_open());
  583. // 3. Handle any user prompts and return its value if it is an error.
  584. TRY(handle_any_user_prompts());
  585. // FIXME: 4. Fully exit fullscreen.
  586. // 5. Restore the window.
  587. restore_the_window(JS::create_heap_function(current_top_level_browsing_context()->heap(), [this]() {
  588. // 6. Maximize the window of the current top-level browsing context.
  589. maximize_the_window();
  590. }));
  591. // 7. Return success with data set to the WindowRect object for the current top-level browsing context.
  592. return JsonValue {};
  593. }
  594. // 11.8.4 Minimize Window, https://w3c.github.io/webdriver/#minimize-window
  595. Messages::WebDriverClient::MinimizeWindowResponse WebDriverConnection::minimize_window()
  596. {
  597. // 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.
  598. // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
  599. TRY(ensure_current_top_level_browsing_context_is_open());
  600. // 3. Handle any user prompts and return its value if it is an error.
  601. TRY(handle_any_user_prompts());
  602. // FIXME: 4. Fully exit fullscreen.
  603. // 5. Iconify the window.
  604. iconify_the_window(JS::create_heap_function(current_top_level_browsing_context()->heap(), [this]() {
  605. auto& page = current_top_level_browsing_context()->page();
  606. async_window_rect_updated(serialize_rect(compute_window_rect(page)));
  607. }));
  608. // 6. Return success with data set to the WindowRect object for the current top-level browsing context.
  609. return JsonValue {};
  610. }
  611. // 11.8.5 Fullscreen Window, https://w3c.github.io/webdriver/#dfn-fullscreen-window
  612. Messages::WebDriverClient::FullscreenWindowResponse WebDriverConnection::fullscreen_window()
  613. {
  614. // 1. If the remote end does not support fullscreen return error with error code unsupported operation.
  615. // 2. If the current top-level browsing context is no longer open, return error with error code no such window.
  616. TRY(ensure_current_top_level_browsing_context_is_open());
  617. // 3. Handle any user prompts and return its value if it is an error.
  618. TRY(handle_any_user_prompts());
  619. // 4. Restore the window.
  620. restore_the_window(JS::create_heap_function(current_top_level_browsing_context()->heap(), [this]() {
  621. // 5. FIXME: Call fullscreen an element with the current top-level browsing context’s active document’s document element.
  622. // As described in https://fullscreen.spec.whatwg.org/#fullscreen-an-element
  623. // NOTE: What we do here is basically `requestFullscreen(options)` with options["navigationUI"]="show"
  624. current_top_level_browsing_context()->page().client().page_did_request_fullscreen_window();
  625. ++m_pending_window_rect_requests;
  626. }));
  627. // 6. Return success with data set to the WindowRect object for the current top-level browsing context.
  628. return JsonValue {};
  629. }
  630. // Extension Consume User Activation, https://html.spec.whatwg.org/multipage/interaction.html#user-activation-user-agent-automation
  631. Messages::WebDriverClient::ConsumeUserActivationResponse WebDriverConnection::consume_user_activation()
  632. {
  633. // FIXME: This should probably be in the spec steps
  634. // If the current top-level browsing context is no longer open, return error with error code no such window.
  635. TRY(ensure_current_top_level_browsing_context_is_open());
  636. // 1. Let window be current browsing context's active window.
  637. auto* window = current_browsing_context().active_window();
  638. // 2. Let consume be true if window has transient activation; otherwise false.
  639. bool consume = window->has_transient_activation();
  640. // 3. If consume is true, then consume user activation of window.
  641. if (consume)
  642. window->consume_user_activation();
  643. // 4. Return success with data consume.
  644. return consume;
  645. }
  646. static Web::WebDriver::Response extract_first_element(Web::WebDriver::Response result)
  647. {
  648. auto array = TRY(result);
  649. VERIFY(array.is_array());
  650. if (!array.as_array().is_empty())
  651. return array.as_array().take(0);
  652. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv);
  653. }
  654. // 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element
  655. Messages::WebDriverClient::FindElementResponse WebDriverConnection::find_element(JsonValue const& payload)
  656. {
  657. // 1. Let location strategy be the result of getting a property named "using" from parameters.
  658. auto location_strategy_string = TRY(Web::WebDriver::get_property(payload, "using"sv));
  659. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  660. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error
  661. // code invalid argument.
  662. if (!location_strategy.has_value())
  663. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  664. // 3. Let selector be the result of getting a property named "value" from parameters.
  665. // 4. If selector is undefined, return error with error code invalid argument.
  666. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv));
  667. // 5. If session's current browsing context is no longer open, return error with error code no such window.
  668. TRY(ensure_current_browsing_context_is_open());
  669. // 6. Try to handle any user prompts with session.
  670. TRY(handle_any_user_prompts());
  671. auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this]() -> ErrorOr<JS::NonnullGCPtr<Web::DOM::ParentNode>, Web::WebDriver::Error> {
  672. // 7. Let start node be session's current browsing context's document element.
  673. auto* start_node = current_browsing_context().active_document();
  674. // 8. If start node is null, return error with error code no such element.
  675. if (!start_node)
  676. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
  677. return *start_node;
  678. });
  679. // 9. Let result be the result of trying to Find with session, start node, location strategy, and selector.
  680. find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
  681. // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
  682. async_find_elements_complete(extract_first_element(move(result)));
  683. }));
  684. return JsonValue {};
  685. }
  686. // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements
  687. Messages::WebDriverClient::FindElementsResponse WebDriverConnection::find_elements(JsonValue const& payload)
  688. {
  689. // 1. Let location strategy be the result of getting a property named "using" from parameters.
  690. auto location_strategy_string = TRY(Web::WebDriver::get_property(payload, "using"sv));
  691. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  692. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error
  693. // code invalid argument.
  694. if (!location_strategy.has_value())
  695. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  696. // 3. Let selector be the result of getting a property named "value" from parameters.
  697. // 4. If selector is undefined, return error with error code invalid argument.
  698. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv));
  699. // 5. If session's current browsing context is no longer open, return error with error code no such window.
  700. TRY(ensure_current_browsing_context_is_open());
  701. // 6. Try to handle any user prompts with session.
  702. TRY(handle_any_user_prompts());
  703. auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this]() -> ErrorOr<JS::NonnullGCPtr<Web::DOM::ParentNode>, Web::WebDriver::Error> {
  704. // 7. Let start node be session's current browsing context's document element.
  705. auto* start_node = current_browsing_context().active_document();
  706. // 8. If start node is null, return error with error code no such element.
  707. if (!start_node)
  708. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv);
  709. return *start_node;
  710. });
  711. // 9. Return the result of trying to Find with session, start node, location strategy, and selector.
  712. find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
  713. async_find_elements_complete(move(result));
  714. }));
  715. return JsonValue {};
  716. }
  717. // 12.3.4 Find Element From Element, https://w3c.github.io/webdriver/#dfn-find-element-from-element
  718. Messages::WebDriverClient::FindElementFromElementResponse WebDriverConnection::find_element_from_element(JsonValue const& payload, String const& element_id)
  719. {
  720. // 1. Let location strategy be the result of getting a property named "using" from parameters.
  721. auto location_strategy_string = TRY(Web::WebDriver::get_property(payload, "using"sv));
  722. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  723. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  724. if (!location_strategy.has_value())
  725. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  726. // 3. Let selector be the result of getting a property named "value" from parameters.
  727. // 4. If selector is undefined, return error with error code invalid argument.
  728. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv));
  729. // 5. If session's current browsing context is no longer open, return error with error code no such window.
  730. TRY(ensure_current_browsing_context_is_open());
  731. // 6. Try to handle any user prompts with session.
  732. TRY(handle_any_user_prompts());
  733. auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this, element_id]() -> ErrorOr<JS::NonnullGCPtr<Web::DOM::ParentNode>, Web::WebDriver::Error> {
  734. // 7. Let start node be the result of trying to get a known element with session and URL variables["element id"].
  735. return Web::WebDriver::get_known_element(current_browsing_context(), element_id);
  736. });
  737. // 8. Let result be the value of trying to Find with session, start node, location strategy, and selector.
  738. find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
  739. // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
  740. async_find_elements_complete(extract_first_element(move(result)));
  741. }));
  742. return JsonValue {};
  743. }
  744. // 12.3.5 Find Elements From Element, https://w3c.github.io/webdriver/#dfn-find-elements-from-element
  745. Messages::WebDriverClient::FindElementsFromElementResponse WebDriverConnection::find_elements_from_element(JsonValue const& payload, String const& element_id)
  746. {
  747. // 1. Let location strategy be the result of getting a property named "using" from parameters.
  748. auto location_strategy_string = TRY(Web::WebDriver::get_property(payload, "using"sv));
  749. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  750. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  751. if (!location_strategy.has_value())
  752. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  753. // 3. Let selector be the result of getting a property named "value" from parameters.
  754. // 4. If selector is undefined, return error with error code invalid argument.
  755. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv));
  756. // 5. If session's current browsing context is no longer open, return error with error code no such window.
  757. TRY(ensure_current_browsing_context_is_open());
  758. // 6. Try to handle any user prompts with session.
  759. TRY(handle_any_user_prompts());
  760. auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this, element_id]() -> ErrorOr<JS::NonnullGCPtr<Web::DOM::ParentNode>, Web::WebDriver::Error> {
  761. // 7. Let start node be the result of trying to get a known element with session and URL variables["element id"].
  762. return Web::WebDriver::get_known_element(current_browsing_context(), element_id);
  763. });
  764. // 8. Return the result of trying to Find with session, start node, location strategy, and selector.
  765. find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
  766. async_find_elements_complete(move(result));
  767. }));
  768. return JsonValue {};
  769. }
  770. // 12.3.6 Find Element From Shadow Root, https://w3c.github.io/webdriver/#find-element-from-shadow-root
  771. Messages::WebDriverClient::FindElementFromShadowRootResponse WebDriverConnection::find_element_from_shadow_root(JsonValue const& payload, String const& shadow_id)
  772. {
  773. // 1. Let location strategy be the result of getting a property called "using".
  774. auto location_strategy_string = TRY(Web::WebDriver::get_property(payload, "using"sv));
  775. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  776. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  777. if (!location_strategy.has_value())
  778. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  779. // 3. Let selector be the result of getting a property called "value".
  780. // 4. If selector is undefined, return error with error code invalid argument.
  781. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv));
  782. // 5. If the ssession's current browsing context is no longer open, return error with error code no such window.
  783. TRY(ensure_current_browsing_context_is_open());
  784. // 6. Handle any user prompts and return its value if it is an error.
  785. TRY(handle_any_user_prompts());
  786. auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this, shadow_id]() -> ErrorOr<JS::NonnullGCPtr<Web::DOM::ParentNode>, Web::WebDriver::Error> {
  787. // 7. Let start node be the result of trying to get a known shadow root with session and URL variables["shadow id"].
  788. return Web::WebDriver::get_known_shadow_root(current_browsing_context(), shadow_id);
  789. });
  790. // 8. Let result be the value of trying to Find with session, start node, location strategy, and selector.
  791. find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
  792. // 9. If result is empty, return error with error code no such element. Otherwise, return the first element of result.
  793. async_find_elements_complete(extract_first_element(move(result)));
  794. }));
  795. return JsonValue {};
  796. }
  797. // 12.3.7 Find Elements From Shadow Root, https://w3c.github.io/webdriver/#find-elements-from-shadow-root
  798. Messages::WebDriverClient::FindElementsFromShadowRootResponse WebDriverConnection::find_elements_from_shadow_root(JsonValue const& payload, String const& shadow_id)
  799. {
  800. // 1. Let location strategy be the result of getting a property called "using".
  801. auto location_strategy_string = TRY(Web::WebDriver::get_property(payload, "using"sv));
  802. auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string);
  803. // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument.
  804. if (!location_strategy.has_value())
  805. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Location strategy '{}' is invalid", location_strategy_string));
  806. // 3. Let selector be the result of getting a property called "value".
  807. // 4. If selector is undefined, return error with error code invalid argument.
  808. auto selector = TRY(Web::WebDriver::get_property(payload, "value"sv));
  809. // 5. If session's current browsing context is no longer open, return error with error code no such window.
  810. TRY(ensure_current_browsing_context_is_open());
  811. // 6. Handle any user prompts and return its value if it is an error.
  812. TRY(handle_any_user_prompts());
  813. auto get_start_node = JS::create_heap_function(current_browsing_context().heap(), [this, shadow_id]() -> ErrorOr<JS::NonnullGCPtr<Web::DOM::ParentNode>, Web::WebDriver::Error> {
  814. // 7. Let start node be the result of trying to get a known shadow root with session and URL variables["shadow id"].
  815. return Web::WebDriver::get_known_shadow_root(current_browsing_context(), shadow_id);
  816. });
  817. // 8. Return the result of trying to Find with session, start node, location strategy, and selector.
  818. find(*location_strategy, move(selector), get_start_node, JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
  819. async_find_elements_complete(move(result));
  820. }));
  821. return JsonValue {};
  822. }
  823. // 12.3.8 Get Active Element, https://w3c.github.io/webdriver/#get-active-element
  824. Messages::WebDriverClient::GetActiveElementResponse WebDriverConnection::get_active_element()
  825. {
  826. // 1. If the current browsing context is no longer open, return error with error code no such window.
  827. TRY(ensure_current_browsing_context_is_open());
  828. // 2. Handle any user prompts and return its value if it is an error.
  829. TRY(handle_any_user_prompts());
  830. // 3. Let active element be the active element of the current browsing context’s document element.
  831. auto* active_element = current_browsing_context().active_document()->active_element();
  832. // 4. If active element is a non-null element, return success with data set to web element reference object for active element.
  833. // Otherwise, return error with error code no such element.
  834. if (active_element)
  835. return ByteString::number(active_element->unique_id().value());
  836. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The current document does not have an active element"sv);
  837. }
  838. // 12.3.9 Get Element Shadow Root, https://w3c.github.io/webdriver/#get-element-shadow-root
  839. Messages::WebDriverClient::GetElementShadowRootResponse WebDriverConnection::get_element_shadow_root(String const& element_id)
  840. {
  841. // 1. If session's current browsing context is no longer open, return error with error code no such window.
  842. TRY(ensure_current_browsing_context_is_open());
  843. // 2. Handle any user prompts and return its value if it is an error.
  844. TRY(handle_any_user_prompts());
  845. // 3. Let element be the result of trying to get a known element with session and URL variables[element id].
  846. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  847. // 4. Let shadow root be element's shadow root.
  848. auto shadow_root = element->shadow_root();
  849. // 5. If shadow root is null, return error with error code no such shadow root.
  850. if (!shadow_root)
  851. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchShadowRoot, ByteString::formatted("Element with ID '{}' does not have a shadow root", element_id));
  852. // 6. Let serialized be the shadow root reference object for session and shadow root.
  853. auto serialized = Web::WebDriver::shadow_root_reference_object(current_browsing_context(), *shadow_root);
  854. // 7. Return success with data serialized.
  855. return serialized;
  856. }
  857. // 12.4.1 Is Element Selected, https://w3c.github.io/webdriver/#dfn-is-element-selected
  858. Messages::WebDriverClient::IsElementSelectedResponse WebDriverConnection::is_element_selected(String const& element_id)
  859. {
  860. // 1. If the current browsing context is no longer open, return error with error code no such window.
  861. TRY(ensure_current_browsing_context_is_open());
  862. // 2. Handle any user prompts and return its value if it is an error.
  863. TRY(handle_any_user_prompts());
  864. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  865. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  866. // 4. Let selected be the value corresponding to the first matching statement:
  867. bool selected = false;
  868. // element is an input element with a type attribute in the Checkbox- or Radio Button state
  869. if (is<Web::HTML::HTMLInputElement>(*element)) {
  870. // -> The result of element’s checkedness.
  871. auto& input = static_cast<Web::HTML::HTMLInputElement&>(*element);
  872. using enum Web::HTML::HTMLInputElement::TypeAttributeState;
  873. if (input.type_state() == Checkbox || input.type_state() == RadioButton)
  874. selected = input.checked();
  875. }
  876. // element is an option element
  877. else if (is<Web::HTML::HTMLOptionElement>(*element)) {
  878. // -> The result of element’s selectedness.
  879. selected = static_cast<Web::HTML::HTMLOptionElement&>(*element).selected();
  880. }
  881. // Otherwise
  882. // -> False.
  883. // 5. Return success with data selected.
  884. return selected;
  885. }
  886. // 12.4.2 Get Element Attribute, https://w3c.github.io/webdriver/#dfn-get-element-attribute
  887. Messages::WebDriverClient::GetElementAttributeResponse WebDriverConnection::get_element_attribute(String const& element_id, String const& name)
  888. {
  889. // 1. If the current browsing context is no longer open, return error with error code no such window.
  890. TRY(ensure_current_browsing_context_is_open());
  891. // 2. Handle any user prompts and return its value if it is an error.
  892. TRY(handle_any_user_prompts());
  893. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  894. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  895. // 4. Let result be the result of the first matching condition:
  896. Optional<ByteString> result;
  897. // -> If name is a boolean attribute
  898. if (Web::HTML::is_boolean_attribute(name)) {
  899. // "true" (string) if the element has the attribute, otherwise null.
  900. if (element->has_attribute(name))
  901. result = "true"sv;
  902. }
  903. // -> Otherwise
  904. else {
  905. // The result of getting an attribute by name name.
  906. if (auto attr = element->get_attribute(name); attr.has_value())
  907. result = attr->to_byte_string();
  908. }
  909. // 5. Return success with data result.
  910. if (result.has_value())
  911. return result.release_value();
  912. return JsonValue {};
  913. }
  914. // 12.4.3 Get Element Property, https://w3c.github.io/webdriver/#dfn-get-element-property
  915. Messages::WebDriverClient::GetElementPropertyResponse WebDriverConnection::get_element_property(String const& element_id, String const& name)
  916. {
  917. // 1. If the current browsing context is no longer open, return error with error code no such window.
  918. TRY(ensure_current_browsing_context_is_open());
  919. // 2. Handle any user prompts and return its value if it is an error.
  920. TRY(handle_any_user_prompts());
  921. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  922. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  923. Optional<ByteString> result;
  924. // 4. Let property be the result of calling the Object.[[GetProperty]](name) on element.
  925. Web::HTML::TemporaryExecutionContext execution_context { current_browsing_context().active_document()->realm() };
  926. if (auto property_or_error = element->get(name.to_byte_string()); !property_or_error.is_throw_completion()) {
  927. auto property = property_or_error.release_value();
  928. // 5. Let result be the value of property if not undefined, or null.
  929. if (!property.is_undefined()) {
  930. if (auto string_or_error = property.to_byte_string(element->vm()); !string_or_error.is_error())
  931. result = string_or_error.release_value();
  932. }
  933. }
  934. // 6. Return success with data result.
  935. if (result.has_value())
  936. return result.release_value();
  937. return JsonValue {};
  938. }
  939. // 12.4.4 Get Element CSS Value, https://w3c.github.io/webdriver/#dfn-get-element-css-value
  940. Messages::WebDriverClient::GetElementCssValueResponse WebDriverConnection::get_element_css_value(String const& element_id, String const& name)
  941. {
  942. // 1. If the current browsing context is no longer open, return error with error code no such window.
  943. TRY(ensure_current_browsing_context_is_open());
  944. // 2. Handle any user prompts and return its value if it is an error.
  945. TRY(handle_any_user_prompts());
  946. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  947. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  948. // 4. Let computed value be the result of the first matching condition:
  949. ByteString computed_value;
  950. // -> current browsing context’s active document’s type is not "xml"
  951. if (!current_browsing_context().active_document()->is_xml_document()) {
  952. // computed value of parameter property name from element’s style declarations. property name is obtained from url variables.
  953. if (auto property = Web::CSS::property_id_from_string(name); property.has_value()) {
  954. if (auto computed_values = element->computed_css_values(); computed_values.has_value())
  955. computed_value = computed_values->property(property.value())->to_string().to_byte_string();
  956. }
  957. }
  958. // -> Otherwise
  959. else {
  960. // "" (empty string)
  961. computed_value = ByteString::empty();
  962. }
  963. // 5. Return success with data computed value.
  964. return computed_value;
  965. }
  966. // 12.4.5 Get Element Text, https://w3c.github.io/webdriver/#dfn-get-element-text
  967. Messages::WebDriverClient::GetElementTextResponse WebDriverConnection::get_element_text(String const& element_id)
  968. {
  969. // 1. If the current browsing context is no longer open, return error with error code no such window.
  970. TRY(ensure_current_browsing_context_is_open());
  971. // 2. Handle any user prompts and return its value if it is an error.
  972. TRY(handle_any_user_prompts());
  973. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  974. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  975. // 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.
  976. auto rendered_text = element->text_content();
  977. // 5. Return success with data rendered text.
  978. return rendered_text.value_or(String {}).to_byte_string();
  979. }
  980. // 12.4.6 Get Element Tag Name, https://w3c.github.io/webdriver/#dfn-get-element-tag-name
  981. Messages::WebDriverClient::GetElementTagNameResponse WebDriverConnection::get_element_tag_name(String const& element_id)
  982. {
  983. // 1. If the current browsing context is no longer open, return error with error code no such window.
  984. TRY(ensure_current_browsing_context_is_open());
  985. // 2. Handle any user prompts and return its value if it is an error.
  986. TRY(handle_any_user_prompts());
  987. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  988. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  989. // 4. Let qualified name be the result of getting element’s tagName IDL attribute.
  990. auto qualified_name = element->tag_name();
  991. // 5. Return success with data qualified name.
  992. return MUST(JsonValue::from_string(qualified_name));
  993. }
  994. // 12.4.7 Get Element Rect, https://w3c.github.io/webdriver/#dfn-get-element-rect
  995. Messages::WebDriverClient::GetElementRectResponse WebDriverConnection::get_element_rect(String const& element_id)
  996. {
  997. // 1. If the current browsing context is no longer open, return error with error code no such window.
  998. TRY(ensure_current_browsing_context_is_open());
  999. // 2. Handle any user prompts and return its value if it is an error.
  1000. TRY(handle_any_user_prompts());
  1001. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  1002. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  1003. // 4. Calculate the absolute position of element and let it be coordinates.
  1004. // 5. Let rect be element’s bounding rectangle.
  1005. auto rect = calculate_absolute_rect_of_element(*element);
  1006. // 6. Let body be a new JSON Object initialized with:
  1007. // "x"
  1008. // The first value of coordinates.
  1009. // "y"
  1010. // The second value of coordinates.
  1011. // "width"
  1012. // Value of rect’s width dimension.
  1013. // "height"
  1014. // Value of rect’s height dimension.
  1015. auto body = serialize_rect(rect);
  1016. // 7. Return success with data body.
  1017. return body;
  1018. }
  1019. // 12.4.8 Is Element Enabled, https://w3c.github.io/webdriver/#dfn-is-element-enabled
  1020. Messages::WebDriverClient::IsElementEnabledResponse WebDriverConnection::is_element_enabled(String const& element_id)
  1021. {
  1022. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1023. TRY(ensure_current_browsing_context_is_open());
  1024. // 2. Handle any user prompts and return its value if it is an error.
  1025. TRY(handle_any_user_prompts());
  1026. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  1027. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  1028. // 4. Let enabled be a boolean initially set to true if the current browsing context’s active document’s type is not "xml".
  1029. // 5. Otherwise, let enabled to false and jump to the last step of this algorithm.
  1030. bool enabled = !current_browsing_context().active_document()->is_xml_document();
  1031. // 6. Set enabled to false if a form control is disabled.
  1032. if (enabled && is<Web::HTML::FormAssociatedElement>(*element)) {
  1033. auto& form_associated_element = dynamic_cast<Web::HTML::FormAssociatedElement&>(*element);
  1034. enabled = form_associated_element.enabled();
  1035. }
  1036. // 7. Return success with data enabled.
  1037. return enabled;
  1038. }
  1039. // 12.4.9 Get Computed Role, https://w3c.github.io/webdriver/#dfn-get-computed-role
  1040. Messages::WebDriverClient::GetComputedRoleResponse WebDriverConnection::get_computed_role(String const& element_id)
  1041. {
  1042. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1043. TRY(ensure_current_top_level_browsing_context_is_open());
  1044. // 2. Handle any user prompts and return its value if it is an error.
  1045. TRY(handle_any_user_prompts());
  1046. // 3. Let element be the result of trying to get a known connected element with url variable element id.
  1047. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  1048. // 4. Let role be the result of computing the WAI-ARIA role of element.
  1049. auto role = element->role_or_default();
  1050. // 5. Return success with data role.
  1051. if (role.has_value())
  1052. return Web::ARIA::role_name(*role);
  1053. return ""sv;
  1054. }
  1055. // 12.4.10 Get Computed Label, https://w3c.github.io/webdriver/#get-computed-label
  1056. Messages::WebDriverClient::GetComputedLabelResponse WebDriverConnection::get_computed_label(String const& element_id)
  1057. {
  1058. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1059. TRY(ensure_current_browsing_context_is_open());
  1060. // 2. Handle any user prompts and return its value if it is an error.
  1061. TRY(handle_any_user_prompts());
  1062. // 3. Let element be the result of trying to get a known element with url variable element id.
  1063. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  1064. // 4. Let label be the result of a Accessible Name and Description Computation for the Accessible Name of the element.
  1065. auto label = element->accessible_name(element->document()).release_value_but_fixme_should_propagate_errors();
  1066. // 5. Return success with data label.
  1067. return label.to_byte_string();
  1068. }
  1069. // 12.5.1 Element Click, https://w3c.github.io/webdriver/#element-click
  1070. Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_click(String const& element_id)
  1071. {
  1072. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1073. TRY(ensure_current_browsing_context_is_open());
  1074. // 2. Handle any user prompts and return its value if it is an error.
  1075. TRY(handle_any_user_prompts());
  1076. // 3. Let element be the result of trying to get a known element with element id.
  1077. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  1078. // 4. If the element is an input element in the file upload state return error with error code invalid argument.
  1079. if (is<Web::HTML::HTMLInputElement>(*element)) {
  1080. // -> The result of element’s checkedness.
  1081. auto& input = static_cast<Web::HTML::HTMLInputElement&>(*element);
  1082. using enum Web::HTML::HTMLInputElement::TypeAttributeState;
  1083. if (input.type_state() == FileUpload)
  1084. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Clicking on an input element in the file upload state is not supported"sv);
  1085. }
  1086. // 5. Scroll into view the element’s container.
  1087. auto element_container = container_for_element(*element);
  1088. scroll_element_into_view(*element_container);
  1089. auto paint_tree = Web::WebDriver::pointer_interactable_tree(current_browsing_context(), *element_container);
  1090. // 6. If element’s container is still not in view, return error with error code element not interactable.
  1091. if (!Web::WebDriver::is_element_in_view(paint_tree, *element_container))
  1092. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Could not bring element into view"sv);
  1093. // 7. If element’s container is obscured by another element, return error with error code element click intercepted.
  1094. if (Web::WebDriver::is_element_obscured(paint_tree, *element_container))
  1095. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementClickIntercepted, "Element is obscured by another element"sv);
  1096. auto on_complete = JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
  1097. // 9. Wait until the user agent event loop has spun enough times to process the DOM events generated by the
  1098. // previous step.
  1099. m_action_executor = nullptr;
  1100. // FIXME: 10. Perform implementation-defined steps to allow any navigations triggered by the click to start.
  1101. // 11. Try to wait for navigation to complete.
  1102. wait_for_navigation_to_complete(JS::create_heap_function(current_browsing_context().heap(), [this, result = move(result)](Web::WebDriver::Response navigation_result) mutable {
  1103. // FIXME: 12. Try to run the post-navigation checks.
  1104. if (navigation_result.is_error())
  1105. async_actions_performed(move(navigation_result));
  1106. else
  1107. async_actions_performed(move(result));
  1108. }));
  1109. });
  1110. // 8. Matching on element:
  1111. // -> option element
  1112. if (is<Web::HTML::HTMLOptionElement>(*element)) {
  1113. auto& option_element = static_cast<Web::HTML::HTMLOptionElement&>(*element);
  1114. // 1. Let parent node be the element’s container.
  1115. auto parent_node = element_container;
  1116. // 2. Fire a mouseOver event at parent node.
  1117. fire_an_event<Web::UIEvents::MouseEvent>(Web::UIEvents::EventNames::mouseover, parent_node);
  1118. // 3. Fire a mouseMove event at parent node.
  1119. fire_an_event<Web::UIEvents::MouseEvent>(Web::UIEvents::EventNames::mousemove, parent_node);
  1120. // 4. Fire a mouseDown event at parent node.
  1121. fire_an_event<Web::UIEvents::MouseEvent>(Web::UIEvents::EventNames::mousedown, parent_node);
  1122. // 5. Run the focusing steps on parent node.
  1123. Web::HTML::run_focusing_steps(parent_node.has_value() ? &*parent_node : nullptr);
  1124. // 6. If element is not disabled:
  1125. if (!option_element.is_actually_disabled()) {
  1126. // 1. Fire an input event at parent node.
  1127. fire_an_event<Web::DOM::Event>(Web::HTML::EventNames::input, parent_node);
  1128. // 2. Let previous selectedness be equal to element selectedness.
  1129. auto previous_selectedness = option_element.selected();
  1130. // 3. If element’s container has the multiple attribute, toggle the element’s selectedness state
  1131. // by setting it to the opposite value of its current selectedness.
  1132. if (parent_node.has_value() && parent_node->has_attribute(Web::HTML::AttributeNames::multiple)) {
  1133. option_element.set_selected(!option_element.selected());
  1134. }
  1135. // Otherwise, set the element’s selectedness state to true.
  1136. else {
  1137. option_element.set_selected(true);
  1138. }
  1139. // 4. If previous selectedness is false, fire a change event at parent node.
  1140. if (!previous_selectedness) {
  1141. fire_an_event<Web::DOM::Event>(Web::HTML::EventNames::change, parent_node);
  1142. }
  1143. }
  1144. // 7. Fire a mouseUp event at parent node.
  1145. fire_an_event<Web::UIEvents::MouseEvent>(Web::UIEvents::EventNames::mouseup, parent_node);
  1146. // 8. Fire a click event at parent node.
  1147. fire_an_event<Web::UIEvents::MouseEvent>(Web::UIEvents::EventNames::click, parent_node);
  1148. Web::HTML::queue_a_task(Web::HTML::Task::Source::Unspecified, nullptr, nullptr, JS::create_heap_function(current_browsing_context().heap(), [on_complete]() {
  1149. on_complete->function()(JsonValue {});
  1150. }));
  1151. }
  1152. // -> Otherwise
  1153. else {
  1154. // 1. Let input state be the result of get the input state given current session and current top-level
  1155. // browsing context.
  1156. auto& input_state = Web::WebDriver::get_input_state(*current_top_level_browsing_context());
  1157. // 2. Let actions options be a new actions options with the is element origin steps set to represents a web
  1158. // element, and the get element origin steps set to get a WebElement origin.
  1159. Web::WebDriver::ActionsOptions actions_options {
  1160. .is_element_origin = &Web::WebDriver::represents_a_web_element,
  1161. .get_element_origin = &Web::WebDriver::get_web_element_origin,
  1162. };
  1163. // 3. Let input id be a the result of generating a UUID.
  1164. auto input_id = MUST(Web::Crypto::generate_random_uuid());
  1165. // 4. Let source be the result of create an input source with input state, and "pointer".
  1166. auto source = Web::WebDriver::create_input_source(input_state, Web::WebDriver::InputSourceType::Pointer, Web::WebDriver::PointerInputSource::Subtype::Mouse);
  1167. // 5. Add an input source with input state, input id and source.
  1168. Web::WebDriver::add_input_source(input_state, input_id, move(source));
  1169. // 6. Let click point be the element’s in-view center point.
  1170. // FIXME: Spec-issue: This parameter is unused. Note that it would not correct to set the mouse move action
  1171. // position to this click point. The [0,0] specified below is ultimately interpreted as an offset from
  1172. // the element's center position.
  1173. // https://github.com/w3c/webdriver/issues/1563
  1174. // 7. Let pointer move action be an action object constructed with arguments input id, "pointer", and "pointerMove".
  1175. Web::WebDriver::ActionObject pointer_move_action { input_id, Web::WebDriver::InputSourceType::Pointer, Web::WebDriver::ActionObject::Subtype::PointerMove };
  1176. // 8. Set a property x to 0 on pointer move action.
  1177. // 9. Set a property y to 0 on pointer move action.
  1178. pointer_move_action.pointer_move_fields().position = { 0, 0 };
  1179. // 10. Set a property origin to element on pointer move action.
  1180. auto origin = Web::WebDriver::get_or_create_a_web_element_reference(current_browsing_context(), *element);
  1181. pointer_move_action.pointer_move_fields().origin = MUST(String::from_byte_string(origin));
  1182. // 11. Let pointer down action be an action object constructed with arguments input id, "pointer", and "pointerDown".
  1183. Web::WebDriver::ActionObject pointer_down_action { input_id, Web::WebDriver::InputSourceType::Pointer, Web::WebDriver::ActionObject::Subtype::PointerDown };
  1184. // 12. Set a property button to 0 on pointer down action.
  1185. pointer_down_action.pointer_up_down_fields().button = Web::UIEvents::button_code_to_mouse_button(0);
  1186. // 13. Let pointer up action be an action object constructed with arguments input id, "pointer", and "pointerUp" as arguments.
  1187. Web::WebDriver::ActionObject pointer_up_action { input_id, Web::WebDriver::InputSourceType::Pointer, Web::WebDriver::ActionObject::Subtype::PointerUp };
  1188. // 14. Set a property button to 0 on pointer up action.
  1189. pointer_up_action.pointer_up_down_fields().button = Web::UIEvents::button_code_to_mouse_button(0);
  1190. // 15. Let actions be the list «pointer move action, pointer down action, pointer up action».
  1191. Vector actions { move(pointer_move_action), move(pointer_down_action), move(pointer_up_action) };
  1192. // 16. Dispatch a list of actions with input state, actions, current browsing context, and actions options.
  1193. m_action_executor = Web::WebDriver::dispatch_list_of_actions(input_state, move(actions), current_browsing_context(), move(actions_options), JS::create_heap_function(current_browsing_context().heap(), [on_complete, &input_state, input_id = move(input_id)](Web::WebDriver::Response result) {
  1194. // 17. Remove an input source with input state and input id.
  1195. Web::WebDriver::remove_input_source(input_state, input_id);
  1196. on_complete->function()(move(result));
  1197. }));
  1198. }
  1199. // 13. Return success with data null.
  1200. return JsonValue {};
  1201. }
  1202. // 12.5.2 Element Clear, https://w3c.github.io/webdriver/#dfn-element-clear
  1203. Messages::WebDriverClient::ElementClearResponse WebDriverConnection::element_clear(String const& element_id)
  1204. {
  1205. // https://w3c.github.io/webdriver/#dfn-clear-a-content-editable-element
  1206. auto clear_content_editable_element = [&](Web::DOM::Element& element) {
  1207. // 1. If element's innerHTML IDL attribute is an empty string do nothing and return.
  1208. if (auto result = element.inner_html(); result.is_error() || result.value().is_empty())
  1209. return;
  1210. // 2. Run the focusing steps for element.
  1211. Web::HTML::run_focusing_steps(&element);
  1212. // 3. Set element's innerHTML IDL attribute to an empty string.
  1213. (void)element.set_inner_html({});
  1214. // 4. Run the unfocusing steps for the element.
  1215. Web::HTML::run_unfocusing_steps(&element);
  1216. };
  1217. // https://w3c.github.io/webdriver/#dfn-clear-a-resettable-element
  1218. auto clear_resettable_element = [&](Web::DOM::Element& element) {
  1219. VERIFY(is<Web::HTML::FormAssociatedElement>(element));
  1220. auto& form_associated_element = dynamic_cast<Web::HTML::FormAssociatedElement&>(element);
  1221. // 1. Let empty be the result of the first matching condition:
  1222. auto empty = [&]() {
  1223. // -> element is an input element whose type attribute is in the File Upload state
  1224. // True if the list of selected files has a length of 0, and false otherwise
  1225. if (is<Web::HTML::HTMLInputElement>(element)) {
  1226. auto& input_element = static_cast<Web::HTML::HTMLInputElement&>(element);
  1227. if (input_element.type_state() == Web::HTML::HTMLInputElement::TypeAttributeState::FileUpload)
  1228. return input_element.files()->length() == 0;
  1229. }
  1230. // -> otherwise
  1231. // True if its value IDL attribute is an empty string, and false otherwise.
  1232. return form_associated_element.value().is_empty();
  1233. }();
  1234. // 2. If element is a candidate for constraint validation it satisfies its constraints, and empty is true,
  1235. // abort these substeps.
  1236. // FIXME: Implement constraint validation.
  1237. if (empty)
  1238. return;
  1239. // 3. Invoke the focusing steps for element.
  1240. Web::HTML::run_focusing_steps(&element);
  1241. // 4. Invoke the clear algorithm for element.
  1242. form_associated_element.clear_algorithm();
  1243. // 5. Invoke the unfocusing steps for the element.
  1244. Web::HTML::run_unfocusing_steps(&element);
  1245. };
  1246. // 1. If session's current browsing context is no longer open, return error with error code no such window.
  1247. TRY(ensure_current_browsing_context_is_open());
  1248. // 2. Try to handle any user prompts with session.
  1249. TRY(handle_any_user_prompts());
  1250. // 3. Let element be the result of trying to get a known element with session and element id.
  1251. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  1252. // 4. If element is not editable, return an error with error code invalid element state.
  1253. if (!Web::WebDriver::is_element_editable(*element))
  1254. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidElementState, "Element is not editable"sv);
  1255. // 5. Scroll into view the element.
  1256. scroll_element_into_view(*element);
  1257. // FIXME: 6. Let timeout be session's session timeouts' implicit wait timeout.
  1258. // FIXME: 7. Let timer be a new timer.
  1259. // FIXME: 8. If timeout is not null:
  1260. {
  1261. // FIXME: 1. Start the timer with timer and timeout.
  1262. }
  1263. // FIXME: 9. Wait for element to become interactable, or timer's timeout fired flag to be set, whichever occurs first.
  1264. // 10. If element is not interactable, return error with error code element not interactable.
  1265. if (!Web::WebDriver::is_element_interactable(current_browsing_context(), *element))
  1266. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Element is not interactable"sv);
  1267. // 11. Run the substeps of the first matching statement:
  1268. // -> element is a mutable form control element
  1269. if (Web::WebDriver::is_element_mutable_form_control(*element)) {
  1270. // Invoke the steps to clear a resettable element.
  1271. clear_resettable_element(*element);
  1272. }
  1273. // -> element is a mutable element
  1274. else if (Web::WebDriver::is_element_mutable(*element)) {
  1275. // Invoke the steps to clear a content editable element.
  1276. clear_content_editable_element(*element);
  1277. }
  1278. // -> otherwise
  1279. else {
  1280. // Return error with error code invalid element state.
  1281. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidElementState, "Element is not editable"sv);
  1282. }
  1283. // 12. Return success with data null.
  1284. return JsonValue {};
  1285. }
  1286. // 12.5.3 Element Send Keys, https://w3c.github.io/webdriver/#dfn-element-send-keys
  1287. Messages::WebDriverClient::ElementSendKeysResponse WebDriverConnection::element_send_keys(String const& element_id, JsonValue const& payload)
  1288. {
  1289. // 1. Let text be the result of getting a property named "text" from parameters.
  1290. // 2. If text is not a String, return an error with error code invalid argument.
  1291. auto text = TRY(Web::WebDriver::get_property(payload, "text"sv));
  1292. // 3. If session's current browsing context is no longer open, return error with error code no such window.
  1293. TRY(ensure_current_browsing_context_is_open());
  1294. // 4. Try to handle any user prompts with session.
  1295. TRY(handle_any_user_prompts());
  1296. // 5. Let element be the result of trying to get a known element with session and URL variables[element id].
  1297. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  1298. // 6. Let file be true if element is input element in the file upload state, or false otherwise.
  1299. auto file = is<Web::HTML::HTMLInputElement>(*element) && static_cast<Web::HTML::HTMLInputElement&>(*element).type_state() == Web::HTML::HTMLInputElement::TypeAttributeState::FileUpload;
  1300. // 7. If file is false or the session's strict file interactability, is true run the following substeps:
  1301. if (!file || m_strict_file_interactability) {
  1302. // 1. Scroll into view the element.
  1303. scroll_element_into_view(*element);
  1304. // FIXME: 2. Let timeout be session's session timeouts' implicit wait timeout.
  1305. // FIXME: 3. Let timer be a new timer.
  1306. // FIXME: 4. If timeout is not null:
  1307. {
  1308. // FIXME: 1. Start the timer with timer and timeout.
  1309. }
  1310. // FIXME: 5. Wait for element to become keyboard-interactable, or timer's timeout fired flag to be set, whichever occurs first.
  1311. // 6. If element is not keyboard-interactable, return error with error code element not interactable.
  1312. if (!Web::WebDriver::is_element_keyboard_interactable(*element))
  1313. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Element is not keyboard-interactable"sv);
  1314. // 7. If element is not the active element run the focusing steps for the element.
  1315. if (!element->is_active())
  1316. Web::HTML::run_focusing_steps(element);
  1317. }
  1318. // 8. Run the substeps of the first matching condition:
  1319. // -> file is true
  1320. if (file) {
  1321. auto& input_element = static_cast<Web::HTML::HTMLInputElement&>(*element);
  1322. // 1. Let files be the result of splitting text on the newline (\n) character.
  1323. auto files = text.split('\n');
  1324. // 2. If files is of 0 length, return an error with error code invalid argument.
  1325. if (files.is_empty())
  1326. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "File list is empty"sv);
  1327. // 3. Let multiple equal the result of calling hasAttribute() with "multiple" on element.
  1328. auto multiple = input_element.has_attribute(Web::HTML::AttributeNames::multiple);
  1329. // 4. if multiple is false and the length of files is not equal to 1, return an error with error code invalid argument.
  1330. if (!multiple && files.size() != 1)
  1331. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Element does not accept multiple files"sv);
  1332. // 5. Verify that each file given by the user exists. If any do not, return error with error code invalid argument.
  1333. // 6. Complete implementation specific steps equivalent to setting the selected files on the input element. If
  1334. // multiple is true files are be appended to element's selected files.
  1335. auto create_selected_file = [](auto const& path) -> ErrorOr<Web::HTML::SelectedFile> {
  1336. auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read));
  1337. auto contents = TRY(file->read_until_eof());
  1338. return Web::HTML::SelectedFile { LexicalPath::basename(path), move(contents) };
  1339. };
  1340. Vector<Web::HTML::SelectedFile> selected_files;
  1341. selected_files.ensure_capacity(files.size());
  1342. for (auto const& path : files) {
  1343. auto selected_file = create_selected_file(path);
  1344. if (selected_file.is_error())
  1345. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("'{}' does not exist", path));
  1346. selected_files.unchecked_append(selected_file.release_value());
  1347. }
  1348. input_element.did_select_files(selected_files, Web::HTML::HTMLInputElement::MultipleHandling::Append);
  1349. // 7. Fire these events in order on element:
  1350. // 1. input
  1351. // 2. change
  1352. // NOTE: These events are fired by `did_select_files` as an element task. So instead of firing them here, we spin
  1353. // the event loop once before informing the client that the action is complete.
  1354. Web::HTML::queue_a_task(Web::HTML::Task::Source::Unspecified, nullptr, nullptr, JS::create_heap_function(current_browsing_context().heap(), [this]() {
  1355. async_actions_performed(JsonValue {});
  1356. }));
  1357. // 8. Return success with data null.
  1358. return JsonValue {};
  1359. }
  1360. // -> element is a non-typeable form control
  1361. else if (Web::WebDriver::is_element_non_typeable_form_control(*element)) {
  1362. // 1. If element does not have an own property named value return an error with error code element not interactable
  1363. if (!is<Web::HTML::HTMLInputElement>(*element))
  1364. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Element does not have a property named 'value'"sv);
  1365. auto& input_element = static_cast<Web::HTML::HTMLInputElement&>(*element);
  1366. // 2. If element is not mutable return an error with error code element not interactable.
  1367. if (input_element.is_mutable())
  1368. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Element is immutable"sv);
  1369. // 3. Set a property value to text on element.
  1370. MUST(input_element.set_value(MUST(String::from_byte_string(text))));
  1371. // FIXME: 4. If element is suffering from bad input return an error with error code invalid argument.
  1372. // 5. Return success with data null.
  1373. async_actions_performed(JsonValue {});
  1374. return JsonValue {};
  1375. }
  1376. // -> element is content editable
  1377. else if (is<Web::HTML::HTMLElement>(*element) && static_cast<Web::HTML::HTMLElement&>(*element).is_content_editable()) {
  1378. // If element does not currently have focus, set the text insertion caret after any child content.
  1379. auto* document = current_browsing_context().active_document();
  1380. document->set_focused_element(element);
  1381. }
  1382. // -> otherwise
  1383. else if (is<Web::HTML::FormAssociatedTextControlElement>(*element)) {
  1384. Optional<Web::HTML::FormAssociatedTextControlElement&> target;
  1385. if (is<Web::HTML::HTMLInputElement>(*element))
  1386. target = static_cast<Web::HTML::HTMLInputElement&>(*element);
  1387. else if (is<Web::HTML::HTMLTextAreaElement>(*element))
  1388. target = static_cast<Web::HTML::HTMLTextAreaElement&>(*element);
  1389. // NOTE: The spec doesn't dictate this, but these steps only make sense for form-associated text elements.
  1390. if (target.has_value()) {
  1391. // 1. If element does not currently have focus, let current text length be the length of element's API value.
  1392. Optional<Web::WebIDL::UnsignedLong> current_text_length;
  1393. if (element->is_focused()) {
  1394. auto api_value = target->relevant_value();
  1395. // FIXME: This should be a UTF-16 code unit length, but `set_the_selection_range` is also currently
  1396. // implemented in terms of code point length.
  1397. current_text_length = api_value.code_points().length();
  1398. }
  1399. // 2. Set the text insertion caret using set selection range using current text length for both the start
  1400. // and end parameters.
  1401. (void)target->set_selection_range(current_text_length, current_text_length, {});
  1402. }
  1403. }
  1404. // 9. Let input state be the result of get the input state with session and session's current top-level browsing context.
  1405. auto& input_state = Web::WebDriver::get_input_state(*current_top_level_browsing_context());
  1406. // 10. Let input id be a the result of generating a UUID.
  1407. auto input_id = MUST(Web::Crypto::generate_random_uuid());
  1408. // 11. Let source be the result of create an input source with input state, and "key".
  1409. auto source = Web::WebDriver::create_input_source(input_state, Web::WebDriver::InputSourceType::Key, {});
  1410. // 12. Add an input source with input state, input id and source.
  1411. Web::WebDriver::add_input_source(input_state, input_id, move(source));
  1412. // 13. Dispatch actions for a string with arguments input state, input id, and source, text, and session's current browsing context.
  1413. m_action_executor = Web::WebDriver::dispatch_actions_for_a_string(input_state, input_id, source, text, current_browsing_context(), JS::create_heap_function(current_browsing_context().heap(), [this, &input_state, input_id](Web::WebDriver::Response result) {
  1414. m_action_executor = nullptr;
  1415. // 14. Remove an input source with input state and input id.
  1416. Web::WebDriver::remove_input_source(input_state, input_id);
  1417. async_actions_performed(move(result));
  1418. }));
  1419. // 15. Return success with data null.
  1420. return JsonValue {};
  1421. }
  1422. // 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source
  1423. Messages::WebDriverClient::GetSourceResponse WebDriverConnection::get_source()
  1424. {
  1425. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1426. TRY(ensure_current_browsing_context_is_open());
  1427. // 2. Handle any user prompts and return its value if it is an error.
  1428. TRY(handle_any_user_prompts());
  1429. auto* document = current_browsing_context().active_document();
  1430. Optional<ByteString> source;
  1431. // 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.
  1432. if (auto result = document->serialize_fragment(Web::DOMParsing::RequireWellFormed::Yes); !result.is_error())
  1433. source = result.release_value().to_byte_string();
  1434. // 4. Let source be the result of serializing to string the current browsing context active document, if source is null.
  1435. if (!source.has_value())
  1436. source = MUST(document->serialize_fragment(Web::DOMParsing::RequireWellFormed::No)).to_byte_string();
  1437. // 5. Return success with data source.
  1438. return source.release_value();
  1439. }
  1440. // 13.2.1 Execute Script, https://w3c.github.io/webdriver/#dfn-execute-script
  1441. Messages::WebDriverClient::ExecuteScriptResponse WebDriverConnection::execute_script(JsonValue const& payload)
  1442. {
  1443. auto* window = current_browsing_context().active_window();
  1444. auto& vm = window->vm();
  1445. // 1. Let body and arguments be the result of trying to extract the script arguments from a request with argument parameters.
  1446. auto [body, arguments] = TRY(extract_the_script_arguments_from_a_request(vm, payload));
  1447. // 2. If the current browsing context is no longer open, return error with error code no such window.
  1448. TRY(ensure_current_browsing_context_is_open());
  1449. // 3. Handle any user prompts, and return its value if it is an error.
  1450. TRY(handle_any_user_prompts());
  1451. // 4. Let timeout be session's session timeouts' script timeout.
  1452. auto timeout_ms = m_timeouts_configuration.script_timeout;
  1453. // This handles steps 5 to 9 and produces the appropriate result type for the following steps.
  1454. Web::WebDriver::execute_script(current_browsing_context(), move(body), move(arguments), timeout_ms, JS::create_heap_function(vm.heap(), [&](Web::WebDriver::ExecuteScriptResultSerialized result) {
  1455. dbgln_if(WEBDRIVER_DEBUG, "Executing script returned: {}", result.value);
  1456. Web::WebDriver::Response response;
  1457. switch (result.type) {
  1458. // 10. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
  1459. case Web::WebDriver::ExecuteScriptResultType::Timeout:
  1460. response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
  1461. break;
  1462. // 11. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
  1463. case Web::WebDriver::ExecuteScriptResultType::PromiseResolved:
  1464. response = move(result.value);
  1465. break;
  1466. // 12. 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.
  1467. case Web::WebDriver::ExecuteScriptResultType::PromiseRejected:
  1468. case Web::WebDriver::ExecuteScriptResultType::JavaScriptError:
  1469. response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
  1470. break;
  1471. case Web::WebDriver::ExecuteScriptResultType::BrowsingContextDiscarded:
  1472. response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Browsing context has been discarded", move(result.value));
  1473. break;
  1474. case Web::WebDriver::ExecuteScriptResultType::StaleElement:
  1475. response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Referenced element has become stale", move(result.value));
  1476. break;
  1477. }
  1478. async_script_executed(move(response));
  1479. }));
  1480. return JsonValue {};
  1481. }
  1482. // 13.2.2 Execute Async Script, https://w3c.github.io/webdriver/#dfn-execute-async-script
  1483. Messages::WebDriverClient::ExecuteAsyncScriptResponse WebDriverConnection::execute_async_script(JsonValue const& payload)
  1484. {
  1485. auto* window = current_browsing_context().active_window();
  1486. auto& vm = window->vm();
  1487. // 1. Let body and arguments by the result of trying to extract the script arguments from a request with argument parameters.
  1488. auto [body, arguments] = TRY(extract_the_script_arguments_from_a_request(vm, payload));
  1489. // 2. If the current browsing context is no longer open, return error with error code no such window.
  1490. TRY(ensure_current_browsing_context_is_open());
  1491. // 3. Handle any user prompts, and return its value if it is an error.
  1492. TRY(handle_any_user_prompts());
  1493. // 4. Let timeout be session's session timeouts' script timeout.
  1494. auto timeout_ms = m_timeouts_configuration.script_timeout;
  1495. // This handles steps 5 to 9 and produces the appropriate result type for the following steps.
  1496. Web::WebDriver::execute_async_script(current_browsing_context(), move(body), move(arguments), timeout_ms, JS::create_heap_function(vm.heap(), [&](Web::WebDriver::ExecuteScriptResultSerialized result) {
  1497. dbgln_if(WEBDRIVER_DEBUG, "Executing async script returned: {}", result.value);
  1498. Web::WebDriver::Response response;
  1499. switch (result.type) {
  1500. // 10. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
  1501. case Web::WebDriver::ExecuteScriptResultType::Timeout:
  1502. response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ScriptTimeoutError, "Script timed out");
  1503. break;
  1504. // 11. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
  1505. case Web::WebDriver::ExecuteScriptResultType::PromiseResolved:
  1506. response = move(result.value);
  1507. break;
  1508. // 12. 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.
  1509. case Web::WebDriver::ExecuteScriptResultType::PromiseRejected:
  1510. case Web::WebDriver::ExecuteScriptResultType::JavaScriptError:
  1511. response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::JavascriptError, "Script returned an error", move(result.value));
  1512. break;
  1513. case Web::WebDriver::ExecuteScriptResultType::BrowsingContextDiscarded:
  1514. response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Browsing context has been discarded", move(result.value));
  1515. break;
  1516. case Web::WebDriver::ExecuteScriptResultType::StaleElement:
  1517. response = Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::StaleElementReference, "Referenced element has become stale", move(result.value));
  1518. break;
  1519. }
  1520. async_script_executed(move(response));
  1521. }));
  1522. return JsonValue {};
  1523. }
  1524. // 14.1 Get All Cookies, https://w3c.github.io/webdriver/#dfn-get-all-cookies
  1525. Messages::WebDriverClient::GetAllCookiesResponse WebDriverConnection::get_all_cookies()
  1526. {
  1527. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1528. TRY(ensure_current_browsing_context_is_open());
  1529. // 2. Handle any user prompts, and return its value if it is an error.
  1530. TRY(handle_any_user_prompts());
  1531. // 3. Let cookies be a new JSON List.
  1532. JsonArray cookies;
  1533. // 4. For each cookie in all associated cookies of the current browsing context’s active document:
  1534. auto* document = current_browsing_context().active_document();
  1535. for (auto const& cookie : current_browsing_context().page().client().page_did_request_all_cookies(document->url())) {
  1536. // 1. Let serialized cookie be the result of serializing cookie.
  1537. auto serialized_cookie = serialize_cookie(cookie);
  1538. // 2. Append serialized cookie to cookies
  1539. TRY(cookies.append(move(serialized_cookie)));
  1540. }
  1541. // 5. Return success with data cookies.
  1542. return cookies;
  1543. }
  1544. // 14.2 Get Named Cookie, https://w3c.github.io/webdriver/#dfn-get-named-cookie
  1545. Messages::WebDriverClient::GetNamedCookieResponse WebDriverConnection::get_named_cookie(String const& name)
  1546. {
  1547. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1548. TRY(ensure_current_browsing_context_is_open());
  1549. // 2. Handle any user prompts, and return its value if it is an error.
  1550. TRY(handle_any_user_prompts());
  1551. // 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.
  1552. auto* document = current_browsing_context().active_document();
  1553. if (auto cookie = current_browsing_context().page().client().page_did_request_named_cookie(document->url(), name); cookie.has_value()) {
  1554. auto serialized_cookie = serialize_cookie(*cookie);
  1555. return serialized_cookie;
  1556. }
  1557. // 4. Otherwise, return error with error code no such cookie.
  1558. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchCookie, ByteString::formatted("Cookie '{}' not found", name));
  1559. }
  1560. // 14.3 Add Cookie, https://w3c.github.io/webdriver/#dfn-adding-a-cookie
  1561. Messages::WebDriverClient::AddCookieResponse WebDriverConnection::add_cookie(JsonValue const& payload)
  1562. {
  1563. // 1. Let data be the result of getting a property named cookie from the parameters argument.
  1564. auto const& data = *TRY(Web::WebDriver::get_property<JsonObject const*>(payload, "cookie"sv));
  1565. // 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.
  1566. // NOTE: This validation is performed in subsequent steps.
  1567. // 3. If the current browsing context is no longer open, return error with error code no such window.
  1568. TRY(ensure_current_browsing_context_is_open());
  1569. // 4. Handle any user prompts, and return its value if it is an error.
  1570. TRY(handle_any_user_prompts());
  1571. auto* document = current_browsing_context().active_document();
  1572. // 5. If the current browsing context’s document element is a cookie-averse Document object, return error with
  1573. // error code invalid cookie domain.
  1574. if (document->is_cookie_averse())
  1575. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidCookieDomain, "Document is cookie-averse"sv);
  1576. // 6. If cookie name or cookie value is null, cookie domain is not equal to the current browsing context’s active
  1577. // document’s domain, cookie secure only or cookie HTTP only are not boolean types, or cookie expiry time is not
  1578. // an integer type, or it less than 0 or greater than the maximum safe integer, return error with error code
  1579. // invalid argument.
  1580. // NOTE: This validation is either performed in subsequent steps.
  1581. // 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:
  1582. Web::Cookie::ParsedCookie cookie {};
  1583. cookie.name = MUST(String::from_byte_string(TRY(Web::WebDriver::get_property(data, "name"sv))));
  1584. cookie.value = MUST(String::from_byte_string(TRY(Web::WebDriver::get_property(data, "value"sv))));
  1585. // Cookie path
  1586. // The value if the entry exists, otherwise "/".
  1587. if (data.has("path"sv))
  1588. cookie.path = MUST(String::from_byte_string(TRY(Web::WebDriver::get_property(data, "path"sv))));
  1589. else
  1590. cookie.path = "/"_string;
  1591. // Cookie domain
  1592. // The value if the entry exists, otherwise the current browsing context’s active document’s URL domain.
  1593. // NOTE: The otherwise case is handled by the CookieJar
  1594. if (data.has("domain"sv)) {
  1595. cookie.domain = MUST(String::from_byte_string(TRY(Web::WebDriver::get_property(data, "domain"sv))));
  1596. // FIXME: Spec issue: We must return InvalidCookieDomain for invalid domains, rather than InvalidArgument.
  1597. // https://github.com/w3c/webdriver/issues/1570
  1598. if (!Web::Cookie::domain_matches(*cookie.domain, document->domain()))
  1599. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidCookieDomain, "Cookie domain does not match document domain"sv);
  1600. }
  1601. // Cookie secure only
  1602. // The value if the entry exists, otherwise false.
  1603. if (data.has("secure"sv))
  1604. cookie.secure_attribute_present = TRY(Web::WebDriver::get_property<bool>(data, "secure"sv));
  1605. // Cookie HTTP only
  1606. // The value if the entry exists, otherwise false.
  1607. if (data.has("httpOnly"sv))
  1608. cookie.http_only_attribute_present = TRY(Web::WebDriver::get_property<bool>(data, "httpOnly"sv));
  1609. // Cookie expiry time
  1610. // The value if the entry exists, otherwise leave unset to indicate that this is a session cookie.
  1611. if (data.has("expiry"sv)) {
  1612. auto expiry = TRY(Web::WebDriver::get_property<i64>(data, "expiry"sv));
  1613. cookie.expiry_time_from_expires_attribute = UnixDateTime::from_seconds_since_epoch(expiry);
  1614. }
  1615. // Cookie same site
  1616. // The value if the entry exists, otherwise leave unset to indicate that no same site policy is defined.
  1617. if (data.has("sameSite"sv)) {
  1618. auto same_site = TRY(Web::WebDriver::get_property(data, "sameSite"sv));
  1619. cookie.same_site_attribute = Web::Cookie::same_site_from_string(same_site);
  1620. if (cookie.same_site_attribute == Web::Cookie::SameSite::Default)
  1621. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Invalid same-site attribute"sv);
  1622. }
  1623. current_browsing_context().page().client().page_did_set_cookie(document->url(), cookie, Web::Cookie::Source::Http);
  1624. // If there is an error during this step, return error with error code unable to set cookie.
  1625. // NOTE: This probably should only apply to the actual setting of the cookie in the Browser, which cannot fail in our case.
  1626. // 8. Return success with data null.
  1627. return JsonValue {};
  1628. }
  1629. // 14.4 Delete Cookie, https://w3c.github.io/webdriver/#dfn-delete-cookie
  1630. Messages::WebDriverClient::DeleteCookieResponse WebDriverConnection::delete_cookie(String const& name)
  1631. {
  1632. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1633. TRY(ensure_current_browsing_context_is_open());
  1634. // 2. Handle any user prompts, and return its value if it is an error.
  1635. TRY(handle_any_user_prompts());
  1636. // 3. Delete cookies using the url variable name parameter as the filter argument.
  1637. delete_cookies(name);
  1638. // 4. Return success with data null.
  1639. return JsonValue {};
  1640. }
  1641. // 14.5 Delete All Cookies, https://w3c.github.io/webdriver/#dfn-delete-all-cookies
  1642. Messages::WebDriverClient::DeleteAllCookiesResponse WebDriverConnection::delete_all_cookies()
  1643. {
  1644. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1645. TRY(ensure_current_browsing_context_is_open());
  1646. // 2. Handle any user prompts, and return its value if it is an error.
  1647. TRY(handle_any_user_prompts());
  1648. // 3. Delete cookies, giving no filtering argument.
  1649. delete_cookies();
  1650. // 4. Return success with data null.
  1651. return JsonValue {};
  1652. }
  1653. // 15.7 Perform Actions, https://w3c.github.io/webdriver/#perform-actions
  1654. Messages::WebDriverClient::PerformActionsResponse WebDriverConnection::perform_actions(JsonValue const& payload)
  1655. {
  1656. // 4. If session's current browsing context is no longer open, return error with error code no such window.
  1657. // NOTE: We do this first so we can assume the current top-level browsing context below is non-null.
  1658. TRY(ensure_current_browsing_context_is_open());
  1659. // 1. Let input state be the result of get the input state with session and session's current top-level browsing context.
  1660. auto& input_state = Web::WebDriver::get_input_state(*current_top_level_browsing_context());
  1661. // 2. Let actions options be a new actions options with the is element origin steps set to represents a web element,
  1662. // and the get element origin steps set to get a WebElement origin.
  1663. Web::WebDriver::ActionsOptions actions_options {
  1664. .is_element_origin = &Web::WebDriver::represents_a_web_element,
  1665. .get_element_origin = &Web::WebDriver::get_web_element_origin,
  1666. };
  1667. // 3. Let actions by tick be the result of trying to extract an action sequence with input state, parameters, and
  1668. // actions options.
  1669. auto actions_by_tick = TRY(Web::WebDriver::extract_an_action_sequence(input_state, payload, actions_options));
  1670. // 5. Try to handle any user prompts with session.
  1671. TRY(handle_any_user_prompts());
  1672. // 6. Dispatch actions with input state, actions by tick, current browsing context, and actions options. If this
  1673. // results in an error return that error.
  1674. auto on_complete = JS::create_heap_function(current_browsing_context().heap(), [this](Web::WebDriver::Response result) {
  1675. m_action_executor = nullptr;
  1676. async_actions_performed(move(result));
  1677. });
  1678. m_action_executor = Web::WebDriver::dispatch_actions(input_state, move(actions_by_tick), current_browsing_context(), move(actions_options), on_complete);
  1679. // 7. Return success with data null.
  1680. return JsonValue {};
  1681. }
  1682. // 15.8 Release Actions, https://w3c.github.io/webdriver/#release-actions
  1683. Messages::WebDriverClient::ReleaseActionsResponse WebDriverConnection::release_actions()
  1684. {
  1685. // 1. If the current browsing context is no longer open, return error with error code no such window.
  1686. TRY(ensure_current_browsing_context_is_open());
  1687. // 2. Let input state be the result of get the input state with current session and current top-level browsing context.
  1688. auto& input_state = Web::WebDriver::get_input_state(*current_top_level_browsing_context());
  1689. // 3. Let actions options be a new actions options with the is element origin steps set to represents a web element,
  1690. // and the get element origin steps set to get a WebElement origin.
  1691. Web::WebDriver::ActionsOptions actions_options {
  1692. .is_element_origin = &Web::WebDriver::represents_a_web_element,
  1693. .get_element_origin = &Web::WebDriver::get_web_element_origin,
  1694. };
  1695. // 4. Let undo actions be input state’s input cancel list in reverse order.
  1696. auto undo_actions = input_state.input_cancel_list;
  1697. undo_actions.reverse();
  1698. // 5. Try to dispatch tick actions with arguments undo actions, 0, current browsing context, and actions options.
  1699. TRY(Web::WebDriver::dispatch_tick_actions(input_state, undo_actions, AK::Duration::zero(), current_browsing_context(), actions_options));
  1700. // 6. Reset the input state with current session and current top-level browsing context.
  1701. Web::WebDriver::reset_input_state(*current_top_level_browsing_context());
  1702. // 7. Return success with data null.
  1703. return JsonValue {};
  1704. }
  1705. // 16.1 Dismiss Alert, https://w3c.github.io/webdriver/#dismiss-alert
  1706. Messages::WebDriverClient::DismissAlertResponse WebDriverConnection::dismiss_alert()
  1707. {
  1708. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1709. TRY(ensure_current_top_level_browsing_context_is_open());
  1710. // 2. If there is no current user prompt, return error with error code no such alert.
  1711. if (!current_browsing_context().page().has_pending_dialog())
  1712. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
  1713. // 3. Dismiss the current user prompt.
  1714. current_browsing_context().page().dismiss_dialog(JS::create_heap_function(current_browsing_context().heap(), [this]() {
  1715. async_dialog_closed(JsonValue {});
  1716. }));
  1717. // 4. Return success with data null.
  1718. return JsonValue {};
  1719. }
  1720. // 16.2 Accept Alert, https://w3c.github.io/webdriver/#accept-alert
  1721. Messages::WebDriverClient::AcceptAlertResponse WebDriverConnection::accept_alert()
  1722. {
  1723. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1724. TRY(ensure_current_top_level_browsing_context_is_open());
  1725. // 2. If there is no current user prompt, return error with error code no such alert.
  1726. if (!current_browsing_context().page().has_pending_dialog())
  1727. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
  1728. // 3. Accept the current user prompt.
  1729. current_browsing_context().page().accept_dialog(JS::create_heap_function(current_browsing_context().heap(), [this]() {
  1730. async_dialog_closed(JsonValue {});
  1731. }));
  1732. // 4. Return success with data null.
  1733. return JsonValue {};
  1734. }
  1735. // 16.3 Get Alert Text, https://w3c.github.io/webdriver/#get-alert-text
  1736. Messages::WebDriverClient::GetAlertTextResponse WebDriverConnection::get_alert_text()
  1737. {
  1738. // 1. If the current top-level browsing context is no longer open, return error with error code no such window.
  1739. TRY(ensure_current_top_level_browsing_context_is_open());
  1740. // 2. If there is no current user prompt, return error with error code no such alert.
  1741. if (!current_browsing_context().page().has_pending_dialog())
  1742. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
  1743. // 3. Let message be the text message associated with the current user prompt, or otherwise be null.
  1744. auto const& message = current_browsing_context().page().pending_dialog_text();
  1745. // 4. Return success with data message.
  1746. if (message.has_value())
  1747. return message->to_byte_string();
  1748. return JsonValue {};
  1749. }
  1750. // 16.4 Send Alert Text, https://w3c.github.io/webdriver/#send-alert-text
  1751. Messages::WebDriverClient::SendAlertTextResponse WebDriverConnection::send_alert_text(JsonValue const& payload)
  1752. {
  1753. // 1. Let text be the result of getting the property "text" from parameters.
  1754. // 2. If text is not a String, return error with error code invalid argument.
  1755. auto text = TRY(Web::WebDriver::get_property(payload, "text"sv));
  1756. // 3. If the current top-level browsing context is no longer open, return error with error code no such window.
  1757. TRY(ensure_current_top_level_browsing_context_is_open());
  1758. // 4. If there is no current user prompt, return error with error code no such alert.
  1759. if (!current_browsing_context().page().has_pending_dialog())
  1760. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchAlert, "No user dialog is currently open"sv);
  1761. // 5. Run the substeps of the first matching current user prompt:
  1762. switch (current_browsing_context().page().pending_dialog()) {
  1763. // -> alert
  1764. // -> confirm
  1765. case Web::Page::PendingDialog::Alert:
  1766. case Web::Page::PendingDialog::Confirm:
  1767. // Return error with error code element not interactable.
  1768. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Only prompt dialogs may receive text"sv);
  1769. // -> prompt
  1770. case Web::Page::PendingDialog::Prompt:
  1771. // Do nothing.
  1772. break;
  1773. // -> Otherwise
  1774. default:
  1775. // Return error with error code unsupported operation.
  1776. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Unknown dialog type"sv);
  1777. }
  1778. // 6. Perform user agent dependent steps to set the value of current user prompt’s text field to text.
  1779. current_browsing_context().page().client().page_did_request_set_prompt_text(TRY(String::from_byte_string(text)));
  1780. // 7. Return success with data null.
  1781. return JsonValue {};
  1782. }
  1783. // 17.1 Take Screenshot, https://w3c.github.io/webdriver/#take-screenshot
  1784. Messages::WebDriverClient::TakeScreenshotResponse WebDriverConnection::take_screenshot()
  1785. {
  1786. // 1. If session's current top-level browsing context is no longer open, return error with error code no such window.
  1787. TRY(ensure_current_top_level_browsing_context_is_open());
  1788. auto* document = current_top_level_browsing_context()->active_document();
  1789. auto window = document->window();
  1790. // 2. When the user agent is next to run the animation frame callbacks:
  1791. (void)window->animation_frame_callback_driver().add(JS::create_heap_function(document->heap(), [this, document](double) mutable {
  1792. // a. Let root rect be session's current top-level browsing context's document element's rectangle.
  1793. auto root_rect = calculate_absolute_rect_of_element(*document->document_element());
  1794. // b. Let screenshot result be the result of trying to call draw a bounding box from the framebuffer, given root rect as an argument.
  1795. auto screenshot_result = Web::WebDriver::draw_bounding_box_from_the_framebuffer(*current_top_level_browsing_context(), *document->document_element(), root_rect);
  1796. if (screenshot_result.is_error()) {
  1797. async_screenshot_taken(screenshot_result.release_error());
  1798. return;
  1799. }
  1800. // c. Let canvas be a canvas element of screenshot result's data.
  1801. auto canvas = screenshot_result.release_value();
  1802. // d. Let encoding result be the result of trying encoding a canvas as Base64 canvas.
  1803. // e. Let encoded string be encoding result's data.
  1804. auto encoded_string = Web::WebDriver::encode_canvas_element(canvas);
  1805. // 3. Return success with data encoded string.
  1806. async_screenshot_taken(move(encoded_string));
  1807. }));
  1808. return JsonValue {};
  1809. }
  1810. // 17.2 Take Element Screenshot, https://w3c.github.io/webdriver/#dfn-take-element-screenshot
  1811. Messages::WebDriverClient::TakeElementScreenshotResponse WebDriverConnection::take_element_screenshot(String const& element_id)
  1812. {
  1813. // 1. If session's current browsing context is no longer open, return error with error code no such window.
  1814. TRY(ensure_current_browsing_context_is_open());
  1815. auto* document = current_browsing_context().active_document();
  1816. auto window = document->window();
  1817. // 2. Try to handle any user prompts with session.
  1818. TRY(handle_any_user_prompts());
  1819. // 3. Let element be the result of trying to get a known element with session and URL variables["element id"].
  1820. auto element = TRY(Web::WebDriver::get_known_element(current_browsing_context(), element_id));
  1821. // 4. Scroll into view the element.
  1822. scroll_element_into_view(element);
  1823. // 5. When the user agent is next to run the animation frame callbacks:
  1824. (void)window->animation_frame_callback_driver().add(JS::create_heap_function(document->heap(), [this, element](double) {
  1825. // a. Let element rect be element's rectangle.
  1826. auto element_rect = calculate_absolute_rect_of_element(element);
  1827. // b. Let screenshot result be the result of trying to call draw a bounding box from the framebuffer, given element rect as an argument.
  1828. auto screenshot_result = Web::WebDriver::draw_bounding_box_from_the_framebuffer(current_browsing_context(), element, element_rect);
  1829. if (screenshot_result.is_error()) {
  1830. async_screenshot_taken(screenshot_result.release_error());
  1831. return;
  1832. }
  1833. // c. Let canvas be a canvas element of screenshot result's data.
  1834. auto canvas = screenshot_result.release_value();
  1835. // d. Let encoding result be the result of trying encoding a canvas as Base64 canvas.
  1836. // e. Let encoded string be encoding result's data.
  1837. auto encoded_string = Web::WebDriver::encode_canvas_element(canvas);
  1838. // 6. Return success with data encoded string.
  1839. async_screenshot_taken(move(encoded_string));
  1840. }));
  1841. return JsonValue {};
  1842. }
  1843. // 18.1 Print Page, https://w3c.github.io/webdriver/#dfn-print-page
  1844. Messages::WebDriverClient::PrintPageResponse WebDriverConnection::print_page(JsonValue const& payload)
  1845. {
  1846. dbgln("FIXME: WebDriverConnection::print_page({})", payload);
  1847. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Print not implemented"sv);
  1848. }
  1849. // https://w3c.github.io/webdriver/#dfn-set-the-current-browsing-context
  1850. void WebDriverConnection::set_current_browsing_context(Web::HTML::BrowsingContext& browsing_context)
  1851. {
  1852. // 1. Set session's current browsing context to context.
  1853. m_current_browsing_context = browsing_context;
  1854. // 2. Set the session's current parent browsing context to the parent browsing context of context, if that context
  1855. // exists, or null otherwise.
  1856. if (auto navigable = browsing_context.active_document()->navigable(); navigable && navigable->parent())
  1857. m_current_parent_browsing_context = navigable->parent()->active_browsing_context();
  1858. else
  1859. m_current_parent_browsing_context = nullptr;
  1860. }
  1861. // https://w3c.github.io/webdriver/#dfn-set-the-current-browsing-context
  1862. void WebDriverConnection::set_current_top_level_browsing_context(Web::HTML::BrowsingContext& browsing_context)
  1863. {
  1864. // 1. Assert: context is a top-level browsing context.
  1865. VERIFY(browsing_context.is_top_level());
  1866. if (m_current_top_level_browsing_context)
  1867. m_current_top_level_browsing_context->page().set_window_rect_observer({});
  1868. // 2. Set session's current top-level browsing context to context.
  1869. m_current_top_level_browsing_context = browsing_context;
  1870. if (m_current_top_level_browsing_context) {
  1871. m_current_top_level_browsing_context->page().set_window_rect_observer(JS::create_heap_function(m_current_top_level_browsing_context->heap(), [this](Web::DevicePixelRect rect) {
  1872. if (m_pending_window_rect_requests > 0 && --m_pending_window_rect_requests == 0)
  1873. async_window_rect_updated(serialize_rect(rect.to_type<int>()));
  1874. }));
  1875. }
  1876. // 3. Set the current browsing context with session and context.
  1877. set_current_browsing_context(browsing_context);
  1878. }
  1879. Messages::WebDriverClient::EnsureTopLevelBrowsingContextIsOpenResponse WebDriverConnection::ensure_top_level_browsing_context_is_open()
  1880. {
  1881. TRY(ensure_current_top_level_browsing_context_is_open());
  1882. return JsonValue {};
  1883. }
  1884. ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::ensure_current_browsing_context_is_open()
  1885. {
  1886. return ensure_browsing_context_is_open(current_browsing_context());
  1887. }
  1888. ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::ensure_current_top_level_browsing_context_is_open()
  1889. {
  1890. return ensure_browsing_context_is_open(current_top_level_browsing_context());
  1891. }
  1892. // https://w3c.github.io/webdriver/#dfn-handle-any-user-prompts
  1893. ErrorOr<void, Web::WebDriver::Error> WebDriverConnection::handle_any_user_prompts()
  1894. {
  1895. // 1. If there is no current user prompt, abort these steps and return success.
  1896. if (!current_browsing_context().page().has_pending_dialog())
  1897. return {};
  1898. // 2. Perform the following substeps based on the current session’s user prompt handler:
  1899. switch (m_unhandled_prompt_behavior) {
  1900. // -> dismiss state
  1901. case Web::WebDriver::UnhandledPromptBehavior::Dismiss:
  1902. // Dismiss the current user prompt.
  1903. current_browsing_context().page().dismiss_dialog();
  1904. break;
  1905. // -> accept state
  1906. case Web::WebDriver::UnhandledPromptBehavior::Accept:
  1907. // Accept the current user prompt.
  1908. current_browsing_context().page().accept_dialog();
  1909. break;
  1910. // -> dismiss and notify state
  1911. case Web::WebDriver::UnhandledPromptBehavior::DismissAndNotify:
  1912. // Dismiss the current user prompt.
  1913. current_browsing_context().page().dismiss_dialog();
  1914. // Return an annotated unexpected alert open error.
  1915. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
  1916. // -> accept and notify state
  1917. case Web::WebDriver::UnhandledPromptBehavior::AcceptAndNotify:
  1918. // Accept the current user prompt.
  1919. current_browsing_context().page().accept_dialog();
  1920. // Return an annotated unexpected alert open error.
  1921. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
  1922. // -> ignore state
  1923. case Web::WebDriver::UnhandledPromptBehavior::Ignore:
  1924. // Return an annotated unexpected alert open error.
  1925. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnexpectedAlertOpen, "A user dialog is open"sv);
  1926. }
  1927. // 3. Return success.
  1928. return {};
  1929. }
  1930. // https://w3c.github.io/webdriver/#dfn-wait-for-navigation-to-complete
  1931. // FIXME: Update this AO to the latest spec steps.
  1932. void WebDriverConnection::wait_for_navigation_to_complete(OnNavigationComplete on_complete)
  1933. {
  1934. // 1. If the current session has a page loading strategy of none, return success with data null.
  1935. if (m_page_load_strategy == Web::WebDriver::PageLoadStrategy::None) {
  1936. on_complete->function()(JsonValue {});
  1937. return;
  1938. }
  1939. // 2. If the current browsing context is no longer open, return success with data null.
  1940. if (ensure_browsing_context_is_open(current_browsing_context()).is_error()) {
  1941. on_complete->function()(JsonValue {});
  1942. return;
  1943. }
  1944. auto& realm = current_browsing_context().active_document()->realm();
  1945. auto navigable = current_browsing_context().active_document()->navigable();
  1946. if (!navigable || navigable->ongoing_navigation().has<Empty>()) {
  1947. on_complete->function()(JsonValue {});
  1948. return;
  1949. }
  1950. auto reset_observers = [](auto& self) {
  1951. if (self.m_navigation_observer) {
  1952. self.m_navigation_observer->set_navigation_complete({});
  1953. self.m_navigation_observer = nullptr;
  1954. }
  1955. if (self.m_document_observer) {
  1956. self.m_document_observer->set_document_readiness_observer({});
  1957. self.m_document_observer = nullptr;
  1958. }
  1959. };
  1960. // 3. Start a timer. If this algorithm has not completed before timer reaches the session’s session page load timeout
  1961. // in milliseconds, return an error with error code timeout.
  1962. m_navigation_timer = realm.heap().allocate<Web::WebDriver::HeapTimer>(realm);
  1963. // 4. If there is an ongoing attempt to navigate the current browsing context that has not yet matured, wait for
  1964. // navigation to mature.
  1965. m_navigation_observer = realm.heap().allocate<Web::HTML::NavigationObserver>(realm, realm, *navigable);
  1966. m_navigation_observer->set_navigation_complete([this, &realm, reset_observers]() {
  1967. reset_observers(*this);
  1968. // 5. Let readiness target be the document readiness state associated with the current session’s page loading
  1969. // strategy, which can be found in the table of page load strategies.
  1970. auto readiness_target = [this]() {
  1971. switch (m_page_load_strategy) {
  1972. case Web::WebDriver::PageLoadStrategy::Normal:
  1973. return Web::HTML::DocumentReadyState::Complete;
  1974. case Web::WebDriver::PageLoadStrategy::Eager:
  1975. return Web::HTML::DocumentReadyState::Interactive;
  1976. default:
  1977. VERIFY_NOT_REACHED();
  1978. };
  1979. }();
  1980. // 6. Wait for the current browsing context’s document readiness state to reach readiness target,
  1981. // or for the session page load timeout to pass, whichever occurs sooner.
  1982. if (auto* document = current_browsing_context().active_document(); document->readiness() != readiness_target) {
  1983. m_document_observer = realm.heap().allocate<Web::DOM::DocumentObserver>(realm, realm, *document);
  1984. m_document_observer->set_document_readiness_observer([this, readiness_target](Web::HTML::DocumentReadyState readiness) {
  1985. if (readiness == readiness_target)
  1986. m_navigation_timer->stop_and_fire_timeout_handler();
  1987. });
  1988. } else {
  1989. m_navigation_timer->stop_and_fire_timeout_handler();
  1990. }
  1991. });
  1992. m_navigation_timer->start(m_timeouts_configuration.page_load_timeout.value_or(300'000), JS::create_heap_function(realm.heap(), [this, on_complete, reset_observers]() {
  1993. reset_observers(*this);
  1994. auto did_time_out = m_navigation_timer->is_timed_out();
  1995. m_navigation_timer = nullptr;
  1996. // 7. If the previous step completed by the session page load timeout being reached and the browser does
  1997. // not have an active user prompt, return error with error code timeout.
  1998. if (did_time_out && !current_browsing_context().active_document()->page().has_pending_dialog()) {
  1999. on_complete->function()(Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::Timeout, "Navigation timed out"sv));
  2000. return;
  2001. }
  2002. // 8. Return success with data null.
  2003. on_complete->function()(JsonValue {});
  2004. }));
  2005. }
  2006. void WebDriverConnection::page_did_open_dialog(Badge<PageClient>)
  2007. {
  2008. // OPTMIZATION: If a dialog is opened while we are awaiting a specific document readiness state, that state will
  2009. // never be reached, as the dialog will block the HTML event loop from any further processing. Instead
  2010. // of waiting for the session's page load timeout to expire, unblock the waiter immediately. This also
  2011. // seems to match how other browsers behave.
  2012. if (m_navigation_timer)
  2013. m_navigation_timer->stop_and_fire_timeout_handler();
  2014. }
  2015. // https://w3c.github.io/webdriver/#dfn-maximize-the-window
  2016. void WebDriverConnection::maximize_the_window()
  2017. {
  2018. // To maximize the window, given an operating system level window with an associated top-level browsing context, run
  2019. // the implementation-specific steps to transition the operating system level window into the maximized window state.
  2020. // Return when the window has completed the transition, or within an implementation-defined timeout.
  2021. current_top_level_browsing_context()->page().client().page_did_request_maximize_window();
  2022. ++m_pending_window_rect_requests;
  2023. }
  2024. // https://w3c.github.io/webdriver/#dfn-iconify-the-window
  2025. void WebDriverConnection::iconify_the_window(JS::NonnullGCPtr<JS::HeapFunction<void()>> on_complete)
  2026. {
  2027. // To iconify the window, given an operating system level window with an associated top-level browsing context, run
  2028. // implementation-specific steps to iconify, minimize, or hide the window from the visible screen.
  2029. current_top_level_browsing_context()->page().client().page_did_request_minimize_window();
  2030. // Do not return from this operation until the visibility state of the top-level browsing context’s active document
  2031. // has reached the hidden state, or until the operation times out.
  2032. wait_for_visibility_state(on_complete, Web::HTML::VisibilityState::Hidden);
  2033. }
  2034. // https://w3c.github.io/webdriver/#dfn-restore-the-window
  2035. void WebDriverConnection::restore_the_window(JS::NonnullGCPtr<JS::HeapFunction<void()>> on_complete)
  2036. {
  2037. // To restore the window, given an operating system level window with an associated top-level browsing context, run
  2038. // implementation-specific steps to restore or unhide the window to the visible screen.
  2039. current_top_level_browsing_context()->page().client().page_did_request_restore_window();
  2040. // Do not return from this operation until the visibility state of the top-level browsing context’s active document
  2041. // has reached the visible state, or until the operation times out.
  2042. wait_for_visibility_state(on_complete, Web::HTML::VisibilityState::Visible);
  2043. }
  2044. void WebDriverConnection::wait_for_visibility_state(JS::NonnullGCPtr<JS::HeapFunction<void()>> on_complete, Web::HTML::VisibilityState target_visibility_state)
  2045. {
  2046. static constexpr auto VISIBILITY_STATE_TIMEOUT_MS = 5'000;
  2047. auto* document = current_top_level_browsing_context()->active_document();
  2048. auto& realm = document->realm();
  2049. if (document->visibility_state_value() == target_visibility_state) {
  2050. on_complete->function()();
  2051. return;
  2052. }
  2053. auto timer = realm.heap().allocate<Web::WebDriver::HeapTimer>(realm);
  2054. m_document_observer = realm.heap().allocate<Web::DOM::DocumentObserver>(realm, realm, *document);
  2055. m_document_observer->set_document_visibility_state_observer([timer, target_visibility_state](Web::HTML::VisibilityState visibility_state) {
  2056. if (visibility_state == target_visibility_state)
  2057. timer->stop_and_fire_timeout_handler();
  2058. });
  2059. timer->start(VISIBILITY_STATE_TIMEOUT_MS, JS::create_heap_function(realm.heap(), [this, on_complete]() {
  2060. m_document_observer->set_document_visibility_state_observer({});
  2061. m_document_observer = nullptr;
  2062. on_complete->function()();
  2063. }));
  2064. }
  2065. class ElementLocator final : public JS::Cell {
  2066. JS_CELL(ElementLocator, JS::Cell);
  2067. JS_DECLARE_ALLOCATOR(ElementLocator);
  2068. public:
  2069. ElementLocator(
  2070. Web::HTML::BrowsingContext const& browsing_context,
  2071. Web::WebDriver::LocationStrategy location_strategy,
  2072. ByteString selector,
  2073. WebDriverConnection::GetStartNode get_start_node,
  2074. WebDriverConnection::OnFindComplete on_complete,
  2075. JS::NonnullGCPtr<Web::WebDriver::HeapTimer> timer)
  2076. : m_browsing_context(browsing_context)
  2077. , m_location_strategy(location_strategy)
  2078. , m_selector(move(selector))
  2079. , m_get_start_node(get_start_node)
  2080. , m_on_complete(on_complete)
  2081. , m_timer(timer)
  2082. {
  2083. }
  2084. void search_for_element()
  2085. {
  2086. if (auto result = perform_search(); result.has_value()) {
  2087. m_on_complete->function()(result.release_value());
  2088. return;
  2089. }
  2090. if (m_timer->is_timed_out())
  2091. return;
  2092. Web::HTML::queue_a_task(Web::HTML::Task::Source::Unspecified, nullptr, nullptr, JS::create_heap_function(heap(), [this]() {
  2093. search_for_element();
  2094. }));
  2095. }
  2096. private:
  2097. Optional<Web::WebDriver::Response> perform_search()
  2098. {
  2099. // 1. Set elements returned to the result of trying to call the relevant element location strategy with arguments
  2100. // start node, and selector.
  2101. auto maybe_elements = Web::WebDriver::invoke_location_strategy(m_location_strategy, TRY(m_get_start_node->function()()), m_selector);
  2102. // 2. If a DOMException, SyntaxError, XPathException, or other error occurs during the execution of the element
  2103. // location strategy, return error invalid selector.
  2104. if (maybe_elements.is_error())
  2105. return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSelector, ByteString::formatted("The location strategy could not finish: {}", maybe_elements.error().message));
  2106. if (auto elements = maybe_elements.release_value(); elements->length() > 0) {
  2107. // 8. Let result be an empty List.
  2108. JsonArray result;
  2109. result.ensure_capacity(elements->length());
  2110. // 9. For each element in elements returned, append the web element reference object for session and element,
  2111. // to result.
  2112. for (size_t i = 0; i < elements->length(); ++i)
  2113. result.must_append(Web::WebDriver::web_element_reference_object(m_browsing_context, *elements->item(i)));
  2114. // 10. Return success with data result.
  2115. return JsonValue { move(result) };
  2116. }
  2117. return {};
  2118. }
  2119. virtual void visit_edges(Cell::Visitor& visitor) override
  2120. {
  2121. Base::visit_edges(visitor);
  2122. visitor.visit(m_browsing_context);
  2123. visitor.visit(m_get_start_node);
  2124. visitor.visit(m_on_complete);
  2125. visitor.visit(m_timer);
  2126. }
  2127. JS::NonnullGCPtr<Web::HTML::BrowsingContext const> m_browsing_context;
  2128. Web::WebDriver::LocationStrategy m_location_strategy;
  2129. ByteString m_selector;
  2130. WebDriverConnection::GetStartNode m_get_start_node;
  2131. WebDriverConnection::OnFindComplete m_on_complete;
  2132. JS::NonnullGCPtr<Web::WebDriver::HeapTimer> m_timer;
  2133. };
  2134. JS_DEFINE_ALLOCATOR(ElementLocator);
  2135. // https://w3c.github.io/webdriver/#dfn-find
  2136. void WebDriverConnection::find(Web::WebDriver::LocationStrategy location_strategy, ByteString selector, GetStartNode get_start_node, OnFindComplete on_complete)
  2137. {
  2138. auto& realm = current_browsing_context().active_document()->realm();
  2139. // 1. Let location strategy be equal to using.
  2140. // 2. Let selector be equal to value.
  2141. // 3. Let timeout be session's session timeouts' implicit wait timeout.
  2142. auto timeout = m_timeouts_configuration.implicit_wait_timeout;
  2143. // 4. Let timer be a new timer.
  2144. auto timer = realm.heap().allocate<Web::WebDriver::HeapTimer>(realm);
  2145. auto wrapped_on_complete = JS::create_heap_function(realm.heap(), [this, on_complete, timer](Web::WebDriver::Response result) {
  2146. m_element_locator = nullptr;
  2147. timer->stop();
  2148. on_complete->function()(move(result));
  2149. });
  2150. // 5. If timeout is not null:
  2151. if (timeout.has_value()) {
  2152. // 1. Start the timer with timer and timeout.
  2153. timer->start(*timeout, JS::create_heap_function(realm.heap(), [wrapped_on_complete]() {
  2154. wrapped_on_complete->function()({ JsonArray {} });
  2155. }));
  2156. }
  2157. // 6. Let elements returned be an empty List.
  2158. // 7. While elements returned is empty and timer's timeout fired flag is not set:
  2159. m_element_locator = realm.heap().allocate<ElementLocator>(realm, current_browsing_context(), location_strategy, move(selector), get_start_node, wrapped_on_complete, timer);
  2160. m_element_locator->search_for_element();
  2161. }
  2162. // https://w3c.github.io/webdriver/#dfn-json-deserialize
  2163. static ErrorOr<JS::Value, Web::WebDriver::Error> json_deserialize(JS::VM& vm, Web::HTML::BrowsingContext const& browsing_context, JsonValue const& value)
  2164. {
  2165. // 1. If seen is not provided, let seen be an empty List.
  2166. // 2. Jump to the first appropriate step below:
  2167. // 3. Matching on value:
  2168. // -> undefined
  2169. // -> null
  2170. // -> type Boolean
  2171. // -> type Number
  2172. // -> type String
  2173. if (value.is_null() || value.is_bool() || value.is_number() || value.is_string()) {
  2174. // Return success with data value.
  2175. return JS::JSONObject::parse_json_value(vm, value);
  2176. }
  2177. // -> Object that represents a web element
  2178. if (Web::WebDriver::represents_a_web_element(value)) {
  2179. // Return the deserialized web element of value.
  2180. return Web::WebDriver::deserialize_web_element(browsing_context, value.as_object());
  2181. }
  2182. // FIXME: -> Object that represents a shadow root
  2183. // Return the deserialized shadow root of value.
  2184. // FIXME: -> Object that represents a web frame
  2185. // Return the deserialized web frame of value.
  2186. // FIXME: -> Object that represents a web window
  2187. // Return the deserialized web window of value.
  2188. // FIXME: -> instance of Array
  2189. // FIXME: -> instance of Object
  2190. // Return clone an object algorithm with session, value and seen, and the JSON deserialize algorithm as the clone algorithm.
  2191. dbgln("FIXME: Implement JSON deserialize for: {}", value);
  2192. return JS::JSONObject::parse_json_value(vm, value);
  2193. }
  2194. // https://w3c.github.io/webdriver/#dfn-extract-the-script-arguments-from-a-request
  2195. ErrorOr<WebDriverConnection::ScriptArguments, Web::WebDriver::Error> WebDriverConnection::extract_the_script_arguments_from_a_request(JS::VM& vm, JsonValue const& payload)
  2196. {
  2197. // Creating JSON objects below requires an execution context.
  2198. Web::HTML::TemporaryExecutionContext execution_context { current_browsing_context().active_document()->realm() };
  2199. // 1. Let script be the result of getting a property named script from the parameters.
  2200. // 2. If script is not a String, return error with error code invalid argument.
  2201. auto script = TRY(Web::WebDriver::get_property(payload, "script"sv));
  2202. // 3. Let args be the result of getting a property named args from the parameters.
  2203. // 4. If args is not an Array return error with error code invalid argument.
  2204. auto const& args = *TRY(Web::WebDriver::get_property<JsonArray const*>(payload, "args"sv));
  2205. // 5. Let arguments be the result of calling the JSON deserialize algorithm with arguments args.
  2206. auto arguments = JS::MarkedVector<JS::Value> { vm.heap() };
  2207. TRY(args.try_for_each([&](auto const& arg) -> ErrorOr<void, Web::WebDriver::Error> {
  2208. arguments.append(TRY(json_deserialize(vm, current_browsing_context(), arg)));
  2209. return {};
  2210. }));
  2211. // 6. Return success with data script and arguments.
  2212. return ScriptArguments { move(script), move(arguments) };
  2213. }
  2214. // https://w3c.github.io/webdriver/#dfn-delete-cookies
  2215. void WebDriverConnection::delete_cookies(Optional<StringView> const& name)
  2216. {
  2217. // For each cookie among all associated cookies of the current browsing context’s active document, un the substeps of the first matching condition:
  2218. auto* document = current_browsing_context().active_document();
  2219. for (auto& cookie : current_browsing_context().page().client().page_did_request_all_cookies(document->url())) {
  2220. // -> name is undefined
  2221. // -> name is equal to cookie name
  2222. if (!name.has_value() || name.value() == cookie.name) {
  2223. // Set the cookie expiry time to a Unix timestamp in the past.
  2224. cookie.expiry_time = UnixDateTime::earliest();
  2225. current_browsing_context().page().client().page_did_update_cookie(move(cookie));
  2226. }
  2227. // -> Otherwise
  2228. // Do nothing.
  2229. }
  2230. }
  2231. // https://w3c.github.io/webdriver/#dfn-calculate-the-absolute-position
  2232. Gfx::IntPoint WebDriverConnection::calculate_absolute_position_of_element(JS::NonnullGCPtr<Web::Geometry::DOMRect> rect)
  2233. {
  2234. // 1. Let rect be the value returned by calling getBoundingClientRect().
  2235. // 2. Let window be the associated window of current top-level browsing context.
  2236. auto const* window = current_top_level_browsing_context()->active_window();
  2237. // 3. Let x be (scrollX of window + rect’s x coordinate).
  2238. auto x = (window ? static_cast<int>(window->scroll_x()) : 0) + static_cast<int>(rect->x());
  2239. // 4. Let y be (scrollY of window + rect’s y coordinate).
  2240. auto y = (window ? static_cast<int>(window->scroll_y()) : 0) + static_cast<int>(rect->y());
  2241. // 5. Return a pair of (x, y).
  2242. return { x, y };
  2243. }
  2244. Gfx::IntRect WebDriverConnection::calculate_absolute_rect_of_element(Web::DOM::Element const& element)
  2245. {
  2246. auto bounding_rect = element.get_bounding_client_rect();
  2247. auto coordinates = calculate_absolute_position_of_element(bounding_rect);
  2248. return {
  2249. coordinates.x(),
  2250. coordinates.y(),
  2251. static_cast<int>(bounding_rect->width()),
  2252. static_cast<int>(bounding_rect->height())
  2253. };
  2254. }
  2255. }