mirror of
https://github.com/servo/servo.git
synced 2025-09-08 14:08:22 +01:00
layout: Move hit testing for scrollable areas to display list construction (#39066)
Move the construction of hit test items for scroll nodes to the display list construction stage. This way they respect the `z-index` of their originating fragments and stacking context ordering in general. Testing: We currently do not have great testing for this as this tests the combination of hit testing of input events and scrolling at that point. The completion of WebDriver support should make this easier to test. Fixes: #38967 Signed-off-by: coding-joedow <ibluegalaxy_taoj@163.com> Co-authored-by: kongbai1996 <1782765876@qq.com>
This commit is contained in:
parent
802fdd9068
commit
f8c0746c44
2 changed files with 68 additions and 72 deletions
|
@ -200,7 +200,18 @@ impl DisplayListBuilder<'_> {
|
|||
builder.add_clip_to_display_list(clip);
|
||||
}
|
||||
|
||||
builder.push_hit_tests_for_scrollable_areas(&stacking_context_tree.hit_test_items);
|
||||
// Add a single hit test that covers the entire viewport, so that WebRender knows
|
||||
// which pipeline it hits when doing hit testing.
|
||||
let pipeline_id = builder.compositor_info.pipeline_id;
|
||||
let viewport_size = builder.compositor_info.viewport_details.size;
|
||||
let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
|
||||
builder.wr().push_hit_test(
|
||||
viewport_rect,
|
||||
ClipChainId::INVALID,
|
||||
SpatialId::root_reference_frame(pipeline_id),
|
||||
PrimitiveFlags::default(),
|
||||
(0, 0), /* tag */
|
||||
);
|
||||
|
||||
// Paint the canvas’ background (if any) before/under everything else
|
||||
stacking_context_tree
|
||||
|
@ -311,39 +322,6 @@ impl DisplayListBuilder<'_> {
|
|||
self.compositor_info.scroll_tree = scroll_tree;
|
||||
}
|
||||
|
||||
fn push_hit_tests_for_scrollable_areas(
|
||||
&mut self,
|
||||
scroll_frame_hit_test_items: &[ScrollFrameHitTestItem],
|
||||
) {
|
||||
// Add a single hit test that covers the entire viewport, so that WebRender knows
|
||||
// which pipeline it hits when doing hit testing.
|
||||
let pipeline_id = self.compositor_info.pipeline_id;
|
||||
let viewport_size = self.compositor_info.viewport_details.size;
|
||||
let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
|
||||
self.wr().push_hit_test(
|
||||
viewport_rect,
|
||||
ClipChainId::INVALID,
|
||||
SpatialId::root_reference_frame(pipeline_id),
|
||||
PrimitiveFlags::default(),
|
||||
(0, 0), /* tag */
|
||||
);
|
||||
|
||||
for item in scroll_frame_hit_test_items {
|
||||
let spatial_id = self
|
||||
.compositor_info
|
||||
.scroll_tree
|
||||
.webrender_id(&item.scroll_node_id);
|
||||
let clip_chain_id = self.clip_chain_id(item.clip_id);
|
||||
self.wr().push_hit_test(
|
||||
item.rect,
|
||||
clip_chain_id,
|
||||
spatial_id,
|
||||
PrimitiveFlags::default(),
|
||||
(item.external_scroll_id.0, 0), /* tag */
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the given [`Clip`] to the WebRender display list and create a mapping from
|
||||
/// its [`ClipId`] to a WebRender [`ClipChainId`]. This happens:
|
||||
/// - When WebRender display list construction starts: All clips created during the
|
||||
|
@ -583,6 +561,7 @@ impl Fragment {
|
|||
builder: &mut DisplayListBuilder,
|
||||
containing_block: &PhysicalRect<Au>,
|
||||
section: StackingContextSection,
|
||||
is_hit_test_for_scrollable_overflow: bool,
|
||||
is_collapsed_table_borders: bool,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
|
@ -606,6 +585,7 @@ impl Fragment {
|
|||
Visibility::Visible => BuilderForBoxFragment::new(
|
||||
box_fragment,
|
||||
containing_block,
|
||||
is_hit_test_for_scrollable_overflow,
|
||||
is_collapsed_table_borders,
|
||||
)
|
||||
.build(builder, section),
|
||||
|
@ -937,6 +917,7 @@ struct BuilderForBoxFragment<'a> {
|
|||
border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
|
||||
padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
|
||||
content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
|
||||
is_hit_test_for_scrollable_overflow: bool,
|
||||
is_collapsed_table_borders: bool,
|
||||
}
|
||||
|
||||
|
@ -944,6 +925,7 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
fn new(
|
||||
fragment: &'a BoxFragment,
|
||||
containing_block: &'a PhysicalRect<Au>,
|
||||
is_hit_test_for_scrollable_overflow: bool,
|
||||
is_collapsed_table_borders: bool,
|
||||
) -> Self {
|
||||
let border_rect = fragment
|
||||
|
@ -960,6 +942,7 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
border_edge_clip_chain_id: RefCell::new(None),
|
||||
padding_edge_clip_chain_id: RefCell::new(None),
|
||||
content_edge_clip_chain_id: RefCell::new(None),
|
||||
is_hit_test_for_scrollable_overflow,
|
||||
is_collapsed_table_borders,
|
||||
}
|
||||
}
|
||||
|
@ -1042,6 +1025,13 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
}
|
||||
|
||||
fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
|
||||
if self.is_hit_test_for_scrollable_overflow &&
|
||||
self.fragment.style.get_inherited_ui().pointer_events !=
|
||||
style::computed_values::pointer_events::T::None
|
||||
{
|
||||
self.build_hit_test(builder, self.fragment.scrollable_overflow().to_webrender());
|
||||
return;
|
||||
}
|
||||
if self.is_collapsed_table_borders {
|
||||
self.build_collapsed_table_borders(builder);
|
||||
return;
|
||||
|
@ -1066,6 +1056,24 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
self.build_border(builder);
|
||||
}
|
||||
|
||||
fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) {
|
||||
let external_scroll_node_id = builder
|
||||
.compositor_info
|
||||
.external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id);
|
||||
|
||||
let mut common = builder.common_properties(rect, &self.fragment.style);
|
||||
if let Some(clip_chain_id) = self.border_edge_clip(builder, false) {
|
||||
common.clip_chain_id = clip_chain_id;
|
||||
}
|
||||
builder.wr().push_hit_test(
|
||||
common.clip_rect,
|
||||
common.clip_chain_id,
|
||||
common.spatial_id,
|
||||
common.flags,
|
||||
(external_scroll_node_id.0, 0), /* tag */
|
||||
);
|
||||
}
|
||||
|
||||
fn build_background_for_painter(
|
||||
&mut self,
|
||||
builder: &mut DisplayListBuilder,
|
||||
|
|
|
@ -34,7 +34,7 @@ use style::values::generics::box_::Perspective;
|
|||
use style::values::generics::transform::{self, GenericRotate, GenericScale, GenericTranslate};
|
||||
use style::values::specified::box_::DisplayOutside;
|
||||
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D};
|
||||
use webrender_api::{self as wr, BorderRadius, ExternalScrollId};
|
||||
use webrender_api::{self as wr, BorderRadius};
|
||||
use wr::StickyOffsetBounds;
|
||||
use wr::units::{LayoutPixel, LayoutSize};
|
||||
|
||||
|
@ -101,23 +101,6 @@ pub(crate) enum StackingContextSection {
|
|||
Outline,
|
||||
}
|
||||
|
||||
#[derive(MallocSizeOf)]
|
||||
pub(crate) struct ScrollFrameHitTestItem {
|
||||
/// The [`ScrollTreeNodeId`] of the spatial node that contains this hit test item.
|
||||
pub scroll_node_id: ScrollTreeNodeId,
|
||||
|
||||
/// The [`ClipId`] of the clip that clips this [`ScrollFrameHitTestItems`].
|
||||
pub clip_id: ClipId,
|
||||
|
||||
/// The rectangle of the scroll frame in the coordinate space of [`Self::scroll_node_id`].
|
||||
pub rect: LayoutRect,
|
||||
|
||||
/// The WebRender [`ExternalScrollId`] of the scrolling spatial node that
|
||||
/// this [`ScrollFrameHitTestItem`] identifies. Note that this is a *different*
|
||||
/// spatial node than the one identified by [`Self::scroll_node_id`] (the parent).
|
||||
pub external_scroll_id: ExternalScrollId,
|
||||
}
|
||||
|
||||
#[derive(MallocSizeOf)]
|
||||
pub(crate) struct StackingContextTree {
|
||||
/// The root stacking context of this [`StackingContextTree`].
|
||||
|
@ -133,10 +116,6 @@ pub(crate) struct StackingContextTree {
|
|||
/// for things like `overflow`. More clips may be created later during WebRender
|
||||
/// display list construction, but they are never added here.
|
||||
pub clip_store: StackingContextTreeClipStore,
|
||||
|
||||
/// A vector of hit test items, one per scroll frame. These are used for allowing
|
||||
/// renderer-side scrolling in the Servo renderer.
|
||||
pub hit_test_items: Vec<ScrollFrameHitTestItem>,
|
||||
}
|
||||
|
||||
impl StackingContextTree {
|
||||
|
@ -197,7 +176,6 @@ impl StackingContextTree {
|
|||
root_stacking_context: StackingContext::create_root(root_scroll_node_id, debug),
|
||||
compositor_info,
|
||||
clip_store: Default::default(),
|
||||
hit_test_items: Vec::new(),
|
||||
};
|
||||
|
||||
let mut root_stacking_context = StackingContext::create_root(root_scroll_node_id, debug);
|
||||
|
@ -306,6 +284,7 @@ pub(crate) enum StackingContextContent {
|
|||
section: StackingContextSection,
|
||||
containing_block: PhysicalRect<Au>,
|
||||
fragment: Fragment,
|
||||
is_hit_test_for_scrollable_overflow: bool,
|
||||
is_collapsed_table_borders: bool,
|
||||
#[conditional_malloc_size_of]
|
||||
text_decorations: Arc<Vec<FragmentTextDecoration>>,
|
||||
|
@ -338,6 +317,7 @@ impl StackingContextContent {
|
|||
section,
|
||||
containing_block,
|
||||
fragment,
|
||||
is_hit_test_for_scrollable_overflow,
|
||||
is_collapsed_table_borders,
|
||||
text_decorations,
|
||||
} => {
|
||||
|
@ -348,6 +328,7 @@ impl StackingContextContent {
|
|||
builder,
|
||||
containing_block,
|
||||
*section,
|
||||
*is_hit_test_for_scrollable_overflow,
|
||||
*is_collapsed_table_borders,
|
||||
text_decorations,
|
||||
);
|
||||
|
@ -664,6 +645,7 @@ impl StackingContext {
|
|||
let mut fragment_builder = BuilderForBoxFragment::new(
|
||||
&root_fragment,
|
||||
&fragment_tree.initial_containing_block,
|
||||
false, /* is_hit_test_for_scrollable_overflow */
|
||||
false, /* is_collapsed_table_borders */
|
||||
);
|
||||
let painter = super::background::BackgroundPainter {
|
||||
|
@ -918,6 +900,7 @@ impl Fragment {
|
|||
clip_id: containing_block.clip_id,
|
||||
containing_block: containing_block.rect,
|
||||
fragment: fragment_clone,
|
||||
is_hit_test_for_scrollable_overflow: false,
|
||||
is_collapsed_table_borders: false,
|
||||
text_decorations: text_decorations.clone(),
|
||||
});
|
||||
|
@ -1121,6 +1104,7 @@ impl BoxFragment {
|
|||
BuilderForBoxFragment::new(
|
||||
self,
|
||||
&containing_block.rect,
|
||||
false, /* is_hit_test_for_scrollable_overflow */
|
||||
false, /* is_collapsed_table_borders */
|
||||
),
|
||||
)
|
||||
|
@ -1204,6 +1188,7 @@ impl BoxFragment {
|
|||
BuilderForBoxFragment::new(
|
||||
self,
|
||||
&containing_block.rect,
|
||||
false, /* is_hit_test_for_scrollable_overflow */
|
||||
false, /* is_collapsed_table_borders */
|
||||
),
|
||||
) {
|
||||
|
@ -1236,6 +1221,7 @@ impl BoxFragment {
|
|||
section,
|
||||
containing_block: containing_block.rect,
|
||||
fragment: fragment.clone(),
|
||||
is_hit_test_for_scrollable_overflow: false,
|
||||
is_collapsed_table_borders: false,
|
||||
text_decorations: text_decorations.clone(),
|
||||
});
|
||||
|
@ -1264,6 +1250,20 @@ impl BoxFragment {
|
|||
if let Some(scroll_frame_data) = overflow_frame_data.scroll_frame_data {
|
||||
new_scroll_node_id = scroll_frame_data.scroll_tree_node_id;
|
||||
new_scroll_frame_size = Some(scroll_frame_data.scroll_frame_rect.size());
|
||||
stacking_context
|
||||
.contents
|
||||
.push(StackingContextContent::Fragment {
|
||||
scroll_node_id: new_scroll_node_id,
|
||||
reference_frame_scroll_node_id:
|
||||
reference_frame_scroll_node_id_for_fragments,
|
||||
clip_id: new_clip_id,
|
||||
section,
|
||||
containing_block: containing_block.rect,
|
||||
fragment: fragment.clone(),
|
||||
is_hit_test_for_scrollable_overflow: true,
|
||||
is_collapsed_table_borders: false,
|
||||
text_decorations: text_decorations.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1360,6 +1360,7 @@ impl BoxFragment {
|
|||
section,
|
||||
containing_block: containing_block.rect,
|
||||
fragment: fragment.clone(),
|
||||
is_hit_test_for_scrollable_overflow: false,
|
||||
is_collapsed_table_borders: true,
|
||||
text_decorations: text_decorations.clone(),
|
||||
});
|
||||
|
@ -1431,7 +1432,7 @@ impl BoxFragment {
|
|||
// https://drafts.csswg.org/css-overflow-3/#corner-clipping
|
||||
let radii;
|
||||
if overflow.x == ComputedOverflow::Clip && overflow.y == ComputedOverflow::Clip {
|
||||
let builder = BuilderForBoxFragment::new(self, containing_block_rect, false);
|
||||
let builder = BuilderForBoxFragment::new(self, containing_block_rect, false, false);
|
||||
radii = offset_radii(builder.border_radius, clip_margin);
|
||||
} else if overflow.x != ComputedOverflow::Clip {
|
||||
overflow_clip_rect.min.x = f32::MIN;
|
||||
|
@ -1462,7 +1463,7 @@ impl BoxFragment {
|
|||
.to_webrender();
|
||||
|
||||
let clip_id = stacking_context_tree.clip_store.add(
|
||||
BuilderForBoxFragment::new(self, containing_block_rect, false).border_radius,
|
||||
BuilderForBoxFragment::new(self, containing_block_rect, false, false).border_radius,
|
||||
scroll_frame_rect,
|
||||
*parent_scroll_node_id,
|
||||
parent_clip_id,
|
||||
|
@ -1487,19 +1488,6 @@ impl BoxFragment {
|
|||
sensitivity,
|
||||
);
|
||||
|
||||
use style::computed_values::pointer_events::T as PointerEvents;
|
||||
|
||||
if self.style.get_inherited_ui().pointer_events != PointerEvents::None {
|
||||
stacking_context_tree
|
||||
.hit_test_items
|
||||
.push(ScrollFrameHitTestItem {
|
||||
scroll_node_id: *parent_scroll_node_id,
|
||||
clip_id,
|
||||
rect: scroll_frame_rect,
|
||||
external_scroll_id,
|
||||
});
|
||||
}
|
||||
|
||||
Some(OverflowFrameData {
|
||||
clip_id,
|
||||
scroll_frame_data: Some(ScrollFrameData {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue