diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 182683e3b5..1de1f4554e 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -405,6 +405,7 @@ set(SOURCES UIEvents/KeyboardEvent.cpp UIEvents/MouseEvent.cpp UIEvents/UIEvent.cpp + UIEvents/WheelEvent.cpp URL/URL.cpp URL/URLSearchParams.cpp URL/URLSearchParamsIterator.cpp diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.cpp b/Userland/Libraries/LibWeb/Page/EventHandler.cpp index a5ed478ced..6b7ac55f15 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Userland/Libraries/LibWeb/Page/EventHandler.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace Web { @@ -133,18 +134,51 @@ bool EventHandler::handle_mousewheel(Gfx::IntPoint const& position, unsigned int if (modifiers & KeyModifier::Mod_Shift) swap(wheel_delta_x, wheel_delta_y); - // FIXME: Support wheel events in nested browsing contexts. + bool handled_event = false; - auto result = paint_root()->hit_test(position.to_type(), Painting::HitTestType::Exact); - if (result.has_value() && result->paintable->handle_mousewheel({}, position, buttons, modifiers, wheel_delta_x, wheel_delta_y)) - return true; - - if (auto* page = m_browsing_context.page()) { - page->client().page_did_request_scroll(wheel_delta_x * 20, wheel_delta_y * 20); - return true; + RefPtr paintable; + if (m_mouse_event_tracking_layout_node) { + paintable = m_mouse_event_tracking_layout_node->paintable(); + } else { + if (auto result = paint_root()->hit_test(position.to_type(), Painting::HitTestType::Exact); result.has_value()) + paintable = result->paintable; } - return false; + if (paintable) { + paintable->handle_mousewheel({}, position, buttons, modifiers, wheel_delta_x, wheel_delta_y); + + JS::GCPtr node = paintable->mouse_event_target(); + if (!node) + node = paintable->dom_node(); + + if (node) { + // FIXME: Support wheel events in nested browsing contexts. + if (is(*node)) { + return false; + } + + // Search for the first parent of the hit target that's an element. + auto* layout_node = &paintable->layout_node(); + while (layout_node && node && !node->is_element() && layout_node->parent()) { + layout_node = layout_node->parent(); + node = layout_node->dom_node(); + } + if (!node || !layout_node) { + return false; + } + + auto offset = compute_mouse_event_offset(position, *layout_node); + if (node->dispatch_event(*UIEvents::WheelEvent::create_from_platform_event(node->realm(), UIEvents::EventNames::wheel, offset.x(), offset.y(), position.x(), position.y(), wheel_delta_x, wheel_delta_y))) { + if (auto* page = m_browsing_context.page()) { + page->client().page_did_request_scroll(wheel_delta_x * 20, wheel_delta_y * 20); + } + } + + handled_event = true; + } + } + + return handled_event; } bool EventHandler::handle_mouseup(Gfx::IntPoint const& position, unsigned button, unsigned modifiers) diff --git a/Userland/Libraries/LibWeb/UIEvents/EventNames.h b/Userland/Libraries/LibWeb/UIEvents/EventNames.h index 773543d0de..3b0399bd6e 100644 --- a/Userland/Libraries/LibWeb/UIEvents/EventNames.h +++ b/Userland/Libraries/LibWeb/UIEvents/EventNames.h @@ -25,7 +25,8 @@ namespace Web::UIEvents::EventNames { __ENUMERATE_UI_EVENT(mouseout) \ __ENUMERATE_UI_EVENT(mouseover) \ __ENUMERATE_UI_EVENT(mouseup) \ - __ENUMERATE_UI_EVENT(resize) + __ENUMERATE_UI_EVENT(resize) \ + __ENUMERATE_UI_EVENT(wheel) #define __ENUMERATE_UI_EVENT(name) extern FlyString name; ENUMERATE_UI_EVENTS diff --git a/Userland/Libraries/LibWeb/UIEvents/MouseEvent.h b/Userland/Libraries/LibWeb/UIEvents/MouseEvent.h index 7022828566..aed8481a01 100644 --- a/Userland/Libraries/LibWeb/UIEvents/MouseEvent.h +++ b/Userland/Libraries/LibWeb/UIEvents/MouseEvent.h @@ -21,7 +21,7 @@ struct MouseEventInit : public EventModifierInit { i16 button = 0; }; -class MouseEvent final : public UIEvent { +class MouseEvent : public UIEvent { WEB_PLATFORM_OBJECT(MouseEvent, UIEvent); public: @@ -43,9 +43,10 @@ public: virtual u32 which() const override { return m_button + 1; } -private: +protected: MouseEvent(JS::Realm&, FlyString const& event_name, MouseEventInit const& event_init); +private: void set_event_characteristics(); double m_offset_x { 0 }; diff --git a/Userland/Libraries/LibWeb/UIEvents/WheelEvent.cpp b/Userland/Libraries/LibWeb/UIEvents/WheelEvent.cpp new file mode 100644 index 0000000000..c2b434baf4 --- /dev/null +++ b/Userland/Libraries/LibWeb/UIEvents/WheelEvent.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::UIEvents { + +WheelEvent::WheelEvent(JS::Realm& realm, FlyString const& event_name, WheelEventInit const& event_init) + : MouseEvent(realm, event_name, event_init) + , m_delta_x(event_init.delta_x) + , m_delta_y(event_init.delta_y) + , m_delta_mode(event_init.delta_mode) +{ + set_prototype(&Bindings::cached_web_prototype(realm, "WheelEvent")); + set_event_characteristics(); +} + +WheelEvent::~WheelEvent() = default; + +WheelEvent* WheelEvent::create(JS::Realm& realm, FlyString const& event_name, WheelEventInit const& event_init) +{ + return realm.heap().allocate(realm, realm, event_name, event_init); +} + +WheelEvent* WheelEvent::create_from_platform_event(JS::Realm& realm, FlyString const& event_name, double offset_x, double offset_y, double client_x, double client_y, double delta_x, double delta_y) +{ + WheelEventInit event_init {}; + event_init.offset_x = offset_x; + event_init.offset_y = offset_y; + event_init.client_x = client_x; + event_init.client_y = client_y; + event_init.delta_x = delta_x; + event_init.delta_y = delta_y; + event_init.delta_mode = WheelDeltaMode::DOM_DELTA_PIXEL; + return WheelEvent::create(realm, event_name, event_init); +} + +void WheelEvent::set_event_characteristics() +{ + set_bubbles(true); + set_cancelable(true); + set_composed(true); +} + +} diff --git a/Userland/Libraries/LibWeb/UIEvents/WheelEvent.h b/Userland/Libraries/LibWeb/UIEvents/WheelEvent.h new file mode 100644 index 0000000000..a9c0b37860 --- /dev/null +++ b/Userland/Libraries/LibWeb/UIEvents/WheelEvent.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::UIEvents { + +enum class WheelDeltaMode : unsigned long { + DOM_DELTA_PIXEL = 0, + DOM_DELTA_LINE = 1, + DOM_DELTA_PAGE = 2, +}; + +struct WheelEventInit : public MouseEventInit { + double delta_x = 0; + double delta_y = 0; + double delta_z = 0; + + WheelDeltaMode delta_mode = WheelDeltaMode::DOM_DELTA_PIXEL; +}; + +class WheelEvent final : public MouseEvent { + WEB_PLATFORM_OBJECT(WheelEvent, MouseEvent); + +public: + static WheelEvent* create(JS::Realm&, FlyString const& event_name, WheelEventInit const& event_init = {}); + static WheelEvent* create_from_platform_event(JS::Realm&, FlyString const& event_name, double offset_x, double offset_y, double client_x, double client_y, double delta_x, double delta_y); + + virtual ~WheelEvent() override; + + double delta_x() const { return m_delta_x; } + double delta_y() const { return m_delta_y; } + double delta_z() const { return m_delta_z; } + unsigned long delta_mode() const { return to_underlying(m_delta_mode); } + +private: + WheelEvent(JS::Realm&, FlyString const& event_name, WheelEventInit const& event_init); + + void set_event_characteristics(); + + double m_delta_x { 0 }; + double m_delta_y { 0 }; + double m_delta_z { 0 }; + WheelDeltaMode m_delta_mode { WheelDeltaMode::DOM_DELTA_PIXEL }; +}; + +} diff --git a/Userland/Libraries/LibWeb/UIEvents/WheelEvent.idl b/Userland/Libraries/LibWeb/UIEvents/WheelEvent.idl new file mode 100644 index 0000000000..51a633737b --- /dev/null +++ b/Userland/Libraries/LibWeb/UIEvents/WheelEvent.idl @@ -0,0 +1,22 @@ +#import + +// https://www.w3.org/TR/uievents/#idl-wheelevent +[Exposed=Window] +interface WheelEvent : MouseEvent { + // DeltaModeCode + const unsigned long DOM_DELTA_PIXEL = 0x00; + const unsigned long DOM_DELTA_LINE = 0x01; + const unsigned long DOM_DELTA_PAGE = 0x02; + + readonly attribute double deltaX; + readonly attribute double deltaY; + readonly attribute double deltaZ; + readonly attribute unsigned long deltaMode; +}; + +dictionary WheelEventInit : MouseEventInit { + double deltaX = 0; + double deltaY = 0; + double deltaZ = 0; + unsigned long deltaMode = 0; +}; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 0eb277be3d..2044acfcd3 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -184,6 +184,7 @@ libweb_js_bindings(UIEvents/FocusEvent) libweb_js_bindings(UIEvents/KeyboardEvent) libweb_js_bindings(UIEvents/MouseEvent) libweb_js_bindings(UIEvents/UIEvent) +libweb_js_bindings(UIEvents/WheelEvent) libweb_js_bindings(URL/URL) libweb_js_bindings(URL/URLSearchParams ITERABLE) libweb_js_bindings(WebGL/WebGLContextEvent)