Actions.cpp 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542
  1. /*
  2. * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Enumerate.h>
  7. #include <AK/Find.h>
  8. #include <AK/GenericShorthands.h>
  9. #include <AK/JsonArray.h>
  10. #include <AK/JsonObject.h>
  11. #include <AK/JsonValue.h>
  12. #include <AK/Math.h>
  13. #include <AK/Utf8View.h>
  14. #include <LibWeb/Crypto/Crypto.h>
  15. #include <LibWeb/HTML/BrowsingContext.h>
  16. #include <LibWeb/HTML/EventLoop/EventLoop.h>
  17. #include <LibWeb/HTML/TraversableNavigable.h>
  18. #include <LibWeb/Page/Page.h>
  19. #include <LibWeb/WebDriver/Actions.h>
  20. #include <LibWeb/WebDriver/ElementReference.h>
  21. #include <LibWeb/WebDriver/InputState.h>
  22. #include <LibWeb/WebDriver/Properties.h>
  23. namespace Web::WebDriver {
  24. static Optional<ActionObject::Subtype> action_object_subtype_from_string(StringView action_subtype)
  25. {
  26. if (action_subtype == "pause"sv)
  27. return ActionObject::Subtype::Pause;
  28. if (action_subtype == "keyUp"sv)
  29. return ActionObject::Subtype::KeyUp;
  30. if (action_subtype == "keyDown"sv)
  31. return ActionObject::Subtype::KeyDown;
  32. if (action_subtype == "pointerUp"sv)
  33. return ActionObject::Subtype::PointerUp;
  34. if (action_subtype == "pointerDown"sv)
  35. return ActionObject::Subtype::PointerDown;
  36. if (action_subtype == "pointerMove"sv)
  37. return ActionObject::Subtype::PointerMove;
  38. if (action_subtype == "pointerCancel"sv)
  39. return ActionObject::Subtype::PointerCancel;
  40. if (action_subtype == "scroll"sv)
  41. return ActionObject::Subtype::Scroll;
  42. return {};
  43. }
  44. static ActionObject::Fields fields_from_subtype(ActionObject::Subtype subtype)
  45. {
  46. switch (subtype) {
  47. case ActionObject::Subtype::Pause:
  48. return ActionObject::PauseFields {};
  49. case ActionObject::Subtype::KeyUp:
  50. case ActionObject::Subtype::KeyDown:
  51. return ActionObject::KeyFields {};
  52. case ActionObject::Subtype::PointerUp:
  53. case ActionObject::Subtype::PointerDown:
  54. return ActionObject::PointerUpDownFields {};
  55. case ActionObject::Subtype::PointerMove:
  56. return ActionObject::PointerMoveFields {};
  57. case ActionObject::Subtype::PointerCancel:
  58. return ActionObject::PointerCancelFields {};
  59. case ActionObject::Subtype::Scroll:
  60. return ActionObject::ScrollFields {};
  61. }
  62. VERIFY_NOT_REACHED();
  63. }
  64. ActionObject::ActionObject(String id, InputSourceType type, Subtype subtype)
  65. : id(move(id))
  66. , type(type)
  67. , subtype(subtype)
  68. , fields(fields_from_subtype(subtype))
  69. {
  70. }
  71. void ActionObject::set_pointer_type(PointerInputSource::Subtype pointer_type)
  72. {
  73. fields.visit(
  74. [&](OneOf<PointerUpDownFields, PointerMoveFields, PointerCancelFields> auto& fields) {
  75. fields.pointer_type = pointer_type;
  76. },
  77. [](auto const&) { VERIFY_NOT_REACHED(); });
  78. }
  79. static Optional<ActionObject::Origin> determine_origin(ActionsOptions const& actions_options, Optional<JsonValue const&> const& origin)
  80. {
  81. if (!origin.has_value())
  82. return ActionObject::OriginType::Viewport;
  83. if (origin->is_string()) {
  84. if (origin->as_string() == "viewport"sv)
  85. return ActionObject::OriginType::Viewport;
  86. if (origin->as_string() == "pointer"sv)
  87. return ActionObject::OriginType::Pointer;
  88. }
  89. if (origin->is_object()) {
  90. if (actions_options.is_element_origin(origin->as_object()))
  91. return MUST(String::from_byte_string(extract_web_element_reference(origin->as_object())));
  92. }
  93. return {};
  94. }
  95. // https://w3c.github.io/webdriver/#dfn-get-coordinates-relative-to-an-origin
  96. static ErrorOr<CSSPixelPoint, WebDriver::Error> get_coordinates_relative_to_origin(PointerInputSource const& source, CSSPixelPoint offset, CSSPixelRect viewport, ActionObject::Origin const& origin, ActionsOptions const& actions_options)
  97. {
  98. // 1. Run the substeps of the first matching value of origin
  99. auto coordinates = TRY(origin.visit(
  100. [&](ActionObject::OriginType origin) -> ErrorOr<CSSPixelPoint, WebDriver::Error> {
  101. switch (origin) {
  102. // "viewport"
  103. case ActionObject::OriginType::Viewport:
  104. // 1. Let x equal x offset and y equal y offset.
  105. return offset;
  106. // "pointer"
  107. case ActionObject::OriginType::Pointer:
  108. // 1. Let start x be equal to the x property of source.
  109. // 2. Let start y be equal to the y property of source.
  110. // 3. Let x equal start x + x offset and y equal start y + y offset.
  111. return source.position.translated(offset);
  112. }
  113. VERIFY_NOT_REACHED();
  114. },
  115. [&](String const& origin) -> ErrorOr<CSSPixelPoint, WebDriver::Error> {
  116. // Otherwise
  117. // 1. Let element be the result of trying to run actions options' get element origin steps with origin and
  118. // browsing context.
  119. // 2. If element is null, return error with error code no such element.
  120. auto element = TRY(actions_options.get_element_origin(origin));
  121. // 3. Let x element and y element be the result of calculating the in-view center point of element.
  122. auto position = in_view_center_point(element, viewport);
  123. // 4. Let x equal x element + x offset, and y equal y element + y offset.
  124. return position.translated(offset);
  125. }));
  126. // 2. Return (x, y)
  127. return coordinates;
  128. }
  129. // https://w3c.github.io/webdriver/#dfn-process-pointer-parameters
  130. struct PointerParameters {
  131. PointerInputSource::Subtype pointer_type { PointerInputSource::Subtype::Mouse };
  132. };
  133. static ErrorOr<PointerParameters, WebDriver::Error> process_pointer_parameters(Optional<JsonValue const&> const& parameters_data)
  134. {
  135. // 1. Let parameters be the default pointer parameters.
  136. PointerParameters parameters;
  137. // 2. If parameters data is undefined, return success with data parameters.
  138. if (!parameters_data.has_value())
  139. return parameters;
  140. // 3. If parameters data is not an Object, return error with error code invalid argument.
  141. if (!parameters_data->is_object())
  142. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'parameters' is not an Object");
  143. // 4. Let pointer type be the result of getting a property named "pointerType" from parameters data.
  144. auto pointer_type = TRY(get_optional_property(parameters_data->as_object(), "pointerType"sv));
  145. // 5. If pointer type is not undefined:
  146. if (pointer_type.has_value()) {
  147. // 1. If pointer type does not have one of the values "mouse", "pen", or "touch", return error with error code
  148. // invalid argument.
  149. auto parsed_pointer_type = pointer_input_source_subtype_from_string(*pointer_type);
  150. if (!parsed_pointer_type.has_value())
  151. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'pointerType' must be one of 'mouse', 'pen', or 'touch'");
  152. // 2. Set the pointerType property of parameters to pointer type.
  153. parameters.pointer_type = *parsed_pointer_type;
  154. }
  155. // 6. Return success with data parameters.
  156. return parameters;
  157. }
  158. // https://w3c.github.io/webdriver/#dfn-process-a-pause-action
  159. static ErrorOr<void, WebDriver::Error> process_pause_action(JsonObject const& action_item, ActionObject& action)
  160. {
  161. // 1. Let duration be the result of getting the property "duration" from action item.
  162. // 2. If duration is not undefined and duration is not an Integer greater than or equal to 0, return error with error code invalid argument.
  163. // 3. Set the duration property of action to duration.
  164. if (auto duration = TRY(get_optional_property_with_limits<i64>(action_item, "duration"sv, 0, {})); duration.has_value())
  165. action.pause_fields().duration = AK::Duration::from_milliseconds(*duration);
  166. // 4. Return success with data action.
  167. return {};
  168. }
  169. // https://w3c.github.io/webdriver/#dfn-process-a-null-action
  170. static ErrorOr<ActionObject, WebDriver::Error> process_null_action(String id, JsonObject const& action_item)
  171. {
  172. // 1. Let subtype be the result of getting a property named "type" from action item.
  173. auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv)));
  174. // 2. If subtype is not "pause", return error with error code invalid argument.
  175. if (subtype != ActionObject::Subtype::Pause)
  176. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be 'pause'");
  177. // 3. Let action be an action object constructed with arguments id, "none", and subtype.
  178. ActionObject action { move(id), InputSourceType::None, *subtype };
  179. // 4. Let result be the result of trying to process a pause action with arguments action item and action.
  180. TRY(process_pause_action(action_item, action));
  181. // 5. Return result.
  182. return action;
  183. }
  184. // https://w3c.github.io/webdriver/#dfn-process-a-key-action
  185. static ErrorOr<ActionObject, WebDriver::Error> process_key_action(String id, JsonObject const& action_item)
  186. {
  187. using enum ActionObject::Subtype;
  188. // 1. Let subtype be the result of getting a property named "type" from action item.
  189. auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv)));
  190. // 2. If subtype is not one of the values "keyUp", "keyDown", or "pause", return an error with error code invalid argument.
  191. if (!first_is_one_of(subtype, KeyUp, KeyDown, Pause))
  192. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'keyUp', 'keyDown', or 'pause'");
  193. // 3. Let action be an action object constructed with arguments id, "key", and subtype.
  194. ActionObject action { move(id), InputSourceType::Key, *subtype };
  195. // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action
  196. // item and action, and return result.
  197. if (subtype == Pause) {
  198. TRY(process_pause_action(action_item, action));
  199. return action;
  200. }
  201. // 5. Let key be the result of getting a property named "value" from action item.
  202. auto key = TRY(get_property(action_item, "value"sv));
  203. // 6. If key is not a String containing a single unicode code point [or grapheme cluster?] return error with error
  204. // code invalid argument.
  205. Utf8View utf8_key { key };
  206. if (utf8_key.length() != 1) {
  207. // FIXME: The spec seems undecided on whether grapheme clusters should be supported. Update this step to check
  208. // for graphemes if we end up needing to support them. We would also need to update Page's key event
  209. // handlers to support multi-code point events.
  210. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'value' must be a single code point");
  211. }
  212. // 7. Set the value property on action to key.
  213. action.key_fields().value = *utf8_key.begin();
  214. // 8. Return success with data action.
  215. return action;
  216. }
  217. // Common steps between:
  218. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-up-or-pointer-down-action
  219. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-move-action
  220. static ErrorOr<void, WebDriver::Error> process_pointer_action_common(JsonObject const& action_item, ActionObject::PointerFields& fields)
  221. {
  222. // 4. Let width be the result of getting the property width from action item.
  223. // 5. If width is not undefined and width is not a Number greater than or equal to 0 return error with error code invalid argument.
  224. // 6. Set the width property of action to width.
  225. fields.width = TRY(get_optional_property_with_limits<double>(action_item, "width"sv, 0.0, {}));
  226. // 7. Let height be the result of getting the property height from action item.
  227. // 8. If height is not undefined and height is not a Number greater than or equal to 0 return error with error code invalid argument.
  228. // 9. Set the height property of action to height.
  229. fields.height = TRY(get_optional_property_with_limits<double>(action_item, "height"sv, 0.0, {}));
  230. // 10. Let pressure be the result of getting the property pressure from action item.
  231. // 11. If pressure is not undefined and pressure is not a Number greater than or equal to 0 and less than or equal to 1 return error with error code invalid argument.
  232. // 12. Set the pressure property of action to pressure.
  233. fields.pressure = TRY(get_optional_property_with_limits<double>(action_item, "pressure"sv, 0.0, 1.0));
  234. // 13. Let tangentialPressure be the result of getting the property tangentialPressure from action item.
  235. // 14. If tangentialPressure is not undefined and tangentialPressure is not a Number greater than or equal to -1 and less than or equal to 1 return error with error code invalid argument.
  236. // 15. Set the tangentialPressure property of action to tangentialPressure.
  237. fields.tangential_pressure = TRY(get_optional_property_with_limits<double>(action_item, "tangentialPressure"sv, -1.0, 1.0));
  238. // 16. Let tiltX be the result of getting the property tiltX from action item.
  239. // 17. If tiltX is not undefined and tiltX is not an Integer greater than or equal to -90 and less than or equal to 90 return error with error code invalid argument.
  240. // 18. Set the tiltX property of action to tiltX.
  241. fields.tilt_x = TRY(get_optional_property_with_limits<i32>(action_item, "tiltX"sv, -90, 90));
  242. // 19. Let tiltY be the result of getting the property tiltY from action item.
  243. // 20. If tiltY is not undefined and tiltY is not an Integer greater than or equal to -90 and less than or equal to 90 return error with error code invalid argument.
  244. // 21. Set the tiltY property of action to tiltY.
  245. fields.tilt_y = TRY(get_optional_property_with_limits<i32>(action_item, "tiltY"sv, -90, 90));
  246. // 22. Let twist be the result of getting the property twist from action item.
  247. // 23. If twist is not undefined and twist is not an Integer greater than or equal to 0 and less than or equal to 359 return error with error code invalid argument.
  248. // 24. Set the twist property of action to twist.
  249. fields.twist = TRY(get_optional_property_with_limits<u32>(action_item, "twist"sv, 0, 359));
  250. // 25. Let altitudeAngle be the result of getting the property altitudeAngle from action item.
  251. // 26. If altitudeAngle is not undefined and altitudeAngle is not a Number greater than or equal to 0 and less than or equal to π/2 return error with error code invalid argument.
  252. // 27. Set the altitudeAngle property of action to altitudeAngle.
  253. fields.altitude_angle = TRY(get_optional_property_with_limits<double>(action_item, "altitudeAngle"sv, 0.0, AK::Pi<double> / 2.0));
  254. // 28. Let azimuthAngle be the result of getting the property azimuthAngle from action item.
  255. // 29. If azimuthAngle is not undefined and azimuthAngle is not a Number greater than or equal to 0 and less than or equal to 2π return error with error code invalid argument.
  256. // 30. Set the azimuthAngle property of action to azimuthAngle.
  257. fields.azimuth_angle = TRY(get_optional_property_with_limits<double>(action_item, "azimuthAngle"sv, 0.0, AK::Pi<double> * 2.0));
  258. // 31. Return success with data null.
  259. return {};
  260. }
  261. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-up-or-pointer-down-action
  262. static ErrorOr<void, WebDriver::Error> process_pointer_up_or_down_action(JsonObject const& action_item, ActionObject& action)
  263. {
  264. auto& fields = action.pointer_up_down_fields();
  265. // 1. Let button be the result of getting the property button from action item.
  266. // 2. If button is not an Integer greater than or equal to 0 return error with error code invalid argument.
  267. // 3. Set the button property of action to button.
  268. fields.button = UIEvents::button_code_to_mouse_button(TRY(get_property_with_limits<i16>(action_item, "button"sv, 0, {})));
  269. return process_pointer_action_common(action_item, fields);
  270. }
  271. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-move-action
  272. static ErrorOr<void, WebDriver::Error> process_pointer_move_action(JsonObject const& action_item, ActionObject& action, ActionsOptions const& actions_options)
  273. {
  274. auto& fields = action.pointer_move_fields();
  275. // 1. Let duration be the result of getting the property duration from action item.
  276. // 2. If duration is not undefined and duration is not an Integer greater than or equal to 0, return error with error code invalid argument.
  277. // 3. Set the duration property of action to duration.
  278. if (auto duration = TRY(get_optional_property_with_limits<i64>(action_item, "duration"sv, 0, {})); duration.has_value())
  279. fields.duration = AK::Duration::from_milliseconds(*duration);
  280. // 4. Let origin be the result of getting the property origin from action item.
  281. // 5. If origin is undefined let origin equal "viewport".
  282. auto origin = determine_origin(actions_options, action_item.get("origin"sv));
  283. // 6. If origin is not equal to "viewport" or "pointer", and actions options is element origin steps given origin
  284. // return false, return error with error code invalid argument.
  285. if (!origin.has_value())
  286. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'origin' must be 'viewport', 'pointer', or an element origin");
  287. // 7. Set the origin property of action to origin.
  288. fields.origin = origin.release_value();
  289. // 8. Let x be the result of getting the property x from action item.
  290. // 9. If x is not an Integer, return error with error code invalid argument.
  291. // 10. Set the x property of action to x.
  292. fields.position.set_x(TRY(get_property<i32>(action_item, "x"sv)));
  293. // 11. Let y be the result of getting the property y from action item.
  294. // 12. If y is not an Integer, return error with error code invalid argument.
  295. // 13. Set the y property of action to y.
  296. fields.position.set_y(TRY(get_property<i32>(action_item, "y"sv)));
  297. return process_pointer_action_common(action_item, fields);
  298. }
  299. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-action
  300. static ErrorOr<ActionObject, WebDriver::Error> process_pointer_action(String id, PointerParameters const& parameters, JsonObject const& action_item, ActionsOptions const& actions_options)
  301. {
  302. using enum ActionObject::Subtype;
  303. // 1. Let subtype be the result of getting a property named "type" from action item.
  304. auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv)));
  305. // 2. If subtype is not one of the values "pause", "pointerUp", "pointerDown", "pointerMove", or "pointerCancel", return an error with error code invalid argument.
  306. if (!first_is_one_of(subtype, Pause, PointerUp, PointerDown, PointerMove, PointerCancel))
  307. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'pause', 'pointerUp', 'pointerDown', 'pointerMove', or 'pointerCancel'");
  308. // 3. Let action be an action object constructed with arguments id, "pointer", and subtype.
  309. ActionObject action { move(id), InputSourceType::Pointer, *subtype };
  310. // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action
  311. // item, action, and actions options, and return result.
  312. if (subtype == Pause) {
  313. TRY(process_pause_action(action_item, action));
  314. return action;
  315. }
  316. // 5. Set the pointerType property of action equal to the pointerType property of parameters.
  317. action.set_pointer_type(parameters.pointer_type);
  318. // 6. If subtype is "pointerUp" or "pointerDown", process a pointer up or pointer down action with arguments action
  319. // item and action. If doing so results in an error, return that error.
  320. if (subtype == PointerUp || subtype == PointerDown) {
  321. TRY(process_pointer_up_or_down_action(action_item, action));
  322. }
  323. // 7. If subtype is "pointerMove" process a pointer move action with arguments action item, action, and actions
  324. // options. If doing so results in an error, return that error.
  325. else if (subtype == PointerMove) {
  326. TRY(process_pointer_move_action(action_item, action, actions_options));
  327. }
  328. // 8. If subtype is "pointerCancel" process a pointer cancel action. If doing so results in an error, return that error.
  329. else if (subtype == PointerCancel) {
  330. // FIXME: There are no spec steps to "process a pointer cancel action" yet.
  331. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "pointerCancel events not implemented"sv);
  332. }
  333. // 9. Return success with data action.
  334. return action;
  335. }
  336. // https://w3c.github.io/webdriver/#dfn-process-a-wheel-action
  337. static ErrorOr<ActionObject, WebDriver::Error> process_wheel_action(String id, JsonObject const& action_item, ActionsOptions const& actions_options)
  338. {
  339. using enum ActionObject::Subtype;
  340. // 1. Let subtype be the result of getting a property named "type" from action item.
  341. auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv)));
  342. // 2. If subtype is not the value "pause", or "scroll", return an error with error code invalid argument.
  343. if (!first_is_one_of(subtype, Pause, Scroll))
  344. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'pause' or 'scroll'");
  345. // 3. Let action be an action object constructed with arguments id, "wheel", and subtype.
  346. ActionObject action { move(id), InputSourceType::Wheel, *subtype };
  347. // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action
  348. // item and action, and return result.
  349. if (subtype == Pause) {
  350. TRY(process_pause_action(action_item, action));
  351. return action;
  352. }
  353. auto& fields = action.scroll_fields();
  354. // 5. Let duration be the result of getting a property named "duration" from action item.
  355. // 6. If duration is not undefined and duration is not an Integer greater than or equal to 0, return error with error code invalid argument.
  356. // 7. Set the duration property of action to duration.
  357. if (auto duration = TRY(get_optional_property_with_limits<i64>(action_item, "duration"sv, 0, {})); duration.has_value())
  358. fields.duration = AK::Duration::from_milliseconds(*duration);
  359. // 8. Let origin be the result of getting the property origin from action item.
  360. // 9. If origin is undefined let origin equal "viewport".
  361. auto origin = determine_origin(actions_options, action_item.get("origin"sv));
  362. // 10. If origin is not equal to "viewport", or actions options' is element origin steps given origin return false,
  363. // return error with error code invalid argument.
  364. if (!origin.has_value() || origin == ActionObject::OriginType::Pointer)
  365. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'origin' must be 'viewport' or an element origin");
  366. // 11. Set the origin property of action to origin.
  367. fields.origin = origin.release_value();
  368. // 12. Let x be the result of getting the property x from action item.
  369. // 13. If x is not an Integer, return error with error code invalid argument.
  370. // 14. Set the x property of action to x.
  371. fields.x = TRY(get_property<i64>(action_item, "x"sv));
  372. // 15. Let y be the result of getting the property y from action item.
  373. // 16. If y is not an Integer, return error with error code invalid argument.
  374. // 17. Set the y property of action to y.
  375. fields.y = TRY(get_property<i64>(action_item, "y"sv));
  376. // 18. Let deltaX be the result of getting the property deltaX from action item.
  377. // 19. If deltaX is not an Integer, return error with error code invalid argument.
  378. // 20. Set the deltaX property of action to deltaX.
  379. fields.delta_x = TRY(get_property<i64>(action_item, "deltaX"sv));
  380. // 21. Let deltaY be the result of getting the property deltaY from action item.
  381. // 22. If deltaY is not an Integer, return error with error code invalid argument.
  382. // 23. Set the deltaY property of action to deltaY.
  383. fields.delta_y = TRY(get_property<i64>(action_item, "deltaY"sv));
  384. // 24. Return success with data action.
  385. return action;
  386. }
  387. // https://w3c.github.io/webdriver/#dfn-process-an-input-source-action-sequence
  388. static ErrorOr<Vector<ActionObject>, WebDriver::Error> process_input_source_action_sequence(InputState& input_state, JsonValue const& action_sequence, ActionsOptions const& actions_options)
  389. {
  390. // 1. Let type be the result of getting a property named "type" from action sequence.
  391. auto type = input_source_type_from_string(TRY(get_property(action_sequence, "type"sv)));
  392. // 2. If type is not "key", "pointer", "wheel", or "none", return an error with error code invalid argument.
  393. if (!type.has_value())
  394. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'key', 'pointer', 'wheel', or 'none'");
  395. // 3. Let id be the result of getting the property "id" from action sequence.
  396. // 4. If id is undefined or is not a String, return error with error code invalid argument.
  397. auto const id = MUST(String::from_byte_string(TRY(get_property(action_sequence, "id"sv))));
  398. // 5. If type is equal to "pointer", let parameters data be the result of getting the property "parameters" from
  399. // action sequence. Then let parameters be the result of trying to process pointer parameters with argument
  400. // parameters data.
  401. Optional<PointerParameters> parameters;
  402. Optional<PointerInputSource::Subtype> subtype;
  403. if (type == InputSourceType::Pointer) {
  404. parameters = TRY(process_pointer_parameters(action_sequence.as_object().get("parameters"sv)));
  405. subtype = parameters->pointer_type;
  406. }
  407. // 6. Let source be the result of trying to get or create an input source given input state, type and id.
  408. auto const& source = *TRY(get_or_create_input_source(input_state, *type, id, subtype));
  409. // 7. If parameters is not undefined, then if its pointerType property is not equal to source's subtype property,
  410. // return an error with error code invalid argument.
  411. if (auto const* pointer_input_source = source.get_pointer<PointerInputSource>(); pointer_input_source && parameters.has_value()) {
  412. if (parameters->pointer_type != pointer_input_source->subtype)
  413. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Invalid 'pointerType' property");
  414. }
  415. // 8. Let action items be the result of getting a property named "actions" from action sequence.
  416. // 9. If action items is not an Array, return error with error code invalid argument.
  417. auto const& action_items = *TRY(get_property<JsonArray const*>(action_sequence, "actions"sv));
  418. // 10. Let actions be a new list.
  419. Vector<ActionObject> actions;
  420. // 11. For each action item in action items:
  421. TRY(action_items.try_for_each([&](auto const& action_item) -> ErrorOr<void, WebDriver::Error> {
  422. // 1. If action item is not an Object return error with error code invalid argument.
  423. if (!action_item.is_object())
  424. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'actions' item is not an Object");
  425. auto action = TRY([&]() {
  426. switch (*type) {
  427. // 2. If type is "none" let action be the result of trying to process a null action with parameters id, and
  428. // action item.
  429. case InputSourceType::None:
  430. return process_null_action(id, action_item.as_object());
  431. // 3. Otherwise, if type is "key" let action be the result of trying to process a key action with parameters
  432. // id, and action item.
  433. case InputSourceType::Key:
  434. return process_key_action(id, action_item.as_object());
  435. // 4. Otherwise, if type is "pointer" let action be the result of trying to process a pointer action with
  436. // parameters id, parameters, action item, and actions options.
  437. case InputSourceType::Pointer:
  438. return process_pointer_action(id, *parameters, action_item.as_object(), actions_options);
  439. // 5. Otherwise, if type is "wheel" let action be the result of trying to process a wheel action with
  440. // parameters id, and action item, and actions options.
  441. case InputSourceType::Wheel:
  442. return process_wheel_action(id, action_item.as_object(), actions_options);
  443. }
  444. VERIFY_NOT_REACHED();
  445. }());
  446. // 6. Append action to actions.
  447. actions.append(move(action));
  448. return {};
  449. }));
  450. // 12. Return success with data actions.
  451. return actions;
  452. }
  453. // https://w3c.github.io/webdriver/#dfn-extract-an-action-sequence
  454. ErrorOr<Vector<Vector<ActionObject>>, WebDriver::Error> extract_an_action_sequence(InputState& input_state, JsonValue const& parameters, ActionsOptions const& actions_options)
  455. {
  456. // 1. Let actions be the result of getting a property named "actions" from parameters.
  457. // 2. If actions is undefined or is not an Array, return error with error code invalid argument.
  458. auto const& actions = *TRY(get_property<JsonArray const*>(parameters, "actions"sv));
  459. // 3. Let actions by tick be an empty List.
  460. Vector<Vector<ActionObject>> actions_by_tick;
  461. // 4. For each value action sequence corresponding to an indexed property in actions:
  462. TRY(actions.try_for_each([&](auto const& action_sequence) -> ErrorOr<void, WebDriver::Error> {
  463. // 1. Let source actions be the result of trying to process an input source action sequence given input state,
  464. // action sequence, and actions options.
  465. auto source_actions = TRY(process_input_source_action_sequence(input_state, action_sequence, actions_options));
  466. // 2. For each action in source actions:
  467. for (auto [i, action] : enumerate(source_actions)) {
  468. // 1. Let i be the zero-based index of action in source actions.
  469. // 2. If the length of actions by tick is less than i + 1, append a new List to actions by tick.
  470. if (actions_by_tick.size() < (i + 1))
  471. actions_by_tick.resize(i + 1);
  472. // 3. Append action to the List at index i in actions by tick.
  473. actions_by_tick[i].append(move(action));
  474. }
  475. return {};
  476. }));
  477. // 5. Return success with data actions by tick.
  478. return actions_by_tick;
  479. }
  480. // https://w3c.github.io/webdriver/#dfn-computing-the-tick-duration
  481. static AK::Duration compute_tick_duration(ReadonlySpan<ActionObject> tick_actions)
  482. {
  483. // 1. Let max duration be 0.
  484. auto max_duration = AK::Duration::zero();
  485. // 2. For each action object in tick actions:
  486. for (auto const& action_object : tick_actions) {
  487. // 1. let duration be undefined.
  488. Optional<AK::Duration> duration;
  489. // 2. If action object has subtype property set to "pause" or action object has type property set to "pointer"
  490. // and subtype property set to "pointerMove", or action object has type property set to "wheel" and subtype
  491. // property set to "scroll", let duration be equal to the duration property of action object.
  492. action_object.fields.visit(
  493. [&](OneOf<ActionObject::PauseFields, ActionObject::PointerMoveFields, ActionObject::ScrollFields> auto const& fields) {
  494. duration = fields.duration;
  495. },
  496. [](auto const&) {});
  497. // 3. If duration is not undefined, and duration is greater than max duration, let max duration be equal to duration.
  498. if (duration.has_value())
  499. max_duration = max(max_duration, *duration);
  500. }
  501. // 3. Return max duration.
  502. return max_duration;
  503. }
  504. // https://w3c.github.io/webdriver/#dfn-dispatch-a-pause-action
  505. static void dispatch_pause_action()
  506. {
  507. // 1. Return success with data null.
  508. }
  509. // https://w3c.github.io/webdriver/#dfn-normalized-key-value
  510. static String normalized_key_value(u32 key)
  511. {
  512. // The normalized key value for a raw key key is, if key appears in the table below, the string value in the second
  513. // column on the row containing key's unicode code point in the first column, otherwise it is key.
  514. // clang-format off
  515. switch (key) {
  516. case 0xE000: return "Unidentified"_string;
  517. case 0xE001: return "Cancel"_string;
  518. case 0xE002: return "Help"_string;
  519. case 0xE003: return "Backspace"_string;
  520. case 0xE004: return "Tab"_string;
  521. case 0xE005: return "Clear"_string;
  522. case 0xE006: return "Return"_string;
  523. case 0xE007: return "Enter"_string;
  524. case 0xE008: return "Shift"_string;
  525. case 0xE009: return "Control"_string;
  526. case 0xE00A: return "Alt"_string;
  527. case 0xE00B: return "Pause"_string;
  528. case 0xE00C: return "Escape"_string;
  529. case 0xE00D: return " "_string;
  530. case 0xE00E: return "PageUp"_string;
  531. case 0xE00F: return "PageDown"_string;
  532. case 0xE010: return "End"_string;
  533. case 0xE011: return "Home"_string;
  534. case 0xE012: return "ArrowLeft"_string;
  535. case 0xE013: return "ArrowUp"_string;
  536. case 0xE014: return "ArrowRight"_string;
  537. case 0xE015: return "ArrowDown"_string;
  538. case 0xE016: return "Insert"_string;
  539. case 0xE017: return "Delete"_string;
  540. case 0xE018: return ";"_string;
  541. case 0xE019: return "="_string;
  542. case 0xE01A: return "0"_string;
  543. case 0xE01B: return "1"_string;
  544. case 0xE01C: return "2"_string;
  545. case 0xE01D: return "3"_string;
  546. case 0xE01E: return "4"_string;
  547. case 0xE01F: return "5"_string;
  548. case 0xE020: return "6"_string;
  549. case 0xE021: return "7"_string;
  550. case 0xE022: return "8"_string;
  551. case 0xE023: return "9"_string;
  552. case 0xE024: return "*"_string;
  553. case 0xE025: return "+"_string;
  554. case 0xE026: return ","_string;
  555. case 0xE027: return "-"_string;
  556. case 0xE028: return "."_string;
  557. case 0xE029: return "/"_string;
  558. case 0xE031: return "F1"_string;
  559. case 0xE032: return "F2"_string;
  560. case 0xE033: return "F3"_string;
  561. case 0xE034: return "F4"_string;
  562. case 0xE035: return "F5"_string;
  563. case 0xE036: return "F6"_string;
  564. case 0xE037: return "F7"_string;
  565. case 0xE038: return "F8"_string;
  566. case 0xE039: return "F9"_string;
  567. case 0xE03A: return "F10"_string;
  568. case 0xE03B: return "F11"_string;
  569. case 0xE03C: return "F12"_string;
  570. case 0xE03D: return "Meta"_string;
  571. case 0xE040: return "ZenkakuHankaku"_string;
  572. case 0xE050: return "Shift"_string;
  573. case 0xE051: return "Control"_string;
  574. case 0xE052: return "Alt"_string;
  575. case 0xE053: return "Meta"_string;
  576. case 0xE054: return "PageUp"_string;
  577. case 0xE055: return "PageDown"_string;
  578. case 0xE056: return "End"_string;
  579. case 0xE057: return "Home"_string;
  580. case 0xE058: return "ArrowLeft"_string;
  581. case 0xE059: return "ArrowUp"_string;
  582. case 0xE05A: return "ArrowRight"_string;
  583. case 0xE05B: return "ArrowDown"_string;
  584. case 0xE05C: return "Insert"_string;
  585. case 0xE05D: return "Delete"_string;
  586. }
  587. // clang-format on
  588. return String::from_code_point(key);
  589. }
  590. struct KeyCodeData {
  591. u32 key { 0 };
  592. Optional<u32> alternate_key {};
  593. UIEvents::KeyCode code { UIEvents::KeyCode::Key_Invalid };
  594. UIEvents::KeyModifier modifiers { UIEvents::KeyModifier::Mod_None };
  595. };
  596. // https://w3c.github.io/webdriver/#dfn-code
  597. static KeyCodeData key_code_data(u32 code_point)
  598. {
  599. // The code for key is the value in the last column of the following table on the row with key in either the first
  600. // or second column, if any such row exists, otherwise it is undefined.
  601. static auto key_code_data = to_array<KeyCodeData>({
  602. { '`', '~', UIEvents::KeyCode::Key_Backtick },
  603. { '\\', '|', UIEvents::KeyCode::Key_Backslash },
  604. { 0xE003, {}, UIEvents::KeyCode::Key_Backspace },
  605. { '[', '{', UIEvents::KeyCode::Key_LeftBracket },
  606. { ']', '}', UIEvents::KeyCode::Key_RightBracket },
  607. { ',', '<', UIEvents::KeyCode::Key_Comma },
  608. { '0', ')', UIEvents::KeyCode::Key_0 },
  609. { '1', '!', UIEvents::KeyCode::Key_1 },
  610. { '2', '@', UIEvents::KeyCode::Key_2 },
  611. { '3', '#', UIEvents::KeyCode::Key_3 },
  612. { '4', '$', UIEvents::KeyCode::Key_4 },
  613. { '5', '%', UIEvents::KeyCode::Key_5 },
  614. { '6', '^', UIEvents::KeyCode::Key_6 },
  615. { '7', '&', UIEvents::KeyCode::Key_7 },
  616. { '8', '*', UIEvents::KeyCode::Key_8 },
  617. { '9', '(', UIEvents::KeyCode::Key_9 },
  618. { '=', '+', UIEvents::KeyCode::Key_Equal },
  619. // FIXME: "IntlBackslash"
  620. { 'a', 'A', UIEvents::KeyCode::Key_A },
  621. { 'b', 'B', UIEvents::KeyCode::Key_B },
  622. { 'c', 'C', UIEvents::KeyCode::Key_C },
  623. { 'd', 'D', UIEvents::KeyCode::Key_D },
  624. { 'e', 'E', UIEvents::KeyCode::Key_E },
  625. { 'f', 'F', UIEvents::KeyCode::Key_F },
  626. { 'g', 'G', UIEvents::KeyCode::Key_G },
  627. { 'h', 'H', UIEvents::KeyCode::Key_H },
  628. { 'i', 'I', UIEvents::KeyCode::Key_I },
  629. { 'j', 'J', UIEvents::KeyCode::Key_J },
  630. { 'k', 'K', UIEvents::KeyCode::Key_K },
  631. { 'l', 'L', UIEvents::KeyCode::Key_L },
  632. { 'm', 'M', UIEvents::KeyCode::Key_M },
  633. { 'n', 'N', UIEvents::KeyCode::Key_N },
  634. { 'o', 'O', UIEvents::KeyCode::Key_O },
  635. { 'p', 'P', UIEvents::KeyCode::Key_P },
  636. { 'q', 'Q', UIEvents::KeyCode::Key_Q },
  637. { 'r', 'R', UIEvents::KeyCode::Key_R },
  638. { 's', 'S', UIEvents::KeyCode::Key_S },
  639. { 't', 'T', UIEvents::KeyCode::Key_T },
  640. { 'u', 'U', UIEvents::KeyCode::Key_U },
  641. { 'v', 'V', UIEvents::KeyCode::Key_V },
  642. { 'w', 'W', UIEvents::KeyCode::Key_W },
  643. { 'x', 'X', UIEvents::KeyCode::Key_X },
  644. { 'y', 'Y', UIEvents::KeyCode::Key_Y },
  645. { 'z', 'Z', UIEvents::KeyCode::Key_Z },
  646. { '-', '_', UIEvents::KeyCode::Key_Minus },
  647. { '.', '>', UIEvents::KeyCode::Key_Period },
  648. { '\'', '"', UIEvents::KeyCode::Key_Apostrophe },
  649. { ';', ':', UIEvents::KeyCode::Key_Semicolon },
  650. { '/', '?', UIEvents::KeyCode::Key_Slash },
  651. { ' ', {}, UIEvents::KeyCode::Key_Space },
  652. { 0xE00A, {}, UIEvents::KeyCode::Key_LeftAlt },
  653. { 0xE052, {}, UIEvents::KeyCode::Key_RightAlt },
  654. { 0xE009, {}, UIEvents::KeyCode::Key_LeftControl },
  655. { 0xE051, {}, UIEvents::KeyCode::Key_RightControl },
  656. { 0xE006, {}, UIEvents::KeyCode::Key_Return },
  657. { 0xE00B, {}, UIEvents::KeyCode::Key_PauseBreak },
  658. { 0xE03D, {}, UIEvents::KeyCode::Key_LeftSuper },
  659. { 0xE053, {}, UIEvents::KeyCode::Key_RightSuper },
  660. { 0xE008, {}, UIEvents::KeyCode::Key_LeftShift },
  661. { 0xE050, {}, UIEvents::KeyCode::Key_RightShift },
  662. { 0xE00D, {}, UIEvents::KeyCode::Key_Space },
  663. { 0xE004, {}, UIEvents::KeyCode::Key_Tab },
  664. { 0xE017, {}, UIEvents::KeyCode::Key_Delete },
  665. { 0xE010, {}, UIEvents::KeyCode::Key_End },
  666. // FIXME: "Help"
  667. { 0xE011, {}, UIEvents::KeyCode::Key_Home },
  668. { 0xE016, {}, UIEvents::KeyCode::Key_Insert },
  669. { 0xE00F, {}, UIEvents::KeyCode::Key_PageDown },
  670. { 0xE00E, {}, UIEvents::KeyCode::Key_PageUp },
  671. { 0xE015, {}, UIEvents::KeyCode::Key_Down },
  672. { 0xE012, {}, UIEvents::KeyCode::Key_Left },
  673. { 0xE014, {}, UIEvents::KeyCode::Key_Right },
  674. { 0xE013, {}, UIEvents::KeyCode::Key_Up },
  675. { 0xE00C, {}, UIEvents::KeyCode::Key_Escape },
  676. { 0xE031, {}, UIEvents::KeyCode::Key_F1 },
  677. { 0xE032, {}, UIEvents::KeyCode::Key_F2 },
  678. { 0xE033, {}, UIEvents::KeyCode::Key_F3 },
  679. { 0xE034, {}, UIEvents::KeyCode::Key_F4 },
  680. { 0xE035, {}, UIEvents::KeyCode::Key_F5 },
  681. { 0xE036, {}, UIEvents::KeyCode::Key_F6 },
  682. { 0xE037, {}, UIEvents::KeyCode::Key_F7 },
  683. { 0xE038, {}, UIEvents::KeyCode::Key_F8 },
  684. { 0xE039, {}, UIEvents::KeyCode::Key_F9 },
  685. { 0xE03A, {}, UIEvents::KeyCode::Key_F10 },
  686. { 0xE03B, {}, UIEvents::KeyCode::Key_F11 },
  687. { 0xE03C, {}, UIEvents::KeyCode::Key_F12 },
  688. { 0xE019, {}, UIEvents::KeyCode::Key_Equal, UIEvents::KeyModifier::Mod_Keypad },
  689. { 0xE01A, 0xE05C, UIEvents::KeyCode::Key_0, UIEvents::KeyModifier::Mod_Keypad },
  690. { 0xE01B, 0xE056, UIEvents::KeyCode::Key_1, UIEvents::KeyModifier::Mod_Keypad },
  691. { 0xE01C, 0xE05B, UIEvents::KeyCode::Key_2, UIEvents::KeyModifier::Mod_Keypad },
  692. { 0xE01D, 0xE055, UIEvents::KeyCode::Key_3, UIEvents::KeyModifier::Mod_Keypad },
  693. { 0xE01E, 0xE058, UIEvents::KeyCode::Key_4, UIEvents::KeyModifier::Mod_Keypad },
  694. { 0xE01F, {}, UIEvents::KeyCode::Key_5, UIEvents::KeyModifier::Mod_Keypad },
  695. { 0xE020, 0xE05A, UIEvents::KeyCode::Key_6, UIEvents::KeyModifier::Mod_Keypad },
  696. { 0xE021, 0xE057, UIEvents::KeyCode::Key_7, UIEvents::KeyModifier::Mod_Keypad },
  697. { 0xE022, 0xE059, UIEvents::KeyCode::Key_8, UIEvents::KeyModifier::Mod_Keypad },
  698. { 0xE023, 0xE054, UIEvents::KeyCode::Key_9, UIEvents::KeyModifier::Mod_Keypad },
  699. { 0xE025, {}, UIEvents::KeyCode::Key_Plus, UIEvents::KeyModifier::Mod_Keypad },
  700. { 0xE026, {}, UIEvents::KeyCode::Key_Comma, UIEvents::KeyModifier::Mod_Keypad },
  701. { 0xE028, 0xE05D, UIEvents::KeyCode::Key_Period, UIEvents::KeyModifier::Mod_Keypad },
  702. { 0xE029, {}, UIEvents::KeyCode::Key_Slash, UIEvents::KeyModifier::Mod_Keypad },
  703. { 0xE007, {}, UIEvents::KeyCode::Key_Return, UIEvents::KeyModifier::Mod_Keypad },
  704. { 0xE024, {}, UIEvents::KeyCode::Key_Asterisk, UIEvents::KeyModifier::Mod_Keypad },
  705. { 0xE027, {}, UIEvents::KeyCode::Key_Minus, UIEvents::KeyModifier::Mod_Keypad },
  706. });
  707. auto it = find_if(key_code_data.begin(), key_code_data.end(), [&](auto const& data) {
  708. return data.key == code_point || data.alternate_key == code_point;
  709. });
  710. if (it == key_code_data.end())
  711. return { .key = code_point };
  712. return *it;
  713. }
  714. // https://w3c.github.io/webdriver/#dfn-shifted-character
  715. static bool is_shifted_character(u32 code_point)
  716. {
  717. // A shifted character is one that appears in the second column of the following table.
  718. auto code = key_code_data(code_point);
  719. return code.alternate_key == code_point;
  720. }
  721. struct KeyEvent {
  722. u32 code_point { 0 };
  723. UIEvents::KeyModifier modifiers { UIEvents::KeyModifier::Mod_None };
  724. };
  725. static KeyEvent key_code_to_page_event(u32 code_point, UIEvents::KeyModifier modifiers, KeyCodeData const& code)
  726. {
  727. if (code_point >= 0xE000 && code_point <= 0xE05D) {
  728. code_point = [&]() -> u32 {
  729. // clang-format off
  730. switch (code_point) {
  731. case 0xE00D: return ' ';
  732. case 0xE018: return ';';
  733. case 0xE019: return '=';
  734. case 0xE01A: return '0';
  735. case 0xE01B: return '1';
  736. case 0xE01C: return '2';
  737. case 0xE01D: return '3';
  738. case 0xE01E: return '4';
  739. case 0xE01F: return '5';
  740. case 0xE020: return '6';
  741. case 0xE021: return '7';
  742. case 0xE022: return '8';
  743. case 0xE023: return '9';
  744. case 0xE024: return '*';
  745. case 0xE025: return '+';
  746. case 0xE026: return ',';
  747. case 0xE027: return '-';
  748. case 0xE028: return '.';
  749. case 0xE029: return '/';
  750. default: return 0;
  751. }
  752. // clang-format on
  753. }();
  754. }
  755. modifiers |= code.modifiers;
  756. if (has_flag(modifiers, UIEvents::KeyModifier::Mod_Shift))
  757. code_point = code.alternate_key.value_or(code_point);
  758. return { code_point, modifiers };
  759. }
  760. // https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action
  761. static ErrorOr<void, WebDriver::Error> dispatch_key_down_action(ActionObject::KeyFields const& action_object, KeyInputSource& source, GlobalKeyState const& global_key_state, HTML::BrowsingContext& browsing_context)
  762. {
  763. // 1. Let raw key be equal to the action object's value property.
  764. auto raw_key = action_object.value;
  765. // 2. Let key be equal to the normalized key value for raw key.
  766. auto key = normalized_key_value(raw_key);
  767. // 3. If the source's pressed property contains key, let repeat be true, otherwise let repeat be false.
  768. // FIXME: Add `repeat` support to Page::handle_keydown.
  769. // 4. Let code be the code for raw key.
  770. auto code = key_code_data(raw_key);
  771. // 5. Let location be the key location for raw key.
  772. // 6. Let charCode, keyCode and which be the implementation-specific values of the charCode, keyCode and which
  773. // properties appropriate for a key with key key and location location on a 102 key US keyboard, following the
  774. // guidelines in [UI-EVENTS].
  775. auto modifiers = global_key_state.modifiers();
  776. // 7. If key is "Alt", let source's alt property be true.
  777. if (key == "Alt"sv) {
  778. modifiers |= UIEvents::KeyModifier::Mod_Alt;
  779. source.alt = true;
  780. }
  781. // 8. If key is "Shift", let source's shift property be true.
  782. else if (key == "Shift"sv) {
  783. modifiers |= UIEvents::KeyModifier::Mod_Shift;
  784. source.shift = true;
  785. }
  786. // 9. If key is "Control", let source's ctrl property be true.
  787. else if (key == "Control"sv) {
  788. modifiers |= UIEvents::KeyModifier::Mod_Ctrl;
  789. source.ctrl = true;
  790. }
  791. // 10. If key is "Meta", let source's meta property be true.
  792. else if (key == "Meta"sv) {
  793. modifiers |= UIEvents::KeyModifier::Mod_Super;
  794. source.meta = true;
  795. }
  796. // 11. Add key to source's pressed property.
  797. source.pressed.set(key);
  798. // 12. Perform implementation-specific action dispatch steps on browsing context equivalent to pressing a key on the
  799. // keyboard in accordance with the requirements of [UI-EVENTS], and producing the following events, as appropriate,
  800. // with the specified properties. This will always produce events including at least a keyDown event.
  801. auto event = key_code_to_page_event(raw_key, modifiers, code);
  802. browsing_context.page().handle_keydown(code.code, event.modifiers, event.code_point);
  803. // 13. Return success with data null.
  804. return {};
  805. }
  806. // https://w3c.github.io/webdriver/#dfn-dispatch-a-keyup-action
  807. static ErrorOr<void, WebDriver::Error> dispatch_key_up_action(ActionObject::KeyFields const& action_object, KeyInputSource& source, GlobalKeyState const& global_key_state, HTML::BrowsingContext& browsing_context)
  808. {
  809. // 1. Let raw key be equal to action object's value property.
  810. auto raw_key = action_object.value;
  811. // 2. Let key be equal to the normalized key value for raw key.
  812. auto key = normalized_key_value(raw_key);
  813. // 3. If the source's pressed item does not contain key, return.
  814. if (!source.pressed.contains(key))
  815. return {};
  816. // 4. Let code be the code for raw key.
  817. auto code = key_code_data(raw_key);
  818. // 5. Let location be the key location for raw key.
  819. // 6. Let charCode, keyCode and which be the implementation-specific values of the charCode, keyCode and which
  820. // properties appropriate for a key with key key and location location on a 102 key US keyboard, following the
  821. // guidelines in [UI-EVENTS].
  822. auto modifiers = global_key_state.modifiers();
  823. // 7. If key is "Alt", let source's alt property be false.
  824. if (key == "Alt"sv) {
  825. modifiers &= ~UIEvents::KeyModifier::Mod_Alt;
  826. source.alt = false;
  827. }
  828. // 8. If key is "Shift", let source's shift property be false.
  829. else if (key == "Shift"sv) {
  830. modifiers &= ~UIEvents::KeyModifier::Mod_Shift;
  831. source.shift = false;
  832. }
  833. // 9. If key is "Control", let source's ctrl property be false.
  834. else if (key == "Control"sv) {
  835. modifiers &= ~UIEvents::KeyModifier::Mod_Ctrl;
  836. source.ctrl = false;
  837. }
  838. // 10. If key is "Meta", let source's meta property be false.
  839. else if (key == "Meta"sv) {
  840. modifiers &= ~UIEvents::KeyModifier::Mod_Super;
  841. source.meta = false;
  842. }
  843. // 11. Remove key from sources's pressed property.
  844. source.pressed.remove(key);
  845. // 12. Perform implementation-specific action dispatch steps on browsing context equivalent to releasing a key on the
  846. // keyboard in accordance with the requirements of [UI-EVENTS], and producing at least the following events with
  847. // the specified properties:
  848. auto event = key_code_to_page_event(raw_key, modifiers, code);
  849. browsing_context.page().handle_keyup(code.code, event.modifiers, event.code_point);
  850. // 13. Return success with data null.
  851. return {};
  852. }
  853. // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action
  854. static ErrorOr<void, WebDriver::Error> dispatch_pointer_down_action(ActionObject::PointerUpDownFields const& action_object, PointerInputSource& source, GlobalKeyState const& global_key_state, HTML::BrowsingContext& browsing_context)
  855. {
  856. // 1. Let pointerType be equal to action object's pointerType property.
  857. auto pointer_type = action_object.pointer_type;
  858. // 2. Let button be equal to action object's button property.
  859. auto button = action_object.button;
  860. // 3. If the source's pressed property contains button return success with data null.
  861. if (has_flag(source.pressed, button))
  862. return {};
  863. // 4. Let x be equal to source's x property.
  864. // 5. Let y be equal to source's y property.
  865. auto position = browsing_context.page().css_to_device_point(source.position);
  866. // 6. Add button to the set corresponding to source's pressed property, and let buttons be the resulting value of
  867. // that property.
  868. auto buttons = (source.pressed |= button);
  869. // 7. Let width be equal to action object's width property.
  870. // 8. Let height be equal to action object's height property.
  871. // 9. Let pressure be equal to action object's pressure property.
  872. // 10. Let tangentialPressure be equal to action object's tangentialPressure property.
  873. // 11. Let tiltX be equal to action object's tiltX property.
  874. // 12. Let tiltY be equal to action object's tiltY property.
  875. // 13. Let twist be equal to action object's twist property.
  876. // 14. Let altitudeAngle be equal to action object's altitudeAngle property.
  877. // 15. Let azimuthAngle be equal to action object's azimuthAngle property.
  878. // 16. Perform implementation-specific action dispatch steps on browsing context equivalent to pressing the button
  879. // numbered button on the pointer with pointerId equal to source's pointerId, having type pointerType at viewport
  880. // x coordinate x, viewport y coordinate y, width, height, pressure, tangentialPressure, tiltX, tiltY, twist,
  881. // altitudeAngle, azimuthAngle, with buttons buttons depressed in accordance with the requirements of [UI-EVENTS]
  882. // and [POINTER-EVENTS]. set ctrlKey, shiftKey, altKey, and metaKey equal to the corresponding items in global
  883. // key state. Type specific properties for the pointer that are not exposed through the webdriver API must be
  884. // set to the default value specified for hardware that doesn't support that property.
  885. switch (pointer_type) {
  886. case PointerInputSource::Subtype::Mouse:
  887. browsing_context.page().handle_mousedown(position, position, button, buttons, global_key_state.modifiers());
  888. break;
  889. case PointerInputSource::Subtype::Pen:
  890. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Pen events not implemented"sv);
  891. case PointerInputSource::Subtype::Touch:
  892. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Touch events not implemented"sv);
  893. }
  894. // 17. Return success with data null.
  895. return {};
  896. }
  897. // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerup-action
  898. static ErrorOr<void, WebDriver::Error> dispatch_pointer_up_action(ActionObject::PointerUpDownFields const& action_object, PointerInputSource& source, GlobalKeyState const& global_key_state, HTML::BrowsingContext& browsing_context)
  899. {
  900. // 1. Let pointerType be equal to action object's pointerType property.
  901. auto pointer_type = action_object.pointer_type;
  902. // 2. Let button be equal to action object's button property.
  903. auto button = action_object.button;
  904. // 3. If the source's pressed property does not contain button, return success with data null.
  905. if (!has_flag(source.pressed, button))
  906. return {};
  907. // 4. Let x be equal to source's x property.
  908. // 5. Let y be equal to source's y property.
  909. auto position = browsing_context.page().css_to_device_point(source.position);
  910. // 6. Remove button from the set corresponding to source's pressed property, and let buttons be the resulting value
  911. // of that property.
  912. auto buttons = (source.pressed &= ~button);
  913. // 7. Perform implementation-specific action dispatch steps on browsing context equivalent to releasing the button
  914. // numbered button on the pointer with pointerId equal to input source's pointerId, having type pointerType at
  915. // viewport x coordinate x, viewport y coordinate y, with buttons buttons depressed, in accordance with the
  916. // requirements of [UI-EVENTS] and [POINTER-EVENTS]. The generated events must set ctrlKey, shiftKey, altKey,
  917. // and metaKey equal to the corresponding items in global key state. Type specific properties for the pointer
  918. // that are not exposed through the webdriver API must be set to the default value specified for hardware that
  919. // doesn't support that property.
  920. switch (pointer_type) {
  921. case PointerInputSource::Subtype::Mouse:
  922. browsing_context.page().handle_mouseup(position, position, button, buttons, global_key_state.modifiers());
  923. break;
  924. case PointerInputSource::Subtype::Pen:
  925. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Pen events not implemented"sv);
  926. case PointerInputSource::Subtype::Touch:
  927. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Touch events not implemented"sv);
  928. }
  929. // 8. Return success with data null.
  930. return {};
  931. }
  932. // https://w3c.github.io/webdriver/#dfn-perform-a-pointer-move
  933. static ErrorOr<void, WebDriver::Error> perform_pointer_move(ActionObject::PointerMoveFields const& action_object, PointerInputSource& source, GlobalKeyState const& global_key_state, HTML::BrowsingContext& browsing_context, AK::Duration, CSSPixelPoint coordinates)
  934. {
  935. // FIXME: 1. Let time delta be the time since the beginning of the current tick, measured in milliseconds on a monotonic clock.
  936. // FIXME: 2. Let duration ratio be the ratio of time delta and duration, if duration is greater than 0, or 1 otherwise.
  937. // FIXME: 3. If duration ratio is 1, or close enough to 1 that the implementation will not further subdivide the move action,
  938. // let last be true. Otherwise let last be false.
  939. // FIXME: 4. If last is true, let x equal target x and y equal target y.
  940. // FIXME: 5. Otherwise let x equal an approximation to duration ratio × (target x - start x) + start x, and y equal an
  941. // approximation to duration ratio × (target y - start y) + start y.
  942. // 6. Let current x equal the x property of input state.
  943. // 7. Let current y equal the y property of input state.
  944. auto current = source.position;
  945. // 8. If x is not equal to current x or y is not equal to current y, run the following steps:
  946. if (current != coordinates) {
  947. // 1. Let buttons be equal to input state's buttons property.
  948. auto buttons = source.pressed;
  949. // 2. Perform implementation-specific action dispatch steps on browsing context equivalent to moving the pointer
  950. // with pointerId equal to input source's pointerId, having type pointerType from viewport x coordinate current
  951. // x, viewport y coordinate current y to viewport x coordinate x and viewport y coordinate y, width, height,
  952. // pressure, tangentialPressure, tiltX, tiltY, twist, altitudeAngle, azimuthAngle, with buttons buttons
  953. // depressed, in accordance with the requirements of [UI-EVENTS] and [POINTER-EVENTS]. The generated events
  954. // must set ctrlKey, shiftKey, altKey, and metaKey equal to the corresponding items in global key state. Type
  955. // specific properties for the pointer that are not exposed through the WebDriver API must be set to the
  956. // default value specified for hardware that doesn't support that property. In the case where the pointerType
  957. // is "pen" or "touch", and buttons is empty, this may be a no-op. For a pointer of type "mouse" this will
  958. // always produce events including at least a pointerMove event.
  959. auto position = browsing_context.page().css_to_device_point(coordinates);
  960. switch (action_object.pointer_type) {
  961. case PointerInputSource::Subtype::Mouse:
  962. browsing_context.page().handle_mousemove(position, position, buttons, global_key_state.modifiers());
  963. break;
  964. case PointerInputSource::Subtype::Pen:
  965. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Pen events not implemented"sv);
  966. case PointerInputSource::Subtype::Touch:
  967. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Touch events not implemented"sv);
  968. }
  969. // 3. Let input state's x property equal x and y property equal y.
  970. source.position = coordinates;
  971. }
  972. // FIXME: 9. If last is true, return.
  973. // FIXME: 10. Run the following substeps in parallel:
  974. {
  975. // FIXME: 1. Asynchronously wait for an implementation defined amount of time to pass.
  976. // FIXME: 2. Perform a pointer move with arguments input state, duration, start x, start y, target x, target y.
  977. }
  978. return {};
  979. }
  980. // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action
  981. static ErrorOr<void, WebDriver::Error> dispatch_pointer_move_action(ActionObject::PointerMoveFields const& action_object, PointerInputSource& source, GlobalKeyState const& global_key_state, AK::Duration tick_duration, HTML::BrowsingContext& browsing_context, ActionsOptions const& actions_options)
  982. {
  983. auto viewport = browsing_context.page().top_level_traversable()->viewport_rect();
  984. // 1. Let x offset be equal to the x property of action object.
  985. // 2. Let y offset be equal to the y property of action object.
  986. // 3. Let origin be equal to the origin property of action object.
  987. // 4. Let (x, y) be the result of trying to get coordinates relative to an origin with source, x offset, y offset,
  988. // origin, browsing context, and actions options.
  989. auto coordinates = TRY(get_coordinates_relative_to_origin(source, action_object.position, viewport, action_object.origin, actions_options));
  990. // 5. If x is less than 0 or greater than the width of the viewport in CSS pixels, then return error with error code move target out of bounds.
  991. if (coordinates.x() < 0 || coordinates.x() > viewport.width())
  992. return WebDriver::Error::from_code(WebDriver::ErrorCode::MoveTargetOutOfBounds, ByteString::formatted("Coordinates {} are out of bounds", coordinates));
  993. // 6. If y is less than 0 or greater than the height of the viewport in CSS pixels, then return error with error code move target out of bounds.
  994. if (coordinates.y() < 0 || coordinates.y() > viewport.height())
  995. return WebDriver::Error::from_code(WebDriver::ErrorCode::MoveTargetOutOfBounds, ByteString::formatted("Coordinates {} are out of bounds", coordinates));
  996. // 7. Let duration be equal to action object's duration property if it is not undefined, or tick duration otherwise.
  997. [[maybe_unused]] auto duration = action_object.duration.value_or(tick_duration);
  998. // FIXME: 8. If duration is greater than 0 and inside any implementation-defined bounds, asynchronously wait for an
  999. // implementation defined amount of time to pass.
  1000. // 9. Let width be equal to action object's width property.
  1001. // 10. Let height be equal to action object's height property.
  1002. // 11. Let pressure be equal to action object's pressure property.
  1003. // 12. Let tangentialPressure be equal to action object's tangentialPressure property.
  1004. // 13. Let tiltX be equal to action object's tiltX property.
  1005. // 14. Let tiltY be equal to action object's tiltY property.
  1006. // 15. Let twist be equal to action object's twist property.
  1007. // 16. Let altitudeAngle be equal to action object's altitudeAngle property.
  1008. // 17. Let azimuthAngle be equal to action object's azimuthAngle property.
  1009. // 18. Perform a pointer move with arguments source, global key state, duration, start x, start y, x, y, width,
  1010. // height, pressure, tangentialPressure, tiltX, tiltY, twist, altitudeAngle, azimuthAngle.
  1011. TRY(perform_pointer_move(action_object, source, global_key_state, browsing_context, duration, coordinates));
  1012. // 19. Return success with data null.
  1013. return {};
  1014. }
  1015. // https://w3c.github.io/webdriver/#dfn-dispatch-actions-inner
  1016. class ActionExecutor final : public JS::Cell {
  1017. JS_CELL(ActionExecutor, JS::Cell);
  1018. JS_DECLARE_ALLOCATOR(ActionExecutor);
  1019. public:
  1020. ActionExecutor(InputState& input_state, Vector<Vector<ActionObject>> actions_by_tick, HTML::BrowsingContext& browsing_context, ActionsOptions actions_options, OnActionsComplete on_complete)
  1021. : m_browsing_context(browsing_context)
  1022. , m_input_state(input_state)
  1023. , m_actions_options(move(actions_options))
  1024. , m_actions_by_tick(move(actions_by_tick))
  1025. , m_on_complete(on_complete)
  1026. {
  1027. }
  1028. // 1. For each item tick actions in actions by tick:
  1029. void process_next_tick()
  1030. {
  1031. if (m_current_tick >= m_actions_by_tick.size()) {
  1032. m_on_complete->function()(JsonValue {});
  1033. return;
  1034. }
  1035. auto const& tick_actions = m_actions_by_tick[m_current_tick++];
  1036. // 1. Let tick duration be the result of computing the tick duration with argument tick actions.
  1037. auto tick_duration = compute_tick_duration(tick_actions);
  1038. // 2. Try to dispatch tick actions with input state, tick actions, tick duration, browsing context, and actions options.
  1039. if (auto result = dispatch_tick_actions(m_input_state, tick_actions, tick_duration, m_browsing_context, m_actions_options); result.is_error()) {
  1040. m_on_complete->function()(result.release_error());
  1041. return;
  1042. }
  1043. // 3. Wait until the following conditions are all met:
  1044. // * There are no pending asynchronous waits arising from the last invocation of the dispatch tick actions
  1045. // steps.
  1046. // * The user agent event loop has spun enough times to process the DOM events generated by the last
  1047. // invocation of the dispatch tick actions steps.
  1048. // * At least tick duration milliseconds have passed.
  1049. // FIXME: We currently do not implement any asynchronous waits. And we assume that Page will generally fire the
  1050. // events of interest synchronously. So we simply wait for the tick duration to pass, and then let the
  1051. // event loop spin a single time.
  1052. m_timer = Core::Timer::create_single_shot(static_cast<int>(tick_duration.to_milliseconds()), [this]() {
  1053. m_timer = nullptr;
  1054. HTML::queue_a_task(HTML::Task::Source::Unspecified, nullptr, nullptr, JS::create_heap_function(heap(), [this]() {
  1055. process_next_tick();
  1056. }));
  1057. });
  1058. m_timer->start();
  1059. }
  1060. private:
  1061. virtual void visit_edges(Cell::Visitor& visitor) override
  1062. {
  1063. Base::visit_edges(visitor);
  1064. visitor.visit(m_browsing_context);
  1065. visitor.visit(m_on_complete);
  1066. }
  1067. JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;
  1068. InputState& m_input_state;
  1069. ActionsOptions m_actions_options;
  1070. Vector<Vector<ActionObject>> m_actions_by_tick;
  1071. size_t m_current_tick { 0 };
  1072. OnActionsComplete m_on_complete;
  1073. RefPtr<Core::Timer> m_timer;
  1074. };
  1075. JS_DEFINE_ALLOCATOR(ActionExecutor);
  1076. // https://w3c.github.io/webdriver/#dfn-dispatch-actions
  1077. JS::NonnullGCPtr<JS::Cell> dispatch_actions(InputState& input_state, Vector<Vector<ActionObject>> actions_by_tick, HTML::BrowsingContext& browsing_context, ActionsOptions actions_options, OnActionsComplete on_complete)
  1078. {
  1079. // 1. Let token be a new unique identifier.
  1080. auto token = MUST(Crypto::generate_random_uuid());
  1081. // 2. Enqueue token in input state's actions queue.
  1082. input_state.actions_queue.append(token);
  1083. // 3. Wait for token to be the first item in input state's actions queue.
  1084. // FIXME: We should probably do this, but our WebDriver currently blocks until a given action is complete anyways,
  1085. // so we should never arrive here with an ongoing action (which we verify for now).
  1086. VERIFY(input_state.actions_queue.size() == 1);
  1087. // 4. Let actions result be the result of dispatch actions inner with input state, actions by tick, browsing
  1088. // context, and actions options.
  1089. auto action_executor = browsing_context.heap().allocate_without_realm<ActionExecutor>(input_state, move(actions_by_tick), browsing_context, move(actions_options), on_complete);
  1090. action_executor->process_next_tick();
  1091. // 5. Dequeue input state's actions queue.
  1092. auto executed_token = input_state.actions_queue.take_first();
  1093. // 6. Assert: this returns token
  1094. VERIFY(executed_token == token);
  1095. // 7. Return actions result.
  1096. return action_executor;
  1097. }
  1098. // https://w3c.github.io/webdriver/#dfn-dispatch-tick-actions
  1099. ErrorOr<void, WebDriver::Error> dispatch_tick_actions(InputState& input_state, ReadonlySpan<ActionObject> tick_actions, AK::Duration tick_duration, HTML::BrowsingContext& browsing_context, ActionsOptions const& actions_options)
  1100. {
  1101. // 1. For each action object in tick actions:
  1102. for (auto const& action_object : tick_actions) {
  1103. // 1. Let input id be equal to the value of action object's id property.
  1104. auto const& input_id = action_object.id;
  1105. // 2. Let source type be equal to the value of action object's type property.
  1106. // NOTE: We don't actually need this, we can determine the event to fire based on the subtype.
  1107. // 3. Let source be the result of get an input source given input state and input id.
  1108. auto source = get_input_source(input_state, input_id);
  1109. // 4. Assert: source is not undefined.
  1110. VERIFY(source.has_value());
  1111. // 5. Let global key state be the result of get the global key state with input state.
  1112. auto global_key_state = get_global_key_state(input_state);
  1113. // 6. Let subtype be action object's subtype.
  1114. auto subtype = action_object.subtype;
  1115. // 7. Let algorithm be the value of the column dispatch action algorithm from the following table where the
  1116. // source type column is source type and the subtype column is equal to subtype.
  1117. //
  1118. // source type | subtype | Dispatch action algorithm
  1119. // ---------------------------------------------------------------
  1120. // "none" | "pause" | Dispatch a pause action
  1121. // "key" | "pause" | Dispatch a pause action
  1122. // "key" | "keyDown" | Dispatch a keyDown action
  1123. // "key" | "keyUp" | Dispatch a keyUp action
  1124. // "pointer" | "pause" | Dispatch a pause action
  1125. // "pointer" | "pointerDown" | Dispatch a pointerDown action
  1126. // "pointer" | "pointerUp" | Dispatch a pointerUp action
  1127. // "pointer" | "pointerMove" | Dispatch a pointerMove action
  1128. // "pointer" | "pointerCancel" | Dispatch a pointerCancel action
  1129. // "wheel" | "pause" | Dispatch a pause action
  1130. // "wheel" | "scroll" | Dispatch a scroll action
  1131. // 8. Try to run algorithm with arguments action object, source, global key state, tick duration, browsing
  1132. // context, and actions options.
  1133. switch (subtype) {
  1134. case ActionObject::Subtype::Pause:
  1135. dispatch_pause_action();
  1136. break;
  1137. case ActionObject::Subtype::KeyDown:
  1138. TRY(dispatch_key_down_action(action_object.key_fields(), source->get<KeyInputSource>(), global_key_state, browsing_context));
  1139. break;
  1140. case ActionObject::Subtype::KeyUp:
  1141. TRY(dispatch_key_up_action(action_object.key_fields(), source->get<KeyInputSource>(), global_key_state, browsing_context));
  1142. break;
  1143. case ActionObject::Subtype::PointerDown:
  1144. TRY(dispatch_pointer_down_action(action_object.pointer_up_down_fields(), source->get<PointerInputSource>(), global_key_state, browsing_context));
  1145. break;
  1146. case ActionObject::Subtype::PointerUp:
  1147. TRY(dispatch_pointer_up_action(action_object.pointer_up_down_fields(), source->get<PointerInputSource>(), global_key_state, browsing_context));
  1148. break;
  1149. case ActionObject::Subtype::PointerMove:
  1150. TRY(dispatch_pointer_move_action(action_object.pointer_move_fields(), source->get<PointerInputSource>(), global_key_state, tick_duration, browsing_context, actions_options));
  1151. break;
  1152. case ActionObject::Subtype::PointerCancel:
  1153. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Pointer cancel events not implemented"sv);
  1154. case ActionObject::Subtype::Scroll:
  1155. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Scroll events not implemented"sv);
  1156. }
  1157. // 9. If subtype is "keyDown", append a copy of action object with the subtype property changed to "keyUp" to
  1158. // input state's input cancel list.
  1159. if (subtype == ActionObject::Subtype::KeyDown) {
  1160. auto action_copy = action_object;
  1161. action_copy.subtype = ActionObject::Subtype::KeyUp;
  1162. input_state.input_cancel_list.append(move(action_copy));
  1163. }
  1164. // 10. If subtype is "pointerDown", append a copy of action object with the subtype property changed to
  1165. // "pointerUp" to input state's input cancel list.
  1166. if (subtype == ActionObject::Subtype::PointerDown) {
  1167. auto action_copy = action_object;
  1168. action_copy.subtype = ActionObject::Subtype::PointerUp;
  1169. input_state.input_cancel_list.append(move(action_copy));
  1170. }
  1171. }
  1172. // 2. Return success with data null.
  1173. return {};
  1174. }
  1175. // https://w3c.github.io/webdriver/#dfn-dispatch-a-list-of-actions
  1176. JS::NonnullGCPtr<JS::Cell> dispatch_list_of_actions(InputState& input_state, Vector<ActionObject> actions, HTML::BrowsingContext& browsing_context, ActionsOptions actions_options, OnActionsComplete on_complete)
  1177. {
  1178. // 1. Let tick actions be the list «actions»
  1179. // 2. Let actions by tick be the list «tick actions».
  1180. Vector<Vector<ActionObject>> actions_by_tick;
  1181. actions_by_tick.append(move(actions));
  1182. // 3. Return the result of dispatch actions with input state, actions by tick, browsing context, and actions options.
  1183. return dispatch_actions(input_state, move(actions_by_tick), browsing_context, move(actions_options), on_complete);
  1184. }
  1185. // https://w3c.github.io/webdriver/#dfn-dispatch-the-events-for-a-typeable-string
  1186. static JS::NonnullGCPtr<JS::Cell> dispatch_the_events_for_a_typeable_string(Web::WebDriver::InputState& input_state, String const& input_id, Web::WebDriver::InputSource& source, StringView text, Web::HTML::BrowsingContext& browsing_context, Web::WebDriver::OnActionsComplete on_complete)
  1187. {
  1188. auto& input_source = source.get<Web::WebDriver::KeyInputSource>();
  1189. // NOTE: Rather than dispatching each action list individually below, we collect a list of "actions by tick" to
  1190. // dispatch, to make handling the asynchronous nature of actions simpler.
  1191. Vector<Vector<Web::WebDriver::ActionObject>> actions_by_tick;
  1192. // 1. Let actions options be a new actions options with the is element origin steps set to represents a web element,
  1193. // and the get element origin steps set to get a WebElement origin.
  1194. Web::WebDriver::ActionsOptions actions_options {
  1195. .is_element_origin = &Web::WebDriver::represents_a_web_element,
  1196. .get_element_origin = &Web::WebDriver::get_web_element_origin,
  1197. };
  1198. // 2. For each char of text:
  1199. for (auto code_point : Utf8View { text }) {
  1200. auto char_is_shifted = Web::WebDriver::is_shifted_character(code_point);
  1201. // 1. Let global key state be the result of get the global key state with input state.
  1202. auto global_key_state = Web::WebDriver::get_global_key_state(input_state);
  1203. // 2. If char is a shifted character, and the shifted state of source is false:
  1204. if (char_is_shifted && !input_source.shift) {
  1205. // 1. Let action be an action object constructed with input id, "key", and "keyDown", and set its value
  1206. // property to U+E008 ("left shift").
  1207. Web::WebDriver::ActionObject action { input_id, Web::WebDriver::InputSourceType::Key, Web::WebDriver::ActionObject::Subtype::KeyDown };
  1208. action.key_fields().value = 0xE008;
  1209. // 2. Let actions be the list «action».
  1210. Vector actions { move(action) };
  1211. // 3. Dispatch a list of actions with input state, actions, and browsing context.
  1212. actions_by_tick.append(move(actions));
  1213. input_source.shift = true;
  1214. }
  1215. // 3. If char is not a shifted character and the shifted state of source is true:
  1216. if (!char_is_shifted && input_source.shift) {
  1217. // 1. Let action be an action object constructed with input id, "key", and "keyUp", and set its value
  1218. // property to U+E008 ("left shift").
  1219. Web::WebDriver::ActionObject action { input_id, Web::WebDriver::InputSourceType::Key, Web::WebDriver::ActionObject::Subtype::KeyUp };
  1220. action.key_fields().value = 0xE008;
  1221. // 2. Let tick actions be the list «action».
  1222. Vector actions { move(action) };
  1223. // 3. Dispatch a list of actions with input state, actions, browsing context, and actions options.
  1224. actions_by_tick.append(move(actions));
  1225. input_source.shift = false;
  1226. }
  1227. // 4. Let keydown action be an action object constructed with arguments input id, "key", and "keyDown".
  1228. Web::WebDriver::ActionObject keydown_action { input_id, Web::WebDriver::InputSourceType::Key, Web::WebDriver::ActionObject::Subtype::KeyDown };
  1229. // 5. Set the value property of keydown action to char.
  1230. keydown_action.key_fields().value = code_point;
  1231. // 6. Let keyup action be a copy of keydown action with the subtype property changed to "keyUp".
  1232. auto keyup_action = keydown_action;
  1233. keyup_action.subtype = Web::WebDriver::ActionObject::Subtype::KeyUp;
  1234. // 7. Let actions be the list «keydown action, keyup action».
  1235. Vector actions { move(keydown_action), move(keyup_action) };
  1236. // 8. Dispatch a list of actions with input state, actions, browsing context, and actions options.
  1237. actions_by_tick.append(move(actions));
  1238. }
  1239. return dispatch_actions(input_state, move(actions_by_tick), browsing_context, move(actions_options), on_complete);
  1240. }
  1241. // https://w3c.github.io/webdriver/#dfn-dispatch-actions-for-a-string
  1242. JS::NonnullGCPtr<JS::Cell> dispatch_actions_for_a_string(Web::WebDriver::InputState& input_state, String const& input_id, Web::WebDriver::InputSource& source, StringView text, Web::HTML::BrowsingContext& browsing_context, Web::WebDriver::OnActionsComplete on_complete)
  1243. {
  1244. // FIXME: 1. Let clusters be an array created by breaking text into extended grapheme clusters.
  1245. // FIXME: 2. Let undo actions be an empty map.
  1246. // FIXME: 3. Let current typeable text be an empty list.
  1247. // FIXME: 4. For each cluster corresponding to an indexed property in clusters run the substeps of the first matching statement:
  1248. {
  1249. // -> cluster is the null key
  1250. {
  1251. // FIXME: 1. Dispatch the events for a typeable string with input state, input id, source, current typeable text, and browsing context. Empty current typeable text.
  1252. // FIXME: 2. Try to clear the modifier key state with input state, input id, source, undo actions and browsing context.
  1253. // FIXME: 3. Clear undo actions.
  1254. }
  1255. // -> cluster is a modifier key
  1256. {
  1257. // FIXME: 1. Dispatch the events for a typeable string with input state, input id, source, current typeable text, and browsing context.
  1258. // FIXME: 2. Empty current typeable text.
  1259. // FIXME: 3. Let keydown action be an action object constructed with arguments input id, "key", and "keyDown".
  1260. // FIXME: 4. Set the value property of keydown action to cluster.
  1261. // FIXME: 5. Let actions be the list «keydown action»
  1262. // FIXME: 6. Dispatch a list of actions with input state, actions, browsing context, and actions options.
  1263. // FIXME: 7. Add an entry to undo actions with key cluster and value being a copy of keydown action with the subtype property modified to "keyUp".
  1264. }
  1265. // -> cluster is typeable
  1266. {
  1267. // FIXME: Append cluster to current typeable text.
  1268. }
  1269. // -> Otherwise
  1270. {
  1271. // FIXME: 1. Dispatch the events for a typeable string with input state, input id, source, current typeable text, and browsing context.
  1272. // FIXME: 2. Empty current typeable text.
  1273. // FIXME: 3. Dispatch a composition event with arguments "compositionstart", undefined, and browsing context.
  1274. // FIXME: 4. Dispatch a composition event with arguments "compositionupdate", cluster, and browsing context.
  1275. // FIXME: 5. Dispatch a composition event with arguments "compositionend", cluster, and browsing context.
  1276. }
  1277. }
  1278. // FIXME: We currently only support sending single code points to Page. Much of the above loop would break the the
  1279. // text into segments, broken by graphemes / modifier keys / null keys. Until we need such support, we take
  1280. // the easy road here and dispatch the string as a single list of actions. When we do implement the above
  1281. // steps, we will likely need to implement a completely asynchronous driver (like ActionExecutor above).
  1282. // 5. Dispatch the events for a typeable string with input state, input id and source, current typeable text, and
  1283. // browsing context.
  1284. return dispatch_the_events_for_a_typeable_string(input_state, input_id, source, text, browsing_context, JS::create_heap_function(browsing_context.heap(), [on_complete](Web::WebDriver::Response result) {
  1285. // FIXME: 6. Try to clear the modifier key state with input state, input id, source, undo actions, and browsing context.
  1286. on_complete->function()(move(result));
  1287. }));
  1288. }
  1289. }