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:
parent
5378c389aa
commit
a11c281dc3
15 changed files with 310 additions and 15 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() =|
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
¤t_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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue