Actions.cpp 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
  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/GenericShorthands.h>
  8. #include <AK/JsonArray.h>
  9. #include <AK/JsonObject.h>
  10. #include <AK/JsonValue.h>
  11. #include <AK/Math.h>
  12. #include <AK/Utf8View.h>
  13. #include <LibWeb/Crypto/Crypto.h>
  14. #include <LibWeb/HTML/BrowsingContext.h>
  15. #include <LibWeb/HTML/EventLoop/EventLoop.h>
  16. #include <LibWeb/HTML/TraversableNavigable.h>
  17. #include <LibWeb/Page/Page.h>
  18. #include <LibWeb/WebDriver/Actions.h>
  19. #include <LibWeb/WebDriver/ElementReference.h>
  20. #include <LibWeb/WebDriver/InputState.h>
  21. #include <LibWeb/WebDriver/Properties.h>
  22. namespace Web::WebDriver {
  23. static Optional<ActionObject::Subtype> action_object_subtype_from_string(StringView action_subtype)
  24. {
  25. if (action_subtype == "pause"sv)
  26. return ActionObject::Subtype::Pause;
  27. if (action_subtype == "keyUp"sv)
  28. return ActionObject::Subtype::KeyUp;
  29. if (action_subtype == "keyDown"sv)
  30. return ActionObject::Subtype::KeyDown;
  31. if (action_subtype == "pointerUp"sv)
  32. return ActionObject::Subtype::PointerUp;
  33. if (action_subtype == "pointerDown"sv)
  34. return ActionObject::Subtype::PointerDown;
  35. if (action_subtype == "pointerMove"sv)
  36. return ActionObject::Subtype::PointerMove;
  37. if (action_subtype == "pointerCancel"sv)
  38. return ActionObject::Subtype::PointerCancel;
  39. if (action_subtype == "scroll"sv)
  40. return ActionObject::Subtype::Scroll;
  41. return {};
  42. }
  43. static ActionObject::Fields fields_from_subtype(ActionObject::Subtype subtype)
  44. {
  45. switch (subtype) {
  46. case ActionObject::Subtype::Pause:
  47. return ActionObject::PauseFields {};
  48. case ActionObject::Subtype::KeyUp:
  49. case ActionObject::Subtype::KeyDown:
  50. return ActionObject::KeyFields {};
  51. case ActionObject::Subtype::PointerUp:
  52. case ActionObject::Subtype::PointerDown:
  53. return ActionObject::PointerUpDownFields {};
  54. case ActionObject::Subtype::PointerMove:
  55. return ActionObject::PointerMoveFields {};
  56. case ActionObject::Subtype::PointerCancel:
  57. return ActionObject::PointerCancelFields {};
  58. case ActionObject::Subtype::Scroll:
  59. return ActionObject::ScrollFields {};
  60. }
  61. VERIFY_NOT_REACHED();
  62. }
  63. ActionObject::ActionObject(String id, InputSourceType type, Subtype subtype)
  64. : id(move(id))
  65. , type(type)
  66. , subtype(subtype)
  67. , fields(fields_from_subtype(subtype))
  68. {
  69. }
  70. void ActionObject::set_pointer_type(PointerInputSource::Subtype pointer_type)
  71. {
  72. fields.visit(
  73. [&](OneOf<PointerUpDownFields, PointerMoveFields, PointerCancelFields> auto& fields) {
  74. fields.pointer_type = pointer_type;
  75. },
  76. [](auto const&) { VERIFY_NOT_REACHED(); });
  77. }
  78. static Optional<ActionObject::Origin> determine_origin(ActionsOptions const& actions_options, Optional<JsonValue const&> const& origin)
  79. {
  80. if (!origin.has_value())
  81. return ActionObject::OriginType::Viewport;
  82. if (origin->is_string()) {
  83. if (origin->as_string() == "viewport"sv)
  84. return ActionObject::OriginType::Viewport;
  85. if (origin->as_string() == "pointer"sv)
  86. return ActionObject::OriginType::Pointer;
  87. }
  88. if (origin->is_object()) {
  89. if (actions_options.is_element_origin(origin->as_object()))
  90. return MUST(String::from_byte_string(extract_web_element_reference(origin->as_object())));
  91. }
  92. return {};
  93. }
  94. // https://w3c.github.io/webdriver/#dfn-get-coordinates-relative-to-an-origin
  95. 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)
  96. {
  97. // 1. Run the substeps of the first matching value of origin
  98. auto coordinates = TRY(origin.visit(
  99. [&](ActionObject::OriginType origin) -> ErrorOr<CSSPixelPoint, WebDriver::Error> {
  100. switch (origin) {
  101. // "viewport"
  102. case ActionObject::OriginType::Viewport:
  103. // 1. Let x equal x offset and y equal y offset.
  104. return offset;
  105. // "pointer"
  106. case ActionObject::OriginType::Pointer:
  107. // 1. Let start x be equal to the x property of source.
  108. // 2. Let start y be equal to the y property of source.
  109. // 3. Let x equal start x + x offset and y equal start y + y offset.
  110. return source.position.translated(offset);
  111. }
  112. VERIFY_NOT_REACHED();
  113. },
  114. [&](String const& origin) -> ErrorOr<CSSPixelPoint, WebDriver::Error> {
  115. // Otherwise
  116. // 1. Let element be the result of trying to run actions options' get element origin steps with origin and
  117. // browsing context.
  118. // 2. If element is null, return error with error code no such element.
  119. auto element = TRY(actions_options.get_element_origin(origin));
  120. // 3. Let x element and y element be the result of calculating the in-view center point of element.
  121. auto position = in_view_center_point(element, viewport);
  122. // 4. Let x equal x element + x offset, and y equal y element + y offset.
  123. return position.translated(offset);
  124. }));
  125. // 2. Return (x, y)
  126. return coordinates;
  127. }
  128. // https://w3c.github.io/webdriver/#dfn-process-pointer-parameters
  129. struct PointerParameters {
  130. PointerInputSource::Subtype pointer_type { PointerInputSource::Subtype::Mouse };
  131. };
  132. static ErrorOr<PointerParameters, WebDriver::Error> process_pointer_parameters(Optional<JsonValue const&> const& parameters_data)
  133. {
  134. // 1. Let parameters be the default pointer parameters.
  135. PointerParameters parameters;
  136. // 2. If parameters data is undefined, return success with data parameters.
  137. if (!parameters_data.has_value())
  138. return parameters;
  139. // 3. If parameters data is not an Object, return error with error code invalid argument.
  140. if (!parameters_data->is_object())
  141. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'parameters' is not an Object");
  142. // 4. Let pointer type be the result of getting a property named "pointerType" from parameters data.
  143. auto pointer_type = TRY(get_optional_property(parameters_data->as_object(), "pointerType"sv));
  144. // 5. If pointer type is not undefined:
  145. if (pointer_type.has_value()) {
  146. // 1. If pointer type does not have one of the values "mouse", "pen", or "touch", return error with error code
  147. // invalid argument.
  148. auto parsed_pointer_type = pointer_input_source_subtype_from_string(*pointer_type);
  149. if (!parsed_pointer_type.has_value())
  150. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'pointerType' must be one of 'mouse', 'pen', or 'touch'");
  151. // 2. Set the pointerType property of parameters to pointer type.
  152. parameters.pointer_type = *parsed_pointer_type;
  153. }
  154. // 6. Return success with data parameters.
  155. return parameters;
  156. }
  157. // https://w3c.github.io/webdriver/#dfn-process-a-pause-action
  158. static ErrorOr<void, WebDriver::Error> process_pause_action(JsonObject const& action_item, ActionObject& action)
  159. {
  160. // 1. Let duration be the result of getting the property "duration" from action item.
  161. // 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.
  162. // 3. Set the duration property of action to duration.
  163. if (auto duration = TRY(get_optional_property_with_limits<i64>(action_item, "duration"sv, 0, {})); duration.has_value())
  164. action.pause_fields().duration = AK::Duration::from_milliseconds(*duration);
  165. // 4. Return success with data action.
  166. return {};
  167. }
  168. // https://w3c.github.io/webdriver/#dfn-process-a-null-action
  169. static ErrorOr<ActionObject, WebDriver::Error> process_null_action(String id, JsonObject const& action_item)
  170. {
  171. // 1. Let subtype be the result of getting a property named "type" from action item.
  172. auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv)));
  173. // 2. If subtype is not "pause", return error with error code invalid argument.
  174. if (subtype != ActionObject::Subtype::Pause)
  175. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be 'pause'");
  176. // 3. Let action be an action object constructed with arguments id, "none", and subtype.
  177. ActionObject action { move(id), InputSourceType::None, *subtype };
  178. // 4. Let result be the result of trying to process a pause action with arguments action item and action.
  179. TRY(process_pause_action(action_item, action));
  180. // 5. Return result.
  181. return action;
  182. }
  183. // https://w3c.github.io/webdriver/#dfn-process-a-key-action
  184. static ErrorOr<ActionObject, WebDriver::Error> process_key_action(String id, JsonObject const& action_item)
  185. {
  186. using enum ActionObject::Subtype;
  187. // 1. Let subtype be the result of getting a property named "type" from action item.
  188. auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv)));
  189. // 2. If subtype is not one of the values "keyUp", "keyDown", or "pause", return an error with error code invalid argument.
  190. if (!first_is_one_of(subtype, KeyUp, KeyDown, Pause))
  191. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'keyUp', 'keyDown', or 'pause'");
  192. // 3. Let action be an action object constructed with arguments id, "key", and subtype.
  193. ActionObject action { move(id), InputSourceType::Key, *subtype };
  194. // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action
  195. // item and action, and return result.
  196. if (subtype == Pause) {
  197. TRY(process_pause_action(action_item, action));
  198. return action;
  199. }
  200. // 5. Let key be the result of getting a property named "value" from action item.
  201. auto key = TRY(get_property(action_item, "value"sv));
  202. // 6. If key is not a String containing a single unicode code point [or grapheme cluster?] return error with error
  203. // code invalid argument.
  204. if (Utf8View { key }.length() != 1) {
  205. // FIXME: The spec seems undecided on whether grapheme clusters should be supported. Update this step to check
  206. // for graphemes if we end up needing to support them.
  207. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'value' must be a single code point");
  208. }
  209. // 7. Set the value property on action to key.
  210. action.key_fields().value = MUST(String::from_byte_string(key));
  211. // 8. Return success with data action.
  212. return action;
  213. }
  214. // Common steps between:
  215. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-up-or-pointer-down-action
  216. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-move-action
  217. static ErrorOr<void, WebDriver::Error> process_pointer_action_common(JsonObject const& action_item, ActionObject::PointerFields& fields)
  218. {
  219. // 4. Let width be the result of getting the property width from action item.
  220. // 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.
  221. // 6. Set the width property of action to width.
  222. fields.width = TRY(get_optional_property_with_limits<double>(action_item, "width"sv, 0.0, {}));
  223. // 7. Let height be the result of getting the property height from action item.
  224. // 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.
  225. // 9. Set the height property of action to height.
  226. fields.height = TRY(get_optional_property_with_limits<double>(action_item, "height"sv, 0.0, {}));
  227. // 10. Let pressure be the result of getting the property pressure from action item.
  228. // 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.
  229. // 12. Set the pressure property of action to pressure.
  230. fields.pressure = TRY(get_optional_property_with_limits<double>(action_item, "pressure"sv, 0.0, 1.0));
  231. // 13. Let tangentialPressure be the result of getting the property tangentialPressure from action item.
  232. // 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.
  233. // 15. Set the tangentialPressure property of action to tangentialPressure.
  234. fields.tangential_pressure = TRY(get_optional_property_with_limits<double>(action_item, "tangentialPressure"sv, -1.0, 1.0));
  235. // 16. Let tiltX be the result of getting the property tiltX from action item.
  236. // 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.
  237. // 18. Set the tiltX property of action to tiltX.
  238. fields.tilt_x = TRY(get_optional_property_with_limits<i32>(action_item, "tiltX"sv, -90, 90));
  239. // 19. Let tiltY be the result of getting the property tiltY from action item.
  240. // 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.
  241. // 21. Set the tiltY property of action to tiltY.
  242. fields.tilt_y = TRY(get_optional_property_with_limits<i32>(action_item, "tiltY"sv, -90, 90));
  243. // 22. Let twist be the result of getting the property twist from action item.
  244. // 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.
  245. // 24. Set the twist property of action to twist.
  246. fields.twist = TRY(get_optional_property_with_limits<u32>(action_item, "twist"sv, 0, 359));
  247. // 25. Let altitudeAngle be the result of getting the property altitudeAngle from action item.
  248. // 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.
  249. // 27. Set the altitudeAngle property of action to altitudeAngle.
  250. fields.altitude_angle = TRY(get_optional_property_with_limits<double>(action_item, "altitudeAngle"sv, 0.0, AK::Pi<double> / 2.0));
  251. // 28. Let azimuthAngle be the result of getting the property azimuthAngle from action item.
  252. // 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.
  253. // 30. Set the azimuthAngle property of action to azimuthAngle.
  254. fields.azimuth_angle = TRY(get_optional_property_with_limits<double>(action_item, "azimuthAngle"sv, 0.0, AK::Pi<double> * 2.0));
  255. // 31. Return success with data null.
  256. return {};
  257. }
  258. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-up-or-pointer-down-action
  259. static ErrorOr<void, WebDriver::Error> process_pointer_up_or_down_action(JsonObject const& action_item, ActionObject& action)
  260. {
  261. auto& fields = action.pointer_up_down_fields();
  262. // 1. Let button be the result of getting the property button from action item.
  263. // 2. If button is not an Integer greater than or equal to 0 return error with error code invalid argument.
  264. // 3. Set the button property of action to button.
  265. fields.button = UIEvents::button_code_to_mouse_button(TRY(get_property_with_limits<i16>(action_item, "button"sv, 0, {})));
  266. return process_pointer_action_common(action_item, fields);
  267. }
  268. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-move-action
  269. static ErrorOr<void, WebDriver::Error> process_pointer_move_action(JsonObject const& action_item, ActionObject& action, ActionsOptions const& actions_options)
  270. {
  271. auto& fields = action.pointer_move_fields();
  272. // 1. Let duration be the result of getting the property duration from action item.
  273. // 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.
  274. // 3. Set the duration property of action to duration.
  275. if (auto duration = TRY(get_optional_property_with_limits<i64>(action_item, "duration"sv, 0, {})); duration.has_value())
  276. fields.duration = AK::Duration::from_milliseconds(*duration);
  277. // 4. Let origin be the result of getting the property origin from action item.
  278. // 5. If origin is undefined let origin equal "viewport".
  279. auto origin = determine_origin(actions_options, action_item.get("origin"sv));
  280. // 6. If origin is not equal to "viewport" or "pointer", and actions options is element origin steps given origin
  281. // return false, return error with error code invalid argument.
  282. if (!origin.has_value())
  283. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'origin' must be 'viewport', 'pointer', or an element origin");
  284. // 7. Set the origin property of action to origin.
  285. fields.origin = origin.release_value();
  286. // 8. Let x be the result of getting the property x from action item.
  287. // 9. If x is not an Integer, return error with error code invalid argument.
  288. // 10. Set the x property of action to x.
  289. fields.position.set_x(TRY(get_property<i32>(action_item, "x"sv)));
  290. // 11. Let y be the result of getting the property y from action item.
  291. // 12. If y is not an Integer, return error with error code invalid argument.
  292. // 13. Set the y property of action to y.
  293. fields.position.set_y(TRY(get_property<i32>(action_item, "y"sv)));
  294. return process_pointer_action_common(action_item, fields);
  295. }
  296. // https://w3c.github.io/webdriver/#dfn-process-a-pointer-action
  297. static ErrorOr<ActionObject, WebDriver::Error> process_pointer_action(String id, PointerParameters const& parameters, JsonObject const& action_item, ActionsOptions const& actions_options)
  298. {
  299. using enum ActionObject::Subtype;
  300. // 1. Let subtype be the result of getting a property named "type" from action item.
  301. auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv)));
  302. // 2. If subtype is not one of the values "pause", "pointerUp", "pointerDown", "pointerMove", or "pointerCancel", return an error with error code invalid argument.
  303. if (!first_is_one_of(subtype, Pause, PointerUp, PointerDown, PointerMove, PointerCancel))
  304. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'pause', 'pointerUp', 'pointerDown', 'pointerMove', or 'pointerCancel'");
  305. // 3. Let action be an action object constructed with arguments id, "pointer", and subtype.
  306. ActionObject action { move(id), InputSourceType::Pointer, *subtype };
  307. // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action
  308. // item, action, and actions options, and return result.
  309. if (subtype == Pause) {
  310. TRY(process_pause_action(action_item, action));
  311. return action;
  312. }
  313. // 5. Set the pointerType property of action equal to the pointerType property of parameters.
  314. action.set_pointer_type(parameters.pointer_type);
  315. // 6. If subtype is "pointerUp" or "pointerDown", process a pointer up or pointer down action with arguments action
  316. // item and action. If doing so results in an error, return that error.
  317. if (subtype == PointerUp || subtype == PointerDown) {
  318. TRY(process_pointer_up_or_down_action(action_item, action));
  319. }
  320. // 7. If subtype is "pointerMove" process a pointer move action with arguments action item, action, and actions
  321. // options. If doing so results in an error, return that error.
  322. else if (subtype == PointerMove) {
  323. TRY(process_pointer_move_action(action_item, action, actions_options));
  324. }
  325. // 8. If subtype is "pointerCancel" process a pointer cancel action. If doing so results in an error, return that error.
  326. else if (subtype == PointerCancel) {
  327. // FIXME: There are no spec steps to "process a pointer cancel action" yet.
  328. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "pointerCancel events not implemented"sv);
  329. }
  330. // 9. Return success with data action.
  331. return action;
  332. }
  333. // https://w3c.github.io/webdriver/#dfn-process-a-wheel-action
  334. static ErrorOr<ActionObject, WebDriver::Error> process_wheel_action(String id, JsonObject const& action_item, ActionsOptions const& actions_options)
  335. {
  336. using enum ActionObject::Subtype;
  337. // 1. Let subtype be the result of getting a property named "type" from action item.
  338. auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv)));
  339. // 2. If subtype is not the value "pause", or "scroll", return an error with error code invalid argument.
  340. if (!first_is_one_of(subtype, Pause, Scroll))
  341. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'pause' or 'scroll'");
  342. // 3. Let action be an action object constructed with arguments id, "wheel", and subtype.
  343. ActionObject action { move(id), InputSourceType::Wheel, *subtype };
  344. // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action
  345. // item and action, and return result.
  346. if (subtype == Pause) {
  347. TRY(process_pause_action(action_item, action));
  348. return action;
  349. }
  350. auto& fields = action.scroll_fields();
  351. // 5. Let duration be the result of getting a property named "duration" from action item.
  352. // 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.
  353. // 7. Set the duration property of action to duration.
  354. if (auto duration = TRY(get_optional_property_with_limits<i64>(action_item, "duration"sv, 0, {})); duration.has_value())
  355. fields.duration = AK::Duration::from_milliseconds(*duration);
  356. // 8. Let origin be the result of getting the property origin from action item.
  357. // 9. If origin is undefined let origin equal "viewport".
  358. auto origin = determine_origin(actions_options, action_item.get("origin"sv));
  359. // 10. If origin is not equal to "viewport", or actions options' is element origin steps given origin return false,
  360. // return error with error code invalid argument.
  361. if (!origin.has_value() || origin == ActionObject::OriginType::Pointer)
  362. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'origin' must be 'viewport' or an element origin");
  363. // 11. Set the origin property of action to origin.
  364. fields.origin = origin.release_value();
  365. // 12. Let x be the result of getting the property x from action item.
  366. // 13. If x is not an Integer, return error with error code invalid argument.
  367. // 14. Set the x property of action to x.
  368. fields.x = TRY(get_property<i64>(action_item, "x"sv));
  369. // 15. Let y be the result of getting the property y from action item.
  370. // 16. If y is not an Integer, return error with error code invalid argument.
  371. // 17. Set the y property of action to y.
  372. fields.y = TRY(get_property<i64>(action_item, "y"sv));
  373. // 18. Let deltaX be the result of getting the property deltaX from action item.
  374. // 19. If deltaX is not an Integer, return error with error code invalid argument.
  375. // 20. Set the deltaX property of action to deltaX.
  376. fields.delta_x = TRY(get_property<i64>(action_item, "deltaX"sv));
  377. // 21. Let deltaY be the result of getting the property deltaY from action item.
  378. // 22. If deltaY is not an Integer, return error with error code invalid argument.
  379. // 23. Set the deltaY property of action to deltaY.
  380. fields.delta_y = TRY(get_property<i64>(action_item, "deltaY"sv));
  381. // 24. Return success with data action.
  382. return action;
  383. }
  384. // https://w3c.github.io/webdriver/#dfn-process-an-input-source-action-sequence
  385. static ErrorOr<Vector<ActionObject>, WebDriver::Error> process_input_source_action_sequence(InputState& input_state, JsonValue const& action_sequence, ActionsOptions const& actions_options)
  386. {
  387. // 1. Let type be the result of getting a property named "type" from action sequence.
  388. auto type = input_source_type_from_string(TRY(get_property(action_sequence, "type"sv)));
  389. // 2. If type is not "key", "pointer", "wheel", or "none", return an error with error code invalid argument.
  390. if (!type.has_value())
  391. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'key', 'pointer', 'wheel', or 'none'");
  392. // 3. Let id be the result of getting the property "id" from action sequence.
  393. // 4. If id is undefined or is not a String, return error with error code invalid argument.
  394. auto const id = MUST(String::from_byte_string(TRY(get_property(action_sequence, "id"sv))));
  395. // 5. If type is equal to "pointer", let parameters data be the result of getting the property "parameters" from
  396. // action sequence. Then let parameters be the result of trying to process pointer parameters with argument
  397. // parameters data.
  398. Optional<PointerParameters> parameters;
  399. Optional<PointerInputSource::Subtype> subtype;
  400. if (type == InputSourceType::Pointer) {
  401. parameters = TRY(process_pointer_parameters(action_sequence.as_object().get("parameters"sv)));
  402. subtype = parameters->pointer_type;
  403. }
  404. // 6. Let source be the result of trying to get or create an input source given input state, type and id.
  405. auto const& source = *TRY(get_or_create_input_source(input_state, *type, id, subtype));
  406. // 7. If parameters is not undefined, then if its pointerType property is not equal to source's subtype property,
  407. // return an error with error code invalid argument.
  408. if (auto const* pointer_input_source = source.get_pointer<PointerInputSource>(); pointer_input_source && parameters.has_value()) {
  409. if (parameters->pointer_type != pointer_input_source->subtype)
  410. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Invalid 'pointerType' property");
  411. }
  412. // 8. Let action items be the result of getting a property named "actions" from action sequence.
  413. // 9. If action items is not an Array, return error with error code invalid argument.
  414. auto const& action_items = *TRY(get_property<JsonArray const*>(action_sequence, "actions"sv));
  415. // 10. Let actions be a new list.
  416. Vector<ActionObject> actions;
  417. // 11. For each action item in action items:
  418. TRY(action_items.try_for_each([&](auto const& action_item) -> ErrorOr<void, WebDriver::Error> {
  419. // 1. If action item is not an Object return error with error code invalid argument.
  420. if (!action_item.is_object())
  421. return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'actions' item is not an Object");
  422. auto action = TRY([&]() {
  423. switch (*type) {
  424. // 2. If type is "none" let action be the result of trying to process a null action with parameters id, and
  425. // action item.
  426. case InputSourceType::None:
  427. return process_null_action(id, action_item.as_object());
  428. // 3. Otherwise, if type is "key" let action be the result of trying to process a key action with parameters
  429. // id, and action item.
  430. case InputSourceType::Key:
  431. return process_key_action(id, action_item.as_object());
  432. // 4. Otherwise, if type is "pointer" let action be the result of trying to process a pointer action with
  433. // parameters id, parameters, action item, and actions options.
  434. case InputSourceType::Pointer:
  435. return process_pointer_action(id, *parameters, action_item.as_object(), actions_options);
  436. // 5. Otherwise, if type is "wheel" let action be the result of trying to process a wheel action with
  437. // parameters id, and action item, and actions options.
  438. case InputSourceType::Wheel:
  439. return process_wheel_action(id, action_item.as_object(), actions_options);
  440. }
  441. VERIFY_NOT_REACHED();
  442. }());
  443. // 6. Append action to actions.
  444. actions.append(move(action));
  445. return {};
  446. }));
  447. // 12. Return success with data actions.
  448. return actions;
  449. }
  450. // https://w3c.github.io/webdriver/#dfn-extract-an-action-sequence
  451. ErrorOr<Vector<Vector<ActionObject>>, WebDriver::Error> extract_an_action_sequence(InputState& input_state, JsonValue const& parameters, ActionsOptions const& actions_options)
  452. {
  453. // 1. Let actions be the result of getting a property named "actions" from parameters.
  454. // 2. If actions is undefined or is not an Array, return error with error code invalid argument.
  455. auto const& actions = *TRY(get_property<JsonArray const*>(parameters, "actions"sv));
  456. // 3. Let actions by tick be an empty List.
  457. Vector<Vector<ActionObject>> actions_by_tick;
  458. // 4. For each value action sequence corresponding to an indexed property in actions:
  459. TRY(actions.try_for_each([&](auto const& action_sequence) -> ErrorOr<void, WebDriver::Error> {
  460. // 1. Let source actions be the result of trying to process an input source action sequence given input state,
  461. // action sequence, and actions options.
  462. auto source_actions = TRY(process_input_source_action_sequence(input_state, action_sequence, actions_options));
  463. // 2. For each action in source actions:
  464. for (auto [i, action] : enumerate(source_actions)) {
  465. // 1. Let i be the zero-based index of action in source actions.
  466. // 2. If the length of actions by tick is less than i + 1, append a new List to actions by tick.
  467. if (actions_by_tick.size() < (i + 1))
  468. actions_by_tick.resize(i + 1);
  469. // 3. Append action to the List at index i in actions by tick.
  470. actions_by_tick[i].append(move(action));
  471. }
  472. return {};
  473. }));
  474. // 5. Return success with data actions by tick.
  475. return actions_by_tick;
  476. }
  477. // https://w3c.github.io/webdriver/#dfn-computing-the-tick-duration
  478. static AK::Duration compute_tick_duration(ReadonlySpan<ActionObject> tick_actions)
  479. {
  480. // 1. Let max duration be 0.
  481. auto max_duration = AK::Duration::zero();
  482. // 2. For each action object in tick actions:
  483. for (auto const& action_object : tick_actions) {
  484. // 1. let duration be undefined.
  485. Optional<AK::Duration> duration;
  486. // 2. If action object has subtype property set to "pause" or action object has type property set to "pointer"
  487. // and subtype property set to "pointerMove", or action object has type property set to "wheel" and subtype
  488. // property set to "scroll", let duration be equal to the duration property of action object.
  489. action_object.fields.visit(
  490. [&](OneOf<ActionObject::PauseFields, ActionObject::PointerMoveFields, ActionObject::ScrollFields> auto const& fields) {
  491. duration = fields.duration;
  492. },
  493. [](auto const&) {});
  494. // 3. If duration is not undefined, and duration is greater than max duration, let max duration be equal to duration.
  495. if (duration.has_value())
  496. max_duration = max(max_duration, *duration);
  497. }
  498. // 3. Return max duration.
  499. return max_duration;
  500. }
  501. // https://w3c.github.io/webdriver/#dfn-dispatch-a-pause-action
  502. static void dispatch_pause_action()
  503. {
  504. // 1. Return success with data null.
  505. }
  506. // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action
  507. 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)
  508. {
  509. // 1. Let pointerType be equal to action object's pointerType property.
  510. auto pointer_type = action_object.pointer_type;
  511. // 2. Let button be equal to action object's button property.
  512. auto button = action_object.button;
  513. // 3. If the source's pressed property contains button return success with data null.
  514. if (has_flag(source.pressed, button))
  515. return {};
  516. // 4. Let x be equal to source's x property.
  517. // 5. Let y be equal to source's y property.
  518. auto position = browsing_context.page().css_to_device_point(source.position);
  519. // 6. Add button to the set corresponding to source's pressed property, and let buttons be the resulting value of
  520. // that property.
  521. auto buttons = (source.pressed |= button);
  522. // 7. Let width be equal to action object's width property.
  523. // 8. Let height be equal to action object's height property.
  524. // 9. Let pressure be equal to action object's pressure property.
  525. // 10. Let tangentialPressure be equal to action object's tangentialPressure property.
  526. // 11. Let tiltX be equal to action object's tiltX property.
  527. // 12. Let tiltY be equal to action object's tiltY property.
  528. // 13. Let twist be equal to action object's twist property.
  529. // 14. Let altitudeAngle be equal to action object's altitudeAngle property.
  530. // 15. Let azimuthAngle be equal to action object's azimuthAngle property.
  531. // 16. Perform implementation-specific action dispatch steps on browsing context equivalent to pressing the button
  532. // numbered button on the pointer with pointerId equal to source's pointerId, having type pointerType at viewport
  533. // x coordinate x, viewport y coordinate y, width, height, pressure, tangentialPressure, tiltX, tiltY, twist,
  534. // altitudeAngle, azimuthAngle, with buttons buttons depressed in accordance with the requirements of [UI-EVENTS]
  535. // and [POINTER-EVENTS]. set ctrlKey, shiftKey, altKey, and metaKey equal to the corresponding items in global
  536. // key state. Type specific properties for the pointer that are not exposed through the webdriver API must be
  537. // set to the default value specified for hardware that doesn't support that property.
  538. switch (pointer_type) {
  539. case PointerInputSource::Subtype::Mouse:
  540. browsing_context.page().handle_mousedown(position, position, button, buttons, global_key_state.modifiers());
  541. break;
  542. case PointerInputSource::Subtype::Pen:
  543. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Pen events not implemented"sv);
  544. case PointerInputSource::Subtype::Touch:
  545. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Touch events not implemented"sv);
  546. }
  547. // 17. Return success with data null.
  548. return {};
  549. }
  550. // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerup-action
  551. 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)
  552. {
  553. // 1. Let pointerType be equal to action object's pointerType property.
  554. auto pointer_type = action_object.pointer_type;
  555. // 2. Let button be equal to action object's button property.
  556. auto button = action_object.button;
  557. // 3. If the source's pressed property does not contain button, return success with data null.
  558. if (!has_flag(source.pressed, button))
  559. return {};
  560. // 4. Let x be equal to source's x property.
  561. // 5. Let y be equal to source's y property.
  562. auto position = browsing_context.page().css_to_device_point(source.position);
  563. // 6. Remove button from the set corresponding to source's pressed property, and let buttons be the resulting value
  564. // of that property.
  565. auto buttons = (source.pressed &= ~button);
  566. // 7. Perform implementation-specific action dispatch steps on browsing context equivalent to releasing the button
  567. // numbered button on the pointer with pointerId equal to input source's pointerId, having type pointerType at
  568. // viewport x coordinate x, viewport y coordinate y, with buttons buttons depressed, in accordance with the
  569. // requirements of [UI-EVENTS] and [POINTER-EVENTS]. The generated events must set ctrlKey, shiftKey, altKey,
  570. // and metaKey equal to the corresponding items in global key state. Type specific properties for the pointer
  571. // that are not exposed through the webdriver API must be set to the default value specified for hardware that
  572. // doesn't support that property.
  573. switch (pointer_type) {
  574. case PointerInputSource::Subtype::Mouse:
  575. browsing_context.page().handle_mouseup(position, position, button, buttons, global_key_state.modifiers());
  576. break;
  577. case PointerInputSource::Subtype::Pen:
  578. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Pen events not implemented"sv);
  579. case PointerInputSource::Subtype::Touch:
  580. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Touch events not implemented"sv);
  581. }
  582. // 8. Return success with data null.
  583. return {};
  584. }
  585. // https://w3c.github.io/webdriver/#dfn-perform-a-pointer-move
  586. 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)
  587. {
  588. // FIXME: 1. Let time delta be the time since the beginning of the current tick, measured in milliseconds on a monotonic clock.
  589. // FIXME: 2. Let duration ratio be the ratio of time delta and duration, if duration is greater than 0, or 1 otherwise.
  590. // FIXME: 3. If duration ratio is 1, or close enough to 1 that the implementation will not further subdivide the move action,
  591. // let last be true. Otherwise let last be false.
  592. // FIXME: 4. If last is true, let x equal target x and y equal target y.
  593. // FIXME: 5. Otherwise let x equal an approximation to duration ratio × (target x - start x) + start x, and y equal an
  594. // approximation to duration ratio × (target y - start y) + start y.
  595. // 6. Let current x equal the x property of input state.
  596. // 7. Let current y equal the y property of input state.
  597. auto current = source.position;
  598. // 8. If x is not equal to current x or y is not equal to current y, run the following steps:
  599. if (current != coordinates) {
  600. // 1. Let buttons be equal to input state's buttons property.
  601. auto buttons = source.pressed;
  602. // 2. Perform implementation-specific action dispatch steps on browsing context equivalent to moving the pointer
  603. // with pointerId equal to input source's pointerId, having type pointerType from viewport x coordinate current
  604. // x, viewport y coordinate current y to viewport x coordinate x and viewport y coordinate y, width, height,
  605. // pressure, tangentialPressure, tiltX, tiltY, twist, altitudeAngle, azimuthAngle, with buttons buttons
  606. // depressed, in accordance with the requirements of [UI-EVENTS] and [POINTER-EVENTS]. The generated events
  607. // must set ctrlKey, shiftKey, altKey, and metaKey equal to the corresponding items in global key state. Type
  608. // specific properties for the pointer that are not exposed through the WebDriver API must be set to the
  609. // default value specified for hardware that doesn't support that property. In the case where the pointerType
  610. // is "pen" or "touch", and buttons is empty, this may be a no-op. For a pointer of type "mouse" this will
  611. // always produce events including at least a pointerMove event.
  612. auto position = browsing_context.page().css_to_device_point(coordinates);
  613. switch (action_object.pointer_type) {
  614. case PointerInputSource::Subtype::Mouse:
  615. browsing_context.page().handle_mousemove(position, position, buttons, global_key_state.modifiers());
  616. break;
  617. case PointerInputSource::Subtype::Pen:
  618. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Pen events not implemented"sv);
  619. case PointerInputSource::Subtype::Touch:
  620. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Touch events not implemented"sv);
  621. }
  622. // 3. Let input state's x property equal x and y property equal y.
  623. source.position = coordinates;
  624. }
  625. // FIXME: 9. If last is true, return.
  626. // FIXME: 10. Run the following substeps in parallel:
  627. {
  628. // FIXME: 1. Asynchronously wait for an implementation defined amount of time to pass.
  629. // FIXME: 2. Perform a pointer move with arguments input state, duration, start x, start y, target x, target y.
  630. }
  631. return {};
  632. }
  633. // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action
  634. 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)
  635. {
  636. auto viewport = browsing_context.page().top_level_traversable()->viewport_rect();
  637. // 1. Let x offset be equal to the x property of action object.
  638. // 2. Let y offset be equal to the y property of action object.
  639. // 3. Let origin be equal to the origin property of action object.
  640. // 4. Let (x, y) be the result of trying to get coordinates relative to an origin with source, x offset, y offset,
  641. // origin, browsing context, and actions options.
  642. auto coordinates = TRY(get_coordinates_relative_to_origin(source, action_object.position, viewport, action_object.origin, actions_options));
  643. // 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.
  644. if (coordinates.x() < 0 || coordinates.x() > viewport.width())
  645. return WebDriver::Error::from_code(WebDriver::ErrorCode::MoveTargetOutOfBounds, ByteString::formatted("Coordinates {} are out of bounds", coordinates));
  646. // 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.
  647. if (coordinates.y() < 0 || coordinates.y() > viewport.height())
  648. return WebDriver::Error::from_code(WebDriver::ErrorCode::MoveTargetOutOfBounds, ByteString::formatted("Coordinates {} are out of bounds", coordinates));
  649. // 7. Let duration be equal to action object's duration property if it is not undefined, or tick duration otherwise.
  650. [[maybe_unused]] auto duration = action_object.duration.value_or(tick_duration);
  651. // FIXME: 8. If duration is greater than 0 and inside any implementation-defined bounds, asynchronously wait for an
  652. // implementation defined amount of time to pass.
  653. // 9. Let width be equal to action object's width property.
  654. // 10. Let height be equal to action object's height property.
  655. // 11. Let pressure be equal to action object's pressure property.
  656. // 12. Let tangentialPressure be equal to action object's tangentialPressure property.
  657. // 13. Let tiltX be equal to action object's tiltX property.
  658. // 14. Let tiltY be equal to action object's tiltY property.
  659. // 15. Let twist be equal to action object's twist property.
  660. // 16. Let altitudeAngle be equal to action object's altitudeAngle property.
  661. // 17. Let azimuthAngle be equal to action object's azimuthAngle property.
  662. // 18. Perform a pointer move with arguments source, global key state, duration, start x, start y, x, y, width,
  663. // height, pressure, tangentialPressure, tiltX, tiltY, twist, altitudeAngle, azimuthAngle.
  664. TRY(perform_pointer_move(action_object, source, global_key_state, browsing_context, duration, coordinates));
  665. // 19. Return success with data null.
  666. return {};
  667. }
  668. // https://w3c.github.io/webdriver/#dfn-dispatch-actions-inner
  669. class ActionExecutor final : public JS::Cell {
  670. JS_CELL(ActionExecutor, JS::Cell);
  671. JS_DECLARE_ALLOCATOR(ActionExecutor);
  672. public:
  673. ActionExecutor(InputState& input_state, Vector<Vector<ActionObject>> actions_by_tick, HTML::BrowsingContext& browsing_context, ActionsOptions actions_options, OnActionsComplete on_complete)
  674. : m_browsing_context(browsing_context)
  675. , m_input_state(input_state)
  676. , m_actions_options(move(actions_options))
  677. , m_actions_by_tick(move(actions_by_tick))
  678. , m_on_complete(on_complete)
  679. {
  680. }
  681. // 1. For each item tick actions in actions by tick:
  682. void process_next_tick()
  683. {
  684. if (m_current_tick >= m_actions_by_tick.size()) {
  685. m_on_complete->function()(JsonValue {});
  686. return;
  687. }
  688. auto const& tick_actions = m_actions_by_tick[m_current_tick++];
  689. // 1. Let tick duration be the result of computing the tick duration with argument tick actions.
  690. auto tick_duration = compute_tick_duration(tick_actions);
  691. // 2. Try to dispatch tick actions with input state, tick actions, tick duration, browsing context, and actions options.
  692. if (auto result = dispatch_tick_actions(m_input_state, tick_actions, tick_duration, m_browsing_context, m_actions_options); result.is_error()) {
  693. m_on_complete->function()(result.release_error());
  694. return;
  695. }
  696. // 3. Wait until the following conditions are all met:
  697. // * There are no pending asynchronous waits arising from the last invocation of the dispatch tick actions
  698. // steps.
  699. // * The user agent event loop has spun enough times to process the DOM events generated by the last
  700. // invocation of the dispatch tick actions steps.
  701. // * At least tick duration milliseconds have passed.
  702. // FIXME: We currently do not implement any asynchronous waits. And we assume that Page will generally fire the
  703. // events of interest synchronously. So we simply wait for the tick duration to pass, and then let the
  704. // event loop spin a single time.
  705. m_timer = Core::Timer::create_single_shot(static_cast<int>(tick_duration.to_milliseconds()), [this]() {
  706. m_timer = nullptr;
  707. HTML::queue_a_task(HTML::Task::Source::Unspecified, nullptr, nullptr, JS::create_heap_function(heap(), [this]() {
  708. process_next_tick();
  709. }));
  710. });
  711. m_timer->start();
  712. }
  713. private:
  714. virtual void visit_edges(Cell::Visitor& visitor) override
  715. {
  716. Base::visit_edges(visitor);
  717. visitor.visit(m_browsing_context);
  718. visitor.visit(m_on_complete);
  719. }
  720. JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;
  721. InputState& m_input_state;
  722. ActionsOptions m_actions_options;
  723. Vector<Vector<ActionObject>> m_actions_by_tick;
  724. size_t m_current_tick { 0 };
  725. OnActionsComplete m_on_complete;
  726. RefPtr<Core::Timer> m_timer;
  727. };
  728. JS_DEFINE_ALLOCATOR(ActionExecutor);
  729. // https://w3c.github.io/webdriver/#dfn-dispatch-actions
  730. 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)
  731. {
  732. // 1. Let token be a new unique identifier.
  733. auto token = MUST(Crypto::generate_random_uuid());
  734. // 2. Enqueue token in input state's actions queue.
  735. input_state.actions_queue.append(token);
  736. // 3. Wait for token to be the first item in input state's actions queue.
  737. // FIXME: We should probably do this, but our WebDriver currently blocks until a given action is complete anyways,
  738. // so we should never arrive here with an ongoing action (which we verify for now).
  739. VERIFY(input_state.actions_queue.size() == 1);
  740. // 4. Let actions result be the result of dispatch actions inner with input state, actions by tick, browsing
  741. // context, and actions options.
  742. auto action_executor = browsing_context.heap().allocate_without_realm<ActionExecutor>(input_state, move(actions_by_tick), browsing_context, move(actions_options), on_complete);
  743. action_executor->process_next_tick();
  744. // 5. Dequeue input state's actions queue.
  745. auto executed_token = input_state.actions_queue.take_first();
  746. // 6. Assert: this returns token
  747. VERIFY(executed_token == token);
  748. // 7. Return actions result.
  749. return action_executor;
  750. }
  751. // https://w3c.github.io/webdriver/#dfn-dispatch-tick-actions
  752. 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)
  753. {
  754. // 1. For each action object in tick actions:
  755. for (auto const& action_object : tick_actions) {
  756. // 1. Let input id be equal to the value of action object's id property.
  757. auto const& input_id = action_object.id;
  758. // 2. Let source type be equal to the value of action object's type property.
  759. // NOTE: We don't actually need this, we can determine the event to fire based on the subtype.
  760. // 3. Let source be the result of get an input source given input state and input id.
  761. auto source = get_input_source(input_state, input_id);
  762. // 4. Assert: source is not undefined.
  763. VERIFY(source.has_value());
  764. // 5. Let global key state be the result of get the global key state with input state.
  765. auto global_key_state = get_global_key_state(input_state);
  766. // 6. Let subtype be action object's subtype.
  767. auto subtype = action_object.subtype;
  768. // 7. Let algorithm be the value of the column dispatch action algorithm from the following table where the
  769. // source type column is source type and the subtype column is equal to subtype.
  770. //
  771. // source type | subtype | Dispatch action algorithm
  772. // ---------------------------------------------------------------
  773. // "none" | "pause" | Dispatch a pause action
  774. // "key" | "pause" | Dispatch a pause action
  775. // "key" | "keyDown" | Dispatch a keyDown action
  776. // "key" | "keyUp" | Dispatch a keyUp action
  777. // "pointer" | "pause" | Dispatch a pause action
  778. // "pointer" | "pointerDown" | Dispatch a pointerDown action
  779. // "pointer" | "pointerUp" | Dispatch a pointerUp action
  780. // "pointer" | "pointerMove" | Dispatch a pointerMove action
  781. // "pointer" | "pointerCancel" | Dispatch a pointerCancel action
  782. // "wheel" | "pause" | Dispatch a pause action
  783. // "wheel" | "scroll" | Dispatch a scroll action
  784. // 8. Try to run algorithm with arguments action object, source, global key state, tick duration, browsing
  785. // context, and actions options.
  786. switch (subtype) {
  787. case ActionObject::Subtype::Pause:
  788. dispatch_pause_action();
  789. break;
  790. case ActionObject::Subtype::KeyDown:
  791. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Key down events not implemented"sv);
  792. case ActionObject::Subtype::KeyUp:
  793. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Key up events not implemented"sv);
  794. case ActionObject::Subtype::PointerDown:
  795. TRY(dispatch_pointer_down_action(action_object.pointer_up_down_fields(), source->get<PointerInputSource>(), global_key_state, browsing_context));
  796. break;
  797. case ActionObject::Subtype::PointerUp:
  798. TRY(dispatch_pointer_up_action(action_object.pointer_up_down_fields(), source->get<PointerInputSource>(), global_key_state, browsing_context));
  799. break;
  800. case ActionObject::Subtype::PointerMove:
  801. TRY(dispatch_pointer_move_action(action_object.pointer_move_fields(), source->get<PointerInputSource>(), global_key_state, tick_duration, browsing_context, actions_options));
  802. break;
  803. case ActionObject::Subtype::PointerCancel:
  804. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Pointer cancel events not implemented"sv);
  805. case ActionObject::Subtype::Scroll:
  806. return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "Scroll events not implemented"sv);
  807. }
  808. // 9. If subtype is "keyDown", append a copy of action object with the subtype property changed to "keyUp" to
  809. // input state's input cancel list.
  810. if (subtype == ActionObject::Subtype::KeyDown) {
  811. auto action_copy = action_object;
  812. action_copy.subtype = ActionObject::Subtype::KeyUp;
  813. input_state.input_cancel_list.append(move(action_copy));
  814. }
  815. // 10. If subtype is "pointerDown", append a copy of action object with the subtype property changed to
  816. // "pointerUp" to input state's input cancel list.
  817. if (subtype == ActionObject::Subtype::PointerDown) {
  818. auto action_copy = action_object;
  819. action_copy.subtype = ActionObject::Subtype::PointerUp;
  820. input_state.input_cancel_list.append(move(action_copy));
  821. }
  822. }
  823. // 2. Return success with data null.
  824. return {};
  825. }
  826. // https://w3c.github.io/webdriver/#dfn-dispatch-a-list-of-actions
  827. JS::NonnullGCPtr<JS::Cell> dispatch_list_of_actions(InputState& input_state, Vector<ActionObject> actions, HTML::BrowsingContext& browsing_context, ActionsOptions actions_options, OnActionsComplete on_complete)
  828. {
  829. // 1. Let tick actions be the list «actions»
  830. // 2. Let actions by tick be the list «tick actions».
  831. Vector<Vector<ActionObject>> actions_by_tick;
  832. actions_by_tick.append(move(actions));
  833. // 3. Return the result of dispatch actions with input state, actions by tick, browsing context, and actions options.
  834. return dispatch_actions(input_state, move(actions_by_tick), browsing_context, move(actions_options), on_complete);
  835. }
  836. }