diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 1f107b1212c..6f0a33e6506 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -730,6 +730,7 @@ set(SOURCES WebAudio/OfflineAudioContext.cpp WebAudio/OscillatorNode.cpp WebAudio/PeriodicWave.cpp + WebDriver/Actions.cpp WebDriver/Capabilities.cpp WebDriver/Client.cpp WebDriver/Contexts.cpp diff --git a/Userland/Libraries/LibWeb/WebDriver/Actions.cpp b/Userland/Libraries/LibWeb/WebDriver/Actions.cpp new file mode 100644 index 00000000000..eca3cbb5741 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebDriver/Actions.cpp @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::WebDriver { + +static Optional action_object_subtype_from_string(StringView action_subtype) +{ + if (action_subtype == "pause"sv) + return ActionObject::Subtype::Pause; + if (action_subtype == "keyUp"sv) + return ActionObject::Subtype::KeyUp; + if (action_subtype == "keyDown"sv) + return ActionObject::Subtype::KeyDown; + if (action_subtype == "pointerUp"sv) + return ActionObject::Subtype::PointerUp; + if (action_subtype == "pointerDown"sv) + return ActionObject::Subtype::PointerDown; + if (action_subtype == "pointerMove"sv) + return ActionObject::Subtype::PointerMove; + if (action_subtype == "pointerCancel"sv) + return ActionObject::Subtype::PointerCancel; + if (action_subtype == "scroll"sv) + return ActionObject::Subtype::Scroll; + return {}; +} + +static ActionObject::Fields fields_from_subtype(ActionObject::Subtype subtype) +{ + switch (subtype) { + case ActionObject::Subtype::Pause: + return ActionObject::PauseFields {}; + + case ActionObject::Subtype::KeyUp: + case ActionObject::Subtype::KeyDown: + return ActionObject::KeyFields {}; + + case ActionObject::Subtype::PointerUp: + case ActionObject::Subtype::PointerDown: + return ActionObject::PointerUpDownFields {}; + + case ActionObject::Subtype::PointerMove: + return ActionObject::PointerMoveFields {}; + + case ActionObject::Subtype::PointerCancel: + return ActionObject::PointerCancelFields {}; + + case ActionObject::Subtype::Scroll: + return ActionObject::ScrollFields {}; + } + + VERIFY_NOT_REACHED(); +} + +ActionObject::ActionObject(String id, InputSourceType type, Subtype subtype) + : id(move(id)) + , type(type) + , subtype(subtype) + , fields(fields_from_subtype(subtype)) +{ +} + +void ActionObject::set_pointer_type(PointerInputSource::Subtype pointer_type) +{ + fields.visit( + [&](OneOf auto& fields) { + fields.pointer_type = pointer_type; + }, + [](auto const&) { VERIFY_NOT_REACHED(); }); +} + +static Optional determine_origin(ActionsOptions const& actions_options, Optional const& origin) +{ + if (!origin.has_value()) + return ActionObject::OriginType::Viewport; + + if (origin->is_string()) { + if (origin->as_string() == "viewport"sv) + return ActionObject::OriginType::Viewport; + if (origin->as_string() == "pointer"sv) + return ActionObject::OriginType::Pointer; + } + + if (origin->is_object()) { + if (actions_options.is_element_origin(origin->as_object())) + return MUST(String::from_byte_string(extract_web_element_reference(origin->as_object()))); + } + + return {}; +} + +// https://w3c.github.io/webdriver/#dfn-process-pointer-parameters +struct PointerParameters { + PointerInputSource::Subtype pointer_type { PointerInputSource::Subtype::Mouse }; +}; +static ErrorOr process_pointer_parameters(Optional const& parameters_data) +{ + // 1. Let parameters be the default pointer parameters. + PointerParameters parameters; + + // 2. If parameters data is undefined, return success with data parameters. + if (!parameters_data.has_value()) + return parameters; + + // 3. If parameters data is not an Object, return error with error code invalid argument. + if (!parameters_data->is_object()) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'parameters' is not an Object"); + + // 4. Let pointer type be the result of getting a property named "pointerType" from parameters data. + auto pointer_type = TRY(get_optional_property(parameters_data->as_object(), "pointerType"sv)); + + // 5. If pointer type is not undefined: + if (pointer_type.has_value()) { + // 1. If pointer type does not have one of the values "mouse", "pen", or "touch", return error with error code + // invalid argument. + auto parsed_pointer_type = pointer_input_source_subtype_from_string(*pointer_type); + + if (!parsed_pointer_type.has_value()) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'pointerType' must be one of 'mouse', 'pen', or 'touch'"); + + // 2. Set the pointerType property of parameters to pointer type. + parameters.pointer_type = *parsed_pointer_type; + } + + // 6. Return success with data parameters. + return parameters; +} + +// https://w3c.github.io/webdriver/#dfn-process-a-pause-action +static ErrorOr process_pause_action(JsonObject const& action_item, ActionObject& action) +{ + // 1. Let duration be the result of getting the property "duration" from action item. + // 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. + // 3. Set the duration property of action to duration. + if (auto duration = TRY(get_optional_property_with_limits(action_item, "duration"sv, 0, {})); duration.has_value()) + action.pause_fields().duration = AK::Duration::from_milliseconds(*duration); + + // 4. Return success with data action. + return {}; +} + +// https://w3c.github.io/webdriver/#dfn-process-a-null-action +static ErrorOr process_null_action(String id, JsonObject const& action_item) +{ + // 1. Let subtype be the result of getting a property named "type" from action item. + auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv))); + + // 2. If subtype is not "pause", return error with error code invalid argument. + if (subtype != ActionObject::Subtype::Pause) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be 'pause'"); + + // 3. Let action be an action object constructed with arguments id, "none", and subtype. + ActionObject action { move(id), InputSourceType::None, *subtype }; + + // 4. Let result be the result of trying to process a pause action with arguments action item and action. + TRY(process_pause_action(action_item, action)); + + // 5. Return result. + return action; +} + +// https://w3c.github.io/webdriver/#dfn-process-a-key-action +static ErrorOr process_key_action(String id, JsonObject const& action_item) +{ + using enum ActionObject::Subtype; + + // 1. Let subtype be the result of getting a property named "type" from action item. + auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv))); + + // 2. If subtype is not one of the values "keyUp", "keyDown", or "pause", return an error with error code invalid argument. + if (!first_is_one_of(subtype, KeyUp, KeyDown, Pause)) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'keyUp', 'keyDown', or 'pause'"); + + // 3. Let action be an action object constructed with arguments id, "key", and subtype. + ActionObject action { move(id), InputSourceType::Key, *subtype }; + + // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action + // item and action, and return result. + if (subtype == Pause) { + TRY(process_pause_action(action_item, action)); + return action; + } + + // 5. Let key be the result of getting a property named "value" from action item. + auto key = TRY(get_property(action_item, "value"sv)); + + // 6. If key is not a String containing a single unicode code point [or grapheme cluster?] return error with error + // code invalid argument. + if (Utf8View { key }.length() != 1) { + // FIXME: The spec seems undecided on whether grapheme clusters should be supported. Update this step to check + // for graphemes if we end up needing to support them. + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'value' must be a single code point"); + } + + // 7. Set the value property on action to key. + action.key_fields().value = MUST(String::from_byte_string(key)); + + // 8. Return success with data action. + return action; +} + +// Common steps between: +// https://w3c.github.io/webdriver/#dfn-process-a-pointer-up-or-pointer-down-action +// https://w3c.github.io/webdriver/#dfn-process-a-pointer-move-action +static ErrorOr process_pointer_action_common(JsonObject const& action_item, ActionObject::PointerFields& fields) +{ + // 4. Let width be the result of getting the property width from action item. + // 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. + // 6. Set the width property of action to width. + fields.width = TRY(get_optional_property_with_limits(action_item, "width"sv, 0.0, {})); + + // 7. Let height be the result of getting the property height from action item. + // 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. + // 9. Set the height property of action to height. + fields.height = TRY(get_optional_property_with_limits(action_item, "height"sv, 0.0, {})); + + // 10. Let pressure be the result of getting the property pressure from action item. + // 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. + // 12. Set the pressure property of action to pressure. + fields.pressure = TRY(get_optional_property_with_limits(action_item, "pressure"sv, 0.0, 1.0)); + + // 13. Let tangentialPressure be the result of getting the property tangentialPressure from action item. + // 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. + // 15. Set the tangentialPressure property of action to tangentialPressure. + fields.tangential_pressure = TRY(get_optional_property_with_limits(action_item, "tangentialPressure"sv, -1.0, 1.0)); + + // 16. Let tiltX be the result of getting the property tiltX from action item. + // 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. + // 18. Set the tiltX property of action to tiltX. + fields.tilt_x = TRY(get_optional_property_with_limits(action_item, "tiltX"sv, -90, 90)); + + // 19. Let tiltY be the result of getting the property tiltY from action item. + // 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. + // 21. Set the tiltY property of action to tiltY. + fields.tilt_y = TRY(get_optional_property_with_limits(action_item, "tiltY"sv, -90, 90)); + + // 22. Let twist be the result of getting the property twist from action item. + // 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. + // 24. Set the twist property of action to twist. + fields.twist = TRY(get_optional_property_with_limits(action_item, "twist"sv, 0, 359)); + + // 25. Let altitudeAngle be the result of getting the property altitudeAngle from action item. + // 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. + // 27. Set the altitudeAngle property of action to altitudeAngle. + fields.altitude_angle = TRY(get_optional_property_with_limits(action_item, "altitudeAngle"sv, 0.0, AK::Pi / 2.0)); + + // 28. Let azimuthAngle be the result of getting the property azimuthAngle from action item. + // 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. + // 30. Set the azimuthAngle property of action to azimuthAngle. + fields.azimuth_angle = TRY(get_optional_property_with_limits(action_item, "azimuthAngle"sv, 0.0, AK::Pi * 2.0)); + + // 31. Return success with data null. + return {}; +} + +// https://w3c.github.io/webdriver/#dfn-process-a-pointer-up-or-pointer-down-action +static ErrorOr process_pointer_up_or_down_action(JsonObject const& action_item, ActionObject& action) +{ + auto& fields = action.pointer_up_down_fields(); + + // 1. Let button be the result of getting the property button from action item. + // 2. If button is not an Integer greater than or equal to 0 return error with error code invalid argument. + // 3. Set the button property of action to button. + fields.button = UIEvents::button_code_to_mouse_button(TRY(get_property_with_limits(action_item, "button"sv, 0, {}))); + + return process_pointer_action_common(action_item, fields); +} + +// https://w3c.github.io/webdriver/#dfn-process-a-pointer-move-action +static ErrorOr process_pointer_move_action(JsonObject const& action_item, ActionObject& action, ActionsOptions const& actions_options) +{ + auto& fields = action.pointer_move_fields(); + + // 1. Let duration be the result of getting the property duration from action item. + // 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. + // 3. Set the duration property of action to duration. + if (auto duration = TRY(get_optional_property_with_limits(action_item, "duration"sv, 0, {})); duration.has_value()) + fields.duration = AK::Duration::from_milliseconds(*duration); + + // 4. Let origin be the result of getting the property origin from action item. + // 5. If origin is undefined let origin equal "viewport". + auto origin = determine_origin(actions_options, action_item.get("origin"sv)); + + // 6. If origin is not equal to "viewport" or "pointer", and actions options is element origin steps given origin + // return false, return error with error code invalid argument. + if (!origin.has_value()) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'origin' must be 'viewport', 'pointer', or an element origin"); + + // 7. Set the origin property of action to origin. + fields.origin = origin.release_value(); + + // 8. Let x be the result of getting the property x from action item. + // 9. If x is not an Integer, return error with error code invalid argument. + // 10. Set the x property of action to x. + fields.position.set_x(TRY(get_property(action_item, "x"sv))); + + // 11. Let y be the result of getting the property y from action item. + // 12. If y is not an Integer, return error with error code invalid argument. + // 13. Set the y property of action to y. + fields.position.set_y(TRY(get_property(action_item, "y"sv))); + + return process_pointer_action_common(action_item, fields); +} + +// https://w3c.github.io/webdriver/#dfn-process-a-pointer-action +static ErrorOr process_pointer_action(String id, PointerParameters const& parameters, JsonObject const& action_item, ActionsOptions const& actions_options) +{ + using enum ActionObject::Subtype; + + // 1. Let subtype be the result of getting a property named "type" from action item. + auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv))); + + // 2. If subtype is not one of the values "pause", "pointerUp", "pointerDown", "pointerMove", or "pointerCancel", return an error with error code invalid argument. + if (!first_is_one_of(subtype, Pause, PointerUp, PointerDown, PointerMove, PointerCancel)) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'pause', 'pointerUp', 'pointerDown', 'pointerMove', or 'pointerCancel'"); + + // 3. Let action be an action object constructed with arguments id, "pointer", and subtype. + ActionObject action { move(id), InputSourceType::Pointer, *subtype }; + + // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action + // item, action, and actions options, and return result. + if (subtype == Pause) { + TRY(process_pause_action(action_item, action)); + return action; + } + + // 5. Set the pointerType property of action equal to the pointerType property of parameters. + action.set_pointer_type(parameters.pointer_type); + + // 6. If subtype is "pointerUp" or "pointerDown", process a pointer up or pointer down action with arguments action + // item and action. If doing so results in an error, return that error. + if (subtype == PointerUp || subtype == PointerDown) { + TRY(process_pointer_up_or_down_action(action_item, action)); + } + + // 7. If subtype is "pointerMove" process a pointer move action with arguments action item, action, and actions + // options. If doing so results in an error, return that error. + else if (subtype == PointerMove) { + TRY(process_pointer_move_action(action_item, action, actions_options)); + } + + // 8. If subtype is "pointerCancel" process a pointer cancel action. If doing so results in an error, return that error. + else if (subtype == PointerCancel) { + // FIXME: There are no spec steps to "process a pointer cancel action" yet. + return WebDriver::Error::from_code(WebDriver::ErrorCode::UnsupportedOperation, "pointerCancel events not implemented"sv); + } + + // 9. Return success with data action. + return action; +} + +// https://w3c.github.io/webdriver/#dfn-process-a-wheel-action +static ErrorOr process_wheel_action(String id, JsonObject const& action_item, ActionsOptions const& actions_options) +{ + using enum ActionObject::Subtype; + + // 1. Let subtype be the result of getting a property named "type" from action item. + auto subtype = action_object_subtype_from_string(TRY(get_property(action_item, "type"sv))); + + // 2. If subtype is not the value "pause", or "scroll", return an error with error code invalid argument. + if (!first_is_one_of(subtype, Pause, Scroll)) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'pause' or 'scroll'"); + + // 3. Let action be an action object constructed with arguments id, "wheel", and subtype. + ActionObject action { move(id), InputSourceType::Wheel, *subtype }; + + // 4. If subtype is "pause", let result be the result of trying to process a pause action with arguments action + // item and action, and return result. + if (subtype == Pause) { + TRY(process_pause_action(action_item, action)); + return action; + } + + auto& fields = action.scroll_fields(); + + // 5. Let duration be the result of getting a property named "duration" from action item. + // 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. + // 7. Set the duration property of action to duration. + if (auto duration = TRY(get_optional_property_with_limits(action_item, "duration"sv, 0, {})); duration.has_value()) + fields.duration = AK::Duration::from_milliseconds(*duration); + + // 8. Let origin be the result of getting the property origin from action item. + // 9. If origin is undefined let origin equal "viewport". + auto origin = determine_origin(actions_options, action_item.get("origin"sv)); + + // 10. If origin is not equal to "viewport", or actions options' is element origin steps given origin return false, + // return error with error code invalid argument. + if (!origin.has_value() || origin == ActionObject::OriginType::Pointer) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'origin' must be 'viewport' or an element origin"); + + // 11. Set the origin property of action to origin. + fields.origin = origin.release_value(); + + // 12. Let x be the result of getting the property x from action item. + // 13. If x is not an Integer, return error with error code invalid argument. + // 14. Set the x property of action to x. + fields.x = TRY(get_property(action_item, "x"sv)); + + // 15. Let y be the result of getting the property y from action item. + // 16. If y is not an Integer, return error with error code invalid argument. + // 17. Set the y property of action to y. + fields.y = TRY(get_property(action_item, "y"sv)); + + // 18. Let deltaX be the result of getting the property deltaX from action item. + // 19. If deltaX is not an Integer, return error with error code invalid argument. + // 20. Set the deltaX property of action to deltaX. + fields.delta_x = TRY(get_property(action_item, "deltaX"sv)); + + // 21. Let deltaY be the result of getting the property deltaY from action item. + // 22. If deltaY is not an Integer, return error with error code invalid argument. + // 23. Set the deltaY property of action to deltaY. + fields.delta_y = TRY(get_property(action_item, "deltaY"sv)); + + // 24. Return success with data action. + return action; +} + +// https://w3c.github.io/webdriver/#dfn-process-an-input-source-action-sequence +static ErrorOr, WebDriver::Error> process_input_source_action_sequence(InputState& input_state, JsonValue const& action_sequence, ActionsOptions const& actions_options) +{ + // 1. Let type be the result of getting a property named "type" from action sequence. + auto type = input_source_type_from_string(TRY(get_property(action_sequence, "type"sv))); + + // 2. If type is not "key", "pointer", "wheel", or "none", return an error with error code invalid argument. + if (!type.has_value()) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' must be one of 'key', 'pointer', 'wheel', or 'none'"); + + // 3. Let id be the result of getting the property "id" from action sequence. + // 4. If id is undefined or is not a String, return error with error code invalid argument. + auto const id = MUST(String::from_byte_string(TRY(get_property(action_sequence, "id"sv)))); + + // 5. If type is equal to "pointer", let parameters data be the result of getting the property "parameters" from + // action sequence. Then let parameters be the result of trying to process pointer parameters with argument + // parameters data. + Optional parameters; + Optional subtype; + + if (type == InputSourceType::Pointer) { + parameters = TRY(process_pointer_parameters(action_sequence.as_object().get("parameters"sv))); + subtype = parameters->pointer_type; + } + + // 6. Let source be the result of trying to get or create an input source given input state, type and id. + auto const& source = *TRY(get_or_create_input_source(input_state, *type, id, subtype)); + + // 7. If parameters is not undefined, then if its pointerType property is not equal to source's subtype property, + // return an error with error code invalid argument. + if (auto const* pointer_input_source = source.get_pointer(); pointer_input_source && parameters.has_value()) { + if (parameters->pointer_type != pointer_input_source->subtype) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Invalid 'pointerType' property"); + } + + // 8. Let action items be the result of getting a property named "actions" from action sequence. + // 9. If action items is not an Array, return error with error code invalid argument. + auto const& action_items = *TRY(get_property(action_sequence, "actions"sv)); + + // 10. Let actions be a new list. + Vector actions; + + // 11. For each action item in action items: + TRY(action_items.try_for_each([&](auto const& action_item) -> ErrorOr { + // 1. If action item is not an Object return error with error code invalid argument. + if (!action_item.is_object()) + return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'actions' item is not an Object"); + + auto action = TRY([&]() { + switch (*type) { + // 2. If type is "none" let action be the result of trying to process a null action with parameters id, and + // action item. + case InputSourceType::None: + return process_null_action(id, action_item.as_object()); + + // 3. Otherwise, if type is "key" let action be the result of trying to process a key action with parameters + // id, and action item. + case InputSourceType::Key: + return process_key_action(id, action_item.as_object()); + + // 4. Otherwise, if type is "pointer" let action be the result of trying to process a pointer action with + // parameters id, parameters, action item, and actions options. + case InputSourceType::Pointer: + return process_pointer_action(id, *parameters, action_item.as_object(), actions_options); + + // 5. Otherwise, if type is "wheel" let action be the result of trying to process a wheel action with + // parameters id, and action item, and actions options. + case InputSourceType::Wheel: + return process_wheel_action(id, action_item.as_object(), actions_options); + } + + VERIFY_NOT_REACHED(); + }()); + + // 6. Append action to actions. + actions.append(move(action)); + return {}; + })); + + // 12. Return success with data actions. + return actions; +} + +// https://w3c.github.io/webdriver/#dfn-extract-an-action-sequence +ErrorOr>, WebDriver::Error> extract_an_action_sequence(InputState& input_state, JsonValue const& parameters, ActionsOptions const& actions_options) +{ + // 1. Let actions be the result of getting a property named "actions" from parameters. + // 2. If actions is undefined or is not an Array, return error with error code invalid argument. + auto const& actions = *TRY(get_property(parameters, "actions"sv)); + + // 3. Let actions by tick be an empty List. + Vector> actions_by_tick; + + // 4. For each value action sequence corresponding to an indexed property in actions: + TRY(actions.try_for_each([&](auto const& action_sequence) -> ErrorOr { + // 1. Let source actions be the result of trying to process an input source action sequence given input state, + // action sequence, and actions options. + auto source_actions = TRY(process_input_source_action_sequence(input_state, action_sequence, actions_options)); + + // 2. For each action in source actions: + for (auto [i, action] : enumerate(source_actions)) { + // 1. Let i be the zero-based index of action in source actions. + // 2. If the length of actions by tick is less than i + 1, append a new List to actions by tick. + if (actions_by_tick.size() < (i + 1)) + actions_by_tick.resize(i + 1); + + // 3. Append action to the List at index i in actions by tick. + actions_by_tick[i].append(move(action)); + } + + return {}; + })); + + // 5. Return success with data actions by tick. + return actions_by_tick; +} + +} diff --git a/Userland/Libraries/LibWeb/WebDriver/Actions.h b/Userland/Libraries/LibWeb/WebDriver/Actions.h new file mode 100644 index 00000000000..cb3e42f187a --- /dev/null +++ b/Userland/Libraries/LibWeb/WebDriver/Actions.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::WebDriver { + +// https://w3c.github.io/webdriver/#dfn-action-object +struct ActionObject { + enum class Subtype { + Pause, + KeyUp, + KeyDown, + PointerUp, + PointerDown, + PointerMove, + PointerCancel, + Scroll, + }; + + enum class OriginType { + Viewport, + Pointer, + }; + using Origin = Variant; + + struct PauseFields { + Optional duration; + }; + + struct KeyFields { + String value; + }; + + struct PointerFields { + PointerInputSource::Subtype pointer_type { PointerInputSource::Subtype::Mouse }; + Optional width; + Optional height; + Optional pressure; + Optional tangential_pressure; + Optional tilt_x; + Optional tilt_y; + Optional twist; + Optional altitude_angle; + Optional azimuth_angle; + }; + + struct PointerUpDownFields : public PointerFields { + UIEvents::MouseButton button { UIEvents::MouseButton::None }; + }; + + struct PointerMoveFields : public PointerFields { + Optional duration; + Origin origin { OriginType::Viewport }; + CSSPixelPoint position; + }; + + struct PointerCancelFields { + PointerInputSource::Subtype pointer_type { PointerInputSource::Subtype::Mouse }; + }; + + struct ScrollFields { + Origin origin { OriginType::Viewport }; + Optional duration; + i64 x { 0 }; + i64 y { 0 }; + i64 delta_x { 0 }; + i64 delta_y { 0 }; + }; + + ActionObject(String id, InputSourceType type, Subtype subtype); + + void set_pointer_type(PointerInputSource::Subtype); + + PauseFields& pause_fields() { return fields.get(); } + PauseFields const& pause_fields() const { return fields.get(); } + + KeyFields& key_fields() { return fields.get(); } + KeyFields const& key_fields() const { return fields.get(); } + + PointerUpDownFields& pointer_up_down_fields() { return fields.get(); } + PointerUpDownFields const& pointer_up_down_fields() const { return fields.get(); } + + PointerMoveFields& pointer_move_fields() { return fields.get(); } + PointerMoveFields const& pointer_move_fields() const { return fields.get(); } + + PointerCancelFields& pointer_cancel_fields() { return fields.get(); } + PointerCancelFields const& pointer_cancel_fields() const { return fields.get(); } + + ScrollFields& scroll_fields() { return fields.get(); } + ScrollFields const& scroll_fields() const { return fields.get(); } + + String id; + InputSourceType type; + Subtype subtype; + + using Fields = Variant; + Fields fields; +}; + +// https://w3c.github.io/webdriver/#dfn-actions-options +struct ActionsOptions { + Function is_element_origin; + Function, WebDriver::Error>(StringView)> get_element_origin; +}; + +ErrorOr>, WebDriver::Error> extract_an_action_sequence(InputState&, JsonValue const&, ActionsOptions const&); + +}