diff options
Diffstat (limited to 'components/layout')
-rw-r--r-- | components/layout/dom.rs | 38 | ||||
-rw-r--r-- | components/layout/flow/construct.rs | 21 | ||||
-rw-r--r-- | components/layout/flow/inline/construct.rs | 64 | ||||
-rw-r--r-- | components/layout/flow/inline/inline_box.rs | 16 | ||||
-rw-r--r-- | components/layout/flow/inline/mod.rs | 8 | ||||
-rw-r--r-- | components/layout/flow/mod.rs | 105 | ||||
-rw-r--r-- | components/layout/flow/root.rs | 32 | ||||
-rw-r--r-- | components/layout/fragment_tree/base_fragment.rs | 5 | ||||
-rw-r--r-- | components/layout/fragment_tree/box_fragment.rs | 25 | ||||
-rw-r--r-- | components/layout/fragment_tree/fragment.rs | 89 | ||||
-rw-r--r-- | components/layout/fragment_tree/fragment_tree.rs | 160 | ||||
-rw-r--r-- | components/layout/fragment_tree/positioning_fragment.rs | 14 | ||||
-rw-r--r-- | components/layout/layout_impl.rs | 529 | ||||
-rw-r--r-- | components/layout/query.rs | 572 | ||||
-rw-r--r-- | components/layout/table/layout.rs | 12 | ||||
-rw-r--r-- | components/layout/table/mod.rs | 1 | ||||
-rw-r--r-- | components/layout/traversal.rs | 8 |
17 files changed, 886 insertions, 813 deletions
diff --git a/components/layout/dom.rs b/components/layout/dom.rs index 6db4dbccd41..add4b3ac2d5 100644 --- a/components/layout/dom.rs +++ b/components/layout/dom.rs @@ -2,18 +2,21 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::any::Any; use std::marker::PhantomData; use std::sync::Arc; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use base::id::{BrowsingContextId, PipelineId}; use html5ever::{local_name, ns}; +use malloc_size_of_derive::MallocSizeOf; use pixels::Image; use script_layout_interface::wrapper_traits::{ LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; use script_layout_interface::{ - HTMLCanvasDataSource, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType, + GenericLayoutDataTrait, HTMLCanvasDataSource, LayoutElementType, + LayoutNodeType as ScriptLayoutNodeType, }; use servo_arc::Arc as ServoArc; use style::properties::ComputedValues; @@ -31,7 +34,7 @@ use crate::table::TableLevelBox; use crate::taffy::TaffyItemBox; /// The data that is stored in each DOM node that is used by layout. -#[derive(Default)] +#[derive(Default, MallocSizeOf)] pub struct InnerDOMLayoutData { pub(super) self_box: ArcRefCell<Option<LayoutBox>>, pub(super) pseudo_before_box: ArcRefCell<Option<LayoutBox>>, @@ -54,10 +57,11 @@ impl InnerDOMLayoutData { } /// A box that is stored in one of the `DOMLayoutData` slots. +#[derive(MallocSizeOf)] pub(super) enum LayoutBox { DisplayContents, BlockLevel(ArcRefCell<BlockLevelBox>), - InlineLevel(ArcRefCell<InlineItem>), + InlineLevel(Vec<ArcRefCell<InlineItem>>), FlexLevel(ArcRefCell<FlexLevelBox>), TableLevelBox(TableLevelBox), TaffyItemBox(ArcRefCell<TaffyItemBox>), @@ -70,8 +74,10 @@ impl LayoutBox { LayoutBox::BlockLevel(block_level_box) => { block_level_box.borrow().invalidate_cached_fragment() }, - LayoutBox::InlineLevel(inline_item) => { - inline_item.borrow().invalidate_cached_fragment() + LayoutBox::InlineLevel(inline_items) => { + for inline_item in inline_items.iter() { + inline_item.borrow().invalidate_cached_fragment() + } }, LayoutBox::FlexLevel(flex_level_box) => { flex_level_box.borrow().invalidate_cached_fragment() @@ -87,7 +93,10 @@ impl LayoutBox { match self { LayoutBox::DisplayContents => vec![], LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().fragments(), - LayoutBox::InlineLevel(inline_item) => inline_item.borrow().fragments(), + LayoutBox::InlineLevel(inline_items) => inline_items + .iter() + .flat_map(|inline_item| inline_item.borrow().fragments()) + .collect(), LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().fragments(), LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().fragments(), LayoutBox::TableLevelBox(table_box) => table_box.fragments(), @@ -98,11 +107,16 @@ impl LayoutBox { /// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data /// structure interior mutability, as we will need to mutate the layout data of /// non-mutable DOM nodes. -#[derive(Default)] +#[derive(Default, MallocSizeOf)] pub struct DOMLayoutData(AtomicRefCell<InnerDOMLayoutData>); // The implementation of this trait allows the data to be stored in the DOM. impl LayoutDataTrait for DOMLayoutData {} +impl GenericLayoutDataTrait for DOMLayoutData { + fn as_any(&self) -> &dyn Any { + self + } +} pub struct BoxSlot<'dom> { pub(crate) slot: Option<ArcRefCell<Option<LayoutBox>>>, @@ -255,6 +269,7 @@ where } LayoutNode::layout_data(&self) .unwrap() + .as_any() .downcast_ref::<DOMLayoutData>() .unwrap() .0 @@ -262,8 +277,13 @@ where } fn layout_data(self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> { - LayoutNode::layout_data(&self) - .map(|data| data.downcast_ref::<DOMLayoutData>().unwrap().0.borrow()) + LayoutNode::layout_data(&self).map(|data| { + data.as_any() + .downcast_ref::<DOMLayoutData>() + .unwrap() + .0 + .borrow() + }) } fn element_box_slot(&self) -> BoxSlot<'dom> { diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs index a6471756db8..5ed567f513b 100644 --- a/components/layout/flow/construct.rs +++ b/components/layout/flow/construct.rs @@ -458,15 +458,14 @@ where self.propagated_data.without_text_decorations(), ), ); - box_slot.set(LayoutBox::InlineLevel(atomic)); + box_slot.set(LayoutBox::InlineLevel(vec![atomic])); return; }; // Otherwise, this is just a normal inline box. Whatever happened before, all we need to do // before recurring is to remember this ongoing inline level box. - let inline_item = self - .inline_formatting_context_builder - .start_inline_box(InlineBox::new(info)); + self.inline_formatting_context_builder + .start_inline_box(InlineBox::new(info), None); if is_list_item { if let Some((marker_info, marker_contents)) = @@ -486,8 +485,14 @@ where self.finish_anonymous_table_if_needed(); - self.inline_formatting_context_builder.end_inline_box(); - box_slot.set(LayoutBox::InlineLevel(inline_item)); + // As we are ending this inline box, during the course of the `traverse()` above, the ongoing + // inline formatting context may have been split around block-level elements. In that case, + // more than a single inline box tree item may have been produced for this inline-level box. + // `InlineFormattingContextBuilder::end_inline_box()` is returning all of those box tree + // items. + box_slot.set(LayoutBox::InlineLevel( + self.inline_formatting_context_builder.end_inline_box(), + )); } fn handle_block_level_element( @@ -574,7 +579,7 @@ where display_inside, contents, )); - box_slot.set(LayoutBox::InlineLevel(inline_level_box)); + box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); return; } @@ -607,7 +612,7 @@ where contents, self.propagated_data, )); - box_slot.set(LayoutBox::InlineLevel(inline_level_box)); + box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); return; } diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index 7c668751ef6..61292701a9f 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -6,6 +6,7 @@ use std::borrow::Cow; use std::char::{ToLowercase, ToUppercase}; use icu_segmenter::WordSegmenter; +use itertools::izip; use servo_arc::Arc; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; use style::values::specified::text::TextTransformCase; @@ -67,6 +68,16 @@ pub(crate) struct InlineFormattingContextBuilder { /// When an inline box ends, it's removed from this stack. inline_box_stack: Vec<InlineBoxIdentifier>, + /// Normally, an inline box produces a single box tree [`InlineItem`]. When a block + /// element causes an inline box [to be split], it can produce multiple + /// [`InlineItem`]s, all inserted into different [`InlineFormattingContext`]s. + /// [`Self::block_in_inline_splits`] is responsible for tracking all of these split + /// inline box results, so that they can be inserted into the [`crate::dom::BoxSlot`] + /// for the DOM element once it has been processed for BoxTree construction. + /// + /// [to be split]: https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level + block_in_inline_splits: Vec<Vec<ArcRefCell<InlineItem>>>, + /// Whether or not the inline formatting context under construction has any /// uncollapsible text content. pub has_uncollapsible_text_content: bool, @@ -162,29 +173,42 @@ impl InlineFormattingContextBuilder { inline_level_box } - pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) -> ArcRefCell<InlineItem> { + pub(crate) fn start_inline_box( + &mut self, + inline_box: InlineBox, + block_in_inline_splits: Option<Vec<ArcRefCell<InlineItem>>>, + ) { self.push_control_character_string(inline_box.base.style.bidi_control_chars().0); let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box); let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box)); self.inline_items.push(inline_level_box.clone()); self.inline_box_stack.push(identifier); - inline_level_box + + let mut block_in_inline_splits = block_in_inline_splits.unwrap_or_default(); + block_in_inline_splits.push(inline_level_box); + self.block_in_inline_splits.push(block_in_inline_splits); } - pub(crate) fn end_inline_box(&mut self) -> ArcRefCell<InlineBox> { - let identifier = self.end_inline_box_internal(); + /// End the ongoing inline box in this [`InlineFormattingContextBuilder`], returning + /// shared references to all of the box tree items that were created for it. More than + /// a single box tree items may be produced for a single inline box when that inline + /// box is split around a block-level element. + pub(crate) fn end_inline_box(&mut self) -> Vec<ArcRefCell<InlineItem>> { + let (identifier, block_in_inline_splits) = self.end_inline_box_internal(); let inline_level_box = self.inline_boxes.get(&identifier); - inline_level_box.borrow_mut().is_last_fragment = true; - - self.push_control_character_string( - inline_level_box.borrow().base.style.bidi_control_chars().1, - ); + { + let mut inline_level_box = inline_level_box.borrow_mut(); + inline_level_box.is_last_split = true; + self.push_control_character_string(inline_level_box.base.style.bidi_control_chars().1); + } - inline_level_box + block_in_inline_splits.unwrap_or_default() } - fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier { + fn end_inline_box_internal( + &mut self, + ) -> (InlineBoxIdentifier, Option<Vec<ArcRefCell<InlineItem>>>) { let identifier = self .inline_box_stack .pop() @@ -193,7 +217,12 @@ impl InlineFormattingContextBuilder { .push(ArcRefCell::new(InlineItem::EndInlineBox)); self.inline_boxes.end_inline_box(identifier); - identifier + + // This might be `None` if this builder has already drained its block-in-inline-splits + // into the new builder on the other side of a new block-in-inline split. + let block_in_inline_splits = self.block_in_inline_splits.pop(); + + (identifier, block_in_inline_splits) } pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>( @@ -295,12 +324,21 @@ impl InlineFormattingContextBuilder { // marked as not being the first fragment. No inline content is carried over to this new // builder. let mut new_builder = InlineFormattingContextBuilder::new(); - for identifier in self.inline_box_stack.iter() { + let block_in_inline_splits = std::mem::take(&mut self.block_in_inline_splits); + for (identifier, historical_inline_boxes) in + izip!(self.inline_box_stack.iter(), block_in_inline_splits) + { + // Start a new inline box for every ongoing inline box in this + // InlineFormattingContext once we are done processing this block element, + // being sure to give the block-in-inline-split to the new + // InlineFormattingContext. These will finally be inserted into the DOM's + // BoxSlot once the inline box has been fully processed. new_builder.start_inline_box( self.inline_boxes .get(identifier) .borrow() .split_around_block(), + Some(historical_inline_boxes), ); } let mut inline_builder_from_before_split = std::mem::replace(self, new_builder); diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs index 97398d6e708..de79f876340 100644 --- a/components/layout/flow/inline/inline_box.rs +++ b/components/layout/flow/inline/inline_box.rs @@ -23,8 +23,12 @@ pub(crate) struct InlineBox { pub base: LayoutBoxBase, /// The identifier of this inline box in the containing [`super::InlineFormattingContext`]. pub(super) identifier: InlineBoxIdentifier, - pub is_first_fragment: bool, - pub is_last_fragment: bool, + /// Whether or not this is the first instance of an [`InlineBox`] before a possible + /// block-in-inline split. When no split occurs, this is always true. + pub is_first_split: bool, + /// Whether or not this is the last instance of an [`InlineBox`] before a possible + /// block-in-inline split. When no split occurs, this is always true. + pub is_last_split: bool, /// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store. /// This is initialized during IFC shaping. pub default_font_index: Option<usize>, @@ -36,8 +40,8 @@ impl InlineBox { base: LayoutBoxBase::new(info.into(), info.style.clone()), // This will be assigned later, when the box is actually added to the IFC. identifier: InlineBoxIdentifier::default(), - is_first_fragment: true, - is_last_fragment: false, + is_first_split: true, + is_last_split: false, default_font_index: None, } } @@ -45,8 +49,8 @@ impl InlineBox { pub(crate) fn split_around_block(&self) -> Self { Self { base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()), - is_first_fragment: false, - is_last_fragment: false, + is_first_split: false, + is_last_split: false, ..*self } } diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 490917d95a3..dabb9773410 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -744,7 +744,7 @@ impl InlineFormattingContextLayout<'_> { self.containing_block, self.layout_context, self.current_inline_container_state(), - inline_box.is_last_fragment, + inline_box.is_last_split, inline_box .default_font_index .map(|index| &self.ifc.font_metrics[index].metrics), @@ -773,7 +773,7 @@ impl InlineFormattingContextLayout<'_> { ); } - if inline_box.is_first_fragment { + if inline_box.is_first_split { self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start + inline_box_state.pbm.border.inline_start + inline_box_state.pbm.margin.inline_start.auto_is(Au::zero); @@ -2349,10 +2349,10 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { .auto_is(Au::zero); let pbm = margin + padding + border; - if inline_box.is_first_fragment { + if inline_box.is_first_split { self.add_inline_size(pbm.inline_start); } - if inline_box.is_last_fragment { + if inline_box.is_last_split { self.ending_inline_pbm_stack.push(pbm.inline_end); } else { self.ending_inline_pbm_stack.push(Au::zero()); diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index f92650ef340..d983e8592c4 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -896,6 +896,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context( block_sizes, depends_on_block_constraints, available_block_size, + justify_self, } = solve_containing_block_padding_and_border_for_in_flow_box( containing_block, &layout_style, @@ -909,6 +910,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context( containing_block, &pbm, containing_block_for_children.size.inline, + justify_self, ); let computed_block_size = style.content_block_size(); @@ -1154,6 +1156,7 @@ impl IndependentNonReplacedContents { block_sizes, depends_on_block_constraints, available_block_size, + justify_self, } = solve_containing_block_padding_and_border_for_in_flow_box( containing_block, &layout_style, @@ -1185,7 +1188,7 @@ impl IndependentNonReplacedContents { let ResolvedMargins { margin, effective_margin_inline_start, - } = solve_margins(containing_block, &pbm, inline_size); + } = solve_margins(containing_block, &pbm, inline_size, justify_self); let content_rect = LogicalRect { start_corner: LogicalVec2 { @@ -1300,17 +1303,12 @@ impl IndependentNonReplacedContents { .sizes }; - // TODO: the automatic inline size should take `justify-self` into account. + let justify_self = resolve_justify_self(style, containing_block.style); let is_table = self.is_table(); - let automatic_inline_size = if is_table { - Size::FitContent - } else { - Size::Stretch - }; let compute_inline_size = |stretch_size| { content_box_sizes.inline.resolve( Direction::Inline, - automatic_inline_size, + automatic_inline_size(justify_self, is_table), Au::zero, Some(stretch_size), get_inline_content_sizes, @@ -1472,6 +1470,7 @@ impl IndependentNonReplacedContents { &pbm, content_size.inline + pbm.padding_border_sums.inline, placement_rect, + justify_self, ); let margin = LogicalSides { @@ -1558,6 +1557,7 @@ impl ReplacedContents { let effective_margin_inline_start; let (margin_block_start, margin_block_end) = solve_block_margins_for_in_flow_block_level(pbm); + let justify_self = resolve_justify_self(&base.style, containing_block.style); let containing_block_writing_mode = containing_block.style.writing_mode; let physical_content_size = content_size.to_physical_size(containing_block_writing_mode); @@ -1597,6 +1597,7 @@ impl ReplacedContents { pbm, size.inline, placement_rect, + justify_self, ); // Clearance prevents margin collapse between this block and previous ones, @@ -1620,6 +1621,7 @@ impl ReplacedContents { containing_block, pbm, content_size.inline, + justify_self, ); }; @@ -1671,6 +1673,7 @@ struct ContainingBlockPaddingAndBorder<'a> { block_sizes: Sizes, depends_on_block_constraints: bool, available_block_size: Option<Au>, + justify_self: AlignFlags, } struct ResolvedMargins { @@ -1719,6 +1722,9 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( // The available block size may actually be definite, but it should be irrelevant // since the sizing properties are set to their initial value. available_block_size: None, + // The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`). + // This is being discussed in <https://github.com/w3c/csswg-drafts/issues/11461>. + justify_self: AlignFlags::NORMAL, }; } @@ -1755,16 +1761,11 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( None, /* TODO: support preferred aspect ratios on non-replaced boxes */ )) }; - // TODO: the automatic inline size should take `justify-self` into account. + let justify_self = resolve_justify_self(style, containing_block.style); let is_table = layout_style.is_table(); - let automatic_inline_size = if is_table { - Size::FitContent - } else { - Size::Stretch - }; let inline_size = content_box_sizes.inline.resolve( Direction::Inline, - automatic_inline_size, + automatic_inline_size(justify_self, is_table), Au::zero, Some(available_inline_size), get_inline_content_sizes, @@ -1793,6 +1794,7 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( block_sizes: content_box_sizes.block, depends_on_block_constraints, available_block_size, + justify_self, } } @@ -1804,9 +1806,15 @@ fn solve_margins( containing_block: &ContainingBlock<'_>, pbm: &PaddingBorderMargin, inline_size: Au, + justify_self: AlignFlags, ) -> ResolvedMargins { let (inline_margins, effective_margin_inline_start) = - solve_inline_margins_for_in_flow_block_level(containing_block, pbm, inline_size); + solve_inline_margins_for_in_flow_block_level( + containing_block, + pbm, + inline_size, + justify_self, + ); let block_margins = solve_block_margins_for_in_flow_block_level(pbm); ResolvedMargins { margin: LogicalSides { @@ -1829,14 +1837,63 @@ fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au ) } -/// This is supposed to handle 'justify-self', but no browser supports it on block boxes. -/// Instead, `<center>` and `<div align>` are implemented via internal 'text-align' values. +/// Resolves the `justify-self` value, preserving flags. +fn resolve_justify_self(style: &ComputedValues, parent_style: &ComputedValues) -> AlignFlags { + let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start(); + let alignment = match style.clone_justify_self().0.0 { + AlignFlags::AUTO => parent_style.clone_justify_items().computed.0, + alignment => alignment, + }; + let alignment_value = match alignment.value() { + AlignFlags::LEFT if is_ltr(parent_style) => AlignFlags::START, + AlignFlags::LEFT => AlignFlags::END, + AlignFlags::RIGHT if is_ltr(parent_style) => AlignFlags::END, + AlignFlags::RIGHT => AlignFlags::START, + AlignFlags::SELF_START if is_ltr(parent_style) == is_ltr(style) => AlignFlags::START, + AlignFlags::SELF_START => AlignFlags::END, + AlignFlags::SELF_END if is_ltr(parent_style) == is_ltr(style) => AlignFlags::END, + AlignFlags::SELF_END => AlignFlags::START, + alignment_value => alignment_value, + }; + alignment.flags() | alignment_value +} + +/// Determines the automatic size for the inline axis of a block-level box. +/// <https://drafts.csswg.org/css-sizing-3/#automatic-size> +#[inline] +fn automatic_inline_size<T>(justify_self: AlignFlags, is_table: bool) -> Size<T> { + match justify_self { + AlignFlags::STRETCH => Size::Stretch, + AlignFlags::NORMAL if !is_table => Size::Stretch, + _ => Size::FitContent, + } +} + +/// Justifies a block-level box, distributing the free space according to `justify-self`. +/// Note `<center>` and `<div align>` are implemented via internal 'text-align' values, +/// which are also handled here. /// The provided free space should already take margins into account. In particular, /// it should be zero if there is an auto margin. /// <https://drafts.csswg.org/css-align/#justify-block> -fn justify_self_alignment(containing_block: &ContainingBlock, free_space: Au) -> Au { +fn justify_self_alignment( + containing_block: &ContainingBlock, + free_space: Au, + justify_self: AlignFlags, +) -> Au { + let mut alignment = justify_self.value(); + let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL; + if is_safe && free_space <= Au::zero() { + alignment = AlignFlags::START + } + match alignment { + AlignFlags::NORMAL => {}, + AlignFlags::CENTER => return free_space / 2, + AlignFlags::END => return free_space, + _ => return Au::zero(), + } + + // For `justify-self: normal`, fall back to the special 'text-align' values. let style = containing_block.style; - debug_assert!(free_space >= Au::zero()); match style.clone_text_align() { TextAlignKeyword::MozCenter => free_space / 2, TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space, @@ -1861,6 +1918,7 @@ fn solve_inline_margins_for_in_flow_block_level( containing_block: &ContainingBlock, pbm: &PaddingBorderMargin, inline_size: Au, + justify_self: AlignFlags, ) -> ((Au, Au), Au) { let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size; let mut justification = Au::zero(); @@ -1878,8 +1936,8 @@ fn solve_inline_margins_for_in_flow_block_level( // But here we may still have some free space to perform 'justify-self' alignment. // This aligns the margin box within the containing block, or in other words, // aligns the border box within the margin-shrunken containing block. - let free_space = Au::zero().max(free_space - start - end); - justification = justify_self_alignment(containing_block, free_space); + justification = + justify_self_alignment(containing_block, free_space - start - end, justify_self); (start, end) }, }; @@ -1902,6 +1960,7 @@ fn solve_inline_margins_avoiding_floats( pbm: &PaddingBorderMargin, inline_size: Au, placement_rect: LogicalRect<Au>, + justify_self: AlignFlags, ) -> ((Au, Au), Au) { let free_space = placement_rect.size.inline - inline_size; debug_assert!(free_space >= Au::zero()); @@ -1922,7 +1981,7 @@ fn solve_inline_margins_avoiding_floats( // and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns // the border box within the instersection of the float-shrunken containing-block // and the margin-shrunken containing-block. - justification = justify_self_alignment(containing_block, free_space); + justification = justify_self_alignment(containing_block, free_space, justify_self); (start, end) }, }; diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index 390b4664e60..187726595f8 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -195,16 +195,17 @@ impl BoxTree { }, _ => return None, }, - LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() { - InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) - if box_style.position.is_absolutely_positioned() => - { - UpdatePoint::AbsolutelyPositionedInlineLevelBox( - inline_level_box.clone(), - *text_offset_index, - ) - }, - _ => return None, + LayoutBox::InlineLevel(inline_level_items) => { + let inline_level_box = inline_level_items.first()?; + let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) = + &*inline_level_box.borrow() + else { + return None; + }; + UpdatePoint::AbsolutelyPositionedInlineLevelBox( + inline_level_box.clone(), + *text_offset_index, + ) }, LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() { FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) @@ -428,13 +429,14 @@ impl BoxTree { acc.union(&child_overflow) }); - FragmentTree { + FragmentTree::new( + layout_context, root_fragments, scrollable_overflow, - initial_containing_block: physical_containing_block, - canvas_background: self.canvas_background.clone(), - viewport_scroll_sensitivity: self.viewport_scroll_sensitivity, - } + physical_containing_block, + self.canvas_background.clone(), + self.viewport_scroll_sensitivity, + ) } } diff --git a/components/layout/fragment_tree/base_fragment.rs b/components/layout/fragment_tree/base_fragment.rs index 48d672a8547..0cf6ee511cb 100644 --- a/components/layout/fragment_tree/base_fragment.rs +++ b/components/layout/fragment_tree/base_fragment.rs @@ -132,11 +132,6 @@ impl Tag { Tag { node, pseudo } } - /// Returns true if this tag is for a pseudo element. - pub(crate) fn is_pseudo(&self) -> bool { - self.pseudo.is_some() - } - pub(crate) fn to_display_list_fragment_id(self) -> u64 { combine_id_with_fragment_type(self.node.id(), self.pseudo.into()) } diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 30be154caf1..e87826ec3ca 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -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,14 @@ impl BoxFragment { ) } + pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) { + self.cumulative_containing_block_rect = *containing_block; + } + + 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 +293,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 +321,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, diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index d0d1b9b1104..7708b0893ee 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use app_units::Au; use base::id::PipelineId; use base::print_tree::PrintTree; +use euclid::{Point2D, Rect, Size2D, UnknownUnit}; use fonts::{ByteIndex, FontMetrics, GlyphStore}; use malloc_size_of_derive::MallocSizeOf; use range::Range as ServoRange; @@ -21,7 +22,7 @@ use super::{ Tag, }; use crate::cell::ArcRefCell; -use crate::geom::{LogicalSides, PhysicalRect}; +use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect}; use crate::style_ext::ComputedValuesExt; #[derive(Clone, MallocSizeOf)] @@ -112,6 +113,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 +126,28 @@ 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(positioning_fragment) => positioning_fragment + .borrow_mut() + .set_containing_block(containing_block), + 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 +170,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(), } } @@ -169,6 +193,59 @@ impl Fragment { } } + pub(crate) fn cumulative_border_box_rect(&self) -> Option<PhysicalRect<Au>> { + match self { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + let fragment = fragment.borrow(); + Some(fragment.offset_by_containing_block(&fragment.border_rect())) + }, + Fragment::Positioning(fragment) => { + let fragment = fragment.borrow(); + Some(fragment.offset_by_containing_block(&fragment.rect)) + }, + Fragment::Text(_) | + Fragment::AbsoluteOrFixedPositioned(_) | + Fragment::Image(_) | + Fragment::IFrame(_) => None, + } + } + + pub(crate) fn client_rect(&self) -> Rect<i32, UnknownUnit> { + let rect = match self { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + // https://drafts.csswg.org/cssom-view/#dom-element-clienttop + // " If the element has no associated CSS layout box or if the + // CSS layout box is inline, return zero." For this check we + // also explicitly ignore the list item portion of the display + // style. + let fragment = fragment.borrow(); + if fragment.is_inline_box() { + return Rect::zero(); + } + + if fragment.is_table_wrapper() { + // For tables the border actually belongs to the table grid box, + // so we need to include it in the dimension of the table wrapper box. + let mut rect = fragment.border_rect(); + rect.origin = PhysicalPoint::zero(); + rect + } else { + let mut rect = fragment.padding_rect(); + rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top); + rect + } + }, + _ => return Rect::zero(), + } + .to_untyped(); + + let rect = Rect::new( + Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()), + Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()), + ); + rect.round().to_i32() + } + pub(crate) fn find<T>( &self, manager: &ContainingBlockManager<PhysicalRect<Au>>, diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs index bb3c659466c..3a082c99389 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -5,17 +5,17 @@ use app_units::Au; use base::print_tree::PrintTree; use compositing_traits::display_list::AxesScrollSensitivity; -use euclid::default::{Point2D, Rect, Size2D}; +use euclid::default::Size2D; use fxhash::FxHashSet; use malloc_size_of_derive::MallocSizeOf; use style::animation::AnimationSetKey; -use style::dom::OpaqueNode; use webrender_api::units; -use super::{ContainingBlockManager, Fragment, Tag}; +use super::{ContainingBlockManager, Fragment}; +use crate::context::LayoutContext; use crate::display_list::StackingContext; use crate::flow::CanvasBackground; -use crate::geom::{PhysicalPoint, PhysicalRect}; +use crate::geom::PhysicalRect; #[derive(MallocSizeOf)] pub struct FragmentTree { @@ -44,6 +44,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,109 +138,11 @@ 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. - /// - /// TODO: This function is supposed to handle scroll offsets, but that isn't happening at all. - pub fn get_content_boxes_for_node(&self, requested_node: OpaqueNode) -> Vec<Rect<Au>> { - let mut content_boxes = Vec::new(); - let tag_to_find = Tag::new(requested_node); - self.find(|fragment, _, containing_block| { - if fragment.tag() != Some(tag_to_find) { - return None::<()>; - } - - let fragment_relative_rect = match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - fragment.borrow().border_rect() - }, - Fragment::Positioning(fragment) => fragment.borrow().rect, - Fragment::Text(fragment) => fragment.borrow().rect, - Fragment::AbsoluteOrFixedPositioned(_) | - Fragment::Image(_) | - Fragment::IFrame(_) => return None, - }; - - let rect = fragment_relative_rect.translate(containing_block.origin.to_vector()); - - content_boxes.push(rect.to_untyped()); - None::<()> - }); - content_boxes - } - - pub fn get_border_dimensions_for_node(&self, requested_node: OpaqueNode) -> Rect<i32> { - let tag_to_find = Tag::new(requested_node); - self.find(|fragment, _, _containing_block| { - if fragment.tag() != Some(tag_to_find) { - return None; - } - - let rect = match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - // https://drafts.csswg.org/cssom-view/#dom-element-clienttop - // " If the element has no associated CSS layout box or if the - // CSS layout box is inline, return zero." For this check we - // also explicitly ignore the list item portion of the display - // style. - let fragment = fragment.borrow(); - if fragment.is_inline_box() { - return Some(Rect::zero()); - } - if fragment.is_table_wrapper() { - // For tables the border actually belongs to the table grid box, - // so we need to include it in the dimension of the table wrapper box. - let mut rect = fragment.border_rect(); - rect.origin = PhysicalPoint::zero(); - rect - } else { - let mut rect = fragment.padding_rect(); - rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top); - rect - } - }, - Fragment::Positioning(fragment) => fragment.borrow().rect.cast_unit(), - Fragment::Text(text_fragment) => text_fragment.borrow().rect, - _ => return None, - }; - - let rect = Rect::new( - Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()), - Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()), - ); - Some(rect.round().to_i32().to_untyped()) - }) - .unwrap_or_else(Rect::zero) - } - 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) - } } diff --git a/components/layout/fragment_tree/positioning_fragment.rs b/components/layout/fragment_tree/positioning_fragment.rs index 853caed6709..1fe968eb484 100644 --- a/components/layout/fragment_tree/positioning_fragment.rs +++ b/components/layout/fragment_tree/positioning_fragment.rs @@ -20,12 +20,17 @@ pub(crate) struct PositioningFragment { pub base: BaseFragment, pub rect: PhysicalRect<Au>, pub children: Vec<Fragment>, + /// The scrollable overflow of this anonymous fragment's children. pub scrollable_overflow: PhysicalRect<Au>, /// If this fragment was created with a style, the style of the fragment. #[conditional_malloc_size_of] pub style: Option<ServoArc<ComputedValues>>, + + /// This [`PositioningFragment`]'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>, } impl PositioningFragment { @@ -61,9 +66,18 @@ impl PositioningFragment { rect, children, scrollable_overflow, + cumulative_containing_block_rect: PhysicalRect::zero(), }) } + pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) { + self.cumulative_containing_block_rect = *containing_block; + } + + pub fn offset_by_containing_block(&self, rect: &PhysicalRect<Au>) -> PhysicalRect<Au> { + rect.translate(self.cumulative_containing_block_rect.origin.to_vector()) + } + pub fn print(&self, tree: &mut PrintTree) { tree.new_level(format!( "PositioningFragment\ diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index 61550df2723..3110899d76e 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -23,7 +23,7 @@ use euclid::{Point2D, Scale, Size2D, Vector2D}; use fnv::FnvHashMap; use fonts::{FontContext, FontContextWebFontMethods}; use fonts_traits::StylesheetWebFontLoadFinishedCallback; -use fxhash::{FxHashMap, FxHashSet}; +use fxhash::FxHashMap; use ipc_channel::ipc::IpcSender; use log::{debug, error}; use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps}; @@ -34,21 +34,22 @@ use profile_traits::time::{ self as profile_time, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType, }; use profile_traits::{path, time_profile}; -use script::layout_dom::{ServoLayoutElement, ServoLayoutNode}; +use rayon::ThreadPool; +use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::{ - ImageAnimationState, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, - OffsetParentResponse, ReflowGoal, ReflowRequest, ReflowResult, TrustedNodeAddress, + Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, ReflowGoal, + ReflowRequest, ReflowResult, TrustedNodeAddress, }; use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage}; use servo_arc::Arc as ServoArc; use servo_config::opts::{self, DebugOptions}; use servo_config::pref; use servo_url::ServoUrl; -use style::animation::{AnimationSetKey, DocumentAnimationSet}; +use style::animation::DocumentAnimationSet; use style::context::{ QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext, }; -use style::dom::{OpaqueNode, TElement, TNode}; +use style::dom::{OpaqueNode, ShowSubtreeDataAndPrimaryValues, TElement, TNode}; use style::error_reporting::RustLogReporter; use style::font_metrics::FontMetrics; use style::global_style_data::GLOBAL_STYLE_DATA; @@ -80,8 +81,8 @@ use webrender_api::{ExternalScrollId, HitTestFlags}; use crate::context::LayoutContext; use crate::display_list::{DisplayList, WebRenderImageInfo}; use crate::query::{ - get_the_text_steps, process_content_box_request, process_content_boxes_request, - process_node_geometry_request, process_node_scroll_area_request, process_offset_parent_query, + get_the_text_steps, process_client_rect_request, process_content_box_request, + process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query, process_resolved_font_style_query, process_resolved_style_request, process_text_index_request, }; use crate::traversal::RecalcStyle; @@ -95,6 +96,10 @@ use crate::{BoxTree, FragmentTree}; static STYLE_THREAD_POOL: Mutex<&style::global_style_data::STYLE_THREAD_POOL> = Mutex::new(&style::global_style_data::STYLE_THREAD_POOL); +thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const { + LazyCell::new(|| RefCell::new(HashSet::new())) +}); + /// Information needed by layout. pub struct LayoutThread { /// The ID of the pipeline that we belong to. @@ -230,24 +235,27 @@ impl Layout for LayoutThread { feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_content_box(&self, node: OpaqueNode) -> Option<UntypedRect<Au>> { - process_content_box_request(node, self.fragment_tree.borrow().clone()) + fn query_content_box(&self, node: TrustedNodeAddress) -> Option<UntypedRect<Au>> { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_content_box_request(node) } #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_content_boxes(&self, node: OpaqueNode) -> Vec<UntypedRect<Au>> { - process_content_boxes_request(node, self.fragment_tree.borrow().clone()) + fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<UntypedRect<Au>> { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_content_boxes_request(node) } #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_client_rect(&self, node: OpaqueNode) -> UntypedRect<i32> { - process_node_geometry_request(node, self.fragment_tree.borrow().clone()) + fn query_client_rect(&self, node: TrustedNodeAddress) -> UntypedRect<i32> { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_client_rect_request(node) } #[cfg_attr( @@ -292,8 +300,9 @@ impl Layout for LayoutThread { feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_offset_parent(&self, node: OpaqueNode) -> OffsetParentResponse { - process_offset_parent_query(node, self.fragment_tree.borrow().clone()) + fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_offset_parent_query(node).unwrap_or_default() } #[cfg_attr( @@ -325,14 +334,7 @@ impl Layout for LayoutThread { TraversalFlags::empty(), ); - let fragment_tree = self.fragment_tree.borrow().clone(); - process_resolved_style_request( - &shared_style_context, - node, - &pseudo, - &property_id, - fragment_tree, - ) + process_resolved_style_request(&shared_style_context, node, &pseudo, &property_id) } #[cfg_attr( @@ -375,7 +377,8 @@ impl Layout for LayoutThread { feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_scrolling_area(&self, node: Option<OpaqueNode>) -> UntypedRect<i32> { + fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> UntypedRect<i32> { + let node = node.map(|node| unsafe { ServoLayoutNode::new(&node) }); process_node_scroll_area_request(node, self.fragment_tree.borrow().clone()) } @@ -545,43 +548,6 @@ impl LayoutThread { } } - #[allow(clippy::too_many_arguments)] - // Create a layout context for use in building display lists, hit testing, &c. - #[allow(clippy::too_many_arguments)] - fn build_layout_context<'a>( - &'a self, - guards: StylesheetGuards<'a>, - snapshot_map: &'a SnapshotMap, - reflow_request: &mut ReflowRequest, - use_rayon: bool, - ) -> LayoutContext<'a> { - let traversal_flags = match reflow_request.stylesheets_changed { - true => TraversalFlags::ForCSSRuleChanges, - false => TraversalFlags::empty(), - }; - - LayoutContext { - id: self.id, - origin: reflow_request.origin.clone(), - style_context: self.build_shared_style_context( - guards, - snapshot_map, - reflow_request.animation_timeline_value, - &reflow_request.animations, - traversal_flags, - ), - image_cache: self.image_cache.clone(), - font_context: self.font_context.clone(), - webrender_image_cache: self.webrender_image_cache.clone(), - pending_images: Mutex::default(), - node_image_animation_map: Arc::new(RwLock::new(std::mem::take( - &mut reflow_request.node_to_image_animation_map, - ))), - iframe_sizes: Mutex::default(), - use_rayon, - } - } - fn load_all_web_fonts_from_stylesheet_with_guard( &self, stylesheet: &DocumentStyleSheet, @@ -621,51 +587,126 @@ impl LayoutThread { return None; }; - // Calculate the actual viewport as per DEVICE-ADAPT § 6 - // If the entire flow tree is invalid, then it will be reflowed anyhow. let document_shared_lock = document.style_shared_lock(); let author_guard = document_shared_lock.read(); - let ua_stylesheets = &*UA_STYLESHEETS; let ua_or_user_guard = ua_stylesheets.shared_lock.read(); + let rayon_pool = STYLE_THREAD_POOL.lock(); + let rayon_pool = rayon_pool.pool(); + let rayon_pool = rayon_pool.as_ref(); let guards = StylesheetGuards { author: &author_guard, ua_or_user: &ua_or_user_guard, }; - let had_used_viewport_units = self.stylist.device().used_viewport_units(); - let viewport_size_changed = self.viewport_did_change(reflow_request.viewport_details); - let theme_changed = self.theme_did_change(reflow_request.theme); - - if viewport_size_changed || theme_changed { - self.update_device( - reflow_request.viewport_details, - reflow_request.theme, - &guards, - ); - } - - if viewport_size_changed && had_used_viewport_units { + if self.update_device_if_necessary(&reflow_request, &guards) { if let Some(mut data) = root_element.mutate_data() { data.hint.insert(RestyleHint::recascade_subtree()); } } + let mut snapshot_map = SnapshotMap::new(); + let _snapshot_setter = SnapshotSetter::new(&mut reflow_request, &mut snapshot_map); + self.prepare_stylist_for_reflow( + &reflow_request, + document, + root_element, + &guards, + ua_stylesheets, + &snapshot_map, + ); + + let mut layout_context = LayoutContext { + id: self.id, + origin: reflow_request.origin.clone(), + style_context: self.build_shared_style_context( + guards, + &snapshot_map, + reflow_request.animation_timeline_value, + &reflow_request.animations, + match reflow_request.stylesheets_changed { + true => TraversalFlags::ForCSSRuleChanges, + false => TraversalFlags::empty(), + }, + ), + image_cache: self.image_cache.clone(), + font_context: self.font_context.clone(), + webrender_image_cache: self.webrender_image_cache.clone(), + pending_images: Mutex::default(), + node_image_animation_map: Arc::new(RwLock::new(std::mem::take( + &mut reflow_request.node_to_image_animation_map, + ))), + iframe_sizes: Mutex::default(), + use_rayon: rayon_pool.is_some(), + }; + + self.restyle_and_build_trees( + &reflow_request, + root_element, + rayon_pool, + &mut layout_context, + ); + self.build_display_list(&reflow_request, &mut layout_context); + self.first_reflow.set(false); + + if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal { + self.update_scroll_node_state(&scroll_state); + } + + let pending_images = std::mem::take(&mut *layout_context.pending_images.lock()); + let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock()); + let node_to_image_animation_map = + std::mem::take(&mut *layout_context.node_image_animation_map.write()); + Some(ReflowResult { + pending_images, + iframe_sizes, + node_to_image_animation_map, + }) + } + + fn update_device_if_necessary( + &mut self, + reflow_request: &ReflowRequest, + guards: &StylesheetGuards, + ) -> bool { + let had_used_viewport_units = self.stylist.device().used_viewport_units(); + let viewport_size_changed = self.viewport_did_change(reflow_request.viewport_details); + let theme_changed = self.theme_did_change(reflow_request.theme); + if !viewport_size_changed && !theme_changed { + return false; + } + self.update_device( + reflow_request.viewport_details, + reflow_request.theme, + guards, + ); + (viewport_size_changed && had_used_viewport_units) || theme_changed + } + + fn prepare_stylist_for_reflow<'dom>( + &mut self, + reflow_request: &ReflowRequest, + document: ServoLayoutDocument<'dom>, + root_element: ServoLayoutElement<'dom>, + guards: &StylesheetGuards, + ua_stylesheets: &UserAgentStylesheets, + snapshot_map: &SnapshotMap, + ) { if self.first_reflow.get() { for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets { self.stylist - .append_stylesheet(stylesheet.clone(), &ua_or_user_guard); - self.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, &ua_or_user_guard); + .append_stylesheet(stylesheet.clone(), guards.ua_or_user); + self.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, guards.ua_or_user); } if self.stylist.quirks_mode() != QuirksMode::NoQuirks { self.stylist.append_stylesheet( ua_stylesheets.quirks_mode_stylesheet.clone(), - &ua_or_user_guard, + guards.ua_or_user, ); self.load_all_web_fonts_from_stylesheet_with_guard( &ua_stylesheets.quirks_mode_stylesheet, - &ua_or_user_guard, + guards.ua_or_user, ); } } @@ -678,185 +719,105 @@ impl LayoutThread { // Flush shadow roots stylesheets if dirty. document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author); - let restyles = std::mem::take(&mut reflow_request.pending_restyles); - debug!("Draining restyles: {}", restyles.len()); - - let mut map = SnapshotMap::new(); - let elements_with_snapshot: Vec<_> = restyles - .iter() - .filter(|r| r.1.snapshot.is_some()) - .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() }) - .collect(); - - for (el, restyle) in restyles { - let el = unsafe { ServoLayoutNode::new(&el).as_element().unwrap() }; - - // If we haven't styled this node yet, we don't need to track a - // restyle. - let mut style_data = match el.mutate_data() { - Some(d) => d, - None => { - unsafe { el.unset_snapshot_flags() }; - continue; - }, - }; - - if let Some(s) = restyle.snapshot { - unsafe { el.set_has_snapshot() }; - map.insert(el.as_node().opaque(), s); - } - - // Stash the data on the element for processing by the style system. - style_data.hint.insert(restyle.hint); - style_data.damage = restyle.damage; - debug!("Noting restyle for {:?}: {:?}", el, style_data); - } - - self.stylist.flush(&guards, Some(root_element), Some(&map)); - - let rayon_pool = STYLE_THREAD_POOL.lock(); - let rayon_pool = rayon_pool.pool(); - let rayon_pool = rayon_pool.as_ref(); - - // Create a layout context for use throughout the following passes. - let mut layout_context = self.build_layout_context( - guards.clone(), - &map, - &mut reflow_request, - rayon_pool.is_some(), - ); + self.stylist + .flush(guards, Some(root_element), Some(snapshot_map)); + } + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + fn restyle_and_build_trees( + &self, + reflow_request: &ReflowRequest, + root_element: ServoLayoutElement<'_>, + rayon_pool: Option<&ThreadPool>, + layout_context: &mut LayoutContext<'_>, + ) { let dirty_root = unsafe { ServoLayoutNode::new(&reflow_request.dirty_root.unwrap()) .as_element() .unwrap() }; - let traversal = RecalcStyle::new(layout_context); + let recalc_style_traversal = RecalcStyle::new(layout_context); let token = { - let shared = DomTraversal::<ServoLayoutElement>::shared_context(&traversal); + let shared = + DomTraversal::<ServoLayoutElement>::shared_context(&recalc_style_traversal); RecalcStyle::pre_traverse(dirty_root, shared) }; - if token.should_traverse() { - #[cfg(feature = "tracing")] - let _span = - tracing::trace_span!("driver::traverse_dom", servo_profiling = true).entered(); - let dirty_root: ServoLayoutNode = - driver::traverse_dom(&traversal, token, rayon_pool).as_node(); - - let root_node = root_element.as_node(); - let mut box_tree = self.box_tree.borrow_mut(); - let box_tree = &mut *box_tree; - let mut build_box_tree = || { - if !BoxTree::update(traversal.context(), dirty_root) { - *box_tree = Some(Arc::new(BoxTree::construct(traversal.context(), root_node))); - } - }; - if let Some(pool) = rayon_pool { - pool.install(build_box_tree) - } else { - build_box_tree() - }; - - let viewport_size = Size2D::new( - self.viewport_size.width.to_f32_px(), - self.viewport_size.height.to_f32_px(), - ); - let run_layout = || { - box_tree - .as_ref() - .unwrap() - .layout(traversal.context(), viewport_size) - }; - let fragment_tree = Arc::new(if let Some(pool) = rayon_pool { - pool.install(run_layout) - } else { - run_layout() - }); - *self.fragment_tree.borrow_mut() = Some(fragment_tree); + if !token.should_traverse() { + layout_context.style_context.stylist.rule_tree().maybe_gc(); + return; } - layout_context = traversal.destroy(); + let dirty_root: ServoLayoutNode = + driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node(); + + let root_node = root_element.as_node(); + let mut box_tree = self.box_tree.borrow_mut(); + let box_tree = &mut *box_tree; + let mut build_box_tree = || { + if !BoxTree::update(recalc_style_traversal.context(), dirty_root) { + *box_tree = Some(Arc::new(BoxTree::construct( + recalc_style_traversal.context(), + root_node, + ))); + } + }; + if let Some(pool) = rayon_pool { + pool.install(build_box_tree) + } else { + build_box_tree() + }; + + let viewport_size = Size2D::new( + self.viewport_size.width.to_f32_px(), + self.viewport_size.height.to_f32_px(), + ); + let run_layout = || { + box_tree + .as_ref() + .unwrap() + .layout(recalc_style_traversal.context(), viewport_size) + }; + let fragment_tree = Arc::new(if let Some(pool) = rayon_pool { + pool.install(run_layout) + } else { + run_layout() + }); - for element in elements_with_snapshot { - unsafe { element.unset_snapshot_flags() } - } + *self.fragment_tree.borrow_mut() = Some(fragment_tree); if self.debug.dump_style_tree { println!( "{:?}", - style::dom::ShowSubtreeDataAndPrimaryValues(root_element.as_node()) + ShowSubtreeDataAndPrimaryValues(root_element.as_node()) ); } - if self.debug.dump_rule_tree { - layout_context + recalc_style_traversal + .context() .style_context .stylist .rule_tree() - .dump_stdout(&guards); + .dump_stdout(&layout_context.shared_context().guards); } // GC the rule tree if some heuristics are met. layout_context.style_context.stylist.rule_tree().maybe_gc(); - - // Perform post-style recalculation layout passes. - if let Some(root) = &*self.fragment_tree.borrow() { - self.perform_post_style_recalc_layout_passes( - root.clone(), - &reflow_request.reflow_goal, - &mut layout_context, - ); - } - - self.first_reflow.set(false); - - if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal { - self.update_scroll_node_state(&scroll_state); - } - - let pending_images = std::mem::take(&mut *layout_context.pending_images.lock()); - let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock()); - let node_to_image_animation_map = - std::mem::take(&mut *layout_context.node_image_animation_map.write()); - Some(ReflowResult { - pending_images, - iframe_sizes, - node_to_image_animation_map, - }) } - fn update_scroll_node_state(&self, state: &ScrollState) { - self.scroll_offsets - .borrow_mut() - .insert(state.scroll_id, state.scroll_offset); - let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y); - self.compositor_api.send_scroll_node( - self.webview_id, - self.id.into(), - LayoutPoint::from_untyped(point), - state.scroll_id, - ); - } - - fn perform_post_style_recalc_layout_passes( + fn build_display_list( &self, - fragment_tree: Arc<FragmentTree>, - reflow_goal: &ReflowGoal, - context: &mut LayoutContext, + reflow_request: &ReflowRequest, + layout_context: &mut LayoutContext<'_>, ) { - Self::cancel_animations_for_nodes_not_in_fragment_tree( - &context.style_context.animations, - &fragment_tree, - ); - - Self::cancel_image_animation_for_nodes_not_in_fragment_tree( - context.node_image_animation_map.clone(), - &fragment_tree, - ); - - if !reflow_goal.needs_display_list() { + let Some(fragment_tree) = &*self.fragment_tree.borrow() else { + return; + }; + if !reflow_request.reflow_goal.needs_display_list() { return; } @@ -890,10 +851,10 @@ impl LayoutThread { // tree of fragments in CSS painting order and also creates all // applicable spatial and clip nodes. let root_stacking_context = - display_list.build_stacking_context_tree(&fragment_tree, &self.debug); + display_list.build_stacking_context_tree(fragment_tree, &self.debug); // Build the rest of the display list which inclues all of the WebRender primitives. - display_list.build(context, &fragment_tree, &root_stacking_context); + display_list.build(layout_context, fragment_tree, &root_stacking_context); if self.debug.dump_flow_tree { fragment_tree.print(); @@ -901,9 +862,8 @@ impl LayoutThread { if self.debug.dump_stacking_context_tree { root_stacking_context.debug_print(); } - debug!("Layout done!"); - if reflow_goal.needs_display() { + if reflow_request.reflow_goal.needs_display() { self.compositor_api.send_display_list( self.webview_id, display_list.compositor_info, @@ -918,6 +878,19 @@ impl LayoutThread { } } + fn update_scroll_node_state(&self, state: &ScrollState) { + self.scroll_offsets + .borrow_mut() + .insert(state.scroll_id, state.scroll_offset); + let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y); + self.compositor_api.send_scroll_node( + self.webview_id, + self.id.into(), + LayoutPoint::from_untyped(point), + state.scroll_id, + ); + } + /// Returns profiling information which is passed to the time profiler. fn profiler_metadata(&self) -> Option<TimerMetadata> { Some(TimerMetadata { @@ -935,42 +908,6 @@ impl LayoutThread { }) } - /// Cancel animations for any nodes which have been removed from fragment tree. - /// TODO(mrobinson): We should look into a way of doing this during flow tree construction. - /// This also doesn't yet handles nodes that have been reparented. - fn cancel_animations_for_nodes_not_in_fragment_tree( - animations: &DocumentAnimationSet, - root: &FragmentTree, - ) { - // Assume all nodes have been removed until proven otherwise. - let mut animations = animations.sets.write(); - let mut invalid_nodes = animations.keys().cloned().collect(); - root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes); - - // Cancel animations for any nodes that are no longer in the fragment tree. - for node in &invalid_nodes { - if let Some(state) = animations.get_mut(node) { - state.cancel_all_animations(); - } - } - } - - fn cancel_image_animation_for_nodes_not_in_fragment_tree( - image_animation_set: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>, - root: &FragmentTree, - ) { - let mut image_animations = image_animation_set.write().to_owned(); - let mut invalid_nodes: FxHashSet<AnimationSetKey> = image_animations - .keys() - .cloned() - .map(|node| AnimationSetKey::new(node, None)) - .collect(); - root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes); - for node in &invalid_nodes { - image_animations.remove(&node.node); - } - } - fn viewport_did_change(&mut self, viewport_details: ViewportDetails) -> bool { let new_pixel_ratio = viewport_details.hidpi_scale_factor.get(); let new_viewport_size = Size2D::new( @@ -1230,6 +1167,54 @@ impl Debug for LayoutFontMetricsProvider { } } -thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const { - LazyCell::new(|| RefCell::new(HashSet::new())) -}); +struct SnapshotSetter<'dom> { + elements_with_snapshot: Vec<ServoLayoutElement<'dom>>, +} + +impl SnapshotSetter<'_> { + fn new(reflow_request: &mut ReflowRequest, snapshot_map: &mut SnapshotMap) -> Self { + debug!( + "Draining restyles: {}", + reflow_request.pending_restyles.len() + ); + let restyles = std::mem::take(&mut reflow_request.pending_restyles); + + let elements_with_snapshot: Vec<_> = restyles + .iter() + .filter(|r| r.1.snapshot.is_some()) + .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() }) + .collect(); + + for (element, restyle) in restyles { + let element = unsafe { ServoLayoutNode::new(&element).as_element().unwrap() }; + + // If we haven't styled this node yet, we don't need to track a + // restyle. + let Some(mut style_data) = element.mutate_data() else { + unsafe { element.unset_snapshot_flags() }; + continue; + }; + + debug!("Noting restyle for {:?}: {:?}", element, style_data); + if let Some(s) = restyle.snapshot { + unsafe { element.set_has_snapshot() }; + snapshot_map.insert(element.as_node().opaque(), s); + } + + // Stash the data on the element for processing by the style system. + style_data.hint.insert(restyle.hint); + style_data.damage = restyle.damage; + } + Self { + elements_with_snapshot, + } + } +} + +impl Drop for SnapshotSetter<'_> { + fn drop(&mut self) { + for element in &self.elements_with_snapshot { + unsafe { element.unset_snapshot_flags() } + } + } +} diff --git a/components/layout/query.rs b/components/layout/query.rs index 08b264deea9..e78acdd0ca8 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use app_units::Au; use euclid::default::{Point2D, Rect}; -use euclid::{SideOffsets2D, Size2D, Vector2D}; +use euclid::{SideOffsets2D, Size2D}; use itertools::Itertools; use script_layout_interface::wrapper_traits::{ LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, @@ -38,59 +38,60 @@ use style::values::specified::GenericGridTemplateComponent; use style::values::specified::box_::DisplayInside; use style_traits::{ParsingMode, ToCss}; +use crate::ArcRefCell; use crate::dom::NodeExt; use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse}; use crate::fragment_tree::{ - BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag, + BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, }; -use crate::geom::{PhysicalRect, PhysicalVec}; use crate::taffy::SpecificTaffyGridInfo; -pub fn process_content_box_request( - requested_node: OpaqueNode, - fragment_tree: Option<Arc<FragmentTree>>, -) -> Option<Rect<Au>> { - let rects = fragment_tree?.get_content_boxes_for_node(requested_node); +pub fn process_content_box_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Option<Rect<Au>> { + let rects: Vec<_> = node + .fragments_for_pseudo(None) + .iter() + .filter_map(Fragment::cumulative_border_box_rect) + .collect(); if rects.is_empty() { return None; } - Some( - rects - .iter() - .fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect)), - ) + Some(rects.iter().fold(Rect::zero(), |unioned_rect, rect| { + rect.to_untyped().union(&unioned_rect) + })) } -pub fn process_content_boxes_request( - requested_node: OpaqueNode, - fragment_tree: Option<Arc<FragmentTree>>, -) -> Vec<Rect<Au>> { - fragment_tree - .map(|tree| tree.get_content_boxes_for_node(requested_node)) - .unwrap_or_default() +pub fn process_content_boxes_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Vec<Rect<Au>> { + node.fragments_for_pseudo(None) + .iter() + .filter_map(Fragment::cumulative_border_box_rect) + .map(|rect| rect.to_untyped()) + .collect() } -pub fn process_node_geometry_request( - requested_node: OpaqueNode, - fragment_tree: Option<Arc<FragmentTree>>, -) -> Rect<i32> { - if let Some(fragment_tree) = fragment_tree { - fragment_tree.get_border_dimensions_for_node(requested_node) - } else { - Rect::zero() - } +pub fn process_client_rect_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Rect<i32> { + node.fragments_for_pseudo(None) + .first() + .map(Fragment::client_rect) + .unwrap_or_default() } /// <https://drafts.csswg.org/cssom-view/#scrolling-area> -pub fn process_node_scroll_area_request( - requested_node: Option<OpaqueNode>, +pub fn process_node_scroll_area_request<'dom>( + requested_node: Option<impl LayoutNode<'dom> + 'dom>, fragment_tree: Option<Arc<FragmentTree>>, ) -> Rect<i32> { - let rect = match (fragment_tree, requested_node) { - (Some(tree), Some(node)) => tree.get_scrolling_area_for_node(node), - (Some(tree), None) => tree.get_scrolling_area_for_viewport(), - _ => return Rect::zero(), + let Some(tree) = fragment_tree else { + return Rect::zero(); + }; + + let rect = match requested_node { + Some(node) => node + .fragments_for_pseudo(None) + .first() + .map(Fragment::scrolling_area) + .unwrap_or_default(), + None => tree.get_scrolling_area_for_viewport(), }; Rect::new( @@ -109,7 +110,6 @@ pub fn process_resolved_style_request<'dom>( node: impl LayoutNode<'dom> + 'dom, pseudo: &Option<PseudoElement>, property: &PropertyId, - fragment_tree: Option<Arc<FragmentTree>>, ) -> String { if !node.as_element().unwrap().has_data() { return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property); @@ -161,8 +161,6 @@ pub fn process_resolved_style_request<'dom>( _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)), }; - let tag_to_find = Tag::new_pseudo(node.opaque(), *pseudo); - // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle // Here we are trying to conform to the specification that says that getComputedStyle // should return the used values in certain circumstances. For size and positional @@ -191,107 +189,87 @@ pub fn process_resolved_style_request<'dom>( return computed_style(None); } - let resolve_for_fragment = - |fragment: &Fragment, containing_block: Option<&PhysicalRect<Au>>| { - let (content_rect, margins, padding, specific_layout_info) = match fragment { - Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { - let box_fragment = box_fragment.borrow(); - if style.get_box().position != Position::Static { - let resolved_insets = || { - box_fragment - .calculate_resolved_insets_if_positioned(containing_block.unwrap()) - }; - match longhand_id { - LonghandId::Top => return resolved_insets().top.to_css_string(), - LonghandId::Right => { - return resolved_insets().right.to_css_string(); - }, - LonghandId::Bottom => { - return resolved_insets().bottom.to_css_string(); - }, - LonghandId::Left => { - return resolved_insets().left.to_css_string(); - }, - _ => {}, - } - } - let content_rect = box_fragment.content_rect; - let margins = box_fragment.margin; - let padding = box_fragment.padding; - let specific_layout_info = box_fragment.specific_layout_info.clone(); - (content_rect, margins, padding, specific_layout_info) - }, - Fragment::Positioning(positioning_fragment) => { - let content_rect = positioning_fragment.borrow().rect; - ( - content_rect, - SideOffsets2D::zero(), - SideOffsets2D::zero(), - None, - ) - }, - _ => return computed_style(Some(fragment)), - }; - - // https://drafts.csswg.org/css-grid/#resolved-track-list - // > The grid-template-rows and grid-template-columns properties are - // > resolved value special case properties. - // - // > When an element generates a grid container box... - if display.inside() == DisplayInside::Grid { - if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info { - if let Some(value) = resolve_grid_template(&info, style, longhand_id) { - return value; + let resolve_for_fragment = |fragment: &Fragment| { + let (content_rect, margins, padding, specific_layout_info) = match fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { + let box_fragment = box_fragment.borrow(); + if style.get_box().position != Position::Static { + let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned(); + match longhand_id { + LonghandId::Top => return resolved_insets().top.to_css_string(), + LonghandId::Right => { + return resolved_insets().right.to_css_string(); + }, + LonghandId::Bottom => { + return resolved_insets().bottom.to_css_string(); + }, + LonghandId::Left => { + return resolved_insets().left.to_css_string(); + }, + _ => {}, } } - } + let content_rect = box_fragment.content_rect; + let margins = box_fragment.margin; + let padding = box_fragment.padding; + let specific_layout_info = box_fragment.specific_layout_info.clone(); + (content_rect, margins, padding, specific_layout_info) + }, + Fragment::Positioning(positioning_fragment) => { + let content_rect = positioning_fragment.borrow().rect; + ( + content_rect, + SideOffsets2D::zero(), + SideOffsets2D::zero(), + None, + ) + }, + _ => return computed_style(Some(fragment)), + }; - // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height - // > If the property applies to the element or pseudo-element and the resolved value of the - // > display property is not none or contents, then the resolved value is the used value. - // > Otherwise the resolved value is the computed value. - // - // However, all browsers ignore that for margin and padding properties, and resolve to a length - // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391 - match longhand_id { - LonghandId::Width if resolved_size_should_be_used_value(fragment) => { - content_rect.size.width - }, - LonghandId::Height if resolved_size_should_be_used_value(fragment) => { - content_rect.size.height - }, - LonghandId::MarginBottom => margins.bottom, - LonghandId::MarginTop => margins.top, - LonghandId::MarginLeft => margins.left, - LonghandId::MarginRight => margins.right, - LonghandId::PaddingBottom => padding.bottom, - LonghandId::PaddingTop => padding.top, - LonghandId::PaddingLeft => padding.left, - LonghandId::PaddingRight => padding.right, - _ => return computed_style(Some(fragment)), + // https://drafts.csswg.org/css-grid/#resolved-track-list + // > The grid-template-rows and grid-template-columns properties are + // > resolved value special case properties. + // + // > When an element generates a grid container box... + if display.inside() == DisplayInside::Grid { + if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info { + if let Some(value) = resolve_grid_template(&info, style, longhand_id) { + return value; + } } - .to_css_string() - }; + } - if !matches!( - longhand_id, - LonghandId::Top | LonghandId::Bottom | LonghandId::Left | LonghandId::Right - ) { - if let Some(fragment) = node.fragments_for_pseudo(*pseudo).first() { - return resolve_for_fragment(fragment, None); + // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height + // > If the property applies to the element or pseudo-element and the resolved value of the + // > display property is not none or contents, then the resolved value is the used value. + // > Otherwise the resolved value is the computed value. + // + // However, all browsers ignore that for margin and padding properties, and resolve to a length + // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391 + match longhand_id { + LonghandId::Width if resolved_size_should_be_used_value(fragment) => { + content_rect.size.width + }, + LonghandId::Height if resolved_size_should_be_used_value(fragment) => { + content_rect.size.height + }, + LonghandId::MarginBottom => margins.bottom, + LonghandId::MarginTop => margins.top, + LonghandId::MarginLeft => margins.left, + LonghandId::MarginRight => margins.right, + LonghandId::PaddingBottom => padding.bottom, + LonghandId::PaddingTop => padding.top, + LonghandId::PaddingLeft => padding.left, + LonghandId::PaddingRight => padding.right, + _ => return computed_style(Some(fragment)), } - } + .to_css_string() + }; - fragment_tree - .and_then(|fragment_tree| { - fragment_tree.find(|fragment, _, containing_block| { - if Some(tag_to_find) == fragment.tag() { - Some(resolve_for_fragment(fragment, Some(containing_block))) - } else { - None - } - }) - }) + node.fragments_for_pseudo(*pseudo) + .first() + .map(resolve_for_fragment) .unwrap_or_else(|| computed_style(None)) } @@ -450,231 +428,157 @@ fn shorthand_to_css_string( } } -pub fn process_offset_parent_query( - node: OpaqueNode, - fragment_tree: Option<Arc<FragmentTree>>, -) -> OffsetParentResponse { - process_offset_parent_query_inner(node, fragment_tree).unwrap_or_default() +struct OffsetParentFragments { + parent: ArcRefCell<BoxFragment>, + grandparent: Option<Fragment>, } -#[inline] -fn process_offset_parent_query_inner( - node: OpaqueNode, - fragment_tree: Option<Arc<FragmentTree>>, -) -> Option<OffsetParentResponse> { - let fragment_tree = fragment_tree?; - - struct NodeOffsetBoxInfo { - border_box: Rect<Au>, - offset_parent_node_address: Option<OpaqueNode>, - is_static_body_element: bool, +/// <https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#dom-htmlelement-offsetparent> +fn offset_parent_fragments<'dom>( + node: impl LayoutNode<'dom> + 'dom, +) -> Option<OffsetParentFragments> { + // 1. If any of the following holds true return null and terminate this algorithm: + // * The element does not have an associated CSS layout box. + // * The element is the root element. + // * The element is the HTML body element. + // * The element’s computed value of the position property is fixed. + let fragment = node.fragments_for_pseudo(None).first().cloned()?; + let flags = fragment.base()?.flags; + if flags.intersects( + FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT, + ) { + return None; + } + if matches!( + fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed + ) { + return None; } - // https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#extensions-to-the-htmlelement-interface - let mut parent_node_addresses: Vec<Option<(OpaqueNode, bool)>> = Vec::new(); - let tag_to_find = Tag::new(node); - let node_offset_box = fragment_tree.find(|fragment, level, containing_block| { - let base = fragment.base()?; - let is_body_element = base - .flags - .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT); - - if fragment.tag() == Some(tag_to_find) { - // Only consider the first fragment of the node found as per a - // possible interpretation of the specification: "[...] return the - // y-coordinate of the top border edge of the first CSS layout box - // associated with the element [...]" - // - // FIXME: Browsers implement this all differently (e.g., [1]) - - // Firefox does returns the union of all layout elements of some - // sort. Chrome returns the first fragment for a block element (the - // same as ours) or the union of all associated fragments in the - // first containing block fragment for an inline element. We could - // implement Chrome's behavior, but our fragment tree currently - // provides insufficient information. - // - // [1]: https://github.com/w3c/csswg-drafts/issues/4541 - let fragment_relative_rect = match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.borrow().border_rect(), - Fragment::Text(fragment) => fragment.borrow().rect, - Fragment::Positioning(fragment) => fragment.borrow().rect, - Fragment::AbsoluteOrFixedPositioned(_) | - Fragment::Image(_) | - Fragment::IFrame(_) => unreachable!(), + // 2. Return the nearest ancestor element of the element for which at least one of + // the following is true and terminate this algorithm if such an ancestor is found: + // * The computed value of the position property is not static. + // * It is the HTML body element. + // * The computed value of the position property of the element is static and the + // ancestor is one of the following HTML elements: td, th, or table. + let mut maybe_parent_node = node.parent_node(); + while let Some(parent_node) = maybe_parent_node { + maybe_parent_node = parent_node.parent_node(); + + if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() { + let parent_fragment = match parent_fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment, + _ => continue, }; - let mut border_box = fragment_relative_rect.translate(containing_block.origin.to_vector()).to_untyped(); - - // "If any of the following holds true return null and terminate - // this algorithm: [...] The element’s computed value of the - // `position` property is `fixed`." - let is_fixed = matches!( - fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed - ); + let grandparent_fragment = + maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned()); - if is_body_element { - // "If the element is the HTML body element or [...] return zero - // and terminate this algorithm." - border_box.origin = Point2D::zero(); + if parent_fragment.borrow().style.get_box().position != Position::Static { + return Some(OffsetParentFragments { + parent: parent_fragment.clone(), + grandparent: grandparent_fragment, + }); } - let offset_parent_node = if is_fixed { - None - } else { - // Find the nearest ancestor element eligible as `offsetParent`. - parent_node_addresses[..level] - .iter() - .rev() - .cloned() - .find_map(std::convert::identity) - }; - - Some(NodeOffsetBoxInfo { - border_box, - offset_parent_node_address: offset_parent_node.map(|node| node.0), - is_static_body_element: offset_parent_node.is_some_and(|node| node.1), - }) - } else { - // Record the paths of the nodes being traversed. - let parent_node_address = match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - let fragment = &*fragment.borrow(); - let is_eligible_parent = is_eligible_parent(fragment); - let is_static_body_element = is_body_element && - fragment.style.get_box().position == Position::Static; - match base.tag { - Some(tag) if is_eligible_parent && !tag.is_pseudo() => { - Some((tag.node, is_static_body_element)) - }, - _ => None, - } - }, - Fragment::AbsoluteOrFixedPositioned(_) | - Fragment::IFrame(_) | - Fragment::Image(_) | - Fragment::Positioning(_) | - Fragment::Text(_) => None, - }; - - while parent_node_addresses.len() <= level { - parent_node_addresses.push(None); + let flags = parent_fragment.borrow().base.flags; + if flags.intersects( + FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT | + FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT, + ) { + return Some(OffsetParentFragments { + parent: parent_fragment.clone(), + grandparent: grandparent_fragment, + }); } - parent_node_addresses[level] = parent_node_address; - None } - }); + } - // Bail out if the element doesn't have an associated fragment. - // "If any of the following holds true return null and terminate this - // algorithm: [...] The element does not have an associated CSS layout box." - // (`offsetParent`) "If the element is the HTML body element [...] return - // zero and terminate this algorithm." (others) - let node_offset_box = node_offset_box?; + None +} - let offset_parent_padding_box_corner = if let Some(offset_parent_node_address) = - node_offset_box.offset_parent_node_address - { - // The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface) - // says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent. - // However, in practice this is not true in major browsers in the case that the offsetParent is the body - // element and the body element is position:static. In that case offsetLeft/offsetTop are computed - // relative to the root node's border box. - if node_offset_box.is_static_body_element { - fn extract_box_fragment( - fragment: &Fragment, - containing_block: &PhysicalRect<Au>, - ) -> PhysicalVec<Au> { - let (Fragment::Box(fragment) | Fragment::Float(fragment)) = fragment else { - unreachable!(); - }; - // Again, take the *first* associated CSS layout box. - fragment.borrow().border_rect().origin.to_vector() + - containing_block.origin.to_vector() - } +#[inline] +pub fn process_offset_parent_query<'dom>( + node: impl LayoutNode<'dom> + 'dom, +) -> Option<OffsetParentResponse> { + // Only consider the first fragment of the node found as per a + // possible interpretation of the specification: "[...] return the + // y-coordinate of the top border edge of the first CSS layout box + // associated with the element [...]" + // + // FIXME: Browsers implement this all differently (e.g., [1]) - + // Firefox does returns the union of all layout elements of some + // sort. Chrome returns the first fragment for a block element (the + // same as ours) or the union of all associated fragments in the + // first containing block fragment for an inline element. We could + // implement Chrome's behavior, but our fragment tree currently + // provides insufficient information. + // + // [1]: https://github.com/w3c/csswg-drafts/issues/4541 + // > 1. If the element is the HTML body element or does not have any associated CSS + // layout box return zero and terminate this algorithm. + let fragment = node.fragments_for_pseudo(None).first().cloned()?; + let mut border_box = fragment.cumulative_border_box_rect()?; + + // 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 + // the initial containing block origin, ignoring any transforms that apply to the + // element and its ancestors, and terminate this algorithm. + let Some(offset_parent_fragment) = offset_parent_fragments(node) else { + return Some(OffsetParentResponse { + node_address: None, + rect: border_box.to_untyped(), + }); + }; - let containing_block = &fragment_tree.initial_containing_block; - let fragment = &fragment_tree.root_fragments[0]; - if let Fragment::AbsoluteOrFixedPositioned(shared_fragment) = fragment { - let shared_fragment = &*shared_fragment.borrow(); - let fragment = shared_fragment.fragment.as_ref().unwrap(); - extract_box_fragment(fragment, containing_block) - } else { - extract_box_fragment(fragment, containing_block) - } + let parent_fragment = offset_parent_fragment.parent.borrow(); + let parent_is_static_body_element = parent_fragment + .base + .flags + .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) && + parent_fragment.style.get_box().position == Position::Static; + + // For `offsetLeft`: + // 3. Return the result of subtracting the y-coordinate of the top padding edge of the + // first CSS layout box associated with the offsetParent of the element from the + // y-coordinate of the top border edge of the first CSS layout box associated with the + // element, relative to the initial containing block origin, ignoring any transforms + // that apply to the element and its ancestors. + // + // We generalize this for `offsetRight` as described in the specification. + let grandparent_box_fragment = || match offset_parent_fragment.grandparent { + Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => { + Some(box_fragment) + }, + _ => None, + }; + + // The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface) + // says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent. + // However, in practice this is not true in major browsers in the case that the offsetParent is the body + // element and the body element is position:static. In that case offsetLeft/offsetTop are computed + // relative to the root node's border box. + // + // See <https://github.com/w3c/csswg-drafts/issues/10549>. + let parent_offset_rect = if parent_is_static_body_element { + if let Some(grandparent_fragment) = grandparent_box_fragment() { + let grandparent_fragment = grandparent_fragment.borrow(); + grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect()) } else { - // Find the top and left padding edges of "the first CSS layout box - // associated with the `offsetParent` of the element". - // - // Since we saw `offset_parent_node_address` once, we should be able - // to find it again. - let offset_parent_node_tag = Tag::new(offset_parent_node_address); - fragment_tree - .find(|fragment, _, containing_block| { - match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - let fragment = fragment.borrow(); - if fragment.base.tag == Some(offset_parent_node_tag) { - // Again, take the *first* associated CSS layout box. - let padding_box_corner = fragment.padding_rect().origin.to_vector() - + containing_block.origin.to_vector(); - Some(padding_box_corner) - } else { - None - } - }, - Fragment::AbsoluteOrFixedPositioned(_) - | Fragment::Text(_) - | Fragment::Image(_) - | Fragment::IFrame(_) - | Fragment::Positioning(_) => None, - } - }) - .unwrap() + parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect()) } } else { - // "If the offsetParent of the element is null," subtract zero in the - // following step. - Vector2D::zero() + parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect()) }; + border_box = border_box.translate(-parent_offset_rect.origin.to_vector()); + Some(OffsetParentResponse { - node_address: node_offset_box.offset_parent_node_address.map(Into::into), - // "Return the result of subtracting the x-coordinate of the left - // padding edge of the first CSS layout box associated with the - // `offsetParent` of the element from the x-coordinate of the left - // border edge of the first CSS layout box associated with the element, - // relative to the initial containing block origin, ignoring any - // transforms that apply to the element and its ancestors." (and vice - // versa for the top border edge) - rect: node_offset_box - .border_box - .translate(-offset_parent_padding_box_corner.to_untyped()), + node_address: parent_fragment.base.tag.map(|tag| tag.node.into()), + rect: border_box.to_untyped(), }) } -/// Returns whether or not the element with the given style and body element determination -/// is eligible to be a parent element for offset* queries. -/// -/// From <https://www.w3.org/TR/cssom-view-1/#dom-htmlelement-offsetparent>: -/// -/// > Return the nearest ancestor element of the element for which at least one of the following is -/// > true and terminate this algorithm if such an ancestor is found: -/// > 1. The computed value of the position property is not static. -/// > 2. It is the HTML body element. -/// > 3. The computed value of the position property of the element is static and the ancestor is -/// > one of the following HTML elements: td, th, or table. -fn is_eligible_parent(fragment: &BoxFragment) -> bool { - fragment - .base - .flags - .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) || - fragment.style.get_box().position != Position::Static || - fragment - .base - .flags - .contains(FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT) -} - /// <https://html.spec.whatwg.org/multipage/#get-the-text-steps> pub fn get_the_text_steps<'dom>(node: impl LayoutNode<'dom>) -> String { // Step 1: If element is not being rendered or if the user agent is a non-CSS user agent, then diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs index 57b48ae0bca..2261f7d165c 100644 --- a/components/layout/table/layout.rs +++ b/components/layout/table/layout.rs @@ -2142,23 +2142,27 @@ impl<'a> TableLayout<'a> { for column_group in self.table.column_groups.iter() { let column_group = column_group.borrow(); if !column_group.is_empty() { - fragments.push(Fragment::Positioning(PositioningFragment::new_empty( + let fragment = Fragment::Positioning(PositioningFragment::new_empty( column_group.base.base_fragment_info, dimensions .get_column_group_rect(&column_group) .as_physical(None), column_group.base.style.clone(), - ))); + )); + column_group.base.set_fragment(fragment.clone()); + fragments.push(fragment); } } for (column_index, column) in self.table.columns.iter().enumerate() { let column = column.borrow(); - fragments.push(Fragment::Positioning(PositioningFragment::new_empty( + let fragment = Fragment::Positioning(PositioningFragment::new_empty( column.base.base_fragment_info, dimensions.get_column_rect(column_index).as_physical(None), column.base.style.clone(), - ))); + )); + column.base.set_fragment(fragment.clone()); + fragments.push(fragment); } } diff --git a/components/layout/table/mod.rs b/components/layout/table/mod.rs index 120270fc7cf..fe7f90437b8 100644 --- a/components/layout/table/mod.rs +++ b/components/layout/table/mod.rs @@ -346,6 +346,7 @@ pub(crate) struct TableLayoutStyle<'a> { /// Table parts that are stored in the DOM. This is used in order to map from /// the DOM to the box tree and will eventually be important for incremental /// layout. +#[derive(MallocSizeOf)] pub(crate) enum TableLevelBox { Caption(ArcRefCell<TableCaption>), Cell(ArcRefCell<TableSlotCell>), diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index 40281b640c9..bf60c41d6ba 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -12,19 +12,15 @@ use crate::context::LayoutContext; use crate::dom::DOMLayoutData; pub struct RecalcStyle<'a> { - context: LayoutContext<'a>, + context: &'a LayoutContext<'a>, } impl<'a> RecalcStyle<'a> { - pub fn new(context: LayoutContext<'a>) -> Self { + pub fn new(context: &'a LayoutContext<'a>) -> Self { RecalcStyle { context } } pub fn context(&self) -> &LayoutContext<'a> { - &self.context - } - - pub fn destroy(self) -> LayoutContext<'a> { self.context } } |