mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
layout: Add a new FragmentTree
pass to calculate containing block rectangles (#36629)
When doing any kind of query, up until now, containing block rectangles were calculated by walking the `FragmentTree` until the node being queried was found. In order to make possible answering queries without walking the `FragmentTree`, `Fragment`s need to cache their cumulative containing block rectangles. This change adds a new `FragmentTree` pass (during construction) that takes care of calculating and caching these values. The new cached value is used during resolved style queries and also scrolling area queries (with the idea that all queries will eventually use them). In addition, extra `FragmentTree` walks used for cancelling animations for elements no longer in the `FragmentTree` are integrated into this new traversal. Testing: Covered by existing WPT tests. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
554fa26da2
commit
e9e103b46c
8 changed files with 206 additions and 207 deletions
|
@ -65,6 +65,10 @@ pub(crate) struct BoxFragment {
|
|||
/// does not include padding, border, or margin -- it only includes content.
|
||||
pub content_rect: PhysicalRect<Au>,
|
||||
|
||||
/// This [`BoxFragment`]'s containing block rectangle in coordinates relative to
|
||||
/// the initial containing block, but not taking into account any transforms.
|
||||
pub cumulative_containing_block_rect: PhysicalRect<Au>,
|
||||
|
||||
pub padding: PhysicalSides<Au>,
|
||||
pub border: PhysicalSides<Au>,
|
||||
pub margin: PhysicalSides<Au>,
|
||||
|
@ -120,6 +124,7 @@ impl BoxFragment {
|
|||
style,
|
||||
children,
|
||||
content_rect,
|
||||
cumulative_containing_block_rect: Default::default(),
|
||||
padding,
|
||||
border,
|
||||
margin,
|
||||
|
@ -195,6 +200,8 @@ impl BoxFragment {
|
|||
self
|
||||
}
|
||||
|
||||
/// Get the scrollable overflow for this [`BoxFragment`] relative to its
|
||||
/// containing block.
|
||||
pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
|
||||
let physical_padding_rect = self.padding_rect();
|
||||
let content_origin = self.content_rect.origin.to_vector();
|
||||
|
@ -205,6 +212,10 @@ impl BoxFragment {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn offset_by_containing_block(&self, rect: &PhysicalRect<Au>) -> PhysicalRect<Au> {
|
||||
rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
|
||||
}
|
||||
|
||||
pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
|
||||
self.content_rect.outer_rect(self.padding)
|
||||
}
|
||||
|
@ -278,10 +289,7 @@ impl BoxFragment {
|
|||
overflow
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_resolved_insets_if_positioned(
|
||||
&self,
|
||||
containing_block: &PhysicalRect<Au>,
|
||||
) -> PhysicalSides<AuOrAuto> {
|
||||
pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides<AuOrAuto> {
|
||||
let position = self.style.get_box().position;
|
||||
debug_assert_ne!(
|
||||
position,
|
||||
|
@ -309,7 +317,10 @@ impl BoxFragment {
|
|||
// used value. Otherwise the resolved value is the computed value."
|
||||
// https://drafts.csswg.org/cssom/#resolved-values
|
||||
let insets = self.style.physical_box_offsets();
|
||||
let (cb_width, cb_height) = (containing_block.width(), containing_block.height());
|
||||
let (cb_width, cb_height) = (
|
||||
self.cumulative_containing_block_rect.width(),
|
||||
self.cumulative_containing_block_rect.height(),
|
||||
);
|
||||
if position == ComputedPosition::Relative {
|
||||
let get_resolved_axis = |start: &LengthPercentageOrAuto,
|
||||
end: &LengthPercentageOrAuto,
|
||||
|
@ -394,4 +405,8 @@ impl BoxFragment {
|
|||
_ => CollapsedBlockMargins::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) {
|
||||
self.cumulative_containing_block_rect = *containing_block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@ impl Fragment {
|
|||
Fragment::Float(fragment) => fragment.borrow().base.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn mutate_content_rect(&mut self, callback: impl FnOnce(&mut PhysicalRect<Au>)) {
|
||||
match self {
|
||||
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
|
||||
|
@ -124,6 +125,26 @@ impl Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
|
||||
match self {
|
||||
Fragment::Box(box_fragment) => box_fragment
|
||||
.borrow_mut()
|
||||
.set_containing_block(containing_block),
|
||||
Fragment::Float(float_fragment) => float_fragment
|
||||
.borrow_mut()
|
||||
.set_containing_block(containing_block),
|
||||
Fragment::Positioning(_) => {},
|
||||
Fragment::AbsoluteOrFixedPositioned(hoisted_shared_fragment) => {
|
||||
if let Some(ref fragment) = hoisted_shared_fragment.borrow().fragment {
|
||||
fragment.set_containing_block(containing_block);
|
||||
}
|
||||
},
|
||||
Fragment::Text(_) => {},
|
||||
Fragment::Image(_) => {},
|
||||
Fragment::IFrame(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> Option<Tag> {
|
||||
self.base().and_then(|base| base.tag)
|
||||
}
|
||||
|
@ -146,12 +167,12 @@ impl Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn scrolling_area(&self, containing_block: &PhysicalRect<Au>) -> PhysicalRect<Au> {
|
||||
pub fn scrolling_area(&self) -> PhysicalRect<Au> {
|
||||
match self {
|
||||
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
|
||||
.borrow()
|
||||
.scrollable_overflow()
|
||||
.translate(containing_block.origin.to_vector()),
|
||||
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
||||
let fragment = fragment.borrow();
|
||||
fragment.offset_by_containing_block(&fragment.scrollable_overflow())
|
||||
},
|
||||
_ => self.scrollable_overflow(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use style::dom::OpaqueNode;
|
|||
use webrender_api::units;
|
||||
|
||||
use super::{ContainingBlockManager, Fragment, Tag};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::StackingContext;
|
||||
use crate::flow::CanvasBackground;
|
||||
use crate::geom::{PhysicalPoint, PhysicalRect};
|
||||
|
@ -44,6 +45,58 @@ pub struct FragmentTree {
|
|||
}
|
||||
|
||||
impl FragmentTree {
|
||||
pub(crate) fn new(
|
||||
layout_context: &LayoutContext,
|
||||
root_fragments: Vec<Fragment>,
|
||||
scrollable_overflow: PhysicalRect<Au>,
|
||||
initial_containing_block: PhysicalRect<Au>,
|
||||
canvas_background: CanvasBackground,
|
||||
viewport_scroll_sensitivity: AxesScrollSensitivity,
|
||||
) -> Self {
|
||||
let fragment_tree = Self {
|
||||
root_fragments,
|
||||
scrollable_overflow,
|
||||
initial_containing_block,
|
||||
canvas_background,
|
||||
viewport_scroll_sensitivity,
|
||||
};
|
||||
|
||||
// As part of building the fragment tree, we want to stop animating elements and
|
||||
// pseudo-elements that used to be animating or had animating images attached to
|
||||
// them. Create a set of all elements that used to be animating.
|
||||
let mut animations = layout_context.style_context.animations.sets.write();
|
||||
let mut invalid_animating_nodes: FxHashSet<_> = animations.keys().cloned().collect();
|
||||
let mut image_animations = layout_context.node_image_animation_map.write().to_owned();
|
||||
let mut invalid_image_animating_nodes: FxHashSet<_> = image_animations
|
||||
.keys()
|
||||
.cloned()
|
||||
.map(|node| AnimationSetKey::new(node, None))
|
||||
.collect();
|
||||
|
||||
fragment_tree.find(|fragment, _level, containing_block| {
|
||||
if let Some(tag) = fragment.tag() {
|
||||
invalid_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
|
||||
invalid_image_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
|
||||
}
|
||||
|
||||
fragment.set_containing_block(containing_block);
|
||||
None::<()>
|
||||
});
|
||||
|
||||
// Cancel animations for any elements and pseudo-elements that are no longer found
|
||||
// in the fragment tree.
|
||||
for node in &invalid_animating_nodes {
|
||||
if let Some(state) = animations.get_mut(node) {
|
||||
state.cancel_all_animations();
|
||||
}
|
||||
}
|
||||
for node in &invalid_image_animating_nodes {
|
||||
image_animations.remove(&node.node);
|
||||
}
|
||||
|
||||
fragment_tree
|
||||
}
|
||||
|
||||
pub(crate) fn build_display_list(
|
||||
&self,
|
||||
builder: &mut crate::display_list::DisplayListBuilder,
|
||||
|
@ -86,14 +139,6 @@ impl FragmentTree {
|
|||
.find_map(|child| child.find(&info, 0, &mut process_func))
|
||||
}
|
||||
|
||||
pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet<AnimationSetKey>) {
|
||||
self.find(|fragment, _, _| {
|
||||
let tag = fragment.tag()?;
|
||||
set.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
|
||||
None::<()>
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the vector of rectangles that surrounds the fragments of the node with the given address.
|
||||
/// This function answers the `getClientRects()` query and the union of the rectangles answers
|
||||
/// the `getBoundingClientRect()` query.
|
||||
|
@ -173,22 +218,8 @@ impl FragmentTree {
|
|||
pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect<Au> {
|
||||
let mut scroll_area = self.initial_containing_block;
|
||||
for fragment in self.root_fragments.iter() {
|
||||
scroll_area = fragment
|
||||
.scrolling_area(&self.initial_containing_block)
|
||||
.union(&scroll_area);
|
||||
scroll_area = fragment.scrolling_area().union(&scroll_area);
|
||||
}
|
||||
scroll_area
|
||||
}
|
||||
|
||||
pub fn get_scrolling_area_for_node(&self, requested_node: OpaqueNode) -> PhysicalRect<Au> {
|
||||
let tag_to_find = Tag::new(requested_node);
|
||||
let scroll_area = self.find(|fragment, _, containing_block| {
|
||||
if fragment.tag() == Some(tag_to_find) {
|
||||
Some(fragment.scrolling_area(containing_block))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
scroll_area.unwrap_or_else(PhysicalRect::<Au>::zero)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue