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;