From 79cba948d445ba37fec5b1749eadceb28374768d Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Fri, 22 Sep 2017 10:08:50 +0200 Subject: [PATCH] Add support for hit testing This adds a new API point for doing hit testing against the display list. This version supports the features Servo needs to do hist testing, except for text index support (which will come in a later PR). Fixes #1575. --- Cargo.lock | 1 + webrender/src/clip.rs | 80 +++++++++++- webrender/src/clip_scroll_tree.rs | 79 +++++++++++- webrender/src/ellipse.rs | 58 ++++++++- webrender/src/frame.rs | 31 +++-- webrender/src/frame_builder.rs | 194 +++++++++++++++++++++++++----- webrender/src/prim_store.rs | 26 +++- webrender/src/render_backend.rs | 6 + webrender/src/util.rs | 9 +- webrender_api/Cargo.toml | 1 + webrender_api/src/api.rs | 52 +++++++- webrender_api/src/display_item.rs | 9 ++ webrender_api/src/display_list.rs | 2 + webrender_api/src/lib.rs | 2 + 14 files changed, 490 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52242046ca..7259727c52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1075,6 +1075,7 @@ version = "0.52.0" dependencies = [ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/webrender/src/clip.rs b/webrender/src/clip.rs index 0c48ef0412..22738e6741 100644 --- a/webrender/src/clip.rs +++ b/webrender/src/clip.rs @@ -2,9 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{BorderRadius, ComplexClipRegion, ImageMask, ImageRendering}; -use api::{DeviceIntRect, LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LocalClip}; +use api::{BorderRadius, ComplexClipRegion, DeviceIntRect, ImageMask, ImageRendering, LayerPoint}; +use api::{LayerRect, LayerSize, LayerToWorldTransform, LayoutPoint, LayoutVector2D, LocalClip}; use border::BorderCornerClipSource; +use ellipse::Ellipse; use freelist::{FreeList, FreeListHandle, WeakFreeListHandle}; use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks}; use prim_store::{ClipData, ImageMaskData}; @@ -112,6 +113,22 @@ impl From for ClipSources { } } +impl ClipSource { + pub fn contains(&self, point: &LayerPoint) -> bool { + // We currently do not handle all types of clip sources, because they + // aren't used for ClipScrollNodes and this method is only used during hit testing. + match self { + &ClipSource::Rectangle(ref rectangle) => rectangle.contains(point), + &ClipSource::RoundedRectangle(rect, radii, ClipMode::Clip) => + rounded_rectangle_contains_point(point, &rect, &radii), + &ClipSource::RoundedRectangle(rect, radii, ClipMode::ClipOut) => + !rounded_rectangle_contains_point(point, &rect, &radii), + _ => unreachable!("Tried to call contains on an unsupported ClipSource."), + } + } + +} + #[derive(Debug)] pub struct ClipSources { pub clips: Vec<(ClipSource, GpuCacheHandle)>, @@ -288,3 +305,62 @@ impl MaskBounds { } } } + +pub trait Contains { + fn contains(&self, point: &LayoutPoint) -> bool; +} + +impl Contains for LocalClip { + fn contains(&self, point: &LayoutPoint) -> bool { + if !self.clip_rect().contains(point) { + return false; + } + match self { + &LocalClip::Rect(..) => true, + &LocalClip::RoundedRect(_, complex_clip) => complex_clip.contains(point), + } + } +} + +impl Contains for ComplexClipRegion { + fn contains(&self, point: &LayoutPoint) -> bool { + rounded_rectangle_contains_point(point, &self.rect, &self.radii) + } +} + +fn rounded_rectangle_contains_point(point: &LayoutPoint, + rect: &LayerRect, + radii: &BorderRadius) + -> bool { + if !rect.contains(point) { + return false; + } + + let top_left_center = rect.origin + radii.top_left.to_vector(); + if top_left_center.x > point.x && top_left_center.y > point.y && + !Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) { + return false; + } + + let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector(); + if bottom_right_center.x < point.x && bottom_right_center.y < point.y && + !Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) { + return false; + } + + let top_right_center = rect.top_right() + + LayoutVector2D::new(-radii.top_right.width, radii.top_right.height); + if top_right_center.x < point.x && top_right_center.y > point.y && + !Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) { + return false; + } + + let bottom_left_center = rect.bottom_left() + + LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height); + if bottom_left_center.x > point.x && bottom_left_center.y < point.y && + !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) { + return false; + } + + true +} diff --git a/webrender/src/clip_scroll_tree.rs b/webrender/src/clip_scroll_tree.rs index 18fce3d2ab..7d422602a5 100644 --- a/webrender/src/clip_scroll_tree.rs +++ b/webrender/src/clip_scroll_tree.rs @@ -119,6 +119,66 @@ impl ClipScrollTree { .unwrap_or(self.topmost_scrolling_node_id()) } + pub fn is_point_clipped_in_for_node( + &self, + point: WorldPoint, + node_id: &ClipId, + cache: &mut FastHashMap>, + clip_store: &ClipStore + ) -> bool { + if let Some(point) = cache.get(node_id) { + return point.is_some(); + } + + let node = self.nodes.get(node_id).unwrap(); + let parent_clipped_in = match node.parent { + None => true, // This is the root node. + Some(ref parent_id) => { + self.is_point_clipped_in_for_node(point, parent_id, cache, clip_store) + } + }; + + if !parent_clipped_in { + cache.insert(*node_id, None); + return false; + } + + let transform = node.world_viewport_transform; + let transformed_point = match transform.inverse() { + Some(inverted) => inverted.transform_point2d(&point), + None => { + cache.insert(*node_id, None); + return false; + } + }; + + let point_in_layer = transformed_point - node.local_viewport_rect.origin.to_vector(); + let clip_info = match node.node_type { + NodeType::Clip(ref info) => info, + _ => { + cache.insert(*node_id, Some(point_in_layer)); + return true; + } + }; + + if !node.local_clip_rect.contains(&transformed_point) { + cache.insert(*node_id, None); + return false; + } + + let point_in_clips = transformed_point - clip_info.clip_rect.origin.to_vector(); + for &(ref clip, _) in clip_store.get(&clip_info.clip_sources).clips() { + if !clip.contains(&point_in_clips) { + cache.insert(*node_id, None); + return false; + } + } + + cache.insert(*node_id, Some(point_in_layer)); + + true + } + pub fn get_scroll_node_state(&self) -> Vec { let mut result = vec![]; for (id, node) in self.nodes.iter() { @@ -342,8 +402,14 @@ impl ClipScrollTree { origin_in_parent_reference_frame: LayerVector2D, pipeline_id: PipelineId, parent_id: Option, + root_for_pipeline: bool, ) -> ClipId { - let reference_frame_id = self.generate_new_clip_id(pipeline_id); + let reference_frame_id = if root_for_pipeline { + ClipId::root_reference_frame(pipeline_id) + } else { + self.generate_new_clip_id(pipeline_id) + }; + let node = ClipScrollNode::new_reference_frame( parent_id, rect, @@ -462,4 +528,15 @@ impl ClipScrollTree { self.print_node(&self.root_reference_frame_id, pt, clip_store); } } + + pub fn make_node_relative_point_absolute( + &self, + pipeline_id: Option, + point: &LayerPoint + ) -> WorldPoint { + pipeline_id.and_then(|id| self.nodes.get(&ClipId::root_reference_frame(id))) + .map(|node| node.world_viewport_transform.transform_point2d(point)) + .unwrap_or_else(|| WorldPoint::new(point.x, point.y)) + + } } diff --git a/webrender/src/ellipse.rs b/webrender/src/ellipse.rs index a021eb0cf5..6457026bf8 100644 --- a/webrender/src/ellipse.rs +++ b/webrender/src/ellipse.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{LayerPoint, LayerSize}; +use api::{LayerPoint, LayerSize, LayerVector2D}; use std::f32::consts::FRAC_PI_2; /// Number of steps to integrate arc length over. @@ -68,6 +68,62 @@ impl Ellipse { ); (point, tangent) } + + pub fn contains(&self, point: LayerPoint) -> bool { + self.signed_distance(point.to_vector()) <= 0.0 + } + + /// Find the signed distance from this ellipse given a point. + /// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm + fn signed_distance(&self, point: LayerVector2D) -> f32 { + // This algorithm fails for circles, so we handle them here. + if self.radius.width == self.radius.height { + return point.length() - self.radius.width; + } + + let mut p = LayerVector2D::new(point.x.abs(), point.y.abs()); + let mut ab = self.radius.to_vector(); + if p.x > p.y { + p = p.yx(); + ab = ab.yx(); + } + + let l = ab.y * ab.y - ab.x * ab.x; + + let m = ab.x * p.x / l; + let n = ab.y * p.y / l; + let m2 = m * m; + let n2 = n * n; + + let c = (m2 + n2 - 1.0) / 3.0; + let c3 = c * c * c; + + let q = c3 + m2 * n2 * 2.0; + let d = c3 + m2 * n2; + let g = m + m * n2; + + let co = if d < 0.0 { + let p = (q / c3).acos() / 3.0; + let s = p.cos(); + let t = p.sin() * (3.0_f32).sqrt(); + let rx = (-c * (s + t + 2.0) + m2).sqrt(); + let ry = (-c * (s - t + 2.0) + m2).sqrt(); + (ry + l.signum() * rx + g.abs() / (rx * ry) - m) / 2.0 + } else { + let h = 2.0 * m * n * d.sqrt(); + let s = (q + h).signum() * (q + h).abs().powf(1.0 / 3.0); + let u = (q - h).signum() * (q - h).abs().powf(1.0 / 3.0); + let rx = -s - u - c * 4.0 + 2.0 * m2; + let ry = (s - u) * (3.0_f32).sqrt(); + let rm = (rx * rx + ry * ry).sqrt(); + let p = ry / (rm - rx).sqrt(); + (p + 2.0 * g / rm - m) / 2.0 + }; + + let si = (1.0 - co * co).sqrt(); + let r = LayerVector2D::new(ab.x * co, ab.y * si); + return (r - p).length() * (p.y - r.y).signum(); + } } /// Use Simpsons rule to approximate the arc length of diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index ffb088f0c6..4a80fe68c7 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -2,14 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF}; -use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp}; -use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize, - LayerToScrollTransform}; -use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, PipelineId}; -use api::{ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation}; -use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset}; -use api::{TransformStyle, WorldPoint}; +use api::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion}; +use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp, HitTestFlags}; +use api::{HitTestResult, ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect}; +use api::{LayerSize, LayerToScrollTransform, LayerVector2D, LayoutSize, LayoutTransform}; +use api::{LocalClip, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState}; +use api::{ScrollLocation, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext}; +use api::{TileOffset, TransformStyle, WorldPoint}; use clip::ClipRegion; use clip_scroll_tree::{ClipScrollTree, ScrollStates}; use euclid::rect; @@ -241,6 +240,18 @@ impl Frame { self.clip_scroll_tree.scroll(scroll_location, cursor, phase) } + pub fn hit_test(&mut self, + pipeline_id: Option, + point: WorldPoint, + flags: HitTestFlags) + -> HitTestResult { + if let Some(ref builder) = self.frame_builder { + builder.hit_test(&self.clip_scroll_tree, pipeline_id, point, flags) + } else { + HitTestResult::default() + } + } + pub fn tick_scrolling_bounce_animations(&mut self) { self.clip_scroll_tree.tick_scrolling_bounce_animations(); } @@ -434,6 +445,7 @@ impl Frame { &reference_frame_bounds, &transform, origin, + false, &mut self.clip_scroll_tree, ); context.replacements.push((context_scroll_node_id, clip_id)); @@ -511,6 +523,7 @@ impl Frame { &iframe_rect, &transform, origin, + true, &mut self.clip_scroll_tree, ); @@ -623,6 +636,7 @@ impl Frame { rect: LayerRect::zero(), local_clip: *item.local_clip(), is_backface_visible: prim_info.is_backface_visible, + tag: prim_info.tag, }; context.builder.add_line( @@ -1291,6 +1305,7 @@ fn try_to_add_rectangle_splitting_on_clip( rect: inner_unclipped_rect, local_clip: LocalClip::from(*info.local_clip.clip_rect()), is_backface_visible: info.is_backface_visible, + tag: None, }; context.builder.add_solid_rectangle( diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index d1fa6d33cc..c2d174dfa4 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -2,19 +2,18 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo, - ClipId, ColorF}; -use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize}; -use api::{device_length, ExtendMode, FilterOp, FontInstance, FontRenderMode}; -use api::{GlyphInstance, GlyphOptions, GradientStop}; -use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, - LayerSize}; -use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle}; -use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow}; -use api::{TileOffset, TransformStyle, WorldPixel, YuvColorSpace, YuvData}; +use api::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo}; +use api::{ClipId, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect}; +use api::{DeviceUintSize, ExtendMode, FIND_ALL, FilterOp, FontInstance, FontRenderMode}; +use api::{GlyphInstance, GlyphOptions, GradientStop, HitTestFlags, HitTestItem, HitTestResult}; +use api::{ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerPrimitiveInfo, LayerRect}; +use api::{LayerSize, LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation}; +use api::{LineStyle, LocalClip, POINT_RELATIVE_TO_PIPELINE_VIEWPORT, PipelineId, RepeatMode}; +use api::{ScrollSensitivity, SubpixelDirection, TextShadow, TileOffset, TransformStyle}; +use api::{WorldPixel, WorldPoint, YuvColorSpace, YuvData, device_length}; use app_units::Au; use border::ImageBorderSegment; -use clip::{ClipMode, ClipRegion, ClipSource, ClipSources, ClipStore}; +use clip::{ClipMode, ClipRegion, ClipSource, ClipSources, ClipStore, Contains}; use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType}; use clip_scroll_tree::ClipScrollTree; use euclid::{SideOffsets2D, vec2, vec3}; @@ -65,12 +64,32 @@ pub struct FrameBuilderConfig { pub debug: bool, } +#[derive(Debug)] +pub struct HitTestingItem { + rect: LayerRect, + clip: LocalClip, + tag: ItemTag, +} + +impl HitTestingItem { + fn new(tag: ItemTag, info: &LayerPrimitiveInfo) -> HitTestingItem { + HitTestingItem { + rect: info.rect, + clip: info.local_clip, + tag: tag, + } + } +} + +pub struct HitTestingRun(Vec, ClipAndScrollInfo); + pub struct FrameBuilder { screen_size: DeviceUintSize, background_color: Option, prim_store: PrimitiveStore, pub clip_store: ClipStore, cmds: Vec, + hit_testing_runs: Vec, config: FrameBuilderConfig, stacking_context_store: Vec, @@ -117,6 +136,7 @@ impl FrameBuilder { clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store), clip_scroll_group_indices: FastHashMap::default(), cmds: recycle_vec(prev.cmds), + hit_testing_runs: recycle_vec(prev.hit_testing_runs), packed_layers: recycle_vec(prev.packed_layers), shadow_prim_stack: recycle_vec(prev.shadow_prim_stack), scrollbar_prims: recycle_vec(prev.scrollbar_prims), @@ -136,6 +156,7 @@ impl FrameBuilder { clip_scroll_group_store: Vec::new(), clip_scroll_group_indices: FastHashMap::default(), cmds: Vec::new(), + hit_testing_runs: Vec::new(), packed_layers: Vec::new(), shadow_prim_stack: Vec::new(), scrollbar_prims: Vec::new(), @@ -189,12 +210,36 @@ impl FrameBuilder { &info.local_clip.clip_rect(), info.is_backface_visible, clip_sources, + info.tag, container, ); prim_index } + pub fn add_primitive_to_hit_testing_list( + &mut self, + info: &LayerPrimitiveInfo, + clip_and_scroll: ClipAndScrollInfo + ) { + let tag = match info.tag { + Some(tag) => tag, + None => return, + }; + + let new_item = HitTestingItem::new(tag, info); + match self.hit_testing_runs.last_mut() { + Some(&mut HitTestingRun(ref mut items, prev_clip_and_scroll)) + if prev_clip_and_scroll == clip_and_scroll => { + items.push(new_item); + return; + } + _ => {} + } + + self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll)); + } + /// Add an already created primitive to the draw lists. pub fn add_primitive_to_draw_list( &mut self, @@ -232,10 +277,10 @@ impl FrameBuilder { clip_sources: Vec, container: PrimitiveContainer, ) -> PrimitiveIndex { + self.add_primitive_to_hit_testing_list(info, clip_and_scroll); let prim_index = self.create_primitive(clip_and_scroll, info, clip_sources, container); self.add_primitive_to_draw_list(prim_index, clip_and_scroll); - prim_index } @@ -319,6 +364,7 @@ impl FrameBuilder { rect: &LayerRect, transform: &LayerToScrollTransform, origin_in_parent_reference_frame: LayerVector2D, + root_for_pipeline: bool, clip_scroll_tree: &mut ClipScrollTree, ) -> ClipId { let new_id = clip_scroll_tree.add_reference_frame( @@ -327,6 +373,7 @@ impl FrameBuilder { origin_in_parent_reference_frame, pipeline_id, parent_id, + root_for_pipeline, ); self.reference_frame_stack.push(new_id); new_id @@ -396,6 +443,7 @@ impl FrameBuilder { &viewport_rect, identity, LayerVector2D::zero(), + true, clip_scroll_tree, ); @@ -508,6 +556,13 @@ impl FrameBuilder { ) { let prim = RectanglePrimitive { color: *color }; + // Don't add transparent rectangles to the draw list, but do consider them for hit + // testing. This allows specifying invisible hit testing areas. + if color.a == 0.0 { + self.add_primitive_to_hit_testing_list(info, clip_and_scroll); + return; + } + let prim_index = self.add_primitive( clip_and_scroll, info, @@ -587,6 +642,7 @@ impl FrameBuilder { ); if color.a > 0.0 { + self.add_primitive_to_hit_testing_list(&info, clip_and_scroll); self.add_primitive_to_draw_list(prim_index, clip_and_scroll); } @@ -1090,6 +1146,7 @@ impl FrameBuilder { // Only add a visual element if it can contribute to the scene. if color.a > 0.0 { + self.add_primitive_to_hit_testing_list(info, clip_and_scroll); self.add_primitive_to_draw_list(prim_index, clip_and_scroll); } @@ -1485,6 +1542,88 @@ impl FrameBuilder { Some(bounding_rect) } + pub fn get_packed_layer_index_if_visible( + &self, + clip_and_scroll: &ClipAndScrollInfo + ) -> Option { + let group_index = self.clip_scroll_group_indices.get(&clip_and_scroll).unwrap(); + let clip_scroll_group = &self.clip_scroll_group_store[group_index.0]; + if clip_scroll_group.is_visible() { + Some(clip_scroll_group.packed_layer_index) + } else { + None + } + } + + pub fn hit_test( + &self, + clip_scroll_tree: &ClipScrollTree, + pipeline_id: Option, + point: WorldPoint, + flags: HitTestFlags + ) -> HitTestResult { + let point = if flags.contains(POINT_RELATIVE_TO_PIPELINE_VIEWPORT) { + let point = LayerPoint::new(point.x, point.y); + clip_scroll_tree.make_node_relative_point_absolute(pipeline_id, &point) + } else { + point + }; + + let mut node_cache = FastHashMap::default(); + let mut result = HitTestResult::default(); + for &HitTestingRun(ref items, ref clip_and_scroll) in self.hit_testing_runs.iter().rev() { + let scroll_node = &clip_scroll_tree.nodes[&clip_and_scroll.scroll_node_id]; + match (pipeline_id, scroll_node.pipeline_id) { + (Some(id), node_id) if node_id != id => continue, + _ => {}, + } + + let transform = scroll_node.world_content_transform; + let point_in_layer = match transform.inverse() { + Some(inverted) => inverted.transform_point2d(&point), + None => continue, + }; + + let mut clipped_in = false; + for item in items.iter().rev() { + if !item.rect.contains(&point_in_layer) || !item.clip.contains(&point_in_layer) { + continue; + } + + let clip_id = &clip_and_scroll.clip_node_id(); + if !clipped_in { + clipped_in = clip_scroll_tree.is_point_clipped_in_for_node(point, + clip_id, + &mut node_cache, + &self.clip_store); + if !clipped_in { + break; + } + } + + let root_pipeline_reference_frame_id = + ClipId::root_reference_frame(clip_id.pipeline_id()); + let point_in_viewport = match node_cache.get(&root_pipeline_reference_frame_id) { + Some(&Some(point)) => point, + _ => unreachable!("Hittest target's root reference frame not hit."), + }; + + result.items.push(HitTestItem { + pipeline: clip_and_scroll.clip_node_id().pipeline_id(), + tag: item.tag, + point_in_viewport, + }); + if !flags.contains(FIND_ALL) { + return result; + } + } + } + + result.items.dedup(); + return result; + } + + fn handle_primitive_run( &mut self, base_prim_index: PrimitiveIndex, @@ -1500,37 +1639,28 @@ impl FrameBuilder { profile_counters: &mut FrameProfileCounters, ) { let stacking_context_index = *self.stacking_context_stack.last().unwrap(); - let (packed_layer_index, pipeline_id) = { - let stacking_context = - &mut self.stacking_context_store[stacking_context_index.0]; - if !stacking_context.can_contribute_to_scene() { + let packed_layer_index = + match self.get_packed_layer_index_if_visible(&clip_and_scroll) { + Some(index) => index, + None => { + debug!("{:?} of invisible {:?}", base_prim_index, stacking_context_index); return; } + }; - let group_index = self.clip_scroll_group_indices - .get(&clip_and_scroll) - .unwrap(); - let clip_scroll_group = &self.clip_scroll_group_store[group_index.0]; - if !clip_scroll_group.is_visible() { - debug!( - "{:?} of invisible {:?}", - base_prim_index, - stacking_context_index - ); + let pipeline_id = { + let stacking_context = + &mut self.stacking_context_store[stacking_context_index.0]; + if !stacking_context.can_contribute_to_scene() { return; } // At least one primitive in this stacking context is visible, so the stacking // context is visible. stacking_context.is_visible = true; - - ( - clip_scroll_group.packed_layer_index, - stacking_context.pipeline_id, - ) + stacking_context.pipeline_id }; - debug!( "\t{:?} of {:?} at {:?}", base_prim_index, diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index e491e2d242..3b03d97c22 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -2,11 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{BorderRadius, ExtendMode, FontRenderMode, GlyphInstance, GradientStop}; -use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint}; -use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle}; -use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat}; -use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow}; +use api::{BorderRadius, BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize}; +use api::{DevicePoint, ExtendMode, FontInstance, FontRenderMode, GlyphInstance, GlyphKey}; +use api::{GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerRect}; +use api::{LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation, LineStyle, TextShadow}; +use api::{TileOffset, YuvColorSpace, YuvFormat, device_length}; use app_units::Au; use border::BorderCornerInstance; use clip::{ClipMode, ClipSourcesHandle, ClipStore}; @@ -144,6 +144,10 @@ pub struct PrimitiveMetadata { pub local_rect: LayerRect, pub local_clip_rect: LayerRect, pub is_backface_visible: bool, + + /// A tag used to identify this primitive outside of WebRender. This is + /// used for returning useful data during hit testing. + pub tag: Option, } #[derive(Debug)] @@ -862,6 +866,7 @@ impl PrimitiveStore { local_clip_rect: &LayerRect, is_backface_visible: bool, clip_sources: ClipSourcesHandle, + tag: Option, container: PrimitiveContainer, ) -> PrimitiveIndex { let prim_index = self.cpu_metadata.len(); @@ -879,6 +884,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_rectangles.push(rect); @@ -896,6 +902,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_lines.push(line); @@ -912,6 +919,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_text_runs.push(text_cpu); @@ -928,6 +936,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_text_shadows.push(text_shadow); @@ -944,6 +953,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_images.push(image_cpu); @@ -960,6 +970,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_yuv_images.push(image_cpu); @@ -976,6 +987,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_borders.push(border_cpu); @@ -992,6 +1004,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_gradients.push(gradient_cpu); @@ -1009,6 +1022,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_gradients.push(gradient_cpu); @@ -1026,6 +1040,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_radial_gradients.push(radial_gradient_cpu); @@ -1042,6 +1057,7 @@ impl PrimitiveStore { local_rect: *local_rect, local_clip_rect: *local_clip_rect, is_backface_visible: is_backface_visible, + tag, }; self.cpu_box_shadows.push(box_shadow); diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 7d5877fe15..406dacf597 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -331,6 +331,12 @@ impl RenderBackend { DocumentOp::ScrolledNop } } + DocumentMsg::HitTest(pipeline_id, point, flags, tx) => { + profile_scope!("HitTest"); + let result = doc.frame.hit_test(pipeline_id, point, flags); + tx.send(result).unwrap(); + DocumentOp::Nop + } DocumentMsg::ScrollNodeWithId(origin, id, clamp) => { profile_scope!("ScrollNodeWithScrollId"); let _timer = profile_counters.total_time.timer(); diff --git a/webrender/src/util.rs b/webrender/src/util.rs index 3749d665ca..1a20912846 100644 --- a/webrender/src/util.rs +++ b/webrender/src/util.rs @@ -2,11 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{BorderRadius, ComplexClipRegion, LayoutRect}; -use api::{DeviceIntRect, DevicePoint, DeviceRect, DeviceSize}; -use api::{LayerRect, LayerToWorldTransform, WorldPoint3D}; -use euclid::{Point2D, Rect, Size2D}; -use euclid::{TypedPoint2D, TypedRect, TypedSize2D, TypedTransform2D, TypedTransform3D}; +use api::{BorderRadius, ComplexClipRegion, DeviceIntRect, DevicePoint, DeviceRect, DeviceSize}; +use api::{LayerRect, LayerToWorldTransform, LayoutRect, WorldPoint3D}; +use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D, TypedTransform2D}; +use euclid::TypedTransform3D; use num_traits::Zero; use std::f32::consts::FRAC_1_SQRT_2; diff --git a/webrender_api/Cargo.toml b/webrender_api/Cargo.toml index 6b78b1dee2..dcf051e23a 100644 --- a/webrender_api/Cargo.toml +++ b/webrender_api/Cargo.toml @@ -12,6 +12,7 @@ ipc = ["ipc-channel"] [dependencies] app_units = "0.5.6" bincode = "0.8" +bitflags = "0.9" byteorder = "1.0" euclid = "0.15" fxhash = "0.2.1" diff --git a/webrender_api/src/api.rs b/webrender_api/src/api.rs index 54f31c82af..b2cde5ff34 100644 --- a/webrender_api/src/api.rs +++ b/webrender_api/src/api.rs @@ -2,12 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint}; -use {DeviceUintRect, DeviceUintSize, FontInstanceKey, FontKey, GlyphDimensions, GlyphKey}; -use {FontInstance, FontInstanceOptions, FontInstancePlatformOptions, FontVariation, - NativeFontHandle, WorldPoint}; -use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize, LayoutTransform, - LayoutVector2D}; +use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceUintRect}; +use {DeviceUintSize, FontInstance, FontInstanceKey, FontInstanceOptions}; +use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphKey, ImageData}; +use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D}; +use {NativeFontHandle, WorldPoint}; use app_units::Au; use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods}; use std::cell::Cell; @@ -143,6 +142,33 @@ pub enum AddFont { Native(FontKey, NativeFontHandle), } +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct HitTestItem { + /// The pipeline that the display item that was hit belongs to. + pub pipeline: PipelineId, + + /// The tag of the hit display item. + pub tag: ItemTag, + + /// The hit point in the coordinate space of the "viewport" of the display item. The + /// viewport is the scroll node formed by the root reference frame of the display item's + /// pipeline. + pub point_in_viewport: LayoutPoint, +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct HitTestResult { + pub items: Vec, +} + +bitflags! { + #[derive(Deserialize, Serialize)] + pub struct HitTestFlags: u8 { + const FIND_ALL = 0b00000001; + const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010; + } +} + #[derive(Clone, Deserialize, Serialize)] pub struct AddFontInstance { pub key: FontInstanceKey, @@ -155,6 +181,7 @@ pub struct AddFontInstance { #[derive(Clone, Deserialize, Serialize)] pub enum DocumentMsg { + HitTest(Option, WorldPoint, HitTestFlags, MsgSender), SetDisplayList { list_descriptor: BuiltDisplayListDescriptor, epoch: Epoch, @@ -187,6 +214,7 @@ impl fmt::Debug for DocumentMsg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { DocumentMsg::SetDisplayList { .. } => "DocumentMsg::SetDisplayList", + DocumentMsg::HitTest(..) => "DocumentMsg::HitTest", DocumentMsg::SetPageZoom(..) => "DocumentMsg::SetPageZoom", DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom", DocumentMsg::SetPan(..) => "DocumentMsg::SetPan", @@ -612,6 +640,18 @@ impl RenderApi { ); } + /// Does a hit test as the given point + pub fn hit_test(&self, + document_id: DocumentId, + pipeline_id: Option, + point: WorldPoint, + flags: HitTestFlags) + -> HitTestResult { + let (tx, rx) = channel::msg_channel().unwrap(); + self.send(document_id, DocumentMsg::HitTest(pipeline_id, point, flags, tx)); + rx.recv().unwrap() + } + pub fn set_page_zoom(&self, document_id: DocumentId, page_zoom: ZoomFactor) { self.send(document_id, DocumentMsg::SetPageZoom(page_zoom)); } diff --git a/webrender_api/src/display_item.rs b/webrender_api/src/display_item.rs index 2d59d55ffe..80a3d28965 100644 --- a/webrender_api/src/display_item.rs +++ b/webrender_api/src/display_item.rs @@ -38,6 +38,13 @@ impl ClipAndScrollInfo { } } +/// A tag that can be used to identify items during hit testing. If the tag +/// is missing then the item doesn't take part in hit testing at all. This +/// is composed of two numbers. In Servo, the first is an identifier while the +/// second is used to select the cursor that should be used during mouse +/// movement. +pub type ItemTag = (u64, u8); + #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub struct DisplayItem { pub item: SpecificDisplayItem, @@ -50,6 +57,7 @@ pub struct PrimitiveInfo { pub rect: TypedRect, pub local_clip: LocalClip, pub is_backface_visible: bool, + pub tag: Option, } impl LayerPrimitiveInfo { @@ -68,6 +76,7 @@ impl LayerPrimitiveInfo { rect: rect, local_clip: clip, is_backface_visible: true, + tag: None, } } } diff --git a/webrender_api/src/display_list.rs b/webrender_api/src/display_list.rs index 011852abac..91d87203b5 100644 --- a/webrender_api/src/display_list.rs +++ b/webrender_api/src/display_list.rs @@ -343,6 +343,7 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> { rect: info.rect.translate(&offset), local_clip: info.local_clip.create_with_offset(offset), is_backface_visible: info.is_backface_visible, + tag: info.tag, } } @@ -997,6 +998,7 @@ impl DisplayListBuilder { rect: content_rect, local_clip: LocalClip::from(clip_rect), is_backface_visible: true, + tag: None, }; self.push_item(item, &info); diff --git a/webrender_api/src/lib.rs b/webrender_api/src/lib.rs index 2f9a4d309d..3e7831e806 100644 --- a/webrender_api/src/lib.rs +++ b/webrender_api/src/lib.rs @@ -7,6 +7,8 @@ extern crate app_units; extern crate bincode; +#[macro_use] +extern crate bitflags; extern crate byteorder; #[cfg(feature = "nightly")] extern crate core;