aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout')
-rw-r--r--components/layout/dom.rs38
-rw-r--r--components/layout/flow/construct.rs21
-rw-r--r--components/layout/flow/inline/construct.rs64
-rw-r--r--components/layout/flow/inline/inline_box.rs16
-rw-r--r--components/layout/flow/inline/mod.rs8
-rw-r--r--components/layout/flow/mod.rs105
-rw-r--r--components/layout/flow/root.rs32
-rw-r--r--components/layout/fragment_tree/base_fragment.rs5
-rw-r--r--components/layout/fragment_tree/box_fragment.rs25
-rw-r--r--components/layout/fragment_tree/fragment.rs89
-rw-r--r--components/layout/fragment_tree/fragment_tree.rs160
-rw-r--r--components/layout/fragment_tree/positioning_fragment.rs14
-rw-r--r--components/layout/layout_impl.rs529
-rw-r--r--components/layout/query.rs572
-rw-r--r--components/layout/table/layout.rs12
-rw-r--r--components/layout/table/mod.rs1
-rw-r--r--components/layout/traversal.rs8
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
}
}