compositor/layout: Rely on layout for fine-grained input event hit testing (#38480)

Before, the compositor was responsible for doing the hit testing during
input events within a page. This change moves that hit testing to
layout.  With this change, epoch mismatches are no longer a bit deal and
we can simply ignore them, as the Constellation and Script will take
care of ignoring hit tests against scroll nodes and browsing contexts
that no longer exist. This means that hit testing retry support can be
removed.

Add the concept of a Script `HitTest` that transforms the coarse-grained
renderer hit test into one that hit tests against the actual layout
items.

Testing: Currently we do not have good tests for verifying the behavior
of
input events, but WebDriver tests should cover this.
Fixes: This is part of #37932.
Fixes: #26608.
Fixes: #25282.
Fixes: #38090.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Co-authored-by: kongbai1996 <1782765876@qq.com>
This commit is contained in:
Martin Robinson 2025-08-07 10:38:43 +02:00 committed by GitHub
parent c0cc8484f8
commit ad805e3110
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 348 additions and 511 deletions

View file

@ -7,13 +7,12 @@ use std::cell::Cell;
use base::id::ScrollTreeNodeId;
use compositing_traits::display_list::{
AxesScrollSensitivity, ScrollTree, ScrollType, ScrollableNodeInfo, SpatialTreeNodeInfo,
StickyNodeInfo,
};
use euclid::{SideOffsets2D, Size2D};
use euclid::Size2D;
use webrender_api::units::LayoutVector2D;
use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation, StickyOffsetBounds};
use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation};
fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId {
fn add_mock_scroll_node(tree: &mut ScrollTree) -> (ScrollTreeNodeId, ExternalScrollId) {
let pipeline_id = PipelineId(0, 0);
let num_nodes = tree.nodes.len();
let parent = if num_nodes > 0 {
@ -24,10 +23,11 @@ fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId {
None
};
tree.add_scroll_tree_node(
let external_id = ExternalScrollId(num_nodes as u64, pipeline_id);
let scroll_node_id = tree.add_scroll_tree_node(
parent.as_ref(),
SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
external_id: ExternalScrollId(num_nodes as u64, pipeline_id),
external_id,
content_rect: Size2D::new(200.0, 200.0).into(),
clip_rect: Size2D::new(100.0, 100.0).into(),
scroll_sensitivity: AxesScrollSensitivity {
@ -37,42 +37,42 @@ fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId {
offset: LayoutVector2D::zero(),
offset_changed: Cell::new(false),
}),
)
);
(scroll_node_id, external_id)
}
#[test]
fn test_scroll_tree_simple_scroll() {
let mut scroll_tree = ScrollTree::default();
let pipeline_id = PipelineId(0, 0);
let id = add_mock_scroll_node(&mut scroll_tree);
let (id, external_id) = add_mock_scroll_node(&mut scroll_tree);
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(
&id,
&external_id,
ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0)),
ScrollType::Script,
)
.unwrap();
let expected_offset = LayoutVector2D::new(20.0, 40.0);
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
assert_eq!(scrolled_id, external_id);
assert_eq!(offset, expected_offset);
assert_eq!(scroll_tree.get_node(&id).offset(), Some(expected_offset));
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(
&id,
&external_id,
ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)),
ScrollType::Script,
)
.unwrap();
let expected_offset = LayoutVector2D::new(0.0, 0.0);
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
assert_eq!(scrolled_id, external_id);
assert_eq!(offset, expected_offset);
assert_eq!(scroll_tree.get_node(&id).offset(), Some(expected_offset));
// Scroll offsets must be positive.
let result = scroll_tree.scroll_node_or_ancestor(
&id,
&external_id,
ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)),
ScrollType::Script,
);
@ -88,26 +88,33 @@ fn test_scroll_tree_simple_scroll_chaining() {
let mut scroll_tree = ScrollTree::default();
let pipeline_id = PipelineId(0, 0);
let parent_id = add_mock_scroll_node(&mut scroll_tree);
let (parent_id, parent_external_id) = add_mock_scroll_node(&mut scroll_tree);
let unscrollable_external_id = ExternalScrollId(100 as u64, pipeline_id);
let unscrollable_child_id = scroll_tree.add_scroll_tree_node(
Some(&parent_id),
SpatialTreeNodeInfo::Sticky(StickyNodeInfo {
frame_rect: Size2D::new(100.0, 100.0).into(),
margins: SideOffsets2D::default(),
vertical_offset_bounds: StickyOffsetBounds::new(0.0, 0.0),
horizontal_offset_bounds: StickyOffsetBounds::new(0.0, 0.0),
SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
external_id: unscrollable_external_id,
content_rect: Size2D::new(100.0, 100.0).into(),
clip_rect: Size2D::new(100.0, 100.0).into(),
scroll_sensitivity: AxesScrollSensitivity {
x: ScrollType::Script | ScrollType::InputEvents,
y: ScrollType::Script | ScrollType::InputEvents,
},
offset: LayoutVector2D::zero(),
offset_changed: Cell::new(false),
}),
);
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(
&unscrollable_child_id,
&unscrollable_external_id,
ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0)),
ScrollType::Script,
)
.unwrap();
let expected_offset = LayoutVector2D::new(20.0, 40.0);
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
assert_eq!(scrolled_id, parent_external_id);
assert_eq!(offset, expected_offset);
assert_eq!(
scroll_tree.get_node(&parent_id).offset(),
@ -116,35 +123,37 @@ fn test_scroll_tree_simple_scroll_chaining() {
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(
&unscrollable_child_id,
&unscrollable_external_id,
ScrollLocation::Delta(LayoutVector2D::new(10.0, 15.0)),
ScrollType::Script,
)
.unwrap();
let expected_offset = LayoutVector2D::new(30.0, 55.0);
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
assert_eq!(scrolled_id, parent_external_id);
assert_eq!(offset, expected_offset);
assert_eq!(
scroll_tree.get_node(&parent_id).offset(),
Some(expected_offset)
);
assert_eq!(scroll_tree.get_node(&unscrollable_child_id).offset(), None);
assert_eq!(
scroll_tree.get_node(&unscrollable_child_id).offset(),
Some(LayoutVector2D::zero())
);
}
#[test]
fn test_scroll_tree_chain_when_at_extent() {
let mut scroll_tree = ScrollTree::default();
let pipeline_id = PipelineId(0, 0);
let parent_id = add_mock_scroll_node(&mut scroll_tree);
let child_id = add_mock_scroll_node(&mut scroll_tree);
let (parent_id, parent_external_id) = add_mock_scroll_node(&mut scroll_tree);
let (child_id, child_external_id) = add_mock_scroll_node(&mut scroll_tree);
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(&child_id, ScrollLocation::End, ScrollType::Script)
.scroll_node_or_ancestor(&child_external_id, ScrollLocation::End, ScrollType::Script)
.unwrap();
let expected_offset = LayoutVector2D::new(0.0, 100.0);
assert_eq!(scrolled_id, ExternalScrollId(1, pipeline_id));
assert_eq!(scrolled_id, child_external_id);
assert_eq!(offset, expected_offset);
assert_eq!(
scroll_tree.get_node(&child_id).offset(),
@ -155,13 +164,13 @@ fn test_scroll_tree_chain_when_at_extent() {
// of its scroll area in the y axis.
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(
&child_id,
&child_external_id,
ScrollLocation::Delta(LayoutVector2D::new(0.0, 10.0)),
ScrollType::Script,
)
.unwrap();
let expected_offset = LayoutVector2D::new(0.0, 10.0);
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
assert_eq!(scrolled_id, parent_external_id);
assert_eq!(offset, expected_offset);
assert_eq!(
scroll_tree.get_node(&parent_id).offset(),
@ -175,9 +184,8 @@ fn test_scroll_tree_chain_through_overflow_hidden() {
// Create a tree with a scrollable leaf, but make its `scroll_sensitivity`
// reflect `overflow: hidden` ie not responsive to non-script scroll events.
let pipeline_id = PipelineId(0, 0);
let parent_id = add_mock_scroll_node(&mut scroll_tree);
let overflow_hidden_id = add_mock_scroll_node(&mut scroll_tree);
let (parent_id, parent_external_id) = add_mock_scroll_node(&mut scroll_tree);
let (overflow_hidden_id, overflow_hidden_external_id) = add_mock_scroll_node(&mut scroll_tree);
let node = scroll_tree.get_node_mut(&overflow_hidden_id);
if let SpatialTreeNodeInfo::Scroll(ref mut scroll_node_info) = node.info {
@ -189,13 +197,13 @@ fn test_scroll_tree_chain_through_overflow_hidden() {
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(
&overflow_hidden_id,
&overflow_hidden_external_id,
ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0)),
ScrollType::InputEvents,
)
.unwrap();
let expected_offset = LayoutVector2D::new(20.0, 40.0);
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
assert_eq!(scrolled_id, parent_external_id);
assert_eq!(offset, expected_offset);
assert_eq!(
scroll_tree.get_node(&parent_id).offset(),