diff options
Diffstat (limited to 'components/layout/construct_modern.rs')
-rw-r--r-- | components/layout/construct_modern.rs | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/components/layout/construct_modern.rs b/components/layout/construct_modern.rs new file mode 100644 index 00000000000..22f179d146c --- /dev/null +++ b/components/layout/construct_modern.rs @@ -0,0 +1,231 @@ +/* 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 std::sync::LazyLock; + +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use style::selector_parser::PseudoElement; + +use crate::PropagatedBoxTreeData; +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; + +/// 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_info = LazyLock::new(|| { + self.info + .pseudo(self.context, PseudoElement::ServoAnonymousBox) + .expect("Should always be able to construct info for anonymous boxes.") + }); + 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: &NodeAndStyleInfo<_> = &*anonymous_info; + 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 + } +} |