LibWeb: Implement WebDriver action extraction

There's a lot of parsing involved in action extraction. So this patch
implements only that - actually executing actions will be a future
patch.
This commit is contained in:
Timothy Flynn 2024-09-26 16:33:21 -04:00 committed by Andreas Kling
parent 0c98c7637e
commit 6cf7d07a98
Notes: github-actions[bot] 2024-10-01 09:03:30 +00:00
3 changed files with 675 additions and 0 deletions

View file

@ -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

View file

@ -0,0 +1,549 @@
/*
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Enumerate.h>
#include <AK/GenericShorthands.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/Math.h>
#include <AK/Utf8View.h>
#include <LibWeb/WebDriver/Actions.h>
#include <LibWeb/WebDriver/ElementReference.h>
#include <LibWeb/WebDriver/InputState.h>
#include <LibWeb/WebDriver/Properties.h>
namespace Web::WebDriver {
static Optional<ActionObject::Subtype> 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<PointerUpDownFields, PointerMoveFields, PointerCancelFields> auto& fields) {
fields.pointer_type = pointer_type;
},
[](auto const&) { VERIFY_NOT_REACHED(); });
}
static Optional<ActionObject::Origin> determine_origin(ActionsOptions const& actions_options, Optional<JsonValue const&> 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<PointerParameters, WebDriver::Error> process_pointer_parameters(Optional<JsonValue const&> 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<void, WebDriver::Error> 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<i64>(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<ActionObject, WebDriver::Error> 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<ActionObject, WebDriver::Error> 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<void, WebDriver::Error> 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<double>(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<double>(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<double>(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<double>(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<i32>(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<i32>(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<u32>(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<double>(action_item, "altitudeAngle"sv, 0.0, AK::Pi<double> / 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<double>(action_item, "azimuthAngle"sv, 0.0, AK::Pi<double> * 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<void, WebDriver::Error> 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<i16>(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<void, WebDriver::Error> 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<i64>(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<i32>(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<i32>(action_item, "y"sv)));
return process_pointer_action_common(action_item, fields);
}
// https://w3c.github.io/webdriver/#dfn-process-a-pointer-action
static ErrorOr<ActionObject, WebDriver::Error> 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<ActionObject, WebDriver::Error> 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<i64>(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<i64>(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<i64>(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<i64>(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<i64>(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<Vector<ActionObject>, 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<PointerParameters> parameters;
Optional<PointerInputSource::Subtype> 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<PointerInputSource>(); 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<JsonArray const*>(action_sequence, "actions"sv));
// 10. Let actions be a new list.
Vector<ActionObject> actions;
// 11. For each action item in action items:
TRY(action_items.try_for_each([&](auto const& action_item) -> ErrorOr<void, WebDriver::Error> {
// 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<Vector<Vector<ActionObject>>, 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<JsonArray const*>(parameters, "actions"sv));
// 3. Let actions by tick be an empty List.
Vector<Vector<ActionObject>> 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<void, WebDriver::Error> {
// 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;
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Time.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibJS/Heap/GCPtr.h>
#include <LibWeb/Forward.h>
#include <LibWeb/PixelUnits.h>
#include <LibWeb/UIEvents/MouseButton.h>
#include <LibWeb/WebDriver/Error.h>
#include <LibWeb/WebDriver/InputSource.h>
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<OriginType, String>;
struct PauseFields {
Optional<AK::Duration> duration;
};
struct KeyFields {
String value;
};
struct PointerFields {
PointerInputSource::Subtype pointer_type { PointerInputSource::Subtype::Mouse };
Optional<double> width;
Optional<double> height;
Optional<double> pressure;
Optional<double> tangential_pressure;
Optional<i32> tilt_x;
Optional<i32> tilt_y;
Optional<u32> twist;
Optional<double> altitude_angle;
Optional<double> azimuth_angle;
};
struct PointerUpDownFields : public PointerFields {
UIEvents::MouseButton button { UIEvents::MouseButton::None };
};
struct PointerMoveFields : public PointerFields {
Optional<AK::Duration> duration;
Origin origin { OriginType::Viewport };
CSSPixelPoint position;
};
struct PointerCancelFields {
PointerInputSource::Subtype pointer_type { PointerInputSource::Subtype::Mouse };
};
struct ScrollFields {
Origin origin { OriginType::Viewport };
Optional<AK::Duration> 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>(); }
PauseFields const& pause_fields() const { return fields.get<PauseFields>(); }
KeyFields& key_fields() { return fields.get<KeyFields>(); }
KeyFields const& key_fields() const { return fields.get<KeyFields>(); }
PointerUpDownFields& pointer_up_down_fields() { return fields.get<PointerUpDownFields>(); }
PointerUpDownFields const& pointer_up_down_fields() const { return fields.get<PointerUpDownFields>(); }
PointerMoveFields& pointer_move_fields() { return fields.get<PointerMoveFields>(); }
PointerMoveFields const& pointer_move_fields() const { return fields.get<PointerMoveFields>(); }
PointerCancelFields& pointer_cancel_fields() { return fields.get<PointerCancelFields>(); }
PointerCancelFields const& pointer_cancel_fields() const { return fields.get<PointerCancelFields>(); }
ScrollFields& scroll_fields() { return fields.get<ScrollFields>(); }
ScrollFields const& scroll_fields() const { return fields.get<ScrollFields>(); }
String id;
InputSourceType type;
Subtype subtype;
using Fields = Variant<PauseFields, KeyFields, PointerUpDownFields, PointerMoveFields, PointerCancelFields, ScrollFields>;
Fields fields;
};
// https://w3c.github.io/webdriver/#dfn-actions-options
struct ActionsOptions {
Function<bool(JsonObject const&)> is_element_origin;
Function<ErrorOr<JS::NonnullGCPtr<DOM::Element>, WebDriver::Error>(StringView)> get_element_origin;
};
ErrorOr<Vector<Vector<ActionObject>>, WebDriver::Error> extract_an_action_sequence(InputState&, JsonValue const&, ActionsOptions const&);
}