diff options
Diffstat (limited to 'components/layout/flow')
-rw-r--r-- | components/layout/flow/construct.rs | 258 | ||||
-rw-r--r-- | components/layout/flow/float.rs | 5 | ||||
-rw-r--r-- | components/layout/flow/inline/construct.rs | 72 | ||||
-rw-r--r-- | components/layout/flow/inline/inline_box.rs | 13 | ||||
-rw-r--r-- | components/layout/flow/inline/line.rs | 21 | ||||
-rw-r--r-- | components/layout/flow/inline/mod.rs | 30 | ||||
-rw-r--r-- | components/layout/flow/inline/text_run.rs | 72 | ||||
-rw-r--r-- | components/layout/flow/mod.rs | 7 | ||||
-rw-r--r-- | components/layout/flow/root.rs | 98 |
9 files changed, 302 insertions, 274 deletions
diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs index 5ed567f513b..334da8ae2b0 100644 --- a/components/layout/flow/construct.rs +++ b/components/layout/flow/construct.rs @@ -13,9 +13,9 @@ use style::selector_parser::PseudoElement; use style::str::char_is_whitespace; use super::OutsideMarker; -use super::inline::InlineFormattingContext; use super::inline::construct::InlineFormattingContextBuilder; use super::inline::inline_box::InlineBox; +use super::inline::{InlineFormattingContext, SharedInlineStyles}; use crate::PropagatedBoxTreeData; use crate::cell::ArcRefCell; use crate::context::LayoutContext; @@ -33,16 +33,13 @@ use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, D use crate::table::{AnonymousTableContent, Table}; impl BlockFormattingContext { - pub(crate) fn construct<'dom, Node>( + pub(crate) fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'_>, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, is_list_item: bool, - ) -> Self - where - Node: NodeExt<'dom>, - { + ) -> Self { Self::from_block_container(BlockContainer::construct( context, info, @@ -61,8 +58,8 @@ impl BlockFormattingContext { } } -struct BlockLevelJob<'dom, Node> { - info: NodeAndStyleInfo<Node>, +struct BlockLevelJob<'dom> { + info: NodeAndStyleInfo<'dom>, box_slot: BoxSlot<'dom>, propagated_data: PropagatedBoxTreeData, kind: BlockLevelCreator, @@ -111,12 +108,12 @@ enum IntermediateBlockContainer { /// /// This builder starts from the first child of a given DOM node /// and does a preorder traversal of all of its inclusive siblings. -pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> { +pub(crate) struct BlockContainerBuilder<'dom, 'style> { context: &'style LayoutContext<'style>, /// This NodeAndStyleInfo contains the root node, the corresponding pseudo /// content designator, and the block container style. - info: &'style NodeAndStyleInfo<Node>, + info: &'style NodeAndStyleInfo<'dom>, /// The list of block-level boxes to be built for the final block container. /// @@ -131,7 +128,7 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> { /// doesn't have a next sibling, we either reached the end of the container /// root or there are ongoing inline-level boxes /// (see `handle_block_level_element`). - block_level_boxes: Vec<BlockLevelJob<'dom, Node>>, + block_level_boxes: Vec<BlockLevelJob<'dom>>, /// Whether or not this builder has yet produced a block which would be /// be considered the first line for the purposes of `text-indent`. @@ -140,29 +137,35 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> { /// The propagated data to use for BoxTree construction. propagated_data: PropagatedBoxTreeData, - inline_formatting_context_builder: InlineFormattingContextBuilder, + /// The [`InlineFormattingContextBuilder`] if we have encountered any inline items, + /// otherwise None. + /// + /// TODO: This can be `OnceCell` once `OnceCell::get_mut_or_init` is stabilized. + inline_formatting_context_builder: Option<InlineFormattingContextBuilder>, /// The [`NodeAndStyleInfo`] to use for anonymous block boxes pushed to the list of - /// block-level boxes, lazily initialized (see `end_ongoing_inline_formatting_context`). - anonymous_box_info: Option<NodeAndStyleInfo<Node>>, + /// block-level boxes, lazily initialized. + anonymous_box_info: Option<NodeAndStyleInfo<'dom>>, /// A collection of content that is being added to an anonymous table. This is /// composed of any sequence of internal table elements or table captions that /// are found outside of a table. - anonymous_table_content: Vec<AnonymousTableContent<'dom, Node>>, + anonymous_table_content: Vec<AnonymousTableContent<'dom>>, + + /// Any [`InlineFormattingContexts`] created need to know about the ongoing `display: contents` + /// ancestors that have been processed. This `Vec` allows passing those into new + /// [`InlineFormattingContext`]s that we create. + display_contents_shared_styles: Vec<SharedInlineStyles>, } impl BlockContainer { - pub fn construct<'dom, Node>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'_>, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, is_list_item: bool, - ) -> BlockContainer - where - Node: NodeExt<'dom>, - { + ) -> BlockContainer { let mut builder = BlockContainerBuilder::new(context, info, propagated_data); if is_list_item { @@ -186,13 +189,10 @@ impl BlockContainer { } } -impl<'dom, 'style, Node> BlockContainerBuilder<'dom, 'style, Node> -where - Node: NodeExt<'dom>, -{ +impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { pub(crate) fn new( context: &'style LayoutContext, - info: &'style NodeAndStyleInfo<Node>, + info: &'style NodeAndStyleInfo<'dom>, propagated_data: PropagatedBoxTreeData, ) -> Self { BlockContainerBuilder { @@ -203,26 +203,44 @@ where have_already_seen_first_line_for_text_indent: false, anonymous_box_info: None, anonymous_table_content: Vec::new(), - inline_formatting_context_builder: InlineFormattingContextBuilder::new(), + inline_formatting_context_builder: None, + display_contents_shared_styles: Vec::new(), } } - pub(crate) fn finish(mut self) -> BlockContainer { - debug_assert!( - !self - .inline_formatting_context_builder - .currently_processing_inline_box() - ); + fn currently_processing_inline_box(&self) -> bool { + self.inline_formatting_context_builder + .as_ref() + .is_some_and(InlineFormattingContextBuilder::currently_processing_inline_box) + } - self.finish_anonymous_table_if_needed(); + fn ensure_inline_formatting_context_builder(&mut self) -> &mut InlineFormattingContextBuilder { + self.inline_formatting_context_builder + .get_or_insert_with(|| { + let mut builder = InlineFormattingContextBuilder::new(self.info); + for shared_inline_styles in self.display_contents_shared_styles.iter() { + builder.enter_display_contents(shared_inline_styles.clone()); + } + builder + }) + } - if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish( + fn finish_ongoing_inline_formatting_context(&mut self) -> Option<InlineFormattingContext> { + self.inline_formatting_context_builder.take()?.finish( self.context, self.propagated_data, !self.have_already_seen_first_line_for_text_indent, self.info.is_single_line_text_input(), self.info.style.writing_mode.to_bidi_level(), - ) { + ) + } + + pub(crate) fn finish(mut self) -> BlockContainer { + debug_assert!(!self.currently_processing_inline_box()); + + self.finish_anonymous_table_if_needed(); + + if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() { // There are two options here. This block was composed of both one or more inline formatting contexts // and child blocks OR this block was a single inline formatting context. In the latter case, we // just return the inline formatting context as the block itself. @@ -260,9 +278,7 @@ where // // Note that text content in the inline formatting context isn't enough to force the // creation of an inline table. It requires the parent to be an inline box. - let inline_table = self - .inline_formatting_context_builder - .currently_processing_inline_box(); + let inline_table = self.currently_processing_inline_box(); // Text decorations are not propagated to atomic inline-level descendants. // From https://drafts.csswg.org/css2/#lining-striking-props: @@ -274,7 +290,7 @@ where false => self.propagated_data, }; - let contents: Vec<AnonymousTableContent<'dom, Node>> = + let contents: Vec<AnonymousTableContent<'dom>> = self.anonymous_table_content.drain(..).collect(); let last_text = match contents.last() { Some(AnonymousTableContent::Text(info, text)) => Some((info.clone(), text.clone())), @@ -285,10 +301,16 @@ where Table::construct_anonymous(self.context, self.info, contents, propagated_data); if inline_table { - self.inline_formatting_context_builder.push_atomic(ifc); + self.ensure_inline_formatting_context_builder() + .push_atomic(ifc); } else { let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc)); - self.end_ongoing_inline_formatting_context(); + + if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() + { + self.push_block_level_job_for_inline_formatting_context(inline_formatting_context); + } + self.block_level_boxes.push(BlockLevelJob { info: table_info, box_slot: BoxSlot::dummy(), @@ -312,13 +334,10 @@ where } } -impl<'dom, Node> TraversalHandler<'dom, Node> for BlockContainerBuilder<'dom, '_, Node> -where - Node: NodeExt<'dom>, -{ +impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> { fn handle_element( &mut self, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, @@ -359,7 +378,7 @@ where } } - fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) { + fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) { if text.is_empty() { return; } @@ -375,18 +394,30 @@ where self.finish_anonymous_table_if_needed(); } - self.inline_formatting_context_builder.push_text(text, info); + self.ensure_inline_formatting_context_builder() + .push_text(text, info); + } + + fn enter_display_contents(&mut self, styles: SharedInlineStyles) { + self.display_contents_shared_styles.push(styles.clone()); + if let Some(builder) = self.inline_formatting_context_builder.as_mut() { + builder.enter_display_contents(styles); + } + } + + fn leave_display_contents(&mut self) { + self.display_contents_shared_styles.pop(); + if let Some(builder) = self.inline_formatting_context_builder.as_mut() { + builder.leave_display_contents(); + } } } -impl<'dom, Node> BlockContainerBuilder<'dom, '_, Node> -where - Node: NodeExt<'dom>, -{ +impl<'dom> BlockContainerBuilder<'dom, '_> { fn handle_list_item_marker_inside( &mut self, - marker_info: &NodeAndStyleInfo<Node>, - container_info: &NodeAndStyleInfo<Node>, + marker_info: &NodeAndStyleInfo<'dom>, + container_info: &NodeAndStyleInfo<'dom>, contents: Vec<crate::dom_traversal::PseudoElementContentItem>, ) { // TODO: We do not currently support saving box slots for ::marker pseudo-elements @@ -411,8 +442,8 @@ where fn handle_list_item_marker_outside( &mut self, - marker_info: &NodeAndStyleInfo<Node>, - container_info: &NodeAndStyleInfo<Node>, + marker_info: &NodeAndStyleInfo<'dom>, + container_info: &NodeAndStyleInfo<'dom>, contents: Vec<crate::dom_traversal::PseudoElementContentItem>, list_item_style: Arc<ComputedValues>, ) { @@ -439,7 +470,7 @@ where fn handle_inline_level_element( &mut self, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, @@ -448,14 +479,16 @@ where (display_inside, contents.is_replaced()) else { // If this inline element is an atomic, handle it and return. - let atomic = self.inline_formatting_context_builder.push_atomic( + let context = self.context; + let propagaged_data = self.propagated_data.without_text_decorations(); + let atomic = self.ensure_inline_formatting_context_builder().push_atomic( IndependentFormattingContext::construct( - self.context, + context, info, display_inside, contents, // Text decorations are not propagated to atomic inline-level descendants. - self.propagated_data.without_text_decorations(), + propagaged_data, ), ); box_slot.set(LayoutBox::InlineLevel(vec![atomic])); @@ -464,7 +497,7 @@ where // 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. - self.inline_formatting_context_builder + self.ensure_inline_formatting_context_builder() .start_inline_box(InlineBox::new(info), None); if is_list_item { @@ -491,13 +524,16 @@ where // `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(), + self.inline_formatting_context_builder + .as_mut() + .expect("Should be building an InlineFormattingContext") + .end_inline_box(), )); } fn handle_block_level_element( &mut self, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, @@ -510,12 +546,15 @@ where // that we want to have after we push the block below. if let Some(inline_formatting_context) = self .inline_formatting_context_builder - .split_around_block_and_finish( - self.context, - self.propagated_data, - !self.have_already_seen_first_line_for_text_indent, - self.info.style.writing_mode.to_bidi_level(), - ) + .as_mut() + .and_then(|builder| { + builder.split_around_block_and_finish( + self.context, + self.propagated_data, + !self.have_already_seen_first_line_for_text_indent, + self.info.style.writing_mode.to_bidi_level(), + ) + }) { self.push_block_level_job_for_inline_formatting_context(inline_formatting_context); } @@ -565,22 +604,23 @@ where fn handle_absolutely_positioned_element( &mut self, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, ) { - if !self.inline_formatting_context_builder.is_empty() { - let inline_level_box = self - .inline_formatting_context_builder - .push_absolutely_positioned_box(AbsolutelyPositionedBox::construct( - self.context, - info, - display_inside, - contents, - )); - box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); - return; + if let Some(builder) = self.inline_formatting_context_builder.as_mut() { + if !builder.is_empty() { + let inline_level_box = + builder.push_absolutely_positioned_box(AbsolutelyPositionedBox::construct( + self.context, + info, + display_inside, + contents, + )); + box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); + return; + } } let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox { @@ -597,23 +637,23 @@ where fn handle_float_element( &mut self, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, ) { - if !self.inline_formatting_context_builder.is_empty() { - let inline_level_box = - self.inline_formatting_context_builder - .push_float_box(FloatBox::construct( - self.context, - info, - display_inside, - contents, - self.propagated_data, - )); - box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); - return; + if let Some(builder) = self.inline_formatting_context_builder.as_mut() { + if !builder.is_empty() { + let inline_level_box = builder.push_float_box(FloatBox::construct( + self.context, + info, + display_inside, + contents, + self.propagated_data, + )); + box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); + return; + } } let kind = BlockLevelCreator::OutOfFlowFloatBox { @@ -628,18 +668,6 @@ where }); } - fn end_ongoing_inline_formatting_context(&mut self) { - if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish( - self.context, - self.propagated_data, - !self.have_already_seen_first_line_for_text_indent, - self.info.is_single_line_text_input(), - self.info.style.writing_mode.to_bidi_level(), - ) { - self.push_block_level_job_for_inline_formatting_context(inline_formatting_context); - } - } - fn push_block_level_job_for_inline_formatting_context( &mut self, inline_formatting_context: InlineFormattingContext, @@ -670,10 +698,7 @@ where } } -impl<'dom, Node> BlockLevelJob<'dom, Node> -where - Node: NodeExt<'dom>, -{ +impl BlockLevelJob<'_> { fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> { let info = &self.info; let block_level_box = match self.kind { @@ -747,14 +772,7 @@ where } impl IntermediateBlockContainer { - fn finish<'dom, Node>( - self, - context: &LayoutContext, - info: &NodeAndStyleInfo<Node>, - ) -> BlockContainer - where - Node: NodeExt<'dom>, - { + fn finish(self, context: &LayoutContext, info: &NodeAndStyleInfo<'_>) -> BlockContainer { match self { IntermediateBlockContainer::Deferred { contents, diff --git a/components/layout/flow/float.rs b/components/layout/flow/float.rs index dbc50c07603..f1ae2b4459a 100644 --- a/components/layout/flow/float.rs +++ b/components/layout/flow/float.rs @@ -22,7 +22,6 @@ use style::properties::ComputedValues; use style::values::computed::Clear as StyleClear; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::{Contents, NodeAndStyleInfo}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::{BoxFragment, CollapsedMargin}; @@ -885,9 +884,9 @@ impl FloatBandLink { impl FloatBox { /// Creates a new float box. - pub fn construct<'dom>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo<impl NodeExt<'dom>>, + info: &NodeAndStyleInfo<'_>, display_inside: DisplayInside, contents: Contents, propagated_data: PropagatedBoxTreeData, diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index 61292701a9f..42d80059ab2 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -13,11 +13,13 @@ use style::values::specified::text::TextTransformCase; use unicode_bidi::Level; use super::text_run::TextRun; -use super::{InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem}; +use super::{ + InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem, + SharedInlineStyles, +}; use crate::PropagatedBoxTreeData; use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::NodeAndStyleInfo; use crate::flow::float::FloatBox; use crate::formatting_contexts::IndependentFormattingContext; @@ -26,6 +28,12 @@ use crate::style_ext::ComputedValuesExt; #[derive(Default)] pub(crate) struct InlineFormattingContextBuilder { + /// A stack of [`SharedInlineStyles`] including one for the root, one for each inline box on the + /// inline box stack, and importantly, one for every `display: contents` element that we are + /// currently processing. Normally `display: contents` elements don't affect the structure of + /// the [`InlineFormattingContext`], but the styles they provide do style their children. + shared_inline_styles_stack: Vec<SharedInlineStyles>, + /// The collection of text strings that make up this [`InlineFormattingContext`] under /// construction. pub text_segments: Vec<String>, @@ -64,7 +72,7 @@ pub(crate) struct InlineFormattingContextBuilder { /// The traversal is at all times as deep in the tree as this stack is, /// which is why the code doesn't need to keep track of the actual /// container root (see `handle_inline_level_element`). - /// + //_ /// When an inline box ends, it's removed from this stack. inline_box_stack: Vec<InlineBoxIdentifier>, @@ -84,10 +92,17 @@ pub(crate) struct InlineFormattingContextBuilder { } impl InlineFormattingContextBuilder { - pub(crate) fn new() -> Self { - // For the purposes of `text-transform: capitalize` the start of the IFC is a word boundary. + pub(crate) fn new(info: &NodeAndStyleInfo) -> Self { + Self::new_for_shared_styles(vec![info.into()]) + } + + pub(crate) fn new_for_shared_styles( + shared_inline_styles_stack: Vec<SharedInlineStyles>, + ) -> Self { Self { + // For the purposes of `text-transform: capitalize` the start of the IFC is a word boundary. on_word_boundary: true, + shared_inline_styles_stack, ..Default::default() } } @@ -101,6 +116,13 @@ impl InlineFormattingContextBuilder { self.current_text_offset += string_to_push.len(); } + fn shared_inline_styles(&self) -> SharedInlineStyles { + self.shared_inline_styles_stack + .last() + .expect("Should always have at least one SharedInlineStyles") + .clone() + } + /// Return true if this [`InlineFormattingContextBuilder`] is empty for the purposes of ignoring /// during box tree construction. An IFC is empty if it only contains TextRuns with /// completely collapsible whitespace. When that happens it can be ignored completely. @@ -180,6 +202,14 @@ impl InlineFormattingContextBuilder { ) { self.push_control_character_string(inline_box.base.style.bidi_control_chars().0); + // Don't push a `SharedInlineStyles` if we are pushing this box when splitting + // an IFC for a block-in-inline split. Shared styles are pushed as part of setting + // up the second split of the IFC. + if inline_box.is_first_split { + self.shared_inline_styles_stack + .push(inline_box.shared_inline_styles.clone()); + } + 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()); @@ -195,6 +225,8 @@ impl InlineFormattingContextBuilder { /// 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>> { + self.shared_inline_styles_stack.pop(); + let (identifier, block_in_inline_splits) = self.end_inline_box_internal(); let inline_level_box = self.inline_boxes.get(&identifier); { @@ -225,11 +257,7 @@ impl InlineFormattingContextBuilder { (identifier, block_in_inline_splits) } - pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>( - &mut self, - text: Cow<'dom, str>, - info: &NodeAndStyleInfo<Node>, - ) { + pub(crate) fn push_text<'dom>(&mut self, text: Cow<'dom, str>, info: &NodeAndStyleInfo<'dom>) { let white_space_collapse = info.style.clone_white_space_collapse(); let collapsed = WhitespaceCollapse::new( text.chars(), @@ -277,8 +305,6 @@ impl InlineFormattingContextBuilder { } let selection_range = info.get_selection_range(); - let selected_style = info.get_selected_style(); - if let Some(last_character) = new_text.chars().next_back() { self.on_word_boundary = last_character.is_whitespace(); self.last_inline_box_ended_with_collapsible_white_space = @@ -300,14 +326,21 @@ impl InlineFormattingContextBuilder { .push(ArcRefCell::new(InlineItem::TextRun(ArcRefCell::new( TextRun::new( info.into(), - info.style.clone(), + self.shared_inline_styles(), new_range, selection_range, - selected_style, ), )))); } + pub(crate) fn enter_display_contents(&mut self, shared_inline_styles: SharedInlineStyles) { + self.shared_inline_styles_stack.push(shared_inline_styles); + } + + pub(crate) fn leave_display_contents(&mut self) { + self.shared_inline_styles_stack.pop(); + } + pub(crate) fn split_around_block_and_finish( &mut self, layout_context: &LayoutContext, @@ -323,7 +356,8 @@ impl InlineFormattingContextBuilder { // context. It has the same inline box structure as this builder, except the boxes are // marked as not being the first fragment. No inline content is carried over to this new // builder. - let mut new_builder = InlineFormattingContextBuilder::new(); + let mut new_builder = Self::new_for_shared_styles(self.shared_inline_styles_stack.clone()); + 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) @@ -361,7 +395,7 @@ impl InlineFormattingContextBuilder { /// Finish the current inline formatting context, returning [`None`] if the context was empty. pub(crate) fn finish( - &mut self, + self, layout_context: &LayoutContext, propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, @@ -372,11 +406,9 @@ impl InlineFormattingContextBuilder { return None; } - let old_builder = std::mem::replace(self, InlineFormattingContextBuilder::new()); - assert!(old_builder.inline_box_stack.is_empty()); - + assert!(self.inline_box_stack.is_empty()); Some(InlineFormattingContext::new_with_builder( - old_builder, + self, layout_context, propagated_data, has_first_formatted_line, diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs index de79f876340..0ff58b6a6f3 100644 --- a/components/layout/flow/inline/inline_box.rs +++ b/components/layout/flow/inline/inline_box.rs @@ -8,11 +8,13 @@ use app_units::Au; use fonts::FontMetrics; use malloc_size_of_derive::MallocSizeOf; -use super::{InlineContainerState, InlineContainerStateFlags, inline_container_needs_strut}; +use super::{ + InlineContainerState, InlineContainerStateFlags, SharedInlineStyles, + inline_container_needs_strut, +}; use crate::ContainingBlock; use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::NodeAndStyleInfo; use crate::fragment_tree::BaseFragmentInfo; use crate::layout_box_base::LayoutBoxBase; @@ -21,6 +23,9 @@ use crate::style_ext::{LayoutStyle, PaddingBorderMargin}; #[derive(Debug, MallocSizeOf)] pub(crate) struct InlineBox { pub base: LayoutBoxBase, + /// The [`SharedInlineStyles`] for this [`InlineBox`] that are used to share styles + /// with all [`super::TextRun`] children. + pub(super) shared_inline_styles: SharedInlineStyles, /// The identifier of this inline box in the containing [`super::InlineFormattingContext`]. pub(super) identifier: InlineBoxIdentifier, /// Whether or not this is the first instance of an [`InlineBox`] before a possible @@ -35,9 +40,10 @@ pub(crate) struct InlineBox { } impl InlineBox { - pub(crate) fn new<'dom, Node: NodeExt<'dom>>(info: &NodeAndStyleInfo<Node>) -> Self { + pub(crate) fn new(info: &NodeAndStyleInfo) -> Self { Self { base: LayoutBoxBase::new(info.into(), info.style.clone()), + shared_inline_styles: info.into(), // This will be assigned later, when the box is actually added to the IFC. identifier: InlineBoxIdentifier::default(), is_first_split: true, @@ -49,6 +55,7 @@ impl InlineBox { pub(crate) fn split_around_block(&self) -> Self { Self { base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()), + shared_inline_styles: self.shared_inline_styles.clone(), is_first_split: false, is_last_split: false, ..*self diff --git a/components/layout/flow/inline/line.rs b/components/layout/flow/inline/line.rs index 80bab1080ed..3b92078d67d 100644 --- a/components/layout/flow/inline/line.rs +++ b/components/layout/flow/inline/line.rs @@ -7,7 +7,6 @@ use bitflags::bitflags; use fonts::{ByteIndex, FontMetrics, GlyphStore}; use itertools::Either; use range::Range; -use servo_arc::Arc; use style::Zero; use style::computed_values::position::T as Position; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; @@ -21,7 +20,7 @@ use unicode_bidi::{BidiInfo, Level}; use webrender_api::FontInstanceKey; use super::inline_box::{InlineBoxContainerState, InlineBoxIdentifier, InlineBoxTreePathToken}; -use super::{InlineFormattingContextLayout, LineBlockSizes}; +use super::{InlineFormattingContextLayout, LineBlockSizes, SharedInlineStyles}; use crate::cell::ArcRefCell; use crate::fragment_tree::{BaseFragmentInfo, BoxFragment, Fragment, TextFragment}; use crate::geom::{LogicalRect, LogicalVec2, PhysicalRect, ToLogical}; @@ -568,7 +567,7 @@ impl LineItemLayout<'_, '_> { self.current_state.fragments.push(( Fragment::Text(ArcRefCell::new(TextFragment { base: text_item.base_fragment_info.into(), - parent_style: text_item.parent_style, + inline_styles: text_item.inline_styles.clone(), rect: PhysicalRect::zero(), font_metrics: text_item.font_metrics, font_key: text_item.font_key, @@ -576,7 +575,6 @@ impl LineItemLayout<'_, '_> { text_decoration_line: text_item.text_decoration_line, justification_adjustment: self.justification_adjustment, selection_range: text_item.selection_range, - selected_style: text_item.selected_style, })), content_rect, )); @@ -763,7 +761,7 @@ impl LineItem { pub(super) struct TextRunLineItem { pub base_fragment_info: BaseFragmentInfo, - pub parent_style: Arc<ComputedValues>, + pub inline_styles: SharedInlineStyles, pub text: Vec<std::sync::Arc<GlyphStore>>, pub font_metrics: FontMetrics, pub font_key: FontInstanceKey, @@ -771,13 +769,16 @@ pub(super) struct TextRunLineItem { /// The BiDi level of this [`TextRunLineItem`] to enable reordering. pub bidi_level: Level, pub selection_range: Option<Range<ByteIndex>>, - pub selected_style: Arc<ComputedValues>, } impl TextRunLineItem { fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool { if matches!( - self.parent_style.get_inherited_text().white_space_collapse, + self.inline_styles + .style + .borrow() + .get_inherited_text() + .white_space_collapse, WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces ) { return false; @@ -803,7 +804,11 @@ impl TextRunLineItem { fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool { if matches!( - self.parent_style.get_inherited_text().white_space_collapse, + self.inline_styles + .style + .borrow() + .get_inherited_text() + .white_space_collapse, WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces ) { return false; diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 2023f4e7174..d623eb4a3d8 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -118,6 +118,7 @@ use super::{ }; use crate::cell::ArcRefCell; use crate::context::LayoutContext; +use crate::dom_traversal::NodeAndStyleInfo; use crate::flow::CollapsibleWithParentStartMargin; use crate::flow::float::{FloatBox, SequentialLayoutState}; use crate::formatting_contexts::{ @@ -131,7 +132,7 @@ use crate::geom::{LogicalRect, LogicalVec2, ToLogical}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin}; -use crate::{ConstraintSpace, ContainingBlock, PropagatedBoxTreeData}; +use crate::{ConstraintSpace, ContainingBlock, PropagatedBoxTreeData, SharedStyle}; // From gfxFontConstants.h in Firefox. static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; @@ -173,6 +174,25 @@ pub(crate) struct InlineFormattingContext { pub(super) has_right_to_left_content: bool, } +/// [`TextRun`] and `TextFragment`s need a handle on their parent inline box (or inline +/// formatting context root)'s style. In order to implement incremental layout, these are +/// wrapped in [`SharedStyle`]. This allows updating the parent box tree element without +/// updating every single descendant box tree node and fragment. +#[derive(Clone, Debug, MallocSizeOf)] +pub(crate) struct SharedInlineStyles { + pub style: SharedStyle, + pub selected: SharedStyle, +} + +impl From<&NodeAndStyleInfo<'_>> for SharedInlineStyles { + fn from(info: &NodeAndStyleInfo) -> Self { + Self { + style: SharedStyle::new(info.style.clone()), + selected: SharedStyle::new(info.get_selected_style()), + } + } +} + /// A collection of data used to cache [`FontMetrics`] in the [`InlineFormattingContext`] #[derive(Debug, MallocSizeOf)] pub(crate) struct FontKeyAndMetrics { @@ -1313,7 +1333,7 @@ impl InlineFormattingContextLayout<'_> { ) { let inline_advance = glyph_store.total_advance(); let flags = if glyph_store.is_whitespace() { - SegmentContentFlags::from(text_run.parent_style.get_inherited_text()) + SegmentContentFlags::from(text_run.inline_styles.style.borrow().get_inherited_text()) } else { SegmentContentFlags::empty() }; @@ -1398,13 +1418,12 @@ impl InlineFormattingContextLayout<'_> { TextRunLineItem { text: vec![glyph_store], base_fragment_info: text_run.base_fragment_info, - parent_style: text_run.parent_style.clone(), + inline_styles: text_run.inline_styles.clone(), font_metrics, font_key: ifc_font_info.key, text_decoration_line: self.current_inline_container_state().text_decoration_line, bidi_level, selection_range, - selected_style: text_run.selected_style.clone(), }, )); } @@ -2363,8 +2382,9 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { }, InlineItem::TextRun(text_run) => { let text_run = &*text_run.borrow(); + let parent_style = text_run.inline_styles.style.borrow(); for segment in text_run.shaped_text.iter() { - let style_text = text_run.parent_style.get_inherited_text(); + let style_text = parent_style.get_inherited_text(); let can_wrap = style_text.text_wrap_mode == TextWrapMode::Wrap; // TODO: This should take account whether or not the first and last character prevent diff --git a/components/layout/flow/inline/text_run.rs b/components/layout/flow/inline/text_run.rs index 0d0c6398017..591c7b9b5e2 100644 --- a/components/layout/flow/inline/text_run.rs +++ b/components/layout/flow/inline/text_run.rs @@ -26,7 +26,7 @@ use unicode_script::Script; use xi_unicode::linebreak_property; use super::line_breaker::LineBreaker; -use super::{FontKeyAndMetrics, InlineFormattingContextLayout}; +use super::{FontKeyAndMetrics, InlineFormattingContextLayout, SharedInlineStyles}; use crate::fragment_tree::BaseFragmentInfo; // These constants are the xi-unicode line breaking classes that are defined in @@ -37,22 +37,6 @@ pub(crate) const XI_LINE_BREAKING_CLASS_ZW: u8 = 28; pub(crate) const XI_LINE_BREAKING_CLASS_WJ: u8 = 30; pub(crate) const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 42; -/// <https://www.w3.org/TR/css-display-3/#css-text-run> -#[derive(Debug, MallocSizeOf)] -pub(crate) struct TextRun { - pub base_fragment_info: BaseFragmentInfo, - #[conditional_malloc_size_of] - pub parent_style: Arc<ComputedValues>, - pub text_range: Range<usize>, - - /// The text of this [`TextRun`] with a font selected, broken into unbreakable - /// segments, and shaped. - pub shaped_text: Vec<TextRunSegment>, - pub selection_range: Option<ServoRange<ByteIndex>>, - #[conditional_malloc_size_of] - pub selected_style: Arc<ComputedValues>, -} - // There are two reasons why we might want to break at the start: // // 1. The line breaker told us that a break was necessary between two separate @@ -334,21 +318,49 @@ impl TextRunSegment { } } +/// A single [`TextRun`] for the box tree. These are all descendants of +/// [`super::InlineBox`] or the root of the [`super::InlineFormattingContext`]. During +/// box tree construction, text is split into [`TextRun`]s based on their font, script, +/// etc. When these are created text is already shaped. +/// +/// <https://www.w3.org/TR/css-display-3/#css-text-run> +#[derive(Debug, MallocSizeOf)] +pub(crate) struct TextRun { + /// The [`BaseFragmentInfo`] for this [`TextRun`]. Usually this comes from the + /// original text node in the DOM for the text. + pub base_fragment_info: BaseFragmentInfo, + + /// The [`crate::SharedStyle`] from this [`TextRun`]s parent element. This is + /// shared so that incremental layout can simply update the parent element and + /// this [`TextRun`] will be updated automatically. + pub inline_styles: SharedInlineStyles, + + /// The range of text in [`super::InlineFormattingContext::text_content`] of the + /// [`super::InlineFormattingContext`] that owns this [`TextRun`]. These are UTF-8 offsets. + pub text_range: Range<usize>, + + /// The text of this [`TextRun`] with a font selected, broken into unbreakable + /// segments, and shaped. + pub shaped_text: Vec<TextRunSegment>, + + /// The selection range for the DOM text node that originated this [`TextRun`]. This + /// comes directly from the DOM. + pub selection_range: Option<ServoRange<ByteIndex>>, +} + impl TextRun { pub(crate) fn new( base_fragment_info: BaseFragmentInfo, - parent_style: Arc<ComputedValues>, + inline_styles: SharedInlineStyles, text_range: Range<usize>, selection_range: Option<ServoRange<ByteIndex>>, - selected_style: Arc<ComputedValues>, ) -> Self { Self { base_fragment_info, - parent_style, + inline_styles, text_range, shaped_text: Vec::new(), selection_range, - selected_style, } } @@ -360,11 +372,12 @@ impl TextRun { font_cache: &mut Vec<FontKeyAndMetrics>, bidi_info: &BidiInfo, ) { - let inherited_text_style = self.parent_style.get_inherited_text().clone(); + let parent_style = self.inline_styles.style.borrow().clone(); + let inherited_text_style = parent_style.get_inherited_text().clone(); let letter_spacing = inherited_text_style .letter_spacing .0 - .resolve(self.parent_style.clone_font().font_size.computed_size()); + .resolve(parent_style.clone_font().font_size.computed_size()); let letter_spacing = if letter_spacing.px() != 0. { Some(app_units::Au::from(letter_spacing)) } else { @@ -384,7 +397,13 @@ impl TextRun { let style_word_spacing: Option<Au> = specified_word_spacing.to_length().map(|l| l.into()); let segments = self - .segment_text_by_font(formatting_context_text, font_context, font_cache, bidi_info) + .segment_text_by_font( + formatting_context_text, + font_context, + font_cache, + bidi_info, + &parent_style, + ) .into_iter() .map(|(mut segment, font)| { let word_spacing = style_word_spacing.unwrap_or_else(|| { @@ -407,7 +426,7 @@ impl TextRun { }; segment.shape_text( - &self.parent_style, + &parent_style, formatting_context_text, linebreaker, &shaping_options, @@ -430,8 +449,9 @@ impl TextRun { font_context: &FontContext, font_cache: &mut Vec<FontKeyAndMetrics>, bidi_info: &BidiInfo, + parent_style: &Arc<ComputedValues>, ) -> Vec<(TextRunSegment, FontRef)> { - let font_group = font_context.font_group(self.parent_style.clone_font()); + let font_group = font_context.font_group(parent_style.clone_font()); let mut current: Option<(TextRunSegment, FontRef)> = None; let mut results = Vec::new(); diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index 772b150ae1c..17a952d24cb 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -52,7 +52,7 @@ pub mod inline; mod root; pub(crate) use construct::BlockContainerBuilder; -pub use root::{BoxTree, CanvasBackground}; +pub use root::BoxTree; #[derive(Debug, MallocSizeOf)] pub(crate) struct BlockFormattingContext { @@ -249,7 +249,6 @@ pub(crate) struct CollapsibleWithParentStartMargin(bool); /// for a list that has `list-style-position: outside`. #[derive(Debug, MallocSizeOf)] pub(crate) struct OutsideMarker { - #[conditional_malloc_size_of] pub list_item_style: Arc<ComputedValues>, pub base: LayoutBoxBase, pub block_container: BlockContainer, @@ -2238,7 +2237,9 @@ fn block_size_is_zero_or_intrinsic(size: &StyleSize, containing_block: &Containi lp.is_definitely_zero() || (lp.0.has_percentage() && !containing_block.size.block.is_definite()) }, - StyleSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), + StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => { + unreachable!("anchor-size() should be disabled") + }, } } diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index bb9ff1d337a..b367b20c881 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -6,12 +6,13 @@ use app_units::Au; use atomic_refcell::AtomicRef; use compositing_traits::display_list::AxesScrollSensitivity; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::ServoLayoutNode; use script_layout_interface::wrapper_traits::{ LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; use script_layout_interface::{LayoutElementType, LayoutNodeType}; use servo_arc::Arc; -use style::dom::OpaqueNode; +use style::dom::{NodeInfo, TNode}; use style::properties::ComputedValues; use style::values::computed::Overflow; use style_traits::CSSPixel; @@ -29,7 +30,7 @@ use crate::fragment_tree::FragmentTree; use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::replaced::ReplacedContents; -use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayInside}; +use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside}; use crate::taffy::{TaffyItemBox, TaffyItemBoxInner}; use crate::{DefiniteContainingBlock, PropagatedBoxTreeData}; @@ -39,18 +40,12 @@ pub struct BoxTree { /// There may be zero if that element has `display: none`. root: BlockFormattingContext, - /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds> - canvas_background: CanvasBackground, - /// Whether or not the viewport should be sensitive to scrolling input events in two axes viewport_scroll_sensitivity: AxesScrollSensitivity, } impl BoxTree { - pub fn construct<'dom, Node>(context: &LayoutContext, root_element: Node) -> Self - where - Node: 'dom + Copy + LayoutNode<'dom> + Send + Sync, - { + pub fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self { let boxes = construct_for_root_element(context, root_element); // Zero box for `:root { display: none }`, one for the root element otherwise. @@ -98,7 +93,6 @@ impl BoxTree { contents, contains_floats, }, - canvas_background: CanvasBackground::for_root_element(context, root_element), // From https://www.w3.org/TR/css-overflow-3/#overflow-propagation: // > If visible is applied to the viewport, it must be interpreted as auto. // > If clip is applied to the viewport, it must be interpreted as hidden. @@ -129,10 +123,7 @@ impl BoxTree { /// * how intrinsic content sizes are computed eagerly makes it hard /// to update those sizes for ancestors of the node from which we /// made an incremental update. - pub fn update<'dom, Node>(context: &LayoutContext, mut dirty_node: Node) -> bool - where - Node: 'dom + Copy + LayoutNode<'dom> + Send + Sync, - { + pub fn update(context: &LayoutContext, mut dirty_node: ServoLayoutNode<'_>) -> bool { #[allow(clippy::enum_variant_names)] enum UpdatePoint { AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>), @@ -141,12 +132,9 @@ impl BoxTree { AbsolutelyPositionedTaffyLevelBox(ArcRefCell<TaffyItemBox>), } - fn update_point<'dom, Node>( - node: Node, - ) -> Option<(Arc<ComputedValues>, DisplayInside, UpdatePoint)> - where - Node: NodeExt<'dom>, - { + fn update_point( + node: ServoLayoutNode<'_>, + ) -> Option<(Arc<ComputedValues>, DisplayInside, UpdatePoint)> { if !node.is_element() { return None; } @@ -162,7 +150,7 @@ impl BoxTree { return None; } - let layout_data = node.layout_data()?; + let layout_data = NodeExt::layout_data(&node)?; if layout_data.pseudo_before_box.borrow().is_some() { return None; } @@ -186,7 +174,7 @@ impl BoxTree { let update_point = match &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)? { - LayoutBox::DisplayContents => return None, + LayoutBox::DisplayContents(..) => return None, LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() { BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_) if box_style.position.is_absolutely_positioned() => @@ -301,9 +289,9 @@ impl BoxTree { } } -fn construct_for_root_element<'dom>( +fn construct_for_root_element( context: &LayoutContext, - root_element: impl NodeExt<'dom>, + root_element: ServoLayoutNode<'_>, ) -> Vec<ArcRefCell<BlockLevelBox>> { let info = NodeAndStyleInfo::new(root_element, root_element.style(context)); let box_style = info.style.get_box(); @@ -433,69 +421,7 @@ impl BoxTree { root_fragments, scrollable_overflow, physical_containing_block, - self.canvas_background.clone(), self.viewport_scroll_sensitivity, ) } } - -/// <https://drafts.csswg.org/css-backgrounds/#root-background> -#[derive(Clone, MallocSizeOf)] -pub struct CanvasBackground { - /// DOM node for the root element - pub root_element: OpaqueNode, - - /// The element whose style the canvas takes background properties from (see next field). - /// This can be the root element (same as the previous field), or the HTML `<body>` element. - /// See <https://drafts.csswg.org/css-backgrounds/#body-background> - pub from_element: OpaqueNode, - - /// The computed styles to take background properties from. - #[conditional_malloc_size_of] - pub style: Option<Arc<ComputedValues>>, -} - -impl CanvasBackground { - fn for_root_element<'dom>(context: &LayoutContext, root_element: impl NodeExt<'dom>) -> Self { - let root_style = root_element.style(context); - - let mut style = root_style; - let mut from_element = root_element; - - // https://drafts.csswg.org/css-backgrounds/#body-background - // “if the computed value of background-image on the root element is none - // and its background-color is transparent” - if style.background_is_transparent() && - // “For documents whose root element is an HTML `HTML` element - // or an XHTML `html` element” - root_element.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLHtmlElement) && - // Don’t try to access styles for an unstyled subtree - !matches!(style.clone_display().into(), Display::None) - { - // “that element’s first HTML `BODY` or XHTML `body` child element” - if let Some(body) = iter_child_nodes(root_element).find(|child| { - child.is_element() && - child.type_id() == - LayoutNodeType::Element(LayoutElementType::HTMLBodyElement) - }) { - style = body.style(context); - from_element = body; - } - } - - Self { - root_element: root_element.opaque(), - from_element: from_element.opaque(), - - // “However, if no boxes are generated for the element - // whose background would be used for the canvas - // (for example, if the root element has display: none), - // then the canvas background is transparent.” - style: if let Display::GeneratingBox(_) = style.clone_display().into() { - Some(style) - } else { - None - }, - } - } -} |