use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use app_units::Au;
use atomic_refcell::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::selector_parser::RestyleDamage;
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::{ConstraintSpace, ContainingBlock, ContainingBlockSize};
#[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,
cached_layout_result: AtomicRefCell<Option<LayoutResultAndInputs>>,
cached_layout_result_dirty: AtomicBool,
pub fragments: AtomicRefCell<Vec<Fragment>>,
pub parent_box: Option<WeakLayoutBox>,
only_descendants_changed: AtomicBool,
}
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(),
fragments: AtomicRefCell::default(),
parent_box: None,
only_descendants_changed: AtomicBool::default(),
}
}
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;
}
}
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) -> Vec<Fragment> {
self.fragments.borrow().clone()
}
pub(crate) fn add_fragment(&self, fragment: Fragment) {
if self.only_descendants_changed.load(Ordering::Relaxed) &&
let Some(base) = fragment.base()
{
base.set_status(FragmentStatus::OnlyDescendantsChanged)
}
self.fragments.borrow_mut().push(fragment);
}
pub(crate) fn set_fragment(&self, fragment: Fragment) {
if self.only_descendants_changed.load(Ordering::Relaxed) &&
let Some(base) = fragment.base()
{
base.set_status(FragmentStatus::OnlyDescendantsChanged)
}
*self.fragments.borrow_mut() = vec![fragment];
}
pub(crate) fn clear_fragments(&self) {
self.fragments.borrow_mut().clear();
}
pub(crate) fn clear_fragments_and_dirty_fragment_cache(&self) {
self.fragments.borrow_mut().clear();
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_mut().iter_mut() {
if let Some(base) = fragment.base() {
base.repair_style(new_style);
}
}
}
#[expect(unused)]
pub(crate) fn parent_box(&self) -> Option<LayoutBox> {
self.parent_box.as_ref().and_then(WeakLayoutBox::upgrade)
}
pub(crate) fn add_damage(
&self,
element_damage: LayoutDamage,
damage_from_children: LayoutDamage,
damage_from_parent: RestyleDamage,
) -> LayoutDamage {
let only_descendants_changed = !RestyleDamage::from(element_damage)
.contains(RestyleDamage::RELAYOUT) &&
!damage_from_parent.contains(RestyleDamage::RELAYOUT) &&
!damage_from_children.contains(LayoutDamage::LAYOUT_AFFECTED_BY_INFLOW_DESCENDANT) &&
self.fragments.borrow().iter().all(|fragment| {
fragment
.base()
.is_some_and(|base| base.status() == FragmentStatus::Clean)
});
self.only_descendants_changed
.store(only_descendants_changed, Ordering::Relaxed);
self.clear_fragments_and_dirty_fragment_cache();
if !element_damage.is_empty() ||
damage_from_children.contains(LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES)
{
*self.cached_inline_content_size.borrow_mut() = None;
}
let mut damage_for_parent = element_damage | damage_from_children;
damage_for_parent.set(
LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES,
!element_damage.is_empty() ||
(!self.base_fragment_info.is_anonymous() &&
self.outer_inline_content_sizes_depend_on_content
.load(Ordering::Relaxed)),
);
damage_for_parent
}
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();
}
}
}
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>,
pub content_block_size: Au,
pub collapsible_margins_in_children: CollapsedBlockMargins,
pub content_inline_size_for_table: Option<Au>,
pub baselines: Baselines,
pub depends_on_block_constraints: bool,
pub specific_layout_info: Option<SpecificLayoutInfo>,
}
#[derive(MallocSizeOf)]
pub(crate) struct IndependentFormattingContextLayoutResultAndInputs {
pub result: IndependentFormattingContextLayoutResult,
pub containing_block_for_children_size: ContainingBlockSize,
pub positioning_context: PositioningContext,
}
#[derive(Clone, MallocSizeOf)]
pub(crate) struct SameFormattingContextBlockLayoutResult {
#[conditional_malloc_size_of]
pub fragment: Arc<BoxFragment>,
original_offset: Point2D<Au, CSSPixel>,
}
#[derive(MallocSizeOf)]
pub(crate) struct SameFormattingContextBlockLayoutResultAndInputs {
pub result: SameFormattingContextBlockLayoutResult,
pub containing_block_size: ContainingBlockSize,
pub containing_block_writing_mode: WritingMode,
pub containing_block_justify_items: AlignFlags,
collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
ignore_block_margins_for_stretch: LogicalSides1D<bool>,
has_inline_parent: bool,
}