Compositor: Handle pinch zoom asynchronously

Apply pinch zoom deltas to the compositor's visual viewport transform
so the currently presented display list can respond without waiting for
the WebContent main thread. Keep the normal WebContent pinch event path
so the real VisualViewport state and DOM-visible events catch up after.

Only take the compositor path when async scrolling is enabled and there
are no blocking wheel listeners, since pinch zoom dispatches a synthetic
wheel event that script may cancel. Coalesce queued pinch events in
WebContent so main-thread catch-up can adopt multiple gesture deltas
together.

Use the compositor visual viewport transform for wheel hit testing and
consume wheel deltas as visual viewport pan while zoomed. Scale the
handoff to layout viewport scrolling by the inverse visual viewport
scale, so touchpad momentum does not jump when the visual viewport hits
an edge.
This commit is contained in:
Andreas Kling 2026-06-14 21:30:28 +02:00 committed by Andreas Kling
parent 5378c389aa
commit a11c281dc3
15 changed files with 310 additions and 15 deletions

View file

@ -3290,12 +3290,18 @@ void Navigable::resolve_all_pending_async_scroll_operations()
static bool adopt_async_viewport_scroll_delta(Navigable& navigable, CSSPixelPoint scroll_delta)
{
auto scroll_offset = navigable.viewport_scroll_offset();
scroll_offset.translate_by(scroll_delta);
if (scroll_offset == navigable.viewport_scroll_offset())
auto document = navigable.active_document();
if (!document)
return false;
navigable.perform_scroll_of_viewport_scrolling_box(scroll_offset);
return true;
auto visual_viewport = document->visual_viewport();
CSSPixelPoint page_position { CSSPixels(visual_viewport->page_left()), CSSPixels(visual_viewport->page_top()) };
auto viewport_scroll_offset = navigable.viewport_scroll_offset();
navigable.scroll_viewport_by_delta(scroll_delta);
CSSPixelPoint new_page_position { CSSPixels(visual_viewport->page_left()), CSSPixels(visual_viewport->page_top()) };
return new_page_position != page_position
|| navigable.viewport_scroll_offset() != viewport_scroll_offset;
}
void Navigable::adopt_pending_async_scroll_offsets()

View file

@ -105,7 +105,7 @@ public:
u64 version() const { return m_version; }
VisualContextIndex append(VisualContextData data, VisualContextIndex parent_index);
void set_visual_viewport_transform(TransformData);
WEB_API void set_visual_viewport_transform(TransformData);
AccumulatedVisualContextNode const& node_at(VisualContextIndex index) const { return m_nodes[index.value()]; }
ReadonlySpan<AccumulatedVisualContextNode> nodes() const { return m_nodes.span(); }

View file

@ -645,6 +645,17 @@ bool Application::handle_mouse_event_in_compositor(Web::Compositor::CompositorCo
return result.release_value();
}
bool Application::handle_pinch_event_in_compositor(Web::Compositor::CompositorContextId context_id, Web::PinchEvent const& event)
{
if (!can_send_compositor_process_ipc(m_compositor_client))
return false;
auto result = m_compositor_client->try_handle_pinch_event(context_id, event);
if (result.is_error())
return false;
return result.release_value();
}
bool Application::dispatch_mouse_event_to_web_content(Web::Compositor::CompositorContextId context_id, Web::MouseEvent const& event)
{
if (!can_send_compositor_process_ipc(m_compositor_client))

View file

@ -45,6 +45,7 @@
namespace Web {
struct MouseEvent;
struct PinchEvent;
}
@ -103,6 +104,7 @@ public:
void update_compositor_display_metadata(Web::Compositor::CompositorContextId, Optional<u64> display_id, double refresh_rate);
bool send_async_scroll_to_compositor(Web::Compositor::CompositorContextId, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels);
bool handle_mouse_event_in_compositor(Web::Compositor::CompositorContextId, Web::MouseEvent const&);
bool handle_pinch_event_in_compositor(Web::Compositor::CompositorContextId, Web::PinchEvent const&);
bool dispatch_mouse_event_to_web_content(Web::Compositor::CompositorContextId, Web::MouseEvent const&);
void notify_compositor_presented_bitmap_ready_to_paint(Web::Compositor::CompositorContextId, i32 bitmap_id);

View file

@ -524,6 +524,7 @@ void ViewImplementation::reset_zoom()
void ViewImplementation::enqueue_input_event(Web::InputEvent event)
{
auto* mouse_event = event.get_pointer<Web::MouseEvent>();
auto* pinch_event = event.get_pointer<Web::PinchEvent>();
if (mouse_event && mouse_event->type == Web::MouseEvent::Type::MouseWheel) {
mouse_event->wheel_delta_x /= zoom_level();
mouse_event->wheel_delta_y /= zoom_level();
@ -556,6 +557,15 @@ void ViewImplementation::enqueue_input_event(Web::InputEvent event)
return;
}
}
if (Application::web_content_options().enable_async_scrolling == EnableAsyncScrolling::Yes
&& m_client_state.has_usable_bitmap
&& pinch_event) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI attempting compositor pinch bypass for page {} at {},{} scale delta {}",
m_client_state.page_index, pinch_event->position.x().value(), pinch_event->position.y().value(), pinch_event->scale_delta);
auto handled = client().handle_pinch_event_in_compositor(m_client_state.page_index, *pinch_event);
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI compositor pinch bypass result for page {}: {}",
m_client_state.page_index, handled ? "accepted"sv : "rejected"sv);
}
// 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

View file

@ -312,6 +312,17 @@ bool WebContentClient::handle_mouse_event_in_compositor(u64 page_id, Web::MouseE
return handled;
}
bool WebContentClient::handle_pinch_event_in_compositor(u64 page_id, Web::PinchEvent const& event)
{
auto timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise);
auto handled = Application::the().handle_pinch_event_in_compositor(compositor_context_id_for_page(page_id), event);
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI compositor IPC pinch_event page {} returned {} in {} us",
page_id, handled, timer.elapsed_time().to_microseconds());
return handled;
}
void WebContentClient::dispatch_mouse_event_to_web_content(u64 page_id, Web::MouseEvent const& event)
{
auto context_id = compositor_context_id_for_page(page_id);

View file

@ -80,6 +80,7 @@ public:
Optional<u64> page_id_for_compositor_context_id(Web::Compositor::CompositorContextId) const;
bool send_async_scroll_to_compositor(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels);
bool handle_mouse_event_in_compositor(u64 page_id, Web::MouseEvent const&);
bool handle_pinch_event_in_compositor(u64 page_id, Web::PinchEvent const&);
void dispatch_mouse_event_to_web_content(u64 page_id, Web::MouseEvent const&);
void notify_presented_bitmap_ready_to_paint(u64 page_id, i32 bitmap_id);
void did_present_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store);

View file

@ -18,6 +18,7 @@ endpoint CompositorControlServer
handle_mouse_event(Web::Compositor::CompositorContextId context_id, Web::MouseEvent event) => (bool handled)
dispatch_mouse_event_to_web_content(Web::Compositor::CompositorContextId context_id, Web::MouseEvent event) => (bool dispatched)
handle_pinch_event(Web::Compositor::CompositorContextId context_id, Web::PinchEvent event) => (bool handled)
async_scroll_by(Web::Compositor::CompositorContextId context_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels) => (bool handled)
presented_bitmap_ready_to_paint(Web::Compositor::CompositorContextId context_id, i32 bitmap_id) =|
crash() =|

View file

@ -200,6 +200,15 @@ bool CompositorState::dispatch_mouse_event_to_web_content(Web::Compositor::Compo
return true;
}
bool CompositorState::handle_pinch_event(Web::Compositor::CompositorContextId context_id, Web::PinchEvent const& event)
{
auto* context = context_if_present(context_id);
if (!context)
return false;
return apply_context_update_result(context_id, *context, context->handle_pinch_event(event));
}
Web::Compositor::AsyncScrollEnqueueResult CompositorState::async_scroll_by(Web::Compositor::CompositorContextId context_id, Web::UniqueNodeID expected_document_id, Gfx::FloatPoint position, Gfx::FloatPoint delta, Gfx::IntRect viewport_rect, Web::Compositor::AsyncScrollOperationTracking operation_tracking)
{
if (!m_async_scrolling_enabled)

View file

@ -32,6 +32,7 @@
namespace Web {
struct MouseEvent;
struct PinchEvent;
}
@ -83,6 +84,7 @@ public:
void invalidate_wheel_event_listener_state(Web::Compositor::CompositorContextId, u64 generation);
bool handle_mouse_event(Web::Compositor::CompositorContextId, Web::MouseEvent const&);
bool dispatch_mouse_event_to_web_content(Web::Compositor::CompositorContextId, Web::MouseEvent const&);
bool handle_pinch_event(Web::Compositor::CompositorContextId, Web::PinchEvent const&);
Web::Compositor::AsyncScrollEnqueueResult async_scroll_by(Web::Compositor::CompositorContextId, Web::UniqueNodeID document_id, Gfx::FloatPoint position, Gfx::FloatPoint delta, Gfx::IntRect viewport_rect, Web::Compositor::AsyncScrollOperationTracking);
bool async_scroll_by(Web::Compositor::CompositorContextId, Gfx::FloatPoint position, Gfx::FloatPoint delta);
bool should_defer_main_thread_present_for_async_scroll(Web::Compositor::CompositorContextId) const;

View file

@ -89,6 +89,11 @@ Messages::CompositorControlServer::DispatchMouseEventToWebContentResponse Connec
return m_compositor_state->dispatch_mouse_event_to_web_content(context_id, event);
}
Messages::CompositorControlServer::HandlePinchEventResponse ConnectionFromClient::handle_pinch_event(Web::Compositor::CompositorContextId context_id, Web::PinchEvent event)
{
return m_compositor_state->handle_pinch_event(context_id, event);
}
Messages::CompositorControlServer::AsyncScrollByResponse ConnectionFromClient::async_scroll_by(Web::Compositor::CompositorContextId context_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
{
return m_compositor_state->async_scroll_by(context_id, position, delta_in_device_pixels);

View file

@ -42,6 +42,7 @@ private:
virtual void set_display_metadata(Web::Compositor::CompositorContextId, Optional<u64>, double) override;
virtual Messages::CompositorControlServer::HandleMouseEventResponse handle_mouse_event(Web::Compositor::CompositorContextId, Web::MouseEvent) override;
virtual Messages::CompositorControlServer::DispatchMouseEventToWebContentResponse dispatch_mouse_event_to_web_content(Web::Compositor::CompositorContextId, Web::MouseEvent) override;
virtual Messages::CompositorControlServer::HandlePinchEventResponse handle_pinch_event(Web::Compositor::CompositorContextId, Web::PinchEvent) override;
virtual Messages::CompositorControlServer::AsyncScrollByResponse async_scroll_by(Web::Compositor::CompositorContextId, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels) override;
virtual void presented_bitmap_ready_to_paint(Web::Compositor::CompositorContextId, i32 bitmap_id) override;
virtual void crash() override;

View file

@ -31,6 +31,52 @@ static void set_or_append_pending_scroll_offset(
pending_scroll_offsets.append(scroll_offset);
}
static Web::Painting::TransformData const& visual_viewport_transform(Web::Painting::AccumulatedVisualContextTree const& visual_context_tree)
{
auto const& visual_viewport_node = visual_context_tree.node_at(Web::Painting::VISUAL_VIEWPORT_NODE_INDEX);
auto const* transform = visual_viewport_node.data.get_pointer<Web::Painting::TransformData>();
VERIFY(transform);
return *transform;
}
static bool visual_viewport_transforms_match(Web::Painting::TransformData const& a, Web::Painting::TransformData const& b)
{
static constexpr float epsilon = 0.01f;
for (size_t row = 0; row < 4; ++row) {
for (size_t column = 0; column < 4; ++column) {
if (AK::fabs(a.matrix[row, column] - b.matrix[row, column]) > epsilon)
return false;
}
}
return AK::fabs(a.origin.x() - b.origin.x()) <= epsilon
&& AK::fabs(a.origin.y() - b.origin.y()) <= epsilon;
}
static Web::Compositor::AsyncScrollNodeStableID viewport_stable_id_from(Web::Compositor::AsyncScrollNodeID node_id)
{
return {
.node_id = node_id.document_id,
.kind = Web::Compositor::AsyncScrollNodeKind::Viewport,
};
}
static void clamp_visual_viewport_transform_to_viewport(Web::Painting::TransformData& transform, Gfx::IntRect viewport_rect)
{
auto scale = transform.matrix[0, 0];
if (scale <= 1.0f) {
transform.matrix[0, 3] = 0;
transform.matrix[1, 3] = 0;
return;
}
auto min_x = -static_cast<float>(viewport_rect.width()) * (scale - 1.0f);
auto min_y = -static_cast<float>(viewport_rect.height()) * (scale - 1.0f);
transform.matrix[0, 3] = clamp(transform.matrix[0, 3], min_x, 0.0f);
transform.matrix[1, 3] = clamp(transform.matrix[1, 3], min_y, 0.0f);
}
ContextState::ContextState(Optional<u64> page_id, CompositorStateWebContentClient& web_content_client, bool async_scrolling_enabled)
: m_web_content_client(web_content_client)
, m_page_id(page_id)
@ -134,7 +180,10 @@ void ContextState::install_display_list_update(
VERIFY(display_list->compatible_visual_context_tree_version() == visual_context_tree.version());
m_display_list = move(display_list);
m_visual_context_tree = move(visual_context_tree);
m_visual_context_tree_for_compositing.clear();
m_scroll_state_snapshot = move(scroll_state_snapshot);
if (m_async_visual_viewport_transform.has_value() && visual_viewport_transforms_match(visual_viewport_transform(*m_visual_context_tree), *m_async_visual_viewport_transform))
m_async_visual_viewport_transform.clear();
if (!m_async_scrolling_enabled)
return;
@ -150,6 +199,7 @@ void ContextState::install_display_list_update(
m_wheel_routing_admission = wheel_routing_admission;
m_can_accept_async_wheel_events = wheel_routing_admission == Web::Compositor::WheelRoutingAdmission::Accepted;
m_has_blocking_wheel_event_listeners = async_scrolling_state.has_blocking_wheel_event_listeners;
m_viewport_scrollbar_controller.set_scrollbars(async_scrolling_state.viewport_scrollbars);
m_async_scroll_tree.set_state(move(async_scrolling_state));
@ -167,6 +217,9 @@ void ContextState::update_visual_context_tree(Web::Painting::AccumulatedVisualCo
VERIFY(m_display_list);
VERIFY(m_display_list->compatible_visual_context_tree_version() == visual_context_tree.version());
m_visual_context_tree = move(visual_context_tree);
m_visual_context_tree_for_compositing.clear();
if (m_async_visual_viewport_transform.has_value() && visual_viewport_transforms_match(visual_viewport_transform(*m_visual_context_tree), *m_async_visual_viewport_transform))
m_async_visual_viewport_transform.clear();
if (m_has_async_scrolling_state)
rebuild_wheel_hit_test_targets();
@ -217,6 +270,7 @@ void ContextState::invalidate_wheel_event_listener_state(u64 generation)
m_wheel_event_listener_state_generation = max(m_wheel_event_listener_state_generation, generation);
m_wheel_routing_admission = Web::Compositor::WheelRoutingAdmission::StaleWheelEventListeners;
m_can_accept_async_wheel_events = false;
m_has_blocking_wheel_event_listeners = true;
}
ContextState::ContextUpdateResult ContextState::handle_mouse_event(Web::MouseEvent const& event)
@ -301,6 +355,52 @@ ContextState::ContextUpdateResult ContextState::handle_mouse_event(Web::MouseEve
VERIFY_NOT_REACHED();
}
ContextState::ContextUpdateResult ContextState::handle_pinch_event(Web::PinchEvent const& event)
{
if (!presents_to_client())
return {};
if (!m_can_accept_async_wheel_events)
return {};
if (m_has_blocking_wheel_event_listeners)
return {};
if (!m_visual_context_tree.has_value())
return {};
auto scale = 1.0 + event.scale_delta;
if (scale == 1.0)
return {};
auto transform = m_async_visual_viewport_transform.value_or(visual_viewport_transform(*m_visual_context_tree));
auto old_scale = transform.matrix[0, 0];
if (old_scale <= 0)
return {};
auto new_scale = clamp(static_cast<double>(old_scale) * scale, 1.0, 5.0);
auto applied_scale = static_cast<float>(new_scale / old_scale);
if (applied_scale == 1.0f)
return {};
auto position = Gfx::FloatPoint {
static_cast<float>(event.position.x().value()),
static_cast<float>(event.position.y().value()),
};
auto gesture_transform = Gfx::translation_matrix(Gfx::FloatVector3 { position.x(), position.y(), 0 })
* Gfx::scale_matrix(Gfx::FloatVector3 { applied_scale, applied_scale, 1 })
* Gfx::translation_matrix(Gfx::FloatVector3 { -position.x(), -position.y(), 0 });
transform.matrix = gesture_transform * transform.matrix;
clamp_visual_viewport_transform_to_viewport(transform, m_async_scrolling_viewport_rect);
m_async_visual_viewport_transform = transform;
m_visual_context_tree_for_compositing.clear();
rebuild_wheel_hit_test_targets();
return {
.accepted = true,
.frame_to_present = m_async_scrolling_viewport_rect,
.should_request_rendering_update = false,
};
}
ContextState::AsyncScrollResult ContextState::async_scroll_by(
Web::UniqueNodeID expected_document_id,
Gfx::FloatPoint position,
@ -347,17 +447,49 @@ ContextState::ContextUpdateResult ContextState::async_scroll_by(Gfx::FloatPoint
if (!m_can_accept_async_wheel_events)
return {};
auto scroll_target = m_async_scroll_tree.hit_test_scroll_node_for_wheel(position, delta);
if (scroll_target.blocked_by_main_thread_region || scroll_target.blocked_by_wheel_event_region || !scroll_target.node_id.has_value())
auto initial_scroll_target = m_async_scroll_tree.hit_test_scroll_node_for_wheel(position, delta);
if (initial_scroll_target.blocked_by_main_thread_region || initial_scroll_target.blocked_by_wheel_event_region)
return {};
Optional<Gfx::IntRect> frame_to_present;
auto remaining_delta = delta;
if (auto visual_viewport_scroll_delta = apply_visual_viewport_scroll_delta(delta); visual_viewport_scroll_delta.has_value()) {
Vector<Web::Compositor::AsyncScrollOffset> scroll_offsets;
scroll_offsets.append(visual_viewport_scroll_delta->scroll_offset);
store_pending_async_scroll_offsets(scroll_offsets);
remaining_delta.translate_by(-visual_viewport_scroll_delta->consumed_delta.x(), -visual_viewport_scroll_delta->consumed_delta.y());
frame_to_present = m_async_scrolling_viewport_rect;
}
if (remaining_delta.is_zero())
return {
.accepted = true,
.frame_to_present = frame_to_present,
.should_request_rendering_update = frame_to_present.has_value(),
};
auto async_scroll_delta = remaining_delta;
if (auto scale = visual_viewport_scale_for_compositing(); scale.has_value() && *scale > 1.0f)
async_scroll_delta.scale_by(1.0f / *scale);
auto scroll_target = m_async_scroll_tree.hit_test_scroll_node_for_wheel(position, async_scroll_delta);
if (scroll_target.blocked_by_main_thread_region || scroll_target.blocked_by_wheel_event_region || !scroll_target.node_id.has_value()) {
if (frame_to_present.has_value())
return {
.accepted = true,
.frame_to_present = frame_to_present,
.should_request_rendering_update = true,
};
return {};
}
auto async_scroll_viewport_rect = m_async_scrolling_viewport_rect;
auto scroll_offsets = m_async_scroll_tree.apply_scroll_delta(*scroll_target.node_id, delta, m_scroll_state_snapshot);
auto scroll_offsets = m_async_scroll_tree.apply_scroll_delta(*scroll_target.node_id, async_scroll_delta, m_scroll_state_snapshot);
if (scroll_offsets.is_empty())
return {
.accepted = true,
.frame_to_present = {},
.should_request_rendering_update = false,
.frame_to_present = frame_to_present,
.should_request_rendering_update = frame_to_present.has_value(),
};
rebuild_wheel_hit_test_targets();
@ -599,6 +731,64 @@ Optional<Gfx::FloatPoint> ContextState::viewport_scroll_offset_from(Vector<Web::
return viewport_scroll_offset;
}
Optional<float> ContextState::visual_viewport_scale_for_compositing() const
{
if (!m_visual_context_tree.has_value())
return {};
if (m_async_visual_viewport_transform.has_value())
return m_async_visual_viewport_transform->matrix[0, 0];
return visual_viewport_transform(*m_visual_context_tree).matrix[0, 0];
}
Optional<ContextState::VisualViewportScrollDelta> ContextState::apply_visual_viewport_scroll_delta(Gfx::FloatPoint delta)
{
if (delta.is_zero())
return {};
if (!m_visual_context_tree.has_value())
return {};
auto viewport_node_id = m_async_scroll_tree.viewport_scroll_node_id();
if (!viewport_node_id.has_value())
return {};
auto viewport_scroll_offset = m_async_scroll_tree.scroll_offset_for_node(*viewport_node_id, m_scroll_state_snapshot);
if (!viewport_scroll_offset.has_value())
return {};
auto transform = m_async_visual_viewport_transform.value_or(visual_viewport_transform(*m_visual_context_tree));
auto scale = transform.matrix[0, 0];
if (scale <= 1.0f)
return {};
auto min_x = -static_cast<float>(m_async_scrolling_viewport_rect.width()) * (scale - 1.0f);
auto min_y = -static_cast<float>(m_async_scrolling_viewport_rect.height()) * (scale - 1.0f);
auto old_x = transform.matrix[0, 3];
auto old_y = transform.matrix[1, 3];
auto new_x = clamp(old_x - delta.x(), min_x, 0.0f);
auto new_y = clamp(old_y - delta.y(), min_y, 0.0f);
Gfx::FloatPoint consumed_delta { old_x - new_x, old_y - new_y };
if (consumed_delta.is_zero())
return {};
transform.matrix[0, 3] = new_x;
transform.matrix[1, 3] = new_y;
m_async_visual_viewport_transform = transform;
m_visual_context_tree_for_compositing.clear();
rebuild_wheel_hit_test_targets();
return VisualViewportScrollDelta {
.scroll_offset = {
.stable_node_id = viewport_stable_id_from(*viewport_node_id),
.compositor_scroll_offset = *viewport_scroll_offset,
.unadopted_scroll_delta = consumed_delta.scaled(1.0f / scale),
},
.consumed_delta = consumed_delta,
};
}
Optional<Gfx::FloatPoint> ContextState::reapply_pending_async_scroll_offsets(Vector<Web::Compositor::AsyncScrollOffset> const& pending_scroll_offsets)
{
Optional<Gfx::FloatPoint> viewport_scroll_offset;
@ -653,7 +843,7 @@ void ContextState::rebuild_wheel_hit_test_targets()
VERIFY(m_display_list);
m_async_scroll_tree.rebuild_wheel_hit_test_targets(
m_display_list,
&current_visual_context_tree(),
&visual_context_tree_for_compositing(),
m_scroll_state_snapshot);
}
@ -668,10 +858,20 @@ bool ContextState::can_render_frame() const
return m_display_list && m_backing_store_manager.is_valid();
}
Web::Painting::AccumulatedVisualContextTree const& ContextState::visual_context_tree_for_compositing() const
{
if (!m_async_visual_viewport_transform.has_value())
return current_visual_context_tree();
m_visual_context_tree_for_compositing = current_visual_context_tree();
m_visual_context_tree_for_compositing->set_visual_viewport_transform(*m_async_visual_viewport_transform);
return *m_visual_context_tree_for_compositing;
}
void ContextState::paint_current_display_list(Web::Painting::DisplayListPlayerSkia& display_list_player, Gfx::PaintingSurface& surface)
{
VERIFY(m_display_list);
display_list_player.execute(*m_display_list, current_visual_context_tree(), m_display_list_resource_storage, m_scroll_state_snapshot, surface);
display_list_player.execute(*m_display_list, visual_context_tree_for_compositing(), m_display_list_resource_storage, m_scroll_state_snapshot, surface);
m_viewport_scrollbar_controller.paint(surface, display_list_player, m_scroll_state_snapshot);
}

View file

@ -40,6 +40,7 @@ class SkiaBackendContext;
namespace Web {
struct MouseEvent;
struct PinchEvent;
}
@ -121,6 +122,7 @@ public:
void invalidate_wheel_event_listener_state(u64 generation);
ContextUpdateResult handle_mouse_event(Web::MouseEvent const&);
ContextUpdateResult handle_pinch_event(Web::PinchEvent const&);
AsyncScrollResult async_scroll_by(
Web::UniqueNodeID document_id,
Gfx::FloatPoint position,
@ -157,15 +159,23 @@ public:
void did_finish_gpu_present(i32 bitmap_id);
private:
struct VisualViewportScrollDelta {
Web::Compositor::AsyncScrollOffset scroll_offset;
Gfx::FloatPoint consumed_delta;
};
void stop_backing_store_shrink_timer();
Web::Painting::AccumulatedVisualContextTree const& current_visual_context_tree() const;
Optional<Gfx::FloatPoint> viewport_scroll_offset_from(Vector<Web::Compositor::AsyncScrollOffset> const&) const;
Optional<float> visual_viewport_scale_for_compositing() const;
Optional<VisualViewportScrollDelta> apply_visual_viewport_scroll_delta(Gfx::FloatPoint);
Optional<Gfx::FloatPoint> reapply_pending_async_scroll_offsets(Vector<Web::Compositor::AsyncScrollOffset> const&);
void store_pending_async_scroll_offsets(Vector<Web::Compositor::AsyncScrollOffset> const&, Optional<Web::Compositor::AsyncScrollOperationID> = {});
Optional<Gfx::IntRect> apply_viewport_scrollbar_drag(ViewportScrollbarController::Drag const&);
void rebuild_wheel_hit_test_targets();
bool is_present_blocked() const;
bool can_render_frame() const;
Web::Painting::AccumulatedVisualContextTree const& visual_context_tree_for_compositing() const;
void paint_current_display_list(Web::Painting::DisplayListPlayerSkia&, Gfx::PaintingSurface&);
CompositorStateWebContentClient& m_web_content_client;
@ -178,6 +188,7 @@ private:
RefPtr<Web::Painting::DisplayList const> m_display_list;
Optional<Web::Painting::AccumulatedVisualContextTree> m_visual_context_tree;
mutable Optional<Web::Painting::AccumulatedVisualContextTree> m_visual_context_tree_for_compositing;
Web::Painting::DisplayListResourceStorage m_display_list_resource_storage;
Web::Painting::ScrollStateSnapshot m_scroll_state_snapshot;
BackingStoreManager m_backing_store_manager;
@ -191,8 +202,10 @@ private:
Gfx::IntRect m_async_scrolling_viewport_rect;
bool m_has_async_scrolling_state { false };
bool m_can_accept_async_wheel_events { false };
bool m_has_blocking_wheel_event_listeners { false };
u64 m_wheel_event_listener_state_generation { 0 };
Web::Compositor::WheelRoutingAdmission m_wheel_routing_admission { Web::Compositor::WheelRoutingAdmission::NoAsyncScrollingState };
Optional<Web::Painting::TransformData> m_async_visual_viewport_transform;
Gfx::IntSize m_viewport_size;
Web::Compositor::WindowResizingInProgress m_window_resize_in_progress { Web::Compositor::WindowResizingInProgress::No };

View file

@ -369,8 +369,12 @@ void ConnectionFromClient::mouse_event(u64 page_id, Web::MouseEvent event)
return nullptr;
if (auto const* mouse_event = m_input_event_queue.tail().event.get_pointer<Web::MouseEvent>()) {
if (mouse_event->type == event.type)
return mouse_event;
if (mouse_event->type != event.type)
return nullptr;
if (event.type == Web::MouseEvent::Type::MouseWheel
&& mouse_event->async_scroll_performed_default_action != event.async_scroll_performed_default_action)
return nullptr;
return mouse_event;
}
return nullptr;
@ -397,6 +401,25 @@ void ConnectionFromClient::drag_event(u64 page_id, Web::DragEvent event)
void ConnectionFromClient::pinch_event(u64 page_id, Web::PinchEvent event)
{
auto page = m_page_host->page(page_id);
if (!page.has_value()) {
async_did_finish_handling_input_event(page_id, Web::EventResult::Dropped);
return;
}
// OPTIMIZATION: Coalesce consecutive unprocessed pinch events. Pinch scale
// deltas are multiplicative, so preserve the combined scale change.
if (!m_input_event_queue.is_empty() && m_input_event_queue.tail().page_id == page_id) {
if (auto const* pinch_event = m_input_event_queue.tail().event.get_pointer<Web::PinchEvent>()) {
event.scale_delta = (1.0 + pinch_event->scale_delta) * (1.0 + event.scale_delta) - 1.0;
m_input_event_queue.tail().event = move(event);
++m_input_event_queue.tail().coalesced_event_count;
page->page().client().request_frame();
return;
}
}
enqueue_input_event({ page_id, move(event), 0 });
}