diff --git a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index bf31b58670..ad2885cca8 100644 --- a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -245,7 +245,7 @@ void EventLoop::process_input_events() const [&](KeyEvent const& key_event) { switch (key_event.type) { case KeyEvent::Type::KeyDown: - return page.handle_keydown(key_event.key, key_event.modifiers, key_event.code_point, key_event.repeat); + return page.handle_keydown(key_event.key, key_event.modifiers, key_event.code_point, key_event.repeat, key_event.should_insert_text); case KeyEvent::Type::KeyUp: return page.handle_keyup(key_event.key, key_event.modifiers, key_event.code_point, key_event.repeat); } diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp index 96c188bc0b..5216d72222 100644 --- a/Libraries/LibWeb/Internals/Internals.cpp +++ b/Libraries/LibWeb/Internals/Internals.cpp @@ -294,9 +294,9 @@ void Internals::send_text(HTML::HTMLElement& target, String const& text, WebIDL: for (auto code_point : text.code_points()) { if (auto data = webdriver_key_to_key_code(code_point); data.has_value()) - page.handle_keydown(data->key_code, modifiers | data->additional_modifiers, data->code_point_to_send, false); + page.handle_keydown(data->key_code, modifiers | data->additional_modifiers, data->code_point_to_send, false, data->code_point_to_send != 0); else - page.handle_keydown(UIEvents::code_point_to_key_code(code_point), modifiers, code_point, false); + page.handle_keydown(UIEvents::code_point_to_key_code(code_point), modifiers, code_point, false, true); } } @@ -305,7 +305,7 @@ void Internals::send_key(HTML::HTMLElement& target, String const& key_name, WebI auto key_code = UIEvents::key_code_from_string(key_name); target.focus(); - page().handle_keydown(key_code, modifiers, 0, false); + page().handle_keydown(key_code, modifiers, 0, false, false); } void Internals::paste(HTML::HTMLElement& target, Utf16String const& text) @@ -318,7 +318,7 @@ void Internals::paste(HTML::HTMLElement& target, Utf16String const& text) void Internals::commit_text() { - page().handle_keydown(UIEvents::Key_Return, 0, 0x0d, false); + page().handle_keydown(UIEvents::Key_Return, 0, 0x0d, false, true); } UIEvents::MouseButton Internals::button_from_unsigned_short(WebIDL::UnsignedShort button) diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index ab29cbaffa..1a15f294bd 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -869,16 +869,24 @@ static constexpr bool is_enter_key_or_interoperable_enter_key_combo(UIEvents::Ke return false; } -static constexpr bool should_ignore_keydown_event(u32 code_point, u32 modifiers) +static bool should_ignore_keydown_event(u32 code_point, u32 modifiers, bool should_insert_text) { - if (modifiers & (UIEvents::KeyModifier::Mod_Ctrl | UIEvents::KeyModifier::Mod_Alt | UIEvents::KeyModifier::Mod_Super)) + if (code_point == 0 || code_point == 27) return true; - // FIXME: There are probably also keys with non-zero code points that should be filtered out. - return code_point == 0 || code_point == 27; + if (modifiers & UIEvents::KeyModifier::Mod_Super) + return true; + + if ((modifiers & UIEvents::KeyModifier::Mod_Ctrl) && !(modifiers & UIEvents::KeyModifier::Mod_Alt)) + return true; + + if ((modifiers & UIEvents::KeyModifier::Mod_Alt) && !should_insert_text) + return true; + + return false; } -EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u32 code_point, bool repeat) +EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u32 code_point, bool repeat, bool should_insert_text) { if (!m_navigable->active_document()) return EventResult::Dropped; @@ -1047,7 +1055,7 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u } // FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here. - if (!should_ignore_keydown_event(code_point, modifiers)) { + if (!should_ignore_keydown_event(code_point, modifiers, should_insert_text)) { FIRE(input_event(UIEvents::EventNames::beforeinput, UIEvents::InputTypes::insertText, m_navigable, code_point)); target->handle_insert(UIEvents::InputTypes::insertText, Utf16String::from_code_point(code_point)); return EventResult::Handled; diff --git a/Libraries/LibWeb/Page/EventHandler.h b/Libraries/LibWeb/Page/EventHandler.h index 29ad861369..639ec552e0 100644 --- a/Libraries/LibWeb/Page/EventHandler.h +++ b/Libraries/LibWeb/Page/EventHandler.h @@ -51,7 +51,7 @@ public: void update_hover_after_scroll(); GC::Ptr target_node_for_mouse_position(CSSPixelPoint); - EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat); + EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat, bool should_insert_text); EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat); EventResult handle_drag_and_drop_event(DragEvent::Type, CSSPixelPoint, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector files); diff --git a/Libraries/LibWeb/Page/InputEvent.cpp b/Libraries/LibWeb/Page/InputEvent.cpp index d315ca68a7..4a584d0cc5 100644 --- a/Libraries/LibWeb/Page/InputEvent.cpp +++ b/Libraries/LibWeb/Page/InputEvent.cpp @@ -13,7 +13,7 @@ namespace Web { KeyEvent KeyEvent::clone_without_browser_data() const { - return { type, key, modifiers, code_point, repeat, nullptr }; + return { type, key, modifiers, code_point, repeat, should_insert_text, nullptr }; } MouseEvent MouseEvent::clone_without_browser_data() const @@ -36,6 +36,7 @@ ErrorOr IPC::encode(Encoder& encoder, Web::KeyEvent const& event) TRY(encoder.encode(event.modifiers)); TRY(encoder.encode(event.code_point)); TRY(encoder.encode(event.repeat)); + TRY(encoder.encode(event.should_insert_text)); return {}; } @@ -47,8 +48,9 @@ ErrorOr IPC::decode(Decoder& decoder) auto modifiers = TRY(decoder.decode()); auto code_point = TRY(decoder.decode()); auto repeat = TRY(decoder.decode()); + auto should_insert_text = TRY(decoder.decode()); - return Web::KeyEvent { type, key, modifiers, code_point, repeat, nullptr }; + return Web::KeyEvent { type, key, modifiers, code_point, repeat, should_insert_text, nullptr }; } template<> diff --git a/Libraries/LibWeb/Page/InputEvent.h b/Libraries/LibWeb/Page/InputEvent.h index 8a2f6c7fe8..28323d686e 100644 --- a/Libraries/LibWeb/Page/InputEvent.h +++ b/Libraries/LibWeb/Page/InputEvent.h @@ -36,6 +36,7 @@ struct WEB_API KeyEvent { UIEvents::KeyModifier modifiers { UIEvents::KeyModifier::Mod_None }; u32 code_point { 0 }; bool repeat { false }; + bool should_insert_text { false }; OwnPtr browser_data; }; diff --git a/Libraries/LibWeb/Page/Page.cpp b/Libraries/LibWeb/Page/Page.cpp index 6f538159f6..1f69b14731 100644 --- a/Libraries/LibWeb/Page/Page.cpp +++ b/Libraries/LibWeb/Page/Page.cpp @@ -351,9 +351,9 @@ EventResult Page::handle_pinch_event(DevicePixelPoint position, unsigned modifie return top_level_traversable()->event_handler().handle_pinch_event(device_to_css_point(position), modifiers, scale); } -EventResult Page::handle_keydown(UIEvents::KeyCode key, unsigned modifiers, u32 code_point, bool repeat) +EventResult Page::handle_keydown(UIEvents::KeyCode key, unsigned modifiers, u32 code_point, bool repeat, bool should_insert_text) { - return focused_navigable().event_handler().handle_keydown(key, modifiers, code_point, repeat); + return focused_navigable().event_handler().handle_keydown(key, modifiers, code_point, repeat, should_insert_text); } EventResult Page::handle_keyup(UIEvents::KeyCode key, unsigned modifiers, u32 code_point, bool repeat) diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index c7725dfb62..a8556432d1 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -125,7 +125,7 @@ public: EventResult handle_drag_and_drop_event(DragEvent::Type, DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector files); EventResult handle_pinch_event(DevicePixelPoint point, unsigned modifiers, double scale); - EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat); + EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat, bool should_insert_text); EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat); void handle_sdl_input_events(); diff --git a/Libraries/LibWeb/WebDriver/Actions.cpp b/Libraries/LibWeb/WebDriver/Actions.cpp index 4de4306cd4..1709c5c861 100644 --- a/Libraries/LibWeb/WebDriver/Actions.cpp +++ b/Libraries/LibWeb/WebDriver/Actions.cpp @@ -922,6 +922,7 @@ static bool is_shifted_character(u32 code_point) struct KeyEvent { u32 code_point { 0 }; UIEvents::KeyModifier modifiers { UIEvents::KeyModifier::Mod_None }; + bool should_insert_text { false }; }; static KeyEvent key_code_to_page_event(u32 code_point, UIEvents::KeyModifier modifiers, KeyCodeData const& code) { @@ -962,7 +963,11 @@ static KeyEvent key_code_to_page_event(u32 code_point, UIEvents::KeyModifier mod if (has_flag(modifiers, UIEvents::KeyModifier::Mod_Shift)) code_point = code.alternate_key.value_or(code_point); - return { code_point, modifiers }; + auto should_insert_text = code_point != 0 + && code_point < 0xE000 + && !(modifiers & (UIEvents::KeyModifier::Mod_Ctrl | UIEvents::KeyModifier::Mod_Alt | UIEvents::KeyModifier::Mod_Super)); + + return { code_point, modifiers, should_insert_text }; } // https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action @@ -1018,7 +1023,7 @@ static ErrorOr dispatch_key_down_action(ActionObject::Ke // 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. auto event = key_code_to_page_event(raw_key, modifiers, code); - browsing_context.page().handle_keydown(code.code, event.modifiers, event.code_point, repeat); + browsing_context.page().handle_keydown(code.code, event.modifiers, event.code_point, repeat, event.should_insert_text); // 13. Return success with data null. return {}; diff --git a/Tests/LibWeb/Text/expected/UIEvents/KeyEvent-alt-text-insertion.txt b/Tests/LibWeb/Text/expected/UIEvents/KeyEvent-alt-text-insertion.txt new file mode 100644 index 0000000000..847008da55 --- /dev/null +++ b/Tests/LibWeb/Text/expected/UIEvents/KeyEvent-alt-text-insertion.txt @@ -0,0 +1,16 @@ +keydown key=å keyCodePoints=e5 altKey=true ctrlKey=false +beforeinput data=å dataCodePoints=e5 inputType=insertText +value after Alt: å +keydown key=@ keyCodePoints=40 altKey=true ctrlKey=true +beforeinput data=@ dataCodePoints=40 inputType=insertText +value after Ctrl+Alt: å@ +keydown key=  keyCodePoints=a0 altKey=true ctrlKey=false +beforeinput data=  dataCodePoints=a0 inputType=insertText +value after Alt+Space: å@  +value code points after Alt+Space: e5 40 a0 +keydown key=f keyCodePoints=66 altKey=true ctrlKey=false +value after Alt+F shortcut: å@  +value code points after Alt+F shortcut: e5 40 a0 +keydown key=x keyCodePoints=78 altKey=false ctrlKey=true +value after Ctrl: å@  +value code points after Ctrl: e5 40 a0 diff --git a/Tests/LibWeb/Text/input/UIEvents/KeyEvent-alt-text-insertion.html b/Tests/LibWeb/Text/input/UIEvents/KeyEvent-alt-text-insertion.html new file mode 100644 index 0000000000..8416e087a2 --- /dev/null +++ b/Tests/LibWeb/Text/input/UIEvents/KeyEvent-alt-text-insertion.html @@ -0,0 +1,36 @@ + + + + + diff --git a/UI/AppKit/Interface/Event.h b/UI/AppKit/Interface/Event.h index 00ad8b2627..e4f791e2fe 100644 --- a/UI/AppKit/Interface/Event.h +++ b/UI/AppKit/Interface/Event.h @@ -20,7 +20,7 @@ Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type, NSEvent*, NSView* Web::DragEvent ns_event_to_drag_event(Web::DragEvent::Type, id, NSView*); Vector drag_event_url_list(Web::DragEvent const&); -Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type, NSEvent*); +Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type, NSEvent*, bool should_insert_text = false); NSEvent* key_event_to_ns_event(Web::KeyEvent const&); NSEvent* create_context_menu_mouse_event(NSView*, Gfx::IntPoint); diff --git a/UI/AppKit/Interface/Event.mm b/UI/AppKit/Interface/Event.mm index 78613b6885..c0a4a5c56a 100644 --- a/UI/AppKit/Interface/Event.mm +++ b/UI/AppKit/Interface/Event.mm @@ -289,7 +289,7 @@ private: CFTypeRef m_event { nullptr }; }; -Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type type, NSEvent* event) +Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type type, NSEvent* event, bool should_insert_text) { auto modifiers = ns_modifiers_to_key_modifiers(event.modifierFlags); auto key_code = ns_key_code_to_key_code(event.keyCode, modifiers); @@ -311,7 +311,7 @@ Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type type, NSEvent* event) if (code_point >= 0xE000 && code_point <= 0xF8FF) code_point = 0; - return { type, key_code, modifiers, code_point, repeat, make(event) }; + return { type, key_code, modifiers, code_point, repeat, should_insert_text, make(event) }; } NSEvent* key_event_to_ns_event(Web::KeyEvent const& event) diff --git a/UI/AppKit/Interface/LadybirdWebView.mm b/UI/AppKit/Interface/LadybirdWebView.mm index 31e383eb86..b70a53da7a 100644 --- a/UI/AppKit/Interface/LadybirdWebView.mm +++ b/UI/AppKit/Interface/LadybirdWebView.mm @@ -952,7 +952,7 @@ static Web::DevicePixelPoint node_picker_position_for(Ladybird::WebViewBridge co }; } -- (void)handleCurrentKeyDownEvent +- (void)handleCurrentKeyDownEvent:(BOOL)shouldInsertText { if (!self.current_key_down_event) return; @@ -962,7 +962,7 @@ static Web::DevicePixelPoint node_picker_position_for(Ladybird::WebViewBridge co return; } - auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyDown, self.current_key_down_event); + auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyDown, self.current_key_down_event, shouldInsertText); m_web_view_bridge->enqueue_input_event(move(key_event)); self.current_key_down_event = nil; @@ -1396,12 +1396,12 @@ static Web::DevicePixelPoint node_picker_position_for(Ladybird::WebViewBridge co - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { - [self handleCurrentKeyDownEvent]; + [self handleCurrentKeyDownEvent:YES]; } - (void)doCommandBySelector:(SEL)selector { - [self handleCurrentKeyDownEvent]; + [self handleCurrentKeyDownEvent:NO]; } - (BOOL)hasMarkedText diff --git a/UI/Gtk/WebContentView.cpp b/UI/Gtk/WebContentView.cpp index 365b2c359c..7c608fa753 100644 --- a/UI/Gtk/WebContentView.cpp +++ b/UI/Gtk/WebContentView.cpp @@ -106,12 +106,19 @@ void WebContentView::enqueue_native_event(Web::MouseEvent::Type type, double x, void WebContentView::enqueue_native_event(Web::KeyEvent::Type type, guint keyval, GdkModifierType state) { + auto modifiers = gdk_modifier_to_web(state); + auto code_point = gdk_keyval_to_unicode(keyval); + auto should_insert_text = type == Web::KeyEvent::Type::KeyDown + && code_point != 0 + && !(modifiers & (Web::UIEvents::KeyModifier::Mod_Ctrl | Web::UIEvents::KeyModifier::Mod_Alt | Web::UIEvents::KeyModifier::Mod_Super)); + Web::KeyEvent event { .type = type, .key = gdk_keyval_to_web(keyval), - .modifiers = gdk_modifier_to_web(state), - .code_point = gdk_keyval_to_unicode(keyval), + .modifiers = modifiers, + .code_point = code_point, .repeat = false, + .should_insert_text = should_insert_text, .browser_data = {}, }; enqueue_input_event(move(event)); diff --git a/UI/Qt/WebContentView.cpp b/UI/Qt/WebContentView.cpp index 961103c7cd..bfc4724ffb 100644 --- a/UI/Qt/WebContentView.cpp +++ b/UI/Qt/WebContentView.cpp @@ -1116,19 +1116,20 @@ void WebContentView::enqueue_native_event(Web::KeyEvent::Type type, QKeyEvent co auto text = event.text(); auto code_point = text.isEmpty() ? 0u : event.text()[0].unicode(); + auto should_insert_text = type == Web::KeyEvent::Type::KeyDown && !text.isEmpty(); auto to_web_event = [&]() -> Web::KeyEvent { if (event.key() == Qt::Key_Backtab) { // 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', event.isAutoRepeat(), make(event) }; + return { type, Web::UIEvents::KeyCode::Key_Tab, Web::UIEvents::Mod_Shift, '\t', event.isAutoRepeat(), false, make(event) }; } 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' - return { type, Web::UIEvents::KeyCode::Key_Return, modifiers, '\n', event.isAutoRepeat(), make(event) }; + return { type, Web::UIEvents::KeyCode::Key_Return, modifiers, '\n', event.isAutoRepeat(), should_insert_text, make(event) }; } - return { type, keycode, modifiers, code_point, event.isAutoRepeat(), make(event) }; + return { type, keycode, modifiers, code_point, event.isAutoRepeat(), should_insert_text, make(event) }; }; enqueue_input_event(to_web_event());