LibWeb: Begin implementing the WebDriver Input State and Input Source
These concepts are rather tightly coupled. This implements the types and AOs needed to handle the Perform Actions endoint.
This commit is contained in:
parent
94c243acd6
commit
0c98c7637e
Notes:
github-actions[bot]
2024-10-01 09:03:37 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/0c98c7637e1 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1566
6 changed files with 379 additions and 0 deletions
|
@ -737,6 +737,8 @@ set(SOURCES
|
|||
WebDriver/ElementReference.cpp
|
||||
WebDriver/Error.cpp
|
||||
WebDriver/ExecuteScript.cpp
|
||||
WebDriver/InputSource.cpp
|
||||
WebDriver/InputState.cpp
|
||||
WebDriver/Response.cpp
|
||||
WebDriver/Screenshot.cpp
|
||||
WebDriver/TimeoutsConfiguration.cpp
|
||||
|
|
|
@ -786,6 +786,11 @@ class ExceptionOr;
|
|||
using Promise = JS::PromiseCapability;
|
||||
}
|
||||
|
||||
namespace Web::WebDriver {
|
||||
struct ActionObject;
|
||||
struct InputState;
|
||||
};
|
||||
|
||||
namespace Web::WebSockets {
|
||||
class WebSocket;
|
||||
}
|
||||
|
|
208
Userland/Libraries/LibWeb/WebDriver/InputSource.cpp
Normal file
208
Userland/Libraries/LibWeb/WebDriver/InputSource.cpp
Normal file
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/WebDriver/InputSource.h>
|
||||
#include <LibWeb/WebDriver/InputState.h>
|
||||
|
||||
namespace Web::WebDriver {
|
||||
|
||||
static InputSourceType input_source_type(InputSource const& input_source)
|
||||
{
|
||||
return input_source.visit(
|
||||
[](NullInputSource const&) { return InputSourceType::None; },
|
||||
[](KeyInputSource const&) { return InputSourceType::Key; },
|
||||
[](PointerInputSource const&) { return InputSourceType::Pointer; },
|
||||
[](WheelInputSource const&) { return InputSourceType::Wheel; });
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-get-a-pointer-id
|
||||
static u32 get_pointer_id(InputState const& input_state, PointerInputSource::Subtype subtype)
|
||||
{
|
||||
// 1. Let minimum id be 0 if subtype is "mouse", or 2 otherwise.
|
||||
auto minimum_id = subtype == PointerInputSource::Subtype::Mouse ? 0u : 2u;
|
||||
|
||||
// 2. Let pointer ids be an empty set.
|
||||
HashTable<u32> pointer_ids;
|
||||
|
||||
// 3. Let sources be the result of getting the values with input state's input state map.
|
||||
// 4. For each source in sources:
|
||||
for (auto const& source : input_state.input_state_map) {
|
||||
// 1. If source is a pointer input source, append source's pointerId to pointer ids.
|
||||
if (auto const* pointer_input_source = source.value.get_pointer<PointerInputSource>())
|
||||
pointer_ids.set(pointer_input_source->pointer_id);
|
||||
}
|
||||
|
||||
// 5. Return the smallest integer that is greater than or equal to minimum id and that is not contained in pointer ids.
|
||||
for (u32 integer = minimum_id; integer < NumericLimits<u32>::max(); ++integer) {
|
||||
if (!pointer_ids.contains(integer))
|
||||
return integer;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-create-a-pointer-input-source
|
||||
PointerInputSource::PointerInputSource(InputState const& input_state, PointerInputSource::Subtype subtype)
|
||||
: subtype(subtype)
|
||||
, pointer_id(get_pointer_id(input_state, subtype))
|
||||
{
|
||||
// To create a pointer input source object given input state, and subtype, return a new pointer input source with
|
||||
// subtype set to subtype, pointerId set to get a pointer id with input state and subtype, and the other items set
|
||||
// to their default values.
|
||||
}
|
||||
|
||||
UIEvents::KeyModifier GlobalKeyState::modifiers() const
|
||||
{
|
||||
auto modifiers = UIEvents::KeyModifier::Mod_None;
|
||||
|
||||
if (ctrl_key)
|
||||
modifiers |= UIEvents::KeyModifier::Mod_Ctrl;
|
||||
if (shift_key)
|
||||
modifiers |= UIEvents::KeyModifier::Mod_Shift;
|
||||
if (alt_key)
|
||||
modifiers |= UIEvents::KeyModifier::Mod_Alt;
|
||||
if (meta_key)
|
||||
modifiers |= UIEvents::KeyModifier::Mod_Super;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
Optional<InputSourceType> input_source_type_from_string(StringView input_source_type)
|
||||
{
|
||||
if (input_source_type == "none"sv)
|
||||
return InputSourceType::None;
|
||||
if (input_source_type == "key"sv)
|
||||
return InputSourceType::Key;
|
||||
if (input_source_type == "pointer"sv)
|
||||
return InputSourceType::Pointer;
|
||||
if (input_source_type == "wheel"sv)
|
||||
return InputSourceType::Wheel;
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<PointerInputSource::Subtype> pointer_input_source_subtype_from_string(StringView pointer_type)
|
||||
{
|
||||
if (pointer_type == "mouse"sv)
|
||||
return PointerInputSource::Subtype::Mouse;
|
||||
if (pointer_type == "pen"sv)
|
||||
return PointerInputSource::Subtype::Pen;
|
||||
if (pointer_type == "touch"sv)
|
||||
return PointerInputSource::Subtype::Touch;
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-create-an-input-source
|
||||
InputSource create_input_source(InputState const& input_state, InputSourceType type, Optional<PointerInputSource::Subtype> subtype)
|
||||
{
|
||||
// Run the substeps matching the first matching value of type:
|
||||
switch (type) {
|
||||
// "none"
|
||||
case InputSourceType::None:
|
||||
// Let source be the result of create a null input source.
|
||||
return NullInputSource {};
|
||||
|
||||
// "key"
|
||||
case InputSourceType::Key:
|
||||
// Let source be the result of create a key input source.
|
||||
return KeyInputSource {};
|
||||
|
||||
// "pointer"
|
||||
case InputSourceType::Pointer:
|
||||
// Let source be the result of create a pointer input source with input state and subtype.
|
||||
return PointerInputSource { input_state, *subtype };
|
||||
|
||||
// "wheel"
|
||||
case InputSourceType::Wheel:
|
||||
// Let source be the result of create a wheel input source.
|
||||
return WheelInputSource {};
|
||||
}
|
||||
|
||||
// Otherwise:
|
||||
// Return error with error code invalid argument.
|
||||
|
||||
// NOTE: We know this cannot be reached because the only caller will have already thrown an invalid argument error
|
||||
// if the `type` parameter was not valid.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-get-an-input-source
|
||||
Optional<InputSource&> get_input_source(InputState& input_state, StringView id)
|
||||
{
|
||||
// 1. Let input state map be input state's input state map.
|
||||
// 2. If input state map[input id] exists, return input state map[input id].
|
||||
// 3. Return undefined.
|
||||
return input_state.input_state_map.get(id);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-get-or-create-an-input-source
|
||||
ErrorOr<InputSource*, WebDriver::Error> get_or_create_input_source(InputState& input_state, InputSourceType type, StringView id, Optional<PointerInputSource::Subtype> subtype)
|
||||
{
|
||||
// 1. Let source be get an input source with input state and input id.
|
||||
auto source = get_input_source(input_state, id);
|
||||
|
||||
// 2. If source is not undefined and source's type is not equal to type, or source is a pointer input source,
|
||||
// return error with error code invalid argument.
|
||||
if (source.has_value() && input_source_type(*source) != type) {
|
||||
// FIXME: Spec issue: It does not make sense to check if "source is a pointer input source". This would errantly
|
||||
// prevent the ability to perform two pointer actions in a row.
|
||||
// https://github.com/w3c/webdriver/issues/1810
|
||||
return WebDriver::Error::from_code(WebDriver::ErrorCode::InvalidArgument, "Property 'type' does not match existing input source type");
|
||||
}
|
||||
|
||||
// 3. If source is undefined, set source to the result of trying to create an input source with input state and type.
|
||||
if (!source.has_value()) {
|
||||
// FIXME: Spec issue: The spec doesn't say to add the source to the input state map, but it is explicitly
|
||||
// expected when we reach the `dispatch tick actions` AO.
|
||||
// https://github.com/w3c/webdriver/issues/1810
|
||||
input_state.input_state_map.set(MUST(String::from_utf8(id)), create_input_source(input_state, type, subtype));
|
||||
source = get_input_source(input_state, id);
|
||||
}
|
||||
|
||||
// 4. Return success with data source.
|
||||
return &source.value();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-get-the-global-key-state
|
||||
GlobalKeyState get_global_key_state(InputState const& input_state)
|
||||
{
|
||||
// 1. Let input state map be input state's input state map.
|
||||
auto const& input_state_map = input_state.input_state_map;
|
||||
|
||||
// 2. Let sources be the result of getting the values with input state map.
|
||||
|
||||
// 3. Let key state be a new global key state with pressed set to an empty set, altKey, ctrlKey, metaKey, and
|
||||
// shiftKey set to false.
|
||||
GlobalKeyState key_state {};
|
||||
|
||||
// 4. For each source in sources:
|
||||
for (auto const& source : input_state_map) {
|
||||
// 1. If source is not a key input source, continue to the first step of this loop.
|
||||
auto const* key_input_source = source.value.get_pointer<KeyInputSource>();
|
||||
if (!key_input_source)
|
||||
continue;
|
||||
|
||||
// 2. Set key state's pressed item to the union of its current value and source's pressed item.
|
||||
for (auto const& pressed : key_input_source->pressed)
|
||||
key_state.pressed.set(pressed);
|
||||
|
||||
// 3. If source's alt item is true, set key state's altKey item to true.
|
||||
key_state.alt_key |= key_input_source->alt;
|
||||
|
||||
// 4. If source's ctrl item is true, set key state's ctrlKey item to true.
|
||||
key_state.ctrl_key |= key_input_source->ctrl;
|
||||
|
||||
// 5. If source's meta item is true, set key state's metaKey item to true.
|
||||
key_state.meta_key |= key_input_source->meta;
|
||||
|
||||
// 6. If source's shift item is true, set key state's shiftKey item to true.
|
||||
key_state.shift_key |= key_input_source->shift;
|
||||
}
|
||||
|
||||
// 5. Return key state.
|
||||
return key_state;
|
||||
}
|
||||
|
||||
}
|
85
Userland/Libraries/LibWeb/WebDriver/InputSource.h
Normal file
85
Userland/Libraries/LibWeb/WebDriver/InputSource.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/PixelUnits.h>
|
||||
#include <LibWeb/UIEvents/KeyCode.h>
|
||||
#include <LibWeb/UIEvents/MouseButton.h>
|
||||
#include <LibWeb/WebDriver/Error.h>
|
||||
|
||||
namespace Web::WebDriver {
|
||||
|
||||
enum class InputSourceType {
|
||||
None,
|
||||
Key,
|
||||
Pointer,
|
||||
Wheel,
|
||||
};
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-null-input-source
|
||||
struct NullInputSource {
|
||||
};
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-key-input-source
|
||||
struct KeyInputSource {
|
||||
HashTable<String> pressed;
|
||||
bool alt { false };
|
||||
bool ctrl { false };
|
||||
bool meta { false };
|
||||
bool shift { false };
|
||||
};
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-pointer-input-source
|
||||
struct PointerInputSource {
|
||||
enum class Subtype {
|
||||
Mouse,
|
||||
Pen,
|
||||
Touch,
|
||||
};
|
||||
|
||||
PointerInputSource(InputState const&, Subtype);
|
||||
|
||||
Subtype subtype { Subtype::Mouse };
|
||||
u32 pointer_id { 0 };
|
||||
UIEvents::MouseButton pressed { UIEvents::MouseButton::None };
|
||||
CSSPixelPoint position;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-wheel-input-source
|
||||
struct WheelInputSource {
|
||||
};
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-input-source
|
||||
using InputSource = Variant<NullInputSource, KeyInputSource, PointerInputSource, WheelInputSource>;
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-global-key-state
|
||||
struct GlobalKeyState {
|
||||
UIEvents::KeyModifier modifiers() const;
|
||||
|
||||
HashTable<String> pressed;
|
||||
bool alt_key { false };
|
||||
bool ctrl_key { false };
|
||||
bool meta_key { false };
|
||||
bool shift_key { false };
|
||||
};
|
||||
|
||||
Optional<InputSourceType> input_source_type_from_string(StringView);
|
||||
Optional<PointerInputSource::Subtype> pointer_input_source_subtype_from_string(StringView);
|
||||
|
||||
InputSource create_input_source(InputState const&, InputSourceType, Optional<PointerInputSource::Subtype>);
|
||||
Optional<InputSource&> get_input_source(InputState&, StringView id);
|
||||
ErrorOr<InputSource*, WebDriver::Error> get_or_create_input_source(InputState&, InputSourceType, StringView id, Optional<PointerInputSource::Subtype>);
|
||||
|
||||
GlobalKeyState get_global_key_state(InputState const&);
|
||||
|
||||
}
|
44
Userland/Libraries/LibWeb/WebDriver/InputState.cpp
Normal file
44
Userland/Libraries/LibWeb/WebDriver/InputState.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/WebDriver/Actions.h>
|
||||
#include <LibWeb/WebDriver/InputState.h>
|
||||
|
||||
namespace Web::WebDriver {
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-browsing-context-input-state-map
|
||||
static HashMap<JS::RawGCPtr<HTML::BrowsingContext>, InputState> s_browsing_context_input_state_map;
|
||||
|
||||
InputState::InputState() = default;
|
||||
InputState::~InputState() = default;
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-get-the-input-state
|
||||
InputState& get_input_state(HTML::BrowsingContext& browsing_context)
|
||||
{
|
||||
// 1. Assert: browsing context is a top-level browsing context.
|
||||
VERIFY(browsing_context.is_top_level());
|
||||
|
||||
// 2. Let input state map be session's browsing context input state map.
|
||||
// 3. If input state map does not contain browsing context, set input state map[browsing context] to create an input state.
|
||||
auto& input_state = s_browsing_context_input_state_map.ensure(browsing_context);
|
||||
|
||||
// 4. Return input state map[browsing context].
|
||||
return input_state;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-reset-the-input-state
|
||||
void reset_input_state(HTML::BrowsingContext& browsing_context)
|
||||
{
|
||||
// 1. Assert: browsing context is a top-level browsing context.
|
||||
VERIFY(browsing_context.is_top_level());
|
||||
|
||||
// 2. Let input state map be session's browsing context input state map.
|
||||
// 3. If input state map[browsing context] exists, then remove input state map[browsing context].
|
||||
s_browsing_context_input_state_map.remove(browsing_context);
|
||||
}
|
||||
|
||||
}
|
35
Userland/Libraries/LibWeb/WebDriver/InputState.h
Normal file
35
Userland/Libraries/LibWeb/WebDriver/InputState.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/WebDriver/InputSource.h>
|
||||
|
||||
namespace Web::WebDriver {
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-input-state
|
||||
struct InputState {
|
||||
InputState();
|
||||
~InputState();
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-input-state-map
|
||||
HashMap<String, InputSource> input_state_map;
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-input-cancel-list
|
||||
Vector<ActionObject> input_cancel_list;
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-actions-queue
|
||||
Vector<String> actions_queue;
|
||||
};
|
||||
|
||||
InputState& get_input_state(HTML::BrowsingContext&);
|
||||
void reset_input_state(HTML::BrowsingContext&);
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue