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>
This commit is contained in:
Martin Robinson 2025-01-27 16:04:37 +01:00 committed by GitHub
parent d5fcc5a5d5
commit 6b04bc6263
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 228 additions and 226 deletions

View file

@ -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 {

View file

@ -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();

View file

@ -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,
}
}

View file

@ -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(),
),
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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,
))
};

View file

@ -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,
))
},
};

View file

@ -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,
}
}
}

View file

@ -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(),
),
}
}

View file

@ -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 */
)
},

View file

@ -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 {:?}",

View file

@ -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,
}
}

View file

@ -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();

View file

@ -415973,7 +415973,7 @@
]
},
"table-as-item-percent-width-cell-001-ref.html": [
"2f40b6c49fdcee593a160c82c381d4c14f377a38",
"b8831e20e761d79ff8ee6e2bfc2a6e1243ca5f7a",
[]
],
"table-item-flex-percentage-min-width-ref.html": [

View file

@ -14,9 +14,6 @@
[main table 8]
expected: FAIL
[main table 12]
expected: FAIL
[main table 9]
expected: FAIL

View file

@ -1,2 +0,0 @@
[percent-width-cell-dynamic.html]
expected: FAIL

View file

@ -1,3 +0,0 @@
[percent-width-ignored-002.tentative.html]
[#stf 1]
expected: FAIL

View file

@ -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

View file

@ -4,6 +4,3 @@
[table 4]
expected: FAIL
[table 6]
expected: FAIL

View file

@ -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

View file

@ -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

View file

@ -1,9 +1,3 @@
[table-minmax.html]
[table 2]
expected: FAIL
[table 1]
expected: FAIL
[table 13]
expected: FAIL

View file

@ -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

View file

@ -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

View file

@ -8,7 +8,7 @@
width: 200px;
border: 1px solid black;
}
table { width: max-content; }
table { width: min-content; }
td {
background-color: cyan;
width: 100%;