mirror of
https://github.com/servo/servo.git
synced 2025-08-01 19:50:30 +01:00
layout: Take into account the client point for fixed positioned stacking contexts.
This commit is contained in:
parent
c420a870c1
commit
28d7c2dca8
7 changed files with 79 additions and 45 deletions
|
@ -61,7 +61,7 @@ pub static BLUR_INFLATION_FACTOR: i32 = 3;
|
||||||
/// LayerInfo is used to store PaintLayer metadata during DisplayList construction.
|
/// LayerInfo is used to store PaintLayer metadata during DisplayList construction.
|
||||||
/// It is also used for tracking LayerIds when creating layers to preserve ordering when
|
/// It is also used for tracking LayerIds when creating layers to preserve ordering when
|
||||||
/// layered DisplayItems should render underneath unlayered DisplayItems.
|
/// layered DisplayItems should render underneath unlayered DisplayItems.
|
||||||
#[derive(Clone, Copy, HeapSizeOf, Deserialize, Serialize)]
|
#[derive(Clone, Copy, HeapSizeOf, Deserialize, Serialize, Debug)]
|
||||||
pub struct LayerInfo {
|
pub struct LayerInfo {
|
||||||
/// The base LayerId of this layer.
|
/// The base LayerId of this layer.
|
||||||
pub layer_id: LayerId,
|
pub layer_id: LayerId,
|
||||||
|
@ -133,7 +133,7 @@ impl<'a> DisplayListTraversal<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(HeapSizeOf, Deserialize, Serialize)]
|
#[derive(HeapSizeOf, Deserialize, Serialize, Debug)]
|
||||||
pub struct StackingContextOffsets {
|
pub struct StackingContextOffsets {
|
||||||
pub start: u32,
|
pub start: u32,
|
||||||
pub block_backgrounds_and_borders: u32,
|
pub block_backgrounds_and_borders: u32,
|
||||||
|
@ -508,9 +508,12 @@ impl DisplayList {
|
||||||
&draw_target, &stacking_context.filters, stacking_context.blend_mode);
|
&draw_target, &stacking_context.filters, stacking_context.blend_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all nodes containing the point of interest, bottommost first,
|
/// Return all nodes containing the point of interest, bottommost first, and
|
||||||
/// and respecting the `pointer-events` CSS property.
|
/// respecting the `pointer-events` CSS property.
|
||||||
pub fn hit_test(&self, point: &Point2D<Au>, scroll_offsets: &ScrollOffsetMap)
|
pub fn hit_test(&self,
|
||||||
|
translated_point: &Point2D<Au>,
|
||||||
|
client_point: &Point2D<Au>,
|
||||||
|
scroll_offsets: &ScrollOffsetMap)
|
||||||
-> Vec<DisplayItemMetadata> {
|
-> Vec<DisplayItemMetadata> {
|
||||||
let mut traversal = DisplayListTraversal {
|
let mut traversal = DisplayListTraversal {
|
||||||
display_list: self,
|
display_list: self,
|
||||||
|
@ -518,7 +521,11 @@ impl DisplayList {
|
||||||
last_item_index: self.list.len() - 1,
|
last_item_index: self.list.len() - 1,
|
||||||
};
|
};
|
||||||
let mut result = Vec::new();
|
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
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -633,27 +640,35 @@ impl StackingContext {
|
||||||
|
|
||||||
pub fn hit_test<'a>(&self,
|
pub fn hit_test<'a>(&self,
|
||||||
traversal: &mut DisplayListTraversal<'a>,
|
traversal: &mut DisplayListTraversal<'a>,
|
||||||
point: &Point2D<Au>,
|
translated_point: &Point2D<Au>,
|
||||||
|
client_point: &Point2D<Au>,
|
||||||
scroll_offsets: &ScrollOffsetMap,
|
scroll_offsets: &ScrollOffsetMap,
|
||||||
result: &mut Vec<DisplayItemMetadata>) {
|
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.
|
// Convert the point into stacking context local transform space.
|
||||||
let mut point = if self.context_type == StackingContextType::Real {
|
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 inv_transform = self.transform.invert();
|
||||||
let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
|
let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
|
||||||
point.y.to_f32_px()));
|
point.y.to_f32_px()));
|
||||||
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
|
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
|
||||||
} else {
|
} else {
|
||||||
*point
|
*effective_point
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adjust the point to account for the scroll offset if necessary. This can only happen
|
// Adjust the translated point to account for the scroll offset if
|
||||||
// when WebRender is in use.
|
// 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
|
// We don't perform this adjustment on the root stacking context because
|
||||||
// has already translated the point for us (e.g. in `Document::handle_mouse_move_event()`)
|
// the DOM-side code has already translated the point for us (e.g. in
|
||||||
// by now.
|
// `Window::hit_test_query()`) by now.
|
||||||
if self.id != StackingContextId::root() {
|
if !is_fixed && self.id != StackingContextId::root() {
|
||||||
if let Some(scroll_offset) = scroll_offsets.get(&self.id) {
|
if let Some(scroll_offset) = scroll_offsets.get(&self.id) {
|
||||||
point.x -= Au::from_f32_px(scroll_offset.x);
|
point.x -= Au::from_f32_px(scroll_offset.x);
|
||||||
point.y -= Au::from_f32_px(scroll_offset.y);
|
point.y -= Au::from_f32_px(scroll_offset.y);
|
||||||
|
@ -666,7 +681,7 @@ impl StackingContext {
|
||||||
result.push(meta);
|
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) {
|
while let Some(item) = traversal.advance(self) {
|
||||||
|
@ -1366,6 +1381,7 @@ impl DisplayItem {
|
||||||
// TODO(pcwalton): Use a precise algorithm here. This will allow us to properly hit
|
// TODO(pcwalton): Use a precise algorithm here. This will allow us to properly hit
|
||||||
// test elements with `border-radius`, for example.
|
// test elements with `border-radius`, for example.
|
||||||
let base_item = self.base();
|
let base_item = self.base();
|
||||||
|
|
||||||
if !base_item.clip.might_intersect_point(&point) {
|
if !base_item.clip.might_intersect_point(&point) {
|
||||||
// Clipped out.
|
// Clipped out.
|
||||||
return None;
|
return None;
|
||||||
|
|
|
@ -133,15 +133,21 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nodes_from_point(&self, point: Point2D<f32>) -> Vec<UntrustedNodeAddress> {
|
fn nodes_from_point(&self,
|
||||||
let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y));
|
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 nodes_from_point_list = {
|
||||||
let &LayoutRPCImpl(ref rw_data) = self;
|
let &LayoutRPCImpl(ref rw_data) = self;
|
||||||
let rw_data = rw_data.lock().unwrap();
|
let rw_data = rw_data.lock().unwrap();
|
||||||
let result = match rw_data.display_list {
|
let result = match rw_data.display_list {
|
||||||
None => panic!("Tried to hit test without a DisplayList"),
|
None => panic!("Tried to hit test without a DisplayList"),
|
||||||
Some(ref display_list) => {
|
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)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1038,7 +1038,7 @@ impl LayoutThread {
|
||||||
ReflowQueryType::ContentBoxesQuery(_) => {
|
ReflowQueryType::ContentBoxesQuery(_) => {
|
||||||
rw_data.content_boxes_response = Vec::new();
|
rw_data.content_boxes_response = Vec::new();
|
||||||
},
|
},
|
||||||
ReflowQueryType::HitTestQuery(_, _) => {
|
ReflowQueryType::HitTestQuery(..) => {
|
||||||
rw_data.hit_test_response = (None, false);
|
rw_data.hit_test_response = (None, false);
|
||||||
},
|
},
|
||||||
ReflowQueryType::NodeGeometryQuery(_) => {
|
ReflowQueryType::NodeGeometryQuery(_) => {
|
||||||
|
@ -1202,12 +1202,21 @@ impl LayoutThread {
|
||||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||||
rw_data.content_boxes_response = process_content_boxes_request(node, &mut root_flow);
|
rw_data.content_boxes_response = process_content_boxes_request(node, &mut root_flow);
|
||||||
},
|
},
|
||||||
ReflowQueryType::HitTestQuery(point, update_cursor) => {
|
ReflowQueryType::HitTestQuery(translated_point, client_point, update_cursor) => {
|
||||||
let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y));
|
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
|
let result = rw_data.display_list
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Tried to hit test with no display list")
|
.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);
|
rw_data.hit_test_response = (result.last().cloned(), update_cursor);
|
||||||
},
|
},
|
||||||
ReflowQueryType::NodeGeometryQuery(node) => {
|
ReflowQueryType::NodeGeometryQuery(node) => {
|
||||||
|
|
|
@ -672,9 +672,7 @@ impl Document {
|
||||||
};
|
};
|
||||||
debug!("{}: at {:?}", mouse_event_type_string, client_point);
|
debug!("{}: at {:?}", mouse_event_type_string, client_point);
|
||||||
|
|
||||||
let page_point = Point2D::new(client_point.x + self.window.PageXOffset() as f32,
|
let node = match self.window.hit_test_query(client_point, false) {
|
||||||
client_point.y + self.window.PageYOffset() as f32);
|
|
||||||
let node = match self.window.hit_test_query(page_point, false) {
|
|
||||||
Some(node_address) => {
|
Some(node_address) => {
|
||||||
debug!("node address is {:?}", node_address);
|
debug!("node address is {:?}", node_address);
|
||||||
node::from_untrusted_node_address(js_runtime, node_address)
|
node::from_untrusted_node_address(js_runtime, node_address)
|
||||||
|
@ -786,9 +784,7 @@ impl Document {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let page_point = Point2D::new(client_point.x + self.window.PageXOffset() as f32,
|
let node = match self.window.hit_test_query(client_point, false) {
|
||||||
client_point.y + self.window.PageYOffset() as f32);
|
|
||||||
let node = match self.window.hit_test_query(page_point, false) {
|
|
||||||
Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address),
|
Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address),
|
||||||
None => return
|
None => return
|
||||||
};
|
};
|
||||||
|
@ -864,22 +860,17 @@ impl Document {
|
||||||
js_runtime: *mut JSRuntime,
|
js_runtime: *mut JSRuntime,
|
||||||
client_point: Option<Point2D<f32>>,
|
client_point: Option<Point2D<f32>>,
|
||||||
prev_mouse_over_target: &MutNullableHeap<JS<Element>>) {
|
prev_mouse_over_target: &MutNullableHeap<JS<Element>>) {
|
||||||
let page_point = match client_point {
|
let client_point = match client_point {
|
||||||
None => {
|
None => {
|
||||||
// If there's no point, there's no target under the mouse
|
// If there's no point, there's no target under the mouse
|
||||||
// FIXME: dispatch mouseout here. We have no point.
|
// FIXME: dispatch mouseout here. We have no point.
|
||||||
prev_mouse_over_target.set(None);
|
prev_mouse_over_target.set(None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Some(ref client_point) => {
|
Some(client_point) => client_point,
|
||||||
Point2D::new(client_point.x + self.window.PageXOffset() as f32,
|
|
||||||
client_point.y + self.window.PageYOffset() as f32)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let client_point = client_point.unwrap();
|
let maybe_new_target = self.window.hit_test_query(client_point, true).and_then(|address| {
|
||||||
|
|
||||||
let maybe_new_target = self.window.hit_test_query(page_point, true).and_then(|address| {
|
|
||||||
let node = node::from_untrusted_node_address(js_runtime, address);
|
let node = node::from_untrusted_node_address(js_runtime, address);
|
||||||
node.inclusive_ancestors()
|
node.inclusive_ancestors()
|
||||||
.filter_map(Root::downcast::<Element>)
|
.filter_map(Root::downcast::<Element>)
|
||||||
|
@ -1593,7 +1584,11 @@ impl Document {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nodes_from_point(&self, page_point: &Point2D<f32>) -> Vec<UntrustedNodeAddress> {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2824,10 +2819,10 @@ impl DocumentMethods for Document {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) };
|
|
||||||
|
|
||||||
match self.window.hit_test_query(*point, false) {
|
match self.window.hit_test_query(*point, false) {
|
||||||
Some(untrusted_node_address) => {
|
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 node = node::from_untrusted_node_address(js_runtime, untrusted_node_address);
|
||||||
let parent_node = node.GetParentNode().unwrap();
|
let parent_node = node.GetParentNode().unwrap();
|
||||||
let element_ref = node.downcast::<Element>().unwrap_or_else(|| {
|
let element_ref = node.downcast::<Element>().unwrap_or_else(|| {
|
||||||
|
|
|
@ -1271,10 +1271,18 @@ impl Window {
|
||||||
self.layout_rpc.node_geometry().client_rect
|
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> {
|
-> 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,
|
if !self.reflow(ReflowGoal::ForScriptQuery,
|
||||||
ReflowQueryType::HitTestQuery(hit_test_request, update_cursor),
|
ReflowQueryType::HitTestQuery(translated_point,
|
||||||
|
client_point,
|
||||||
|
update_cursor),
|
||||||
ReflowReason::Query) {
|
ReflowReason::Query) {
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
|
@ -1770,7 +1778,7 @@ fn debug_reflow_events(id: PipelineId, goal: &ReflowGoal, query_type: &ReflowQue
|
||||||
ReflowQueryType::NoQuery => "\tNoQuery",
|
ReflowQueryType::NoQuery => "\tNoQuery",
|
||||||
ReflowQueryType::ContentBoxQuery(_n) => "\tContentBoxQuery",
|
ReflowQueryType::ContentBoxQuery(_n) => "\tContentBoxQuery",
|
||||||
ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
|
ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
|
||||||
ReflowQueryType::HitTestQuery(_n, _o) => "\tHitTestQuery",
|
ReflowQueryType::HitTestQuery(..) => "\tHitTestQuery",
|
||||||
ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
|
ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
|
||||||
ReflowQueryType::NodeLayerIdQuery(_n) => "\tNodeLayerIdQuery",
|
ReflowQueryType::NodeLayerIdQuery(_n) => "\tNodeLayerIdQuery",
|
||||||
ReflowQueryType::NodeOverflowQuery(_n) => "\tNodeOverFlowQuery",
|
ReflowQueryType::NodeOverflowQuery(_n) => "\tNodeOverFlowQuery",
|
||||||
|
|
|
@ -98,7 +98,7 @@ pub enum ReflowQueryType {
|
||||||
ContentBoxQuery(TrustedNodeAddress),
|
ContentBoxQuery(TrustedNodeAddress),
|
||||||
ContentBoxesQuery(TrustedNodeAddress),
|
ContentBoxesQuery(TrustedNodeAddress),
|
||||||
NodeOverflowQuery(TrustedNodeAddress),
|
NodeOverflowQuery(TrustedNodeAddress),
|
||||||
HitTestQuery(Point2D<f32>, bool),
|
HitTestQuery(Point2D<f32>, Point2D<f32>, bool),
|
||||||
NodeGeometryQuery(TrustedNodeAddress),
|
NodeGeometryQuery(TrustedNodeAddress),
|
||||||
NodeLayerIdQuery(TrustedNodeAddress),
|
NodeLayerIdQuery(TrustedNodeAddress),
|
||||||
NodeScrollGeometryQuery(TrustedNodeAddress),
|
NodeScrollGeometryQuery(TrustedNodeAddress),
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub trait LayoutRPC {
|
||||||
/// Query layout for the resolve values of the margin properties for an element.
|
/// Query layout for the resolve values of the margin properties for an element.
|
||||||
fn margin_style(&self) -> MarginStyleResponse;
|
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 ContentBoxResponse(pub Rect<Au>);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue