servo/components/layout_2020/construct_modern.rs
Martin Robinson 264c0f972f
layout: Add LayoutBoxBase and use it for IndependentFormattingContext (#34507)
Add a new struct `LayoutBoxBase`, that will be used throughout the box
tree. The idea of this struct is that we have a place to consistently
store common layout information (style and node information) and also to
cache layout results such as content sizes (inline and maybe later box
sizes) and eventually layout results.

In addition to the addition of this struct,
`IndependentFormattingContext` is flattened slightly so that it directly
holds the contents of both replaced and non-replaced elements.

This is only added to independent formatting contexts, but will later be
added to all block containers as well.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
2024-12-07 19:12:25 +00:00

235 lines
8.3 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 style::values::computed::TextDecorationLine;
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;
/// <https://drafts.csswg.org/css-flexbox/#flex-items>
pub(crate) struct ModernContainerBuilder<'a, 'dom, Node> {
context: &'a LayoutContext<'a>,
info: &'a NodeAndStyleInfo<Node>,
text_decoration_line: TextDecorationLine,
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<'a, 'dom, Node: 'dom> TraversalHandler<'dom, Node> for ModernContainerBuilder<'a, '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>,
text_decoration_line: TextDecorationLine,
) -> Self {
ModernContainerBuilder {
context,
info,
text_decoration_line,
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.text_decoration_line,
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();
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
},
);
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
}
}