mirror of
https://github.com/servo/servo.git
synced 2025-09-20 11:50:09 +01:00
layout: Take sticky offsets into account for offset queries (#39385)
`offsetLeft` and `offsetTop` were ignoring that sticky positioned boxes can be shifted out of their normal position. Testing: Various test improvements. Signed-off-by: Oriol Brufau <obrufau@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
754c938722
commit
2c3d580ef1
21 changed files with 69 additions and 118 deletions
|
@ -569,4 +569,8 @@ impl BoxFragment {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn spatial_tree_node(&self) -> Option<ScrollTreeNodeId> {
|
||||||
|
*self.spatial_tree_node.borrow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,7 +322,12 @@ impl Layout for LayoutThread {
|
||||||
#[servo_tracing::instrument(skip_all)]
|
#[servo_tracing::instrument(skip_all)]
|
||||||
fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse {
|
fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse {
|
||||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||||
process_offset_parent_query(node).unwrap_or_default()
|
let stacking_context_tree = self.stacking_context_tree.borrow();
|
||||||
|
let stacking_context_tree = stacking_context_tree
|
||||||
|
.as_ref()
|
||||||
|
.expect("Should always have a StackingContextTree for offset parent queries");
|
||||||
|
process_offset_parent_query(&stacking_context_tree.compositor_info.scroll_tree, node)
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[servo_tracing::instrument(skip_all)]
|
#[servo_tracing::instrument(skip_all)]
|
||||||
|
@ -1608,26 +1613,26 @@ impl ReflowPhases {
|
||||||
/// so [`ReflowPhases::empty()`] implies that.
|
/// so [`ReflowPhases::empty()`] implies that.
|
||||||
fn necessary(reflow_goal: &ReflowGoal) -> Self {
|
fn necessary(reflow_goal: &ReflowGoal) -> Self {
|
||||||
match reflow_goal {
|
match reflow_goal {
|
||||||
ReflowGoal::UpdateTheRendering | ReflowGoal::UpdateScrollNode(..) => {
|
|
||||||
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
|
|
||||||
},
|
|
||||||
ReflowGoal::LayoutQuery(query) => match query {
|
ReflowGoal::LayoutQuery(query) => match query {
|
||||||
QueryMsg::NodesFromPointQuery => {
|
QueryMsg::NodesFromPointQuery => {
|
||||||
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
|
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
|
||||||
},
|
},
|
||||||
QueryMsg::BoxArea |
|
QueryMsg::BoxArea |
|
||||||
QueryMsg::BoxAreas |
|
QueryMsg::BoxAreas |
|
||||||
|
QueryMsg::ElementsFromPoint |
|
||||||
|
QueryMsg::OffsetParentQuery |
|
||||||
QueryMsg::ResolvedStyleQuery |
|
QueryMsg::ResolvedStyleQuery |
|
||||||
QueryMsg::ScrollingAreaOrOffsetQuery |
|
QueryMsg::ScrollingAreaOrOffsetQuery => Self::StackingContextTreeConstruction,
|
||||||
QueryMsg::ElementsFromPoint => Self::StackingContextTreeConstruction,
|
|
||||||
QueryMsg::ClientRectQuery |
|
QueryMsg::ClientRectQuery |
|
||||||
QueryMsg::ElementInnerOuterTextQuery |
|
QueryMsg::ElementInnerOuterTextQuery |
|
||||||
QueryMsg::InnerWindowDimensionsQuery |
|
QueryMsg::InnerWindowDimensionsQuery |
|
||||||
QueryMsg::OffsetParentQuery |
|
|
||||||
QueryMsg::ScrollParentQuery |
|
|
||||||
QueryMsg::ResolvedFontStyleQuery |
|
QueryMsg::ResolvedFontStyleQuery |
|
||||||
QueryMsg::TextIndexQuery |
|
QueryMsg::ScrollParentQuery |
|
||||||
QueryMsg::StyleQuery => Self::empty(),
|
QueryMsg::StyleQuery |
|
||||||
|
QueryMsg::TextIndexQuery => Self::empty(),
|
||||||
|
},
|
||||||
|
ReflowGoal::UpdateScrollNode(..) | ReflowGoal::UpdateTheRendering => {
|
||||||
|
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,7 @@ fn root_transform_for_layout_node(
|
||||||
.first()
|
.first()
|
||||||
.and_then(Fragment::retrieve_box_fragment)?
|
.and_then(Fragment::retrieve_box_fragment)?
|
||||||
.borrow();
|
.borrow();
|
||||||
let scroll_tree_node_id = box_fragment.spatial_tree_node.borrow();
|
let scroll_tree_node_id = box_fragment.spatial_tree_node()?;
|
||||||
let scroll_tree_node_id = (*scroll_tree_node_id)?;
|
|
||||||
Some(scroll_tree.cumulative_node_to_root_transform(scroll_tree_node_id))
|
Some(scroll_tree.cumulative_node_to_root_transform(scroll_tree_node_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,7 +564,10 @@ fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFrag
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetParentResponse> {
|
pub fn process_offset_parent_query(
|
||||||
|
scroll_tree: &ScrollTree,
|
||||||
|
node: ServoLayoutNode<'_>,
|
||||||
|
) -> Option<OffsetParentResponse> {
|
||||||
// Only consider the first fragment of the node found as per a
|
// Only consider the first fragment of the node found as per a
|
||||||
// possible interpretation of the specification: "[...] return the
|
// possible interpretation of the specification: "[...] return the
|
||||||
// y-coordinate of the top border edge of the first CSS layout box
|
// y-coordinate of the top border edge of the first CSS layout box
|
||||||
|
@ -588,6 +590,16 @@ pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetPa
|
||||||
.first()
|
.first()
|
||||||
.cloned()?;
|
.cloned()?;
|
||||||
let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
|
let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
|
||||||
|
let cumulative_sticky_offsets = fragment
|
||||||
|
.retrieve_box_fragment()
|
||||||
|
.and_then(|box_fragment| box_fragment.borrow().spatial_tree_node())
|
||||||
|
.map(|node_id| {
|
||||||
|
scroll_tree
|
||||||
|
.cumulative_sticky_offsets(node_id)
|
||||||
|
.map(Au::from_f32_px)
|
||||||
|
.cast_unit()
|
||||||
|
});
|
||||||
|
border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
|
||||||
|
|
||||||
// 2. If the offsetParent of the element is null return the x-coordinate of the left
|
// 2. If the offsetParent of the element is null return the x-coordinate of the left
|
||||||
// border edge of the first CSS layout box associated with the element, relative to
|
// border edge of the first CSS layout box associated with the element, relative to
|
||||||
|
@ -638,7 +650,18 @@ pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetPa
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
|
parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
|
||||||
};
|
}
|
||||||
|
.translate(
|
||||||
|
cumulative_sticky_offsets
|
||||||
|
.and_then(|_| parent_fragment.spatial_tree_node())
|
||||||
|
.map(|node_id| {
|
||||||
|
scroll_tree
|
||||||
|
.cumulative_sticky_offsets(node_id)
|
||||||
|
.map(Au::from_f32_px)
|
||||||
|
.cast_unit()
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
|
border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
|
||||||
|
|
||||||
|
|
|
@ -283,12 +283,14 @@ impl ScrollableNodeInfo {
|
||||||
pub struct ScrollTreeNodeTransformationCache {
|
pub struct ScrollTreeNodeTransformationCache {
|
||||||
node_to_root_transform: FastLayoutTransform,
|
node_to_root_transform: FastLayoutTransform,
|
||||||
root_to_node_transform: Option<FastLayoutTransform>,
|
root_to_node_transform: Option<FastLayoutTransform>,
|
||||||
|
cumulative_sticky_offsets: LayoutVector2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct AncestorStickyInfo {
|
struct AncestorStickyInfo {
|
||||||
nearest_scrolling_ancestor_offset: LayoutVector2D,
|
nearest_scrolling_ancestor_offset: LayoutVector2D,
|
||||||
nearest_scrolling_ancestor_viewport: LayoutRect,
|
nearest_scrolling_ancestor_viewport: LayoutRect,
|
||||||
|
cumulative_sticky_offsets: LayoutVector2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
|
#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||||
|
@ -610,14 +612,8 @@ impl ScrollTree {
|
||||||
&self,
|
&self,
|
||||||
node_id: ScrollTreeNodeId,
|
node_id: ScrollTreeNodeId,
|
||||||
) -> FastLayoutTransform {
|
) -> FastLayoutTransform {
|
||||||
let node = self.get_node(node_id);
|
self.cumulative_node_transform(node_id)
|
||||||
if let Some(cached_transforms) = node.transformation_cache.get() {
|
.node_to_root_transform
|
||||||
return cached_transforms.node_to_root_transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (transforms, _) = self.cumulative_node_transform_inner(node);
|
|
||||||
node.transformation_cache.set(Some(transforms));
|
|
||||||
transforms.node_to_root_transform
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a transformation that can convert a point in the root coordinate system to a
|
/// Find a transformation that can convert a point in the root coordinate system to a
|
||||||
|
@ -627,14 +623,29 @@ impl ScrollTree {
|
||||||
&self,
|
&self,
|
||||||
node_id: ScrollTreeNodeId,
|
node_id: ScrollTreeNodeId,
|
||||||
) -> Option<FastLayoutTransform> {
|
) -> Option<FastLayoutTransform> {
|
||||||
|
self.cumulative_node_transform(node_id)
|
||||||
|
.root_to_node_transform
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the cumulative offsets of sticky positioned boxes from the given node up to
|
||||||
|
/// the root.
|
||||||
|
pub fn cumulative_sticky_offsets(&self, node_id: ScrollTreeNodeId) -> LayoutVector2D {
|
||||||
|
self.cumulative_node_transform(node_id)
|
||||||
|
.cumulative_sticky_offsets
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cumulative_node_transform(
|
||||||
|
&self,
|
||||||
|
node_id: ScrollTreeNodeId,
|
||||||
|
) -> ScrollTreeNodeTransformationCache {
|
||||||
let node = self.get_node(node_id);
|
let node = self.get_node(node_id);
|
||||||
if let Some(cached_transforms) = node.transformation_cache.get() {
|
if let Some(cached_transforms) = node.transformation_cache.get() {
|
||||||
return cached_transforms.root_to_node_transform;
|
return cached_transforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (transforms, _) = self.cumulative_node_transform_inner(node);
|
let (transforms, _) = self.cumulative_node_transform_inner(node);
|
||||||
node.transformation_cache.set(Some(transforms));
|
node.transformation_cache.set(Some(transforms));
|
||||||
transforms.root_to_node_transform
|
transforms
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Traverse a scroll node to its root to calculate the transform.
|
/// Traverse a scroll node to its root to calculate the transform.
|
||||||
|
@ -678,6 +689,7 @@ impl ScrollTree {
|
||||||
SpatialTreeNodeInfo::Sticky(info) => {
|
SpatialTreeNodeInfo::Sticky(info) => {
|
||||||
let offset = info.calculate_sticky_offset(&sticky_info);
|
let offset = info.calculate_sticky_offset(&sticky_info);
|
||||||
sticky_info.nearest_scrolling_ancestor_offset += offset;
|
sticky_info.nearest_scrolling_ancestor_offset += offset;
|
||||||
|
sticky_info.cumulative_sticky_offsets += offset;
|
||||||
let offset_transform = FastLayoutTransform::Offset(offset);
|
let offset_transform = FastLayoutTransform::Offset(offset);
|
||||||
(offset_transform, offset_transform.inverse())
|
(offset_transform, offset_transform.inverse())
|
||||||
},
|
},
|
||||||
|
@ -696,6 +708,7 @@ impl ScrollTree {
|
||||||
let transforms = ScrollTreeNodeTransformationCache {
|
let transforms = ScrollTreeNodeTransformationCache {
|
||||||
node_to_root_transform,
|
node_to_root_transform,
|
||||||
root_to_node_transform,
|
root_to_node_transform,
|
||||||
|
cumulative_sticky_offsets: sticky_info.cumulative_sticky_offsets,
|
||||||
};
|
};
|
||||||
(transforms, sticky_info)
|
(transforms, sticky_info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[position-sticky-bottom.html]
|
|
||||||
[after reaching the sticking point the sticky box should be offset]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[the sticky box should not be pushed outside its containing block]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[position-sticky-change-top.html]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[position-sticky-flexbox.html]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[position-sticky-inflow-position.html]
|
|
||||||
[sticky offset should not affect the position of other elements.]
|
|
||||||
expected: FAIL
|
|
|
@ -1,6 +0,0 @@
|
||||||
[position-sticky-left.html]
|
|
||||||
[after reaching the sticking point the sticky box should be offset]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[the sticky box should not be pushed outside its containing block]
|
|
||||||
expected: FAIL
|
|
|
@ -1,6 +1,3 @@
|
||||||
[position-sticky-margins.html]
|
[position-sticky-margins.html]
|
||||||
[Whilst stuck, the margin is irrelevant.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[The margin is taken into account when making sure the sticky element does not escape its container]
|
[The margin is taken into account when making sure the sticky element does not escape its container]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
[position-sticky-nested-bottom.html]
|
|
||||||
[both sticky boxes can be stuck at the same time]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[neither sticky can escape their containing block]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[the inner sticky cannot be pushed outside the outer sticky]
|
|
||||||
expected: FAIL
|
|
|
@ -1,12 +0,0 @@
|
||||||
[position-sticky-nested-left.html]
|
|
||||||
[the inner sticky can stick before the outer one if necessary]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[both sticky boxes can be stuck at the same time]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[neither sticky can escape their containing block]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[the inner sticky cannot be pushed outside the outer sticky]
|
|
||||||
expected: FAIL
|
|
|
@ -1,9 +0,0 @@
|
||||||
[position-sticky-nested-right.html]
|
|
||||||
[both sticky boxes can be stuck at the same time]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[neither sticky can escape their containing block]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[the inner sticky cannot be pushed outside the outer sticky]
|
|
||||||
expected: FAIL
|
|
|
@ -1,12 +0,0 @@
|
||||||
[position-sticky-nested-top.html]
|
|
||||||
[the inner sticky can stick before the outer one if necessary]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[both sticky boxes can be stuck at the same time]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[neither sticky can escape their containing block]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[the inner sticky cannot be pushed outside the outer sticky]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[position-sticky-offset-overflow.html]
|
|
||||||
[sticky position offset should be contained by scrolling box]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[position-sticky-rendering.html]
|
|
||||||
expected: FAIL
|
|
|
@ -1,6 +0,0 @@
|
||||||
[position-sticky-right.html]
|
|
||||||
[after reaching the sticking point the sticky box should be offset]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[the sticky box should not be pushed outside its containing block]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[position-sticky-root-scroller.html]
|
|
||||||
[Sticky elements work with the root (document) scroller]
|
|
||||||
expected: FAIL
|
|
|
@ -1,6 +0,0 @@
|
||||||
[position-sticky-top-and-bottom.html]
|
|
||||||
[initially the sticky box should be pushed to the top of the container]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[when we scroll past the flow position the top constraint pushes it down]
|
|
||||||
expected: FAIL
|
|
|
@ -1,6 +0,0 @@
|
||||||
[position-sticky-top.html]
|
|
||||||
[after reaching the sticking point the sticky box should be offset]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[the sticky box should not be pushed outside its containing block]
|
|
||||||
expected: FAIL
|
|
|
@ -1,4 +0,0 @@
|
||||||
[position-sticky-root-scroller-with-scroll-behavior.html]
|
|
||||||
[Sticky elements work with the root (document) scroller]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue