layout: Add a hit test item that covers all scroll frame contents (#34347)

When building scroll frames, add a special
`StackingContextContent::Fragment` type for a hit test that covers all
scroll frame contents. This makes it so that you don't have to be
hovering over actual content to scroll the scroll frame.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
Martin Robinson 2024-11-24 20:04:57 +01:00 committed by GitHub
parent c60e4afbee
commit c11e0e8e70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 104 additions and 55 deletions

View file

@ -33,7 +33,7 @@ use style::values::specified::text::TextDecorationLine;
use style::values::specified::ui::CursorKind; use style::values::specified::ui::CursorKind;
use style::Zero; use style::Zero;
use style_traits::{CSSPixel, DevicePixel}; use style_traits::{CSSPixel, DevicePixel};
use webrender_api::units::{LayoutPixel, LayoutSize}; use webrender_api::units::{LayoutPixel, LayoutRect, LayoutSize};
use webrender_api::{ use webrender_api::{
self as wr, units, BorderDetails, BoxShadowClipMode, ClipChainId, CommonItemProperties, self as wr, units, BorderDetails, BoxShadowClipMode, ClipChainId, CommonItemProperties,
ImageRendering, NinePatchBorder, NinePatchBorderSource, ImageRendering, NinePatchBorder, NinePatchBorderSource,
@ -250,14 +250,20 @@ impl Fragment {
builder: &mut DisplayListBuilder, builder: &mut DisplayListBuilder,
containing_block: &PhysicalRect<Au>, containing_block: &PhysicalRect<Au>,
section: StackingContextSection, section: StackingContextSection,
is_hit_test_for_scrollable_overflow: bool,
) { ) {
match self { match self {
Fragment::Box(b) | Fragment::Float(b) => match b.style.get_inherited_box().visibility { Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
Visibility::Visible => { match box_fragment.style.get_inherited_box().visibility {
BuilderForBoxFragment::new(b, containing_block).build(builder, section) Visibility::Visible => BuilderForBoxFragment::new(
}, box_fragment,
Visibility::Hidden => (), containing_block,
Visibility::Collapse => (), is_hit_test_for_scrollable_overflow,
)
.build(builder, section),
Visibility::Hidden => (),
Visibility::Collapse => (),
}
}, },
Fragment::AbsoluteOrFixedPositioned(_) => {}, Fragment::AbsoluteOrFixedPositioned(_) => {},
Fragment::Positioning(positioning_fragment) => { Fragment::Positioning(positioning_fragment) => {
@ -508,10 +514,15 @@ struct BuilderForBoxFragment<'a> {
border_edge_clip_chain_id: RefCell<Option<ClipChainId>>, border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>, padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
content_edge_clip_chain_id: RefCell<Option<ClipChainId>>, content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
is_hit_test_for_scrollable_overflow: bool,
} }
impl<'a> BuilderForBoxFragment<'a> { impl<'a> BuilderForBoxFragment<'a> {
fn new(fragment: &'a BoxFragment, containing_block: &'a PhysicalRect<Au>) -> Self { fn new(
fragment: &'a BoxFragment,
containing_block: &'a PhysicalRect<Au>,
is_hit_test_for_scrollable_overflow: bool,
) -> Self {
let border_rect = fragment let border_rect = fragment
.border_rect() .border_rect()
.translate(containing_block.origin.to_vector()); .translate(containing_block.origin.to_vector());
@ -550,6 +561,7 @@ impl<'a> BuilderForBoxFragment<'a> {
border_edge_clip_chain_id: RefCell::new(None), border_edge_clip_chain_id: RefCell::new(None),
padding_edge_clip_chain_id: RefCell::new(None), padding_edge_clip_chain_id: RefCell::new(None),
content_edge_clip_chain_id: RefCell::new(None), content_edge_clip_chain_id: RefCell::new(None),
is_hit_test_for_scrollable_overflow,
} }
} }
@ -635,25 +647,31 @@ impl<'a> BuilderForBoxFragment<'a> {
} }
fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) { fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
if self.is_hit_test_for_scrollable_overflow {
self.build_hit_test(builder, self.fragment.scrollable_overflow().to_webrender());
return;
}
if section == StackingContextSection::Outline { if section == StackingContextSection::Outline {
self.build_outline(builder); self.build_outline(builder);
} else { return;
self.build_hit_test(builder);
if self
.fragment
.base
.flags
.contains(FragmentFlags::DO_NOT_PAINT)
{
return;
}
self.build_background(builder);
self.build_box_shadow(builder);
self.build_border(builder);
} }
self.build_hit_test(builder, self.border_rect);
if self
.fragment
.base
.flags
.contains(FragmentFlags::DO_NOT_PAINT)
{
return;
}
self.build_background(builder);
self.build_box_shadow(builder);
self.build_border(builder);
} }
fn build_hit_test(&self, builder: &mut DisplayListBuilder) { fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) {
let hit_info = builder.hit_info( let hit_info = builder.hit_info(
&self.fragment.style, &self.fragment.style,
self.fragment.base.tag, self.fragment.base.tag,
@ -664,7 +682,7 @@ impl<'a> BuilderForBoxFragment<'a> {
None => return, None => return,
}; };
let mut common = builder.common_properties(self.border_rect, &self.fragment.style); let mut common = builder.common_properties(rect, &self.fragment.style);
if let Some(clip_chain_id) = self.border_edge_clip(builder, false) { if let Some(clip_chain_id) = self.border_edge_clip(builder, false) {
common.clip_chain_id = clip_chain_id; common.clip_chain_id = clip_chain_id;
} }

View file

@ -261,6 +261,7 @@ pub(crate) enum StackingContextContent {
section: StackingContextSection, section: StackingContextSection,
containing_block: PhysicalRect<Au>, containing_block: PhysicalRect<Au>,
fragment: ArcRefCell<Fragment>, fragment: ArcRefCell<Fragment>,
is_hit_test_for_scrollable_overflow: bool,
}, },
/// An index into [StackingContext::atomic_inline_stacking_containers]. /// An index into [StackingContext::atomic_inline_stacking_containers].
@ -290,13 +291,17 @@ impl StackingContextContent {
section, section,
containing_block, containing_block,
fragment, fragment,
is_hit_test_for_scrollable_overflow,
} => { } => {
builder.current_scroll_node_id = *scroll_node_id; builder.current_scroll_node_id = *scroll_node_id;
builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id; builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id;
builder.current_clip_chain_id = *clip_chain_id; builder.current_clip_chain_id = *clip_chain_id;
fragment fragment.borrow().build_display_list(
.borrow() builder,
.build_display_list(builder, containing_block, *section); containing_block,
*section,
*is_hit_test_for_scrollable_overflow,
);
}, },
Self::AtomicInlineStackingContainer { index } => { Self::AtomicInlineStackingContainer { index } => {
inline_stacking_containers[*index].build_display_list(builder); inline_stacking_containers[*index].build_display_list(builder);
@ -666,7 +671,8 @@ impl StackingContext {
} }
} }
let mut fragment_builder = BuilderForBoxFragment::new(box_fragment, containing_block); let mut fragment_builder =
BuilderForBoxFragment::new(box_fragment, containing_block, false);
let painter = super::background::BackgroundPainter { let painter = super::background::BackgroundPainter {
style, style,
painting_area_override: Some(painting_area), painting_area_override: Some(painting_area),
@ -804,10 +810,10 @@ impl StackingContext {
match field { match field {
DebugPrintField::Contents => match self.contents[*index] { DebugPrintField::Contents => match self.contents[*index] {
StackingContextContent::Fragment { section, .. } => { StackingContextContent::Fragment { section, .. } => {
tree.add_item(format!("{:?}", section)); tree.add_item(format!("{section:?}"));
}, },
StackingContextContent::AtomicInlineStackingContainer { index } => { StackingContextContent::AtomicInlineStackingContainer { index } => {
tree.new_level(format!("AtomicInlineStackingContainer #{}", index)); tree.new_level(format!("AtomicInlineStackingContainer #{index}"));
self.atomic_inline_stacking_containers[index].debug_print_with_tree(tree); self.atomic_inline_stacking_containers[index].debug_print_with_tree(tree);
tree.end_level(); tree.end_level();
}, },
@ -912,6 +918,7 @@ impl Fragment {
clip_chain_id: containing_block.clip_chain_id, clip_chain_id: containing_block.clip_chain_id,
containing_block: containing_block.rect, containing_block: containing_block.rect,
fragment: fragment_ref.clone(), fragment: fragment_ref.clone(),
is_hit_test_for_scrollable_overflow: false,
}); });
}, },
} }
@ -923,6 +930,11 @@ struct ReferenceFrameData {
transform: LayoutTransform, transform: LayoutTransform,
kind: wr::ReferenceFrameKind, kind: wr::ReferenceFrameKind,
} }
struct ScrollFrameData {
scroll_tree_node_id: ScrollTreeNodeId,
clip_chain_id: wr::ClipChainId,
scroll_frame_rect: LayoutRect,
}
impl BoxFragment { impl BoxFragment {
fn get_stacking_context_type(&self) -> Option<StackingContextType> { fn get_stacking_context_type(&self) -> Option<StackingContextType> {
@ -1080,7 +1092,7 @@ impl BoxFragment {
display_list, display_list,
&containing_block.scroll_node_id, &containing_block.scroll_node_id,
&containing_block.clip_chain_id, &containing_block.clip_chain_id,
BuilderForBoxFragment::new(self, &containing_block.rect), BuilderForBoxFragment::new(self, &containing_block.rect, false),
) )
.unwrap_or(containing_block.clip_chain_id); .unwrap_or(containing_block.clip_chain_id);
@ -1152,7 +1164,7 @@ impl BoxFragment {
display_list, display_list,
&new_scroll_node_id, &new_scroll_node_id,
&new_clip_chain_id, &new_clip_chain_id,
BuilderForBoxFragment::new(self, &containing_block.rect), BuilderForBoxFragment::new(self, &containing_block.rect, false),
) { ) {
new_clip_chain_id = clip_chain_id; new_clip_chain_id = clip_chain_id;
} }
@ -1181,6 +1193,7 @@ impl BoxFragment {
section: self.get_stacking_context_section(), section: self.get_stacking_context_section(),
containing_block: containing_block.rect, containing_block: containing_block.rect,
fragment: fragment.clone(), fragment: fragment.clone(),
is_hit_test_for_scrollable_overflow: false,
}); });
if !self.style.get_outline().outline_width.is_zero() { if !self.style.get_outline().outline_width.is_zero() {
@ -1193,22 +1206,33 @@ impl BoxFragment {
section: StackingContextSection::Outline, section: StackingContextSection::Outline,
containing_block: containing_block.rect, containing_block: containing_block.rect,
fragment: fragment.clone(), fragment: fragment.clone(),
is_hit_test_for_scrollable_overflow: false,
}); });
} }
// We want to build the scroll frame after the background and border, because // We want to build the scroll frame after the background and border, because
// they shouldn't scroll with the rest of the box content. // they shouldn't scroll with the rest of the box content.
if let Some((scroll_node_id, clip_chain_id, scroll_frame_size)) = self if let Some(scroll_frame_data) = self.build_scroll_frame_if_necessary(
.build_scroll_frame_if_necessary( display_list,
display_list, &new_scroll_node_id,
&new_scroll_node_id, &new_clip_chain_id,
&new_clip_chain_id, &containing_block.rect,
&containing_block.rect, ) {
) new_scroll_node_id = scroll_frame_data.scroll_tree_node_id;
{ new_clip_chain_id = scroll_frame_data.clip_chain_id;
new_scroll_node_id = scroll_node_id; new_scroll_frame_size = Some(scroll_frame_data.scroll_frame_rect.size());
new_clip_chain_id = clip_chain_id;
new_scroll_frame_size = Some(scroll_frame_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_chain_id: new_clip_chain_id,
section: self.get_stacking_context_section(),
containing_block: containing_block.rect,
fragment: fragment.clone(),
is_hit_test_for_scrollable_overflow: true,
});
} }
let padding_rect = self let padding_rect = self
@ -1297,8 +1321,7 @@ impl BoxFragment {
parent_scroll_node_id: &ScrollTreeNodeId, parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_id: &wr::ClipChainId, parent_clip_id: &wr::ClipChainId,
containing_block_rect: &PhysicalRect<Au>, containing_block_rect: &PhysicalRect<Au>,
) -> Option<(ScrollTreeNodeId, wr::ClipChainId, LayoutSize)> { ) -> Option<ScrollFrameData> {
let overflow = self.style.effective_overflow();
if !self.style.establishes_scroll_container() { if !self.style.establishes_scroll_container() {
return None; return None;
} }
@ -1327,6 +1350,7 @@ impl BoxFragment {
display_list.wr.pipeline_id, display_list.wr.pipeline_id,
); );
let overflow = self.style.effective_overflow();
let sensitivity = let sensitivity =
if ComputedOverflow::Hidden == overflow.x && ComputedOverflow::Hidden == overflow.y { if ComputedOverflow::Hidden == overflow.x && ComputedOverflow::Hidden == overflow.y {
ScrollSensitivity::Script ScrollSensitivity::Script
@ -1334,21 +1358,26 @@ impl BoxFragment {
ScrollSensitivity::ScriptAndInputEvents ScrollSensitivity::ScriptAndInputEvents
}; };
let padding_rect = self let scroll_frame_rect = self
.padding_rect() .padding_rect()
.translate(containing_block_rect.origin.to_vector()) .translate(containing_block_rect.origin.to_vector())
.to_webrender(); .to_webrender();
let content_rect = self.scrollable_overflow().to_webrender();
let (scroll_tree_node_id, clip_chain_id) = display_list.define_scroll_frame( let (scroll_tree_node_id, clip_chain_id) = display_list.define_scroll_frame(
parent_scroll_node_id, parent_scroll_node_id,
parent_clip_id, parent_clip_id,
external_id, external_id,
self.scrollable_overflow().to_webrender(), content_rect,
padding_rect, scroll_frame_rect,
sensitivity, sensitivity,
); );
Some((scroll_tree_node_id, clip_chain_id, padding_rect.size())) Some(ScrollFrameData {
scroll_tree_node_id,
clip_chain_id,
scroll_frame_rect,
})
} }
fn build_sticky_frame_if_necessary( fn build_sticky_frame_if_necessary(

View file

@ -21,7 +21,7 @@ pub enum ScrollSensitivity {
/// Information that Servo keeps alongside WebRender display items /// Information that Servo keeps alongside WebRender display items
/// in order to add more context to hit test results. /// in order to add more context to hit test results.
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct HitTestInfo { pub struct HitTestInfo {
/// The id of the node of this hit test item. /// The id of the node of this hit test item.
pub node: u64, pub node: u64,
@ -327,17 +327,19 @@ impl CompositorDisplayListInfo {
cursor: Option<Cursor>, cursor: Option<Cursor>,
scroll_tree_node: ScrollTreeNodeId, scroll_tree_node: ScrollTreeNodeId,
) -> usize { ) -> usize {
let hit_test_info = HitTestInfo {
node,
cursor,
scroll_tree_node,
};
if let Some(last) = self.hit_test_info.last() { if let Some(last) = self.hit_test_info.last() {
if node == last.node && cursor == last.cursor { if hit_test_info == *last {
return self.hit_test_info.len() - 1; return self.hit_test_info.len() - 1;
} }
} }
self.hit_test_info.push(HitTestInfo { self.hit_test_info.push(hit_test_info);
node,
cursor,
scroll_tree_node,
});
self.hit_test_info.len() - 1 self.hit_test_info.len() - 1
} }
} }