/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/layout/layout_box_base.rs
430 строк
18 KB
Martin Robinson
layout: Add `TextFragmentRunData` (#47015)
05 авг 2026, 18:47
Не верифицирован
05 авг 2026, 18:47
8e22026
Код
Авторство
О чём код?
/* 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 std::fmt::{Debug, Formatter}; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use app_units::Au; use atomic_refcell::{AtomicRef, AtomicRefCell}; use euclid::Point2D; use layout_api::LayoutDamage; use malloc_size_of_derive::MallocSizeOf; use servo_arc::Arc as ServoArc; use style::computed_values::position::T as Position; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; use style::values::specified::align::AlignFlags; use style_traits::CSSPixel; use crate::context::LayoutContext; use crate::dom::{LayoutBox, WeakLayoutBox}; use crate::flow::CollapsibleWithParentStartMargin; use crate::formatting_contexts::Baselines; use crate::fragment_tree::{ BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, Fragment, FragmentStatus, SpecificLayoutInfo, }; use crate::geom::LogicalSides1D; use crate::positioned::{PositioningContext, relative_adjustement}; use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult, SizeConstraint}; use crate::traversal::ElementDamageSet; use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize}; /// A box tree node that handles containing information about style and the original DOM /// node or pseudo-element that it is based on. This also handles caching of layout values /// such as the inline content sizes to avoid recalculating these values during layout /// passes. /// /// In the future, this will hold layout results to support incremental layout. #[derive(MallocSizeOf)] pub(crate) struct LayoutBoxBase { pub base_fragment_info: BaseFragmentInfo, pub style: ServoArc<ComputedValues>, pub cached_inline_content_size: AtomicRefCell<Option<Box<(SizeConstraint, InlineContentSizesResult)>>>, pub outer_inline_content_sizes_depend_on_content: AtomicBool, /// The cached layout results for this [`LayoutBoxBase`]. These are either cached /// independent formatting context results or a cached block layout for use within /// a block flow. cached_layout_result: AtomicRefCell<Option<LayoutResultAndInputs>>, /// Whether or not the cached layout result for this [`LayoutBoxBase`] is dirty. /// This flag is used to preserve the cache when it can be used to do a faster /// layout, but cannot be reused directly. cached_layout_result_dirty: AtomicBool, /// A count of the number of boxes are in this box's subtree (including itself). /// This is used as a heuristic to know when to perform parallel layout. subtree_size: AtomicUsize, pub fragments: AtomicRefCell<Vec<Fragment>>, pub parent_box: Option<WeakLayoutBox>, } impl LayoutBoxBase { pub(crate) fn new( base_fragment_info: BaseFragmentInfo, style: ServoArc<ComputedValues>, ) -> Self { Self { base_fragment_info, style, cached_inline_content_size: AtomicRefCell::default(), outer_inline_content_sizes_depend_on_content: AtomicBool::new(true), cached_layout_result: AtomicRefCell::default(), cached_layout_result_dirty: AtomicBool::default(), subtree_size: AtomicUsize::default(), fragments: AtomicRefCell::default(), parent_box: None, } } /// Set the subtree size on this [`LayoutBoxBase`]. This should be done once /// box construction knows how many boxes are in this box's subtree. pub(crate) fn set_subtree_size(&self, size: usize) { self.subtree_size.store(size, Ordering::Relaxed); } pub(crate) fn subtree_size(&self) -> usize { self.subtree_size.load(Ordering::Relaxed) } /// Get the inline content sizes of a box tree node that extends this [`LayoutBoxBase`], fetch /// the result from a cache when possible. pub(crate) fn inline_content_sizes( &self, layout_context: &LayoutContext, constraint_space: &ConstraintSpace, layout_box: &impl ComputeInlineContentSizes, ) -> InlineContentSizesResult { let mut cache = self.cached_inline_content_size.borrow_mut(); if let Some(cached_inline_content_size) = cache.as_ref() { let (previous_cb_block_size, result) = **cached_inline_content_size; if !result.depends_on_block_constraints || previous_cb_block_size == constraint_space.block_size { return result; } // TODO: Should we keep multiple caches for various block sizes? } let result = layout_box.compute_inline_content_sizes_with_fixup(layout_context, constraint_space); *cache = Some(Box::new((constraint_space.block_size, result))); result } pub(crate) fn fragments(&self) -> AtomicRef<'_, Vec<Fragment>> { self.fragments.borrow() } pub(crate) fn add_fragment(&self, fragment: Fragment) { self.fragments.borrow_mut().push(fragment); } pub(crate) fn set_fragment(&self, fragment: Fragment) { *self.fragments.borrow_mut() = vec![fragment]; } pub(crate) fn clear_fragments(&self) { self.fragments.borrow_mut().clear(); } /// Clear all resulting fragments and dirty and fragment caches. Resulting fragments are /// used for layout queries and fragment caches are used for incremental layout. pub(crate) fn clear_fragments_and_dirty_fragment_cache(&self) { self.clear_fragments(); self.cached_layout_result_dirty .store(true, Ordering::Relaxed); } pub(crate) fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) { self.style = new_style.clone(); for fragment in self.fragments.borrow().iter() { fragment.repair_style(new_style); } } #[expect(unused)] pub(crate) fn parent_box(&self) -> Option<LayoutBox> { self.parent_box.as_ref().and_then(WeakLayoutBox::upgrade) } /// Clear fragment layout caches on this base, depending on upward flowing damage, but /// *do not* clear its resulting fragment. The layout cache itself is always cleared, /// but the inline content size cache is cleared conditionally. /// /// Returns true is this [`LayoutBoxBase`] propagates `RecomputeInlineContentSizes` /// and false otherwise. pub(crate) fn invalidate_caches(&self, damage_set: &ElementDamageSet) -> bool { self.cached_layout_result_dirty .store(true, Ordering::Relaxed); if !damage_set.on_element.is_empty() || damage_set .from_children .contains(LayoutDamage::RecomputeInlineContentSizes) { *self.cached_inline_content_size.borrow_mut() = None; } // When a block container has a mix of inline-level and block-level contents, the // inline-level ones are wrapped inside an anonymous block associated with the // block container. The anonymous block has an `auto` size, so its intrinsic // contribution depends on content, but it can't affect the intrinsic size of // ancestors if the block container is sized extrinsically. // // If the intrinsic contributions of this node depend on content, we will need to // clear the cached intrinsic sizes of the parent. But if the contributions are // purely extrinsic, then the intrinsic sizes of the ancestors won't be affected, // and we can keep the cache. !self.base_fragment_info.is_anonymous() && self.outer_inline_content_sizes_depend_on_content .load(Ordering::Relaxed) } /// Clear fragment layout caches on this base, depending on upward flowing damage, and /// also clear its resulting fragment. The layout cache itself is always cleared, but /// the inline content size cache is cleared conditionally. /// /// Returns true is this [`LayoutBoxBase`] propagates `RecomputeInlineContentSizes` /// and false otherwise. pub(crate) fn invalidate_caches_for_fragment_tree_layout( &self, damage_set: &ElementDamageSet, ) -> bool { self.clear_fragments(); self.invalidate_caches(damage_set) } pub(crate) fn cached_independent_formatting_context_layout_if_applicable( &self, positioning_context: &mut PositioningContext, containing_block_for_children: &ContainingBlock<'_>, ) -> Option<IndependentFormattingContextLayoutResult> { if self.cached_layout_result_dirty.load(Ordering::Relaxed) { return None; } let cache = self.cached_layout_result.borrow(); let Some(LayoutResultAndInputs::IndependentFormattingContext(cache)) = &*cache else { return None; }; let cache = &**cache; if cache.containing_block_for_children_size.inline != containing_block_for_children.size.inline { return None; } if cache.containing_block_for_children_size.block != containing_block_for_children.size.block && cache.result.depends_on_block_constraints { return None; } positioning_context.append(cache.positioning_context.clone()); Some(cache.result.clone()) } pub(crate) fn cache_independent_formatting_context_layout( &self, containing_block_for_children: &ContainingBlock<'_>, child_positioning_context: &PositioningContext, result: &IndependentFormattingContextLayoutResult, ) { self.cached_layout_result_dirty .store(false, Ordering::Relaxed); *self.cached_layout_result.borrow_mut() = Some(LayoutResultAndInputs::IndependentFormattingContext( Box::new(IndependentFormattingContextLayoutResultAndInputs { result: result.clone(), positioning_context: child_positioning_context.clone(), containing_block_for_children_size: containing_block_for_children.size.clone(), }), )); } pub(crate) fn cached_same_formatting_context_block_if_applicable( &self, containing_block: &ContainingBlock, collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>, ignore_block_margins_for_stretch: LogicalSides1D<bool>, has_inline_parent: bool, ) -> Option<Arc<BoxFragment>> { if self.cached_layout_result_dirty.load(Ordering::Relaxed) { return None; } let mut cached_layout_result = self.cached_layout_result.borrow_mut(); let Some(LayoutResultAndInputs::SameFormattingContextBlock(result)) = &mut *cached_layout_result else { return None; }; if result.containing_block_size != containing_block.size || result.containing_block_writing_mode != containing_block.style.writing_mode || result.containing_block_justify_items != containing_block.style.clone_justify_items().computed.0.0 || result.collapsible_with_parent_start_margin != collapsible_with_parent_start_margin || result.ignore_block_margins_for_stretch != ignore_block_margins_for_stretch || result.has_inline_parent != has_inline_parent { return None; } let fragment = result.result.fragment.clone(); { let mut origin = result.result.original_offset; if self.style.clone_position() == Position::Relative { origin += relative_adjustement(&self.style, containing_block) .to_physical_vector(containing_block.style.writing_mode) } fragment.base.set_rect_origin(origin); } Some(fragment) } pub(crate) fn cache_same_formatting_context_block_layout( &self, containing_block: &ContainingBlock, collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>, ignore_block_margins_for_stretch: LogicalSides1D<bool>, has_inline_parent: bool, fragment: Arc<BoxFragment>, ) { let mut original_offset; { original_offset = fragment.content_rect().origin; if self.style.clone_position() == Position::Relative { original_offset -= relative_adjustement(&self.style, containing_block) .to_physical_vector(containing_block.style.writing_mode) } } self.cached_layout_result_dirty .store(false, Ordering::Relaxed); *self.cached_layout_result.borrow_mut() = Some(LayoutResultAndInputs::SameFormattingContextBlock(Box::new( SameFormattingContextBlockLayoutResultAndInputs { result: SameFormattingContextBlockLayoutResult { fragment, original_offset, }, containing_block_size: containing_block.size.clone(), containing_block_writing_mode: containing_block.style.writing_mode, containing_block_justify_items: containing_block .style .clone_justify_items() .computed .0 .0, collapsible_with_parent_start_margin, ignore_block_margins_for_stretch, has_inline_parent, }, ))); } pub(crate) fn clear_scrollable_overflow_all_on_fragments(&self) { for fragment in self.fragments.borrow().iter() { fragment.clear_scrollable_overflow(); } } pub(crate) fn mark_fragments_as_descendants_changed(&self) { for fragment in self.fragments.borrow().iter() { if let Some(base) = fragment.base() { base.set_status(FragmentStatus::OnlyDescendantsChanged); } } } } impl Debug for LayoutBoxBase { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { f.debug_struct("LayoutBoxBase").finish() } } #[derive(MallocSizeOf)] pub(crate) enum LayoutResultAndInputs { IndependentFormattingContext(Box<IndependentFormattingContextLayoutResultAndInputs>), SameFormattingContextBlock(Box<SameFormattingContextBlockLayoutResultAndInputs>), } #[derive(Clone, MallocSizeOf)] pub(crate) struct IndependentFormattingContextLayoutResult { pub fragments: Vec<Fragment>, /// <https://drafts.csswg.org/css2/visudet.html#root-height> pub content_block_size: Au, /// If this layout is for a block container, this tracks the collapsable size /// of start and end margins and whether or not the block container collapsed through. pub collapsible_margins_in_children: CollapsedBlockMargins, /// The contents of a table may force it to become wider than what we would expect /// from 'width' and 'min-width'. This is the resulting inline content size, /// or None for non-table layouts. pub content_inline_size_for_table: Option<Au>, /// The offset of the last inflow baseline of this layout in the content area, if /// there was one. This is used to propagate baselines to the ancestors of `display: /// inline-block`. pub baselines: Baselines, /// Whether or not this layout depends on the containing block size. pub depends_on_block_constraints: bool, /// Additional information of this layout that could be used by Javascripts and devtools. pub specific_layout_info: Option<SpecificLayoutInfo>, } /// A collection of layout inputs and a cached layout result for an IndependentFormattingContext for /// use in [`LayoutBoxBase`]. #[derive(MallocSizeOf)] pub(crate) struct IndependentFormattingContextLayoutResultAndInputs { /// The [`IndependentFormattingContextLayoutResult`] for this layout. pub result: IndependentFormattingContextLayoutResult, /// The [`ContainingBlockSize`] to use for this box's contents, but not /// for the box itself. pub containing_block_for_children_size: ContainingBlockSize, /// A [`PositioningContext`] holding absolutely-positioned descendants /// collected during the layout of this box. pub positioning_context: PositioningContext, } #[derive(Clone, MallocSizeOf)] pub(crate) struct SameFormattingContextBlockLayoutResult { #[conditional_malloc_size_of] pub fragment: Arc<BoxFragment>, original_offset: Point2D<Au, CSSPixel>, } /// A collection of layout inputs and a cached layout result for a SameFormattingContextBlock for /// use in [`LayoutBoxBase`]. #[derive(MallocSizeOf)] pub(crate) struct SameFormattingContextBlockLayoutResultAndInputs { pub result: SameFormattingContextBlockLayoutResult, /// The [`ContainingBlockSize`] used when this block was laid out. pub containing_block_size: ContainingBlockSize, /// The containing block's [`WritingMode`] used when this block was laid out. pub containing_block_writing_mode: WritingMode, /// The containing block's `justify-items` [`AlignFlags`] used when this block was laid out. pub containing_block_justify_items: AlignFlags, /// Whether or not the margin in this block was collapsible with the parent's start margin /// when this block was laid out. collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>, /// Whether or not block margins were ignored for stretch when this block was laid out. ignore_block_margins_for_stretch: LogicalSides1D<bool>, /// Whether or not this block had an inline parent. has_inline_parent: bool, }