servo: Merge #12777 - layout: Take into account the client point for fixed positioned stacking contexts (from emilio:hit-test); r=pcwalton
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Wed, 10 Aug 2016 18:50:33 -0500
changeset 339470 ebbbf889efa6cfb5f7d3fde57a423f0f376e919a
parent 339469 e3357211bdda25d90ee7460c74fcd1fae434686e
child 339471 a598e41b2e1bb6899342de7f1916feb4e7391d9e
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspcwalton
servo: Merge #12777 - layout: Take into account the client point for fixed positioned stacking contexts (from emilio:hit-test); r=pcwalton <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #12763 (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 9b4b94aa755a04d4b9c9703574b01c03ad25a788
servo/components/gfx/display_list/mod.rs
servo/components/layout/query.rs
servo/components/layout_thread/lib.rs
servo/components/script/dom/document.rs
servo/components/script/dom/window.rs
servo/components/script_layout_interface/message.rs
servo/components/script_layout_interface/rpc.rs
--- a/servo/components/gfx/display_list/mod.rs
+++ b/servo/components/gfx/display_list/mod.rs
@@ -56,17 +56,17 @@ pub use azure::azure_hl::GradientStop;
 
 /// The factor that we multiply the blur radius by in order to inflate the boundaries of display
 /// items that involve a blur. This ensures that the display item boundaries include all the ink.
 pub static BLUR_INFLATION_FACTOR: i32 = 3;
 
 /// LayerInfo is used to store PaintLayer metadata during DisplayList construction.
 /// It is also used for tracking LayerIds when creating layers to preserve ordering when
 /// layered DisplayItems should render underneath unlayered DisplayItems.
-#[derive(Clone, Copy, HeapSizeOf, Deserialize, Serialize)]
+#[derive(Clone, Copy, HeapSizeOf, Deserialize, Serialize, Debug)]
 pub struct LayerInfo {
     /// The base LayerId of this layer.
     pub layer_id: LayerId,
 
     /// The scroll policy of this layer.
     pub scroll_policy: ScrollPolicy,
 
     /// The subpage that this layer represents, if there is one.
@@ -128,17 +128,17 @@ impl<'a> DisplayListTraversal<'a> {
             self.display_list.offsets[&stacking_context.id].outlines + 1;
         while self.can_draw_item_at_index(self.current_item_index + 1) &&
               self.current_item_offset() < next_stacking_context_offset {
             self.current_item_index += 1;
         }
     }
 }
 
-#[derive(HeapSizeOf, Deserialize, Serialize)]
+#[derive(HeapSizeOf, Deserialize, Serialize, Debug)]
 pub struct StackingContextOffsets {
     pub start: u32,
     pub block_backgrounds_and_borders: u32,
     pub content: u32,
     pub outlines: u32,
 }
 
 /// A FNV-based hash map. This is not serializable by `serde` by default, so we provide an
@@ -503,27 +503,34 @@ impl DisplayList {
             paint_subcontext.pop_clip_if_applicable();
         }
 
         draw_target.set_transform(&old_transform);
         paint_context.draw_temporary_draw_target_if_necessary(
             &draw_target, &stacking_context.filters, stacking_context.blend_mode);
     }
 
-    /// Return all nodes containing the point of interest, bottommost first,
-    /// and respecting the `pointer-events` CSS property.
-    pub fn hit_test(&self, point: &Point2D<Au>, scroll_offsets: &ScrollOffsetMap)
+    /// Return all nodes containing the point of interest, bottommost first, and
+    /// respecting the `pointer-events` CSS property.
+    pub fn hit_test(&self,
+                    translated_point: &Point2D<Au>,
+                    client_point: &Point2D<Au>,
+                    scroll_offsets: &ScrollOffsetMap)
                     -> Vec<DisplayItemMetadata> {
         let mut traversal = DisplayListTraversal {
             display_list: self,
             current_item_index: 0,
             last_item_index: self.list.len() - 1,
         };
         let mut result = Vec::new();
-        self.root_stacking_context.hit_test(&mut traversal, point, scroll_offsets, &mut result);
+        self.root_stacking_context.hit_test(&mut traversal,
+                                            translated_point,
+                                            client_point,
+                                            scroll_offsets,
+                                            &mut result);
         result
     }
 }
 
 fn transformed_tile_rect(tile_rect: TypedRect<ScreenPx, usize>, transform: &Matrix4D<f32>) -> Rect<Au> {
     // Invert the current transform, then use this to back transform
     // the tile rect (placed at the origin) into the space of this
     // stacking context.
@@ -628,50 +635,58 @@ impl StackingContext {
             scrolls_overflow_area: scrolls_overflow_area,
             layer_info: layer_info,
             children: Vec::new(),
         }
     }
 
     pub fn hit_test<'a>(&self,
                         traversal: &mut DisplayListTraversal<'a>,
-                        point: &Point2D<Au>,
+                        translated_point: &Point2D<Au>,
+                        client_point: &Point2D<Au>,
                         scroll_offsets: &ScrollOffsetMap,
                         result: &mut Vec<DisplayItemMetadata>) {
+        let is_fixed = match self.layer_info {
+            Some(ref layer_info) => layer_info.scroll_policy == ScrollPolicy::FixedPosition,
+            None => false,
+        };
+
+        let effective_point = if is_fixed { client_point } else { translated_point };
+
         // Convert the point into stacking context local transform space.
         let mut point = if self.context_type == StackingContextType::Real {
-            let point = *point - self.bounds.origin;
+            let point = *effective_point - self.bounds.origin;
             let inv_transform = self.transform.invert();
             let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
                                                                          point.y.to_f32_px()));
             Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
         } else {
-            *point
+            *effective_point
         };
 
-        // Adjust the point to account for the scroll offset if necessary. This can only happen
-        // when WebRender is in use.
+        // Adjust the translated point to account for the scroll offset if
+        // necessary. This can only happen when WebRender is in use.
         //
-        // We don't perform this adjustment on the root stacking context because the DOM-side code
-        // has already translated the point for us (e.g. in `Document::handle_mouse_move_event()`)
-        // by now.
-        if self.id != StackingContextId::root() {
+        // We don't perform this adjustment on the root stacking context because
+        // the DOM-side code has already translated the point for us (e.g. in
+        // `Window::hit_test_query()`) by now.
+        if !is_fixed && self.id != StackingContextId::root() {
             if let Some(scroll_offset) = scroll_offsets.get(&self.id) {
                 point.x -= Au::from_f32_px(scroll_offset.x);
                 point.y -= Au::from_f32_px(scroll_offset.y);
             }
         }
 
         for child in self.children.iter() {
             while let Some(item) = traversal.advance(self) {
                 if let Some(meta) = item.hit_test(point) {
                     result.push(meta);
                 }
             }
-            child.hit_test(traversal, &point, scroll_offsets, result);
+            child.hit_test(traversal, translated_point, client_point, scroll_offsets, result);
         }
 
         while let Some(item) = traversal.advance(self) {
             if let Some(meta) = item.hit_test(point) {
                 result.push(meta);
             }
         }
     }
@@ -1361,16 +1376,17 @@ impl DisplayItem {
         }
         println!("{}+ {:?}", indent, self);
     }
 
     fn hit_test(&self, point: Point2D<Au>) -> Option<DisplayItemMetadata> {
         // TODO(pcwalton): Use a precise algorithm here. This will allow us to properly hit
         // test elements with `border-radius`, for example.
         let base_item = self.base();
+
         if !base_item.clip.might_intersect_point(&point) {
             // Clipped out.
             return None;
         }
         if !self.bounds().contains(&point) {
             // Can't possibly hit.
             return None;
         }
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -128,25 +128,31 @@ impl LayoutRPC for LayoutRPCImpl {
             };
             rw_data.constellation_chan.send(ConstellationMsg::SetCursor(cursor)).unwrap();
         }
         HitTestResponse {
             node_address: result.map(|dim| dim.node.to_untrusted_node_address()),
         }
     }
 
-    fn nodes_from_point(&self, point: Point2D<f32>) -> Vec<UntrustedNodeAddress> {
-        let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y));
+    fn nodes_from_point(&self,
+                        page_point: Point2D<f32>,
+                        client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress> {
+        let page_point = Point2D::new(Au::from_f32_px(page_point.x),
+                                      Au::from_f32_px(page_point.y));
+        let client_point = Point2D::new(Au::from_f32_px(client_point.x),
+                                        Au::from_f32_px(client_point.y));
+
         let nodes_from_point_list = {
             let &LayoutRPCImpl(ref rw_data) = self;
             let rw_data = rw_data.lock().unwrap();
             let result = match rw_data.display_list {
                 None => panic!("Tried to hit test without a DisplayList"),
                 Some(ref display_list) => {
-                    display_list.hit_test(&point, &rw_data.stacking_context_scroll_offsets)
+                    display_list.hit_test(&page_point, &client_point, &rw_data.stacking_context_scroll_offsets)
                 }
             };
 
             result
         };
 
         nodes_from_point_list.iter()
            .rev()
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -1033,17 +1033,17 @@ impl LayoutThread {
                 debug!("layout: No root node: bailing");
                 match data.query_type {
                     ReflowQueryType::ContentBoxQuery(_) => {
                         rw_data.content_box_response = Rect::zero();
                     },
                     ReflowQueryType::ContentBoxesQuery(_) => {
                         rw_data.content_boxes_response = Vec::new();
                     },
-                    ReflowQueryType::HitTestQuery(_, _) => {
+                    ReflowQueryType::HitTestQuery(..) => {
                         rw_data.hit_test_response = (None, false);
                     },
                     ReflowQueryType::NodeGeometryQuery(_) => {
                         rw_data.client_rect_response = Rect::zero();
                     },
                     ReflowQueryType::NodeLayerIdQuery(_) => {
                         rw_data.layer_id_response = None;
                     },
@@ -1197,22 +1197,31 @@ impl LayoutThread {
                 ReflowQueryType::ContentBoxQuery(node) => {
                     let node = unsafe { ServoLayoutNode::new(&node) };
                     rw_data.content_box_response = process_content_box_request(node, &mut root_flow);
                 },
                 ReflowQueryType::ContentBoxesQuery(node) => {
                     let node = unsafe { ServoLayoutNode::new(&node) };
                     rw_data.content_boxes_response = process_content_boxes_request(node, &mut root_flow);
                 },
-                ReflowQueryType::HitTestQuery(point, update_cursor) => {
-                    let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y));
+                ReflowQueryType::HitTestQuery(translated_point, client_point, update_cursor) => {
+                    let translated_point =
+                        Point2D::new(Au::from_f32_px(translated_point.x),
+                                     Au::from_f32_px(translated_point.y));
+
+                    let client_point =
+                        Point2D::new(Au::from_f32_px(client_point.x),
+                                     Au::from_f32_px(client_point.y));
+
                     let result = rw_data.display_list
                                         .as_ref()
                                         .expect("Tried to hit test with no display list")
-                                        .hit_test(&point, &rw_data.stacking_context_scroll_offsets);
+                                        .hit_test(&translated_point,
+                                                  &client_point,
+                                                  &rw_data.stacking_context_scroll_offsets);
                     rw_data.hit_test_response = (result.last().cloned(), update_cursor);
                 },
                 ReflowQueryType::NodeGeometryQuery(node) => {
                     let node = unsafe { ServoLayoutNode::new(&node) };
                     rw_data.client_rect_response = process_node_geometry_request(node, &mut root_flow);
                 },
                 ReflowQueryType::NodeScrollGeometryQuery(node) => {
                     let node = unsafe { ServoLayoutNode::new(&node) };
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -667,19 +667,17 @@ impl Document {
                               mouse_event_type: MouseEventType) {
         let mouse_event_type_string = match mouse_event_type {
             MouseEventType::Click => "click".to_owned(),
             MouseEventType::MouseUp => "mouseup".to_owned(),
             MouseEventType::MouseDown => "mousedown".to_owned(),
         };
         debug!("{}: at {:?}", mouse_event_type_string, client_point);
 
-        let page_point = Point2D::new(client_point.x + self.window.PageXOffset() as f32,
-                                      client_point.y + self.window.PageYOffset() as f32);
-        let node = match self.window.hit_test_query(page_point, false) {
+        let node = match self.window.hit_test_query(client_point, false) {
             Some(node_address) => {
                 debug!("node address is {:?}", node_address);
                 node::from_untrusted_node_address(js_runtime, node_address)
             },
             None => return,
         };
 
         let el = match node.downcast::<Element>() {
@@ -781,19 +779,17 @@ impl Document {
         let phase_before = self.touchpad_pressure_phase.get();
         self.touchpad_pressure_phase.set(phase_now);
 
         if phase_before == TouchpadPressurePhase::BeforeClick &&
            phase_now == TouchpadPressurePhase::BeforeClick {
             return;
         }
 
-        let page_point = Point2D::new(client_point.x + self.window.PageXOffset() as f32,
-                                      client_point.y + self.window.PageYOffset() as f32);
-        let node = match self.window.hit_test_query(page_point, false) {
+        let node = match self.window.hit_test_query(client_point, false) {
             Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address),
             None => return
         };
 
         let el = match node.downcast::<Element>() {
             Some(el) => Root::from_ref(el),
             None => {
                 let parent = node.GetParentNode();
@@ -859,32 +855,27 @@ impl Document {
         let event = mouse_event.upcast::<Event>();
         event.fire(target);
     }
 
     pub fn handle_mouse_move_event(&self,
                                    js_runtime: *mut JSRuntime,
                                    client_point: Option<Point2D<f32>>,
                                    prev_mouse_over_target: &MutNullableHeap<JS<Element>>) {
-        let page_point = match client_point {
+        let client_point = match client_point {
             None => {
                 // If there's no point, there's no target under the mouse
                 // FIXME: dispatch mouseout here. We have no point.
                 prev_mouse_over_target.set(None);
                 return;
             }
-            Some(ref client_point) => {
-                Point2D::new(client_point.x + self.window.PageXOffset() as f32,
-                             client_point.y + self.window.PageYOffset() as f32)
-            }
+            Some(client_point) => client_point,
         };
 
-        let client_point = client_point.unwrap();
-
-        let maybe_new_target = self.window.hit_test_query(page_point, true).and_then(|address| {
+        let maybe_new_target = self.window.hit_test_query(client_point, true).and_then(|address| {
             let node = node::from_untrusted_node_address(js_runtime, address);
             node.inclusive_ancestors()
                 .filter_map(Root::downcast::<Element>)
                 .next()
         });
 
         // Send mousemove event to topmost target, and forward it if it's an iframe
         if let Some(ref new_target) = maybe_new_target {
@@ -1588,17 +1579,21 @@ impl Document {
     }
 
     /// https://html.spec.whatwg.org/multipage/#cookie-averse-document-object
     pub fn is_cookie_averse(&self) -> bool {
         self.browsing_context.is_none() || !url_has_network_scheme(&self.url)
     }
 
     pub fn nodes_from_point(&self, page_point: &Point2D<f32>) -> Vec<UntrustedNodeAddress> {
-        self.window.layout().nodes_from_point(*page_point)
+        let client_point =
+            Point2D::new(page_point.x - self.window.PageXOffset() as f32,
+                         page_point.y - self.window.PageYOffset() as f32);
+
+        self.window.layout().nodes_from_point(*page_point, client_point)
     }
 }
 
 #[derive(PartialEq, HeapSizeOf)]
 pub enum DocumentSource {
     FromParser,
     NotFromParser,
 }
@@ -2819,20 +2814,20 @@ impl DocumentMethods for Document {
         if self.browsing_context().is_none() {
             return None;
         }
 
         if x < 0.0 || y < 0.0 || x > viewport.width.get() || y > viewport.height.get() {
             return None;
         }
 
-        let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) };
-
         match self.window.hit_test_query(*point, false) {
             Some(untrusted_node_address) => {
+                let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) };
+
                 let node = node::from_untrusted_node_address(js_runtime, untrusted_node_address);
                 let parent_node = node.GetParentNode().unwrap();
                 let element_ref = node.downcast::<Element>().unwrap_or_else(|| {
                     parent_node.downcast::<Element>().unwrap()
                 });
 
                 Some(Root::from_ref(element_ref))
             },
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -1266,20 +1266,28 @@ impl Window {
         if !self.reflow(ReflowGoal::ForScriptQuery,
                         ReflowQueryType::NodeGeometryQuery(node_geometry_request),
                         ReflowReason::Query) {
             return Rect::zero();
         }
         self.layout_rpc.node_geometry().client_rect
     }
 
-    pub fn hit_test_query(&self, hit_test_request: Point2D<f32>, update_cursor: bool)
+    pub fn hit_test_query(&self,
+                          client_point: Point2D<f32>,
+                          update_cursor: bool)
                           -> Option<UntrustedNodeAddress> {
+        let translated_point =
+            Point2D::new(client_point.x + self.PageXOffset() as f32,
+                         client_point.y + self.PageYOffset() as f32);
+
         if !self.reflow(ReflowGoal::ForScriptQuery,
-                        ReflowQueryType::HitTestQuery(hit_test_request, update_cursor),
+                        ReflowQueryType::HitTestQuery(translated_point,
+                                                      client_point,
+                                                      update_cursor),
                         ReflowReason::Query) {
             return None
         }
 
         self.layout_rpc.hit_test().node_address
     }
 
     pub fn scroll_area_query(&self, node: TrustedNodeAddress) -> Rect<i32> {
@@ -1765,17 +1773,17 @@ fn debug_reflow_events(id: PipelineId, g
         ReflowGoal::ForDisplay => "\tForDisplay",
         ReflowGoal::ForScriptQuery => "\tForScriptQuery",
     });
 
     debug_msg.push_str(match *query_type {
         ReflowQueryType::NoQuery => "\tNoQuery",
         ReflowQueryType::ContentBoxQuery(_n) => "\tContentBoxQuery",
         ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
-        ReflowQueryType::HitTestQuery(_n, _o) => "\tHitTestQuery",
+        ReflowQueryType::HitTestQuery(..) => "\tHitTestQuery",
         ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
         ReflowQueryType::NodeLayerIdQuery(_n) => "\tNodeLayerIdQuery",
         ReflowQueryType::NodeOverflowQuery(_n) => "\tNodeOverFlowQuery",
         ReflowQueryType::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery",
         ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
         ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",
         ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery",
     });
--- a/servo/components/script_layout_interface/message.rs
+++ b/servo/components/script_layout_interface/message.rs
@@ -93,17 +93,17 @@ pub enum Msg {
 
 /// Any query to perform with this reflow.
 #[derive(PartialEq)]
 pub enum ReflowQueryType {
     NoQuery,
     ContentBoxQuery(TrustedNodeAddress),
     ContentBoxesQuery(TrustedNodeAddress),
     NodeOverflowQuery(TrustedNodeAddress),
-    HitTestQuery(Point2D<f32>, bool),
+    HitTestQuery(Point2D<f32>, Point2D<f32>, bool),
     NodeGeometryQuery(TrustedNodeAddress),
     NodeLayerIdQuery(TrustedNodeAddress),
     NodeScrollGeometryQuery(TrustedNodeAddress),
     ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, Atom),
     OffsetParentQuery(TrustedNodeAddress),
     MarginStyleQuery(TrustedNodeAddress),
 }
 
--- a/servo/components/script_layout_interface/rpc.rs
+++ b/servo/components/script_layout_interface/rpc.rs
@@ -33,17 +33,17 @@ pub trait LayoutRPC {
     /// Requests the node containing the point of interest
     fn hit_test(&self) -> HitTestResponse;
     /// Query layout for the resolved value of a given CSS property
     fn resolved_style(&self) -> ResolvedStyleResponse;
     fn offset_parent(&self) -> OffsetParentResponse;
     /// Query layout for the resolve values of the margin properties for an element.
     fn margin_style(&self) -> MarginStyleResponse;
 
-    fn nodes_from_point(&self, point: Point2D<f32>) -> Vec<UntrustedNodeAddress>;
+    fn nodes_from_point(&self, page_point: Point2D<f32>, client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress>;
 }
 
 pub struct ContentBoxResponse(pub Rect<Au>);
 
 pub struct ContentBoxesResponse(pub Vec<Rect<Au>>);
 
 pub struct NodeGeometryResponse {
     pub client_rect: Rect<i32>,