UI+LibWeb+WebContent: Implement KeyEvent repeat property

When a platform key press or release event is repeated, we now pass
along a `repeat` flag to indicate that auto-repeating is happening. This
flag eventually ends up in `KeyboardEvent.repeat`.
This commit is contained in:
Jelle Raaijmakers 2024-10-22 15:32:42 +02:00 committed by Tim Flynn
parent cf315d54ec
commit 9309cc9df3
Notes: github-actions[bot] 2024-10-22 15:21:37 +00:00
13 changed files with 39 additions and 35 deletions

View file

@ -297,6 +297,7 @@ Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type type, NSEvent* event)
{ {
auto modifiers = ns_modifiers_to_key_modifiers(event.modifierFlags); auto modifiers = ns_modifiers_to_key_modifiers(event.modifierFlags);
auto key_code = ns_key_code_to_key_code(event.keyCode, modifiers); auto key_code = ns_key_code_to_key_code(event.keyCode, modifiers);
auto repeat = event.isARepeat;
// FIXME: WebContent should really support multi-code point key events. // FIXME: WebContent should really support multi-code point key events.
u32 code_point = 0; u32 code_point = 0;
@ -312,7 +313,7 @@ Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type type, NSEvent* event)
if (code_point >= 0xE000 && code_point <= 0xF8FF) if (code_point >= 0xE000 && code_point <= 0xF8FF)
code_point = 0; code_point = 0;
return { type, key_code, modifiers, code_point, make<KeyData>(event) }; return { type, key_code, modifiers, code_point, repeat, make<KeyData>(event) };
} }
NSEvent* key_event_to_ns_event(Web::KeyEvent const& event) NSEvent* key_event_to_ns_event(Web::KeyEvent const& event)

View file

@ -899,15 +899,15 @@ void WebContentView::enqueue_native_event(Web::KeyEvent::Type type, QKeyEvent co
auto to_web_event = [&]() -> Web::KeyEvent { auto to_web_event = [&]() -> Web::KeyEvent {
if (event.key() == Qt::Key_Backtab) { if (event.key() == Qt::Key_Backtab) {
// Qt transforms Shift+Tab into a "Backtab", so we undo that transformation here. // Qt transforms Shift+Tab into a "Backtab", so we undo that transformation here.
return { type, Web::UIEvents::KeyCode::Key_Tab, Web::UIEvents::Mod_Shift, '\t', make<KeyData>(event) }; return { type, Web::UIEvents::KeyCode::Key_Tab, Web::UIEvents::Mod_Shift, '\t', event.isAutoRepeat(), make<KeyData>(event) };
} }
if (event.key() == Qt::Key_Enter || event.key() == Qt::Key_Return) { if (event.key() == Qt::Key_Enter || event.key() == Qt::Key_Return) {
// This ensures consistent behavior between systems that treat Enter as '\n' and '\r\n' // This ensures consistent behavior between systems that treat Enter as '\n' and '\r\n'
return { type, Web::UIEvents::KeyCode::Key_Return, Web::UIEvents::Mod_Shift, '\n', make<KeyData>(event) }; return { type, Web::UIEvents::KeyCode::Key_Return, Web::UIEvents::Mod_Shift, '\n', event.isAutoRepeat(), make<KeyData>(event) };
} }
return { type, keycode, modifiers, code_point, make<KeyData>(event) }; return { type, keycode, modifiers, code_point, event.isAutoRepeat(), make<KeyData>(event) };
}; };
enqueue_input_event(to_web_event()); enqueue_input_event(to_web_event());

View file

@ -79,7 +79,7 @@ void Internals::send_text(HTML::HTMLElement& target, String const& text, WebIDL:
target.focus(); target.focus();
for (auto code_point : text.code_points()) for (auto code_point : text.code_points())
page.handle_keydown(UIEvents::code_point_to_key_code(code_point), modifiers, code_point); page.handle_keydown(UIEvents::code_point_to_key_code(code_point), modifiers, code_point, false);
} }
void Internals::send_key(HTML::HTMLElement& target, String const& key_name, WebIDL::UnsignedShort modifiers) void Internals::send_key(HTML::HTMLElement& target, String const& key_name, WebIDL::UnsignedShort modifiers)
@ -87,12 +87,12 @@ void Internals::send_key(HTML::HTMLElement& target, String const& key_name, WebI
auto key_code = UIEvents::key_code_from_string(key_name); auto key_code = UIEvents::key_code_from_string(key_name);
target.focus(); target.focus();
internals_page().handle_keydown(key_code, modifiers, 0); internals_page().handle_keydown(key_code, modifiers, 0, false);
} }
void Internals::commit_text() void Internals::commit_text()
{ {
internals_page().handle_keydown(UIEvents::Key_Return, 0, 0); internals_page().handle_keydown(UIEvents::Key_Return, 0, 0, false);
} }
void Internals::click(double x, double y) void Internals::click(double x, double y)

View file

@ -840,7 +840,7 @@ constexpr bool should_ignore_keydown_event(u32 code_point, u32 modifiers)
return code_point == 0 || code_point == 27; return code_point == 0 || code_point == 27;
} }
EventResult EventHandler::fire_keyboard_event(FlyString const& event_name, HTML::Navigable& navigable, UIEvents::KeyCode key, u32 modifiers, u32 code_point) EventResult EventHandler::fire_keyboard_event(FlyString const& event_name, HTML::Navigable& navigable, UIEvents::KeyCode key, u32 modifiers, u32 code_point, bool repeat)
{ {
JS::GCPtr<DOM::Document> document = navigable.active_document(); JS::GCPtr<DOM::Document> document = navigable.active_document();
if (!document) if (!document)
@ -852,15 +852,15 @@ EventResult EventHandler::fire_keyboard_event(FlyString const& event_name, HTML:
if (is<HTML::NavigableContainer>(*focused_element)) { if (is<HTML::NavigableContainer>(*focused_element)) {
auto& navigable_container = verify_cast<HTML::NavigableContainer>(*focused_element); auto& navigable_container = verify_cast<HTML::NavigableContainer>(*focused_element);
if (navigable_container.content_navigable()) if (navigable_container.content_navigable())
return fire_keyboard_event(event_name, *navigable_container.content_navigable(), key, modifiers, code_point); return fire_keyboard_event(event_name, *navigable_container.content_navigable(), key, modifiers, code_point, repeat);
} }
auto event = UIEvents::KeyboardEvent::create_from_platform_event(document->realm(), event_name, key, modifiers, code_point); auto event = UIEvents::KeyboardEvent::create_from_platform_event(document->realm(), event_name, key, modifiers, code_point, repeat);
return focused_element->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled; return focused_element->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled;
} }
// FIXME: De-duplicate this. This is just to prevent wasting a KeyboardEvent allocation when recursing into an (i)frame. // FIXME: De-duplicate this. This is just to prevent wasting a KeyboardEvent allocation when recursing into an (i)frame.
auto event = UIEvents::KeyboardEvent::create_from_platform_event(document->realm(), event_name, key, modifiers, code_point); auto event = UIEvents::KeyboardEvent::create_from_platform_event(document->realm(), event_name, key, modifiers, code_point, repeat);
JS::GCPtr target = document->body() ?: &document->root(); JS::GCPtr target = document->body() ?: &document->root();
return target->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled; return target->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled;
@ -911,14 +911,14 @@ EventResult EventHandler::input_event(FlyString const& event_name, FlyString con
return document->root().dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled; return document->root().dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled;
} }
EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u32 code_point) EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u32 code_point, bool repeat)
{ {
if (!m_navigable->active_document()) if (!m_navigable->active_document())
return EventResult::Dropped; return EventResult::Dropped;
if (!m_navigable->active_document()->is_fully_active()) if (!m_navigable->active_document()->is_fully_active())
return EventResult::Dropped; return EventResult::Dropped;
auto dispatch_result = fire_keyboard_event(UIEvents::EventNames::keydown, m_navigable, key, modifiers, code_point); auto dispatch_result = fire_keyboard_event(UIEvents::EventNames::keydown, m_navigable, key, modifiers, code_point, repeat);
if (dispatch_result != EventResult::Accepted) if (dispatch_result != EventResult::Accepted)
return dispatch_result; return dispatch_result;
@ -926,7 +926,7 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u
// If supported by a user agent, this event MUST be dispatched when a key is pressed down, if and only if that key // If supported by a user agent, this event MUST be dispatched when a key is pressed down, if and only if that key
// normally produces a character value. // normally produces a character value.
if (produces_character_value(code_point)) { if (produces_character_value(code_point)) {
dispatch_result = fire_keyboard_event(UIEvents::EventNames::keypress, m_navigable, key, modifiers, code_point); dispatch_result = fire_keyboard_event(UIEvents::EventNames::keypress, m_navigable, key, modifiers, code_point, repeat);
if (dispatch_result != EventResult::Accepted) if (dispatch_result != EventResult::Accepted)
return dispatch_result; return dispatch_result;
} }
@ -1171,9 +1171,9 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u
return EventResult::Accepted; return EventResult::Accepted;
} }
EventResult EventHandler::handle_keyup(UIEvents::KeyCode key, u32 modifiers, u32 code_point) EventResult EventHandler::handle_keyup(UIEvents::KeyCode key, u32 modifiers, u32 code_point, [[maybe_unused]] bool repeat)
{ {
return fire_keyboard_event(UIEvents::EventNames::keyup, m_navigable, key, modifiers, code_point); return fire_keyboard_event(UIEvents::EventNames::keyup, m_navigable, key, modifiers, code_point, false);
} }
void EventHandler::handle_paste(String const& text) void EventHandler::handle_paste(String const& text)

View file

@ -34,8 +34,8 @@ public:
EventResult handle_drag_and_drop_event(DragEvent::Type, CSSPixelPoint, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files); EventResult handle_drag_and_drop_event(DragEvent::Type, CSSPixelPoint, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files);
EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point); EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point); EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
void set_mouse_event_tracking_paintable(Painting::Paintable*); void set_mouse_event_tracking_paintable(Painting::Paintable*);
@ -49,7 +49,7 @@ private:
bool focus_next_element(); bool focus_next_element();
bool focus_previous_element(); bool focus_previous_element();
EventResult fire_keyboard_event(FlyString const& event_name, HTML::Navigable&, UIEvents::KeyCode, unsigned modifiers, u32 code_point); EventResult fire_keyboard_event(FlyString const& event_name, HTML::Navigable&, UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
[[nodiscard]] EventResult input_event(FlyString const& event_name, FlyString const& input_type, HTML::Navigable&, u32 code_point); [[nodiscard]] EventResult input_event(FlyString const& event_name, FlyString const& input_type, HTML::Navigable&, u32 code_point);
CSSPixelPoint compute_mouse_event_client_offset(CSSPixelPoint event_page_position) const; CSSPixelPoint compute_mouse_event_client_offset(CSSPixelPoint event_page_position) const;
CSSPixelPoint compute_mouse_event_page_offset(CSSPixelPoint event_client_offset) const; CSSPixelPoint compute_mouse_event_page_offset(CSSPixelPoint event_client_offset) const;

View file

@ -12,7 +12,7 @@ namespace Web {
KeyEvent KeyEvent::clone_without_chrome_data() const KeyEvent KeyEvent::clone_without_chrome_data() const
{ {
return { type, key, modifiers, code_point, nullptr }; return { type, key, modifiers, code_point, repeat, nullptr };
} }
MouseEvent MouseEvent::clone_without_chrome_data() const MouseEvent MouseEvent::clone_without_chrome_data() const
@ -34,6 +34,7 @@ ErrorOr<void> IPC::encode(Encoder& encoder, Web::KeyEvent const& event)
TRY(encoder.encode(event.key)); TRY(encoder.encode(event.key));
TRY(encoder.encode(event.modifiers)); TRY(encoder.encode(event.modifiers));
TRY(encoder.encode(event.code_point)); TRY(encoder.encode(event.code_point));
TRY(encoder.encode(event.repeat));
return {}; return {};
} }
@ -44,8 +45,9 @@ ErrorOr<Web::KeyEvent> IPC::decode(Decoder& decoder)
auto key = TRY(decoder.decode<Web::UIEvents::KeyCode>()); auto key = TRY(decoder.decode<Web::UIEvents::KeyCode>());
auto modifiers = TRY(decoder.decode<Web::UIEvents::KeyModifier>()); auto modifiers = TRY(decoder.decode<Web::UIEvents::KeyModifier>());
auto code_point = TRY(decoder.decode<u32>()); auto code_point = TRY(decoder.decode<u32>());
auto repeat = TRY(decoder.decode<bool>());
return Web::KeyEvent { type, key, modifiers, code_point, nullptr }; return Web::KeyEvent { type, key, modifiers, code_point, repeat, nullptr };
} }
template<> template<>

View file

@ -34,6 +34,7 @@ struct KeyEvent {
UIEvents::KeyCode key { UIEvents::KeyCode::Key_Invalid }; UIEvents::KeyCode key { UIEvents::KeyCode::Key_Invalid };
UIEvents::KeyModifier modifiers { UIEvents::KeyModifier::Mod_None }; UIEvents::KeyModifier modifiers { UIEvents::KeyModifier::Mod_None };
u32 code_point { 0 }; u32 code_point { 0 };
bool repeat { false };
OwnPtr<ChromeInputData> chrome_data; OwnPtr<ChromeInputData> chrome_data;
}; };

View file

@ -216,14 +216,14 @@ EventResult Page::handle_drag_and_drop_event(DragEvent::Type type, DevicePixelPo
return top_level_traversable()->event_handler().handle_drag_and_drop_event(type, device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers, move(files)); return top_level_traversable()->event_handler().handle_drag_and_drop_event(type, device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers, move(files));
} }
EventResult Page::handle_keydown(UIEvents::KeyCode key, unsigned modifiers, u32 code_point) EventResult Page::handle_keydown(UIEvents::KeyCode key, unsigned modifiers, u32 code_point, bool repeat)
{ {
return focused_navigable().event_handler().handle_keydown(key, modifiers, code_point); return focused_navigable().event_handler().handle_keydown(key, modifiers, code_point, repeat);
} }
EventResult Page::handle_keyup(UIEvents::KeyCode key, unsigned modifiers, u32 code_point) EventResult Page::handle_keyup(UIEvents::KeyCode key, unsigned modifiers, u32 code_point, bool repeat)
{ {
return focused_navigable().event_handler().handle_keyup(key, modifiers, code_point); return focused_navigable().event_handler().handle_keyup(key, modifiers, code_point, repeat);
} }
void Page::set_top_level_traversable(JS::NonnullGCPtr<HTML::TraversableNavigable> navigable) void Page::set_top_level_traversable(JS::NonnullGCPtr<HTML::TraversableNavigable> navigable)

View file

@ -100,8 +100,8 @@ public:
EventResult handle_drag_and_drop_event(DragEvent::Type, DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files); EventResult handle_drag_and_drop_event(DragEvent::Type, DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files);
EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point); EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point); EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
Gfx::Palette palette() const; Gfx::Palette palette() const;
CSSPixelRect web_exposed_screen_area() const; CSSPixelRect web_exposed_screen_area() const;

View file

@ -668,7 +668,7 @@ static DOMKeyLocation get_event_location(KeyCode platform_key, unsigned modifier
return DOMKeyLocation::Standard; return DOMKeyLocation::Standard;
} }
JS::NonnullGCPtr<KeyboardEvent> KeyboardEvent::create_from_platform_event(JS::Realm& realm, FlyString const& event_name, KeyCode platform_key, unsigned modifiers, u32 code_point) JS::NonnullGCPtr<KeyboardEvent> KeyboardEvent::create_from_platform_event(JS::Realm& realm, FlyString const& event_name, KeyCode platform_key, unsigned modifiers, u32 code_point, bool repeat)
{ {
auto event_key = MUST(get_event_key(platform_key, code_point)); auto event_key = MUST(get_event_key(platform_key, code_point));
auto event_code = MUST(get_event_code(platform_key, modifiers)); auto event_code = MUST(get_event_code(platform_key, modifiers));
@ -683,7 +683,7 @@ JS::NonnullGCPtr<KeyboardEvent> KeyboardEvent::create_from_platform_event(JS::Re
event_init.shift_key = modifiers & Mod_Shift; event_init.shift_key = modifiers & Mod_Shift;
event_init.alt_key = modifiers & Mod_Alt; event_init.alt_key = modifiers & Mod_Alt;
event_init.meta_key = modifiers & Mod_Super; event_init.meta_key = modifiers & Mod_Super;
event_init.repeat = false; event_init.repeat = repeat;
event_init.is_composing = false; event_init.is_composing = false;
event_init.key_code = key_code; event_init.key_code = key_code;
event_init.char_code = char_code; event_init.char_code = char_code;

View file

@ -38,7 +38,7 @@ class KeyboardEvent final : public UIEvent {
public: public:
[[nodiscard]] static JS::NonnullGCPtr<KeyboardEvent> create(JS::Realm&, FlyString const& event_name, KeyboardEventInit const& = {}); [[nodiscard]] static JS::NonnullGCPtr<KeyboardEvent> create(JS::Realm&, FlyString const& event_name, KeyboardEventInit const& = {});
[[nodiscard]] static JS::NonnullGCPtr<KeyboardEvent> create_from_platform_event(JS::Realm&, FlyString const& event_name, KeyCode, unsigned modifiers, u32 code_point); [[nodiscard]] static JS::NonnullGCPtr<KeyboardEvent> create_from_platform_event(JS::Realm&, FlyString const& event_name, KeyCode, unsigned modifiers, u32 code_point, bool repeat);
static WebIDL::ExceptionOr<JS::NonnullGCPtr<KeyboardEvent>> construct_impl(JS::Realm&, FlyString const& event_name, KeyboardEventInit const&); static WebIDL::ExceptionOr<JS::NonnullGCPtr<KeyboardEvent>> construct_impl(JS::Realm&, FlyString const& event_name, KeyboardEventInit const&);
virtual ~KeyboardEvent() override; virtual ~KeyboardEvent() override;

View file

@ -902,7 +902,7 @@ static ErrorOr<void, WebDriver::Error> dispatch_key_down_action(ActionObject::Ke
auto key = normalized_key_value(raw_key); auto key = normalized_key_value(raw_key);
// 3. If the source's pressed property contains key, let repeat be true, otherwise let repeat be false. // 3. If the source's pressed property contains key, let repeat be true, otherwise let repeat be false.
// FIXME: Add `repeat` support to Page::handle_keydown. bool repeat = source.pressed.contains(key);
// 4. Let code be the code for raw key. // 4. Let code be the code for raw key.
auto code = key_code_data(raw_key); auto code = key_code_data(raw_key);
@ -945,7 +945,7 @@ static ErrorOr<void, WebDriver::Error> dispatch_key_down_action(ActionObject::Ke
// keyboard in accordance with the requirements of [UI-EVENTS], and producing the following events, as appropriate, // keyboard in accordance with the requirements of [UI-EVENTS], and producing the following events, as appropriate,
// with the specified properties. This will always produce events including at least a keyDown event. // with the specified properties. This will always produce events including at least a keyDown event.
auto event = key_code_to_page_event(raw_key, modifiers, code); auto event = key_code_to_page_event(raw_key, modifiers, code);
browsing_context.page().handle_keydown(code.code, event.modifiers, event.code_point); browsing_context.page().handle_keydown(code.code, event.modifiers, event.code_point, repeat);
// 13. Return success with data null. // 13. Return success with data null.
return {}; return {};
@ -1005,7 +1005,7 @@ static ErrorOr<void, WebDriver::Error> dispatch_key_up_action(ActionObject::KeyF
// keyboard in accordance with the requirements of [UI-EVENTS], and producing at least the following events with // keyboard in accordance with the requirements of [UI-EVENTS], and producing at least the following events with
// the specified properties: // the specified properties:
auto event = key_code_to_page_event(raw_key, modifiers, code); auto event = key_code_to_page_event(raw_key, modifiers, code);
browsing_context.page().handle_keyup(code.code, event.modifiers, event.code_point); browsing_context.page().handle_keyup(code.code, event.modifiers, event.code_point, false);
// 13. Return success with data null. // 13. Return success with data null.
return {}; return {};

View file

@ -189,9 +189,9 @@ void ConnectionFromClient::process_next_input_event()
[&](Web::KeyEvent const& event) { [&](Web::KeyEvent const& event) {
switch (event.type) { switch (event.type) {
case Web::KeyEvent::Type::KeyDown: case Web::KeyEvent::Type::KeyDown:
return page->page().handle_keydown(event.key, event.modifiers, event.code_point); return page->page().handle_keydown(event.key, event.modifiers, event.code_point, event.repeat);
case Web::KeyEvent::Type::KeyUp: case Web::KeyEvent::Type::KeyUp:
return page->page().handle_keyup(event.key, event.modifiers, event.code_point); return page->page().handle_keyup(event.key, event.modifiers, event.code_point, event.repeat);
} }
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
}, },