diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 8e6b49945b02d..0e13e54b12221 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -2197,15 +2197,9 @@ void Document::update_layout(UpdateLayoutReason reason) // on, so pending changes that escaped classification are accounted for from here on. m_partial_relayout_invalidation.clear_escape(PartialRelayoutEscapeClearReason::FullLayoutPass); - Layout::NodeWithStyleAndBoxModelMetrics* document_element_layout_node = nullptr; - // NB: Called during layout update. - if (document_element && document_element->unsafe_layout_node()) - document_element_layout_node = &as(*document_element->unsafe_layout_node()); - Layout::LayoutRustBridge bridge; bridge.run_root_layout( *m_layout_root, - document_element_layout_node, viewport_rect.width(), viewport_rect.height(), should_collect_devtools_layout_data); diff --git a/Libraries/LibWeb/Layout/LayoutRustBridge.cpp b/Libraries/LibWeb/Layout/LayoutRustBridge.cpp index 81f82284b0665..7c0ada123ad78 100644 --- a/Libraries/LibWeb/Layout/LayoutRustBridge.cpp +++ b/Libraries/LibWeb/Layout/LayoutRustBridge.cpp @@ -575,7 +575,7 @@ LayoutRustBridge::LayoutRustBridge() = default; LayoutRustBridge::~LayoutRustBridge() = default; -void LayoutRustBridge::run_root_layout(Box& viewport, NodeWithStyleAndBoxModelMetrics* document_element_layout_node, CSSPixels viewport_inline_size, CSSPixels viewport_block_size, bool should_collect_devtools_layout_data) +void LayoutRustBridge::run_root_layout(Box& viewport, CSSPixels viewport_inline_size, CSSPixels viewport_block_size, bool should_collect_devtools_layout_data) { VERIFY(!m_commit_root); m_commit_root = &viewport; @@ -591,7 +591,6 @@ void LayoutRustBridge::run_root_layout(Box& viewport, NodeWithStyleAndBoxModelMe ActiveLayoutPassScope active_pass; RustFFI::rust_layout_run_root_layout( Node::slot_id(&viewport), - Node::slot_id(document_element_layout_node), viewport_inline_size.raw_value(), viewport_block_size.raw_value(), should_collect_devtools_layout_data, @@ -611,6 +610,7 @@ void LayoutRustBridge::compute_subtree_layout(Box& root, Painting::Paintable& pa root.document().invalidate_stacking_context_tree(); root.document().layout_node_arena().sync_enrolled_text_node_content(); + auto viewport_rect = root.document().viewport_rect(); auto callbacks = formatting_context_callbacks(); auto sink = commit_sink(); { @@ -619,6 +619,8 @@ void LayoutRustBridge::compute_subtree_layout(Box& root, Painting::Paintable& pa Node::slot_id(&root), Node::slot_id(&root.root()), &paintable_to_replace, + viewport_rect.width().raw_value(), + viewport_rect.height().raw_value(), &callbacks, &sink); } @@ -1149,11 +1151,8 @@ RustFFI::FfiLayoutFcCallbacks LayoutRustBridge::formatting_context_callbacks() return build_svg_element_facts(*node_with_style); }, .read_paintable_geometry = [](void*, void* node, void* paintable_pointer, RustFFI::FfiPaintableGeometry* out) { VERIFY(out); - auto const* paintable = paintable_pointer - ? static_cast(paintable_pointer) - : static_cast(node)->paintable_ptr(); - if (!paintable) - return false; + VERIFY(paintable_pointer); + auto const* paintable = static_cast(paintable_pointer); auto const& box_model = paintable->box_model(); *out = { .content_inline_size = paintable->content_width().raw_value(), diff --git a/Libraries/LibWeb/Layout/LayoutRustBridge.h b/Libraries/LibWeb/Layout/LayoutRustBridge.h index f7e06aa749168..0d62721104dd1 100644 --- a/Libraries/LibWeb/Layout/LayoutRustBridge.h +++ b/Libraries/LibWeb/Layout/LayoutRustBridge.h @@ -32,7 +32,7 @@ class LayoutRustBridge { LayoutRustBridge(); ~LayoutRustBridge(); - void run_root_layout(Box& viewport, NodeWithStyleAndBoxModelMetrics* document_element_layout_node, CSSPixels viewport_inline_size, CSSPixels viewport_block_size, bool should_collect_devtools_layout_data); + void run_root_layout(Box& viewport, CSSPixels viewport_inline_size, CSSPixels viewport_block_size, bool should_collect_devtools_layout_data); void compute_subtree_layout(Box&, Painting::Paintable& paintable_to_replace); void replay_saved_abspos_layout(Box&, Painting::Paintable& paintable_to_replace); diff --git a/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs b/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs index d38ebacca45fa..06f696a1c11a6 100644 --- a/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs +++ b/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs @@ -46,13 +46,31 @@ pub(crate) fn aligned_static_offset( offset } +fn out_of_flow_root_space(inputs: AbsposLayoutInputs) -> (AvailableSpace, ContainingBlockConstraints) { + let containing_block_size = LogicalSize { + inline_size: clamp_to_max_dimension_value(inputs.containing_block_info.rect.size.inline_size), + block_size: clamp_to_max_dimension_value(inputs.containing_block_info.rect.size.block_size), + }; + ( + AvailableSpace { + inline_size: AvailableSize::definite(containing_block_size.inline_size), + block_size: AvailableSize::definite(containing_block_size.block_size), + }, + ContainingBlockConstraints { + percentage_basis_inline_size: Some(containing_block_size.inline_size), + percentage_basis_block_size: Some(containing_block_size.block_size), + quirks_mode_percentage_basis_block_size: None, + }, + ) +} + pub(crate) struct AbsposEngine<'pass> { state: &'pass LayoutState, callbacks: FfiLayoutFcCallbacks, } impl<'pass> AbsposEngine<'pass> { - fn new(state: &'pass LayoutState, callbacks: FfiLayoutFcCallbacks) -> Self { + pub(crate) fn new(state: &'pass LayoutState, callbacks: FfiLayoutFcCallbacks) -> Self { Self { state, callbacks } } @@ -1625,21 +1643,12 @@ impl AbsposEngine<'_> { } impl<'pass> AbsposEngine<'pass> { - fn layout_element(&self, frame: &mut crate::layout::FcFrame<'pass>, node: Node, inputs: AbsposLayoutInputs) { - assert!(!self.facts(node).is_svg_box()); - let containing_block_size = LogicalSize { - inline_size: clamp_to_max_dimension_value(inputs.containing_block_info.rect.size.inline_size), - block_size: clamp_to_max_dimension_value(inputs.containing_block_info.rect.size.block_size), - }; - let available_space = AvailableSpace { - inline_size: AvailableSize::definite(containing_block_size.inline_size), - block_size: AvailableSize::definite(containing_block_size.block_size), - }; - let constraints = ContainingBlockConstraints { - percentage_basis_inline_size: Some(containing_block_size.inline_size), - percentage_basis_block_size: Some(containing_block_size.block_size), - quirks_mode_percentage_basis_block_size: None, - }; + // Run-prelude sizing for an absolutely positioned root: box-model + // metrics, the inset-aware inline solve, the pre-inside-layout block + // pass, and the definiteness overrides insets and aspect ratios provide. + pub(crate) fn dimension_out_of_flow_root(&self, node: Node, inputs: AbsposLayoutInputs) { + let (available_space, constraints) = out_of_flow_root_space(inputs); + let containing_block_inline_size = available_space.inline_size.to_px_or_zero(); let style = self.style(node); { let used = self.used_mut(node); @@ -1648,13 +1657,13 @@ impl<'pass> AbsposEngine<'pass> { used.border_top.set(style.border_top_width()); used.border_bottom.set(style.border_bottom_width()); used.padding_left - .set(style.padding_left().to_px(containing_block_size.inline_size)); + .set(style.padding_left().to_px(containing_block_inline_size)); used.padding_right - .set(style.padding_right().to_px(containing_block_size.inline_size)); + .set(style.padding_right().to_px(containing_block_inline_size)); used.padding_top - .set(style.padding_top().to_px(containing_block_size.inline_size)); + .set(style.padding_top().to_px(containing_block_inline_size)); used.padding_bottom - .set(style.padding_bottom().to_px(containing_block_size.inline_size)); + .set(style.padding_bottom().to_px(containing_block_inline_size)); } self.compute_inline_size(node, available_space, constraints, inputs.static_position_rect); @@ -1693,34 +1702,15 @@ impl<'pass> AbsposEngine<'pass> { self.sizing() .make_button_content_box_definite(node, LayoutMode::Normal, available_space, constraints, None); + } - let inner_available_space = self - .used(node) - .available_inner_space_or_constraints_from(available_space); - let child_layout = match crate::layout::layout_inside_child( - frame, - None, - None, - node, - LayoutMode::Normal, - LayoutInput { - available_space: inner_available_space, - containing_block_constraints: constraints, - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }, - false, - ) { - crate::layout::ChildLayoutOutcome::Created(child_layout) => Some(child_layout), - crate::layout::ChildLayoutOutcome::Skipped => None, - // Absolutely positioned boxes with children establish an - // independent formatting context, so they cannot remain in - // the currently running context. - crate::layout::ChildLayoutOutcome::ReenterCurrent => { - unreachable!("abspos child with contents did not establish a formatting context") - } + pub(crate) fn finalize_out_of_flow_root_after_inside_layout(&self, node: Node, inputs: AbsposLayoutInputs) { + let (available_space, constraints) = out_of_flow_root_space(inputs); + let containing_block_size = LogicalSize { + inline_size: available_space.inline_size.to_px_or_zero(), + block_size: available_space.block_size.to_px_or_zero(), }; - + let style = self.style(node); if style.height().is_auto() { self.compute_block_size( node, @@ -1773,6 +1763,26 @@ impl<'pass> AbsposEngine<'pass> { } } } + } + + fn layout_element(&self, run: &crate::layout::FormattingContextRun<'pass>, node: Node, inputs: AbsposLayoutInputs) { + assert!(!self.facts(node).is_svg_box()); + let (available_space, constraints) = out_of_flow_root_space(inputs); + + match crate::layout::layout_inside_child( + run, + None, + None, + node, + LayoutMode::Normal, + LayoutInput::new(available_space, constraints, ParticipationInParentFormattingContext::AbsolutelyPositioned(inputs)), + false, + ) { + crate::layout::ChildLayoutOutcome::Created(_) | crate::layout::ChildLayoutOutcome::Skipped => {} + crate::layout::ChildLayoutOutcome::ReenterCurrent => { + unreachable!("abspos child with contents did not establish a formatting context") + } + } let static_offset = self.static_offset(node, inputs.static_position_rect); let used = self.used(node); @@ -1808,14 +1818,11 @@ impl<'pass> AbsposEngine<'pass> { .abspos_layout_inputs = Some(inputs); } - if let Some(child_layout) = child_layout { - child_layout.finish(); - } } - pub(crate) fn layout_children(&self, frame: &mut crate::layout::FcFrame<'pass>) { + pub(crate) fn layout_children(&self, run: &crate::layout::FormattingContextRun<'pass>) { debug_assert!(!self.state.is_measurement()); - while let Some(child) = self.state.take_next_contained_abspos_child(frame.box_) { + while let Some(child) = self.state.take_next_contained_abspos_child(run.box_) { let child_box = child.child_box; if self.try_used_pointer(child_box).is_none() { self.state @@ -1829,11 +1836,11 @@ impl<'pass> AbsposEngine<'pass> { .containing_block_info_override .unwrap_or_else(|| self.base_containing_block_info(child_box)), }; - self.layout_element(frame, child_box, inputs); + self.layout_element(run, child_box, inputs); } } - fn replay(&self, frame: &mut crate::layout::FcFrame<'pass>, node: Node) { + fn replay(&self, run: &crate::layout::FormattingContextRun<'pass>, node: Node) { let saved_inputs = self.callbacks.saved_abspos_layout_inputs(node); let found = saved_inputs.is_some(); assert!(found); @@ -1847,7 +1854,7 @@ impl<'pass> AbsposEngine<'pass> { // exactly once. self.state .create_used_values(&self.callbacks, node, ContainingBlockConstraints::default()); - self.layout_element(frame, node, inputs); + self.layout_element(run, node, inputs); } fn compute_inset(&self, node: Node, containing_block_size: LogicalSize) { @@ -1921,8 +1928,8 @@ impl<'pass> AbsposEngine<'pass> { } } -pub(crate) fn layout_contained_abspos_children(frame: &mut crate::layout::FcFrame<'_>) { - AbsposEngine::new(frame.state, frame.callbacks).layout_children(frame); +pub(crate) fn layout_contained_abspos_children(run: &crate::layout::FormattingContextRun<'_>) { + AbsposEngine::new(run.state, run.callbacks).layout_children(run); } /// Lays out every registered abspos child once the in-flow run has finished. @@ -1942,9 +1949,9 @@ pub(crate) fn run_abspos_layout_pass( if !state.has_contained_abspos_children(root) { continue; } - let mut frame = - crate::layout::FcFrame::new(state, root, LayoutMode::Normal, callbacks, should_collect_devtools_layout_data); - layout_contained_abspos_children(&mut frame); + let run = + crate::layout::FormattingContextRun::new(state, root, LayoutMode::Normal, callbacks, should_collect_devtools_layout_data); + layout_contained_abspos_children(&run); } state.set_abspos_layout_pass_is_active(false); debug_assert!( diff --git a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs index 1b05623a816a2..ffde7a430966e 100644 --- a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs @@ -5,6 +5,11 @@ * SPDX-License-Identifier: BSD-2-Clause */ +struct FloatAvoidanceProbe { + opportunity: Option, + content_inline_size: Option, +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] struct BlockCssPixelRect { x: CssPixels, @@ -189,13 +194,11 @@ pub(crate) struct BlockFormattingContext<'pass> { callbacks: FfiLayoutFcCallbacks, block_offset_of_current_block_container: Cell>, pending_legend_flow_position: Cell>, - pending_table_box_content_offset_in_wrapper: Cell>, margin_state: RefCell, floats: RefCell>>, bands: RefCell>, lowest_left_margin_edge: Cell, lowest_right_margin_edge: Cell, - was_notified_after_parent_dimensioned_my_root_box: Cell, } impl<'pass> BlockFormattingContext<'pass> { @@ -207,13 +210,11 @@ impl<'pass> BlockFormattingContext<'pass> { callbacks, block_offset_of_current_block_container: Cell::new(None), pending_legend_flow_position: Cell::new(None), - pending_table_box_content_offset_in_wrapper: Cell::new(None), margin_state: RefCell::new(BlockMarginState::default()), floats: RefCell::new(Vec::new()), bands: RefCell::new(vec![FloatBand::default()]), lowest_left_margin_edge: Cell::new(CssPixels::default()), lowest_right_margin_edge: Cell::new(CssPixels::default()), - was_notified_after_parent_dimensioned_my_root_box: Cell::new(false), } } @@ -380,6 +381,44 @@ impl<'pass> BlockFormattingContext<'pass> { ) } + // The definite inline space left for this box after the float bands that + // intrude at its position are subtracted, including the negative-margin + // adjustment; None when the space is not definite or the box may overlap + // floats. This is the parent-owned half of block-level inline sizing — + // it needs the flow position and the live float bands. + fn float_reduced_inline_opportunity( + &self, + node: Node, + available_space: AvailableSpace, + content_position_in_root: FfiCssPixelPoint, + ) -> Option { + if !matches!(available_space.inline_size, AvailableSize::Definite(_)) + || !self.box_should_avoid_floats_because_it_establishes_fc(node) + { + return None; + } + let style = self.style(node); + let available_inline_size = available_space.inline_size.to_px_or_zero(); + let box_in_root_rect = BlockCssPixelRect { + x: content_position_in_root.x, + y: content_position_in_root.y, + width: available_inline_size, + height: self.used(node).content_block_size.get(), + }; + let intrusion = self.intrusions_for_band_into_rect(self.band_at(box_in_root_rect.y), box_in_root_rect); + let mut remaining_inline_size = available_inline_size - intrusion.left - intrusion.right; + if intrusion.left > CssPixels::default() || intrusion.right > CssPixels::default() { + // Negative margins do not create additional space next to a float. Reduce the space available for + // resolving an automatic inline size by any negative margins, so that the resulting border box is no + // larger than the space next to the float in the inline axis. + let margin_left = style.margin_left().to_px(available_inline_size); + let margin_right = style.margin_right().to_px(available_inline_size); + let negative_margin_sum = margin_left.min(CssPixels::default()) + margin_right.min(CssPixels::default()); + remaining_inline_size = (remaining_inline_size + negative_margin_sum).max(CssPixels::default()); + } + Some(remaining_inline_size) + } + fn compute_inline_size( &self, node: Node, @@ -387,57 +426,61 @@ impl<'pass> BlockFormattingContext<'pass> { constraints: ContainingBlockConstraints, content_position_in_root: FfiCssPixelPoint, ) { - let mut remaining_available_space = available_space; + let float_avoidance_inline_size = + self.float_reduced_inline_opportunity(node, available_space, content_position_in_root); + let content_inline_size = + self.resolve_root_inline_metrics_and_content_size(node, available_space, constraints, float_avoidance_inline_size); + if let Some(content_inline_size) = content_inline_size { + self.used_mut(node).set_content_inline_size(content_inline_size); + } + } + + fn resolve_root_inline_metrics_and_content_size( + &self, + node: Node, + available_space: AvailableSpace, + constraints: ContainingBlockConstraints, + float_avoidance_inline_size: Option, + ) -> Option { let facts = self.facts(node); let style = self.style(node); - - // Certain formatting contexts do not allow float intrusions, so reduce the available space for them. - if matches!(available_space.inline_size, AvailableSize::Definite(_)) && self.box_should_avoid_floats_because_it_establishes_fc(node) { - let available_inline_size = available_space.inline_size.to_px_or_zero(); - let box_in_root_rect = BlockCssPixelRect { - x: content_position_in_root.x, - y: content_position_in_root.y, - width: available_inline_size, - height: self.used(node).content_block_size.get(), - }; - let intrusion = self.intrusions_for_band_into_rect(self.band_at(box_in_root_rect.y), box_in_root_rect); - let mut remaining_inline_size = available_inline_size - intrusion.left - intrusion.right; - if intrusion.left > CssPixels::default() || intrusion.right > CssPixels::default() { - // Negative margins do not create additional space next to a float. Reduce the space available for - // resolving an automatic inline size by any negative margins, so that the resulting border box is no - // larger than the space next to the float in the inline axis. - let margin_left = style.margin_left().to_px(available_inline_size); - let margin_right = style.margin_right().to_px(available_inline_size); - let negative_margin_sum = - margin_left.min(CssPixels::default()) + margin_right.min(CssPixels::default()); - remaining_inline_size = (remaining_inline_size + negative_margin_sum).max(CssPixels::default()); - } + let mut remaining_available_space = available_space; + if let Some(remaining_inline_size) = float_avoidance_inline_size { remaining_available_space.inline_size = AvailableSize::definite(remaining_inline_size); } let sizing = self.sizing(); + let available_inline_size = available_space.inline_size.to_px_or_zero(); let sized_as_replaced = sizing.box_is_sized_as_replaced_element(node, available_space, constraints); - if sized_as_replaced { - self.compute_inline_size_for_block_level_replaced_element_in_normal_flow( - node, - available_space, - constraints, - ); - if facts.is_floating() { - // 10.3.6 Floating, replaced elements: - // https://www.w3.org/TR/CSS22/visudet.html#float-replaced-width - return; + let replaced_inline_size = sized_as_replaced.then(|| { + // 10.3.4 Block-level, replaced elements in normal flow: + // the used value of 'width' is determined as for inline replaced + // elements; the non-replaced rules below then resolve the + // margins. Replaced sizing consults the box's own used metrics, + // so they are written first. + { + let used = self.used_mut(node); + used.margin_left.set(style.margin_left().to_px(available_inline_size)); + used.margin_right.set(style.margin_right().to_px(available_inline_size)); + used.border_left.set(style.border_left_width()); + used.border_right.set(style.border_right_width()); + used.padding_left.set(style.padding_left().to_px(available_inline_size)); + used.padding_right.set(style.padding_right().to_px(available_inline_size)); } + sizing.compute_inline_size_for_replaced_element(node, available_space, constraints) + }); + if sized_as_replaced && facts.is_floating() { + // 10.3.6 Floating, replaced elements: + // https://www.w3.org/TR/CSS22/visudet.html#float-replaced-width + return replaced_inline_size; } if facts.is_floating() { // 10.3.5 Floating, non-replaced elements: // https://www.w3.org/TR/CSS22/visudet.html#float-width - self.compute_inline_size_for_floating_box(node, available_space, constraints); - return; + return Some(self.compute_floating_root_inline_sizes(node, available_space, constraints)); } - let available_inline_size = available_space.inline_size.to_px_or_zero(); let mut margin_left_is_auto = style.margin_left().is_auto(); let mut margin_right_is_auto = style.margin_right().is_auto(); let mut margin_left = style.margin_left().to_px(available_inline_size); @@ -455,12 +498,11 @@ impl<'pass> BlockFormattingContext<'pass> { used.padding_left.set(padding_left); used.padding_right.set(padding_right); } - // NOTE: If we are calculating the min-content or max-content inline size of this box, // and the inline size should be treated as auto, then we can simply return here, // as the preferred inline size and min/max constraints are irrelevant for intrinsic sizing. if self.used(node).inline_size_constraint.get() != SizeConstraint::None { - return; + return None; } let remaining_inline_size = remaining_available_space.inline_size.to_px_or_zero(); @@ -558,7 +600,7 @@ impl<'pass> BlockFormattingContext<'pass> { let input_inline_size = if sized_as_replaced { // NOTE: Replaced elements had their inline size calculated independently above. // We use that inline size as the input here to ensure that margins get resolved. - Some(self.used(node).content_inline_size.get()) + replaced_inline_size } else if facts.is_table_wrapper() { Some(sizing.compute_table_box_inline_size_inside_wrapper( node, @@ -621,20 +663,20 @@ impl<'pass> BlockFormattingContext<'pass> { } } - if !sized_as_replaced && let Some(value) = used_inline_size { - self.used_mut(node).set_content_inline_size(value); + { + let used = self.used_mut(node); + used.margin_left.set(margin_left); + used.margin_right.set(margin_right); } - let used = self.used_mut(node); - used.margin_left.set(margin_left); - used.margin_right.set(margin_right); + if sized_as_replaced { replaced_inline_size } else { used_inline_size } } - fn compute_inline_size_for_floating_box( + fn compute_floating_root_inline_sizes( &self, node: Node, available_space: AvailableSpace, constraints: ContainingBlockConstraints, - ) { + ) -> CssPixels { // 10.3.5 Floating, non-replaced elements let style = self.style(node); let containing_block_inline_size = available_space.inline_size.to_px_or_zero(); @@ -642,12 +684,12 @@ impl<'pass> BlockFormattingContext<'pass> { // If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'. let margin_left = style.margin_left().to_px(containing_block_inline_size); let margin_right = style.margin_right().to_px(containing_block_inline_size); + let padding_left = style.padding_left().to_px(containing_block_inline_size); + let padding_right = style.padding_right().to_px(containing_block_inline_size); { let used = self.used_mut(node); - used.padding_left - .set(style.padding_left().to_px(containing_block_inline_size)); - used.padding_right - .set(style.padding_right().to_px(containing_block_inline_size)); + used.padding_left.set(padding_left); + used.padding_right.set(padding_right); used.margin_left.set(margin_left); used.margin_right.set(margin_right); used.border_left.set(style.border_left_width()); @@ -663,12 +705,11 @@ impl<'pass> BlockFormattingContext<'pass> { // Find the available inline size: in this case, this is the inline size of the containing // block minus the used values of 'margin-left', 'border-left-width', 'padding-left', // 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars. - let used = self.used(node); let available_inline_size = available_space.inline_size.to_px_or_zero() - margin_left - style.border_left_width() - - used.padding_left.get() - - used.padding_right.get() + - padding_left + - padding_right - style.border_right_width() - margin_right; // Then the shrink-to-fit inline size is: @@ -719,37 +760,7 @@ impl<'pass> BlockFormattingContext<'pass> { inline_size = compute(Some(minimum)); } } - self.used_mut(node).set_content_inline_size(inline_size); - } - - fn compute_inline_size_for_block_level_replaced_element_in_normal_flow( - &self, - node: Node, - available_space: AvailableSpace, - constraints: ContainingBlockConstraints, - ) { - // 10.3.6 Floating, replaced elements - let style = self.style(node); - let containing_block_inline_size = available_space.inline_size.to_px_or_zero(); - // 10.3.4 Block-level, replaced elements in normal flow - // The used value of 'width' is determined as for inline replaced elements. Then the rules for - // non-replaced block-level elements are applied to determine the margins. - // If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'. - let used = self.used_mut(node); - used.margin_left - .set(style.margin_left().to_px(containing_block_inline_size)); - used.margin_right - .set(style.margin_right().to_px(containing_block_inline_size)); - used.border_left.set(style.border_left_width()); - used.border_right.set(style.border_right_width()); - used.padding_left - .set(style.padding_left().to_px(containing_block_inline_size)); - used.padding_right - .set(style.padding_right().to_px(containing_block_inline_size)); - let inline_size = self - .sizing() - .compute_inline_size_for_replaced_element(node, available_space, constraints); - self.used_mut(node).set_content_inline_size(inline_size); + inline_size } pub(crate) fn resolve_used_block_size_if_not_treated_as_auto( @@ -758,38 +769,8 @@ impl<'pass> BlockFormattingContext<'pass> { available_space: AvailableSpace, constraints: ContainingBlockConstraints, ) { - let sizing = self.sizing(); - if sizing.should_treat_block_size_as_auto(node, available_space, constraints) { - return; - } - let style = self.style(node); - let mut block_size = sizing.calculate_inner_block_size(node, available_space, style.height(), constraints); - if !sizing.should_treat_max_block_size_as_none(node, available_space.block_size, constraints) - && !style.max_height().is_auto() - { - block_size = block_size.min(sizing.calculate_inner_block_size( - node, - available_space, - style.max_height(), - constraints, - )); - } - if !style.min_height().is_auto() { - block_size = block_size.max(sizing.calculate_inner_block_size( - node, - available_space, - style.min_height(), - constraints, - )); - } - let used = self.used_mut(node); - used.set_content_block_size(block_size); - // A resolved used block size is not always a definite containing block size. - // Intrinsic sizing keywords like fit-content still depend on child layout, - // so percentage-sized descendants must continue to treat it as indefinite. - if !style.height().is_intrinsic_sizing_constraint() { - used.has_definite_block_size.set(true); - } + self.sizing() + .resolve_used_block_size_if_not_treated_as_auto(node, available_space, constraints); } pub(crate) fn resolve_used_block_size_if_treated_as_auto( @@ -799,87 +780,20 @@ impl<'pass> BlockFormattingContext<'pass> { constraints: ContainingBlockConstraints, child_automatic_block_size: Option, ) { - let sizing = self.sizing(); - if !sizing.should_treat_block_size_as_auto(node, available_space, constraints) { - return; - } - let style = self.style(node); - let facts = self.facts(node); - let mut block_size = if sizing.box_is_sized_as_replaced_element(node, available_space, constraints) { - sizing.compute_block_size_for_replaced_element(node, available_space, constraints) - } else { - child_automatic_block_size.unwrap_or_else(|| { + self.sizing().resolve_used_block_size_if_treated_as_auto( + node, + available_space, + constraints, + child_automatic_block_size, + || { self.compute_automatic_block_size_for_block_level_element( node, self.used(node) .available_inner_space_or_constraints_from(available_space), constraints, ) - }) - }; - if !sizing.should_treat_max_block_size_as_none(node, available_space.block_size, constraints) - && !style.max_height().is_auto() - { - block_size = block_size.min(sizing.calculate_inner_block_size( - node, - available_space, - style.max_height(), - constraints, - )); - } - if !style.min_height().is_auto() { - block_size = block_size.max(sizing.calculate_inner_block_size( - node, - available_space, - style.min_height(), - constraints, - )); - } - - if facts.document_in_quirks_mode() && facts.is_html_html_element() && style.height().is_auto() { - // 3.6. The html element fills the viewport quirk - // https://quirks.spec.whatwg.org/#the-html-element-fills-the-viewport-quirk - // FIXME: Handle vertical writing mode. - - // 1. Let margins be sum of the used values of the margin-left and margin-right properties of element - // if element has a vertical writing mode, otherwise let margins be the sum of the used values of - // the margin-top and margin-bottom properties of element. - let used = self.used(node); - let margins = used.margin_top.get() + used.margin_bottom.get(); - // 2. Let size be the size of the initial containing block in the block flow direction minus margins. - let size = constraints.block_basis() - margins; - // 3. Return the bigger value of size and the normal border box size the element would have - // according to the CSS specification. - block_size = block_size.max(size); - // NOTE: The block size of the root element when affected by this quirk is considered to be definite. - self.used_mut(node).has_definite_block_size.set(true); - } - - if facts.document_in_quirks_mode() && facts.is_html_body_element() && style.height().is_auto() { - // 3.7. The body element fills the html element quirk - // https://quirks.spec.whatwg.org/#the-body-element-fills-the-html-element-quirk - // FIXME: Handle vertical writing mode. - - // The element body must additionally meet the following conditions: - // - The computed value of the 'position' property of element is neither 'absolute' nor 'fixed'. - // - The computed value of the 'float' property of element is 'none'. - // - Element is not an inline-level element. - // - Element is not a multi-column spanning element. - // NON-STANDARD: We don't check column-span since no browser actually excludes it. - if !facts.is_absolutely_positioned() && !facts.is_floating() && !facts.is_inline() { - // 1. Let margins be sum of the used values of the margin-left and margin-right properties of element - // if element has a vertical writing mode, otherwise let margins be the sum of the used values of - // the margin-top and margin-bottom properties of element. - let used = self.used(node); - let margins = used.margin_top.get() + used.margin_bottom.get(); - // 2. Let size be the size of element's parent element's content box in the block flow direction minus margins. - let size = constraints.block_basis() - margins; - // 3. Return the bigger value of size and the normal border box size the element would have - // according to the CSS specification. - block_size = block_size.max(size); - } - } - self.used_mut(node).set_content_block_size(block_size); + }, + ); } fn band_index_at(&self, block_offset: CssPixels) -> usize { @@ -1198,6 +1112,7 @@ impl<'pass> BlockFormattingContext<'pass> { constraints: ContainingBlockConstraints, mut content_block_offset: CssPixels, containing_block_rect_in_root: BlockCssPixelRect, + probe: &mut FloatAvoidanceProbe, ) -> CssPixels { if !matches!(available_space.inline_size, AvailableSize::Definite(_)) || !self.box_should_avoid_floats_because_it_establishes_fc(node) { return content_block_offset; @@ -1207,6 +1122,11 @@ impl<'pass> BlockFormattingContext<'pass> { // place it adjacent to such floats if there is sufficient space. loop { let used = self.used(node); + let candidate_border_box_inline_size = probe + .content_inline_size + .unwrap_or_else(|| used.content_inline_size.get()) + + used.border_box_left(false) + + used.border_box_right(false); let border_box_block_offset_in_root = containing_block_rect_in_root.y + content_block_offset - used.border_box_top(false); let band_rect = BlockCssPixelRect { @@ -1218,13 +1138,13 @@ impl<'pass> BlockFormattingContext<'pass> { let constrained = space.left > CssPixels::default() || space.right > CssPixels::default(); let border_box_left = self.border_box_left_of_box_avoiding_floats(node, used, space); let mut must_clear = constrained - && border_box_left + used.border_box_inline_size(false) + && border_box_left + candidate_border_box_inline_size > available_space.inline_size.to_px_or_zero() - space.right; if !must_clear { let border_rect = BlockCssPixelRect { x: band_rect.x + border_box_left, y: border_box_block_offset_in_root, - width: used.border_box_inline_size(false), + width: candidate_border_box_inline_size, height: used.border_box_block_size(false), }; must_clear = self.floats.borrow().iter().any(|floating_box| { @@ -1248,8 +1168,16 @@ impl<'pass> BlockFormattingContext<'pass> { x: containing_block_rect_in_root.x, y: containing_block_rect_in_root.y + content_block_offset, }; - // Deliberately re-run inline sizing after every band descent. - self.compute_inline_size(node, available_space, constraints, position); + // Deliberately re-run inline sizing after every band descent, + // without committing: the winning candidate is what the run + // prelude reproduces from the float-avoidance directive. + probe.opportunity = self.float_reduced_inline_opportunity(node, available_space, position); + probe.content_inline_size = self.resolve_root_inline_metrics_and_content_size( + node, + available_space, + constraints, + probe.opportunity, + ); } content_block_offset } @@ -1379,6 +1307,7 @@ impl<'pass> BlockFormattingContext<'pass> { node: Node, available_space: AvailableSpace, content_position_in_root: FfiCssPixelPoint, + content_inline_size: CssPixels, ) -> CssPixels { let used = self.used(node); let mut inline_offset = CssPixels::default(); @@ -1388,7 +1317,7 @@ impl<'pass> BlockFormattingContext<'pass> { BlockCssPixelRect { x: content_position_in_root.x, y: content_position_in_root.y, - width: used.content_inline_size.get(), + width: content_inline_size, height: used.content_block_size.get(), } .into(), @@ -1403,11 +1332,11 @@ impl<'pass> BlockFormattingContext<'pass> { let containing_block = self.containing_block(node); let containing_text_align = self.style(containing_block).text_align(); if containing_text_align == text_align::_LIBWEB_CENTER { - inline_offset += available_inline_size_within_containing_block / 2 - used.content_inline_size.get() / 2; + inline_offset += available_inline_size_within_containing_block / 2 - content_inline_size / 2; } else if containing_text_align == text_align::_LIBWEB_RIGHT { // Subtracting the left margin here because left and right margins need to be swapped when aligning to the right inline_offset += available_inline_size_within_containing_block - - used.content_inline_size.get() + - content_inline_size - used.margin_left.get() - used.border_box_left(false); } else { @@ -1478,43 +1407,13 @@ impl<'pass> BlockFormattingContext<'pass> { fn layout_inside( &self, - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, node: Node, input: LayoutInput, force_independent_context_run: bool, - ) -> Option> { - let used = self.used(node); - let facts = self.facts(node); - // OPTIMIZATION: If we're doing intrinsic sizing and `child_box` has definite size in both axes, - // we don't need to layout its insides. The size is resolvable without learning - // the metrics of whatever's inside the box. - // - // https://drafts.csswg.org/css2/#propdef-vertical-align - // The baseline of an inline-block is the baseline of its last line box in the normal flow, unless it has - // either no in-flow line boxes or if its 'overflow' property has a computed value other than visible, in which - // case the baseline is the bottom margin edge. - // - // Inline-level boxes can contribute a baseline to their parent line box, so they still need their contents - // laid out even when their own intrinsic size is already definite. - if !force_independent_context_run - && self.layout_mode == LayoutMode::IntrinsicSizing - && !facts.is_inline() - && used.inline_size_constraint.get() == SizeConstraint::None - && used.block_size_constraint.get() == SizeConstraint::None - && used.has_definite_inline_size() - && used.has_definite_block_size() - { - return None; - } - let creates_replaced_context = matches!( - formatting_context_type_created_by_box(facts), - Some(FfiFormattingContextType::InternalReplaced | FfiFormattingContextType::ReplacedWithChildren) - ); - if !facts.can_have_children() && !creates_replaced_context { - return None; - } + ) -> Option { match crate::layout::layout_inside_child( - frame, + run, Some(self), None, node, @@ -1525,7 +1424,7 @@ impl<'pass> BlockFormattingContext<'pass> { crate::layout::ChildLayoutOutcome::Skipped => None, crate::layout::ChildLayoutOutcome::Created(child_layout) => Some(child_layout), crate::layout::ChildLayoutOutcome::ReenterCurrent => { - self.run(frame, input); + self.run(run, input); None } } @@ -1543,13 +1442,14 @@ impl<'pass> BlockFormattingContext<'pass> { .sizing() .constraints_for_child_context(containing_block, containing_input.containing_block_constraints), content_box_position_in_bfc_root: containing_input.content_box_position_in_bfc_root, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::BlockLevel, } } fn layout_block_level_box( &self, - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, node: Node, block_container: Node, bottom_of_lowest_margin_box: &mut CssPixels, @@ -1588,15 +1488,7 @@ impl<'pass> BlockFormattingContext<'pass> { } let block_container_inline_size = self.used(block_container).content_inline_size.get(); - if self.try_used_pointer(node).is_some() { - assert!( - self.state - .may_reuse_precreated_used_values(self.callbacks.slot_index(node)), - "block layout visited a box whose used values were already created" - ); - } else { - self.create_used_values(node, input.containing_block_constraints); - } + self.create_used_values(node, input.containing_block_constraints); self.resolve_vertical_box_model_metrics(node, block_container_inline_size); assert_eq!(self.containing_block(node), block_container); @@ -1617,7 +1509,7 @@ impl<'pass> BlockFormattingContext<'pass> { margin_state.current_collapsed_margin() } }; - self.layout_floating_box(frame, node, block_container, input, margin_top + block_offset, None); + self.layout_floating_box(run, node, input, margin_top + block_offset, None); if let Some(floating_box) = self.floats.borrow().last() { *bottom_of_lowest_margin_box = (*bottom_of_lowest_margin_box).max(floating_box.bottom_margin_edge); } @@ -1692,23 +1584,42 @@ impl<'pass> BlockFormattingContext<'pass> { y: containing_block_rect_in_root_now.y + content_block_offset, }; - self.compute_inline_size( + let opportunity = self.float_reduced_inline_opportunity( node, available_space, - input.containing_block_constraints, content_position_in_root_now(content_block_offset), ); + let mut probe = FloatAvoidanceProbe { + opportunity, + content_inline_size: self.resolve_root_inline_metrics_and_content_size( + node, + available_space, + input.containing_block_constraints, + opportunity, + ), + }; content_block_offset = self.avoid_float_intrusions( node, available_space, input.containing_block_constraints, content_block_offset, containing_block_rect_in_root_now, + &mut probe, ); + let float_avoidance_inline_size = probe.opportunity; + if !has_independent_formatting_context + && let Some(content_inline_size) = probe.content_inline_size + { + self.used_mut(node).set_content_inline_size(content_inline_size); + } + let content_inline_size_now = probe + .content_inline_size + .unwrap_or_else(|| self.used(node).content_inline_size.get()); let mut content_inline_offset = self.compute_normal_flow_inline_offset( node, available_space, content_position_in_root_now(content_block_offset), + content_inline_size_now, ); // FIXME: We currently do not support ListItemBoxes generated by pseudo-elements. We will need to, eventually. @@ -1726,17 +1637,17 @@ impl<'pass> BlockFormattingContext<'pass> { let is_table_formatting_context = independent_type == Some(FfiFormattingContextType::Table); let mut pending_position = None; + let mut table_box_content_offset_in_wrapper = None; if box_is_positioned_by_fieldset_layout { self.pending_legend_flow_position.set(Some(LogicalOffset { inline_offset: content_inline_offset, block_offset: content_block_offset, })); } else if is_table_formatting_context { - self.pending_table_box_content_offset_in_wrapper - .set(Some(LogicalOffset { - inline_offset: content_inline_offset, - block_offset: content_block_offset, - })); + table_box_content_offset_in_wrapper = Some(LogicalOffset { + inline_offset: content_inline_offset, + block_offset: content_block_offset, + }); } else if !box_opens_top_margin_group { pending_position = Some(FfiCssPixelPoint { x: content_inline_offset, @@ -1744,40 +1655,31 @@ impl<'pass> BlockFormattingContext<'pass> { }); } - let mut available_space_for_block_size_resolution = available_space; - let is_table_box = facts.is_table_row() - || facts.is_table_row_group() - || facts.is_table_header_group() - || facts.is_table_footer_group() - || facts.is_table_cell() - || facts.is_table_caption(); - // https://quirks.spec.whatwg.org/#the-percentage-height-calculation-quirk - if facts.document_in_quirks_mode() - && style.height().is_percentage() - && !is_table_box - && !facts.is_in_user_agent_shadow_tree() - { - available_space_for_block_size_resolution.block_size = AvailableSize::definite( - input - .containing_block_constraints - .quirks_mode_percentage_basis_block_size - .unwrap_or_default(), - ); - } - - self.resolve_used_block_size_if_not_treated_as_auto( + let available_space_for_block_size_resolution = self.sizing().available_space_for_block_size_resolution( node, - available_space_for_block_size_resolution, + available_space, input.containing_block_constraints, ); - // NOTE: Flex containers with an automatic block size are treated as max-content, so resolve it early. - if facts.has_auto_content_box_size() || style.display().is_flex_inside() { - self.resolve_used_block_size_if_treated_as_auto( + + // Whether a block size is treated as automatic can depend on the + // inline size being definite (aspect-ratio transfer), which an + // independent run only commits in its prelude — so independent + // children resolve these pre-body block sizes there instead. + if !has_independent_formatting_context { + self.resolve_used_block_size_if_not_treated_as_auto( node, available_space_for_block_size_resolution, input.containing_block_constraints, - None, ); + // NOTE: Flex containers with an automatic block size are treated as max-content, so resolve it early. + if facts.has_auto_content_box_size() || style.display().is_flex_inside() { + self.resolve_used_block_size_if_treated_as_auto( + node, + available_space_for_block_size_resolution, + input.containing_block_constraints, + None, + ); + } } // Before we insert the children of a list item we need to know the location of the marker. @@ -1793,7 +1695,7 @@ impl<'pass> BlockFormattingContext<'pass> { BlockCssPixelRect { x: content_position_in_root_now(content_block_offset).x + content_inline_offset, y: content_position_in_root_now(content_block_offset).y, - width: list_item_used.content_inline_size.get(), + width: content_inline_size_now, height: list_item_used.content_block_size.get(), } .into(), @@ -1806,59 +1708,31 @@ impl<'pass> BlockFormattingContext<'pass> { // Margins of elements that establish new formatting contexts do not collapse with their in-flow children self.margin_state.borrow_mut().reset(); - // This box establishes a new formatting context. Pass control to it. - let mut inner_available_space = self - .used(node) - .available_inner_space_or_constraints_from(available_space); - // For boxes with an automatic block size but non-auto min-height, determine whether the content block size is - // less than min-height. If so, run layout with min-height as the available block size. - let mut measured_content_block_size = None; - let sizing = self.sizing(); - if sizing.should_treat_block_size_as_auto(node, available_space, input.containing_block_constraints) - && !style.min_height().is_auto() - { - let content_block_size = sizing.measure_automatic_content_block_size( - node, - self.layout_mode, - inner_available_space, - input.containing_block_constraints, - ); - measured_content_block_size = Some(content_block_size); - let min_block_size = sizing.calculate_inner_block_size( - node, - available_space, - style.min_height(), - input.containing_block_constraints, - ); - if content_block_size < min_block_size { - inner_available_space.block_size = AvailableSize::definite(min_block_size); - } - } - self.sizing().make_button_content_box_definite( - node, - self.layout_mode, - available_space, - input.containing_block_constraints, - measured_content_block_size, - ); let inside_layout_input = LayoutInput { - available_space: inner_available_space, + available_space, containing_block_constraints: input.containing_block_constraints, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: if is_table_formatting_context { - input.table_grid_min_border_box_block_size - } else { - None + sizing: RootSizingDirectives { + forced_min_border_box_block_size: if is_table_formatting_context { + input.sizing.forced_min_border_box_block_size + } else { + None + }, + table_box_content_offset_in_wrapper, + float_avoidance_inline_size, + ..RootSizingDirectives::default() }, + participation: ParticipationInParentFormattingContext::BlockLevel, }; - let child_layout = self.layout_inside(frame, node, inside_layout_input, true); - if is_table_formatting_context { - let pending = self - .take_pending_table_box_content_offset_in_wrapper() - .expect("table layout did not publish its wrapper content offset"); + let child_layout = self.layout_inside(run, node, inside_layout_input, true); + if let Some(stashed_offset) = table_box_content_offset_in_wrapper { + let block_offset = child_layout + .as_ref() + .and_then(|child_layout| child_layout.table_block_offset_in_wrapper) + .unwrap_or(stashed_offset.block_offset); pending_position = Some(FfiCssPixelPoint { - x: pending.inline_offset, - y: pending.block_offset, + x: stashed_offset.inline_offset, + y: block_offset, }); } if container_facts.is_table_wrapper() && style.display().is_table_inside() { @@ -1866,13 +1740,6 @@ impl<'pass> BlockFormattingContext<'pass> { used.margin_left.set(used.margin_left.get().max(CssPixels::default())); used.margin_right.set(used.margin_right.get().max(CssPixels::default())); } - if facts.is_table_wrapper() - && !facts.is_grid_item() - && let Some(child_layout) = child_layout.as_ref() - { - self.used_mut(node) - .set_content_inline_size(child_layout.result().automatic_content_inline_size); - } child_layout } else { // This box participates in the current block container's flow. @@ -1900,9 +1767,9 @@ impl<'pass> BlockFormattingContext<'pass> { ..input }; if facts.children_are_inline() { - self.layout_inline_children(frame, node, inside_layout_input, space_available_for_children); + self.layout_inline_children(run, node, inside_layout_input, space_available_for_children); } else { - self.layout_block_level_children(frame, node, inside_layout_input, space_available_for_children); + self.layout_block_level_children(run, node, inside_layout_input, space_available_for_children); } if box_opens_top_margin_group { let resolved_margin_top = self.margin_state.borrow_mut().take_pending_top_margin(); @@ -1933,16 +1800,16 @@ impl<'pass> BlockFormattingContext<'pass> { None }; - // Tables already set their block size during the independent formatting context run. With multi-line text cells, - // using different available space here can produce different line breaks and therefore a different block size. - if !style.display().is_table_inside() { + // An independent run that actually executed resolved its automatic + // block size in its own epilogue; same-flow children and skipped + // inside layouts still resolve here. Tables set their block size + // during their run in every case. + if child_layout.is_none() && !style.display().is_table_inside() { self.resolve_used_block_size_if_treated_as_auto( node, available_space_for_block_size_resolution, input.containing_block_constraints, - child_layout - .as_ref() - .map(|child_layout| child_layout.result().automatic_content_block_size), + None, ); } @@ -1987,15 +1854,11 @@ impl<'pass> BlockFormattingContext<'pass> { let used = self.used(node); *bottom_of_lowest_margin_box = (*bottom_of_lowest_margin_box) .max(used.content_offset.get().y + used.content_block_size.get() + used.margin_box_bottom(false)); - - if let Some(child_layout) = child_layout { - child_layout.finish(); - } } fn layout_block_level_children( &self, - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, block_container: Node, input: LayoutInput, available_space_for_children: AvailableSpace, @@ -2019,7 +1882,7 @@ impl<'pass> BlockFormattingContext<'pass> { .replace(Some(CssPixels::default())); for child in self.children(block_container) { self.layout_block_level_box( - frame, + run, child, block_container, &mut bottom_of_lowest_margin_box, @@ -2067,7 +1930,7 @@ impl<'pass> BlockFormattingContext<'pass> { } // https://html.spec.whatwg.org/multipage/rendering.html#the-fieldset-and-legend-elements - fn layout_fieldset_with_rendered_legend(&self, frame: &mut FcFrame<'pass>, fieldset: Node, input: LayoutInput) { + fn layout_fieldset_with_rendered_legend(&self, run: &FormattingContextRun<'pass>, fieldset: Node, input: LayoutInput) { let available_space = input.available_space; let child_input = self.child_layout_input(fieldset, input, available_space); let legend = self.facts(fieldset).rendered_legend(); @@ -2079,7 +1942,7 @@ impl<'pass> BlockFormattingContext<'pass> { .block_offset_of_current_block_container .replace(Some(CssPixels::default())); let mut dummy_bottom = CssPixels::default(); - self.layout_block_level_box(frame, legend, fieldset, &mut dummy_bottom, child_input); + self.layout_block_level_box(run, legend, fieldset, &mut dummy_bottom, child_input); self.block_offset_of_current_block_container.set(saved); } @@ -2111,7 +1974,7 @@ impl<'pass> BlockFormattingContext<'pass> { let saved = self.block_offset_of_current_block_container.replace(Some(extra_top)); for child in self.children(fieldset) { if child != legend { - self.layout_block_level_box(frame, child, fieldset, &mut bottom_of_lowest_margin_box, child_input); + self.layout_block_level_box(run, child, fieldset, &mut bottom_of_lowest_margin_box, child_input); } } self.block_offset_of_current_block_container.set(saved); @@ -2211,7 +2074,7 @@ impl<'pass> BlockFormattingContext<'pass> { } } - pub(crate) fn run(&self, frame: &mut FcFrame<'pass>, input: LayoutInput) { + pub(crate) fn run(&self, run: &FormattingContextRun<'pass>, input: LayoutInput) { let available_space = input.available_space; // https://drafts.csswg.org/css-multicol-2/#the-multi-column-model let root_inline_size = self.used(self.root).content_inline_size.get(); @@ -2233,13 +2096,13 @@ impl<'pass> BlockFormattingContext<'pass> { }; let root_facts = self.facts(self.root); if root_facts.is_fieldset_box() && !root_facts.rendered_legend().is_invalid() { - self.layout_fieldset_with_rendered_legend(frame, self.root, root_input); + self.layout_fieldset_with_rendered_legend(run, self.root, root_input); return; } if root_facts.children_are_inline() { - self.layout_inline_children(frame, self.root, root_input, available_space); + self.layout_inline_children(run, self.root, root_input, available_space); } else { - self.layout_block_level_children(frame, self.root, root_input, available_space); + self.layout_block_level_children(run, self.root, root_input, available_space); } // Fieldsets without a rendered legend skip collapsed margin assignment. @@ -2267,8 +2130,7 @@ impl<'pass> BlockFormattingContext<'pass> { } } - pub(crate) fn parent_context_did_dimension_child_root_box(&self) { - self.was_notified_after_parent_dimensioned_my_root_box.set(true); + pub(crate) fn place_floats_after_run(&self) { let floats = self.floats.borrow(); for &floating_box in floats.iter() { // SAFETY: Float records retain stable state-owned used-values pointers. @@ -2298,21 +2160,9 @@ impl<'pass> BlockFormattingContext<'pass> { } } - pub(crate) fn was_notified_after_parent_dimensioned_root(&self) -> bool { - self.was_notified_after_parent_dimensioned_my_root_box.get() - } - - pub(crate) fn take_pending_table_box_content_offset_in_wrapper(&self) -> Option { - self.pending_table_box_content_offset_in_wrapper.take() - } - - pub(crate) fn set_pending_table_box_content_offset_in_wrapper(&self, offset: LogicalOffset) { - self.pending_table_box_content_offset_in_wrapper.set(Some(offset)); - } - pub(crate) fn layout_table_caption( &self, - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, table_box: Node, caption: Node, phase: CaptionPhase, @@ -2320,7 +2170,6 @@ impl<'pass> BlockFormattingContext<'pass> { constraints: ContainingBlockConstraints, ) -> CaptionLayoutResult { let mut caption_was_placed = false; - let mut child_layout = None; if formatting_context_type_created_by_box(self.facts(caption)).is_some() { let mut inner_available_space = available_space; let is_block_context = @@ -2341,14 +2190,15 @@ impl<'pass> BlockFormattingContext<'pass> { } } - child_layout = self.layout_inside( - frame, + let child_layout = self.layout_inside( + run, caption, LayoutInput { available_space: inner_available_space, containing_block_constraints: constraints, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Item, }, true, ); @@ -2362,7 +2212,7 @@ impl<'pass> BlockFormattingContext<'pass> { let content_block_size = if self.style(caption).has_size_containment() { CssPixels::default() } else { - child_layout.result().automatic_content_block_size + child_layout.automatic_content_block_size }; self.used_mut(caption).set_content_block_size(content_block_size); } @@ -2388,23 +2238,19 @@ impl<'pass> BlockFormattingContext<'pass> { }, ); } - let result = CaptionLayoutResult { + CaptionLayoutResult { margin_box_block_size: self.used(caption).margin_box_block_size(false), pending_table_block_offset: if phase == CaptionPhase::Top { self.used(caption).content_block_size.get() + self.used(caption).margin_box_bottom(false) } else { CssPixels::default() }, - }; - if let Some(child_layout) = child_layout { - child_layout.finish(); } - result } pub(crate) fn layout_interrupting_block_inside_inline_context( &self, - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, node: Node, containing_block: Node, input: LayoutInput, @@ -2415,7 +2261,7 @@ impl<'pass> BlockFormattingContext<'pass> { .block_offset_of_current_block_container .replace(Some(current_block_offset)); let mut dummy_bottom = CssPixels::default(); - self.layout_block_level_box(frame, node, containing_block, &mut dummy_bottom, input); + self.layout_block_level_box(run, node, containing_block, &mut dummy_bottom, input); // SAFETY: The builder remains live and no reference escaped. let block_bottom = self .block_offset_of_current_block_container @@ -2436,17 +2282,40 @@ impl<'pass> BlockFormattingContext<'pass> { ) } - pub(crate) fn layout_floating_box( - &self, - frame: &mut FcFrame<'pass>, - node: Node, - block_container: Node, - input: LayoutInput, - block_offset: CssPixels, - mut line_builder: Option<&mut LineBuilder<'_, '_, '_>>, - ) { + // Run-prelude inline sizing for a block-level root: reproduces the + // parent's winning float-avoidance probe candidate from the directive + // carried by the input, and commits it. + pub(crate) fn commit_block_level_root_inline_size(&self, node: Node, input: &LayoutInput) { + if let Some(content_inline_size) = self.resolve_root_inline_metrics_and_content_size( + node, + input.available_space, + input.containing_block_constraints, + input.sizing.float_avoidance_inline_size, + ) { + self.used_mut(node).set_content_inline_size(content_inline_size); + } + } + + pub(crate) fn resolve_block_level_root_block_size_before_body(&self, node: Node, input: &LayoutInput) { + let resolution_space = self.sizing().available_space_for_block_size_resolution( + node, + input.available_space, + input.containing_block_constraints, + ); + self.resolve_used_block_size_if_not_treated_as_auto(node, resolution_space, input.containing_block_constraints); + if self.facts(node).has_auto_content_box_size() || self.style(node).display().is_flex_inside() { + self.resolve_used_block_size_if_treated_as_auto( + node, + resolution_space, + input.containing_block_constraints, + None, + ); + } + } + + pub(crate) fn dimension_float_root(&self, node: Node, input: &LayoutInput) { let available_space = input.available_space; - assert!(self.facts(node).is_floating()); + let block_container = self.containing_block(node); let block_container_inline_size = self.used(block_container).content_inline_size.get(); self.resolve_vertical_box_model_metrics(node, block_container_inline_size); let containing_block_rect = self.containing_block_rect( @@ -2469,8 +2338,7 @@ impl<'pass> BlockFormattingContext<'pass> { }, ); self.resolve_used_block_size_if_not_treated_as_auto(node, available_space, input.containing_block_constraints); - let facts = self.facts(node); - if facts.has_auto_content_box_size() || self.style(node).display().is_flex_inside() { + if self.facts(node).has_auto_content_box_size() || self.style(node).display().is_flex_inside() { self.resolve_used_block_size_if_treated_as_auto( node, available_space, @@ -2478,35 +2346,61 @@ impl<'pass> BlockFormattingContext<'pass> { None, ); } - let inner = self - .used(node) - .available_inner_space_or_constraints_from(available_space); - let child_layout = self.layout_inside( - frame, - node, - LayoutInput { - available_space: inner, - containing_block_constraints: input.containing_block_constraints, - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }, - false, - ); + } + + pub(crate) fn finalize_float_root( + &self, + node: Node, + input: &LayoutInput, + run_automatic_sizes: Option<(CssPixels, CssPixels)>, + ) { // A floating table wrapper shrink-to-fits from cached intrinsic sizes, which may not match // the inline size table layout just produced; the wrapper has the same inline size as the table grid box. - if facts.is_table_wrapper() - && let Some(child_layout) = child_layout.as_ref() + if self.facts(node).is_table_wrapper() + && let Some((automatic_content_inline_size, _)) = run_automatic_sizes { - self.used_mut(node) - .set_content_inline_size(child_layout.result().automatic_content_inline_size); + self.used_mut(node).set_content_inline_size(automatic_content_inline_size); } self.resolve_used_block_size_if_treated_as_auto( node, - available_space, + input.available_space, input.containing_block_constraints, - child_layout - .as_ref() - .map(|child_layout| child_layout.result().automatic_content_block_size), + run_automatic_sizes.map(|(_, automatic_content_block_size)| automatic_content_block_size), + ); + } + + pub(crate) fn layout_floating_box( + &self, + run: &FormattingContextRun<'pass>, + node: Node, + input: LayoutInput, + block_offset: CssPixels, + mut line_builder: Option<&mut LineBuilder<'_, '_, '_>>, + ) { + let available_space = input.available_space; + assert!(self.facts(node).is_floating()); + let block_container = self.containing_block(node); + let _ = self.layout_inside( + run, + node, + LayoutInput { + available_space, + containing_block_constraints: input.containing_block_constraints, + content_box_position_in_bfc_root: input.content_box_position_in_bfc_root, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Float, + }, + false, + ); + let containing_block_rect = self.containing_block_rect( + block_container, + input + .content_box_position_in_bfc_root + .expect("float layout requires its containing block position in the BFC root"), + ); + let containing_block_rect_now = containing_block_rect.translated( + CssPixels::default(), + self.block_offset_adjustment_from_pending_ancestor_block_start_margins(block_container), ); // Next, float to the left and/or right @@ -2520,9 +2414,6 @@ impl<'pass> BlockFormattingContext<'pass> { None }; let Some(side) = side else { - if let Some(child_layout) = child_layout { - child_layout.finish(); - } return; }; let mut margin_box_ceiling = if let Some(line_builder) = line_builder.as_deref_mut() { @@ -2598,14 +2489,11 @@ impl<'pass> BlockFormattingContext<'pass> { block_size: block_container_used.content_block_size.get(), }, ); - if let Some(child_layout) = child_layout { - child_layout.finish(); - } } fn layout_inline_children( &self, - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, block_container: Node, input: LayoutInput, available_space_for_children: AvailableSpace, @@ -2613,7 +2501,7 @@ impl<'pass> BlockFormattingContext<'pass> { assert!(self.facts(block_container).children_are_inline()); let inline_input = self.child_layout_input(block_container, input, available_space_for_children); let mut context = InlineFormattingContext::new_with_rust_parent( - frame, + run, self.state, block_container, self.layout_mode, @@ -2675,43 +2563,20 @@ impl<'pass> BlockFormattingContext<'pass> { } } - fn compute_automatic_block_size_for_block_level_element( + pub(crate) fn compute_automatic_block_size_for_block_level_element( &self, node: Node, available_space: AvailableSpace, constraints: ContainingBlockConstraints, ) -> CssPixels { let facts = self.facts(node); - if facts.creates_block_formatting_context() { - return automatic_block_size_for_bfc_root(self.state, self.callbacks, node); - } let style = self.style(node); - let sizing = self.sizing(); - if style.display().is_flex_inside() { - // https://drafts.csswg.org/css-flexbox-1/#algo-main-container - // NOTE: The automatic block size of a block-level flex container is its max-content size. - return sizing.calculate_max_content_block_size( - node, - available_space.inline_size.to_px_or_zero(), - constraints, - ); - } - if style.display().is_grid_inside() { - // https://www.w3.org/TR/css-grid-2/#intrinsic-sizes - // In both inline and block formatting contexts, the grid container’s auto block size is its - // max-content size. - return sizing.calculate_max_content_block_size( - node, - available_space.inline_size.to_px_or_zero(), - constraints, - ); - } - if style.display().is_table_inside() { - return sizing.calculate_max_content_block_size( - node, - available_space.inline_size.to_px_or_zero(), - constraints, - ); + if facts.creates_block_formatting_context() + || style.display().is_flex_inside() + || style.display().is_grid_inside() + || style.display().is_table_inside() + { + return independent_root_automatic_block_size(self.state, &self.callbacks, node, available_space, constraints); } // https://www.w3.org/TR/CSS22/visudet.html#normal-block diff --git a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs index 018315b9bbf74..38513779d713d 100644 --- a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs @@ -186,15 +186,15 @@ struct FlexFormattingContext<'pass> { } impl<'pass> FlexFormattingContext<'pass> { - fn new(frame: &FcFrame<'pass>) -> Self { - let flex_container_state = frame.state.used_values(&frame.callbacks, frame.box_); - let flex_direction = frame.state.style_facts(&frame.callbacks, frame.box_).flex_direction(); + fn new(run: &FormattingContextRun<'pass>) -> Self { + let flex_container_state = run.state.used_values(&run.callbacks, run.box_); + let flex_direction = run.state.style_facts(&run.callbacks, run.box_).flex_direction(); Self { - state: frame.state, - flex_container: frame.box_, - layout_mode: frame.layout_mode, - callbacks: frame.callbacks, - should_collect_devtools_layout_data: frame.should_collect_devtools_layout_data, + state: run.state, + flex_container: run.box_, + layout_mode: run.layout_mode, + callbacks: run.callbacks, + should_collect_devtools_layout_data: run.should_collect_devtools_layout_data, flex_container_state, flex_lines: Vec::new(), flex_items: Vec::new(), @@ -2350,7 +2350,7 @@ impl<'pass> FlexFormattingContext<'pass> { } } - fn layout_inside_item(&mut self, frame: &mut FcFrame<'pass>, index: usize) { + fn layout_inside_item(&mut self, run: &FormattingContextRun<'pass>, index: usize) { let node = self.flex_items[index].box_; let mut input = LayoutInput { available_space: self @@ -2358,7 +2358,8 @@ impl<'pass> FlexFormattingContext<'pass> { .available_inner_space_or_constraints_from(self.available_space_for_items.unwrap().space), containing_block_constraints: self.item_containing_block_constraints(), content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Item, }; // https://drafts.csswg.org/css-flexbox-1/#flex-items // In the case of flex items with display: table, the table wrapper box becomes the flex item, @@ -2381,12 +2382,12 @@ impl<'pass> FlexFormattingContext<'pass> { ); let extra = (self.flex_items[index].cross_size.unwrap() - self.flex_items[index].hypothetical_cross_size) .max(CssPixels::default()); - input.table_grid_min_border_box_block_size = Some(intrinsic_size + extra); + input.sizing.forced_min_border_box_block_size = Some(intrinsic_size + extra); } - match crate::layout::layout_inside_child(frame, None, None, node, LayoutMode::Normal, input, false) { - crate::layout::ChildLayoutOutcome::Created(child_layout) => child_layout.finish(), - crate::layout::ChildLayoutOutcome::ReenterCurrent => self.run(frame, input), + match crate::layout::layout_inside_child(run, None, None, node, LayoutMode::Normal, input, false) { + crate::layout::ChildLayoutOutcome::Created(_) => {} + crate::layout::ChildLayoutOutcome::ReenterCurrent => self.run(run, input), crate::layout::ChildLayoutOutcome::Skipped => {} } @@ -2915,7 +2916,7 @@ impl<'pass> FlexFormattingContext<'pass> { sum } - fn run(&mut self, frame: &mut FcFrame<'pass>, layout_input: LayoutInput) { + fn run(&mut self, run: &FormattingContextRun<'pass>, layout_input: LayoutInput) { let available_space = layout_input.available_space; // This implements https://www.w3.org/TR/css-flexbox-1/#layout-algorithm @@ -3098,7 +3099,7 @@ impl<'pass> FlexFormattingContext<'pass> { // AD-HOC: Finally, layout the inside of all flex items. self.copy_dimensions_from_flex_items_to_boxes(); for index in 0..self.flex_items.len() { - self.layout_inside_item(frame, index); + self.layout_inside_item(run, index); } self.resolve_baseline_aligned_items(); for index in 0..self.flex_items.len() { diff --git a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs index d87d0e7a0ea45..7450d727c9988 100644 --- a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs @@ -4,7 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ - const CALC_NUMERIC_KIND_LENGTH: u8 = 4; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -268,21 +267,17 @@ impl MeasurementState { ) -> crate::layout::ChildLayoutResult { let rust_state = self.rust_state(); let fc_type = crate::layout::independent_formatting_context_type(rust_state, node, &self.callbacks); - let mut context = crate::layout::create_formatting_context( + crate::layout::run_formatting_context( rust_state, node, - crate::layout::FcParents::default(), + None, fc_type, layout_mode, false, self.callbacks, - ); - crate::layout::run_formatting_context(&mut context, input, None); - complete_formatting_context_after_root_box_has_used_size(&mut context); - crate::layout::ChildLayoutResult { - automatic_content_inline_size: context.automatic_content_inline_size, - automatic_content_block_size: context.automatic_content_block_size, - } + input, + None, + ) } pub(crate) fn rust_state(&self) -> &LayoutState { @@ -729,28 +724,12 @@ pub struct FfiBordersData { pub(crate) struct ChildLayoutResult { pub automatic_content_inline_size: CssPixels, pub automatic_content_block_size: CssPixels, + pub table_block_offset_in_wrapper: Option, } -pub(crate) struct PendingChildLayout<'pass> { - context: Box>, -} - -impl PendingChildLayout<'_> { - pub(crate) fn result(&self) -> ChildLayoutResult { - ChildLayoutResult { - automatic_content_inline_size: self.context.automatic_content_inline_size, - automatic_content_block_size: self.context.automatic_content_block_size, - } - } - - pub(crate) fn finish(mut self) { - complete_formatting_context_after_root_box_has_used_size(&mut self.context); - } -} - -pub(crate) enum ChildLayoutOutcome<'pass> { +pub(crate) enum ChildLayoutOutcome { Skipped, - Created(PendingChildLayout<'pass>), + Created(ChildLayoutResult), ReenterCurrent, } @@ -1054,17 +1033,15 @@ impl FfiLayoutFcCallbacks { } } -pub(crate) struct FcFrame<'pass> { +pub(crate) struct FormattingContextRun<'pass> { pub(crate) state: &'pass LayoutState, pub(crate) box_: Node, pub(crate) layout_mode: LayoutMode, pub(crate) callbacks: FfiLayoutFcCallbacks, pub(crate) should_collect_devtools_layout_data: bool, - pub(crate) automatic_content_inline_size: CssPixels, - pub(crate) automatic_content_block_size: CssPixels, } -impl<'pass> FcFrame<'pass> { +impl<'pass> FormattingContextRun<'pass> { pub(crate) fn new( state: &'pass LayoutState, box_: Node, @@ -1078,19 +1055,11 @@ impl<'pass> FcFrame<'pass> { layout_mode, callbacks, should_collect_devtools_layout_data, - automatic_content_inline_size: CssPixels::default(), - automatic_content_block_size: CssPixels::default(), } } } -#[derive(Clone, Copy, Default)] -struct FcParents<'parent, 'pass> { - block: Option<&'parent BlockFormattingContext<'pass>>, - grid: Option<&'parent GridFormattingContext<'pass>>, -} - -enum FcImpl<'pass> { +enum FormattingContextImplementation<'pass> { Block(Box>), Flex(Box>), Grid(Box>), @@ -1101,25 +1070,6 @@ enum FcImpl<'pass> { InternalDummy, } -pub(crate) struct FormattingContextInstance<'pass> { - pub(crate) frame: FcFrame<'pass>, - implementation: FcImpl<'pass>, -} - -impl<'pass> std::ops::Deref for FormattingContextInstance<'pass> { - type Target = FcFrame<'pass>; - - fn deref(&self) -> &Self::Target { - &self.frame - } -} - -impl std::ops::DerefMut for FormattingContextInstance<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.frame - } -} - pub(crate) fn formatting_context_type_created_by_node_data( data: &NodeData, style: Option>, @@ -1218,60 +1168,51 @@ pub extern "C" fn rust_layout_formatting_context_type_for_box(facts: FfiFormatti }) } -fn create_formatting_context<'pass>( - state: &'pass LayoutState, - box_: Node, - parents: FcParents<'_, 'pass>, +fn create_formatting_context_implementation<'pass>( + run: &FormattingContextRun<'pass>, + parent_grid: Option<&GridFormattingContext<'pass>>, fc_type: FfiFormattingContextType, - layout_mode: LayoutMode, - should_collect_devtools_layout_data: bool, - callbacks: FfiLayoutFcCallbacks, -) -> Box> { - assert!(!box_.is_invalid()); - - let frame = FcFrame::new(state, box_, layout_mode, callbacks, should_collect_devtools_layout_data); - let implementation = match fc_type { - FfiFormattingContextType::Block => FcImpl::Block(Box::new( - BlockFormattingContext::new(state, box_, layout_mode, callbacks), - )), - FfiFormattingContextType::Flex => { - FcImpl::Flex(Box::new(FlexFormattingContext::new(&frame))) - } - FfiFormattingContextType::Grid => FcImpl::Grid(Box::new(GridFormattingContext::new( - state, - box_, - parents.grid, - layout_mode, - callbacks, - should_collect_devtools_layout_data, +) -> FormattingContextImplementation<'pass> { + match fc_type { + FfiFormattingContextType::Block => FormattingContextImplementation::Block(Box::new(BlockFormattingContext::new( + run.state, + run.box_, + run.layout_mode, + run.callbacks, ))), - FfiFormattingContextType::Table => { - let pending_table_offset = parents - .block - .and_then(|parent| parent.take_pending_table_box_content_offset_in_wrapper()); - FcImpl::Table(Box::new(TableFormattingContext::new(&frame, pending_table_offset))) - } - FfiFormattingContextType::Svg => { - FcImpl::Svg(Box::new(SvgFormattingContext::new(state, box_, layout_mode, callbacks))) - } - FfiFormattingContextType::ReplacedWithChildren => FcImpl::ReplacedWithChildren, - FfiFormattingContextType::InternalReplaced => FcImpl::InternalReplaced, - FfiFormattingContextType::InternalDummy => FcImpl::InternalDummy, + FfiFormattingContextType::Flex => FormattingContextImplementation::Flex(Box::new(FlexFormattingContext::new(run))), + FfiFormattingContextType::Grid => FormattingContextImplementation::Grid(Box::new(GridFormattingContext::new( + run.state, + run.box_, + parent_grid, + run.layout_mode, + run.callbacks, + run.should_collect_devtools_layout_data, + ))), + FfiFormattingContextType::Table => FormattingContextImplementation::Table(Box::new(TableFormattingContext::new(run))), + FfiFormattingContextType::Svg => FormattingContextImplementation::Svg(Box::new(SvgFormattingContext::new( + run.state, + run.box_, + run.layout_mode, + run.callbacks, + ))), + FfiFormattingContextType::ReplacedWithChildren => FormattingContextImplementation::ReplacedWithChildren, + FfiFormattingContextType::InternalReplaced => FormattingContextImplementation::InternalReplaced, + FfiFormattingContextType::InternalDummy => FormattingContextImplementation::InternalDummy, FfiFormattingContextType::Inline => panic!("no Rust implementation for inline formatting contexts"), - }; - Box::new(FormattingContextInstance { frame, implementation }) + } } -fn register_table_abspos_descendants(frame: &mut FcFrame, parent: Node) { - let mut child = frame.callbacks.first_child(parent); +fn register_table_abspos_descendants(run: &FormattingContextRun, parent: Node) { + let mut child = run.callbacks.first_child(parent); while !child.is_invalid() { - let next = frame.callbacks.next_sibling(child); - let facts = frame.state.node_facts(&frame.callbacks, child); + let next = run.callbacks.next_sibling(child); + let facts = run.state.node_facts(&run.callbacks, child); if facts.is_box() { if facts.is_absolutely_positioned() { register_contained_abspos_child( - frame.state, - &frame.callbacks, + run.state, + &run.callbacks, child, StaticPositionRect { rect: Default::default(), @@ -1282,111 +1223,385 @@ fn register_table_abspos_descendants(frame: &mut FcFrame, parent: Node) { ); } if formatting_context_type_created_by_box(facts).is_none() { - register_table_abspos_descendants(frame, child); + register_table_abspos_descendants(run, child); } } else { - register_table_abspos_descendants(frame, child); + register_table_abspos_descendants(run, child); } child = next; } } -fn complete_formatting_context_after_root_box_has_used_size(instance: &mut FormattingContextInstance) { - if let FcImpl::Block(context) = &instance.implementation { - context.parent_context_did_dimension_child_root_box(); - } - let registered_abspos_children_could_never_be_laid_out = - instance.layout_mode != LayoutMode::Normal || instance.frame.state.is_measurement(); - if registered_abspos_children_could_never_be_laid_out { - return; +pub(crate) fn independent_root_automatic_block_size( + state: &LayoutState, + callbacks: &FfiLayoutFcCallbacks, + node: Node, + available_inner_space: AvailableSpace, + constraints: ContainingBlockConstraints, +) -> CssPixels { + let facts = state.node_facts(callbacks, node); + if facts.creates_block_formatting_context() { + return automatic_block_size_for_bfc_root(state, *callbacks, node); + } + let style = state.style_facts(callbacks, node); + if style.display().is_flex_inside() || style.display().is_grid_inside() || style.display().is_table_inside() { + // The automatic block size of a flex, grid, or table container is its + // max-content size. + // https://drafts.csswg.org/css-flexbox-1/#algo-main-container + // https://www.w3.org/TR/css-grid-2/#intrinsic-sizes + return SizingContext::new(state, *callbacks).calculate_max_content_block_size( + node, + available_inner_space.inline_size.to_px_or_zero(), + constraints, + ); } - match &instance.implementation { - FcImpl::Block(_) => {} - FcImpl::Table(_) => { - let box_ = instance.frame.box_; - register_table_abspos_descendants(&mut instance.frame, box_); + debug_assert!(false, "independent formatting context root of unexpected kind"); + CssPixels::default() +} + +fn apply_root_sizing_directives( + run: &FormattingContextRun, + input: &LayoutInput, + parent_block: Option<&BlockFormattingContext>, +) -> LayoutInput { + match input.participation { + ParticipationInParentFormattingContext::BlockLevel => dimension_block_level_root(run, input, parent_block), + ParticipationInParentFormattingContext::Float => { + let parent = parent_block.expect("a floating run requires an enclosing block formatting context"); + parent.dimension_float_root(run.box_, input); + body_input_with_inner_available_space(run, input) } - FcImpl::Flex(context) => { - context.parent_did_dimension(); + ParticipationInParentFormattingContext::AtomicInline => { + SizingContext::new(run.state, run.callbacks).dimension_atomic_root( + run.box_, + input.available_space, + input.containing_block_constraints, + run.layout_mode, + ); + body_input_with_inner_available_space(run, input) } - FcImpl::Grid(context) => { - context.parent_did_dimension(); + ParticipationInParentFormattingContext::AbsolutelyPositioned(abspos_inputs) => { + AbsposEngine::new(run.state, run.callbacks).dimension_out_of_flow_root(run.box_, abspos_inputs); + body_input_with_inner_available_space(run, input) + } + ParticipationInParentFormattingContext::Item => { + if cfg!(debug_assertions) && run.layout_mode == LayoutMode::Normal && !run.state.is_measurement() { + let used = run.state.try_used_values(&run.callbacks, run.box_); + debug_assert!( + used.is_some_and(|used| used.has_definite_inline_size.get()), + "container-internal run root must arrive with a container-assigned inline size" + ); + } + *input + } + ParticipationInParentFormattingContext::Root => { + let directives = input.sizing; + if directives.forced_content_inline_size.is_some() || directives.forced_content_block_size.is_some() { + let used = run.state.used_values(&run.callbacks, run.box_); + if let Some(inline_size) = directives.forced_content_inline_size { + used.set_content_inline_size(inline_size); + } + if let Some(block_size) = directives.forced_content_block_size { + used.set_content_block_size(block_size); + used.has_definite_block_size.set(true); + } + } + *input } - FcImpl::Svg(_) | FcImpl::ReplacedWithChildren => {} - FcImpl::InternalReplaced | FcImpl::InternalDummy => return, } - let box_ = instance.box_; - if instance.frame.state.abspos_layout_pass_is_active() { - layout_contained_abspos_children(&mut instance.frame); - } else { - instance.frame.state.enqueue_for_abspos_layout_pass(box_); +} + +fn dimension_block_level_root( + run: &FormattingContextRun, + input: &LayoutInput, + parent_block: Option<&BlockFormattingContext>, +) -> LayoutInput { + let node = run.box_; + let available_space = input.available_space; + let constraints = input.containing_block_constraints; + let parent = parent_block.expect("a block-level run requires an enclosing block formatting context"); + parent.commit_block_level_root_inline_size(node, input); + parent.resolve_block_level_root_block_size_before_body(node, input); + let sizing = SizingContext::new(run.state, run.callbacks); + let style = run.state.style_facts(&run.callbacks, node); + let mut body_input = body_input_with_inner_available_space(run, input); + let mut measured_content_block_size = None; + if sizing.should_treat_block_size_as_auto(node, available_space, constraints) && !style.min_height().is_auto() { + let content_block_size = + sizing.measure_automatic_content_block_size(node, run.layout_mode, body_input.available_space, constraints); + measured_content_block_size = Some(content_block_size); + let min_block_size = sizing.calculate_inner_block_size(node, available_space, style.min_height(), constraints); + if content_block_size < min_block_size { + body_input.available_space.block_size = AvailableSize::definite(min_block_size); + } } + sizing.make_button_content_box_definite( + node, + run.layout_mode, + available_space, + constraints, + measured_content_block_size, + ); + body_input } -impl Drop for FormattingContextInstance<'_> { - fn drop(&mut self) { - if let FcImpl::Block(context) = &self.implementation { - debug_assert!( - context.was_notified_after_parent_dimensioned_root(), - "block formatting context dropped without being completed" - ); +fn finalize_block_level_root( + run: &FormattingContextRun, + input: &LayoutInput, + parent_block: Option<&BlockFormattingContext>, + body_result: &ChildLayoutResult, +) { + let node = run.box_; + let facts = run.state.node_facts(&run.callbacks, node); + if facts.is_table_wrapper() { + run + .state + .used_values(&run.callbacks, node) + .set_content_inline_size(body_result.automatic_content_inline_size); + } + let style = run.state.style_facts(&run.callbacks, node); + if !style.display().is_table_inside() { + let parent = parent_block.expect("a block-level run requires an enclosing block formatting context"); + let resolution_space = parent.sizing().available_space_for_block_size_resolution( + node, + input.available_space, + input.containing_block_constraints, + ); + parent.resolve_used_block_size_if_treated_as_auto( + node, + resolution_space, + input.containing_block_constraints, + Some(body_result.automatic_content_block_size), + ); + } +} + +fn size_skipped_independent_root( + run: &FormattingContextRun, + parent_block: Option<&BlockFormattingContext>, + child: Node, + input: &LayoutInput, +) { + match input.participation { + ParticipationInParentFormattingContext::BlockLevel => { + let parent = parent_block.expect("a block-level run requires an enclosing block formatting context"); + parent.commit_block_level_root_inline_size(child, input); + parent.resolve_block_level_root_block_size_before_body(child, input); + } + ParticipationInParentFormattingContext::Float => { + let parent = parent_block.expect("a floating run requires an enclosing block formatting context"); + parent.dimension_float_root(child, input); + parent.finalize_float_root(child, input, None); + } + ParticipationInParentFormattingContext::AbsolutelyPositioned(abspos_inputs) => { + let engine = AbsposEngine::new(run.state, run.callbacks); + engine.dimension_out_of_flow_root(child, abspos_inputs); + engine.finalize_out_of_flow_root_after_inside_layout(child, abspos_inputs); } + ParticipationInParentFormattingContext::AtomicInline | ParticipationInParentFormattingContext::Item | ParticipationInParentFormattingContext::Root => {} } } +fn body_input_with_inner_available_space(run: &FormattingContextRun, input: &LayoutInput) -> LayoutInput { + let inner_available_space = run + .state + .used_values(&run.callbacks, run.box_) + .available_inner_space_or_constraints_from(input.available_space); + let mut body_input = *input; + body_input.available_space = inner_available_space; + body_input +} + +#[expect(clippy::too_many_arguments)] fn run_formatting_context<'pass>( - instance: &mut FormattingContextInstance<'pass>, + state: &'pass LayoutState, + box_: Node, + parent_grid: Option<&GridFormattingContext<'pass>>, + fc_type: FfiFormattingContextType, + layout_mode: LayoutMode, + should_collect_devtools_layout_data: bool, + callbacks: FfiLayoutFcCallbacks, input: LayoutInput, parent_block: Option<&BlockFormattingContext<'pass>>, -) { - let FormattingContextInstance { frame, implementation } = instance; - match implementation { - FcImpl::Block(context) => { - context.run(frame, input); - frame.automatic_content_inline_size = context.automatic_content_inline_size(); - frame.automatic_content_block_size = context.automatic_content_block_size(); +) -> ChildLayoutResult { + assert!(!box_.is_invalid()); + let run = FormattingContextRun::new(state, box_, layout_mode, callbacks, should_collect_devtools_layout_data); + let run = &run; + let body_input = apply_root_sizing_directives(run, &input, parent_block); + + let cached_atomic_block_size = if matches!(input.participation, ParticipationInParentFormattingContext::AtomicInline) { + SizingContext::new(run.state, run.callbacks).apply_cached_intrinsic_inline_measurement( + run.box_, + input.available_space.inline_size, + body_input.available_space.block_size, + input.containing_block_constraints, + ) + } else { + None + }; + let mut implementation = None; + let result = if let Some(cached_block_size) = cached_atomic_block_size { + ChildLayoutResult { + automatic_content_block_size: cached_block_size, + ..ChildLayoutResult::default() + } + } else { + let mut context_implementation = create_formatting_context_implementation(run, parent_grid, fc_type); + let result = match &mut context_implementation { + FormattingContextImplementation::Block(context) => { + context.run(run, body_input); + let result = ChildLayoutResult { + automatic_content_inline_size: context.automatic_content_inline_size(), + automatic_content_block_size: context.automatic_content_block_size(), + table_block_offset_in_wrapper: None, + }; + context.place_floats_after_run(); + result + } + FormattingContextImplementation::Flex(context) => { + context.run(run, body_input); + ChildLayoutResult { + automatic_content_inline_size: context.automatic_content_inline_size(), + automatic_content_block_size: context.automatic_content_block_size(), + table_block_offset_in_wrapper: None, + } + } + FormattingContextImplementation::Grid(context) => { + context.run(run, body_input); + ChildLayoutResult { + automatic_content_inline_size: context.automatic_content_inline_size(), + automatic_content_block_size: context.automatic_content_block_size(), + table_block_offset_in_wrapper: None, + } + } + FormattingContextImplementation::Table(context) => { + context.run(run, parent_block, body_input); + ChildLayoutResult { + automatic_content_inline_size: context.automatic_content_inline_size(), + automatic_content_block_size: context.automatic_content_block_size, + table_block_offset_in_wrapper: body_input + .sizing + .table_box_content_offset_in_wrapper + .map(|_| context.pending_table_offset.block_offset), + } + } + FormattingContextImplementation::Svg(context) => { + context.run(run, body_input); + ChildLayoutResult::default() + } + FormattingContextImplementation::ReplacedWithChildren => layout_replaced_with_children(run, body_input), + FormattingContextImplementation::InternalReplaced | FormattingContextImplementation::InternalDummy => ChildLayoutResult::default(), + }; + implementation = Some(context_implementation); + result + }; + + match input.participation { + ParticipationInParentFormattingContext::BlockLevel => { + finalize_block_level_root(run, &input, parent_block, &result); } - FcImpl::Flex(context) => { - context.run(frame, input); - frame.automatic_content_inline_size = context.automatic_content_inline_size(); - frame.automatic_content_block_size = context.automatic_content_block_size(); + ParticipationInParentFormattingContext::Float => { + let parent = parent_block.expect("a floating run requires an enclosing block formatting context"); + parent.finalize_float_root( + run.box_, + &input, + Some((result.automatic_content_inline_size, result.automatic_content_block_size)), + ); } - FcImpl::Grid(context) => { - context.run(frame, input); - frame.automatic_content_inline_size = context.automatic_content_inline_size(); - frame.automatic_content_block_size = context.automatic_content_block_size(); + ParticipationInParentFormattingContext::AtomicInline => { + finalize_atomic_root_block_size(run, &input, cached_atomic_block_size, parent_block); } - FcImpl::Table(context) => { - context.run(frame, parent_block, input); - frame.automatic_content_inline_size = context.automatic_content_inline_size(); - frame.automatic_content_block_size = context.automatic_content_block_size; - if context.should_publish_pending_table_offset - && let Some(parent) = parent_block - { - parent.set_pending_table_box_content_offset_in_wrapper(context.pending_table_offset); + ParticipationInParentFormattingContext::AbsolutelyPositioned(abspos_inputs) => { + AbsposEngine::new(run.state, run.callbacks) + .finalize_out_of_flow_root_after_inside_layout(run.box_, abspos_inputs); + } + ParticipationInParentFormattingContext::Item => { + if input.sizing.adopt_automatic_content_block_size { + let used = run.state.used_values(&run.callbacks, run.box_); + used.set_content_block_size(result.automatic_content_block_size); } } - FcImpl::Svg(context) => { - context.run(frame, input); + ParticipationInParentFormattingContext::Root => {} + } + + let registered_abspos_children_could_never_be_laid_out = + run.layout_mode != LayoutMode::Normal || run.state.is_measurement(); + if registered_abspos_children_could_never_be_laid_out { + return result; + } + let implementation = implementation.expect("cached measurement replay only occurs on measurement states"); + match &implementation { + FormattingContextImplementation::Block(_) => {} + FormattingContextImplementation::Table(_) => { + let box_ = run.box_; + register_table_abspos_descendants(run, box_); } - FcImpl::ReplacedWithChildren => { - run(frame, input); + FormattingContextImplementation::Flex(context) => { + context.parent_did_dimension(); + } + FormattingContextImplementation::Grid(context) => { + context.parent_did_dimension(); } - FcImpl::InternalReplaced | FcImpl::InternalDummy => {} + FormattingContextImplementation::Svg(_) | FormattingContextImplementation::ReplacedWithChildren => {} + FormattingContextImplementation::InternalReplaced | FormattingContextImplementation::InternalDummy => return result, + } + let box_ = run.box_; + if run.state.abspos_layout_pass_is_active() { + layout_contained_abspos_children(run); + } else { + run.state.enqueue_for_abspos_layout_pass(box_); + } + result +} + +fn finalize_atomic_root_block_size( + run: &FormattingContextRun, + input: &LayoutInput, + cached_block_size: Option, + parent_block: Option<&BlockFormattingContext>, +) { + let node = run.box_; + let available_space = input.available_space; + let constraints = input.containing_block_constraints; + let sizing = SizingContext::new(run.state, run.callbacks); + if sizing.box_is_sized_as_replaced_element(node, available_space, constraints) { + return; + } + if sizing.should_treat_block_size_as_auto(node, available_space, constraints) { + sizing.resolve_used_block_size_if_treated_as_auto(node, available_space, constraints, cached_block_size, || { + let available_inner_space = run + .state + .used_values(&run.callbacks, node) + .available_inner_space_or_constraints_from(available_space); + match parent_block { + Some(parent) => { + parent.compute_automatic_block_size_for_block_level_element(node, available_inner_space, constraints) + } + None => independent_root_automatic_block_size( + run.state, + &run.callbacks, + node, + available_inner_space, + constraints, + ), + } + }); + } else { + sizing.resolve_used_block_size_if_not_treated_as_auto(node, available_space, constraints); } } pub(crate) fn layout_inside_child<'pass>( - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, parent_block: Option<&BlockFormattingContext<'pass>>, parent_grid: Option<&GridFormattingContext<'pass>>, child: Node, layout_mode: LayoutMode, input: LayoutInput, force_independent_context_run: bool, -) -> ChildLayoutOutcome<'pass> { - let facts = frame.state.node_facts(&frame.callbacks, child); - let used = frame.state.try_used_values(&frame.callbacks, child); +) -> ChildLayoutOutcome { + let facts = run.state.node_facts(&run.callbacks, child); + let used = run.state.try_used_values(&run.callbacks, child); if !force_independent_context_run && layout_mode == LayoutMode::IntrinsicSizing && !facts.is_inline() @@ -1397,6 +1612,7 @@ pub(crate) fn layout_inside_child<'pass>( && used.has_definite_block_size() }) { + size_skipped_independent_root(run, parent_block, child, &input); return ChildLayoutOutcome::Skipped; } let creates_replaced_context = matches!( @@ -1404,11 +1620,12 @@ pub(crate) fn layout_inside_child<'pass>( Some(FfiFormattingContextType::InternalReplaced | FfiFormattingContextType::ReplacedWithChildren) ); if !facts.can_have_children() && !creates_replaced_context { + size_skipped_independent_root(run, parent_block, child, &input); return ChildLayoutOutcome::Skipped; } let fc_type = formatting_context_type_created_by_box(facts).or_else(|| { - force_independent_context_run.then(|| independent_formatting_context_type(frame.state, child, &frame.callbacks)) + force_independent_context_run.then(|| independent_formatting_context_type(run.state, child, &run.callbacks)) }); let Some(fc_type) = fc_type else { if force_independent_context_run { @@ -1416,20 +1633,17 @@ pub(crate) fn layout_inside_child<'pass>( } return ChildLayoutOutcome::ReenterCurrent; }; - let mut context = create_formatting_context( - frame.state, + ChildLayoutOutcome::Created(run_formatting_context( + run.state, child, - FcParents { - block: parent_block, - grid: parent_grid, - }, + parent_grid, fc_type, layout_mode, - frame.should_collect_devtools_layout_data, - frame.callbacks, - ); - run_formatting_context(&mut context, input, parent_block); - ChildLayoutOutcome::Created(PendingChildLayout { context }) + run.should_collect_devtools_layout_data, + run.callbacks, + input, + parent_block, + )) } fn independent_formatting_context_type( @@ -1458,7 +1672,6 @@ fn independent_formatting_context_type( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_layout_run_root_layout( root: NodeSlotId, - document_element_layout_node: NodeSlotId, viewport_inline_size_raw: i32, viewport_block_size_raw: i32, should_collect_devtools_layout_data: bool, @@ -1483,49 +1696,37 @@ pub unsafe extern "C" fn rust_layout_run_root_layout( ..crate::layout::ContainingBlockConstraints::default() }; let viewport_used = state.create_used_values(&callbacks, root, root_constraints); - state.record_precreated_used_values(&callbacks, root); - viewport_used.set_content_inline_size(viewport_inline_size); - viewport_used.set_content_block_size(viewport_block_size); let mut root_for_layout = root; - let has_initial_containing_block = !document_element_layout_node.is_invalid(); - if has_initial_containing_block { - let icb_used = state.create_used_values(&callbacks, document_element_layout_node, root_constraints); - state.record_precreated_used_values(&callbacks, document_element_layout_node); - icb_used.set_content_inline_size(viewport_inline_size); - } - let first_child = callbacks.first_child(root); if !first_child.is_invalid() && state.node_facts(&callbacks, first_child).is_svg_svg_box() { - // Standalone SVG documents use the viewport size for the root - // SVG container and enter SVG layout directly. - let svg_root_used = state.used_values(&callbacks, first_child); - svg_root_used.set_content_block_size(viewport_used.content_block_size.get()); + viewport_used.set_content_inline_size(viewport_inline_size); + viewport_used.set_content_block_size(viewport_block_size); + state.create_used_values(&callbacks, first_child, root_constraints); root_for_layout = first_child; } - let input = LayoutInput { - available_space: AvailableSpace { + let input = LayoutInput::new( + AvailableSpace { inline_size: AvailableSize::definite(viewport_inline_size), block_size: AvailableSize::definite(viewport_block_size), }, - containing_block_constraints: crate::layout::ContainingBlockConstraints::default(), - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }; + crate::layout::ContainingBlockConstraints::default(), + ParticipationInParentFormattingContext::Root, + ) + .with_forced_sizes(viewport_inline_size, viewport_block_size); let state_ref = &state; let fc_type = independent_formatting_context_type(state_ref, root_for_layout, &callbacks); - let mut context = create_formatting_context( + run_formatting_context( state_ref, root_for_layout, - FcParents::default(), + None, fc_type, LayoutMode::Normal, should_collect_devtools_layout_data, callbacks, + input, + None, ); - run_formatting_context(&mut context, input, None); - complete_formatting_context_after_root_box_has_used_size(&mut context); - drop(context); run_abspos_layout_pass(state_ref, callbacks, should_collect_devtools_layout_data); state.commit_replacing(root, std::ptr::null_mut(), &callbacks, sink); }); @@ -1539,6 +1740,8 @@ pub unsafe extern "C" fn rust_layout_compute_subtree_layout( root: NodeSlotId, viewport: NodeSlotId, paintable_to_replace: *mut c_void, + viewport_inline_size_raw: i32, + viewport_block_size_raw: i32, callbacks: *const FfiLayoutFcCallbacks, sink: *const FfiCommitSink, ) { @@ -1557,37 +1760,33 @@ pub unsafe extern "C" fn rust_layout_compute_subtree_layout( .populate_from_paintable(&callbacks, root, paintable_to_replace) .expect("partial relayout root must have committed geometry"); if !viewport.is_invalid() && viewport != root { - let _ = state.populate_from_paintable(&callbacks, viewport, std::ptr::null_mut()); + let viewport_inline_size = CssPixels::from_raw(viewport_inline_size_raw); + let viewport_block_size = CssPixels::from_raw(viewport_block_size_raw); + let viewport_constraints = crate::layout::ContainingBlockConstraints { + percentage_basis_inline_size: Some(viewport_inline_size), + percentage_basis_block_size: Some(viewport_block_size), + ..crate::layout::ContainingBlockConstraints::default() + }; + let viewport_used = state.create_used_values(&callbacks, viewport, viewport_constraints); + viewport_used.set_content_inline_size(viewport_inline_size); + viewport_used.set_content_block_size(viewport_block_size); } - let input = LayoutInput { - available_space: AvailableSpace { + let input = LayoutInput::new( + AvailableSpace { inline_size: AvailableSize::definite(root_used.content_inline_size.get()), block_size: AvailableSize::definite(root_used.content_block_size.get()), }, // The subtree root has definite sizes in both axes, so boxes // below it do not need inherited percentage constraints. - containing_block_constraints: crate::layout::ContainingBlockConstraints::default(), - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }; + crate::layout::ContainingBlockConstraints::default(), + ParticipationInParentFormattingContext::Root, + ); let state_ref = &state; let facts = state.node_facts(&callbacks, root); let fc_type = formatting_context_type_created_by_box(facts) .expect("partial relayout root must establish an independent formatting context"); - let mut context = create_formatting_context( - state_ref, - root, - FcParents::default(), - fc_type, - LayoutMode::Normal, - false, - callbacks, - ); - run_formatting_context(&mut context, input, None); - - complete_formatting_context_after_root_box_has_used_size(&mut context); - drop(context); + run_formatting_context(state_ref, root, None, fc_type, LayoutMode::Normal, false, callbacks, input, None); run_abspos_layout_pass(state_ref, callbacks, false); state.commit_replacing(root, paintable_to_replace, &callbacks, sink); }); @@ -1616,8 +1815,8 @@ pub unsafe extern "C" fn rust_layout_replay_saved_abspos_layout( let state_ref = &state; let containing_block = callbacks.containing_block(box_); assert!(!containing_block.is_invalid()); - let mut frame = crate::layout::FcFrame::new(state_ref, containing_block, LayoutMode::Normal, callbacks, false); - AbsposEngine::new(state_ref, callbacks).replay(&mut frame, box_); + let run = crate::layout::FormattingContextRun::new(state_ref, containing_block, LayoutMode::Normal, callbacks, false); + AbsposEngine::new(state_ref, callbacks).replay(&run, box_); run_abspos_layout_pass(state_ref, callbacks, false); state.commit_replacing(box_, paintable_to_replace, &callbacks, sink); }); diff --git a/Libraries/LibWeb/Rust/src/layout/geometry.rs b/Libraries/LibWeb/Rust/src/layout/geometry.rs index e6b0cba7fb747..2beb6823f4188 100644 --- a/Libraries/LibWeb/Rust/src/layout/geometry.rs +++ b/Libraries/LibWeb/Rust/src/layout/geometry.rs @@ -113,10 +113,57 @@ impl ContainingBlockConstraints { } } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub(crate) struct RootSizingDirectives { + pub(crate) forced_content_inline_size: Option, + pub(crate) forced_content_block_size: Option, + pub(crate) forced_min_border_box_block_size: Option, + pub(crate) table_box_content_offset_in_wrapper: Option, + pub(crate) adopt_automatic_content_block_size: bool, + pub(crate) float_avoidance_inline_size: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum ParticipationInParentFormattingContext { + BlockLevel, + Float, + AtomicInline, + AbsolutelyPositioned(AbsposLayoutInputs), + Item, + Root, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct LayoutInput { pub(crate) available_space: AvailableSpace, pub(crate) containing_block_constraints: ContainingBlockConstraints, pub(crate) content_box_position_in_bfc_root: Option, - pub(crate) table_grid_min_border_box_block_size: Option, + pub(crate) sizing: RootSizingDirectives, + pub(crate) participation: ParticipationInParentFormattingContext, +} + +impl LayoutInput { + pub(crate) fn new( + available_space: AvailableSpace, + containing_block_constraints: ContainingBlockConstraints, + participation: ParticipationInParentFormattingContext, + ) -> Self { + Self { + available_space, + containing_block_constraints, + content_box_position_in_bfc_root: None, + sizing: RootSizingDirectives::default(), + participation, + } + } + + pub(crate) fn with_forced_sizes( + mut self, + forced_content_inline_size: CssPixels, + forced_content_block_size: CssPixels, + ) -> Self { + self.sizing.forced_content_inline_size = Some(forced_content_inline_size); + self.sizing.forced_content_block_size = Some(forced_content_block_size); + self + } } diff --git a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs index 9faffe4b13443..38e66bdc1ec79 100644 --- a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs @@ -2574,12 +2574,7 @@ impl<'pass> GridFormattingContext<'pass> { if !axis.is_column() && self.used(subgrid).has_definite_inline_size() { available.inline_size = AvailableSize::definite(self.used(subgrid).content_inline_size.get()); } - let input = LayoutInput { - available_space: available, - containing_block_constraints: self.track_sizing_constraints(), - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }; + let input = LayoutInput::new(available, self.track_sizing_constraints(), ParticipationInParentFormattingContext::Item); context.reset_for_run(input); let grid_style = context.grid_style(context.grid_container); context.cache_subgrid_axes(grid_style); @@ -3340,7 +3335,7 @@ impl<'pass> GridFormattingContext<'pass> { rect } - fn layout_items(&mut self, frame: &mut FcFrame<'pass>) { + fn layout_items(&mut self, run: &FormattingContextRun<'pass>) { for item_index in 0..self.items.len() { let item = self.items[item_index]; let area = self.grid_area(item); @@ -3377,10 +3372,11 @@ impl<'pass> GridFormattingContext<'pass> { constraints }, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Item, }; - let child_layout = match crate::layout::layout_inside_child( - frame, + match crate::layout::layout_inside_child( + run, None, Some(self), item.box_, @@ -3388,12 +3384,10 @@ impl<'pass> GridFormattingContext<'pass> { input, false, ) { - crate::layout::ChildLayoutOutcome::Created(child_layout) => Some(child_layout), + crate::layout::ChildLayoutOutcome::Created(_) | crate::layout::ChildLayoutOutcome::Skipped => {} crate::layout::ChildLayoutOutcome::ReenterCurrent => { - self.run(frame, input); - None + self.run(run, input); } - crate::layout::ChildLayoutOutcome::Skipped => None, }; let offset = FfiCssPixelPoint { x: area.offset.inline_offset + self.item_margin_box_start(item, Axis::Column), @@ -3407,9 +3401,6 @@ impl<'pass> GridFormattingContext<'pass> { area.size.inline_size, area.size.block_size, ); - if let Some(child_layout) = child_layout { - child_layout.finish(); - } } crate::layout::compute_and_store_baselines(self.state, &self.callbacks, self.grid_container, false); } @@ -3581,7 +3572,7 @@ impl<'pass> GridFormattingContext<'pass> { .grid_layout_data = Some(data); } - pub(crate) fn run(&mut self, frame: &mut FcFrame<'pass>, input: LayoutInput) { + pub(crate) fn run(&mut self, run: &FormattingContextRun<'pass>, input: LayoutInput) { let available = input.available_space; // OPTIMIZATION: If we're in intrinsic sizing layout, but the grid container is not the // box being measured, we can skip everything here. @@ -3662,7 +3653,7 @@ impl<'pass> GridFormattingContext<'pass> { return; } - self.layout_items(frame); + self.layout_items(run); self.save_used_tracks(grid_style); self.save_devtools_data(grid_style); } diff --git a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs index df58aa3c6c47a..882de20314697 100644 --- a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs @@ -513,7 +513,7 @@ pub(crate) fn compute(context: &InlineFormattingContext) -> Vec { - pub(crate) frame: RefCell<&'context mut FcFrame<'pass>>, + pub(crate) run: &'context FormattingContextRun<'pass>, pub(crate) state: &'pass LayoutState, pub(crate) containing_block: Node, pub(crate) layout_mode: LayoutMode, @@ -530,7 +530,7 @@ pub(crate) struct InlineFormattingContext<'context, 'pass> { impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { pub(crate) fn new_with_rust_parent( - frame: &'context mut FcFrame<'pass>, + run: &'context FormattingContextRun<'pass>, state: &'pass LayoutState, containing_block: Node, layout_mode: LayoutMode, @@ -541,7 +541,7 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { let containing_used_values = state.used_values(&callbacks, containing_block); let line_data = state.line_data_cell(callbacks.slot_index(containing_block)); Self { - frame: RefCell::new(frame), + run, state, containing_block, layout_mode, @@ -728,38 +728,14 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { next.map(|next| next - containing_block_offset_in_root) } - fn parent_resolve_used_block_size( - &self, - node: Node, - treated_as_auto: bool, - available_space: AvailableSpace, - child_automatic_block_size: Option, - ) { - if treated_as_auto { - self.parent.resolve_used_block_size_if_treated_as_auto( - node, - available_space, - self.input.containing_block_constraints, - child_automatic_block_size, - ); - } else { - self.parent.resolve_used_block_size_if_not_treated_as_auto( - node, - available_space, - self.input.containing_block_constraints, - ); - } - } - - fn layout_inside(&mut self, node: Node, available_space: AvailableSpace) -> Option> { - let input = LayoutInput { + fn layout_inside(&mut self, node: Node, available_space: AvailableSpace) { + let input = LayoutInput::new( available_space, - containing_block_constraints: self.input.containing_block_constraints, - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }; - match crate::layout::layout_inside_child( - &mut self.frame.borrow_mut(), + self.input.containing_block_constraints, + ParticipationInParentFormattingContext::AtomicInline, + ); + if let crate::layout::ChildLayoutOutcome::ReenterCurrent = crate::layout::layout_inside_child( + self.run, Some(self.parent), None, node, @@ -767,47 +743,20 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { input, false, ) { - crate::layout::ChildLayoutOutcome::Skipped => None, - crate::layout::ChildLayoutOutcome::Created(child_layout) => Some(child_layout), - crate::layout::ChildLayoutOutcome::ReenterCurrent => { - self.parent.run(&mut self.frame.borrow_mut(), input); - None - } + self.parent.run(self.run, input); } } pub(crate) fn dimension_box_on_line(&mut self, node: Node) { let available_space = self.input.available_space; - let constraints = self.input.containing_block_constraints; - let containing_inline_size = available_space.inline_size.to_px_or_zero(); - let style = self.style(node); - { - let used = self.used_mut(node); - used.margin_left.set(style.margin_left().to_px(containing_inline_size)); - used.border_left.set(style.border_left_width()); - used.padding_left - .set(style.padding_left().to_px(containing_inline_size)); - used.margin_right - .set(style.margin_right().to_px(containing_inline_size)); - used.border_right.set(style.border_right_width()); - used.padding_right - .set(style.padding_right().to_px(containing_inline_size)); - used.margin_top.set(style.margin_top().to_px(containing_inline_size)); - used.border_top.set(style.border_top_width()); - used.padding_top.set(style.padding_top().to_px(containing_inline_size)); - used.padding_bottom - .set(style.padding_bottom().to_px(containing_inline_size)); - used.border_bottom.set(style.border_bottom_width()); - used.margin_bottom - .set(style.margin_bottom().to_px(containing_inline_size)); - } - let facts = self.facts(node); if facts.is_list_item_marker_box() { + SizingContext::new(self.state, self.callbacks) + .resolve_box_model_metrics_against_inline_basis(node, available_space.inline_size.to_px_or_zero()); self.parent.dimension_list_item_marker(node); let distance = self.parent.distance_between_marker_and_list_item(node); let used = self.used_mut(node); - if style.direction() == direction::LTR { + if self.style(node).direction() == direction::LTR { used.margin_right.set(used.margin_right.get() + distance); } else { used.margin_left.set(used.margin_left.get() + distance); @@ -815,27 +764,6 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { return; } - let sizing = SizingContext::new(self.state, self.callbacks); - if sizing.box_is_sized_as_replaced_element(node, available_space, constraints) { - let inline_size = sizing.compute_inline_size_for_replaced_element(node, available_space, constraints); - self.used_mut(node).set_content_inline_size(inline_size); - let block_size = sizing.compute_block_size_for_replaced_element(node, available_space, constraints); - self.used_mut(node).set_content_block_size(block_size); - let block_size_is_automatic = - style.height().is_auto() || sizing.should_treat_block_size_as_auto(node, available_space, constraints); - if self.used(node).has_definite_inline_size() && facts.has_preferred_aspect_ratio() && block_size_is_automatic - { - self.used_mut(node).has_definite_block_size.set(true); - } - let inner = self - .used(node) - .available_inner_space_or_constraints_from(available_space); - if let Some(child_layout) = self.layout_inside(node, inner) { - child_layout.finish(); - } - return; - } - // Any fragmented inline box should have generated line box fragments already. if facts.is_fragmented_inline() { // SAFETY: The callback table and layout node remain live for this @@ -849,89 +777,12 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { return; } - let unconstrained_inline_size = if sizing.should_treat_inline_size_as_auto(node, available_space) { - if matches!(available_space.inline_size, AvailableSize::Definite(_)) { - let used = self.used(node); - let available = available_space.inline_size.to_px_or_zero() - - used.margin_left.get() - - used.border_left.get() - - used.padding_left.get() - - used.padding_right.get() - - used.border_right.get() - - used.margin_right.get(); - let preferred = sizing.calculate_max_content_inline_size(node, constraints); - if preferred <= available { - preferred - } else { - sizing - .calculate_min_content_inline_size(node, constraints) - .max(available) - .min(preferred) - } - } else if available_space.inline_size == AvailableSize::MinContent { - sizing.calculate_min_content_inline_size(node, constraints) - } else { - sizing.calculate_max_content_inline_size(node, constraints) - } - } else if style.width().contains_percentage() && !matches!(available_space.inline_size, AvailableSize::Definite(_)) { - CssPixels::default() - } else { - sizing.calculate_inner_inline_size(node, available_space.inline_size, style.width(), constraints) - }; - - let mut inline_size = unconstrained_inline_size; - if !sizing.should_treat_max_inline_size_as_none(node, available_space.inline_size, constraints) { - inline_size = inline_size.min(sizing.calculate_inner_inline_size( - node, - available_space.inline_size, - style.max_width(), - constraints, - )); - } - if !style.min_width().is_auto() { - inline_size = inline_size.max(sizing.calculate_inner_inline_size( - node, - available_space.inline_size, - style.min_width(), - constraints, - )); - } - self.used_mut(node).set_content_inline_size(inline_size); - - let inline_definite_space = AvailableSpace { - inline_size: crate::layout::AvailableSize::definite(inline_size), - block_size: crate::layout::AvailableSize::Indefinite, - }; - self.parent_resolve_used_block_size(node, false, inline_definite_space, None); - if style.display().is_flex_inside() { - self.parent_resolve_used_block_size(node, true, inline_definite_space, None); - } - sizing.make_button_content_box_definite(node, self.layout_mode, available_space, constraints, None); - let inner_before_cached_measurement = self - .used(node) - .available_inner_space_or_constraints_from(available_space); - let cached_automatic_block_size = sizing.apply_cached_intrinsic_inline_measurement( - node, - available_space.inline_size, - inner_before_cached_measurement.block_size, - constraints, + self.layout_inside(node, available_space); + debug_assert!( + self.used(node).has_definite_inline_size.get() + || self.used(node).inline_size_constraint.get() != SizeConstraint::None, + "atomic inline-level run left its root's inline size unresolved" ); - let inner = self - .used(node) - .available_inner_space_or_constraints_from(available_space); - let child_layout = if cached_automatic_block_size.is_some() { - None - } else { - self.layout_inside(node, inner) - }; - if sizing.should_treat_block_size_as_auto(node, available_space, constraints) { - self.parent_resolve_used_block_size(node, true, available_space, cached_automatic_block_size); - } else { - self.parent_resolve_used_block_size(node, false, available_space, None); - } - if let Some(child_layout) = child_layout { - child_layout.finish(); - } } fn clear_floating_boxes(&self, node: Node) -> bool { @@ -1034,7 +885,7 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { leading_padding += item.padding_start; line_builder.finish_current_line_before_block_level_box(); self.parent.layout_interrupting_block_inside_inline_context( - &mut self.frame.borrow_mut(), + self.run, item.node, self.containing_block, self.input, @@ -1062,9 +913,8 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { iterator.next_non_whitespace_sequence_inline_size(self), ); self.parent.layout_floating_box( - &mut self.frame.borrow_mut(), + self.run, item.node, - self.containing_block, self.input, CssPixels::default(), Some(&mut line_builder), diff --git a/Libraries/LibWeb/Rust/src/layout/layout_state.rs b/Libraries/LibWeb/Rust/src/layout/layout_state.rs index 4d04429469c67..e0a34bbf2c02d 100644 --- a/Libraries/LibWeb/Rust/src/layout/layout_state.rs +++ b/Libraries/LibWeb/Rust/src/layout/layout_state.rs @@ -147,7 +147,6 @@ pub(crate) struct AbsposLayoutInputs { pub(crate) containing_block_info: AbsposContainingBlockInfo, } - #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct FfiTableCellCoordinates { @@ -557,10 +556,6 @@ impl<'pass> NodeFacts<'pass> { ) } - pub(crate) fn is_grid_item(&self) -> bool { - crate::layout::has_flag(self.data(), NodeFlag::IsGridItem) - } - pub(crate) fn is_editing_host(&self) -> bool { crate::layout::has_flag(self.data(), NodeFlag::IsEditingHost) } @@ -808,7 +803,6 @@ pub(crate) struct LayoutState { line_data: PagedStore>, block_rare_data: PagedStore>, used_values_rare_data: PagedStore>, - precreated_used_value_slots: RefCell>, purpose: LayoutStatePurpose, } @@ -861,7 +855,6 @@ impl LayoutState { line_data: PagedStore::default(), block_rare_data: PagedStore::default(), used_values_rare_data: PagedStore::default(), - precreated_used_value_slots: RefCell::new(HashSet::new()), purpose, } } @@ -870,13 +863,6 @@ impl LayoutState { self.purpose == LayoutStatePurpose::Measurement } - pub(crate) fn record_precreated_used_values(&self, callbacks: &FfiLayoutFcCallbacks, node: Node) { - assert!(!self.is_measurement()); - let slot_index = callbacks.slot_index(node); - assert!(self.used_values.get(slot_index).is_some()); - assert!(self.precreated_used_value_slots.borrow_mut().insert(slot_index)); - } - #[inline] pub(crate) fn node_facts<'pass>( &'pass self, @@ -890,10 +876,6 @@ impl LayoutState { } } - pub(crate) fn may_reuse_precreated_used_values(&self, slot_index: u32) -> bool { - self.precreated_used_value_slots.borrow().contains(&slot_index) - } - pub(crate) fn create_used_values( &self, callbacks: &FfiLayoutFcCallbacks, diff --git a/Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs index bbdfec383392f..17d94be2a9be5 100644 --- a/Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs @@ -4,12 +4,12 @@ * SPDX-License-Identifier: BSD-2-Clause */ -fn run(frame: &mut FcFrame, layout_input: LayoutInput) { +fn layout_replaced_with_children(run: &FormattingContextRun, layout_input: LayoutInput) -> ChildLayoutResult { // The parent FC has already resolved this replaced box's used size (natural size, explicit size, or the default // object size), so both dimensions are definite for its children — shadow content sized to fill // (e.g. width/height: 100%) resolves against them. let (content_inline_size, root_content_block_size) = { - let root_state = frame.state.used_values(&frame.callbacks, frame.box_); + let root_state = run.state.used_values(&run.callbacks, run.box_); root_state.has_definite_inline_size.set(true); root_state.has_definite_block_size.set(true); ( @@ -25,50 +25,50 @@ fn run(frame: &mut FcFrame, layout_input: LayoutInput) { // The TreeBuilder wraps shadow DOM children in an anonymous BlockContainer. // Delegate layout to a BFC for that wrapper. - let mut wrapper = frame.callbacks.first_child(frame.box_); + let mut wrapper = run.callbacks.first_child(run.box_); while !wrapper.is_invalid() { - if frame.state.node_facts(&frame.callbacks, wrapper).is_block_container() { + if run.state.node_facts(&run.callbacks, wrapper).is_block_container() { break; } - wrapper = frame.callbacks.next_sibling(wrapper); + wrapper = run.callbacks.next_sibling(wrapper); } if wrapper.is_invalid() { - return; + return ChildLayoutResult::default(); } - let wrapper_constraints = SizingContext::new(frame.state, frame.callbacks) - .constraints_for_child_context(frame.box_, layout_input.containing_block_constraints); - let wrapper_state = frame + let wrapper_constraints = SizingContext::new(run.state, run.callbacks) + .constraints_for_child_context(run.box_, layout_input.containing_block_constraints); + let wrapper_state = run .state - .create_used_values(&frame.callbacks, wrapper, wrapper_constraints); + .create_used_values(&run.callbacks, wrapper, wrapper_constraints); wrapper_state.set_content_inline_size(content_inline_size); - let mut bfc = crate::layout::create_formatting_context( - frame.state, + let wrapper_layout = crate::layout::run_formatting_context( + run.state, wrapper, - crate::layout::FcParents::default(), + None, FfiFormattingContextType::Block, - frame.layout_mode, - frame.should_collect_devtools_layout_data, - frame.callbacks, - ); - crate::layout::run_formatting_context( - &mut bfc, + run.layout_mode, + run.should_collect_devtools_layout_data, + run.callbacks, LayoutInput { available_space: child_available_space, containing_block_constraints: wrapper_constraints, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives { + adopt_automatic_content_block_size: true, + ..RootSizingDirectives::default() + }, + participation: ParticipationInParentFormattingContext::Item, }, None, ); - frame.automatic_content_inline_size = content_inline_size; - frame.automatic_content_block_size = bfc.automatic_content_block_size; - - wrapper_state.set_content_block_size(frame.automatic_content_block_size); + crate::layout::place_child(run.state, &run.callbacks, wrapper, FfiCssPixelPoint::default()); - crate::layout::place_child(frame.state, &frame.callbacks, wrapper, FfiCssPixelPoint::default()); - - crate::layout::complete_formatting_context_after_root_box_has_used_size(&mut bfc); + ChildLayoutResult { + automatic_content_inline_size: content_inline_size, + automatic_content_block_size: wrapper_layout.automatic_content_block_size, + table_block_offset_in_wrapper: None, + } } diff --git a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs index 912bec1d90c1d..abdac3dfadaa5 100644 --- a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs @@ -884,6 +884,276 @@ impl<'pass> SizingContext<'pass> { || (size.is_min_content() && available == AvailableSize::MinContent) } + // https://quirks.spec.whatwg.org/#the-percentage-height-calculation-quirk + // The available space to resolve a block-level box's block size against: + // in quirks mode, percentage heights outside table internals and UA + // shadow trees resolve against the quirks percentage basis. + pub(crate) fn available_space_for_block_size_resolution( + &self, + node: Node, + available_space: AvailableSpace, + constraints: ContainingBlockConstraints, + ) -> AvailableSpace { + let facts = self.facts(node); + // The quirks-mode check comes first so standards-mode documents pay a + // single flag read here. + if !facts.document_in_quirks_mode() + || !self.style(node).height().is_percentage() + || facts.is_in_user_agent_shadow_tree() + { + return available_space; + } + let is_table_box = facts.is_table_row() + || facts.is_table_row_group() + || facts.is_table_header_group() + || facts.is_table_footer_group() + || facts.is_table_cell() + || facts.is_table_caption(); + if is_table_box { + return available_space; + } + let mut resolution_space = available_space; + resolution_space.block_size = + AvailableSize::definite(constraints.quirks_mode_percentage_basis_block_size.unwrap_or_default()); + resolution_space + } + + pub(crate) fn resolve_used_block_size_if_not_treated_as_auto( + &self, + node: Node, + available_space: AvailableSpace, + constraints: ContainingBlockConstraints, + ) { + if self.should_treat_block_size_as_auto(node, available_space, constraints) { + return; + } + let style = self.style(node); + let mut block_size = self.calculate_inner_block_size(node, available_space, style.height(), constraints); + if !self.should_treat_max_block_size_as_none(node, available_space.block_size, constraints) + && !style.max_height().is_auto() + { + block_size = block_size.min(self.calculate_inner_block_size( + node, + available_space, + style.max_height(), + constraints, + )); + } + if !style.min_height().is_auto() { + block_size = block_size.max(self.calculate_inner_block_size( + node, + available_space, + style.min_height(), + constraints, + )); + } + let used = self.used_mut(node); + used.set_content_block_size(block_size); + if !style.height().is_intrinsic_sizing_constraint() { + used.has_definite_block_size.set(true); + } + } + + pub(crate) fn resolve_used_block_size_if_treated_as_auto( + &self, + node: Node, + available_space: AvailableSpace, + constraints: ContainingBlockConstraints, + child_automatic_block_size: Option, + automatic_block_size_fallback: impl FnOnce() -> CssPixels, + ) { + if !self.should_treat_block_size_as_auto(node, available_space, constraints) { + return; + } + let style = self.style(node); + let facts = self.facts(node); + let mut block_size = if self.box_is_sized_as_replaced_element(node, available_space, constraints) { + self.compute_block_size_for_replaced_element(node, available_space, constraints) + } else { + child_automatic_block_size.unwrap_or_else(automatic_block_size_fallback) + }; + if !self.should_treat_max_block_size_as_none(node, available_space.block_size, constraints) + && !style.max_height().is_auto() + { + block_size = block_size.min(self.calculate_inner_block_size( + node, + available_space, + style.max_height(), + constraints, + )); + } + if !style.min_height().is_auto() { + block_size = block_size.max(self.calculate_inner_block_size( + node, + available_space, + style.min_height(), + constraints, + )); + } + + if facts.document_in_quirks_mode() && facts.is_html_html_element() && style.height().is_auto() { + // 3.6. The html element fills the viewport quirk + // https://quirks.spec.whatwg.org/#the-html-element-fills-the-viewport-quirk + // FIXME: Handle vertical writing mode. + + // 1. Let margins be sum of the used values of the margin-left and margin-right properties of element + // if element has a vertical writing mode, otherwise let margins be the sum of the used values of + // the margin-top and margin-bottom properties of element. + let used = self.used(node); + let margins = used.margin_top.get() + used.margin_bottom.get(); + // 2. Let size be the size of the initial containing block in the block flow direction minus margins. + let size = constraints.block_basis() - margins; + // 3. Return the bigger value of size and the normal border box size the element would have + // according to the CSS specification. + block_size = block_size.max(size); + // NOTE: The block size of the root element when affected by this quirk is considered to be definite. + self.used_mut(node).has_definite_block_size.set(true); + } + + if facts.document_in_quirks_mode() && facts.is_html_body_element() && style.height().is_auto() { + // 3.7. The body element fills the html element quirk + // https://quirks.spec.whatwg.org/#the-body-element-fills-the-html-element-quirk + // FIXME: Handle vertical writing mode. + + // The element body must additionally meet the following conditions: + // - The computed value of the 'position' property of element is neither 'absolute' nor 'fixed'. + // - The computed value of the 'float' property of element is 'none'. + // - Element is not an inline-level element. + // - Element is not a multi-column spanning element. + // NON-STANDARD: We don't check column-span since no browser actually excludes it. + if !facts.is_absolutely_positioned() && !facts.is_floating() && !facts.is_inline() { + // 1. Let margins be sum of the used values of the margin-left and margin-right properties of element + // if element has a vertical writing mode, otherwise let margins be the sum of the used values of + // the margin-top and margin-bottom properties of element. + let used = self.used(node); + let margins = used.margin_top.get() + used.margin_bottom.get(); + // 2. Let size be the size of element's parent element's content box in the block flow direction minus margins. + let size = constraints.block_basis() - margins; + // 3. Return the bigger value of size and the normal border box size the element would have + // according to the CSS specification. + block_size = block_size.max(size); + } + } + self.used_mut(node).set_content_block_size(block_size); + } + + pub(crate) fn resolve_box_model_metrics_against_inline_basis(&self, node: Node, containing_inline_size: CssPixels) { + let style = self.style(node); + let used = self.used_mut(node); + used.margin_left.set(style.margin_left().to_px(containing_inline_size)); + used.border_left.set(style.border_left_width()); + used.padding_left + .set(style.padding_left().to_px(containing_inline_size)); + used.margin_right + .set(style.margin_right().to_px(containing_inline_size)); + used.border_right.set(style.border_right_width()); + used.padding_right + .set(style.padding_right().to_px(containing_inline_size)); + used.margin_top.set(style.margin_top().to_px(containing_inline_size)); + used.border_top.set(style.border_top_width()); + used.padding_top.set(style.padding_top().to_px(containing_inline_size)); + used.padding_bottom + .set(style.padding_bottom().to_px(containing_inline_size)); + used.border_bottom.set(style.border_bottom_width()); + used.margin_bottom + .set(style.margin_bottom().to_px(containing_inline_size)); + } + + pub(crate) fn dimension_atomic_root( + &self, + node: Node, + available_space: AvailableSpace, + constraints: ContainingBlockConstraints, + layout_mode: LayoutMode, + ) { + let containing_inline_size = available_space.inline_size.to_px_or_zero(); + let style = self.style(node); + self.resolve_box_model_metrics_against_inline_basis(node, containing_inline_size); + + let facts = self.facts(node); + if self.box_is_sized_as_replaced_element(node, available_space, constraints) { + let inline_size = self.compute_inline_size_for_replaced_element(node, available_space, constraints); + self.used_mut(node).set_content_inline_size(inline_size); + let block_size = self.compute_block_size_for_replaced_element(node, available_space, constraints); + self.used_mut(node).set_content_block_size(block_size); + let block_size_is_automatic = + style.height().is_auto() || self.should_treat_block_size_as_auto(node, available_space, constraints); + if self.used(node).has_definite_inline_size() && facts.has_preferred_aspect_ratio() && block_size_is_automatic + { + self.used_mut(node).has_definite_block_size.set(true); + } + return; + } + + let unconstrained_inline_size = if self.should_treat_inline_size_as_auto(node, available_space) { + if matches!(available_space.inline_size, AvailableSize::Definite(_)) { + let used = self.used(node); + let available = available_space.inline_size.to_px_or_zero() + - used.margin_left.get() + - used.border_left.get() + - used.padding_left.get() + - used.padding_right.get() + - used.border_right.get() + - used.margin_right.get(); + let preferred = self.calculate_max_content_inline_size(node, constraints); + if preferred <= available { + preferred + } else { + self.calculate_min_content_inline_size(node, constraints) + .max(available) + .min(preferred) + } + } else if available_space.inline_size == AvailableSize::MinContent { + self.calculate_min_content_inline_size(node, constraints) + } else { + self.calculate_max_content_inline_size(node, constraints) + } + } else if style.width().contains_percentage() && !matches!(available_space.inline_size, AvailableSize::Definite(_)) { + CssPixels::default() + } else { + self.calculate_inner_inline_size(node, available_space.inline_size, style.width(), constraints) + }; + + let mut inline_size = unconstrained_inline_size; + if !self.should_treat_max_inline_size_as_none(node, available_space.inline_size, constraints) { + inline_size = inline_size.min(self.calculate_inner_inline_size( + node, + available_space.inline_size, + style.max_width(), + constraints, + )); + } + if !style.min_width().is_auto() { + inline_size = inline_size.max(self.calculate_inner_inline_size( + node, + available_space.inline_size, + style.min_width(), + constraints, + )); + } + self.used_mut(node).set_content_inline_size(inline_size); + + let inline_definite_space = AvailableSpace { + inline_size: AvailableSize::definite(inline_size), + block_size: AvailableSize::Indefinite, + }; + self.resolve_used_block_size_if_not_treated_as_auto(node, inline_definite_space, constraints); + if style.display().is_flex_inside() { + // Flex containers with an automatic block size are treated as max-content, so resolve it early. + self.resolve_used_block_size_if_treated_as_auto(node, inline_definite_space, constraints, None, || { + crate::layout::independent_root_automatic_block_size( + self.state, + &self.callbacks, + node, + self.used(node) + .available_inner_space_or_constraints_from(inline_definite_space), + constraints, + ) + }); + } + self.make_button_content_box_definite(node, layout_mode, available_space, constraints, None); + } + fn calculate_stretch_fit_inline_size(&self, node: Node, available: AvailableSize) -> CssPixels { // https://drafts.csswg.org/css-sizing-3/#stretch-fit-size // The size a box would take if its outer size filled the available space in the given axis; @@ -1148,7 +1418,8 @@ impl<'pass> SizingContext<'pass> { }, containing_block_constraints: constraints, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Root, }, ); result.automatic_content_inline_size = clamp_to_max_dimension_value(result.automatic_content_inline_size); @@ -1348,7 +1619,8 @@ impl<'pass> SizingContext<'pass> { }, containing_block_constraints: constraints, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Root, }, ); result.automatic_content_inline_size = clamp_to_max_dimension_value(result.automatic_content_inline_size); @@ -1403,7 +1675,8 @@ impl<'pass> SizingContext<'pass> { }, containing_block_constraints: constraints, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Root, }, ); let value = clamp_to_max_dimension_value(result.automatic_content_block_size); @@ -1455,7 +1728,8 @@ impl<'pass> SizingContext<'pass> { }, containing_block_constraints: constraints, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Root, }, ); let value = clamp_to_max_dimension_value(result.automatic_content_block_size); @@ -1475,12 +1749,7 @@ impl<'pass> SizingContext<'pass> { .run_with_layout_mode( node, layout_mode, - LayoutInput { - available_space: inner_available_space, - containing_block_constraints: constraints, - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }, + LayoutInput::new(inner_available_space, constraints, ParticipationInParentFormattingContext::Root), ) .automatic_content_block_size } @@ -1625,30 +1894,19 @@ impl<'pass> SizingContext<'pass> { .padding_right .set(table_style.padding_right().to_px(containing_block_inline_size)); - let mut context = crate::layout::create_formatting_context( + let table_run = crate::layout::FormattingContextRun::new( measurement.rust_state(), table_box, - crate::layout::FcParents::default(), - crate::layout::FfiFormattingContextType::Table, LayoutMode::IntrinsicSizing, - false, *measurement.callbacks(), + false, ); + let mut table = TableFormattingContext::new(&table_run); let table_available = table_used.available_inner_space_or_constraints_from(available_space); - let FormattingContextInstance { frame, implementation } = &mut *context; - let FcImpl::Table(table) = implementation else { - unreachable!("table measurement created a non-table context"); - }; table.run_until_inline_size_calculation( - LayoutInput { - available_space: table_available, - containing_block_constraints: table_constraints, - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }, + LayoutInput::new(table_available, table_constraints, ParticipationInParentFormattingContext::Root), true, ); - frame.automatic_content_inline_size = table.automatic_content_inline_size(); let table_used_inline_size = table_used.border_box_inline_size(false); if table_wrapper_inline_size_mode == TableWrapperInlineSizeMode::UseTableUsedInlineSizeIfNotAuto @@ -1695,7 +1953,8 @@ impl<'pass> SizingContext<'pass> { .available_inner_space_or_constraints_from(available_space), containing_block_constraints: table_wrapper_constraints, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Root, }, ); diff --git a/Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs index 55552ad7b7ee6..88ba449b542e1 100644 --- a/Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs @@ -517,7 +517,7 @@ impl<'pass> SvgFormattingContext<'pass> { result } - fn run(&mut self, frame: &mut FcFrame, input: LayoutInput) { + fn run(&mut self, run: &FormattingContextRun, input: LayoutInput) { // NOTE: SVG doesn't have a "formatting context" in the spec, but this is the most // obvious way to drive SVG layout in our engine at the moment. let kind = self.node_kind(self.box_); @@ -651,7 +651,7 @@ impl<'pass> SvgFormattingContext<'pass> { while !child.is_invalid() { let next = self.next_sibling(child); if self.state.node_facts(&self.callbacks, child).is_box() { - self.layout_svg_element(frame, child, input, svg_transform_for_children); + self.layout_svg_element(run, child, input, svg_transform_for_children); } child = next; } @@ -659,7 +659,7 @@ impl<'pass> SvgFormattingContext<'pass> { fn layout_svg_element( &mut self, - frame: &mut FcFrame, + run: &FormattingContextRun, child: Node, input: LayoutInput, parent_svg_transform: FfiAffineTransform, @@ -667,7 +667,7 @@ impl<'pass> SvgFormattingContext<'pass> { let kind = self.node_kind(child); let facts = self.svg_facts(child); if facts.is_fit_to_view_box { - self.layout_nested_viewport(frame, child, parent_svg_transform); + self.layout_nested_viewport(run, child, parent_svg_transform); } else if kind == NodeKind::SVGForeignObjectBox { let child_used_pointer = self.create_used_values(child); let style = self.style_facts(child); @@ -710,33 +710,32 @@ impl<'pass> SvgFormattingContext<'pass> { ..Default::default() }, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Item, + }; + match crate::layout::layout_inside_child(run, None, None, child, self.layout_mode, child_input, true) { + crate::layout::ChildLayoutOutcome::Created(_) => {} + crate::layout::ChildLayoutOutcome::Skipped | crate::layout::ChildLayoutOutcome::ReenterCurrent => { + panic!("SVG foreign object did not create an independent formatting context") + } }; - let child_layout = - match crate::layout::layout_inside_child(frame, None, None, child, self.layout_mode, child_input, true) { - crate::layout::ChildLayoutOutcome::Created(child_layout) => child_layout, - crate::layout::ChildLayoutOutcome::Skipped | crate::layout::ChildLayoutOutcome::ReenterCurrent => { - panic!("SVG foreign object did not create an independent formatting context") - } - }; // Masks and clips may use this offset for objectBoundingBox units. self.place_child(child, transformed_rect.x, transformed_rect.y); if let Some(mask) = self.first_child_of_kind(child, NodeKind::SVGMaskBox) { - self.layout_mask_or_clip(frame, mask); + self.layout_mask_or_clip(run, mask); } if let Some(clip) = self.first_child_of_kind(child, NodeKind::SVGClipBox) { - self.layout_mask_or_clip(frame, clip); + self.layout_mask_or_clip(run, clip); } - child_layout.finish(); } else if kind_is_svg_graphics_box(kind) { - self.layout_graphics_element(frame, child, input, parent_svg_transform); + self.layout_graphics_element(run, child, input, parent_svg_transform); } } fn layout_nested_viewport( &mut self, - frame: &mut FcFrame, + run: &FormattingContextRun, viewport: Node, parent_svg_transform: FfiAffineTransform, ) { @@ -818,7 +817,7 @@ impl<'pass> SvgFormattingContext<'pass> { Some(parent_svg_transform), ); nested_context.run( - frame, + run, LayoutInput { available_space: self.available_space.unwrap(), containing_block_constraints: ContainingBlockConstraints { @@ -826,7 +825,8 @@ impl<'pass> SvgFormattingContext<'pass> { ..Default::default() }, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Item, }, ); self.set_svg_viewport_size( @@ -859,7 +859,7 @@ impl<'pass> SvgFormattingContext<'pass> { fn layout_graphics_element( &mut self, - frame: &mut FcFrame, + run: &FormattingContextRun, graphics_box: Node, input: LayoutInput, parent_svg_transform: FfiAffineTransform, @@ -885,31 +885,31 @@ impl<'pass> SvgFormattingContext<'pass> { // https://svgwg.org/svg2-draft/struct.html#Groups // 5.2. Grouping: the ‘g’ element // The ‘g’ element is a container element for grouping together related graphics elements. - self.layout_container_element(frame, graphics_box, input, svg_transform); + self.layout_container_element(run, graphics_box, input, svg_transform); } else if kind == NodeKind::SVGImageBox { self.layout_image_element(graphics_box); } else { // Assume this is a path-like element. - self.layout_path_like_element(frame, graphics_box, input); + self.layout_path_like_element(run, graphics_box, input); } if let Some(mask) = self.first_child_of_kind(graphics_box, NodeKind::SVGMaskBox) { - self.layout_mask_or_clip(frame, mask); + self.layout_mask_or_clip(run, mask); } if let Some(clip) = self.first_child_of_kind(graphics_box, NodeKind::SVGClipBox) { - self.layout_mask_or_clip(frame, clip); + self.layout_mask_or_clip(run, clip); } let mut child = self.first_child(graphics_box); while !child.is_invalid() { let next = self.next_sibling(child); if self.node_kind(child) == NodeKind::SVGPatternBox { - self.layout_mask_or_clip(frame, child); + self.layout_mask_or_clip(run, child); } child = next; } } - fn layout_path_like_element(&mut self, frame: &mut FcFrame, graphics_box: Node, input: LayoutInput) { + fn layout_path_like_element(&mut self, run: &FormattingContextRun, graphics_box: Node, input: LayoutInput) { let transforms = self .computed_transforms(graphics_box) .expect("SVG graphics box must have computed transforms"); @@ -939,7 +939,7 @@ impl<'pass> SvgFormattingContext<'pass> { while !child.is_invalid() { let next = self.next_sibling(child); if matches!(self.node_kind(child), NodeKind::SVGTextBox | NodeKind::SVGTextPathBox) { - self.layout_graphics_element(frame, child, input, transforms.svg_transform); + self.layout_graphics_element(run, child, input, transforms.svg_transform); } child = next; } @@ -997,7 +997,7 @@ impl<'pass> SvgFormattingContext<'pass> { used.has_definite_block_size.set(true); } - fn layout_mask_or_clip(&mut self, frame: &mut FcFrame, resource: Node) { + fn layout_mask_or_clip(&mut self, run: &FormattingContextRun, resource: Node) { let kind = self.node_kind(resource); let facts = self.svg_facts(resource); assert!(kind_is_svg_resource_box(kind)); @@ -1075,7 +1075,7 @@ impl<'pass> SvgFormattingContext<'pass> { Some(FfiAffineTransform::default()), ); nested_context.run( - frame, + run, LayoutInput { available_space: self.available_space.unwrap(), containing_block_constraints: ContainingBlockConstraints { @@ -1083,7 +1083,8 @@ impl<'pass> SvgFormattingContext<'pass> { ..Default::default() }, content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives::default(), + participation: ParticipationInParentFormattingContext::Item, }, ); @@ -1105,7 +1106,7 @@ impl<'pass> SvgFormattingContext<'pass> { fn layout_container_element( &mut self, - frame: &mut FcFrame, + run: &FormattingContextRun, container: Node, input: LayoutInput, container_svg_transform: FfiAffineTransform, @@ -1122,7 +1123,7 @@ impl<'pass> SvgFormattingContext<'pass> { if self.state.node_facts(&self.callbacks, child).is_box() && !kind_is_svg_resource_box(self.node_kind(child)) { - self.layout_svg_element(frame, child, input, container_svg_transform); + self.layout_svg_element(run, child, input, container_svg_transform); let child_used_pointer = self.used_values(child); let child_used = child_used_pointer; let left = child_used.content_offset.get().x; diff --git a/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs index 1dfefc9c4f6a5..5e276665e0bbc 100644 --- a/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs @@ -785,7 +785,6 @@ struct TableFormattingContext<'pass> { table_block_size: CssPixels, automatic_content_block_size: CssPixels, pending_table_offset: crate::layout::LogicalOffset, - should_publish_pending_table_offset: bool, min_border_box_block_size_from_flex_item: Option, needs_fixed_mode_row_measurement: bool, cells: Vec, @@ -832,19 +831,18 @@ impl<'pass> TableFormattingContext<'pass> { self.callbacks.arena().raw_table_column_span(column) as usize } - fn new(frame: &FcFrame<'pass>, pending_table_offset: Option) -> Self { + fn new(run: &FormattingContextRun<'pass>) -> Self { Self { - state: frame.state, - table_box: frame.box_, - layout_mode: frame.layout_mode, - callbacks: frame.callbacks, + state: run.state, + table_box: run.box_, + layout_mode: run.layout_mode, + callbacks: run.callbacks, table_constraints: ContainingBlockConstraints::default(), participant_constraints: ContainingBlockConstraints::default(), available_space: AvailableSpace::default(), table_block_size: CssPixels::default(), automatic_content_block_size: CssPixels::default(), - pending_table_offset: pending_table_offset.unwrap_or_default(), - should_publish_pending_table_offset: pending_table_offset.is_some(), + pending_table_offset: crate::layout::LogicalOffset::default(), min_border_box_block_size_from_flex_item: None, needs_fixed_mode_row_measurement: false, cells: Vec::new(), @@ -1909,24 +1907,26 @@ impl<'pass> TableFormattingContext<'pass> { fn layout_inside_cell( &mut self, - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, parent: Option<&BlockFormattingContext<'pass>>, cell: TableCell, input: AvailableSpace, - ) -> Option> { + adopt_automatic_content_block_size: bool, + ) { let layout_input = LayoutInput { available_space: input, containing_block_constraints: ContainingBlockConstraints::default(), content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, + sizing: RootSizingDirectives { + adopt_automatic_content_block_size, + ..RootSizingDirectives::default() + }, + participation: ParticipationInParentFormattingContext::Item, }; - match crate::layout::layout_inside_child(frame, None, None, cell.box_, self.layout_mode, layout_input, false) { - crate::layout::ChildLayoutOutcome::Skipped => None, - crate::layout::ChildLayoutOutcome::Created(child_layout) => Some(child_layout), - crate::layout::ChildLayoutOutcome::ReenterCurrent => { - self.run(frame, parent, layout_input); - None - } + if let crate::layout::ChildLayoutOutcome::ReenterCurrent = + crate::layout::layout_inside_child(run, None, None, cell.box_, self.layout_mode, layout_input, false) + { + self.run(run, parent, layout_input); } } @@ -1991,12 +1991,7 @@ impl<'pass> TableFormattingContext<'pass> { let result = measurement.run_with_layout_mode( cell.box_, self.layout_mode, - LayoutInput { - available_space: inner, - containing_block_constraints: ContainingBlockConstraints::default(), - content_box_position_in_bfc_root: None, - table_grid_min_border_box_block_size: None, - }, + LayoutInput::new(inner, ContainingBlockConstraints::default(), ParticipationInParentFormattingContext::Item), ); measurement .root_used() @@ -2012,7 +2007,7 @@ impl<'pass> TableFormattingContext<'pass> { }) } - fn compute_table_block_size(&mut self, frame: &mut FcFrame<'pass>, parent: Option<&BlockFormattingContext<'pass>>) { + fn compute_table_block_size(&mut self, run: &FormattingContextRun<'pass>, parent: Option<&BlockFormattingContext<'pass>>) { // First pass of row block-size calculation: for row_index in 0..self.rows.len() { if self.rows[row_index].is_collapsed { @@ -2077,9 +2072,8 @@ impl<'pass> TableFormattingContext<'pass> { used.set_content_block_size(measured.content_block_size); measured_baseline = Some(measured.first_baseline); } - } else if let Some(child_layout) = self.layout_inside_cell(frame, parent, cell, inner) { - used.set_content_block_size(child_layout.result().automatic_content_block_size); - child_layout.finish(); + } else { + self.layout_inside_cell(run, parent, cell, inner, true); } if self.needs_fixed_mode_row_measurement { let min_size = style.min_height().to_px(participant_block_basis); @@ -2192,9 +2186,7 @@ impl<'pass> TableFormattingContext<'pass> { let inner = used.available_inner_space_or_constraints_from(self.available_space); // The first pass only measured this cell in a throwaway state; this is its one and // only inside layout in the committing state. - if let Some(child_layout) = self.layout_inside_cell(frame, parent, cell, inner) { - child_layout.finish(); - } + self.layout_inside_cell(run, parent, cell, inner, false); let baseline = self.box_baseline(cell.box_); self.cells[cell_index].baseline = baseline; if !self.rows[cell.row_index].is_collapsed { @@ -2435,7 +2427,7 @@ impl<'pass> TableFormattingContext<'pass> { fn run_caption_layout( &mut self, - frame: &mut FcFrame<'pass>, + run: &FormattingContextRun<'pass>, parent: Option<&BlockFormattingContext<'pass>>, phase: CaptionPhase, available: AvailableSpace, @@ -2451,7 +2443,7 @@ impl<'pass> TableFormattingContext<'pass> { // and are rendered as normal block boxes inside the table wrapper box, as described in https://www.w3.org/TR/CSS22/tables.html#model let result = if let Some(parent) = parent { parent.layout_table_caption( - frame, + run, self.table_box, caption, phase, @@ -2464,7 +2456,7 @@ impl<'pass> TableFormattingContext<'pass> { // formatting-context instance. BlockFormattingContext::new(self.state, self.table_box, self.layout_mode, self.callbacks) .layout_table_caption( - frame, + run, self.table_box, caption, phase, @@ -2484,9 +2476,10 @@ impl<'pass> TableFormattingContext<'pass> { crate::layout::compute_and_store_baselines(self.state, &self.callbacks, node, false); } - fn run(&mut self, frame: &mut FcFrame<'pass>, parent: Option<&BlockFormattingContext<'pass>>, input: LayoutInput) { + fn run(&mut self, run: &FormattingContextRun<'pass>, parent: Option<&BlockFormattingContext<'pass>>, input: LayoutInput) { self.available_space = input.available_space; - self.min_border_box_block_size_from_flex_item = input.table_grid_min_border_box_block_size; + self.min_border_box_block_size_from_flex_item = input.sizing.forced_min_border_box_block_size; + self.pending_table_offset = input.sizing.table_box_content_offset_in_wrapper.unwrap_or_default(); self.run_until_inline_size_calculation(input, false); if matches!( self.available_space.inline_size, @@ -2504,7 +2497,7 @@ impl<'pass> TableFormattingContext<'pass> { inline_size: AvailableSize::definite(table_border_inline.min(max_dimension_value())), block_size: self.available_space.block_size, }; - let mut captions = self.run_caption_layout(frame, parent, CaptionPhase::Top, caption_available); + let mut captions = self.run_caption_layout(run, parent, CaptionPhase::Top, caption_available); // The total inline-axis border spacing is defined for each table: // - For tables laid out in separated-borders mode containing at least one column, the inline-axis component of the computed value of the border-spacing property times one plus the number of columns in the table // - Otherwise, 0 @@ -2518,12 +2511,12 @@ impl<'pass> TableFormattingContext<'pass> { let fixed = self.use_fixed_mode_layout(); // Distribute the inline size of the table among columns. distribute_inline_size(&mut self.columns, assignable, fixed); - self.compute_table_block_size(frame, parent); + self.compute_table_block_size(run, parent); self.distribute_block_size_to_rows(); self.position_row_boxes(); self.position_cell_boxes(); table_used.set_content_block_size(self.table_block_size); - captions += self.run_caption_layout(frame, parent, CaptionPhase::Bottom, caption_available); + captions += self.run_caption_layout(run, parent, CaptionPhase::Bottom, caption_available); // Table captions are positioned between the table margins and its borders (outside the grid box borders) as described in // https://www.w3.org/TR/css-tables-3/#bounding-box-assignment // A visual representation of this model can be found at https://www.w3.org/TR/css-tables-3/images/table_container.png