diff options
author | Martin Robinson <mrobinson@igalia.com> | 2025-01-27 16:04:37 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-27 15:04:37 +0000 |
commit | 6b04bc6263726a789ff241af95702cb7c14d4202 (patch) | |
tree | 3fdc3fe0dd6c42c6bd8389cdd92dac54186651da | |
parent | d5fcc5a5d50d270a1e96d91507a5c224240300bb (diff) | |
download | servo-6b04bc6263726a789ff241af95702cb7c14d4202.tar.gz servo-6b04bc6263726a789ff241af95702cb7c14d4202.zip |
layout: Take percentage columns into account when sizing table grid min and max (#35167)
The specification doesn't say how to deal with percentages when
determining the minimum and maximum size of a table grid, so follow the
approach that Chromium uses.
Essentially, figure out the "missing" percentage from the non-percentage
columns and then use that to work backwards to fine the size of the
percentage ones.
This change is larger than one might expect, because this percentage
approach shouldn't happen for tables that are descendants of a flex,
grid or table container (except when there is an interceding absolute).
We have to pass this information down when building the box tree. This
will also make it easier to improve propagated text decorations in the
future.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
26 files changed, 228 insertions, 226 deletions
diff --git a/components/layout_2020/construct_modern.rs b/components/layout_2020/construct_modern.rs index 96fa29b9c96..07b8c6e983f 100644 --- a/components/layout_2020/construct_modern.rs +++ b/components/layout_2020/construct_modern.rs @@ -7,7 +7,6 @@ use std::borrow::Cow; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use style::values::computed::TextDecorationLine; use crate::context::LayoutContext; use crate::dom::{BoxSlot, NodeExt}; @@ -20,12 +19,13 @@ use crate::formatting_contexts::{ }; use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::DisplayGeneratingBox; +use crate::PropagatedBoxTreeData; -/// <https://drafts.csswg.org/css-flexbox/#flex-items> +/// A builder used for both flex and grid containers. pub(crate) struct ModernContainerBuilder<'a, 'dom, Node> { context: &'a LayoutContext<'a>, info: &'a NodeAndStyleInfo<Node>, - text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, contiguous_text_runs: Vec<ModernContainerTextRun<'dom, Node>>, /// To be run in parallel with rayon in `finish` jobs: Vec<ModernContainerJob<'dom, Node>>, @@ -108,12 +108,12 @@ where pub fn new( context: &'a LayoutContext<'a>, info: &'a NodeAndStyleInfo<Node>, - text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> Self { ModernContainerBuilder { context, info, - text_decoration_line, + propagated_data: propagated_data.disallowing_percentage_table_columns(), contiguous_text_runs: Vec::new(), jobs: Vec::new(), has_text_runs: false, @@ -164,7 +164,7 @@ where let inline_formatting_context = inline_formatting_context_builder.finish( self.context, - self.text_decoration_line, + self.propagated_data, true, /* has_first_formatted_line */ false, /* is_single_line_text_box */ self.info.style.writing_mode.to_bidi_level(), @@ -195,17 +195,21 @@ where box_slot, } => { let is_abspos = info.style.get_box().position.is_absolutely_positioned(); + + // Text decorations are not propagated to any out-of-flow descendants. In addition, + // absolutes don't affect the size of ancestors so it is fine to allow descendent + // tables to resolve percentage columns. + let propagated_data = match is_abspos { + false => self.propagated_data, + true => PropagatedBoxTreeData::default(), + }; + let formatting_context = IndependentFormattingContext::construct( self.context, &info, display.display_inside(), contents, - // Text decorations are not propagated to any out-of-flow descendants. - if is_abspos { - TextDecorationLine::NONE - } else { - self.text_decoration_line - }, + propagated_data, ); if is_abspos { diff --git a/components/layout_2020/flexbox/mod.rs b/components/layout_2020/flexbox/mod.rs index 32c1023214f..8583d26c88b 100644 --- a/components/layout_2020/flexbox/mod.rs +++ b/components/layout_2020/flexbox/mod.rs @@ -12,7 +12,6 @@ use style::properties::longhands::flex_wrap::computed_value::T as FlexWrap; use style::properties::ComputedValues; use style::values::computed::{AlignContent, JustifyContent}; use style::values::specified::align::AlignFlags; -use style::values::specified::text::TextDecorationLine; use crate::cell::ArcRefCell; use crate::construct_modern::{ModernContainerBuilder, ModernItemKind}; @@ -22,7 +21,7 @@ use crate::dom_traversal::{NodeAndStyleInfo, NonReplacedContents}; use crate::formatting_contexts::{IndependentFormattingContext, IndependentLayout}; use crate::fragment_tree::BaseFragmentInfo; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; -use crate::ContainingBlock; +use crate::{ContainingBlock, PropagatedBoxTreeData}; mod geom; mod layout; @@ -102,12 +101,10 @@ impl FlexContainer { context: &LayoutContext, info: &NodeAndStyleInfo<impl NodeExt<'dom>>, contents: NonReplacedContents, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> Self { - let text_decoration_line = - propagated_text_decoration_line | info.style.clone_text_decoration_line(); - - let mut builder = ModernContainerBuilder::new(context, info, text_decoration_line); + let mut builder = + ModernContainerBuilder::new(context, info, propagated_data.union(&info.style)); contents.traverse(context, info, &mut builder); let items = builder.finish(); diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs index d14d4347dfa..f458c5713dc 100644 --- a/components/layout_2020/flow/construct.rs +++ b/components/layout_2020/flow/construct.rs @@ -11,7 +11,6 @@ use style::properties::longhands::list_style_position::computed_value::T as List use style::properties::ComputedValues; use style::selector_parser::PseudoElement; use style::str::char_is_whitespace; -use style::values::specified::text::TextDecorationLine; use super::inline::construct::InlineFormattingContextBuilder; use super::inline::inline_box::InlineBox; @@ -30,13 +29,14 @@ use crate::layout_box_base::LayoutBoxBase; use crate::positioned::AbsolutelyPositionedBox; use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, DisplayOutside}; use crate::table::{AnonymousTableContent, Table}; +use crate::PropagatedBoxTreeData; impl BlockFormattingContext { pub(crate) fn construct<'dom, Node>( context: &LayoutContext, info: &NodeAndStyleInfo<Node>, contents: NonReplacedContents, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, is_list_item: bool, ) -> Self where @@ -46,7 +46,7 @@ impl BlockFormattingContext { context, info, contents, - propagated_text_decoration_line, + propagated_data, is_list_item, )) } @@ -63,6 +63,7 @@ impl BlockFormattingContext { struct BlockLevelJob<'dom, Node> { info: NodeAndStyleInfo<Node>, box_slot: BoxSlot<'dom>, + propagated_data: PropagatedBoxTreeData, kind: BlockLevelCreator, } @@ -71,7 +72,6 @@ enum BlockLevelCreator { Independent { display_inside: DisplayInside, contents: Contents, - propagated_text_decoration_line: TextDecorationLine, }, OutOfFlowAbsolutelyPositionedBox { display_inside: DisplayInside, @@ -100,7 +100,7 @@ enum IntermediateBlockContainer { InlineFormattingContext(BlockContainer), Deferred { contents: NonReplacedContents, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, is_list_item: bool, }, } @@ -135,8 +135,8 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> { /// be considered the first line for the purposes of `text-indent`. have_already_seen_first_line_for_text_indent: bool, - /// The propagated [`TextDecorationLine`]. - text_decoration_line: TextDecorationLine, + /// The propagated data to use for BoxTree construction. + propagated_data: PropagatedBoxTreeData, inline_formatting_context_builder: InlineFormattingContextBuilder, @@ -155,14 +155,13 @@ impl BlockContainer { context: &LayoutContext, info: &NodeAndStyleInfo<Node>, contents: NonReplacedContents, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, is_list_item: bool, ) -> BlockContainer where Node: NodeExt<'dom>, { - let mut builder = - BlockContainerBuilder::new(context, info, propagated_text_decoration_line); + let mut builder = BlockContainerBuilder::new(context, info, propagated_data); if is_list_item { if let Some(marker_contents) = crate::lists::make_marker(context, info) { @@ -189,16 +188,13 @@ where pub(crate) fn new( context: &'style LayoutContext, info: &'style NodeAndStyleInfo<Node>, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> Self { - let text_decoration_line = - propagated_text_decoration_line | info.style.clone_text_decoration_line(); - BlockContainerBuilder { context, info, block_level_boxes: Vec::new(), - text_decoration_line, + propagated_data: propagated_data.union(&info.style), have_already_seen_first_line_for_text_indent: false, anonymous_style: None, anonymous_table_content: Vec::new(), @@ -215,7 +211,7 @@ where if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish( self.context, - self.text_decoration_line, + 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(), @@ -266,10 +262,9 @@ where // > Note that text decorations are not propagated to floating and absolutely // > positioned descendants, nor to the contents of atomic inline-level descendants // > such as inline blocks and inline tables. - let propagated_text_decoration_line = if inline_table { - TextDecorationLine::NONE - } else { - self.text_decoration_line + let propagated_data = match inline_table { + true => self.propagated_data.without_text_decorations(), + false => self.propagated_data, }; let contents: Vec<AnonymousTableContent<'dom, Node>> = @@ -279,12 +274,7 @@ where _ => None, }; - let ifc = Table::construct_anonymous( - self.context, - self.info, - contents, - propagated_text_decoration_line, - ); + let ifc = Table::construct_anonymous(self.context, self.info, contents, propagated_data); if inline_table { self.inline_formatting_context_builder.push_atomic(ifc); @@ -296,6 +286,7 @@ where info: anonymous_info, box_slot: BoxSlot::dummy(), kind: BlockLevelCreator::AnonymousTable { table_block }, + propagated_data, }); } @@ -418,6 +409,7 @@ where info: info.clone(), box_slot: BoxSlot::dummy(), kind: BlockLevelCreator::OutsideMarker { contents }, + propagated_data: self.propagated_data.without_text_decorations(), }); } @@ -439,7 +431,7 @@ where display_inside, contents, // Text decorations are not propagated to atomic inline-level descendants. - TextDecorationLine::NONE, + self.propagated_data.without_text_decorations(), ), ); box_slot.set(LayoutBox::InlineLevel(atomic)); @@ -489,7 +481,7 @@ where .inline_formatting_context_builder .split_around_block_and_finish( self.context, - self.text_decoration_line, + self.propagated_data, !self.have_already_seen_first_line_for_text_indent, self.info.style.writing_mode.to_bidi_level(), ) @@ -497,7 +489,7 @@ where self.push_block_level_job_for_inline_formatting_context(inline_formatting_context); } - let propagated_text_decoration_line = self.text_decoration_line; + let propagated_data = self.propagated_data; let kind = match contents { Contents::NonReplaced(contents) => match display_inside { DisplayInside::Flow { is_list_item } @@ -506,7 +498,7 @@ where BlockLevelCreator::SameFormattingContextBlock( IntermediateBlockContainer::Deferred { contents, - propagated_text_decoration_line, + propagated_data, is_list_item, }, ) @@ -514,7 +506,6 @@ where _ => BlockLevelCreator::Independent { display_inside, contents: contents.into(), - propagated_text_decoration_line, }, }, Contents::Replaced(contents) => { @@ -522,7 +513,6 @@ where BlockLevelCreator::Independent { display_inside, contents, - propagated_text_decoration_line, } }, }; @@ -530,6 +520,7 @@ where info: info.clone(), box_slot, kind, + propagated_data, }); // Any block also counts as the first line for the purposes of text indent. Even if @@ -565,6 +556,7 @@ where info: info.clone(), box_slot, kind, + propagated_data: self.propagated_data.without_text_decorations(), }); } @@ -583,6 +575,7 @@ where info, display_inside, contents, + self.propagated_data, )); box_slot.set(LayoutBox::InlineLevel(inline_level_box)); return; @@ -596,13 +589,14 @@ where info: info.clone(), box_slot, kind, + propagated_data: self.propagated_data.without_text_decorations(), }); } fn end_ongoing_inline_formatting_context(&mut self) { if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish( self.context, - self.text_decoration_line, + 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(), @@ -638,6 +632,7 @@ where BlockContainer::InlineFormattingContext(inline_formatting_context), ), ), + propagated_data: self.propagated_data, }); self.have_already_seen_first_line_for_text_indent = true; @@ -663,14 +658,13 @@ where BlockLevelCreator::Independent { display_inside, contents, - propagated_text_decoration_line, } => { let context = IndependentFormattingContext::construct( context, info, display_inside, contents, - propagated_text_decoration_line, + self.propagated_data, ); ArcRefCell::new(BlockLevelBox::Independent(context)) }, @@ -693,6 +687,7 @@ where info, display_inside, contents, + self.propagated_data, ))), BlockLevelCreator::OutsideMarker { contents } => { let marker_style = context @@ -708,7 +703,7 @@ where context, &info.new_replacing_style(marker_style.clone()), contents, - TextDecorationLine::empty(), + self.propagated_data.without_text_decorations(), false, /* is_list_item */ ); ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker { @@ -737,15 +732,9 @@ impl IntermediateBlockContainer { match self { IntermediateBlockContainer::Deferred { contents, - propagated_text_decoration_line, + propagated_data, is_list_item, - } => BlockContainer::construct( - context, - info, - contents, - propagated_text_decoration_line, - is_list_item, - ), + } => BlockContainer::construct(context, info, contents, propagated_data, is_list_item), IntermediateBlockContainer::InlineFormattingContext(block_container) => block_container, } } diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs index 0967ad3006a..5ebcfbb2615 100644 --- a/components/layout_2020/flow/float.rs +++ b/components/layout_2020/flow/float.rs @@ -19,7 +19,6 @@ use style::computed_values::position::T as Position; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; use style::values::computed::Clear as StyleClear; -use style::values::specified::text::TextDecorationLine; use crate::context::LayoutContext; use crate::dom::NodeExt; @@ -29,7 +28,7 @@ use crate::fragment_tree::{BoxFragment, CollapsedMargin}; use crate::geom::{LogicalRect, LogicalVec2, ToLogical}; use crate::positioned::{relative_adjustement, PositioningContext}; use crate::style_ext::{DisplayInside, PaddingBorderMargin}; -use crate::ContainingBlock; +use crate::{ContainingBlock, PropagatedBoxTreeData}; /// A floating box. #[derive(Debug)] @@ -902,6 +901,7 @@ impl FloatBox { info: &NodeAndStyleInfo<impl NodeExt<'dom>>, display_inside: DisplayInside, contents: Contents, + propagated_data: PropagatedBoxTreeData, ) -> Self { Self { contents: IndependentFormattingContext::construct( @@ -910,7 +910,7 @@ impl FloatBox { display_inside, contents, // Text decorations are not propagated to any out-of-flow descendants - TextDecorationLine::NONE, + propagated_data.without_text_decorations(), ), } } diff --git a/components/layout_2020/flow/inline/construct.rs b/components/layout_2020/flow/inline/construct.rs index f87cba344ec..2e05e2f0056 100644 --- a/components/layout_2020/flow/inline/construct.rs +++ b/components/layout_2020/flow/inline/construct.rs @@ -8,7 +8,6 @@ use std::char::{ToLowercase, ToUppercase}; use icu_segmenter::WordSegmenter; use servo_arc::Arc; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; -use style::values::computed::TextDecorationLine; use style::values::specified::text::TextTransformCase; use unicode_bidi::Level; @@ -22,6 +21,7 @@ use crate::flow::float::FloatBox; use crate::formatting_contexts::IndependentFormattingContext; use crate::positioned::AbsolutelyPositionedBox; use crate::style_ext::ComputedValuesExt; +use crate::PropagatedBoxTreeData; #[derive(Default)] pub(crate) struct InlineFormattingContextBuilder { @@ -271,7 +271,7 @@ impl InlineFormattingContextBuilder { pub(crate) fn split_around_block_and_finish( &mut self, layout_context: &LayoutContext, - text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, default_bidi_level: Level, ) -> Option<InlineFormattingContext> { @@ -303,7 +303,7 @@ impl InlineFormattingContextBuilder { inline_builder_from_before_split.finish( layout_context, - text_decoration_line, + propagated_data, has_first_formatted_line, /* is_single_line_text_input = */ false, default_bidi_level, @@ -314,7 +314,7 @@ impl InlineFormattingContextBuilder { pub(crate) fn finish( &mut self, layout_context: &LayoutContext, - text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, is_single_line_text_input: bool, default_bidi_level: Level, @@ -329,7 +329,7 @@ impl InlineFormattingContextBuilder { Some(InlineFormattingContext::new_with_builder( old_builder, layout_context, - text_decoration_line, + propagated_data, has_first_formatted_line, is_single_line_text_input, default_bidi_level, diff --git a/components/layout_2020/flow/inline/mod.rs b/components/layout_2020/flow/inline/mod.rs index c366a8d72c6..828d490db82 100644 --- a/components/layout_2020/flow/inline/mod.rs +++ b/components/layout_2020/flow/inline/mod.rs @@ -127,7 +127,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}; +use crate::{ConstraintSpace, ContainingBlock, PropagatedBoxTreeData}; // From gfxFontConstants.h in Firefox. static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; @@ -1519,7 +1519,7 @@ impl InlineFormattingContext { pub(super) fn new_with_builder( builder: InlineFormattingContextBuilder, layout_context: &LayoutContext, - text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, is_single_line_text_input: bool, starting_bidi_level: Level, @@ -1570,7 +1570,7 @@ impl InlineFormattingContext { inline_items: builder.inline_items, inline_boxes: builder.inline_boxes, font_metrics, - text_decoration_line, + text_decoration_line: propagated_data.text_decoration, has_first_formatted_line, contains_floats: builder.contains_floats, is_single_line_text_input, diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs index b80b106da1e..0f51c5f626d 100644 --- a/components/layout_2020/flow/root.rs +++ b/components/layout_2020/flow/root.rs @@ -30,7 +30,7 @@ use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::replaced::ReplacedContents; use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayInside}; use crate::taffy::{TaffyItemBox, TaffyItemBoxInner}; -use crate::DefiniteContainingBlock; +use crate::{DefiniteContainingBlock, PropagatedBoxTreeData}; pub struct BoxTree { /// Contains typically exactly one block-level box, which was generated by the root element. @@ -289,6 +289,8 @@ fn construct_for_root_element<'dom>( let contents = ReplacedContents::for_element(root_element, context) .map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced); + + let propagated_data = PropagatedBoxTreeData::default().union(&info.style); let root_box = if box_style.position.is_absolutely_positioned() { BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new( AbsolutelyPositionedBox::construct(context, &info, display_inside, contents), @@ -299,15 +301,15 @@ fn construct_for_root_element<'dom>( &info, display_inside, contents, + propagated_data, )) } else { - let propagated_text_decoration_line = info.style.clone_text_decoration_line(); BlockLevelBox::Independent(IndependentFormattingContext::construct( context, &info, display_inside, contents, - propagated_text_decoration_line, + propagated_data, )) }; diff --git a/components/layout_2020/formatting_contexts.rs b/components/layout_2020/formatting_contexts.rs index 91cf78950b6..1e28aa01d6c 100644 --- a/components/layout_2020/formatting_contexts.rs +++ b/components/layout_2020/formatting_contexts.rs @@ -6,7 +6,6 @@ use app_units::Au; use servo_arc::Arc; use style::properties::ComputedValues; use style::selector_parser::PseudoElement; -use style::values::specified::text::TextDecorationLine; use crate::context::LayoutContext; use crate::dom::NodeExt; @@ -24,7 +23,9 @@ use crate::sizing::{self, ComputeInlineContentSizes, InlineContentSizesResult}; use crate::style_ext::{AspectRatio, DisplayInside, LayoutStyle}; use crate::table::Table; use crate::taffy::TaffyContainer; -use crate::{ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, LogicalVec2}; +use crate::{ + ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, LogicalVec2, PropagatedBoxTreeData, +}; /// <https://drafts.csswg.org/css-display/#independent-formatting-context> #[derive(Debug)] @@ -102,7 +103,7 @@ impl IndependentFormattingContext { node_and_style_info: &NodeAndStyleInfo<Node>, display_inside: DisplayInside, contents: Contents, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> Self { let mut base_fragment_info: BaseFragmentInfo = node_and_style_info.into(); @@ -115,7 +116,7 @@ impl IndependentFormattingContext { context, node_and_style_info, non_replaced_contents, - propagated_text_decoration_line, + propagated_data, is_list_item, )) }, @@ -124,7 +125,7 @@ impl IndependentFormattingContext { context, node_and_style_info, non_replaced_contents, - propagated_text_decoration_line, + propagated_data, )) }, DisplayInside::Flex => { @@ -132,7 +133,7 @@ impl IndependentFormattingContext { context, node_and_style_info, non_replaced_contents, - propagated_text_decoration_line, + propagated_data, )) }, DisplayInside::Table => { @@ -150,7 +151,7 @@ impl IndependentFormattingContext { node_and_style_info, table_grid_style, non_replaced_contents, - propagated_text_decoration_line, + propagated_data, )) }, }; diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs index 940b95309c8..a3410ddff66 100644 --- a/components/layout_2020/lib.rs +++ b/components/layout_2020/lib.rs @@ -33,6 +33,7 @@ pub use fragment_tree::FragmentTree; use geom::AuOrAuto; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; +use style::values::computed::TextDecorationLine; use crate::geom::{LogicalVec2, SizeConstraint}; use crate::style_ext::AspectRatio; @@ -140,3 +141,46 @@ impl<'a> From<&'_ DefiniteContainingBlock<'a>> for ContainingBlock<'a> { } } } + +/// Data that is propagated from ancestors to descendants during [`crate::flow::BoxTree`] +/// construction. This allows data to flow in the reverse direction of the typical layout +/// propoagation, but only during `BoxTree` construction. +#[derive(Clone, Copy, Debug)] +struct PropagatedBoxTreeData { + text_decoration: TextDecorationLine, + allow_percentage_column_in_tables: bool, +} + +impl Default for PropagatedBoxTreeData { + fn default() -> Self { + Self { + text_decoration: Default::default(), + allow_percentage_column_in_tables: true, + } + } +} + +impl PropagatedBoxTreeData { + pub(crate) fn union(&self, style: &ComputedValues) -> Self { + Self { + // FIXME(#31736): This is only taking into account the line style and not the decoration + // color. This should collect information about both so that they can be rendered properly. + text_decoration: self.text_decoration | style.clone_text_decoration_line(), + allow_percentage_column_in_tables: self.allow_percentage_column_in_tables, + } + } + + pub(crate) fn without_text_decorations(&self) -> Self { + Self { + text_decoration: TextDecorationLine::NONE, + allow_percentage_column_in_tables: self.allow_percentage_column_in_tables, + } + } + + fn disallowing_percentage_table_columns(&self) -> PropagatedBoxTreeData { + Self { + text_decoration: self.text_decoration, + allow_percentage_column_in_tables: false, + } + } +} diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs index 9d77c396cfc..23dcd0a25ef 100644 --- a/components/layout_2020/positioned.rs +++ b/components/layout_2020/positioned.rs @@ -11,7 +11,6 @@ use style::computed_values::position::T as Position; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; use style::values::specified::align::{AlignFlags, AxisDirection}; -use style::values::specified::text::TextDecorationLine; use style::Zero; use crate::cell::ArcRefCell; @@ -32,7 +31,8 @@ use crate::geom::{ use crate::sizing::ContentSizes; use crate::style_ext::{ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside}; use crate::{ - ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock, SizeConstraint, + ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock, + PropagatedBoxTreeData, SizeConstraint, }; #[derive(Debug)] @@ -75,8 +75,10 @@ impl AbsolutelyPositionedBox { node_info, display_inside, contents, - // Text decorations are not propagated to any out-of-flow descendants. - TextDecorationLine::NONE, + // Text decorations are not propagated to any out-of-flow descendants. In addition, + // absolutes don't affect the size of ancestors so it is fine to allow descendent + // tables to resolve percentage columns. + PropagatedBoxTreeData::default(), ), } } diff --git a/components/layout_2020/table/construct.rs b/components/layout_2020/table/construct.rs index 9196675530a..92fdfdff538 100644 --- a/components/layout_2020/table/construct.rs +++ b/components/layout_2020/table/construct.rs @@ -13,7 +13,6 @@ use style::properties::style_structs::Font; use style::properties::ComputedValues; use style::selector_parser::PseudoElement; use style::str::char_is_whitespace; -use style::values::specified::TextDecorationLine; use super::{ Table, TableCaption, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset, @@ -31,6 +30,7 @@ use crate::formatting_contexts::{ use crate::fragment_tree::BaseFragmentInfo; use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal}; +use crate::PropagatedBoxTreeData; /// A reference to a slot and its coordinates in the table #[derive(Clone, Copy, Debug)] @@ -78,12 +78,14 @@ impl Table { info: &NodeAndStyleInfo<impl NodeExt<'dom>>, grid_style: Arc<ComputedValues>, contents: NonReplacedContents, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> Self { - let text_decoration_line = - propagated_text_decoration_line | info.style.clone_text_decoration_line(); - let mut traversal = - TableBuilderTraversal::new(context, info, grid_style, text_decoration_line); + let mut traversal = TableBuilderTraversal::new( + context, + info, + grid_style, + propagated_data.union(&info.style), + ); contents.traverse(context, info, &mut traversal); traversal.finish() } @@ -92,7 +94,7 @@ impl Table { context: &LayoutContext, parent_info: &NodeAndStyleInfo<Node>, contents: Vec<AnonymousTableContent<'dom, Node>>, - propagated_text_decoration_line: style::values::specified::TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> IndependentFormattingContext where Node: crate::dom::NodeExt<'dom>, @@ -111,7 +113,7 @@ impl Table { context, &anonymous_info, grid_and_wrapper_style.clone(), - propagated_text_decoration_line, + propagated_data, ); for content in contents { @@ -242,9 +244,15 @@ impl TableBuilder { style: Arc<ComputedValues>, grid_style: Arc<ComputedValues>, base_fragment_info: BaseFragmentInfo, + percentage_columns_allowed_for_inline_content_sizes: bool, ) -> Self { Self { - table: Table::new(style, grid_style, base_fragment_info), + table: Table::new( + style, + grid_style, + base_fragment_info, + percentage_columns_allowed_for_inline_content_sizes, + ), incoming_rowspans: Vec::new(), } } @@ -256,6 +264,7 @@ impl TableBuilder { testing_style.clone(), testing_style.clone(), BaseFragmentInfo::anonymous(), + true, /* percentage_columns_allowed_for_inline_content_sizes */ ) } @@ -627,9 +636,9 @@ pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> { context: &'style LayoutContext<'style>, info: &'style NodeAndStyleInfo<Node>, - /// The value of the [`TextDecorationLine`] to use, either for the row group + /// The value of the [`PropagatedBoxTreeData`] to use, either for the row group /// if processing one or for the table itself if outside a row group. - current_text_decoration_line: TextDecorationLine, + current_propagated_data: PropagatedBoxTreeData, /// The [`TableBuilder`] for this [`TableBuilderTraversal`]. This is separated /// into another struct so that we can write unit tests against the builder. @@ -649,13 +658,18 @@ where context: &'style LayoutContext<'style>, info: &'style NodeAndStyleInfo<Node>, grid_style: Arc<ComputedValues>, - text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> Self { TableBuilderTraversal { context, info, - current_text_decoration_line: text_decoration_line, - builder: TableBuilder::new(info.style.clone(), grid_style, info.into()), + current_propagated_data: propagated_data, + builder: TableBuilder::new( + info.style.clone(), + grid_style, + info.into(), + propagated_data.allow_percentage_column_in_tables, + ), current_anonymous_row_content: Vec::new(), current_row_group_index: None, } @@ -686,7 +700,7 @@ where ); let anonymous_info = self.info.new_anonymous(anonymous_style.clone()); let mut row_builder = - TableRowBuilder::new(self, &anonymous_info, self.current_text_decoration_line); + TableRowBuilder::new(self, &anonymous_info, self.current_propagated_data); for cell_content in row_content { match cell_content { @@ -758,8 +772,8 @@ where track_range: next_row_index..next_row_index, }); - let previous_text_decoration_line = self.current_text_decoration_line; - self.current_text_decoration_line |= info.style.clone_text_decoration_line(); + let previous_propagated_data = self.current_propagated_data; + self.current_propagated_data = self.current_propagated_data.union(&info.style); let new_row_group_index = self.builder.table.row_groups.len() - 1; self.current_row_group_index = Some(new_row_group_index); @@ -772,7 +786,7 @@ where self.finish_anonymous_row_if_needed(); self.current_row_group_index = None; - self.current_text_decoration_line = previous_text_decoration_line; + self.current_propagated_data = previous_propagated_data; self.builder.incoming_rowspans.clear(); // We are doing this until we have actually set a Box for this `BoxSlot`. @@ -784,7 +798,7 @@ where let context = self.context; let mut row_builder = - TableRowBuilder::new(self, info, self.current_text_decoration_line); + TableRowBuilder::new(self, info, self.current_propagated_data); NonReplacedContents::try_from(contents).unwrap().traverse( context, info, @@ -857,7 +871,7 @@ where self.context, info, non_replaced_contents, - self.current_text_decoration_line, + self.current_propagated_data, false, /* is_list_item */ )) }, @@ -910,8 +924,8 @@ struct TableRowBuilder<'style, 'builder, 'dom, 'a, Node> { current_anonymous_cell_content: Vec<AnonymousTableContent<'dom, Node>>, - /// The [`TextDecorationLine`] to use for all children of this row. - text_decoration_line: TextDecorationLine, + /// The [`PropagatedBoxTreeData`] to use for all children of this row. + propagated_data: PropagatedBoxTreeData, } impl<'style, 'builder, 'dom, 'a, Node: 'dom> TableRowBuilder<'style, 'builder, 'dom, 'a, Node> @@ -921,17 +935,15 @@ where fn new( table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom, Node>, info: &'a NodeAndStyleInfo<Node>, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> Self { table_traversal.builder.start_row(); - let text_decoration_line = - propagated_text_decoration_line | info.style.clone_text_decoration_line(); TableRowBuilder { table_traversal, info, current_anonymous_cell_content: Vec::new(), - text_decoration_line, + propagated_data: propagated_data.union(&info.style), } } @@ -957,8 +969,9 @@ where &self.info.style, ); let anonymous_info = self.info.new_anonymous(anonymous_style); - let mut builder = - BlockContainerBuilder::new(context, &anonymous_info, self.text_decoration_line); + + let propagated_data = self.propagated_data.disallowing_percentage_table_columns(); + let mut builder = BlockContainerBuilder::new(context, &anonymous_info, propagated_data); for cell_content in self.current_anonymous_cell_content.drain(..) { match cell_content { @@ -1021,13 +1034,15 @@ where (rowspan, colspan) }); + let propagated_data = + self.propagated_data.disallowing_percentage_table_columns(); let contents = match contents.try_into() { Ok(non_replaced_contents) => { BlockFormattingContext::construct( self.table_traversal.context, info, non_replaced_contents, - self.text_decoration_line, + propagated_data, false, /* is_list_item */ ) }, diff --git a/components/layout_2020/table/layout.rs b/components/layout_2020/table/layout.rs index 8a6d82c532f..062e5c118bb 100644 --- a/components/layout_2020/table/layout.rs +++ b/components/layout_2020/table/layout.rs @@ -646,12 +646,51 @@ impl<'a> TableLayout<'a> { // https://drafts.csswg.org/css-tables/#gridmax: // > The row/column-grid width maximum (GRIDMAX) width is the sum of the max-content width of // > all the columns plus cell spacing or borders. - let mut grid_min_max = self - .columns - .iter() - .fold(ContentSizes::zero(), |result, measure| { - result + measure.content_sizes - }); + // + // The specification doesn't say what to do with columns with percentages, so we follow the + // approach that LayoutNG takes here. We try to figure out the size contribution + // of the percentage columns, by working backward to find the calculated + // percentage of non-percent columns and using that to calculate the size of the + // percent columns. + let mut largest_percentage_column_max_size = Au::zero(); + let mut percent_sum = 0.; + let mut non_percent_columns_max_sum = Au::zero(); + let mut grid_min_max = ContentSizes::zero(); + for column in self.columns.iter() { + match column.percentage { + Some(percentage) if !percentage.is_zero() => { + largest_percentage_column_max_size.max_assign( + column + .content_sizes + .max_content + .scale_by(1.0 / percentage.0), + ); + percent_sum += percentage.0; + }, + _ => { + non_percent_columns_max_sum += column.content_sizes.max_content; + }, + } + + grid_min_max += column.content_sizes; + } + + grid_min_max + .max_content + .max_assign(largest_percentage_column_max_size); + + // Do not take into account percentage of columns when this table is a descendant + // of a flex, grid, or table container. These modes with percentage columns can + // cause inline width to become infinitely wide. + if !percent_sum.is_zero() && + self.table + .percentage_columns_allowed_for_inline_content_sizes + { + let total_inline_size = + non_percent_columns_max_sum.scale_by(1.0 / (1.0 - percent_sum.min(1.0))); + grid_min_max.max_content.max_assign(total_inline_size); + } + assert!( grid_min_max.min_content <= grid_min_max.max_content, "GRIDMAX should never be smaller than GRIDMIN {:?}", diff --git a/components/layout_2020/table/mod.rs b/components/layout_2020/table/mod.rs index 2c16853f2f9..dfcf2b840ea 100644 --- a/components/layout_2020/table/mod.rs +++ b/components/layout_2020/table/mod.rs @@ -130,6 +130,9 @@ pub struct Table { /// Whether or not this Table is anonymous. anonymous: bool, + + /// Whether percentage columns are taken into account during inline content sizes calculation. + percentage_columns_allowed_for_inline_content_sizes: bool, } impl Table { @@ -137,6 +140,7 @@ impl Table { style: Arc<ComputedValues>, grid_style: Arc<ComputedValues>, base_fragment_info: BaseFragmentInfo, + percentage_columns_allowed_for_inline_content_sizes: bool, ) -> Self { Self { style, @@ -150,6 +154,7 @@ impl Table { slots: Vec::new(), size: TableSize::zero(), anonymous: false, + percentage_columns_allowed_for_inline_content_sizes, } } diff --git a/components/layout_2020/taffy/mod.rs b/components/layout_2020/taffy/mod.rs index 6aed847e094..e163fdfaac8 100644 --- a/components/layout_2020/taffy/mod.rs +++ b/components/layout_2020/taffy/mod.rs @@ -8,7 +8,6 @@ use std::fmt; use app_units::Au; use servo_arc::Arc; use style::properties::ComputedValues; -use style::values::computed::TextDecorationLine; use stylo_taffy::TaffyStyloStyle; use crate::cell::ArcRefCell; @@ -19,6 +18,7 @@ use crate::dom_traversal::{NodeAndStyleInfo, NonReplacedContents}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::Fragment; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; +use crate::PropagatedBoxTreeData; #[derive(Debug)] pub(crate) struct TaffyContainer { @@ -31,11 +31,10 @@ impl TaffyContainer { context: &LayoutContext, info: &NodeAndStyleInfo<impl NodeExt<'dom>>, contents: NonReplacedContents, - propagated_text_decoration_line: TextDecorationLine, + propagated_data: PropagatedBoxTreeData, ) -> Self { - let text_decoration_line = - propagated_text_decoration_line | info.style.clone_text_decoration_line(); - let mut builder = ModernContainerBuilder::new(context, info, text_decoration_line); + let mut builder = + ModernContainerBuilder::new(context, info, propagated_data.union(&info.style)); contents.traverse(context, info, &mut builder); let items = builder.finish(); diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index 4e9c021419a..9e23b6caa8a 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -415973,7 +415973,7 @@ ] }, "table-as-item-percent-width-cell-001-ref.html": [ - "2f40b6c49fdcee593a160c82c381d4c14f377a38", + "b8831e20e761d79ff8ee6e2bfc2a6e1243ca5f7a", [] ], "table-item-flex-percentage-min-width-ref.html": [ diff --git a/tests/wpt/meta/css/css-tables/column-track-merging.html.ini b/tests/wpt/meta/css/css-tables/column-track-merging.html.ini index f41d3d824eb..2e8779fed5e 100644 --- a/tests/wpt/meta/css/css-tables/column-track-merging.html.ini +++ b/tests/wpt/meta/css/css-tables/column-track-merging.html.ini @@ -14,9 +14,6 @@ [main table 8] expected: FAIL - [main table 12] - expected: FAIL - [main table 9] expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/percent-width-cell-dynamic.html.ini b/tests/wpt/meta/css/css-tables/percent-width-cell-dynamic.html.ini deleted file mode 100644 index 3f1e27ae000..00000000000 --- a/tests/wpt/meta/css/css-tables/percent-width-cell-dynamic.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[percent-width-cell-dynamic.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/percent-width-ignored-002.tentative.html.ini b/tests/wpt/meta/css/css-tables/percent-width-ignored-002.tentative.html.ini deleted file mode 100644 index bfbe9d6a505..00000000000 --- a/tests/wpt/meta/css/css-tables/percent-width-ignored-002.tentative.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[percent-width-ignored-002.tentative.html] - [#stf 1] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/table-model-fixup.html.ini b/tests/wpt/meta/css/css-tables/table-model-fixup.html.ini index 1320421ad7b..f922038fc0b 100644 --- a/tests/wpt/meta/css/css-tables/table-model-fixup.html.ini +++ b/tests/wpt/meta/css/css-tables/table-model-fixup.html.ini @@ -1,7 +1,4 @@ [table-model-fixup.html] - [2.1. An anonymous table-row box must be generated around each sequence of consecutive children of a table-root box which are not proper table child boxes. (1/2)] - expected: FAIL - [2.2. An anonymous table-row box must be generated around each sequence of consecutive children of a table-row-grouping box which are not table-row boxes. (1/3)] expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/colgroup-col.html.ini b/tests/wpt/meta/css/css-tables/tentative/colgroup-col.html.ini index 54122c07acd..78395e655ca 100644 --- a/tests/wpt/meta/css/css-tables/tentative/colgroup-col.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/colgroup-col.html.ini @@ -4,6 +4,3 @@ [table 4] expected: FAIL - - [table 6] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/colspan-redistribution.html.ini b/tests/wpt/meta/css/css-tables/tentative/colspan-redistribution.html.ini index 4552581db90..aeba8ca6e5c 100644 --- a/tests/wpt/meta/css/css-tables/tentative/colspan-redistribution.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/colspan-redistribution.html.ini @@ -1,42 +1,9 @@ [colspan-redistribution.html] - [table 1] - expected: FAIL - - [table 2] - expected: FAIL - - [table 3] - expected: FAIL - [table 6] expected: FAIL - [table 14] - expected: FAIL - - [table 15] - expected: FAIL - - [table 16] - expected: FAIL - - [table 17] - expected: FAIL - - [table 20] - expected: FAIL - - [table 22] - expected: FAIL - [table 26] expected: FAIL - [table 27] - expected: FAIL - - [table 28] - expected: FAIL - [table 8] expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/column-widths.html.ini b/tests/wpt/meta/css/css-tables/tentative/column-widths.html.ini index 31b6cf26a22..3c01677d62c 100644 --- a/tests/wpt/meta/css/css-tables/tentative/column-widths.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/column-widths.html.ini @@ -1,28 +1,4 @@ [column-widths.html] - [table 14] - expected: FAIL - - [table 19] - expected: FAIL - - [table 21] - expected: FAIL - - [table 22] - expected: FAIL - - [table 23] - expected: FAIL - - [table 25] - expected: FAIL - - [table 26] - expected: FAIL - - [table 30] - expected: FAIL - [table 32] expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/table-minmax.html.ini b/tests/wpt/meta/css/css-tables/tentative/table-minmax.html.ini index 9e69b5a5022..ee1e6c4b23c 100644 --- a/tests/wpt/meta/css/css-tables/tentative/table-minmax.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/table-minmax.html.ini @@ -1,9 +1,3 @@ [table-minmax.html] - [table 2] - expected: FAIL - - [table 1] - expected: FAIL - [table 13] expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution.html.ini b/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution.html.ini index bd7e925f2f5..f4f31a8b831 100644 --- a/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/table-width-redistribution.html.ini @@ -1,12 +1,6 @@ [table-width-redistribution.html] - [table 5] - expected: FAIL - [table 6] expected: FAIL - [table 20] - expected: FAIL - - [table 22] + [table 4] expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/td-box-sizing-001.html.ini b/tests/wpt/meta/css/css-tables/tentative/td-box-sizing-001.html.ini index ab1485f9841..27f3851716a 100644 --- a/tests/wpt/meta/css/css-tables/tentative/td-box-sizing-001.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/td-box-sizing-001.html.ini @@ -1,15 +1,3 @@ [td-box-sizing-001.html] - [table 1] - expected: FAIL - - [table 2] - expected: FAIL - - [table 9] - expected: FAIL - - [table 10] - expected: FAIL - [table 11] expected: FAIL diff --git a/tests/wpt/tests/css/css-flexbox/table-as-item-percent-width-cell-001-ref.html b/tests/wpt/tests/css/css-flexbox/table-as-item-percent-width-cell-001-ref.html index 2f40b6c49fd..b8831e20e76 100644 --- a/tests/wpt/tests/css/css-flexbox/table-as-item-percent-width-cell-001-ref.html +++ b/tests/wpt/tests/css/css-flexbox/table-as-item-percent-width-cell-001-ref.html @@ -8,7 +8,7 @@ width: 200px; border: 1px solid black; } - table { width: max-content; } + table { width: min-content; } td { background-color: cyan; width: 100%; |