diff options
Diffstat (limited to 'components/layout/flow')
-rw-r--r-- | components/layout/flow/construct.rs | 173 | ||||
-rw-r--r-- | components/layout/flow/inline/construct.rs | 71 | ||||
-rw-r--r-- | components/layout/flow/inline/inline_box.rs | 26 | ||||
-rw-r--r-- | components/layout/flow/inline/line.rs | 21 | ||||
-rw-r--r-- | components/layout/flow/inline/mod.rs | 85 | ||||
-rw-r--r-- | components/layout/flow/inline/text_run.rs | 72 | ||||
-rw-r--r-- | components/layout/flow/mod.rs | 58 | ||||
-rw-r--r-- | components/layout/flow/root.rs | 8 |
8 files changed, 375 insertions, 139 deletions
diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs index a50464123bc..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; @@ -137,16 +137,25 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style> { /// 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`). + /// 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>>, + + /// 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 { @@ -194,26 +203,44 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { 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. @@ -251,9 +278,7 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { // // 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: @@ -276,10 +301,16 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { 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(), @@ -363,7 +394,22 @@ impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> { 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(); + } } } @@ -433,14 +479,16 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { (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])); @@ -449,7 +497,7 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { // 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 { @@ -476,7 +524,10 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { // `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(), )); } @@ -495,12 +546,15 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { // 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); } @@ -555,17 +609,18 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { 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 { @@ -587,18 +642,18 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { 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 { @@ -613,18 +668,6 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { }); } - 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, diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index 74b0cf4ea7d..a99de1679a4 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -7,13 +7,15 @@ 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; 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; @@ -25,6 +27,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>, @@ -63,7 +71,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>, @@ -83,10 +91,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() } } @@ -100,6 +115,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. @@ -135,7 +157,7 @@ impl InlineFormattingContextBuilder { independent_formatting_context: IndependentFormattingContext, ) -> ArcRefCell<InlineItem> { let inline_level_box = ArcRefCell::new(InlineItem::Atomic( - Arc::new(independent_formatting_context), + ArcRefCell::new(independent_formatting_context), self.current_text_offset, Level::ltr(), /* This will be assigned later if necessary. */ )); @@ -166,7 +188,8 @@ impl InlineFormattingContextBuilder { } pub(crate) fn push_float_box(&mut self, float_box: FloatBox) -> ArcRefCell<InlineItem> { - let inline_level_box = ArcRefCell::new(InlineItem::OutOfFlowFloatBox(Arc::new(float_box))); + let inline_level_box = + ArcRefCell::new(InlineItem::OutOfFlowFloatBox(ArcRefCell::new(float_box))); self.inline_items.push(inline_level_box.clone()); self.contains_floats = true; inline_level_box @@ -179,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()); @@ -194,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); { @@ -272,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 = @@ -295,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, @@ -318,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) @@ -356,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, @@ -367,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 1c953c13074..b547f3b5935 100644 --- a/components/layout/flow/inline/inline_box.rs +++ b/components/layout/flow/inline/inline_box.rs @@ -7,8 +7,15 @@ use std::vec::IntoIter; use app_units::Au; use fonts::FontMetrics; use malloc_size_of_derive::MallocSizeOf; - -use super::{InlineContainerState, InlineContainerStateFlags, inline_container_needs_strut}; +use script::layout_dom::ServoLayoutNode; +use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; +use servo_arc::Arc as ServoArc; +use style::properties::ComputedValues; + +use super::{ + InlineContainerState, InlineContainerStateFlags, SharedInlineStyles, + inline_container_needs_strut, +}; use crate::ContainingBlock; use crate::cell::ArcRefCell; use crate::context::LayoutContext; @@ -20,6 +27,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 @@ -37,6 +47,7 @@ impl InlineBox { 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, @@ -48,6 +59,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 @@ -58,6 +70,16 @@ impl InlineBox { pub(crate) fn layout_style(&self) -> LayoutStyle { LayoutStyle::Default(&self.base.style) } + + pub(crate) fn repair_style( + &mut self, + node: &ServoLayoutNode, + new_style: &ServoArc<ComputedValues>, + ) { + self.base.repair_style(new_style); + *self.shared_inline_styles.style.borrow_mut() = new_style.clone(); + *self.shared_inline_styles.selected.borrow_mut() = node.to_threadsafe().selected_style(); + } } #[derive(Debug, Default, MallocSizeOf)] 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..7e69aa1aaae 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -90,12 +90,13 @@ use line::{ use line_breaker::LineBreaker; use malloc_size_of_derive::MallocSizeOf; use range::Range; +use script::layout_dom::ServoLayoutNode; use servo_arc::Arc; use style::Zero; use style::computed_values::text_wrap_mode::T as TextWrapMode; use style::computed_values::vertical_align::T as VerticalAlign; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; -use style::context::QuirksMode; +use style::context::{QuirksMode, SharedStyleContext}; use style::properties::ComputedValues; use style::properties::style_structs::InheritedText; use style::values::generics::box_::VerticalAlignKeyword; @@ -118,6 +119,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 +133,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 +175,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 { @@ -190,15 +211,41 @@ pub(crate) enum InlineItem { ArcRefCell<AbsolutelyPositionedBox>, usize, /* offset_in_text */ ), - OutOfFlowFloatBox(#[conditional_malloc_size_of] Arc<FloatBox>), + OutOfFlowFloatBox(ArcRefCell<FloatBox>), Atomic( - #[conditional_malloc_size_of] Arc<IndependentFormattingContext>, + ArcRefCell<IndependentFormattingContext>, usize, /* offset_in_text */ Level, /* bidi_level */ ), } impl InlineItem { + pub(crate) fn repair_style( + &self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc<ComputedValues>, + ) { + match self { + InlineItem::StartInlineBox(inline_box) => { + inline_box.borrow_mut().repair_style(node, new_style); + }, + InlineItem::EndInlineBox => {}, + // TextRun holds a handle the `InlineSharedStyles` which is updated when repairing inline box + // and `display: contents` styles. + InlineItem::TextRun(..) => {}, + InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => positioned_box + .borrow_mut() + .context + .repair_style(context, new_style), + InlineItem::OutOfFlowFloatBox(float_box) => float_box + .borrow_mut() + .contents + .repair_style(context, new_style), + InlineItem::Atomic(atomic, ..) => atomic.borrow_mut().repair_style(context, new_style), + } + } + pub(crate) fn invalidate_cached_fragment(&self) { match self { InlineItem::StartInlineBox(inline_box) => { @@ -212,11 +259,14 @@ impl InlineItem { .base .invalidate_cached_fragment(); }, - InlineItem::OutOfFlowFloatBox(float_box) => { - float_box.contents.base.invalidate_cached_fragment() - }, + InlineItem::OutOfFlowFloatBox(float_box) => float_box + .borrow() + .contents + .base + .invalidate_cached_fragment(), InlineItem::Atomic(independent_formatting_context, ..) => { independent_formatting_context + .borrow() .base .invalidate_cached_fragment(); }, @@ -232,9 +282,11 @@ impl InlineItem { InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => { positioned_box.borrow().context.base.fragments() }, - InlineItem::OutOfFlowFloatBox(float_box) => float_box.contents.base.fragments(), + InlineItem::OutOfFlowFloatBox(float_box) => { + float_box.borrow().contents.base.fragments() + }, InlineItem::Atomic(independent_formatting_context, ..) => { - independent_formatting_context.base.fragments() + independent_formatting_context.borrow().base.fragments() }, } } @@ -958,6 +1010,7 @@ impl InlineFormattingContextLayout<'_> { .as_physical(Some(self.containing_block)); self.fragments .push(Fragment::Positioning(PositioningFragment::new_anonymous( + self.root_nesting_level.style.clone(), physical_line_rect, fragments, ))); @@ -1313,7 +1366,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 +1451,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(), }, )); } @@ -1751,7 +1803,7 @@ impl InlineFormattingContext { InlineItem::EndInlineBox => layout.finish_inline_box(), InlineItem::TextRun(run) => run.borrow().layout_into_line_items(&mut layout), InlineItem::Atomic(atomic_formatting_context, offset_in_text, bidi_level) => { - atomic_formatting_context.layout_into_line_items( + atomic_formatting_context.borrow().layout_into_line_items( &mut layout, *offset_in_text, *bidi_level, @@ -1766,7 +1818,7 @@ impl InlineFormattingContext { )); }, InlineItem::OutOfFlowFloatBox(float_box) => { - float_box.layout_into_line_items(&mut layout); + float_box.borrow().layout_into_line_items(&mut layout); }, } } @@ -2363,8 +2415,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 @@ -2428,7 +2481,7 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { let InlineContentSizesResult { sizes: outer, depends_on_block_constraints, - } = atomic.outer_inline_content_sizes( + } = atomic.borrow().outer_inline_content_sizes( self.layout_context, &self.constraint_space.into(), &LogicalVec2::zero(), 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 e23193f3904..6adb63153d6 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -9,9 +9,11 @@ use app_units::{Au, MAX_AU}; use inline::InlineFormattingContext; use malloc_size_of_derive::MallocSizeOf; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use script::layout_dom::ServoLayoutNode; use servo_arc::Arc; use style::Zero; use style::computed_values::clear::T as StyleClear; +use style::context::SharedStyleContext; use style::logical_geometry::Direction; use style::properties::ComputedValues; use style::servo::selector_parser::PseudoElement; @@ -21,6 +23,7 @@ use style::values::specified::{Display, TextAlignKeyword}; use crate::cell::ArcRefCell; use crate::context::LayoutContext; +use crate::dom::NodeExt; use crate::flow::float::{ Clear, ContainingBlockPositionInfo, FloatBox, FloatSide, PlacementAmongFloats, SequentialLayoutState, @@ -91,6 +94,36 @@ pub(crate) enum BlockLevelBox { } impl BlockLevelBox { + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc<ComputedValues>, + ) { + self.with_base_mut(|base| { + base.repair_style(new_style); + }); + + match self { + BlockLevelBox::Independent(independent_formatting_context) => { + independent_formatting_context.repair_style(context, new_style) + }, + BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box + .borrow_mut() + .context + .repair_style(context, new_style), + BlockLevelBox::OutOfFlowFloatBox(float_box) => { + float_box.contents.repair_style(context, new_style) + }, + BlockLevelBox::OutsideMarker(outside_marker) => { + outside_marker.repair_style(context, node, new_style) + }, + BlockLevelBox::SameFormattingContextBlock { base, .. } => { + base.repair_style(new_style); + }, + } + } + pub(crate) fn invalidate_cached_fragment(&self) { self.with_base(LayoutBoxBase::invalidate_cached_fragment); } @@ -113,6 +146,20 @@ impl BlockLevelBox { } } + pub(crate) fn with_base_mut<T>(&mut self, callback: impl Fn(&mut LayoutBoxBase) -> T) -> T { + match self { + BlockLevelBox::Independent(independent_formatting_context) => { + callback(&mut independent_formatting_context.base) + }, + BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => { + callback(&mut positioned_box.borrow_mut().context.base) + }, + BlockLevelBox::OutOfFlowFloatBox(float_box) => callback(&mut float_box.contents.base), + BlockLevelBox::OutsideMarker(outside_marker) => callback(&mut outside_marker.base), + BlockLevelBox::SameFormattingContextBlock { base, .. } => callback(base), + } + } + fn contains_floats(&self) -> bool { match self { BlockLevelBox::SameFormattingContextBlock { @@ -249,7 +296,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, @@ -361,6 +407,16 @@ impl OutsideMarker { None, ))) } + + fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc<ComputedValues>, + ) { + self.list_item_style = node.style(context); + self.base.repair_style(new_style); + } } impl BlockFormattingContext { diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index ec85f3574dc..a37db54065d 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -59,7 +59,7 @@ impl BoxTree { // > none, user agents must instead apply the overflow-* values of the first such child // > element to the viewport. The element from which the value is propagated must then have a // > used overflow value of visible. - let root_style = root_element.style(context); + let root_style = root_element.style(context.shared_context()); let mut viewport_overflow_x = root_style.clone_overflow_x(); let mut viewport_overflow_y = root_style.clone_overflow_y(); @@ -76,7 +76,7 @@ impl BoxTree { continue; } - let style = child.style(context); + let style = child.style(context.shared_context()); if !style.get_box().display.is_none() { viewport_overflow_x = style.clone_overflow_x(); viewport_overflow_y = style.clone_overflow_y(); @@ -174,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() => @@ -293,7 +293,7 @@ fn construct_for_root_element( context: &LayoutContext, root_element: ServoLayoutNode<'_>, ) -> Vec<ArcRefCell<BlockLevelBox>> { - let info = NodeAndStyleInfo::new(root_element, root_element.style(context)); + let info = NodeAndStyleInfo::new(root_element, root_element.style(context.shared_context())); let box_style = info.style.get_box(); let display_inside = match Display::from(box_style.display) { |