mirror of
https://github.com/servo/servo.git
synced 2025-08-12 17:05:33 +01:00
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:
parent
c0cc8484f8
commit
ad805e3110
19 changed files with 348 additions and 511 deletions
|
@ -6,12 +6,13 @@ use std::collections::HashMap;
|
|||
|
||||
use app_units::Au;
|
||||
use base::id::ScrollTreeNodeId;
|
||||
use euclid::{Box2D, Point2D, Point3D};
|
||||
use euclid::{Box2D, Point2D, Point3D, Vector2D};
|
||||
use kurbo::{Ellipse, Shape};
|
||||
use layout_api::{ElementsFromPointFlags, ElementsFromPointResult};
|
||||
use style::computed_values::backface_visibility::T as BackfaceVisibility;
|
||||
use style::computed_values::pointer_events::T as PointerEvents;
|
||||
use style::computed_values::visibility::T as Visibility;
|
||||
use style::properties::ComputedValues;
|
||||
use webrender_api::BorderRadius;
|
||||
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, RectExt};
|
||||
|
||||
|
@ -20,7 +21,7 @@ use crate::display_list::stacking_context::StackingContextSection;
|
|||
use crate::display_list::{
|
||||
StackingContext, StackingContextContent, StackingContextTree, ToWebRender,
|
||||
};
|
||||
use crate::fragment_tree::{BoxFragment, Fragment};
|
||||
use crate::fragment_tree::Fragment;
|
||||
use crate::geom::PhysicalRect;
|
||||
|
||||
pub(crate) struct HitTest<'a> {
|
||||
|
@ -125,7 +126,7 @@ impl<'a> HitTest<'a> {
|
|||
|
||||
impl Clip {
|
||||
fn contains(&self, point: LayoutPoint) -> bool {
|
||||
rounded_rect_contains_point(self.rect, || self.radii, point)
|
||||
rounded_rect_contains_point(self.rect, &self.radii, point)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,22 +246,10 @@ impl Fragment {
|
|||
return false;
|
||||
};
|
||||
|
||||
let point_in_target;
|
||||
let transform;
|
||||
let hit = match self {
|
||||
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
|
||||
(point_in_target, transform) =
|
||||
match hit_test.location_in_spatial_node(spatial_node_id) {
|
||||
Some(point) => point,
|
||||
None => return false,
|
||||
};
|
||||
box_fragment
|
||||
.borrow()
|
||||
.hit_test(point_in_target, containing_block, &transform)
|
||||
},
|
||||
Fragment::Text(text) => {
|
||||
let text = &*text.borrow();
|
||||
let style = text.inline_styles.style.borrow();
|
||||
let mut hit_test_fragment_inner =
|
||||
|style: &ComputedValues,
|
||||
fragment_rect: PhysicalRect<Au>,
|
||||
border_radius: BorderRadius| {
|
||||
if style.get_inherited_ui().pointer_events == PointerEvents::None {
|
||||
return false;
|
||||
}
|
||||
|
@ -268,7 +257,7 @@ impl Fragment {
|
|||
return false;
|
||||
}
|
||||
|
||||
(point_in_target, transform) =
|
||||
let (point_in_spatial_node, transform) =
|
||||
match hit_test.location_in_spatial_node(spatial_node_id) {
|
||||
Some(point) => point,
|
||||
None => return false,
|
||||
|
@ -280,68 +269,59 @@ impl Fragment {
|
|||
return false;
|
||||
}
|
||||
|
||||
text.rect
|
||||
.translate(containing_block.origin.to_vector())
|
||||
.to_webrender()
|
||||
.contains(point_in_target)
|
||||
let fragment_rect = fragment_rect.translate(containing_block.origin.to_vector());
|
||||
if !rounded_rect_contains_point(
|
||||
fragment_rect.to_webrender(),
|
||||
&border_radius,
|
||||
point_in_spatial_node,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let point_in_target = point_in_spatial_node.cast_unit() -
|
||||
Vector2D::new(
|
||||
fragment_rect.origin.x.to_f32_px(),
|
||||
fragment_rect.origin.y.to_f32_px(),
|
||||
);
|
||||
hit_test.results.push(ElementsFromPointResult {
|
||||
node: tag.node,
|
||||
point_in_target,
|
||||
});
|
||||
|
||||
!hit_test.flags.contains(ElementsFromPointFlags::FindAll)
|
||||
};
|
||||
|
||||
match self {
|
||||
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
|
||||
let box_fragment = box_fragment.borrow();
|
||||
hit_test_fragment_inner(
|
||||
&box_fragment.style,
|
||||
box_fragment.border_rect(),
|
||||
box_fragment.border_radius(),
|
||||
)
|
||||
},
|
||||
Fragment::AbsoluteOrFixedPositioned(_) |
|
||||
Fragment::IFrame(_) |
|
||||
Fragment::Image(_) |
|
||||
Fragment::Positioning(_) => return false,
|
||||
};
|
||||
|
||||
if !hit {
|
||||
return false;
|
||||
Fragment::Text(text) => {
|
||||
let text = &*text.borrow();
|
||||
hit_test_fragment_inner(
|
||||
&text.inline_styles.style.borrow(),
|
||||
text.rect,
|
||||
BorderRadius::zero(),
|
||||
)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
|
||||
hit_test.results.push(ElementsFromPointResult {
|
||||
node: tag.node,
|
||||
point_in_target,
|
||||
});
|
||||
|
||||
!hit_test.flags.contains(ElementsFromPointFlags::FindAll)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxFragment {
|
||||
fn hit_test(
|
||||
&self,
|
||||
point_in_fragment: LayoutPoint,
|
||||
containing_block: &PhysicalRect<Au>,
|
||||
transform: &LayoutTransform,
|
||||
) -> bool {
|
||||
if self.style.get_inherited_ui().pointer_events == PointerEvents::None {
|
||||
return false;
|
||||
}
|
||||
if self.style.get_inherited_box().visibility != Visibility::Visible {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.style.get_box().backface_visibility == BackfaceVisibility::Hidden &&
|
||||
transform.is_backface_visible()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let border_rect = self
|
||||
.border_rect()
|
||||
.translate(containing_block.origin.to_vector())
|
||||
.to_webrender();
|
||||
rounded_rect_contains_point(border_rect, || self.border_radius(), point_in_fragment)
|
||||
}
|
||||
}
|
||||
|
||||
fn rounded_rect_contains_point(
|
||||
rect: LayoutRect,
|
||||
border_radius: impl FnOnce() -> BorderRadius,
|
||||
border_radius: &BorderRadius,
|
||||
point: LayoutPoint,
|
||||
) -> bool {
|
||||
if !rect.contains(point) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let border_radius = border_radius();
|
||||
if border_radius.is_zero() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ use std::cell::{OnceCell, RefCell};
|
|||
use std::sync::Arc;
|
||||
|
||||
use app_units::{AU_PER_PX, Au};
|
||||
use base::WebRenderEpochToU16;
|
||||
use base::id::ScrollTreeNodeId;
|
||||
use clip::{Clip, ClipId};
|
||||
use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo};
|
||||
|
@ -72,9 +71,6 @@ use background::BackgroundPainter;
|
|||
pub(crate) use hit_test::HitTest;
|
||||
pub(crate) use stacking_context::*;
|
||||
|
||||
// webrender's `ItemTag` is private.
|
||||
type ItemTag = (u64, u16);
|
||||
type HitInfo = Option<ItemTag>;
|
||||
const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
|
||||
|
||||
pub(crate) struct DisplayListBuilder<'a> {
|
||||
|
@ -168,8 +164,6 @@ impl DisplayListBuilder<'_> {
|
|||
) -> BuiltDisplayList {
|
||||
// Build the rest of the display list which inclues all of the WebRender primitives.
|
||||
let compositor_info = &mut stacking_context_tree.compositor_info;
|
||||
compositor_info.hit_test_info.clear();
|
||||
|
||||
let mut webrender_display_list_builder =
|
||||
webrender_api::DisplayListBuilder::new(compositor_info.pipeline_id);
|
||||
webrender_display_list_builder.begin();
|
||||
|
@ -396,27 +390,6 @@ impl DisplayListBuilder<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn hit_info(
|
||||
&mut self,
|
||||
style: &ComputedValues,
|
||||
tag: Option<Tag>,
|
||||
auto_cursor: Cursor,
|
||||
) -> HitInfo {
|
||||
use style::computed_values::pointer_events::T as PointerEvents;
|
||||
|
||||
let inherited_ui = style.get_inherited_ui();
|
||||
if inherited_ui.pointer_events == PointerEvents::None {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hit_test_index = self.compositor_info.add_hit_test_info(
|
||||
tag?.node.0 as u64,
|
||||
Some(cursor(inherited_ui.cursor.keyword, auto_cursor)),
|
||||
self.current_scroll_node_id,
|
||||
);
|
||||
Some((hit_test_index as u64, self.compositor_info.epoch.as_u16()))
|
||||
}
|
||||
|
||||
/// Draw highlights around the node that is currently hovered in the devtools.
|
||||
fn paint_dom_inspector_highlight(&mut self) {
|
||||
let Some(highlight) = self
|
||||
|
@ -616,7 +589,6 @@ impl Fragment {
|
|||
self.maybe_push_hit_test_for_style_and_tag(
|
||||
builder,
|
||||
&positioning_fragment.style,
|
||||
positioning_fragment.base.tag,
|
||||
rect,
|
||||
Cursor::Default,
|
||||
);
|
||||
|
@ -706,24 +678,20 @@ impl Fragment {
|
|||
&self,
|
||||
builder: &mut DisplayListBuilder,
|
||||
style: &ComputedValues,
|
||||
tag: Option<Tag>,
|
||||
rect: PhysicalRect<Au>,
|
||||
cursor: Cursor,
|
||||
) {
|
||||
let hit_info = builder.hit_info(style, tag, cursor);
|
||||
let hit_info = match hit_info {
|
||||
Some(hit_info) => hit_info,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
|
||||
let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
|
||||
let external_scroll_id = builder
|
||||
.compositor_info
|
||||
.external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id);
|
||||
builder.wr().push_hit_test(
|
||||
rect.to_webrender(),
|
||||
clip_chain_id,
|
||||
spatial_id,
|
||||
style.get_webrender_primitive_flags(),
|
||||
hit_info,
|
||||
(external_scroll_id.0, cursor as u16), /* tag */
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -756,13 +724,7 @@ impl Fragment {
|
|||
}
|
||||
|
||||
let parent_style = fragment.inline_styles.style.borrow();
|
||||
self.maybe_push_hit_test_for_style_and_tag(
|
||||
builder,
|
||||
&parent_style,
|
||||
fragment.base.tag,
|
||||
rect,
|
||||
Cursor::Text,
|
||||
);
|
||||
self.maybe_push_hit_test_for_style_and_tag(builder, &parent_style, rect, Cursor::Text);
|
||||
|
||||
let color = parent_style.clone_color();
|
||||
let font_metrics = &fragment.font_metrics;
|
||||
|
@ -1116,15 +1078,13 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
}
|
||||
|
||||
fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) {
|
||||
let hit_info = builder.hit_info(
|
||||
&self.fragment.style,
|
||||
self.fragment.base.tag,
|
||||
let cursor = cursor(
|
||||
self.fragment.style.get_inherited_ui().cursor.keyword,
|
||||
Cursor::Default,
|
||||
);
|
||||
let hit_info = match hit_info {
|
||||
Some(hit_info) => hit_info,
|
||||
None => return,
|
||||
};
|
||||
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) {
|
||||
|
@ -1135,7 +1095,7 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
common.clip_chain_id,
|
||||
common.spatial_id,
|
||||
common.flags,
|
||||
hit_info,
|
||||
(external_scroll_node_id.0, cursor as u16), /* tag */
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1902,17 +1862,25 @@ pub(super) fn compute_margin_box_radius(
|
|||
|
||||
impl BoxFragment {
|
||||
fn border_radius(&self) -> BorderRadius {
|
||||
let resolve =
|
||||
|radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
|
||||
let border = self.style.get_border();
|
||||
if border.border_top_left_radius.0.is_zero() &&
|
||||
border.border_top_right_radius.0.is_zero() &&
|
||||
border.border_bottom_right_radius.0.is_zero() &&
|
||||
border.border_bottom_left_radius.0.is_zero()
|
||||
{
|
||||
return BorderRadius::zero();
|
||||
}
|
||||
|
||||
let border_rect = self.border_rect();
|
||||
let resolve =
|
||||
|radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
|
||||
let corner = |corner: &style::values::computed::BorderCornerRadius| {
|
||||
Size2D::new(
|
||||
resolve(&corner.0.width.0, border_rect.size.width),
|
||||
resolve(&corner.0.height.0, border_rect.size.height),
|
||||
)
|
||||
};
|
||||
let border = self.style.get_border();
|
||||
|
||||
let mut radius = wr::BorderRadius {
|
||||
top_left: corner(&border.border_top_left_radius),
|
||||
top_right: corner(&border.border_top_right_radius),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue