mirror of
https://github.com/servo/servo.git
synced 2025-08-07 22:45:34 +01:00
First hit testing stuff
This commit is contained in:
parent
b18f5a1b89
commit
3537ddd95c
8 changed files with 497 additions and 82 deletions
|
@ -16,7 +16,7 @@ use super::{BuilderForBoxFragment, compute_margin_box_radius, normalize_radii};
|
|||
|
||||
/// An identifier for a clip used during StackingContextTree construction. This is a simple index in
|
||||
/// a [`ClipStore`]s vector of clips.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub(crate) struct ClipId(pub usize);
|
||||
|
||||
impl ClipId {
|
||||
|
@ -43,6 +43,10 @@ pub(crate) struct Clip {
|
|||
pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>);
|
||||
|
||||
impl StackingContextTreeClipStore {
|
||||
pub(super) fn get(&self, clip_id: ClipId) -> &Clip {
|
||||
&self.0[clip_id.0]
|
||||
}
|
||||
|
||||
pub(crate) fn add(
|
||||
&mut self,
|
||||
radii: webrender_api::BorderRadius,
|
||||
|
|
354
components/layout/display_list/hit_test.rs
Normal file
354
components/layout/display_list/hit_test.rs
Normal file
|
@ -0,0 +1,354 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use app_units::Au;
|
||||
use base::id::ScrollTreeNodeId;
|
||||
use layout_api::{ElementsFromPointFlags, ElementsFromPointResult};
|
||||
use style::computed_values::pointer_events::T as PointerEvents;
|
||||
use style::computed_values::visibility::T as Visibility;
|
||||
use webrender_api::units::LayoutPoint;
|
||||
|
||||
use crate::display_list::clip::{Clip, ClipId};
|
||||
use crate::display_list::stacking_context::StackingContextSection;
|
||||
use crate::display_list::{
|
||||
StackingContext, StackingContextContent, StackingContextTree, ToWebRender,
|
||||
};
|
||||
use crate::fragment_tree::{BoxFragment, Fragment};
|
||||
use crate::geom::PhysicalRect;
|
||||
|
||||
pub(crate) struct HitTest<'a> {
|
||||
/// The flags which describe how to perform this [`HitTest`].
|
||||
flags: ElementsFromPointFlags,
|
||||
/// The point to test for this hit test, relative to the page.
|
||||
point_to_test: LayoutPoint,
|
||||
/// The stacking context tree against which to perform the hit test.
|
||||
stacking_context_tree: &'a StackingContextTree,
|
||||
/// The resulting [`HitTestResultItems`] for this hit test.
|
||||
results: Vec<ElementsFromPointResult>,
|
||||
/// A cache of hit test results for shared clip nodes.
|
||||
clip_hit_test_results: HashMap<ClipId, bool>,
|
||||
}
|
||||
|
||||
impl<'a> HitTest<'a> {
|
||||
pub(crate) fn run(
|
||||
stacking_context_tree: &'a StackingContextTree,
|
||||
point_to_test: LayoutPoint,
|
||||
flags: ElementsFromPointFlags,
|
||||
) -> Vec<ElementsFromPointResult> {
|
||||
let mut hit_test = Self {
|
||||
flags,
|
||||
point_to_test,
|
||||
stacking_context_tree,
|
||||
results: Vec::new(),
|
||||
clip_hit_test_results: HashMap::new(),
|
||||
};
|
||||
stacking_context_tree
|
||||
.root_stacking_context
|
||||
.hit_test(&mut hit_test);
|
||||
hit_test.results
|
||||
}
|
||||
|
||||
/// Perform a hit test against a the clip node for the given [`ClipId`], returning
|
||||
/// true if it is not clipped out or false if is clipped out.
|
||||
fn hit_test_clip_id(&mut self, clip_id: ClipId) -> bool {
|
||||
if clip_id == ClipId::INVALID {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(result) = self.clip_hit_test_results.get(&clip_id) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
let clip = self.stacking_context_tree.clip_store.get(clip_id);
|
||||
let result = self
|
||||
.location_in_spatial_node(clip.parent_scroll_node_id)
|
||||
.is_some_and(|point| {
|
||||
clip.contains(point) && self.hit_test_clip_id(clip.parent_clip_id)
|
||||
});
|
||||
self.clip_hit_test_results.insert(clip_id, result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Get the hit test location in the coordinate system of the given spatial node,
|
||||
/// returning `None` if the transformation is uninvertible or the point cannot be
|
||||
/// projected into the spatial node.
|
||||
fn location_in_spatial_node(
|
||||
&self,
|
||||
scroll_tree_node_id: ScrollTreeNodeId,
|
||||
) -> Option<LayoutPoint> {
|
||||
self.stacking_context_tree
|
||||
.compositor_info
|
||||
.scroll_tree
|
||||
.cumulative_root_to_node_transform(&scroll_tree_node_id)
|
||||
.and_then(|transform| transform.transform_point2d(self.point_to_test))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clip {
|
||||
fn contains(&self, point: LayoutPoint) -> bool {
|
||||
// TODO: Handle rounded rectangles.
|
||||
self.rect.contains(point)
|
||||
}
|
||||
}
|
||||
|
||||
impl StackingContext {
|
||||
/// Perform a hit test against a [`StackingContext`]. Note that this is the reverse
|
||||
/// of the stacking context walk algorithm in `stacking_context.rs`. Any changes made
|
||||
/// here should be reflected in the forward version in that file.
|
||||
fn hit_test(&self, hit_test: &mut HitTest) -> bool {
|
||||
let mut contents = self.contents.iter().rev().peekable();
|
||||
|
||||
// Step 10: Outlines
|
||||
while contents
|
||||
.peek()
|
||||
.is_some_and(|child| child.section() == StackingContextSection::Outline)
|
||||
{
|
||||
// The hit test will not hit the outline.
|
||||
let _ = contents.next().unwrap();
|
||||
}
|
||||
|
||||
// Steps 8 and 9: Stacking contexts with non-negative ‘z-index’, and
|
||||
// positioned stacking containers (where ‘z-index’ is auto)
|
||||
let mut real_stacking_contexts_and_positioned_stacking_containers = self
|
||||
.real_stacking_contexts_and_positioned_stacking_containers
|
||||
.iter()
|
||||
.rev()
|
||||
.peekable();
|
||||
while real_stacking_contexts_and_positioned_stacking_containers
|
||||
.peek()
|
||||
.is_some_and(|child| child.z_index() >= 0)
|
||||
{
|
||||
let child = real_stacking_contexts_and_positioned_stacking_containers
|
||||
.next()
|
||||
.unwrap();
|
||||
if child.hit_test(hit_test) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 7 and 8: Fragments and inline stacking containers
|
||||
while contents
|
||||
.peek()
|
||||
.is_some_and(|child| child.section() == StackingContextSection::Foreground)
|
||||
{
|
||||
let child = contents.next().unwrap();
|
||||
if self.hit_test_content(child, hit_test) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Float stacking containers
|
||||
for child in self.float_stacking_containers.iter() {
|
||||
if child.hit_test(hit_test) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Block backgrounds and borders
|
||||
while contents.peek().is_some_and(|child| {
|
||||
child.section() == StackingContextSection::DescendantBackgroundsAndBorders
|
||||
}) {
|
||||
let child = contents.next().unwrap();
|
||||
if self.hit_test_content(child, hit_test) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Stacking contexts with negative ‘z-index’
|
||||
for child in real_stacking_contexts_and_positioned_stacking_containers {
|
||||
if child.hit_test(hit_test) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 2 and 3: Borders and background for the root
|
||||
while contents.peek().is_some_and(|child| {
|
||||
child.section() == StackingContextSection::OwnBackgroundsAndBorders
|
||||
}) {
|
||||
let child = contents.next().unwrap();
|
||||
if self.hit_test_content(child, hit_test) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn hit_test_content(
|
||||
&self,
|
||||
content: &StackingContextContent,
|
||||
hit_test: &mut HitTest<'_>,
|
||||
) -> bool {
|
||||
match content {
|
||||
StackingContextContent::Fragment {
|
||||
scroll_node_id,
|
||||
clip_id,
|
||||
containing_block,
|
||||
fragment,
|
||||
..
|
||||
} => {
|
||||
hit_test.hit_test_clip_id(*clip_id) &&
|
||||
fragment.hit_test(hit_test, *scroll_node_id, containing_block)
|
||||
},
|
||||
StackingContextContent::AtomicInlineStackingContainer { index } => {
|
||||
self.atomic_inline_stacking_containers[*index].hit_test(hit_test)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fragment {
|
||||
pub(crate) fn hit_test(
|
||||
&self,
|
||||
hit_test: &mut HitTest,
|
||||
spatial_node_id: ScrollTreeNodeId,
|
||||
containing_block: &PhysicalRect<Au>,
|
||||
) -> bool {
|
||||
let Some(tag) = self.tag() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let point_in_target;
|
||||
let hit = match self {
|
||||
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
|
||||
point_in_target = 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)
|
||||
},
|
||||
Fragment::Text(text) => {
|
||||
let text = &*text.borrow();
|
||||
let visibilty = text
|
||||
.inline_styles
|
||||
.style
|
||||
.borrow()
|
||||
.get_inherited_box()
|
||||
.visibility;
|
||||
|
||||
if visibilty != Visibility::Visible {
|
||||
return false;
|
||||
}
|
||||
|
||||
point_in_target = match hit_test.location_in_spatial_node(spatial_node_id) {
|
||||
Some(point) => point,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
text.rect
|
||||
.translate(containing_block.origin.to_vector())
|
||||
.to_webrender()
|
||||
.contains(point_in_target)
|
||||
},
|
||||
Fragment::AbsoluteOrFixedPositioned(_) |
|
||||
Fragment::IFrame(_) |
|
||||
Fragment::Image(_) |
|
||||
Fragment::Positioning(_) => return false,
|
||||
};
|
||||
|
||||
if !hit {
|
||||
return 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>,
|
||||
) -> bool {
|
||||
if self.style.get_inherited_ui().pointer_events == PointerEvents::None {
|
||||
return false;
|
||||
}
|
||||
let border_rect = self
|
||||
.border_rect()
|
||||
.translate(containing_block.origin.to_vector())
|
||||
.to_webrender();
|
||||
border_rect.contains(point_in_fragment)
|
||||
|
||||
// TODO: Rounded rectangles.
|
||||
}
|
||||
}
|
||||
|
||||
//pub(crate) fn hit_test_fragment_in_rect(
|
||||
// &self,
|
||||
// hit_test_location: LayoutPoint,
|
||||
// hit_test_flags: &HitTestFlags,
|
||||
// hit_test_result: &mut HitTestResult,
|
||||
// border_rect: PhysicalRect<Au>,
|
||||
// style: &ComputedValues,
|
||||
//) -> bool {
|
||||
// let inherited_ui = style.get_inherited_ui();
|
||||
// if inherited_ui.pointer_events == PointerEvents::None {
|
||||
// return false;
|
||||
// }
|
||||
// if let Some(tag) = self.tag() {
|
||||
// // obtain border radius
|
||||
// let webrender_border_rect = border_rect.to_webrender();
|
||||
// let border_radius = {
|
||||
// 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 b = style.get_border();
|
||||
// let mut radius = wr::BorderRadius {
|
||||
// top_left: corner(&b.border_top_left_radius),
|
||||
// top_right: corner(&b.border_top_right_radius),
|
||||
// bottom_right: corner(&b.border_bottom_right_radius),
|
||||
// bottom_left: corner(&b.border_bottom_left_radius),
|
||||
// };
|
||||
//
|
||||
// normalize_radii(&webrender_border_rect, &mut radius);
|
||||
// radius
|
||||
// };
|
||||
// if border_radius.is_zero() {
|
||||
// // if not has border radius hit test in rect.
|
||||
// if webrender_border_rect.contains(hit_test_location) {
|
||||
// let point_in_target = hit_test_location - webrender_border_rect.min.to_vector();
|
||||
// debug!(
|
||||
// "Fragment::hit_test_fragment_in_rect true point_in_target:{point_in_target:?}"
|
||||
// );
|
||||
// hit_test_result.items.push(HitTestResultItem {
|
||||
// opaque_node: tag.node,
|
||||
// point_in_target,
|
||||
// });
|
||||
// return !hit_test_flags.contains(HitTestFlags::FIND_ALL);
|
||||
// }
|
||||
// } else {
|
||||
// // if has border radius, handle clip region.
|
||||
// if rounded_rectangle_contains_point(
|
||||
// &hit_test_location,
|
||||
// &webrender_border_rect,
|
||||
// &border_radius,
|
||||
// ) {
|
||||
// let point_in_target = hit_test_location - webrender_border_rect.min.to_vector();
|
||||
// debug!(
|
||||
// "Fragment::hit_test_fragment_in_rect true point_in_target:{point_in_target:?}"
|
||||
// );
|
||||
// hit_test_result.items.push(HitTestResultItem {
|
||||
// opaque_node: tag.node,
|
||||
// point_in_target,
|
||||
// });
|
||||
// return !hit_test_flags.contains(HitTestFlags::FIND_ALL);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// false
|
||||
//}
|
|
@ -65,9 +65,11 @@ mod background;
|
|||
mod clip;
|
||||
mod conversions;
|
||||
mod gradient;
|
||||
mod hit_test;
|
||||
mod stacking_context;
|
||||
|
||||
use background::BackgroundPainter;
|
||||
pub(crate) use hit_test::HitTest;
|
||||
pub use stacking_context::*;
|
||||
|
||||
// webrender's `ItemTag` is private.
|
||||
|
|
|
@ -293,7 +293,7 @@ pub(crate) enum StackingContextContent {
|
|||
}
|
||||
|
||||
impl StackingContextContent {
|
||||
fn section(&self) -> StackingContextSection {
|
||||
pub(crate) fn section(&self) -> StackingContextSection {
|
||||
match self {
|
||||
Self::Fragment { section, .. } => *section,
|
||||
Self::AtomicInlineStackingContainer { .. } => StackingContextSection::Foreground,
|
||||
|
@ -365,7 +365,7 @@ pub struct StackingContext {
|
|||
context_type: StackingContextType,
|
||||
|
||||
/// The contents that need to be painted in fragment order.
|
||||
contents: Vec<StackingContextContent>,
|
||||
pub(super) contents: Vec<StackingContextContent>,
|
||||
|
||||
/// Stacking contexts that need to be stolen by the parent stacking context
|
||||
/// if this is a stacking container, that is, real stacking contexts and
|
||||
|
@ -376,13 +376,13 @@ pub struct StackingContext {
|
|||
/// > if it created a new stacking context, but omitting any positioned
|
||||
/// > descendants or descendants that actually create a stacking context
|
||||
/// > (letting the parent stacking context paint them, instead).
|
||||
real_stacking_contexts_and_positioned_stacking_containers: Vec<StackingContext>,
|
||||
pub(super) real_stacking_contexts_and_positioned_stacking_containers: Vec<StackingContext>,
|
||||
|
||||
/// Float stacking containers.
|
||||
/// Separate from real_stacking_contexts_or_positioned_stacking_containers
|
||||
/// because they should never be stolen by the parent stacking context.
|
||||
/// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
|
||||
float_stacking_containers: Vec<StackingContext>,
|
||||
pub(super) float_stacking_containers: Vec<StackingContext>,
|
||||
|
||||
/// Atomic inline stacking containers.
|
||||
/// Separate from real_stacking_contexts_or_positioned_stacking_containers
|
||||
|
@ -391,7 +391,7 @@ pub struct StackingContext {
|
|||
/// can index into this vec to paint them in fragment order.
|
||||
/// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
|
||||
/// <https://drafts.csswg.org/css-position-4/#paint-a-box-in-a-line-box>
|
||||
atomic_inline_stacking_containers: Vec<StackingContext>,
|
||||
pub(super) atomic_inline_stacking_containers: Vec<StackingContext>,
|
||||
|
||||
/// Information gathered about the painting order, for [Self::debug_print].
|
||||
debug_print_items: Option<RefCell<Vec<DebugPrintItem>>>,
|
||||
|
@ -473,7 +473,7 @@ impl StackingContext {
|
|||
.push(stacking_context)
|
||||
}
|
||||
|
||||
fn z_index(&self) -> i32 {
|
||||
pub(crate) fn z_index(&self) -> i32 {
|
||||
self.initializing_fragment.as_ref().map_or(0, |fragment| {
|
||||
let fragment = fragment.borrow();
|
||||
fragment.style.effective_z_index(fragment.base.flags)
|
||||
|
@ -652,6 +652,9 @@ impl StackingContext {
|
|||
fragment_builder.build_background_image(builder, &painter);
|
||||
}
|
||||
|
||||
/// Build a display list from a a [`StackingContext`]. Note that this is the forward
|
||||
/// version of the reversed stacking context walk algorithm in `hit_test.rs`. Any
|
||||
/// changes made here should be reflected in the forward version in that file.
|
||||
pub(crate) fn build_display_list(&self, builder: &mut DisplayListBuilder) {
|
||||
let pushed_context = self.push_webrender_stacking_context_if_necessary(builder);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ use base::id::{PipelineId, WebViewId};
|
|||
use bitflags::bitflags;
|
||||
use compositing_traits::CrossProcessCompositorApi;
|
||||
use compositing_traits::display_list::ScrollType;
|
||||
use embedder_traits::{Theme, UntrustedNodeAddress, ViewportDetails};
|
||||
use embedder_traits::{Theme, ViewportDetails};
|
||||
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect};
|
||||
use euclid::{Point2D, Scale, Size2D};
|
||||
use fnv::FnvHashMap;
|
||||
|
@ -26,9 +26,8 @@ use fonts_traits::StylesheetWebFontLoadFinishedCallback;
|
|||
use fxhash::FxHashMap;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use layout_api::{
|
||||
IFrameSizes, Layout, LayoutConfig, LayoutDamage, LayoutFactory, NodesFromPointQueryType,
|
||||
OffsetParentResponse, QueryMsg, ReflowGoal, ReflowRequest, ReflowRequestRestyle, ReflowResult,
|
||||
TrustedNodeAddress,
|
||||
IFrameSizes, Layout, LayoutConfig, LayoutDamage, LayoutFactory, OffsetParentResponse, QueryMsg,
|
||||
ReflowGoal, ReflowRequest, ReflowRequestRestyle, ReflowResult, TrustedNodeAddress,
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
|
||||
|
@ -75,11 +74,11 @@ use style::{Zero, driver};
|
|||
use style_traits::{CSSPixel, SpeculativePainter};
|
||||
use stylo_atoms::Atom;
|
||||
use url::Url;
|
||||
use webrender_api::units::{DevicePixel, DevicePoint, LayoutVector2D};
|
||||
use webrender_api::{ExternalScrollId, HitTestFlags};
|
||||
use webrender_api::ExternalScrollId;
|
||||
use webrender_api::units::{DevicePixel, LayoutVector2D};
|
||||
|
||||
use crate::context::{CachedImageOrError, ImageResolver, LayoutContext};
|
||||
use crate::display_list::{DisplayListBuilder, StackingContextTree};
|
||||
use crate::display_list::{DisplayListBuilder, HitTest, StackingContextTree};
|
||||
use crate::query::{
|
||||
get_the_text_steps, process_client_rect_request, process_content_box_request,
|
||||
process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
|
||||
|
@ -294,30 +293,6 @@ impl Layout for LayoutThread {
|
|||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
get_the_text_steps(node)
|
||||
}
|
||||
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
fn query_nodes_from_point(
|
||||
&self,
|
||||
point: UntypedPoint2D<f32>,
|
||||
query_type: NodesFromPointQueryType,
|
||||
) -> Vec<UntrustedNodeAddress> {
|
||||
let mut flags = match query_type {
|
||||
NodesFromPointQueryType::Topmost => HitTestFlags::empty(),
|
||||
NodesFromPointQueryType::All => HitTestFlags::FIND_ALL,
|
||||
};
|
||||
|
||||
// The point we get is not relative to the entire WebRender scene, but to this
|
||||
// particular pipeline, so we need to tell WebRender about that.
|
||||
flags.insert(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT);
|
||||
|
||||
let client_point = DevicePoint::from_untyped(point);
|
||||
let results = self
|
||||
.compositor_api
|
||||
.hit_test(Some(self.id.into()), client_point, flags);
|
||||
|
||||
results.iter().map(|result| result.node).collect()
|
||||
}
|
||||
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse {
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
|
@ -495,6 +470,19 @@ impl Layout for LayoutThread {
|
|||
.as_mut()
|
||||
.and_then(|tree| tree.compositor_info.scroll_tree.scroll_offset(id))
|
||||
}
|
||||
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
fn query_elements_from_point(
|
||||
&self,
|
||||
point: webrender_api::units::LayoutPoint,
|
||||
flags: layout_api::ElementsFromPointFlags,
|
||||
) -> Vec<layout_api::ElementsFromPointResult> {
|
||||
self.stacking_context_tree
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.map(|tree| HitTest::run(tree, point, flags))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutThread {
|
||||
|
@ -1452,7 +1440,8 @@ impl ReflowPhases {
|
|||
QueryMsg::ContentBox |
|
||||
QueryMsg::ContentBoxes |
|
||||
QueryMsg::ResolvedStyleQuery |
|
||||
QueryMsg::ScrollingAreaOrOffsetQuery => Self::StackingContextTreeConstruction,
|
||||
QueryMsg::ScrollingAreaOrOffsetQuery |
|
||||
QueryMsg::ElementsFromPoint => Self::StackingContextTreeConstruction,
|
||||
QueryMsg::ClientRectQuery |
|
||||
QueryMsg::ElementInnerOuterTextQuery |
|
||||
QueryMsg::InnerWindowDimensionsQuery |
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::c_void;
|
||||
use std::fmt;
|
||||
|
||||
use embedder_traits::UntrustedNodeAddress;
|
||||
use euclid::default::Point2D;
|
||||
use js::rust::HandleValue;
|
||||
use layout_api::{NodesFromPointQueryType, QueryMsg};
|
||||
use layout_api::{ElementsFromPointFlags, ElementsFromPointResult, QueryMsg};
|
||||
use script_bindings::error::{Error, ErrorResult};
|
||||
use script_bindings::script_runtime::JSContext;
|
||||
use servo_arc::Arc;
|
||||
|
@ -19,6 +19,7 @@ use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuar
|
|||
use style::stylesheets::scope_rule::ImplicitScopeRoot;
|
||||
use style::stylesheets::{Stylesheet, StylesheetContents};
|
||||
use stylo_atoms::Atom;
|
||||
use webrender_api::units::LayoutPoint;
|
||||
|
||||
use super::bindings::trace::HashMapTracedValues;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
|
@ -137,17 +138,15 @@ impl DocumentOrShadowRoot {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn nodes_from_point(
|
||||
pub(crate) fn query_elements_from_point(
|
||||
&self,
|
||||
client_point: &Point2D<f32>,
|
||||
query_type: NodesFromPointQueryType,
|
||||
point: LayoutPoint,
|
||||
flags: ElementsFromPointFlags,
|
||||
can_gc: CanGc,
|
||||
) -> Vec<UntrustedNodeAddress> {
|
||||
) -> Vec<ElementsFromPointResult> {
|
||||
self.window
|
||||
.layout_reflow(QueryMsg::NodesFromPointQuery, can_gc);
|
||||
self.window
|
||||
.layout()
|
||||
.query_nodes_from_point(*client_point, query_type)
|
||||
.layout_reflow(QueryMsg::ElementsFromPoint, can_gc);
|
||||
self.window.layout().query_elements_from_point(point, flags)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
|
@ -162,7 +161,6 @@ impl DocumentOrShadowRoot {
|
|||
) -> Option<DomRoot<Element>> {
|
||||
let x = *x as f32;
|
||||
let y = *y as f32;
|
||||
let point = &Point2D::new(x, y);
|
||||
let viewport = self.window.viewport_details().size;
|
||||
|
||||
if !has_browsing_context {
|
||||
|
@ -174,11 +172,18 @@ impl DocumentOrShadowRoot {
|
|||
}
|
||||
|
||||
match self
|
||||
.nodes_from_point(point, NodesFromPointQueryType::Topmost, can_gc)
|
||||
.query_elements_from_point(
|
||||
LayoutPoint::new(x, y),
|
||||
ElementsFromPointFlags::empty(),
|
||||
can_gc,
|
||||
)
|
||||
.first()
|
||||
{
|
||||
Some(address) => {
|
||||
let node = unsafe { node::from_untrusted_node_address(*address) };
|
||||
Some(result) => {
|
||||
// SAFETY: This is safe because `Self::query_elements_from_point` has ensured that
|
||||
// layout has run and any OpaqueNodes that no longer refer to real nodes are gone.
|
||||
let address = UntrustedNodeAddress(result.node.0 as *const c_void);
|
||||
let node = unsafe { node::from_untrusted_node_address(address) };
|
||||
let parent_node = node.GetParentNode().unwrap();
|
||||
let shadow_host = parent_node
|
||||
.downcast::<ShadowRoot>()
|
||||
|
@ -210,7 +215,6 @@ impl DocumentOrShadowRoot {
|
|||
) -> Vec<DomRoot<Element>> {
|
||||
let x = *x as f32;
|
||||
let y = *y as f32;
|
||||
let point = &Point2D::new(x, y);
|
||||
let viewport = self.window.viewport_details().size;
|
||||
|
||||
if !has_browsing_context {
|
||||
|
@ -223,11 +227,18 @@ impl DocumentOrShadowRoot {
|
|||
}
|
||||
|
||||
// Step 1 and Step 3
|
||||
let nodes = self.nodes_from_point(point, NodesFromPointQueryType::All, can_gc);
|
||||
let nodes = self.query_elements_from_point(
|
||||
LayoutPoint::new(x, y),
|
||||
ElementsFromPointFlags::FindAll,
|
||||
can_gc,
|
||||
);
|
||||
let mut elements: Vec<DomRoot<Element>> = nodes
|
||||
.iter()
|
||||
.flat_map(|&untrusted_node_address| {
|
||||
let node = unsafe { node::from_untrusted_node_address(untrusted_node_address) };
|
||||
.flat_map(|result| {
|
||||
// SAFETY: This is safe because `Self::query_elements_from_point` has ensured that
|
||||
// layout has run and any OpaqueNodes that no longer refer to real nodes are gone.
|
||||
let address = UntrustedNodeAddress(result.node.0 as *const c_void);
|
||||
let node = unsafe { node::from_untrusted_node_address(address) };
|
||||
DomRoot::downcast::<Element>(node)
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -296,6 +296,7 @@ impl ScrollableNodeInfo {
|
|||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct ScrollTreeNodeTransformationCache {
|
||||
node_to_root_transform: LayoutTransform,
|
||||
root_to_node_transform: Option<LayoutTransform>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -599,7 +600,8 @@ impl ScrollTree {
|
|||
})
|
||||
}
|
||||
|
||||
/// Traverse a scroll node to its root to calculate the transform.
|
||||
/// Find a transformation that can convert a point in the node coordinate system to a
|
||||
/// point in the root coordinate system.
|
||||
pub fn cumulative_node_to_root_transform(&self, node_id: &ScrollTreeNodeId) -> LayoutTransform {
|
||||
let node = self.get_node(node_id);
|
||||
if let Some(cached_transforms) = node.transformation_cache.get() {
|
||||
|
@ -611,6 +613,23 @@ impl ScrollTree {
|
|||
transforms.node_to_root_transform
|
||||
}
|
||||
|
||||
/// Find a transformation that can convert a point in the root coordinate system to a
|
||||
/// point in the coordinate system of the given node. This may be `None` if the cumulative
|
||||
/// transform is uninvertible.
|
||||
pub fn cumulative_root_to_node_transform(
|
||||
&self,
|
||||
node_id: &ScrollTreeNodeId,
|
||||
) -> Option<LayoutTransform> {
|
||||
let node = self.get_node(node_id);
|
||||
if let Some(cached_transforms) = node.transformation_cache.get() {
|
||||
return cached_transforms.root_to_node_transform;
|
||||
}
|
||||
|
||||
let (transforms, _) = self.cumulative_node_transform_inner(node);
|
||||
node.transformation_cache.set(Some(transforms));
|
||||
transforms.root_to_node_transform
|
||||
}
|
||||
|
||||
/// Traverse a scroll node to its root to calculate the transform.
|
||||
fn cumulative_node_transform_inner(
|
||||
&self,
|
||||
|
@ -628,7 +647,7 @@ impl ScrollTree {
|
|||
post_translation.then(transform).then(&pre_translation)
|
||||
};
|
||||
|
||||
let node_to_parent_transform = match &node.info {
|
||||
let (node_to_parent_transform, parent_to_node_transform) = match &node.info {
|
||||
SpatialTreeNodeInfo::ReferenceFrame(info) => {
|
||||
// To apply a transformation we need to make sure the rectangle's
|
||||
// coordinate space is the same as reference frame's coordinate space.
|
||||
|
@ -638,30 +657,49 @@ impl ScrollTree {
|
|||
info.frame_origin_for_query.y,
|
||||
);
|
||||
|
||||
let parent_to_node_transform = info.transform.inverse().map(|inverse_transform| {
|
||||
change_basis(&inverse_transform, -info.origin.x, info.origin.y)
|
||||
});
|
||||
|
||||
sticky_info.nearest_scrolling_ancestor_viewport = sticky_info
|
||||
.nearest_scrolling_ancestor_viewport
|
||||
.translate(-info.origin.to_vector());
|
||||
|
||||
node_to_parent_transform
|
||||
(node_to_parent_transform, parent_to_node_transform)
|
||||
},
|
||||
SpatialTreeNodeInfo::Scroll(info) => {
|
||||
sticky_info.nearest_scrolling_ancestor_viewport = info.clip_rect;
|
||||
sticky_info.nearest_scrolling_ancestor_offset = -info.offset;
|
||||
|
||||
Transform3D::translation(-info.offset.x, -info.offset.y, 0.0)
|
||||
(
|
||||
Transform3D::translation(-info.offset.x, -info.offset.y, 0.0),
|
||||
Some(Transform3D::translation(info.offset.x, info.offset.y, 0.0)),
|
||||
)
|
||||
},
|
||||
|
||||
SpatialTreeNodeInfo::Sticky(info) => {
|
||||
let offset = info.calculate_sticky_offset(&sticky_info);
|
||||
sticky_info.nearest_scrolling_ancestor_offset += offset;
|
||||
Transform3D::translation(offset.x, offset.y, 0.0)
|
||||
(
|
||||
Transform3D::translation(offset.x, offset.y, 0.0),
|
||||
Some(Transform3D::translation(-offset.x, -offset.y, 0.0)),
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
let node_to_root_transform =
|
||||
node_to_parent_transform.then(&parent_transforms.node_to_root_transform);
|
||||
let root_to_node_transform = parent_to_node_transform.map(|parent_to_node_transform| {
|
||||
parent_transforms
|
||||
.root_to_node_transform
|
||||
.map_or(parent_to_node_transform, |parent_transform| {
|
||||
parent_transform.then(&parent_to_node_transform)
|
||||
})
|
||||
});
|
||||
|
||||
let transforms = ScrollTreeNodeTransformationCache {
|
||||
node_to_root_transform,
|
||||
root_to_node_transform,
|
||||
};
|
||||
(transforms, sticky_info)
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ use style::properties::PropertyId;
|
|||
use style::properties::style_structs::Font;
|
||||
use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
|
||||
use style::stylesheets::Stylesheet;
|
||||
use webrender_api::units::{DeviceIntSize, LayoutVector2D};
|
||||
use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
|
||||
use webrender_api::{ExternalScrollId, ImageKey};
|
||||
|
||||
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
|
||||
|
@ -263,11 +263,6 @@ pub trait Layout {
|
|||
fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<Rect<Au>>;
|
||||
fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;
|
||||
fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
|
||||
fn query_nodes_from_point(
|
||||
&self,
|
||||
point: Point2D<f32>,
|
||||
query_type: NodesFromPointQueryType,
|
||||
) -> Vec<UntrustedNodeAddress>;
|
||||
fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
|
||||
fn query_resolved_style(
|
||||
&self,
|
||||
|
@ -286,6 +281,11 @@ pub trait Layout {
|
|||
) -> Option<ServoArc<Font>>;
|
||||
fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32>;
|
||||
fn query_text_indext(&self, node: OpaqueNode, point: Point2D<f32>) -> Option<usize>;
|
||||
fn query_elements_from_point(
|
||||
&self,
|
||||
point: LayoutPoint,
|
||||
flags: ElementsFromPointFlags,
|
||||
) -> Vec<ElementsFromPointResult>;
|
||||
}
|
||||
|
||||
/// This trait is part of `layout_api` because it depends on both `script_traits`
|
||||
|
@ -306,26 +306,21 @@ pub struct OffsetParentResponse {
|
|||
pub rect: Rect<Au>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum NodesFromPointQueryType {
|
||||
All,
|
||||
Topmost,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum QueryMsg {
|
||||
ClientRectQuery,
|
||||
ContentBox,
|
||||
ContentBoxes,
|
||||
ClientRectQuery,
|
||||
ScrollingAreaOrOffsetQuery,
|
||||
OffsetParentQuery,
|
||||
TextIndexQuery,
|
||||
NodesFromPointQuery,
|
||||
ResolvedStyleQuery,
|
||||
StyleQuery,
|
||||
ElementInnerOuterTextQuery,
|
||||
ResolvedFontStyleQuery,
|
||||
ElementsFromPoint,
|
||||
InnerWindowDimensionsQuery,
|
||||
NodesFromPointQuery,
|
||||
OffsetParentQuery,
|
||||
ResolvedFontStyleQuery,
|
||||
ResolvedStyleQuery,
|
||||
ScrollingAreaOrOffsetQuery,
|
||||
StyleQuery,
|
||||
TextIndexQuery,
|
||||
}
|
||||
|
||||
/// The goal of a reflow request.
|
||||
|
@ -584,6 +579,25 @@ impl ImageAnimationState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Describe an item that matched a hit-test query.
|
||||
#[derive(Debug)]
|
||||
pub struct ElementsFromPointResult {
|
||||
/// An [`OpaqueNode`] that contains a pointer to the node hit by
|
||||
/// this hit test result.
|
||||
pub node: OpaqueNode,
|
||||
/// The [`LayoutPoint`] of the original query point relative to the
|
||||
/// node fragment rectangle.
|
||||
pub point_in_target: LayoutPoint,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ElementsFromPointFlags: u8 {
|
||||
/// Whether or not to find all of the items for a hit test or stop at the
|
||||
/// first hit.
|
||||
const FindAll = 0b00000001;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue