mirror of
https://github.com/servo/servo.git
synced 2025-06-08 08:33:26 +00:00
This replaces `IndependentLayout` with `CacheableLayoutResult` and stores it in `LayoutBoxBase` so it can be reused when we need to lay out a box multiple times. This is a generalization of the caching that we had for flexbox, which is now removed in favor of the new one. With this, the number of runs per second in the Chromium perf test `flexbox-deeply-nested-column-flow.html` are multiplied by 3. Signed-off-by: Oriol Brufau <obrufau@igalia.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
361 lines
13 KiB
Rust
361 lines
13 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/. */
|
|
|
|
use app_units::Au;
|
|
use servo_arc::Arc;
|
|
use style::properties::ComputedValues;
|
|
use style::selector_parser::PseudoElement;
|
|
|
|
use crate::context::LayoutContext;
|
|
use crate::dom::NodeExt;
|
|
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
|
|
use crate::flexbox::FlexContainer;
|
|
use crate::flow::BlockFormattingContext;
|
|
use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags};
|
|
use crate::layout_box_base::{
|
|
CacheableLayoutResult, CacheableLayoutResultAndInputs, LayoutBoxBase,
|
|
};
|
|
use crate::positioned::PositioningContext;
|
|
use crate::replaced::ReplacedContents;
|
|
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, PropagatedBoxTreeData,
|
|
};
|
|
|
|
/// <https://drafts.csswg.org/css-display/#independent-formatting-context>
|
|
#[derive(Debug)]
|
|
pub(crate) struct IndependentFormattingContext {
|
|
pub base: LayoutBoxBase,
|
|
pub contents: IndependentFormattingContextContents,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum IndependentFormattingContextContents {
|
|
NonReplaced(IndependentNonReplacedContents),
|
|
Replaced(ReplacedContents),
|
|
}
|
|
|
|
// Private so that code outside of this module cannot match variants.
|
|
// It should got through methods instead.
|
|
#[derive(Debug)]
|
|
pub(crate) enum IndependentNonReplacedContents {
|
|
Flow(BlockFormattingContext),
|
|
Flex(FlexContainer),
|
|
Grid(TaffyContainer),
|
|
Table(Table),
|
|
// Other layout modes go here
|
|
}
|
|
|
|
/// The baselines of a layout or a [`crate::fragment_tree::BoxFragment`]. Some layout
|
|
/// uses the first and some layout uses the last.
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
pub(crate) struct Baselines {
|
|
pub first: Option<Au>,
|
|
pub last: Option<Au>,
|
|
}
|
|
|
|
impl Baselines {
|
|
pub(crate) fn offset(&self, block_offset: Au) -> Baselines {
|
|
Self {
|
|
first: self.first.map(|first| first + block_offset),
|
|
last: self.last.map(|last| last + block_offset),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IndependentFormattingContext {
|
|
pub fn construct<'dom, Node: NodeExt<'dom>>(
|
|
context: &LayoutContext,
|
|
node_and_style_info: &NodeAndStyleInfo<Node>,
|
|
display_inside: DisplayInside,
|
|
contents: Contents,
|
|
propagated_data: PropagatedBoxTreeData,
|
|
) -> Self {
|
|
let mut base_fragment_info: BaseFragmentInfo = node_and_style_info.into();
|
|
|
|
match contents {
|
|
Contents::NonReplaced(non_replaced_contents) => {
|
|
let contents = match display_inside {
|
|
DisplayInside::Flow { is_list_item } |
|
|
DisplayInside::FlowRoot { is_list_item } => {
|
|
IndependentNonReplacedContents::Flow(BlockFormattingContext::construct(
|
|
context,
|
|
node_and_style_info,
|
|
non_replaced_contents,
|
|
propagated_data,
|
|
is_list_item,
|
|
))
|
|
},
|
|
DisplayInside::Grid => {
|
|
IndependentNonReplacedContents::Grid(TaffyContainer::construct(
|
|
context,
|
|
node_and_style_info,
|
|
non_replaced_contents,
|
|
propagated_data,
|
|
))
|
|
},
|
|
DisplayInside::Flex => {
|
|
IndependentNonReplacedContents::Flex(FlexContainer::construct(
|
|
context,
|
|
node_and_style_info,
|
|
non_replaced_contents,
|
|
propagated_data,
|
|
))
|
|
},
|
|
DisplayInside::Table => {
|
|
let table_grid_style = context
|
|
.shared_context()
|
|
.stylist
|
|
.style_for_anonymous::<Node::ConcreteElement>(
|
|
&context.shared_context().guards,
|
|
&PseudoElement::ServoTableGrid,
|
|
&node_and_style_info.style,
|
|
);
|
|
base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
|
|
IndependentNonReplacedContents::Table(Table::construct(
|
|
context,
|
|
node_and_style_info,
|
|
table_grid_style,
|
|
non_replaced_contents,
|
|
propagated_data,
|
|
))
|
|
},
|
|
};
|
|
Self {
|
|
base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()),
|
|
contents: IndependentFormattingContextContents::NonReplaced(contents),
|
|
}
|
|
},
|
|
Contents::Replaced(contents) => {
|
|
base_fragment_info.flags.insert(FragmentFlags::IS_REPLACED);
|
|
Self {
|
|
base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()),
|
|
contents: IndependentFormattingContextContents::Replaced(contents),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn is_replaced(&self) -> bool {
|
|
matches!(
|
|
self.contents,
|
|
IndependentFormattingContextContents::Replaced(_)
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn style(&self) -> &Arc<ComputedValues> {
|
|
&self.base.style
|
|
}
|
|
|
|
#[inline]
|
|
pub fn base_fragment_info(&self) -> BaseFragmentInfo {
|
|
self.base.base_fragment_info
|
|
}
|
|
|
|
pub(crate) fn inline_content_sizes(
|
|
&self,
|
|
layout_context: &LayoutContext,
|
|
constraint_space: &ConstraintSpace,
|
|
) -> InlineContentSizesResult {
|
|
match &self.contents {
|
|
IndependentFormattingContextContents::NonReplaced(contents) => self
|
|
.base
|
|
.inline_content_sizes(layout_context, constraint_space, contents),
|
|
IndependentFormattingContextContents::Replaced(contents) => self
|
|
.base
|
|
.inline_content_sizes(layout_context, constraint_space, contents),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn outer_inline_content_sizes(
|
|
&self,
|
|
layout_context: &LayoutContext,
|
|
containing_block: &IndefiniteContainingBlock,
|
|
auto_minimum: &LogicalVec2<Au>,
|
|
auto_block_size_stretches_to_containing_block: bool,
|
|
) -> InlineContentSizesResult {
|
|
sizing::outer_inline(
|
|
&self.layout_style(),
|
|
containing_block,
|
|
auto_minimum,
|
|
auto_block_size_stretches_to_containing_block,
|
|
self.is_replaced(),
|
|
true, /* establishes_containing_block */
|
|
|padding_border_sums| self.preferred_aspect_ratio(padding_border_sums),
|
|
|constraint_space| self.inline_content_sizes(layout_context, constraint_space),
|
|
)
|
|
}
|
|
|
|
pub(crate) fn preferred_aspect_ratio(
|
|
&self,
|
|
padding_border_sums: &LogicalVec2<Au>,
|
|
) -> Option<AspectRatio> {
|
|
match &self.contents {
|
|
IndependentFormattingContextContents::NonReplaced(content) => {
|
|
content.preferred_aspect_ratio()
|
|
},
|
|
IndependentFormattingContextContents::Replaced(content) => {
|
|
content.preferred_aspect_ratio(self.style(), padding_border_sums)
|
|
},
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn layout_style(&self) -> LayoutStyle {
|
|
match &self.contents {
|
|
IndependentFormattingContextContents::NonReplaced(content) => {
|
|
content.layout_style(&self.base)
|
|
},
|
|
IndependentFormattingContextContents::Replaced(content) => {
|
|
content.layout_style(&self.base)
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IndependentNonReplacedContents {
|
|
pub fn layout(
|
|
&self,
|
|
layout_context: &LayoutContext,
|
|
positioning_context: &mut PositioningContext,
|
|
containing_block_for_children: &ContainingBlock,
|
|
containing_block: &ContainingBlock,
|
|
depends_on_block_constraints: bool,
|
|
) -> CacheableLayoutResult {
|
|
match self {
|
|
IndependentNonReplacedContents::Flow(bfc) => bfc.layout(
|
|
layout_context,
|
|
positioning_context,
|
|
containing_block_for_children,
|
|
depends_on_block_constraints,
|
|
),
|
|
IndependentNonReplacedContents::Flex(fc) => fc.layout(
|
|
layout_context,
|
|
positioning_context,
|
|
containing_block_for_children,
|
|
depends_on_block_constraints,
|
|
),
|
|
IndependentNonReplacedContents::Grid(fc) => fc.layout(
|
|
layout_context,
|
|
positioning_context,
|
|
containing_block_for_children,
|
|
containing_block,
|
|
),
|
|
IndependentNonReplacedContents::Table(table) => table.layout(
|
|
layout_context,
|
|
positioning_context,
|
|
containing_block_for_children,
|
|
containing_block,
|
|
depends_on_block_constraints,
|
|
),
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(
|
|
feature = "tracing",
|
|
tracing::instrument(
|
|
name = "IndependentNonReplacedContents::layout_with_caching",
|
|
skip_all,
|
|
fields(servo_profiling = true),
|
|
level = "trace",
|
|
)
|
|
)]
|
|
pub fn layout_with_caching(
|
|
&self,
|
|
layout_context: &LayoutContext,
|
|
positioning_context: &mut PositioningContext,
|
|
containing_block_for_children: &ContainingBlock,
|
|
containing_block: &ContainingBlock,
|
|
base: &LayoutBoxBase,
|
|
depends_on_block_constraints: bool,
|
|
) -> CacheableLayoutResult {
|
|
if let Some(cache) = base.cached_layout_result.borrow().as_ref() {
|
|
if cache.containing_block_for_children_size.inline ==
|
|
containing_block_for_children.size.inline &&
|
|
(cache.containing_block_for_children_size.block ==
|
|
containing_block_for_children.size.block ||
|
|
!(cache.result.depends_on_block_constraints ||
|
|
depends_on_block_constraints))
|
|
{
|
|
positioning_context.append(cache.positioning_context.clone());
|
|
return cache.result.clone();
|
|
}
|
|
#[cfg(feature = "tracing")]
|
|
tracing::debug!(
|
|
name: "NonReplaced cache miss",
|
|
cached = ?cache.containing_block_for_children_size,
|
|
required = ?containing_block_for_children.size,
|
|
);
|
|
}
|
|
|
|
let mut child_positioning_context = PositioningContext::new_for_subtree(
|
|
positioning_context.collects_for_nearest_positioned_ancestor(),
|
|
);
|
|
|
|
let result = self.layout(
|
|
layout_context,
|
|
&mut child_positioning_context,
|
|
containing_block_for_children,
|
|
containing_block,
|
|
depends_on_block_constraints,
|
|
);
|
|
|
|
*base.cached_layout_result.borrow_mut() = Some(CacheableLayoutResultAndInputs {
|
|
result: result.clone(),
|
|
positioning_context: child_positioning_context.clone(),
|
|
containing_block_for_children_size: containing_block_for_children.size.clone(),
|
|
});
|
|
positioning_context.append(child_positioning_context);
|
|
|
|
result
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn layout_style<'a>(&'a self, base: &'a LayoutBoxBase) -> LayoutStyle<'a> {
|
|
match self {
|
|
IndependentNonReplacedContents::Flow(fc) => fc.layout_style(base),
|
|
IndependentNonReplacedContents::Flex(fc) => fc.layout_style(),
|
|
IndependentNonReplacedContents::Grid(fc) => fc.layout_style(),
|
|
IndependentNonReplacedContents::Table(fc) => fc.layout_style(None),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn preferred_aspect_ratio(&self) -> Option<AspectRatio> {
|
|
// TODO: support preferred aspect ratios on non-replaced boxes.
|
|
None
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn is_table(&self) -> bool {
|
|
matches!(self, Self::Table(_))
|
|
}
|
|
}
|
|
|
|
impl ComputeInlineContentSizes for IndependentNonReplacedContents {
|
|
fn compute_inline_content_sizes(
|
|
&self,
|
|
layout_context: &LayoutContext,
|
|
constraint_space: &ConstraintSpace,
|
|
) -> InlineContentSizesResult {
|
|
match self {
|
|
Self::Flow(inner) => inner
|
|
.contents
|
|
.compute_inline_content_sizes(layout_context, constraint_space),
|
|
Self::Flex(inner) => {
|
|
inner.compute_inline_content_sizes(layout_context, constraint_space)
|
|
},
|
|
Self::Grid(inner) => {
|
|
inner.compute_inline_content_sizes(layout_context, constraint_space)
|
|
},
|
|
Self::Table(inner) => {
|
|
inner.compute_inline_content_sizes(layout_context, constraint_space)
|
|
},
|
|
}
|
|
}
|
|
}
|