diff --git a/Meta/gn/secondary/Ladybird/BUILD.gn b/Meta/gn/secondary/Ladybird/BUILD.gn index 0685bb566e..dce1827492 100644 --- a/Meta/gn/secondary/Ladybird/BUILD.gn +++ b/Meta/gn/secondary/Ladybird/BUILD.gn @@ -40,6 +40,7 @@ link_qt("ladybird_qt_components") { config("ladybird_config") { include_dirs = [ + "//Userland", "//Userland/Applications", "//Userland/Services", ] diff --git a/Userland/Libraries/LibWeb/Page/InputEvent.h b/Userland/Libraries/LibWeb/Page/InputEvent.h new file mode 100644 index 0000000000..c5f4109a35 --- /dev/null +++ b/Userland/Libraries/LibWeb/Page/InputEvent.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +// FIXME: These should not be included outside of Serenity. This FIXME appears in several locations across the Ladybird +// chromes. The classes in this file provide a good opportunity to remove LibGUI and Kernel types from LibWeb. +#include +#include + +namespace Web { + +struct ChromeInputData { + virtual ~ChromeInputData() = default; +}; + +struct KeyEvent { +public: + enum class Type { + KeyDown, + KeyUp, + }; + + Type type; + KeyCode key { KeyCode::Key_Invalid }; + KeyModifier modifiers { KeyModifier::Mod_None }; + u32 code_point { 0 }; + + OwnPtr chrome_data; +}; + +struct MouseEvent { +public: + enum class Type { + MouseDown, + MouseUp, + MouseMove, + MouseWheel, + DoubleClick, + }; + + Type type; + Web::DevicePixelPoint position; + Web::DevicePixelPoint screen_position; + GUI::MouseButton button { GUI::MouseButton::None }; + GUI::MouseButton buttons { GUI::MouseButton::None }; + KeyModifier modifiers { KeyModifier::Mod_None }; + int wheel_delta_x { 0 }; + int wheel_delta_y { 0 }; + + OwnPtr chrome_data; +}; + +using InputEvent = Variant; + +} diff --git a/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp b/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp index a3e52bd1f3..981666f75f 100644 --- a/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp +++ b/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp @@ -69,8 +69,8 @@ OutOfProcessWebView::OutOfProcessWebView() GUI::Application::the()->hide_tooltip(); }; - on_finish_handling_input_event = [this](auto event_was_accepted) { - did_finish_handling_input_event(event_was_accepted); + on_finish_handling_key_event = [this](auto const& event) { + finish_handling_key_event(event); }; on_request_worker_agent = []() { @@ -169,22 +169,22 @@ void OutOfProcessWebView::update_zoom() void OutOfProcessWebView::keydown_event(GUI::KeyEvent& event) { - enqueue_input_event(event); + enqueue_native_event(Web::KeyEvent::Type::KeyDown, event); } void OutOfProcessWebView::keyup_event(GUI::KeyEvent& event) { - enqueue_input_event(event); + enqueue_native_event(Web::KeyEvent::Type::KeyUp, event); } void OutOfProcessWebView::mousedown_event(GUI::MouseEvent& event) { - enqueue_input_event(event); + enqueue_native_event(Web::MouseEvent::Type::MouseDown, event); } void OutOfProcessWebView::mouseup_event(GUI::MouseEvent& event) { - enqueue_input_event(event); + enqueue_native_event(Web::MouseEvent::Type::MouseUp, event); if (event.button() == GUI::MouseButton::Backward) { if (on_navigate_back) @@ -197,17 +197,17 @@ void OutOfProcessWebView::mouseup_event(GUI::MouseEvent& event) void OutOfProcessWebView::mousemove_event(GUI::MouseEvent& event) { - enqueue_input_event(event); + enqueue_native_event(Web::MouseEvent::Type::MouseMove, event); } void OutOfProcessWebView::mousewheel_event(GUI::MouseEvent& event) { - enqueue_input_event(event); + enqueue_native_event(Web::MouseEvent::Type::MouseWheel, event); } void OutOfProcessWebView::doubleclick_event(GUI::MouseEvent& event) { - enqueue_input_event(event); + enqueue_native_event(Web::MouseEvent::Type::DoubleClick, event); } void OutOfProcessWebView::theme_change_event(GUI::ThemeChangeEvent& event) @@ -305,138 +305,61 @@ void OutOfProcessWebView::hide_event(GUI::HideEvent&) set_system_visibility_state(false); } -void OutOfProcessWebView::enqueue_input_event(InputEvent const& event) +void OutOfProcessWebView::enqueue_native_event(Web::MouseEvent::Type type, GUI::MouseEvent const& event) { - m_pending_input_events.enqueue(event); - process_next_input_event(); + auto position = to_content_position(event.position()).to_type(); + auto screen_position = (event.position() + (window()->position() + relative_position())).to_type(); + + // FIXME: This wheel delta step size multiplier is used to remain the old scroll behaviour, in future use system step size. + static constexpr int SCROLL_STEP_SIZE = 24; + auto wheel_delta_x = event.wheel_delta_x() * SCROLL_STEP_SIZE; + auto wheel_delta_y = event.wheel_delta_y() * SCROLL_STEP_SIZE; + + enqueue_input_event(Web::MouseEvent { type, position, screen_position, event.button(), static_cast(event.buttons()), static_cast(event.modifiers()), wheel_delta_x, wheel_delta_y, nullptr }); } -void OutOfProcessWebView::process_next_input_event() -{ - if (m_pending_input_events.is_empty()) - return; - - if (m_is_awaiting_response_for_input_event) - return; - m_is_awaiting_response_for_input_event = true; - - // Send the next event over to the web content to be handled by JS. - // We'll later get a message to say whether JS prevented the default event behavior, - // at which point we either discard or handle that event, then try and process the next one. - auto event = m_pending_input_events.head(); - event.visit( - [this](GUI::KeyEvent const& event) { - switch (event.type()) { - case GUI::Event::Type::KeyDown: - client().async_key_down(m_client_state.page_index, event.key(), event.modifiers(), event.code_point()); - break; - case GUI::Event::Type::KeyUp: - client().async_key_up(m_client_state.page_index, event.key(), event.modifiers(), event.code_point()); - break; - default: - dbgln("Unrecognized key event type in OOPWV input event queue: {}", event.type()); - VERIFY_NOT_REACHED(); - } - }, - [this](GUI::MouseEvent const& event) { - auto position = to_content_position(event.position()).to_type(); - auto screen_position = (event.position() + (window()->position() + relative_position())).to_type(); - switch (event.type()) { - case GUI::Event::Type::MouseDown: - client().async_mouse_down(m_client_state.page_index, position, screen_position, event.button(), event.buttons(), event.modifiers()); - break; - case GUI::Event::Type::MouseUp: - client().async_mouse_up(m_client_state.page_index, position, screen_position, event.button(), event.buttons(), event.modifiers()); - break; - case GUI::Event::Type::MouseMove: - client().async_mouse_move(m_client_state.page_index, position, screen_position, event.button(), event.buttons(), event.modifiers()); - break; - case GUI::Event::Type::MouseWheel: { - // FIXME: This wheel delta step size multiplier is used to remain the old scroll behaviour, in future use system step size. - constexpr int scroll_step_size = 24; - client().async_mouse_wheel(m_client_state.page_index, position, screen_position, event.button(), event.buttons(), event.modifiers(), event.wheel_delta_x() * scroll_step_size, event.wheel_delta_y() * scroll_step_size); - break; - } - case GUI::Event::Type::MouseDoubleClick: - client().async_doubleclick(m_client_state.page_index, position, screen_position, event.button(), event.buttons(), event.modifiers()); - break; - default: - dbgln("Unrecognized mouse event type in OOPWV input event queue: {}", event.type()); - VERIFY_NOT_REACHED(); - } - }); -} - -void OutOfProcessWebView::did_finish_handling_input_event(bool event_was_accepted) -{ - VERIFY(m_is_awaiting_response_for_input_event); - - auto event = m_pending_input_events.dequeue(); - m_is_awaiting_response_for_input_event = false; - - if (!event_was_accepted) { - // Here we handle events that were not consumed or cancelled by web content. - // That is, we manually implement the steps that would have happened if the original - // OutOfProcessWebView::foo_event() had called event.ignore(). - // - // The first step is to give our superclass a chance to handle the event. - // - // Then, if it does not, we dispatch the event to our parent widget, but limited so - // that it will never bubble up to the Window. (Otherwise, it would then dispatch the - // event to us since we are the focused widget, and it would go round indefinitely.) - // - // Finally, any unhandled KeyDown events are propagated to trigger any Actions. - event.visit( - [this](GUI::KeyEvent& event) { - switch (event.type()) { - case GUI::Event::Type::KeyDown: - Super::keydown_event(event); - break; - case GUI::Event::Type::KeyUp: - Super::keyup_event(event); - break; - default: - dbgln("Unhandled key event type in OOPWV input event queue: {}", event.type()); - VERIFY_NOT_REACHED(); - } - - if (!event.is_accepted()) { - parent_widget()->dispatch_event(event, window()); - - // NOTE: If other events can ever trigger shortcuts, propagate those here. - if (!event.is_accepted() && event.type() == GUI::Event::Type::KeyDown) - window()->propagate_shortcuts(event, this); - } - }, - [this](GUI::MouseEvent& event) { - switch (event.type()) { - case GUI::Event::Type::MouseDown: - Super::mousedown_event(event); - break; - case GUI::Event::Type::MouseUp: - Super::mouseup_event(event); - break; - case GUI::Event::Type::MouseMove: - Super::mousemove_event(event); - break; - case GUI::Event::Type::MouseWheel: - Super::mousewheel_event(event); - break; - case GUI::Event::Type::MouseDoubleClick: - Super::doubleclick_event(event); - break; - default: - dbgln("Unhandled mouse event type in OOPWV input event queue: {}", event.type()); - VERIFY_NOT_REACHED(); - } - - if (!event.is_accepted()) - parent_widget()->dispatch_event(event, window()); - // FIXME: Propagate event for mouse-button shortcuts once that is implemented. - }); +struct KeyData : Web::ChromeInputData { + explicit KeyData(GUI::KeyEvent const& event) + : event(make(event)) + { } - process_next_input_event(); + NonnullOwnPtr event; +}; + +void OutOfProcessWebView::enqueue_native_event(Web::KeyEvent::Type type, GUI::KeyEvent const& event) +{ + enqueue_input_event(Web::KeyEvent { type, event.key(), static_cast(event.modifiers()), event.code_point(), make(event) }); +} + +void OutOfProcessWebView::finish_handling_key_event(Web::KeyEvent const& key_event) +{ + // First, we give our superclass a chance to handle the event. + // + // If it does not, we dispatch the event to our parent widget, but limited such that it will never bubble up to the + // Window. (Otherwise, it would then dispatch the event to us since we are the focused widget, and it would go around + // indefinitely.) + // + // Finally, any unhandled KeyDown events are propagated to trigger any shortcut Actions. + auto& chrome_data = verify_cast(*key_event.chrome_data); + auto& event = *chrome_data.event; + + switch (key_event.type) { + case Web::KeyEvent::Type::KeyDown: + Super::keydown_event(event); + break; + case Web::KeyEvent::Type::KeyUp: + Super::keyup_event(event); + break; + } + + if (!event.is_accepted()) { + parent_widget()->dispatch_event(event, window()); + + // NOTE: If other events can ever trigger shortcuts, propagate those here. + if (!event.is_accepted() && event.type() == GUI::Event::Type::KeyDown) + window()->propagate_shortcuts(static_cast(event), this); + } } void OutOfProcessWebView::set_content_scales_to_viewport(bool b) diff --git a/Userland/Libraries/LibWebView/OutOfProcessWebView.h b/Userland/Libraries/LibWebView/OutOfProcessWebView.h index ab8c8f16ea..f8753c2bc2 100644 --- a/Userland/Libraries/LibWebView/OutOfProcessWebView.h +++ b/Userland/Libraries/LibWebView/OutOfProcessWebView.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include #include @@ -85,13 +84,9 @@ private: virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const override; virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const override; - using InputEvent = Variant; - void enqueue_input_event(InputEvent const&); - void process_next_input_event(); - void did_finish_handling_input_event(bool event_was_accepted); - - bool m_is_awaiting_response_for_input_event { false }; - Queue m_pending_input_events; + void enqueue_native_event(Web::MouseEvent::Type, GUI::MouseEvent const& event); + void enqueue_native_event(Web::KeyEvent::Type, GUI::KeyEvent const& event); + void finish_handling_key_event(Web::KeyEvent const&); bool m_content_scales_to_viewport { false }; }; diff --git a/Userland/Libraries/LibWebView/ViewImplementation.cpp b/Userland/Libraries/LibWebView/ViewImplementation.cpp index 833abb4509..15e5042850 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.cpp +++ b/Userland/Libraries/LibWebView/ViewImplementation.cpp @@ -112,6 +112,60 @@ void ViewImplementation::reset_zoom() update_zoom(); } +void ViewImplementation::enqueue_input_event(Web::InputEvent event) +{ + // Send the next event over to the WebContent to be handled by JS. We'll later get a message to say whether JS + // prevented the default event behavior, at which point we either discard or handle that event, and then try to + // process the next one. + m_pending_input_events.enqueue(move(event)); + + // FIXME: Replace these IPCs with a singular "async_input_event". + m_pending_input_events.tail().visit( + [this](Web::KeyEvent const& event) { + switch (event.type) { + case Web::KeyEvent::Type::KeyDown: + client().async_key_down(m_client_state.page_index, event.key, event.modifiers, event.code_point); + break; + case Web::KeyEvent::Type::KeyUp: + client().async_key_up(m_client_state.page_index, event.key, event.modifiers, event.code_point); + break; + } + }, + [this](Web::MouseEvent const& event) { + switch (event.type) { + case Web::MouseEvent::Type::MouseDown: + client().async_mouse_down(m_client_state.page_index, event.position, event.screen_position, event.button, event.buttons, event.modifiers); + break; + case Web::MouseEvent::Type::MouseUp: + client().async_mouse_up(m_client_state.page_index, event.position, event.screen_position, event.button, event.buttons, event.modifiers); + break; + case Web::MouseEvent::Type::MouseMove: + client().async_mouse_move(m_client_state.page_index, event.position, event.screen_position, event.button, event.buttons, event.modifiers); + break; + case Web::MouseEvent::Type::MouseWheel: + client().async_mouse_wheel(m_client_state.page_index, event.position, event.screen_position, event.button, event.buttons, event.modifiers, event.wheel_delta_x, event.wheel_delta_y); + break; + case Web::MouseEvent::Type::DoubleClick: + client().async_doubleclick(m_client_state.page_index, event.position, event.screen_position, event.button, event.buttons, event.modifiers); + break; + } + }); +} + +void ViewImplementation::did_finish_handling_input_event(Badge, bool event_was_accepted) +{ + auto event = m_pending_input_events.dequeue(); + + if (!event_was_accepted && event.has()) { + auto const& key_event = event.get(); + + // Here we handle events that were not consumed or cancelled by the WebContent. Propagate the event back + // to the concrete view implementation. + if (on_finish_handling_key_event) + on_finish_handling_key_event(key_event); + } +} + void ViewImplementation::set_preferred_color_scheme(Web::CSS::PreferredColorScheme color_scheme) { client().async_set_preferred_color_scheme(page_id(), color_scheme); diff --git a/Userland/Libraries/LibWebView/ViewImplementation.h b/Userland/Libraries/LibWebView/ViewImplementation.h index 805728ccd9..b3f25c320c 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.h +++ b/Userland/Libraries/LibWebView/ViewImplementation.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include @@ -52,6 +54,9 @@ public: float zoom_level() const { return m_zoom_level; } float device_pixel_ratio() const { return m_device_pixel_ratio; } + void enqueue_input_event(Web::InputEvent); + void did_finish_handling_input_event(Badge, bool event_was_accepted); + void set_preferred_color_scheme(Web::CSS::PreferredColorScheme); ByteString selected_text(); @@ -167,7 +172,7 @@ public: Function on_request_color_picker; Function on_request_file_picker; Function items)> on_request_select_dropdown; - Function on_finish_handling_input_event; + Function on_finish_handling_key_event; Function on_text_test_finish; Function on_theme_color_change; Function on_insert_clipboard_entry; @@ -234,6 +239,8 @@ protected: float m_zoom_level { 1.0 }; float m_device_pixel_ratio { 1.0 }; + Queue m_pending_input_events; + RefPtr m_backing_store_shrink_timer; RefPtr m_backup_bitmap; diff --git a/Userland/Libraries/LibWebView/WebContentClient.cpp b/Userland/Libraries/LibWebView/WebContentClient.cpp index 1f423eb833..55cbad80c9 100644 --- a/Userland/Libraries/LibWebView/WebContentClient.cpp +++ b/Userland/Libraries/LibWebView/WebContentClient.cpp @@ -839,10 +839,9 @@ void WebContentClient::did_finish_handling_input_event(u64 page_id, bool event_w dbgln("Received finish handling input event for unknown page ID {}", page_id); return; } - auto& view = *maybe_view.value(); - if (view.on_finish_handling_input_event) - view.on_finish_handling_input_event(event_was_accepted); + auto& view = *maybe_view.value(); + view.did_finish_handling_input_event({}, event_was_accepted); } void WebContentClient::did_change_theme_color(u64 page_id, Gfx::Color color)