LibWeb+UI: Synthesize ctrl-wheel events for pinches
Trackpad pinches applied directly as visual viewport zoom, so pages could not observe the gesture or cancel the browser default. Canvas apps such as maps expect the ctrl-wheel path instead.
This commit is contained in:
parent
baecb14472
commit
9ab311507e
15 changed files with 173 additions and 30 deletions
|
|
@ -274,7 +274,7 @@ void EventLoop::process_input_events() const
|
|||
return page.handle_drag_and_drop_event(drag_event.type, drag_event.position, drag_event.screen_position, drag_event.button, drag_event.buttons, drag_event.modifiers, move(drag_event.files));
|
||||
},
|
||||
[&](Web::PinchEvent& pinch_event) {
|
||||
return page.handle_pinch_event(pinch_event.position, pinch_event.scale_delta);
|
||||
return page.handle_pinch_event(pinch_event.position, pinch_event.modifiers, pinch_event.scale_delta);
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < event.coalesced_event_count; ++i)
|
||||
|
|
|
|||
|
|
@ -388,11 +388,11 @@ GC::Ref<WebIDL::Promise> Internals::wheel(double x, double y, double delta_x, do
|
|||
return promise;
|
||||
}
|
||||
|
||||
void Internals::pinch(double x, double y, double scale_delta)
|
||||
void Internals::pinch(double x, double y, double scale_delta, WebIDL::UnsignedShort modifiers)
|
||||
{
|
||||
auto& page = this->page();
|
||||
auto position = page.css_to_device_point({ x, y });
|
||||
page.handle_pinch_event(position, scale_delta);
|
||||
page.handle_pinch_event(position, modifiers, scale_delta);
|
||||
}
|
||||
|
||||
String Internals::current_cursor()
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public:
|
|||
void click(double x, double y, WebIDL::UnsignedShort click_count, WebIDL::UnsignedShort button, WebIDL::UnsignedShort modifiers);
|
||||
void click_and_hold(double x, double y, WebIDL::UnsignedShort click_count, WebIDL::UnsignedShort button, WebIDL::UnsignedShort modifiers);
|
||||
GC::Ref<WebIDL::Promise> wheel(double x, double y, double delta_x, double delta_y);
|
||||
void pinch(double x, double y, double scale_delta);
|
||||
void pinch(double x, double y, double scale_delta, WebIDL::UnsignedShort modifiers);
|
||||
|
||||
String current_cursor();
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ interface Internals {
|
|||
undefined click(double x, double y, optional unsigned short clickCount = 1, optional unsigned short button = 0, optional unsigned short modifiers = 0);
|
||||
undefined clickAndHold(double x, double y, optional unsigned short clickCount = 1, optional unsigned short button = 0, optional unsigned short modifiers = 0);
|
||||
Promise<undefined> wheel(double x, double y, double deltaX, double deltaY);
|
||||
undefined pinch(double x, double y, double scaleDelta);
|
||||
undefined pinch(double x, double y, double scaleDelta, optional unsigned short modifiers = 0);
|
||||
|
||||
DOMString currentCursor();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Math.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibUnicode/CharacterTypes.h>
|
||||
#include <LibUnicode/Segmenter.h>
|
||||
|
|
@ -528,7 +529,6 @@ EventResult EventHandler::handle_mousewheel(CSSPixelPoint visual_viewport_positi
|
|||
return EventResult::Dropped;
|
||||
|
||||
auto visual_viewport = document->visual_viewport();
|
||||
auto viewport_position = visual_viewport->map_to_layout_viewport(visual_viewport_position);
|
||||
|
||||
document->update_layout(DOM::UpdateLayoutReason::EventHandlerHandleMouseWheel);
|
||||
|
||||
|
|
@ -632,28 +632,19 @@ EventResult EventHandler::handle_mousewheel(CSSPixelPoint visual_viewport_positi
|
|||
return result.value();
|
||||
}
|
||||
|
||||
// NB: Search for the first parent of the hit target that's an element.
|
||||
GC::Ptr<Layout::Node> layout_node;
|
||||
if (!parent_element_for_event_dispatch(*paintable, node, layout_node))
|
||||
return EventResult::Dropped;
|
||||
|
||||
auto page_offset = compute_mouse_event_page_offset(viewport_position);
|
||||
RefPtr<Painting::Paintable> offset_paintable = layout_node->first_paintable();
|
||||
if (!offset_paintable)
|
||||
offset_paintable = paintable;
|
||||
auto scroll_offset = document->navigable()->viewport_scroll_offset();
|
||||
auto offset = compute_mouse_event_offset(visual_viewport_position.translated(scroll_offset), *offset_paintable);
|
||||
auto is_cancelable = async_scroll_performed_default_action ? UIEvents::WheelEventIsCancelable::No : UIEvents::WheelEventIsCancelable::Yes;
|
||||
if (node->dispatch_event(UIEvents::WheelEvent::create_from_platform_event(node->realm(), m_navigable->active_window_proxy(), UIEvents::EventNames::wheel, screen_position, page_offset, viewport_position, offset, wheel_delta_x, wheel_delta_y, button, buttons, modifiers, is_cancelable).release_value_but_fixme_should_propagate_errors())) {
|
||||
auto dispatch_result = dispatch_wheel_event(*paintable, visual_viewport_position, screen_position, button, buttons, modifiers, wheel_delta_x, wheel_delta_y, is_cancelable == UIEvents::WheelEventIsCancelable::Yes);
|
||||
if (dispatch_result == EventResult::Accepted) {
|
||||
if (async_scroll_performed_default_action)
|
||||
handled_event = EventResult::Handled;
|
||||
else
|
||||
handled_event = perform_wheel_default_action(resolve_wheel_default_action_target());
|
||||
} else if (async_scroll_performed_default_action) {
|
||||
} else if (dispatch_result == EventResult::Cancelled && async_scroll_performed_default_action) {
|
||||
handled_event = EventResult::Handled;
|
||||
} else {
|
||||
} else if (dispatch_result == EventResult::Cancelled) {
|
||||
handled_event = EventResult::Cancelled;
|
||||
}
|
||||
} else
|
||||
handled_event = dispatch_result;
|
||||
} else if (!async_scroll_performed_default_action) {
|
||||
handled_event = perform_wheel_default_action(paintable);
|
||||
}
|
||||
|
|
@ -662,6 +653,66 @@ EventResult EventHandler::handle_mousewheel(CSSPixelPoint visual_viewport_positi
|
|||
return handled_event;
|
||||
}
|
||||
|
||||
EventResult EventHandler::dispatch_wheel_event(Painting::Paintable& paintable, CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers, double wheel_delta_x, double wheel_delta_y, bool is_cancelable)
|
||||
{
|
||||
auto document = m_navigable->active_document();
|
||||
if (!document || !document->is_fully_active())
|
||||
return EventResult::Dropped;
|
||||
|
||||
auto node = dom_node_for_event_dispatch(paintable);
|
||||
if (!node)
|
||||
return EventResult::Dropped;
|
||||
|
||||
// NB: Search for the first parent of the hit target that's an element.
|
||||
GC::Ptr<Layout::Node> layout_node;
|
||||
if (!parent_element_for_event_dispatch(paintable, node, layout_node))
|
||||
return EventResult::Dropped;
|
||||
|
||||
auto viewport_position = document->visual_viewport()->map_to_layout_viewport(visual_viewport_position);
|
||||
auto page_offset = compute_mouse_event_page_offset(viewport_position);
|
||||
RefPtr<Painting::Paintable> offset_paintable = layout_node->first_paintable();
|
||||
if (!offset_paintable)
|
||||
offset_paintable = &paintable;
|
||||
auto scroll_offset = document->navigable()->viewport_scroll_offset();
|
||||
auto offset = compute_mouse_event_offset(visual_viewport_position.translated(scroll_offset), *offset_paintable);
|
||||
auto cancelability = is_cancelable ? UIEvents::WheelEventIsCancelable::Yes : UIEvents::WheelEventIsCancelable::No;
|
||||
auto event = UIEvents::WheelEvent::create_from_platform_event(node->realm(), m_navigable->active_window_proxy(), UIEvents::EventNames::wheel, screen_position, page_offset, viewport_position, offset, wheel_delta_x, wheel_delta_y, button, buttons, modifiers, cancelability).release_value_but_fixme_should_propagate_errors();
|
||||
return node->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled;
|
||||
}
|
||||
|
||||
EventResult EventHandler::dispatch_synthetic_pinch_wheel_event(CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, u32 modifiers, double wheel_delta_y)
|
||||
{
|
||||
auto document = m_navigable->active_document();
|
||||
if (!document || !document->is_fully_active())
|
||||
return EventResult::Dropped;
|
||||
|
||||
document->update_layout(DOM::UpdateLayoutReason::EventHandlerHandleMouseWheel);
|
||||
|
||||
if (!paint_root())
|
||||
return EventResult::Dropped;
|
||||
|
||||
RefPtr<Painting::Paintable> paintable;
|
||||
if (auto result = target_for_mouse_position(visual_viewport_position); result.has_value())
|
||||
paintable = result->paintable;
|
||||
|
||||
if (!paintable)
|
||||
return EventResult::Dropped;
|
||||
|
||||
if (!dom_node_for_event_dispatch(*paintable))
|
||||
return EventResult::Dropped;
|
||||
|
||||
if (auto result = dispatch_event_to_nested_navigable(*paintable, visual_viewport_position, [screen_position, modifiers, wheel_delta_y](EventHandler& event_handler, CSSPixelPoint position) -> EventResult {
|
||||
return event_handler.dispatch_synthetic_pinch_wheel_event(position, screen_position, modifiers, wheel_delta_y);
|
||||
});
|
||||
result.has_value()) {
|
||||
if (result.value() == EventResult::Handled || result.value() == EventResult::Cancelled)
|
||||
return result.value();
|
||||
}
|
||||
|
||||
modifiers |= UIEvents::KeyModifier::Mod_Ctrl;
|
||||
return dispatch_wheel_event(*paintable, visual_viewport_position, screen_position, UIEvents::MouseButton::None, UIEvents::MouseButton::None, modifiers, 0, wheel_delta_y, true);
|
||||
}
|
||||
|
||||
EventResult EventHandler::handle_mouseleave()
|
||||
{
|
||||
if (should_ignore_device_input_event()) {
|
||||
|
|
@ -1122,7 +1173,7 @@ bool EventHandler::should_ignore_device_input_event() const
|
|||
return m_drag_and_drop_event_handler->has_ongoing_drag_and_drop_operation();
|
||||
}
|
||||
|
||||
EventResult EventHandler::handle_pinch_event(CSSPixelPoint point, double scale_delta)
|
||||
EventResult EventHandler::handle_pinch_event(CSSPixelPoint point, u32 modifiers, double scale_delta)
|
||||
{
|
||||
auto document = m_navigable->active_document();
|
||||
if (!document)
|
||||
|
|
@ -1130,8 +1181,19 @@ EventResult EventHandler::handle_pinch_event(CSSPixelPoint point, double scale_d
|
|||
if (!document->is_fully_active())
|
||||
return EventResult::Dropped;
|
||||
|
||||
auto visual_viewport = document->visual_viewport();
|
||||
visual_viewport->zoom(point, scale_delta);
|
||||
auto scale = 1.0 + scale_delta;
|
||||
if (scale == 1.0)
|
||||
return EventResult::Handled;
|
||||
|
||||
auto wheel_delta_y = -100.0 * AK::log(scale);
|
||||
if (dispatch_synthetic_pinch_wheel_event(point, point, modifiers, wheel_delta_y) == EventResult::Cancelled)
|
||||
return EventResult::Cancelled;
|
||||
|
||||
document = m_navigable->active_document();
|
||||
if (!document || !document->is_fully_active())
|
||||
return EventResult::Dropped;
|
||||
|
||||
document->visual_viewport()->zoom(point, scale_delta);
|
||||
return EventResult::Handled;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public:
|
|||
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<HTML::SelectedFile> files);
|
||||
EventResult handle_pinch_event(CSSPixelPoint, double scale_delta);
|
||||
EventResult handle_pinch_event(CSSPixelPoint, unsigned modifiers, double scale_delta);
|
||||
EventResult handle_paste(Utf16String const& text);
|
||||
void handle_sdl_input_events();
|
||||
|
||||
|
|
@ -123,6 +123,8 @@ private:
|
|||
void stop_updating_selection();
|
||||
|
||||
void update_hover_after_scroll(CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers);
|
||||
EventResult dispatch_wheel_event(Painting::Paintable&, CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, double wheel_delta_x, double wheel_delta_y, bool is_cancelable);
|
||||
EventResult dispatch_synthetic_pinch_wheel_event(CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, unsigned modifiers, double wheel_delta_y);
|
||||
|
||||
enum class PointerEventType : u8 {
|
||||
PointerDown,
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ template<>
|
|||
WEB_API ErrorOr<void> IPC::encode(Encoder& encoder, Web::PinchEvent const& event)
|
||||
{
|
||||
TRY(encoder.encode(event.position));
|
||||
TRY(encoder.encode(event.modifiers));
|
||||
TRY(encoder.encode(event.scale_delta));
|
||||
return {};
|
||||
}
|
||||
|
|
@ -123,10 +124,11 @@ template<>
|
|||
WEB_API ErrorOr<Web::PinchEvent> IPC::decode(Decoder& decoder)
|
||||
{
|
||||
auto position = TRY(decoder.decode<Web::DevicePixelPoint>());
|
||||
auto modifiers = TRY(decoder.decode<Web::UIEvents::KeyModifier>());
|
||||
auto scale_delta = TRY(decoder.decode<double>());
|
||||
|
||||
if (isnan(scale_delta) || isinf(scale_delta))
|
||||
return Error::from_string_literal("IPC: Invalid scale_delta value");
|
||||
|
||||
return Web::PinchEvent { position, scale_delta };
|
||||
return Web::PinchEvent { position, modifiers, scale_delta };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ struct WEB_API DragEvent {
|
|||
|
||||
struct WEB_API PinchEvent {
|
||||
Web::DevicePixelPoint position;
|
||||
UIEvents::KeyModifier modifiers { UIEvents::KeyModifier::Mod_None };
|
||||
double scale_delta;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -334,9 +334,9 @@ 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));
|
||||
}
|
||||
|
||||
EventResult Page::handle_pinch_event(DevicePixelPoint position, double scale)
|
||||
EventResult Page::handle_pinch_event(DevicePixelPoint position, unsigned modifiers, double scale)
|
||||
{
|
||||
return top_level_traversable()->event_handler().handle_pinch_event(device_to_css_point(position), scale);
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ public:
|
|||
EventResult handle_mousewheel(DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, double wheel_delta_x, double wheel_delta_y, bool async_scroll_performed_default_action = false, Optional<AsyncScrollOperation>* async_scroll_operation = nullptr);
|
||||
|
||||
EventResult handle_drag_and_drop_event(DragEvent::Type, DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files);
|
||||
EventResult handle_pinch_event(DevicePixelPoint point, double scale);
|
||||
EventResult handle_pinch_event(DevicePixelPoint point, unsigned modifiers, double scale);
|
||||
|
||||
EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
|
||||
EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
initial scale: 1.00
|
||||
events after prevented pinch: 1
|
||||
ctrlKey: true
|
||||
shiftKey: true
|
||||
cancelable: true
|
||||
defaultPrevented: true
|
||||
deltaX: 0
|
||||
deltaY: -4.8790
|
||||
deltaMode: 0
|
||||
scale after prevented pinch: 1.00
|
||||
events after unprevented pinch: 2
|
||||
scale after unprevented pinch: 1.05
|
||||
scrollY after unprevented pinch: 0
|
||||
events after zero-delta pinch: 2
|
||||
scale after zero-delta pinch: 1.05
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="include.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 2000px;
|
||||
}
|
||||
|
||||
#target {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
<div id="target"></div>
|
||||
<script>
|
||||
test(() => {
|
||||
const visualViewport = window.visualViewport;
|
||||
const events = [];
|
||||
let shouldPreventDefault = true;
|
||||
|
||||
window.addEventListener("wheel", event => {
|
||||
if (event.ctrlKey && shouldPreventDefault)
|
||||
event.preventDefault();
|
||||
|
||||
events.push({
|
||||
ctrlKey: event.ctrlKey,
|
||||
shiftKey: event.shiftKey,
|
||||
cancelable: event.cancelable,
|
||||
defaultPrevented: event.defaultPrevented,
|
||||
deltaX: event.deltaX,
|
||||
deltaY: event.deltaY,
|
||||
deltaMode: event.deltaMode,
|
||||
});
|
||||
}, { passive: false });
|
||||
|
||||
println(`initial scale: ${visualViewport.scale.toFixed(2)}`);
|
||||
|
||||
internals.pinch(100, 100, 0.05, internals.MOD_SHIFT);
|
||||
println(`events after prevented pinch: ${events.length}`);
|
||||
println(`ctrlKey: ${events[0].ctrlKey}`);
|
||||
println(`shiftKey: ${events[0].shiftKey}`);
|
||||
println(`cancelable: ${events[0].cancelable}`);
|
||||
println(`defaultPrevented: ${events[0].defaultPrevented}`);
|
||||
println(`deltaX: ${events[0].deltaX}`);
|
||||
println(`deltaY: ${events[0].deltaY.toFixed(4)}`);
|
||||
println(`deltaMode: ${events[0].deltaMode}`);
|
||||
println(`scale after prevented pinch: ${visualViewport.scale.toFixed(2)}`);
|
||||
|
||||
shouldPreventDefault = false;
|
||||
internals.pinch(100, 100, 0.05);
|
||||
println(`events after unprevented pinch: ${events.length}`);
|
||||
println(`scale after unprevented pinch: ${visualViewport.scale.toFixed(2)}`);
|
||||
println(`scrollY after unprevented pinch: ${window.scrollY}`);
|
||||
|
||||
internals.pinch(100, 100, 0);
|
||||
println(`events after zero-delta pinch: ${events.length}`);
|
||||
println(`scale after zero-delta pinch: ${visualViewport.scale.toFixed(2)}`);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
namespace Ladybird {
|
||||
|
||||
Web::UIEvents::KeyModifier ns_modifiers_to_key_modifiers(NSEventModifierFlags);
|
||||
Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type, NSEvent*, NSView*, Web::UIEvents::MouseButton);
|
||||
|
||||
Web::DragEvent ns_event_to_drag_event(Web::DragEvent::Type, id<NSDraggingInfo>, NSView*);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
namespace Ladybird {
|
||||
|
||||
static Web::UIEvents::KeyModifier ns_modifiers_to_key_modifiers(NSEventModifierFlags modifier_flags)
|
||||
Web::UIEvents::KeyModifier ns_modifiers_to_key_modifiers(NSEventModifierFlags modifier_flags)
|
||||
{
|
||||
unsigned modifiers = Web::UIEvents::KeyModifier::Mod_None;
|
||||
|
||||
|
|
|
|||
|
|
@ -1396,6 +1396,7 @@ struct HideCursor {
|
|||
NSPoint point = [recognizer locationInView:self];
|
||||
Web::PinchEvent pinch_event;
|
||||
pinch_event.position = Ladybird::ns_point_to_gfx_point(point).to_type<Web::DevicePixels>() * m_web_view_bridge->device_pixel_ratio();
|
||||
pinch_event.modifiers = Ladybird::ns_modifiers_to_key_modifiers([NSEvent modifierFlags]);
|
||||
pinch_event.scale_delta = scale_delta;
|
||||
m_web_view_bridge->enqueue_input_event(move(pinch_event));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue