diff options
author | Martin Robinson <mrobinson@igalia.com> | 2025-05-12 11:38:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-12 09:38:50 +0000 |
commit | a0dd2c1bebbc238d2fe36b1134acfbc842c1f084 (patch) | |
tree | 367a47983a81113a4c4988ff8a11cca6de6f007d | |
parent | db83601b62b81965176e1e8272d5b434afeb0da6 (diff) | |
download | servo-a0dd2c1bebbc238d2fe36b1134acfbc842c1f084.tar.gz servo-a0dd2c1bebbc238d2fe36b1134acfbc842c1f084.zip |
layout: Share styles to inline box children via `SharedInlineStyles` (#36896)
`TextRun`s use their parent style to render. Previously, these styles
were cloned and stored directly in the box tree `TextRun` and resulting
`TextFragment`s. This presents a problem for incremental layout.
Wrapping the style in another layer of shared ownership and mutability
will allow updating all `TextFragment`s during repaint-only incremental
layout by simply updating the box tree styles of the original text
parents.
This adds a new set of borrows when accessing text styles, but also
makes it so that during box tree block construction
`InlineFormattingContext`s are created lazily and now
`InlineFormattingContextBuilder::finish` consumes the builder, making
the API make a bit more sense. This should also improve performance of
box tree block construction slightly.
Testing: This should not change observable behavior and thus is covered
by existing WPT tests.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
24 files changed, 369 insertions, 187 deletions
diff --git a/components/layout/construct_modern.rs b/components/layout/construct_modern.rs index 1489d82635b..8f1282ec9f6 100644 --- a/components/layout/construct_modern.rs +++ b/components/layout/construct_modern.rs @@ -142,7 +142,7 @@ impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> { .filter_map(|job| match job { ModernContainerJob::TextRuns(runs) => { let mut inline_formatting_context_builder = - InlineFormattingContextBuilder::new(); + InlineFormattingContextBuilder::new(self.info); for flex_text_run in runs.into_iter() { inline_formatting_context_builder .push_text(flex_text_run.text, &flex_text_run.info); diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index d6cbb50e4a1..9990528911c 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -14,6 +14,7 @@ use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit}; use fonts::GlyphStore; use gradient::WebRenderGradient; use range::Range as ServoRange; +use servo_arc::Arc as ServoArc; use servo_geometry::MaxRect; use style::Zero; use style::color::{AbsoluteColor, ColorSpace}; @@ -544,7 +545,13 @@ impl Fragment { }, Fragment::Text(text) => { let text = &*text.borrow(); - match text.parent_style.get_inherited_box().visibility { + match text + .inline_styles + .style + .borrow() + .get_inherited_box() + .visibility + { Visibility::Visible => { self.build_display_list_for_text_fragment(text, builder, containing_block) }, @@ -604,22 +611,23 @@ impl Fragment { return; } + let parent_style = fragment.inline_styles.style.borrow(); self.maybe_push_hit_test_for_style_and_tag( builder, - &fragment.parent_style, + &parent_style, fragment.base.tag, rect, Cursor::Text, ); - let color = fragment.parent_style.clone_color(); + let color = parent_style.clone_color(); let font_metrics = &fragment.font_metrics; let dppx = builder.context.style_context.device_pixel_ratio().get(); - let common = builder.common_properties(rect.to_webrender(), &fragment.parent_style); + let common = builder.common_properties(rect.to_webrender(), &parent_style); // Shadows. According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front to // back). - let shadows = &fragment.parent_style.get_inherited_text().text_shadow; + let shadows = &parent_style.get_inherited_text().text_shadow; for shadow in shadows.0.iter().rev() { builder.wr().push_shadow( &wr::SpaceAndClipInfo { @@ -642,7 +650,7 @@ impl Fragment { let mut rect = rect; rect.origin.y += font_metrics.ascent - font_metrics.underline_offset; rect.size.height = Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(fragment, builder, &rect, &color); + self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); } if fragment @@ -651,7 +659,7 @@ impl Fragment { { let mut rect = rect; rect.size.height = Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(fragment, builder, &rect, &color); + self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); } // TODO: This caret/text selection implementation currently does not account for vertical text @@ -678,12 +686,13 @@ impl Fragment { Point2D::new(end.x.to_f32_px(), containing_block.max_y().to_f32_px()), ); if let Some(selection_color) = fragment - .selected_style + .inline_styles + .selected + .borrow() .clone_background_color() .as_absolute() { - let selection_common = - builder.common_properties(selection_rect, &fragment.parent_style); + let selection_common = builder.common_properties(selection_rect, &parent_style); builder.wr().push_rect( &selection_common, selection_rect, @@ -709,7 +718,7 @@ impl Fragment { ), ); let insertion_point_common = - builder.common_properties(insertion_point_rect, &fragment.parent_style); + builder.common_properties(insertion_point_rect, &parent_style); // TODO: The color of the caret is currently hardcoded to the text color. // We should be retrieving the caret color from the style properly. builder @@ -734,7 +743,7 @@ impl Fragment { let mut rect = rect; rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset; rect.size.height = Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(fragment, builder, &rect, &color); + self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); } if !shadows.0.is_empty() { @@ -744,23 +753,22 @@ impl Fragment { fn build_display_list_for_text_decoration( &self, - fragment: &TextFragment, + parent_style: &ServoArc<ComputedValues>, builder: &mut DisplayListBuilder, rect: &PhysicalRect<Au>, color: &AbsoluteColor, ) { let rect = rect.to_webrender(); let wavy_line_thickness = (0.33 * rect.size().height).ceil(); - let text_decoration_color = fragment - .parent_style + let text_decoration_color = parent_style .clone_text_decoration_color() .resolve_to_absolute(color); - let text_decoration_style = fragment.parent_style.clone_text_decoration_style(); + let text_decoration_style = parent_style.clone_text_decoration_style(); if text_decoration_style == ComputedTextDecorationStyle::MozNone { return; } builder.display_list.wr.push_line( - &builder.common_properties(rect, &fragment.parent_style), + &builder.common_properties(rect, parent_style), &rect, wavy_line_thickness, wr::LineOrientation::Horizontal, @@ -1026,7 +1034,7 @@ impl<'a> BuilderForBoxFragment<'a> { for extra_background in extra_backgrounds { let positioning_area = extra_background.rect; let painter = BackgroundPainter { - style: &extra_background.style.borrow_mut().0, + style: &extra_background.style.borrow_mut(), painting_area_override: None, positioning_area_override: Some( positioning_area diff --git a/components/layout/dom.rs b/components/layout/dom.rs index 4400d78feb1..faedf608749 100644 --- a/components/layout/dom.rs +++ b/components/layout/dom.rs @@ -26,7 +26,7 @@ use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::flexbox::FlexLevelBox; use crate::flow::BlockLevelBox; -use crate::flow::inline::InlineItem; +use crate::flow::inline::{InlineItem, SharedInlineStyles}; use crate::fragment_tree::Fragment; use crate::geom::PhysicalSize; use crate::replaced::CanvasInfo; @@ -59,7 +59,7 @@ impl InnerDOMLayoutData { /// A box that is stored in one of the `DOMLayoutData` slots. #[derive(MallocSizeOf)] pub(super) enum LayoutBox { - DisplayContents, + DisplayContents(SharedInlineStyles), BlockLevel(ArcRefCell<BlockLevelBox>), InlineLevel(Vec<ArcRefCell<InlineItem>>), FlexLevel(ArcRefCell<FlexLevelBox>), @@ -70,7 +70,7 @@ pub(super) enum LayoutBox { impl LayoutBox { fn invalidate_cached_fragment(&self) { match self { - LayoutBox::DisplayContents => {}, + LayoutBox::DisplayContents(..) => {}, LayoutBox::BlockLevel(block_level_box) => { block_level_box.borrow().invalidate_cached_fragment() }, @@ -91,7 +91,7 @@ impl LayoutBox { pub(crate) fn fragments(&self) -> Vec<Fragment> { match self { - LayoutBox::DisplayContents => vec![], + LayoutBox::DisplayContents(..) => vec![], LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().fragments(), LayoutBox::InlineLevel(inline_items) => inline_items .iter() diff --git a/components/layout/dom_traversal.rs b/components/layout/dom_traversal.rs index a75d699a1b3..d9019bbf8e0 100644 --- a/components/layout/dom_traversal.rs +++ b/components/layout/dom_traversal.rs @@ -23,6 +23,7 @@ use style::values::specified::Quotes; use crate::context::LayoutContext; use crate::dom::{BoxSlot, LayoutBox, NodeExt}; +use crate::flow::inline::SharedInlineStyles; use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags, Tag}; use crate::quotes::quotes_for_lang; use crate::replaced::ReplacedContents; @@ -185,6 +186,12 @@ pub(super) trait TraversalHandler<'dom> { contents: Contents, box_slot: BoxSlot<'dom>, ); + + /// Notify the handler that we are about to recurse into a `display: contents` element. + fn enter_display_contents(&mut self, _: SharedInlineStyles) {} + + /// Notify the handler that we have finished a `display: contents` element. + fn leave_display_contents(&mut self) {} } fn traverse_children_of<'dom>( @@ -254,8 +261,15 @@ fn traverse_element<'dom>( // <https://drafts.csswg.org/css-display-3/#valdef-display-contents> element.unset_all_boxes() } else { - element.element_box_slot().set(LayoutBox::DisplayContents); - traverse_children_of(element, context, handler) + let shared_inline_styles: SharedInlineStyles = + (&NodeAndStyleInfo::new(element, style)).into(); + element + .element_box_slot() + .set(LayoutBox::DisplayContents(shared_inline_styles.clone())); + + handler.enter_display_contents(shared_inline_styles); + traverse_children_of(element, context, handler); + handler.leave_display_contents(); } }, Display::GeneratingBox(display) => { @@ -308,8 +322,12 @@ fn traverse_eager_pseudo_element<'dom>( Display::Contents => { let items = generate_pseudo_element_content(&info.style, node, context); let box_slot = node.pseudo_element_box_slot(pseudo_element_type); - box_slot.set(LayoutBox::DisplayContents); + let shared_inline_styles: SharedInlineStyles = (&info).into(); + box_slot.set(LayoutBox::DisplayContents(shared_inline_styles.clone())); + + handler.enter_display_contents(shared_inline_styles); traverse_pseudo_element_contents(&info, context, handler, items); + handler.leave_display_contents(); }, Display::GeneratingBox(display) => { let items = generate_pseudo_element_content(&info.style, node, context); diff --git a/components/layout/flexbox/mod.rs b/components/layout/flexbox/mod.rs index 27b69bf289f..737b15de25b 100644 --- a/components/layout/flexbox/mod.rs +++ b/components/layout/flexbox/mod.rs @@ -90,7 +90,6 @@ impl FlexContainerConfig { pub(crate) struct FlexContainer { children: Vec<ArcRefCell<FlexLevelBox>>, - #[conditional_malloc_size_of] style: ServoArc<ComputedValues>, /// The configuration of this [`FlexContainer`]. 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..42d80059ab2 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -13,7 +13,10 @@ 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 +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>, @@ -63,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>, @@ -83,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() } } @@ -100,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. @@ -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..0ff58b6a6f3 100644 --- a/components/layout/flow/inline/inline_box.rs +++ b/components/layout/flow/inline/inline_box.rs @@ -8,7 +8,10 @@ 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; @@ -20,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 @@ -37,6 +43,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 +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 e23193f3904..17a952d24cb 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -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, diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index ec85f3574dc..b367b20c881 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -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() => diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 596556b296c..9b96b1c4fb4 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -17,7 +17,7 @@ use style::properties::ComputedValues; use style::values::specified::box_::DisplayOutside; use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, FragmentFlags}; -use crate::ArcRefCell; +use crate::SharedStyle; use crate::display_list::ToWebRender; use crate::formatting_contexts::Baselines; use crate::geom::{ @@ -40,15 +40,9 @@ pub(crate) enum BackgroundMode { /// Draw the background normally, getting information from the Fragment style. Normal, } - -#[derive(Debug, MallocSizeOf)] -pub(crate) struct BackgroundStyle(#[conditional_malloc_size_of] pub ServoArc<ComputedValues>); - -pub(crate) type SharedBackgroundStyle = ArcRefCell<BackgroundStyle>; - #[derive(MallocSizeOf)] pub(crate) struct ExtraBackground { - pub style: SharedBackgroundStyle, + pub style: SharedStyle, pub rect: PhysicalRect<Au>, } @@ -64,7 +58,6 @@ pub(crate) enum SpecificLayoutInfo { pub(crate) struct BoxFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] pub style: ServoArc<ComputedValues>, pub children: Vec<Fragment>, diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index 1c5324fa1c4..f67da133031 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -22,6 +22,7 @@ use super::{ Tag, }; use crate::cell::ArcRefCell; +use crate::flow::inline::SharedInlineStyles; use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect}; use crate::style_ext::ComputedValuesExt; @@ -64,8 +65,7 @@ pub(crate) struct CollapsedMargin { #[derive(MallocSizeOf)] pub(crate) struct TextFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] - pub parent_style: ServoArc<ComputedValues>, + pub inline_styles: SharedInlineStyles, pub rect: PhysicalRect<Au>, pub font_metrics: FontMetrics, pub font_key: FontInstanceKey, @@ -78,14 +78,11 @@ pub(crate) struct TextFragment { /// Extra space to add for each justification opportunity. pub justification_adjustment: Au, pub selection_range: Option<ServoRange<ByteIndex>>, - #[conditional_malloc_size_of] - pub selected_style: ServoArc<ComputedValues>, } #[derive(MallocSizeOf)] pub(crate) struct ImageFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] pub style: ServoArc<ComputedValues>, pub rect: PhysicalRect<Au>, pub clip: PhysicalRect<Au>, @@ -97,7 +94,6 @@ pub(crate) struct IFrameFragment { pub base: BaseFragment, pub pipeline_id: PipelineId, pub rect: PhysicalRect<Au>, - #[conditional_malloc_size_of] pub style: ServoArc<ComputedValues>, } diff --git a/components/layout/fragment_tree/positioning_fragment.rs b/components/layout/fragment_tree/positioning_fragment.rs index 0cf525a3479..803a154ba83 100644 --- a/components/layout/fragment_tree/positioning_fragment.rs +++ b/components/layout/fragment_tree/positioning_fragment.rs @@ -25,7 +25,6 @@ pub(crate) struct PositioningFragment { 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 diff --git a/components/layout/layout_box_base.rs b/components/layout/layout_box_base.rs index 71fbfdeced1..d025a627aef 100644 --- a/components/layout/layout_box_base.rs +++ b/components/layout/layout_box_base.rs @@ -27,7 +27,6 @@ use crate::{ConstraintSpace, ContainingBlockSize}; #[derive(MallocSizeOf)] pub(crate) struct LayoutBoxBase { pub base_fragment_info: BaseFragmentInfo, - #[conditional_malloc_size_of] pub style: Arc<ComputedValues>, pub cached_inline_content_size: AtomicRefCell<Option<Box<(SizeConstraint, InlineContentSizesResult)>>>, diff --git a/components/layout/lib.rs b/components/layout/lib.rs index af7d432c4d8..cd992387277 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -38,6 +38,7 @@ pub use flow::BoxTree; pub use fragment_tree::FragmentTree; pub use layout_impl::LayoutFactoryImpl; use malloc_size_of_derive::MallocSizeOf; +use servo_arc::Arc as ServoArc; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; use style::values::computed::TextDecorationLine; @@ -45,6 +46,16 @@ use style::values::computed::TextDecorationLine; use crate::geom::{LogicalVec2, SizeConstraint}; use crate::style_ext::AspectRatio; +/// At times, a style is "owned" by more than one layout object. For example, text +/// fragments need a handle on their parent inline box's style. In order to make +/// incremental layout easier to implement, another layer of shared ownership is added via +/// [`SharedStyle`]. This allows updating the style in originating layout object and +/// having all "depdendent" objects update automatically. +/// +/// Note that this is not a cost-free data structure, so should only be +/// used when necessary. +pub(crate) type SharedStyle = ArcRefCell<ServoArc<ComputedValues>>; + /// Represents the set of constraints that we use when computing the min-content /// and max-content inline sizes of an element. pub(crate) struct ConstraintSpace { diff --git a/components/layout/table/construct.rs b/components/layout/table/construct.rs index 133904db7ae..0c238073df2 100644 --- a/components/layout/table/construct.rs +++ b/components/layout/table/construct.rs @@ -19,7 +19,6 @@ use super::{ Table, TableCaption, TableLevelBox, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset, TableTrack, TableTrackGroup, TableTrackGroupType, }; -use crate::PropagatedBoxTreeData; use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::dom::{BoxSlot, LayoutBox}; @@ -29,9 +28,10 @@ use crate::formatting_contexts::{ IndependentFormattingContext, IndependentFormattingContextContents, IndependentNonReplacedContents, }; -use crate::fragment_tree::{BackgroundStyle, BaseFragmentInfo, SharedBackgroundStyle}; +use crate::fragment_tree::BaseFragmentInfo; use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal}; +use crate::{PropagatedBoxTreeData, SharedStyle}; /// A reference to a slot and its coordinates in the table #[derive(Debug)] @@ -725,7 +725,7 @@ impl<'style, 'dom> TableBuilderTraversal<'style, 'dom> { base: LayoutBoxBase::new((&anonymous_info).into(), style.clone()), group_index: self.current_row_group_index, is_anonymous: true, - shared_background_style: SharedBackgroundStyle::new(BackgroundStyle(style)), + shared_background_style: SharedStyle::new(style), })); } @@ -767,9 +767,7 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { base: LayoutBoxBase::new(info.into(), info.style.clone()), group_type: internal.into(), track_range: next_row_index..next_row_index, - shared_background_style: SharedBackgroundStyle::new(BackgroundStyle( - info.style.clone(), - )), + shared_background_style: SharedStyle::new(info.style.clone()), }); self.builder.table.row_groups.push(row_group.clone()); @@ -812,9 +810,7 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { base: LayoutBoxBase::new(info.into(), info.style.clone()), group_index: self.current_row_group_index, is_anonymous: false, - shared_background_style: SharedBackgroundStyle::new(BackgroundStyle( - info.style.clone(), - )), + shared_background_style: SharedStyle::new(info.style.clone()), }); self.push_table_row(row.clone()); box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(row))); @@ -860,9 +856,7 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { base: LayoutBoxBase::new(info.into(), info.style.clone()), group_type: internal.into(), track_range: first_column..self.builder.table.columns.len(), - shared_background_style: SharedBackgroundStyle::new(BackgroundStyle( - info.style.clone(), - )), + shared_background_style: SharedStyle::new(info.style.clone()), }); self.builder.table.column_groups.push(column_group.clone()); box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup( @@ -1145,9 +1139,7 @@ fn add_column( base: LayoutBoxBase::new(column_info.into(), column_info.style.clone()), group_index, is_anonymous, - shared_background_style: SharedBackgroundStyle::new(BackgroundStyle( - column_info.style.clone(), - )), + shared_background_style: SharedStyle::new(column_info.style.clone()), }); collection.extend(repeat(column.clone()).take(span as usize)); column diff --git a/components/layout/table/mod.rs b/components/layout/table/mod.rs index 8e2783e2919..08fc89d927e 100644 --- a/components/layout/table/mod.rs +++ b/components/layout/table/mod.rs @@ -82,10 +82,11 @@ use style::properties::style_structs::Font; use style_traits::dom::OpaqueNode; use super::flow::BlockFormattingContext; +use crate::SharedStyle; use crate::cell::ArcRefCell; use crate::flow::BlockContainer; use crate::formatting_contexts::IndependentFormattingContext; -use crate::fragment_tree::{BaseFragmentInfo, Fragment, SharedBackgroundStyle}; +use crate::fragment_tree::{BaseFragmentInfo, Fragment}; use crate::geom::PhysicalVec; use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::BorderStyleColor; @@ -98,12 +99,10 @@ pub struct Table { /// The style of this table. These are the properties that apply to the "wrapper" ie the element /// that contains both the grid and the captions. Not all properties are actually used on the /// wrapper though, such as background and borders, which apply to the grid. - #[conditional_malloc_size_of] style: Arc<ComputedValues>, /// The style of this table's grid. This is an anonymous style based on the table's style, but /// eliminating all the properties handled by the "wrapper." - #[conditional_malloc_size_of] grid_style: Arc<ComputedValues>, /// The [`BaseFragmentInfo`] for this table's grid. This is necessary so that when the @@ -292,7 +291,7 @@ pub struct TableTrack { /// A shared container for this track's style, used to share the style for the purposes /// of drawing backgrounds in individual cells. This allows updating the style in a /// single place and having it affect all cell `Fragment`s. - shared_background_style: SharedBackgroundStyle, + shared_background_style: SharedStyle, } #[derive(Debug, MallocSizeOf, PartialEq)] @@ -317,7 +316,7 @@ pub struct TableTrackGroup { /// A shared container for this track's style, used to share the style for the purposes /// of drawing backgrounds in individual cells. This allows updating the style in a /// single place and having it affect all cell `Fragment`s. - shared_background_style: SharedBackgroundStyle, + shared_background_style: SharedStyle, } impl TableTrackGroup { diff --git a/components/layout/taffy/mod.rs b/components/layout/taffy/mod.rs index 50873fc3e66..fc13e6c52ae 100644 --- a/components/layout/taffy/mod.rs +++ b/components/layout/taffy/mod.rs @@ -24,7 +24,6 @@ use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; #[derive(Debug, MallocSizeOf)] pub(crate) struct TaffyContainer { children: Vec<ArcRefCell<TaffyItemBox>>, - #[conditional_malloc_size_of] style: Arc<ComputedValues>, } @@ -76,7 +75,6 @@ pub(crate) struct TaffyItemBox { pub(crate) taffy_layout: taffy::Layout, pub(crate) child_fragments: Vec<Fragment>, pub(crate) positioning_context: PositioningContext, - #[conditional_malloc_size_of] pub(crate) style: Arc<ComputedValues>, pub(crate) taffy_level_box: TaffyItemBoxInner, } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 2bdeedf986d..ae951da97e5 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -53,6 +53,7 @@ use std::ops::Range; use std::rc::Rc; use std::sync::Arc; +use style::properties::ComputedValues; use style::values::generics::length::GenericLengthPercentageOrAuto; pub use stylo_malloc_size_of::MallocSizeOfOps; use uuid::Uuid; @@ -750,6 +751,12 @@ impl<T: MallocSizeOf> MallocSizeOf for accountable_refcell::RefCell<T> { } } +impl MallocSizeOf for servo_arc::Arc<ComputedValues> { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.conditional_size_of(ops) + } +} + malloc_size_of_hash_map!(indexmap::IndexMap<K, V, S>); malloc_size_of_hash_set!(indexmap::IndexSet<T, S>); diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index 277c0f4217d..73aa09203dc 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -167223,6 +167223,19 @@ {} ] ], + "display-contents-inline-002.html": [ + "f40a34129f348aca37fca83f70598053cf22785b", + [ + null, + [ + [ + "/css/css-display/display-contents-pass-no-red-ref.html", + "==" + ] + ], + {} + ] + ], "display-contents-inline-flex-001.html": [ "43b502731aefbaa348671e32171f0e1eb4bca04b", [ diff --git a/tests/wpt/tests/css/css-display/display-contents-inline-002.html b/tests/wpt/tests/css/css-display/display-contents-inline-002.html new file mode 100644 index 00000000000..f40a34129f3 --- /dev/null +++ b/tests/wpt/tests/css/css-display/display-contents-inline-002.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Display: display:contents in inline layout should affect style of descendants</title> +<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com"> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents"> +<link rel="match" href="display-contents-pass-no-red-ref.html"> +<style> + #contents { + display: contents; + color: black; + } + .red { color: red; } +</style> +<p>You should see the word PASS and no red below.</p> +<span> + P<span class="red"><div id="contents">AS</div></span>S +</span> |