Browse Source

Ladybird/AppKit: Handle input events through LibWebView

The AppKit chrome currently handles all input events before selectively
forwarding those events to WebContent. This means that WebContent does
not see events like cmd+c.

Here, we make use of LibWebView's input handling and wait for LibWebView
to inform the chrome that it should handle the event itself.
Timothy Flynn 1 year ago
parent
commit
2c31ef11bc

+ 4 - 17
Ladybird/AppKit/UI/Event.h

@@ -6,30 +6,17 @@
 
 #pragma once
 
-// FIXME: These should not be included outside of Serenity.
-#include <Kernel/API/KeyCode.h>
-#include <LibGUI/Event.h>
+#include <LibWeb/Page/InputEvent.h>
 
 #import <System/Cocoa.h>
 
 namespace Ladybird {
 
-struct MouseEvent {
-    Gfx::IntPoint position {};
-    Gfx::IntPoint screen_position {};
-    GUI::MouseButton button { GUI::MouseButton::Primary };
-    KeyModifier modifiers { KeyModifier::Mod_None };
-};
-MouseEvent ns_event_to_mouse_event(NSEvent*, NSView*, GUI::MouseButton);
+Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type, NSEvent*, NSView*, NSScrollView*, GUI::MouseButton);
+Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type, NSEvent*);
+NSEvent* key_event_to_ns_event(Web::KeyEvent const&);
 
 NSEvent* create_context_menu_mouse_event(NSView*, Gfx::IntPoint);
 NSEvent* create_context_menu_mouse_event(NSView*, NSPoint);
 
-struct KeyEvent {
-    KeyCode key_code { KeyCode::Key_Invalid };
-    KeyModifier modifiers { KeyModifier::Mod_None };
-    u32 code_point { 0 };
-};
-KeyEvent ns_event_to_key_event(NSEvent*);
-
 }

+ 60 - 4
Ladybird/AppKit/UI/Event.mm

@@ -36,13 +36,37 @@ static KeyModifier ns_modifiers_to_key_modifiers(NSEventModifierFlags modifier_f
     return static_cast<KeyModifier>(modifiers);
 }
 
-MouseEvent ns_event_to_mouse_event(NSEvent* event, NSView* view, GUI::MouseButton button)
+Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type type, NSEvent* event, NSView* view, NSScrollView* scroll_view, GUI::MouseButton button)
 {
     auto position = [view convertPoint:event.locationInWindow fromView:nil];
+    auto device_position = ns_point_to_gfx_point(position).to_type<Web::DevicePixels>();
+
     auto screen_position = [NSEvent mouseLocation];
+    auto device_screen_position = ns_point_to_gfx_point(screen_position).to_type<Web::DevicePixels>();
+
     auto modifiers = ns_modifiers_to_key_modifiers(event.modifierFlags, button);
 
-    return { ns_point_to_gfx_point(position), ns_point_to_gfx_point(screen_position), button, modifiers };
+    int wheel_delta_x = 0;
+    int wheel_delta_y = 0;
+
+    if (type == Web::MouseEvent::Type::MouseDown) {
+        if (event.clickCount % 2 == 0) {
+            type = Web::MouseEvent::Type::DoubleClick;
+        }
+    } else if (type == Web::MouseEvent::Type::MouseWheel) {
+        CGFloat delta_x = -[event scrollingDeltaX];
+        CGFloat delta_y = -[event scrollingDeltaY];
+
+        if (![event hasPreciseScrollingDeltas]) {
+            delta_x *= scroll_view.horizontalLineScroll;
+            delta_y *= scroll_view.verticalLineScroll;
+        }
+
+        wheel_delta_x = static_cast<int>(delta_x);
+        wheel_delta_y = static_cast<int>(delta_y);
+    }
+
+    return { type, device_position, device_screen_position, button, button, modifiers, wheel_delta_x, wheel_delta_y, nullptr };
 }
 
 NSEvent* create_context_menu_mouse_event(NSView* view, Gfx::IntPoint position)
@@ -179,7 +203,33 @@ static KeyCode ns_key_code_to_key_code(unsigned short key_code, KeyModifier& mod
     return KeyCode::Key_Invalid;
 }
 
-KeyEvent ns_event_to_key_event(NSEvent* event)
+class KeyData : public Web::ChromeInputData {
+public:
+    explicit KeyData(NSEvent* event)
+        : m_event(CFBridgingRetain(event))
+    {
+    }
+
+    virtual ~KeyData() override
+    {
+        if (m_event != nullptr) {
+            CFBridgingRelease(m_event);
+        }
+    }
+
+    NSEvent* take_event()
+    {
+        VERIFY(m_event != nullptr);
+
+        CFTypeRef event = exchange(m_event, nullptr);
+        return CFBridgingRelease(event);
+    }
+
+private:
+    CFTypeRef m_event { nullptr };
+};
+
+Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type type, NSEvent* event)
 {
     auto modifiers = ns_modifiers_to_key_modifiers(event.modifierFlags);
     auto key_code = ns_key_code_to_key_code(event.keyCode, modifiers);
@@ -190,7 +240,13 @@ KeyEvent ns_event_to_key_event(NSEvent* event)
     // FIXME: WebContent should really support multi-code point key events.
     auto code_point = utf8_view.is_empty() ? 0u : *utf8_view.begin();
 
-    return { key_code, modifiers, code_point };
+    return { type, key_code, modifiers, code_point, make<KeyData>(event) };
+}
+
+NSEvent* key_event_to_ns_event(Web::KeyEvent const& event)
+{
+    auto& chrome_data = verify_cast<KeyData>(*event.chrome_data);
+    return chrome_data.take_event();
 }
 
 }

+ 57 - 36
Ladybird/AppKit/UI/LadybirdWebView.mm

@@ -67,6 +67,11 @@ struct HideCursor {
 @property (nonatomic, strong) NSTextField* status_label;
 @property (nonatomic, strong) NSAlert* dialog;
 
+// NSEvent does not provide a way to mark whether it has been handled, nor can we attach user data to the event. So
+// when we dispatch the event for a second time after WebContent has had a chance to handle it, we must track that
+// event ourselves to prevent indefinitely repeating the event.
+@property (nonatomic, strong) NSEvent* event_being_redispatched;
+
 @end
 
 @implementation LadybirdWebView
@@ -284,6 +289,14 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
         [self.observer onFaviconChange:bitmap];
     };
 
+    m_web_view_bridge->on_finish_handling_key_event = [self](auto const& key_event) {
+        NSEvent* event = Ladybird::key_event_to_ns_event(key_event);
+
+        self.event_being_redispatched = event;
+        [NSApp sendEvent:event];
+        self.event_being_redispatched = nil;
+    };
+
     m_web_view_bridge->on_scroll = [self](auto position) {
         auto content_rect = [self frame];
         auto document_rect = [[self documentView] frame];
@@ -1238,82 +1251,90 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
 
 - (void)mouseMoved:(NSEvent*)event
 {
-    auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::None);
-    m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers);
+    auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], GUI::MouseButton::None);
+    m_web_view_bridge->enqueue_input_event(move(mouse_event));
 }
 
 - (void)scrollWheel:(NSEvent*)event
 {
-    auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Middle);
-    CGFloat delta_x = -[event scrollingDeltaX];
-    CGFloat delta_y = -[event scrollingDeltaY];
-    if (![event hasPreciseScrollingDeltas]) {
-        delta_x *= [self scrollView].horizontalLineScroll;
-        delta_y *= [self scrollView].verticalLineScroll;
-    }
-    m_web_view_bridge->mouse_wheel_event(position, screen_position, button, modifiers, delta_x, delta_y);
+    auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseWheel, event, self, [self scrollView], GUI::MouseButton::Middle);
+    m_web_view_bridge->enqueue_input_event(move(mouse_event));
 }
 
 - (void)mouseDown:(NSEvent*)event
 {
     [[self window] makeFirstResponder:self];
 
-    auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary);
-
-    if (event.clickCount % 2 == 0) {
-        m_web_view_bridge->mouse_double_click_event(position, screen_position, button, modifiers);
-    } else {
-        m_web_view_bridge->mouse_down_event(position, screen_position, button, modifiers);
-    }
+    auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, [self scrollView], GUI::MouseButton::Primary);
+    m_web_view_bridge->enqueue_input_event(move(mouse_event));
 }
 
 - (void)mouseUp:(NSEvent*)event
 {
-    auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary);
-    m_web_view_bridge->mouse_up_event(position, screen_position, button, modifiers);
+    auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, [self scrollView], GUI::MouseButton::Primary);
+    m_web_view_bridge->enqueue_input_event(move(mouse_event));
 }
 
 - (void)mouseDragged:(NSEvent*)event
 {
-    auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary);
-    m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers);
+    auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], GUI::MouseButton::Primary);
+    m_web_view_bridge->enqueue_input_event(move(mouse_event));
 }
 
 - (void)rightMouseDown:(NSEvent*)event
 {
     [[self window] makeFirstResponder:self];
 
-    auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary);
-
-    if (event.clickCount % 2 == 0) {
-        m_web_view_bridge->mouse_double_click_event(position, screen_position, button, modifiers);
-    } else {
-        m_web_view_bridge->mouse_down_event(position, screen_position, button, modifiers);
-    }
+    auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, [self scrollView], GUI::MouseButton::Primary);
+    m_web_view_bridge->enqueue_input_event(move(mouse_event));
 }
 
 - (void)rightMouseUp:(NSEvent*)event
 {
-    auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary);
-    m_web_view_bridge->mouse_up_event(position, screen_position, button, modifiers);
+    auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, [self scrollView], GUI::MouseButton::Secondary);
+    m_web_view_bridge->enqueue_input_event(move(mouse_event));
 }
 
 - (void)rightMouseDragged:(NSEvent*)event
 {
-    auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary);
-    m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers);
+    auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], GUI::MouseButton::Secondary);
+    m_web_view_bridge->enqueue_input_event(move(mouse_event));
+}
+
+- (BOOL)performKeyEquivalent:(NSEvent*)event
+{
+    if ([event window] != [self window]) {
+        return NO;
+    }
+    if ([[self window] firstResponder] != self) {
+        return NO;
+    }
+    if (self.event_being_redispatched == event) {
+        return NO;
+    }
+
+    [self keyDown:event];
+    return YES;
 }
 
 - (void)keyDown:(NSEvent*)event
 {
-    auto [key_code, modifiers, code_point] = Ladybird::ns_event_to_key_event(event);
-    m_web_view_bridge->key_down_event(key_code, modifiers, code_point);
+    if (self.event_being_redispatched == event) {
+        return;
+    }
+
+    auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyDown, event);
+    m_web_view_bridge->enqueue_input_event(move(key_event));
 }
 
 - (void)keyUp:(NSEvent*)event
 {
-    auto [key_code, modifiers, code_point] = Ladybird::ns_event_to_key_event(event);
-    m_web_view_bridge->key_up_event(key_code, modifiers, code_point);
+    if (self.event_being_redispatched == event) {
+        return;
+    }
+
+    auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyUp, event);
+    m_web_view_bridge->enqueue_input_event(move(key_event));
 }
 
 @end

+ 6 - 29
Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp

@@ -95,39 +95,16 @@ void WebViewBridge::set_preferred_color_scheme(Web::CSS::PreferredColorScheme co
     client().async_set_preferred_color_scheme(m_client_state.page_index, color_scheme);
 }
 
-void WebViewBridge::mouse_down_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers)
+void WebViewBridge::enqueue_input_event(Web::MouseEvent event)
 {
-    client().async_mouse_down(m_client_state.page_index, to_content_position(position).to_type<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), to_underlying(button), to_underlying(button), modifiers);
+    event.position = to_content_position(event.position.to_type<int>()).to_type<Web::DevicePixels>();
+    event.screen_position = to_content_position(event.screen_position.to_type<int>()).to_type<Web::DevicePixels>();
+    ViewImplementation::enqueue_input_event(move(event));
 }
 
-void WebViewBridge::mouse_up_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers)
+void WebViewBridge::enqueue_input_event(Web::KeyEvent event)
 {
-    client().async_mouse_up(m_client_state.page_index, to_content_position(position).to_type<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), to_underlying(button), to_underlying(button), modifiers);
-}
-
-void WebViewBridge::mouse_move_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers)
-{
-    client().async_mouse_move(m_client_state.page_index, to_content_position(position).to_type<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), 0, to_underlying(button), modifiers);
-}
-
-void WebViewBridge::mouse_wheel_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers, int wheel_delta_x, int wheel_delta_y)
-{
-    client().async_mouse_wheel(m_client_state.page_index, to_content_position(position).to_type<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), to_underlying(button), to_underlying(button), modifiers, wheel_delta_x, wheel_delta_y);
-}
-
-void WebViewBridge::mouse_double_click_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers)
-{
-    client().async_doubleclick(m_client_state.page_index, to_content_position(position).to_type<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), button, to_underlying(button), modifiers);
-}
-
-void WebViewBridge::key_down_event(KeyCode key_code, KeyModifier modifiers, u32 code_point)
-{
-    client().async_key_down(m_client_state.page_index, key_code, modifiers, code_point);
-}
-
-void WebViewBridge::key_up_event(KeyCode key_code, KeyModifier modifiers, u32 code_point)
-{
-    client().async_key_up(m_client_state.page_index, key_code, modifiers, code_point);
+    ViewImplementation::enqueue_input_event(move(event));
 }
 
 Optional<WebViewBridge::Paintable> WebViewBridge::paintable()

+ 3 - 12
Ladybird/AppKit/UI/LadybirdWebViewBridge.h

@@ -13,12 +13,9 @@
 #include <LibGfx/Size.h>
 #include <LibGfx/StandardCursor.h>
 #include <LibWeb/CSS/PreferredColorScheme.h>
+#include <LibWeb/Page/InputEvent.h>
 #include <LibWebView/ViewImplementation.h>
 
-// FIXME: These should not be included outside of Serenity.
-#include <Kernel/API/KeyCode.h>
-#include <LibGUI/Event.h>
-
 namespace Ladybird {
 
 class WebViewBridge final : public WebView::ViewImplementation {
@@ -41,14 +38,8 @@ public:
     void update_palette();
     void set_preferred_color_scheme(Web::CSS::PreferredColorScheme);
 
-    void mouse_down_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier);
-    void mouse_up_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier);
-    void mouse_move_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier);
-    void mouse_wheel_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier, int, int);
-    void mouse_double_click_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier);
-
-    void key_down_event(KeyCode, KeyModifier, u32);
-    void key_up_event(KeyCode, KeyModifier, u32);
+    void enqueue_input_event(Web::MouseEvent);
+    void enqueue_input_event(Web::KeyEvent);
 
     struct Paintable {
         Gfx::Bitmap& bitmap;