servo/components/layout_2020/construct_modern.rs
Martin Robinson 6b04bc6263
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>
2025-01-27 15:04:37 +00:00

239 lines
8.5 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Layout construction code that is shared between modern layout modes (Flexbox and CSS Grid)
use std::borrow::Cow;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, NodeExt};
use crate::dom_traversal::{Contents, NodeAndStyleInfo, TraversalHandler};
use crate::flow::inline::construct::InlineFormattingContextBuilder;
use crate::flow::{BlockContainer, BlockFormattingContext};
use crate::formatting_contexts::{
IndependentFormattingContext, IndependentFormattingContextContents,
IndependentNonReplacedContents,
};
use crate::layout_box_base::LayoutBoxBase;
use crate::style_ext::DisplayGeneratingBox;
use crate::PropagatedBoxTreeData;
/// A builder used for both flex and grid containers.
pub(crate) struct ModernContainerBuilder<'a, 'dom, Node> {
context: &'a LayoutContext<'a>,
info: &'a NodeAndStyleInfo<Node>,
propagated_data: PropagatedBoxTreeData,
contiguous_text_runs: Vec<ModernContainerTextRun<'dom, Node>>,
/// To be run in parallel with rayon in `finish`
jobs: Vec<ModernContainerJob<'dom, Node>>,
has_text_runs: bool,
}
enum ModernContainerJob<'dom, Node> {
ElementOrPseudoElement {
info: NodeAndStyleInfo<Node>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
},
TextRuns(Vec<ModernContainerTextRun<'dom, Node>>),
}
struct ModernContainerTextRun<'dom, Node> {
info: NodeAndStyleInfo<Node>,
text: Cow<'dom, str>,
}
impl<Node> ModernContainerTextRun<'_, Node> {
/// <https://drafts.csswg.org/css-text/#white-space>
fn is_only_document_white_space(&self) -> bool {
// FIXME: is this the right definition? See
// https://github.com/w3c/csswg-drafts/issues/5146
// https://github.com/w3c/csswg-drafts/issues/5147
self.text
.bytes()
.all(|byte| matches!(byte, b' ' | b'\n' | b'\t'))
}
}
pub(crate) enum ModernItemKind {
InFlow,
OutOfFlow,
}
pub(crate) struct ModernItem<'dom> {
pub kind: ModernItemKind,
pub order: i32,
pub box_slot: Option<BoxSlot<'dom>>,
pub formatting_context: IndependentFormattingContext,
}
impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for ModernContainerBuilder<'_, 'dom, Node>
where
Node: NodeExt<'dom>,
{
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
self.contiguous_text_runs.push(ModernContainerTextRun {
info: info.clone(),
text,
})
}
/// Or pseudo-element
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
self.wrap_any_text_in_anonymous_block_container();
self.jobs.push(ModernContainerJob::ElementOrPseudoElement {
info: info.clone(),
display,
contents,
box_slot,
})
}
}
impl<'a, 'dom, Node: 'dom> ModernContainerBuilder<'a, 'dom, Node>
where
Node: NodeExt<'dom>,
{
pub fn new(
context: &'a LayoutContext<'a>,
info: &'a NodeAndStyleInfo<Node>,
propagated_data: PropagatedBoxTreeData,
) -> Self {
ModernContainerBuilder {
context,
info,
propagated_data: propagated_data.disallowing_percentage_table_columns(),
contiguous_text_runs: Vec::new(),
jobs: Vec::new(),
has_text_runs: false,
}
}
fn wrap_any_text_in_anonymous_block_container(&mut self) {
let runs = std::mem::take(&mut self.contiguous_text_runs);
if runs
.iter()
.all(ModernContainerTextRun::is_only_document_white_space)
{
// There is no text run, or they all only contain document white space characters
} else {
self.jobs.push(ModernContainerJob::TextRuns(runs));
self.has_text_runs = true;
}
}
pub(crate) fn finish(mut self) -> Vec<ModernItem<'dom>> {
self.wrap_any_text_in_anonymous_block_container();
let anonymous_style = if self.has_text_runs {
Some(
self.context
.shared_context()
.stylist
.style_for_anonymous::<Node::ConcreteElement>(
&self.context.shared_context().guards,
&style::selector_parser::PseudoElement::ServoAnonymousBox,
&self.info.style,
),
)
} else {
None
};
let mut children: Vec<ModernItem> = std::mem::take(&mut self.jobs)
.into_par_iter()
.filter_map(|job| match job {
ModernContainerJob::TextRuns(runs) => {
let mut inline_formatting_context_builder =
InlineFormattingContextBuilder::new();
for flex_text_run in runs.into_iter() {
inline_formatting_context_builder
.push_text(flex_text_run.text, &flex_text_run.info);
}
let inline_formatting_context = inline_formatting_context_builder.finish(
self.context,
self.propagated_data,
true, /* has_first_formatted_line */
false, /* is_single_line_text_box */
self.info.style.writing_mode.to_bidi_level(),
)?;
let block_formatting_context = BlockFormattingContext::from_block_container(
BlockContainer::InlineFormattingContext(inline_formatting_context),
);
let info = &self.info.new_anonymous(anonymous_style.clone().unwrap());
let formatting_context = IndependentFormattingContext {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
contents: IndependentFormattingContextContents::NonReplaced(
IndependentNonReplacedContents::Flow(block_formatting_context),
),
};
Some(ModernItem {
kind: ModernItemKind::InFlow,
order: 0,
box_slot: None,
formatting_context,
})
},
ModernContainerJob::ElementOrPseudoElement {
info,
display,
contents,
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,
propagated_data,
);
if is_abspos {
Some(ModernItem {
kind: ModernItemKind::OutOfFlow,
order: 0,
box_slot: Some(box_slot),
formatting_context,
})
} else {
Some(ModernItem {
kind: ModernItemKind::InFlow,
order: info.style.clone_order(),
box_slot: Some(box_slot),
formatting_context,
})
}
},
})
.collect::<Vec<_>>();
// https://drafts.csswg.org/css-flexbox/#order-modified-document-order
children.sort_by_key(|child| child.order);
children
}
}