diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index c8ffec6b15d93..52d7ad93f8047 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -42,128 +42,6 @@ namespace Web::Layout { -static RustFFI::FfiTableDisplay table_display(CSS::Display display) -{ - if (display.is_table_inside()) - return RustFFI::FfiTableDisplay::TableRoot; - if (display.is_table_row_group()) - return RustFFI::FfiTableDisplay::TableRowGroup; - if (display.is_table_header_group()) - return RustFFI::FfiTableDisplay::TableHeaderGroup; - if (display.is_table_footer_group()) - return RustFFI::FfiTableDisplay::TableFooterGroup; - if (display.is_table_column_group()) - return RustFFI::FfiTableDisplay::TableColumnGroup; - if (display.is_table_column()) - return RustFFI::FfiTableDisplay::TableColumn; - if (display.is_table_row()) - return RustFFI::FfiTableDisplay::TableRow; - if (display.is_table_cell()) - return RustFFI::FfiTableDisplay::TableCell; - if (display.is_table_caption()) - return RustFFI::FfiTableDisplay::TableCaption; - return RustFFI::FfiTableDisplay::Other; -} - -static u8 display_bits(CSS::ComputedValues const& computed_values) -{ - auto display = computed_values.display(); - u8 bits = 0; - auto set = [&](RustFFI::NodeDisplayFlag flag, bool value) { - if (value) - bits |= static_cast(flag); - }; - set(RustFFI::NodeDisplayFlag::InlineOutside, display.is_inline_outside()); - set(RustFFI::NodeDisplayFlag::FlowInside, display.is_flow_inside()); - set(RustFFI::NodeDisplayFlag::FlexInside, display.is_flex_inside()); - set(RustFFI::NodeDisplayFlag::GridInside, display.is_grid_inside()); - set(RustFFI::NodeDisplayFlag::MathInside, display.is_math_inside()); - set(RustFFI::NodeDisplayFlag::Floating, computed_values.float_() != CSS::Float::None); - auto position = computed_values.position(); - set(RustFFI::NodeDisplayFlag::AbsolutelyPositioned, - position == CSS::Positioning::Absolute || position == CSS::Positioning::Fixed); - set(RustFFI::NodeDisplayFlag::BlockOutsideBeforeBoxTypeTransformation, - computed_values.display_before_box_type_transformation().is_block_outside()); - return bits; -} - -// https://drafts.csswg.org/css-contain-2/#containment-types -// Mirrors NodeWithStyle::has_layout_containment() / has_paint_containment() with the -// is_replaced_box() escape dropped: the stamped flag is only consulted after the Rust -// side has already excluded replaced boxes from creating a block formatting context. -static bool containment_applies_to_principal_box(CSS::Display display) -{ - if (display.is_internal_table() && !display.is_table_cell()) - return false; - if (display.is_inline_outside() && display.is_flow_inside()) - return false; - return true; -} - -// The computed-style-only half of the block-formatting-context predicate. Terms that -// need the node kind, the DOM, or the parent (replaced boxes, SVG foreignObject, -// table/flex/grid insides, the root element, fieldsets, button layout, and flex/grid -// parents) are evaluated by the Rust side over stamped NodeData instead: the first -// mirror runs from the NodeWithStyle constructor, before the most-derived class has -// assigned its node kind. -static bool own_computed_style_establishes_block_formatting_context(CSS::ComputedValues const& computed_values) -{ - auto display = computed_values.display(); - - // The float term is deliberately absent: floating only establishes a block - // formatting context for non-flex-items, and the IsFlexItem flag is written - // during layout, after this stamp. The Rust composite evaluates that term - // from the Floating display bit and the live IsFlexItem flag instead. - - // Absolutely positioned elements (elements where position is absolute or fixed). - auto position = computed_values.position(); - if (position == CSS::Positioning::Absolute || position == CSS::Positioning::Fixed) - return true; - - // Inline-blocks (elements with display: inline-block). - if (display.is_inline_block()) - return true; - - // Table cells and table captions. - if (display.is_table_cell() || display.is_table_caption()) - return true; - - // Block elements where overflow has a value other than visible and clip. - CSS::Overflow overflow_x = computed_values.overflow_x(); - if (overflow_x != CSS::Overflow::Visible && overflow_x != CSS::Overflow::Clip) - return true; - CSS::Overflow overflow_y = computed_values.overflow_y(); - if (overflow_y != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Clip) - return true; - - // display: flow-root. - if (display.is_flow_root_inside()) - return true; - - // https://drafts.csswg.org/css-contain-2/#containment-types - // 1. The layout containment box establishes an independent formatting context. - // 4. The paint containment box establishes an independent formatting context. - bool content_visibility_forces_containment = computed_values.content_visibility() == CSS::ContentVisibility::Auto; - if ((computed_values.contain().layout_containment || computed_values.contain().paint_containment - || content_visibility_forces_containment) - && containment_applies_to_principal_box(display)) - return true; - - // https://drafts.csswg.org/css-conditional-5/#valdef-container-type-size - // Applies style containment and size containment to the principal box, and establishes an independent formatting - // context. - if (computed_values.container_type().is_size_container || computed_values.container_type().is_inline_size_container) - return true; - - // https://drafts.csswg.org/css-multicol-2/#the-multi-column-model - // An element whose 'column-width', 'column-count', or 'column-height' property is not 'auto' establishes a multi- - // column container (or multicol container for short), and therefore acts as a container for multi-column layout. - if (!computed_values.column_width().is_auto() || !computed_values.column_count().is_auto()) - return true; - - return false; -} - NodeArenaAllocation::NodeArenaAllocation(DOM::Document& document) : m_arena(document.layout_node_arena()) { @@ -1089,11 +967,6 @@ void NodeWithStyle::set_computed_values(NonnullRefPtr void NodeWithStyle::mirror_computed_values_to_node_data() { node_data().style = m_computed_values.ptr(); - node_data().table_display = table_display(m_computed_values->display()); - node_data().table_display_before = table_display(m_computed_values->display_before_box_type_transformation()); - node_data().display_bits = display_bits(*m_computed_values); - set_flag(RustFFI::NodeFlag::OwnStyleEstablishesBlockFormattingContext, - own_computed_style_establishes_block_formatting_context(*m_computed_values)); RustFFI::FfiStylePayloads style_payloads {}; m_computed_values->fill_style_group_payloads({ style_payloads.groups, array_size(style_payloads.groups) }); diff --git a/Libraries/LibWeb/Layout/Node.h b/Libraries/LibWeb/Layout/Node.h index 391054cd16ea7..e2d79dfb6aebd 100644 --- a/Libraries/LibWeb/Layout/Node.h +++ b/Libraries/LibWeb/Layout/Node.h @@ -43,19 +43,14 @@ static_assert(offsetof(RustFFI::NodeData, generated_for) == 29); static_assert(offsetof(RustFFI::NodeData, intrinsic_cache_epoch) == 30); static_assert(offsetof(RustFFI::NodeData, flags) == 32); static_assert(offsetof(RustFFI::NodeData, initial_quote_nesting_level) == 36); -static_assert(offsetof(RustFFI::NodeData, table_display) == 40); -static_assert(offsetof(RustFFI::NodeData, table_display_before) == 41); -static_assert(offsetof(RustFFI::NodeData, display_bits) == 42); -static_assert(offsetof(RustFFI::NodeData, slot_generation) == 43); -static_assert(offsetof(RustFFI::NodeData, table_column_span) == 44); -static_assert(offsetof(RustFFI::NodeData, table_row_span) == 46); +static_assert(offsetof(RustFFI::NodeData, slot_generation) == 40); +static_assert(offsetof(RustFFI::NodeData, table_column_span) == 42); +static_assert(offsetof(RustFFI::NodeData, table_row_span) == 44); static_assert(offsetof(RustFFI::NodeData, style) == 48); static_assert(offsetof(RustFFI::NodeData, shell) == 56); static_assert(sizeof(RustFFI::NodeKind) == sizeof(u8)); static_assert(sizeof(RustFFI::NodeFlag) == sizeof(u32)); -static_assert(sizeof(RustFFI::NodeDisplayFlag) == sizeof(u8)); -static_assert(sizeof(RustFFI::FfiTableDisplay) == sizeof(u8)); class NodeKindSetter; diff --git a/Libraries/LibWeb/Rust/build.rs b/Libraries/LibWeb/Rust/build.rs index ca199b2e6df88..541640c02e525 100644 --- a/Libraries/LibWeb/Rust/build.rs +++ b/Libraries/LibWeb/Rust/build.rs @@ -988,10 +988,8 @@ fn main() -> Result<(), Box> { tree_builder_config.export.include = vec![ "FfiNodeKindFacts".to_string(), "FfiStylePayloads".to_string(), - "FfiTableDisplay".to_string(), "NodeAllocation".to_string(), "NodeData".to_string(), - "NodeDisplayFlag".to_string(), "NodeFlag".to_string(), "NodeKind".to_string(), "NodeSlotId".to_string(), diff --git a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs index e89eb3f8d8fae..983d80ecb4505 100644 --- a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs @@ -2907,6 +2907,14 @@ impl FfiLayoutFcCallbacks { unsafe { &*std::ptr::from_ref(payloads) } } + pub(crate) fn style_reader_if_styled(&self, node: Node) -> Option> { + let payloads = self.arena().style_payloads(node)?; + // SAFETY: The document arena outlives the layout pass, and the mirror + // is only rewritten between passes: set_computed_values verifies no + // pass is running and no layout node is created mid-pass. + Some(StyleReader::new(unsafe { &*std::ptr::from_ref(payloads) })) + } + pub(crate) fn can_skip_is_anonymous_text_run(&self, node: Node) -> bool { let data = self.node_data(node); if !crate::layout::has_flag(data, NodeFlag::Anonymous) || data.generated_for != 0 { @@ -3070,7 +3078,8 @@ impl std::ops::DerefMut for FormattingContextInstance<'_> { pub(crate) fn formatting_context_type_created_by_node_data( data: &NodeData, - parent_data: Option<&NodeData>, + style: Option>, + parent_style: Option>, ) -> Option { if data.kind == crate::layout::node_data::NodeKind::SVGSVGBox { return Some(FfiFormattingContextType::Svg); @@ -3087,7 +3096,7 @@ pub(crate) fn formatting_context_type_created_by_node_data( return None; } if crate::layout::has_flag(data, NodeFlag::IsReplacedElement) - && data.table_display_before != crate::layout::node_data::FfiTableDisplay::Other + && style.is_some_and(|style| style.table_display_before() != FfiTableDisplay::Other) { return Some(if crate::layout::kind_is_block_container(data.kind) { FfiFormattingContextType::Block @@ -3095,41 +3104,47 @@ pub(crate) fn formatting_context_type_created_by_node_data( FfiFormattingContextType::InternalReplaced }); } - if crate::layout::has_display_flag(data, crate::layout::node_data::NodeDisplayFlag::FlexInside) { + let display = style.map(|style| style.display()); + if display.is_some_and(|display| display.is_flex_inside()) { return Some(FfiFormattingContextType::Flex); } - if data.table_display == crate::layout::node_data::FfiTableDisplay::TableRoot { + let table_display = display.map_or(FfiTableDisplay::Other, crate::layout::table_display_of); + if table_display == FfiTableDisplay::TableRoot { return Some(FfiFormattingContextType::Table); } - if crate::layout::has_display_flag(data, crate::layout::node_data::NodeDisplayFlag::GridInside) { + if display.is_some_and(|display| display.is_grid_inside()) { return Some(FfiFormattingContextType::Grid); } - if crate::layout::has_display_flag(data, crate::layout::node_data::NodeDisplayFlag::MathInside) - || crate::layout::node_creates_block_formatting_context(data, parent_data) + if display.is_some_and(|display| display.is_math_inside()) + || crate::layout::node_creates_block_formatting_context(data, style, parent_style) { return Some(FfiFormattingContextType::Block); } if crate::layout::has_flag(data, NodeFlag::ChildrenAreInline) || matches!( - data.table_display, - crate::layout::node_data::FfiTableDisplay::TableColumn - | crate::layout::node_data::FfiTableDisplay::TableColumnGroup - | crate::layout::node_data::FfiTableDisplay::TableRow - | crate::layout::node_data::FfiTableDisplay::TableRowGroup - | crate::layout::node_data::FfiTableDisplay::TableHeaderGroup - | crate::layout::node_data::FfiTableDisplay::TableFooterGroup + table_display, + FfiTableDisplay::TableColumn + | FfiTableDisplay::TableColumnGroup + | FfiTableDisplay::TableRow + | FfiTableDisplay::TableRowGroup + | FfiTableDisplay::TableHeaderGroup + | FfiTableDisplay::TableFooterGroup ) { return None; } - if !crate::layout::has_display_flag(data, crate::layout::node_data::NodeDisplayFlag::FlowInside) { + if !display.is_some_and(|display| display.is_flow_inside()) { return Some(FfiFormattingContextType::InternalDummy); } None } pub(crate) fn formatting_context_type_created_by_box(facts: NodeFacts<'_>) -> Option { - formatting_context_type_created_by_node_data(facts.data(), facts.parent_data()) + formatting_context_type_created_by_node_data( + facts.data(), + facts.style_reader_if_styled(), + facts.parent_style_reader_if_styled(), + ) } #[derive(Clone, Copy)] @@ -3147,9 +3162,12 @@ pub extern "C" fn rust_layout_formatting_context_type_for_box(facts: FfiFormatti let arena = unsafe { LayoutNodeArena::from_handle(facts.arena) }; // SAFETY: The caller supplies the live box's arena slot. let data = unsafe { &*arena.data(facts.node) }; - // SAFETY: Parent links resolve within the same live arena. - let parent_data = (!data.parent.is_invalid()).then(|| unsafe { &*arena.data(data.parent) }); - formatting_context_type_created_by_node_data(data, parent_data) + let style = arena.style_payloads(facts.node).map(StyleReader::new); + let parent_style = (!data.parent.is_invalid()) + .then(|| arena.style_payloads(data.parent)) + .flatten() + .map(StyleReader::new); + formatting_context_type_created_by_node_data(data, style, parent_style) .map(|type_| type_ as u8) .unwrap_or(NO_FORMATTING_CONTEXT) }) diff --git a/Libraries/LibWeb/Rust/src/layout/layout_state.rs b/Libraries/LibWeb/Rust/src/layout/layout_state.rs index 7a25b3ae7b6b3..c1c4d55ed747d 100644 --- a/Libraries/LibWeb/Rust/src/layout/layout_state.rs +++ b/Libraries/LibWeb/Rust/src/layout/layout_state.rs @@ -424,6 +424,23 @@ impl<'pass> NodeFacts<'pass> { self.state.style_facts(self.callbacks, self.node) } + fn style_reader_if_styled(&self) -> Option> { + self.callbacks.style_reader_if_styled(self.node) + } + + fn parent_style_reader_if_styled(&self) -> Option> { + let parent = self.data().parent; + if parent.is_invalid() { + return None; + } + self.callbacks.style_reader_if_styled(parent) + } + + fn table_display(&self) -> FfiTableDisplay { + self.style_reader_if_styled() + .map_or(FfiTableDisplay::Other, |style| style.table_display()) + } + fn replaced_content(&self) -> crate::layout::FfiReplacedContentFacts { self.state.replaced_content_facts(self.callbacks, self.node) } @@ -458,24 +475,29 @@ impl<'pass> NodeFacts<'pass> { } pub(crate) fn is_floating(&self) -> bool { - crate::layout::has_display_flag(self.data(), NodeDisplayFlag::Floating) + self.style_reader_if_styled().is_some_and(|style| style.is_floating()) } pub(crate) fn is_absolutely_positioned(&self) -> bool { - crate::layout::has_display_flag(self.data(), NodeDisplayFlag::AbsolutelyPositioned) + self.style_reader_if_styled() + .is_some_and(|style| style.is_absolutely_positioned()) } pub(crate) fn is_inline(&self) -> bool { - let data = self.data(); - crate::layout::kind_is_text(data.kind) || crate::layout::has_display_flag(data, NodeDisplayFlag::InlineOutside) + crate::layout::kind_is_text(self.data().kind) + || self + .style_reader_if_styled() + .is_some_and(|style| style.display().is_inline_outside()) } pub(crate) fn is_atomic_inline(&self) -> bool { let data = self.data(); crate::layout::has_flag(data, NodeFlag::IsReplacedElement) || data.kind == NodeKind::ListItemMarkerBox - || (crate::layout::has_display_flag(data, NodeDisplayFlag::InlineOutside) - && !crate::layout::has_display_flag(data, NodeDisplayFlag::FlowInside)) + || self.style_reader_if_styled().is_some_and(|style| { + let display = style.display(); + display.is_inline_outside() && !display.is_flow_inside() + }) } pub(crate) fn has_box_model_metrics(&self) -> bool { @@ -494,8 +516,10 @@ impl<'pass> NodeFacts<'pass> { let data = self.data(); data.kind == NodeKind::InlineNode || (data.kind == NodeKind::ListItemBox - && crate::layout::has_display_flag(data, NodeDisplayFlag::InlineOutside) - && crate::layout::has_display_flag(data, NodeDisplayFlag::FlowInside)) + && self.style_reader_if_styled().is_some_and(|style| { + let display = style.display(); + display.is_inline_outside() && display.is_flow_inside() + })) } pub(crate) fn is_inline_flow_interrupting_block(&self) -> bool { @@ -506,18 +530,23 @@ impl<'pass> NodeFacts<'pass> { let Some(parent) = self.parent_data() else { return false; }; - if !crate::layout::has_display_flag(parent, NodeDisplayFlag::InlineOutside) - || !crate::layout::has_display_flag(parent, NodeDisplayFlag::FlowInside) - { + let parent_is_inline_flow = self.parent_style_reader_if_styled().is_some_and(|style| { + let display = style.display(); + display.is_inline_outside() && display.is_flow_inside() + }); + if !parent_is_inline_flow { return false; } - if crate::layout::has_display_flag(data, NodeDisplayFlag::InlineOutside) || crate::layout::node_is_out_of_flow(data) { + let style = self.style_reader_if_styled(); + if style.is_some_and(|style| style.display().is_inline_outside()) + || crate::layout::node_is_out_of_flow(data, style) + { return false; } if self.display().is_contents() { return false; } - if !matches!(data.table_display, FfiTableDisplay::Other | FfiTableDisplay::TableRoot) { + if !matches!(self.table_display(), FfiTableDisplay::Other | FfiTableDisplay::TableRoot) { return false; } if parent.kind == NodeKind::SVGForeignObjectBox { @@ -554,7 +583,8 @@ impl<'pass> NodeFacts<'pass> { } pub(crate) fn display_before_box_type_transformation_is_block_outside(&self) -> bool { - crate::layout::has_display_flag(self.data(), NodeDisplayFlag::BlockOutsideBeforeBoxTypeTransformation) + self.style_reader_if_styled() + .is_some_and(|style| style.display_before_box_type_transformation().is_block_outside()) } pub(crate) fn inline_axis_is_reverse(&self) -> bool { @@ -590,12 +620,18 @@ impl<'pass> NodeFacts<'pass> { } pub(crate) fn has_replaced_element_table_display_adjustment(&self) -> bool { - let data = self.data(); - crate::layout::has_flag(data, NodeFlag::IsReplacedElement) && data.table_display_before != FfiTableDisplay::Other + crate::layout::has_flag(self.data(), NodeFlag::IsReplacedElement) + && self + .style_reader_if_styled() + .is_some_and(|style| style.table_display_before() != FfiTableDisplay::Other) } pub(crate) fn creates_block_formatting_context(&self) -> bool { - crate::layout::node_creates_block_formatting_context(self.data(), self.parent_data()) + crate::layout::node_creates_block_formatting_context( + self.data(), + self.style_reader_if_styled(), + self.parent_style_reader_if_styled(), + ) } pub(crate) fn is_grid_item(&self) -> bool { @@ -633,7 +669,9 @@ impl<'pass> NodeFacts<'pass> { let mut child = data.first_child; while !child.is_invalid() { let child_data = self.callbacks.node_data(child); - if child_data.kind == NodeKind::LegendBox && !crate::layout::node_is_out_of_flow(child_data) { + if child_data.kind == NodeKind::LegendBox + && !crate::layout::node_is_out_of_flow(child_data, self.callbacks.style_reader_if_styled(child)) + { return child; } child = child_data.next_sibling; @@ -699,7 +737,7 @@ impl<'pass> NodeFacts<'pass> { fn node_has_size_containment(&self) -> bool { if !matches!( - self.data().table_display, + self.table_display(), FfiTableDisplay::Other | FfiTableDisplay::TableCaption ) { return false; @@ -774,7 +812,7 @@ impl<'pass> NodeFacts<'pass> { } pub(crate) fn is_table_box(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableRoot + self.table_display() == FfiTableDisplay::TableRoot } pub(crate) fn is_table_wrapper(&self) -> bool { @@ -782,35 +820,35 @@ impl<'pass> NodeFacts<'pass> { } pub(crate) fn is_table_row_group(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableRowGroup + self.table_display() == FfiTableDisplay::TableRowGroup } pub(crate) fn is_table_header_group(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableHeaderGroup + self.table_display() == FfiTableDisplay::TableHeaderGroup } pub(crate) fn is_table_footer_group(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableFooterGroup + self.table_display() == FfiTableDisplay::TableFooterGroup } pub(crate) fn is_table_row(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableRow + self.table_display() == FfiTableDisplay::TableRow } pub(crate) fn is_table_cell(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableCell + self.table_display() == FfiTableDisplay::TableCell } pub(crate) fn is_table_column_group(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableColumnGroup + self.table_display() == FfiTableDisplay::TableColumnGroup } pub(crate) fn is_table_column(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableColumn + self.table_display() == FfiTableDisplay::TableColumn } pub(crate) fn is_table_caption(&self) -> bool { - self.data().table_display == FfiTableDisplay::TableCaption + self.table_display() == FfiTableDisplay::TableCaption } pub(crate) fn is_viewport(&self) -> bool { diff --git a/Libraries/LibWeb/Rust/src/layout/mod.rs b/Libraries/LibWeb/Rust/src/layout/mod.rs index 9ce434cba3bf8..46bc3d783b5a5 100644 --- a/Libraries/LibWeb/Rust/src/layout/mod.rs +++ b/Libraries/LibWeb/Rust/src/layout/mod.rs @@ -45,9 +45,7 @@ use crate::layout::layout_node_arena::IntrinsicSizeCacheKey; use crate::layout::layout_node_arena::IntrinsicSizeCacheKind; use crate::layout::layout_node_arena::LayoutNodeArena; pub use crate::layout::node_data::FfiStylePayloads; -use crate::layout::node_data::FfiTableDisplay; use crate::layout::node_data::NodeData; -use crate::layout::node_data::NodeDisplayFlag; use crate::layout::node_data::NodeFlag; use crate::layout::node_data::NodeKind; use crate::layout::node_data::NodeSlotId; diff --git a/Libraries/LibWeb/Rust/src/layout/node_data.rs b/Libraries/LibWeb/Rust/src/layout/node_data.rs index 69238aef529a7..a5c0f3b8787ec 100644 --- a/Libraries/LibWeb/Rust/src/layout/node_data.rs +++ b/Libraries/LibWeb/Rust/src/layout/node_data.rs @@ -143,40 +143,9 @@ pub enum NodeFlag { UsesButtonLayout = 1 << 16, IsEditingHost = 1 << 17, ReplacedBoxCanHaveChildren = 1 << 18, - OwnStyleEstablishesBlockFormattingContext = 1 << 19, - HasSavedAbsposLayoutInputs = 1 << 20, - SavedAbsposCbDerivesFromOwnComputedValues = 1 << 21, - SavedAbsposAlignmentDerivesFromOwnComputedValues = 1 << 22, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[repr(u8)] -// NB: Some variants are only constructed by C++ through the FFI. -#[allow(dead_code)] -pub enum FfiTableDisplay { - Other, - TableRoot, - TableRowGroup, - TableHeaderGroup, - TableFooterGroup, - TableColumnGroup, - TableColumn, - TableRow, - TableCell, - TableCaption, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[repr(u8)] -pub enum NodeDisplayFlag { - InlineOutside = 1 << 0, - FlowInside = 1 << 1, - FlexInside = 1 << 2, - GridInside = 1 << 3, - Floating = 1 << 4, - AbsolutelyPositioned = 1 << 5, - MathInside = 1 << 6, - BlockOutsideBeforeBoxTypeTransformation = 1 << 7, + HasSavedAbsposLayoutInputs = 1 << 19, + SavedAbsposCbDerivesFromOwnComputedValues = 1 << 20, + SavedAbsposAlignmentDerivesFromOwnComputedValues = 1 << 21, } #[repr(C)] @@ -193,9 +162,6 @@ pub struct NodeData { pub intrinsic_cache_epoch: u16, pub flags: u32, pub initial_quote_nesting_level: u32, - pub table_display: FfiTableDisplay, - pub table_display_before: FfiTableDisplay, - pub display_bits: u8, pub slot_generation: u8, pub table_column_span: u16, pub table_row_span: u16, @@ -218,9 +184,6 @@ impl Default for NodeData { intrinsic_cache_epoch: 0, flags: 0, initial_quote_nesting_level: 0, - table_display: FfiTableDisplay::Other, - table_display_before: FfiTableDisplay::Other, - display_bits: 0, slot_generation: 0, table_column_span: 1, table_row_span: 1, @@ -232,7 +195,7 @@ impl Default for NodeData { #[cfg(test)] mod tests { - use crate::layout::node_data::{MAX_NODE_SLOT_COUNT, NodeData, NodeDisplayFlag, NodeFlag, NodeKind, NodeSlotId}; + use crate::layout::node_data::{MAX_NODE_SLOT_COUNT, NodeData, NodeFlag, NodeKind, NodeSlotId}; #[test] fn node_kind_has_a_stable_default_and_byte_width() { @@ -245,7 +208,7 @@ mod tests { assert_eq!(std::mem::size_of::(), 64); assert_eq!(std::mem::offset_of!(NodeData, intrinsic_cache_epoch), 30); assert_eq!(std::mem::offset_of!(NodeData, flags), 32); - assert_eq!(std::mem::offset_of!(NodeData, slot_generation), 43); + assert_eq!(std::mem::offset_of!(NodeData, slot_generation), 40); assert_eq!(std::mem::offset_of!(NodeData, style), 48); assert_eq!(std::mem::offset_of!(NodeData, shell), 56); } @@ -261,11 +224,11 @@ mod tests { #[test] fn saved_abspos_flags_use_previously_unassigned_bits() { assert_eq!(NodeFlag::IsReplacedElement as u32, 1 << 12); - assert_eq!(NodeFlag::HasSavedAbsposLayoutInputs as u32, 1 << 20); - assert_eq!(NodeFlag::SavedAbsposCbDerivesFromOwnComputedValues as u32, 1 << 21); + assert_eq!(NodeFlag::HasSavedAbsposLayoutInputs as u32, 1 << 19); + assert_eq!(NodeFlag::SavedAbsposCbDerivesFromOwnComputedValues as u32, 1 << 20); assert_eq!( NodeFlag::SavedAbsposAlignmentDerivesFromOwnComputedValues as u32, - 1 << 22 + 1 << 21 ); } @@ -277,7 +240,5 @@ mod tests { assert_eq!(NodeFlag::UsesButtonLayout as u32, 1 << 16); assert_eq!(NodeFlag::IsEditingHost as u32, 1 << 17); assert_eq!(NodeFlag::ReplacedBoxCanHaveChildren as u32, 1 << 18); - assert_eq!(NodeFlag::OwnStyleEstablishesBlockFormattingContext as u32, 1 << 19); - assert_eq!(NodeDisplayFlag::BlockOutsideBeforeBoxTypeTransformation as u8, 1 << 7); } } diff --git a/Libraries/LibWeb/Rust/src/layout/node_facts.rs b/Libraries/LibWeb/Rust/src/layout/node_facts.rs index f6e5984e03db5..1e6ce57f88dd1 100644 --- a/Libraries/LibWeb/Rust/src/layout/node_facts.rs +++ b/Libraries/LibWeb/Rust/src/layout/node_facts.rs @@ -65,9 +65,11 @@ pub(crate) fn node_may_have_list_item_facts(data: &NodeData) -> bool { matches!(data.kind, NodeKind::ListItemBox | NodeKind::ListItemMarkerBox) } -pub(crate) fn node_is_out_of_flow(data: &NodeData) -> bool { - (has_display_flag(data, NodeDisplayFlag::Floating) && !has_flag(data, NodeFlag::IsFlexItem)) - || has_display_flag(data, NodeDisplayFlag::AbsolutelyPositioned) +pub(crate) fn node_is_out_of_flow(data: &NodeData, style: Option>) -> bool { + let Some(style) = style else { + return false; + }; + (style.is_floating() && !has_flag(data, NodeFlag::IsFlexItem)) || style.is_absolutely_positioned() } pub(crate) fn node_can_have_children(data: &NodeData) -> bool { @@ -89,44 +91,46 @@ pub(crate) fn node_has_auto_content_box_size(data: &NodeData) -> bool { } // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context -// The computed-style-only terms live in the stamped -// OwnStyleEstablishesBlockFormattingContext flag; this composite adds the terms -// that need the node kind, stamped DOM identity, the live IsFlexItem flag, or -// the parent's display. -pub(crate) fn node_creates_block_formatting_context(data: &NodeData, parent_data: Option<&NodeData>) -> bool { +// StyleReader::own_style_establishes_block_formatting_context covers the +// computed-style-only terms; this composite adds the terms that need the node +// kind, stamped DOM identity, the live IsFlexItem flag, or the parent's +// display. +pub(crate) fn node_creates_block_formatting_context( + data: &NodeData, + style: Option>, + parent_style: Option>, +) -> bool { if kind_is_replaced_box(data.kind) { return false; } if data.kind == NodeKind::SVGForeignObjectBox { return true; } - if data.table_display == FfiTableDisplay::TableRoot - || has_display_flag(data, NodeDisplayFlag::FlexInside) - || has_display_flag(data, NodeDisplayFlag::GridInside) - { - return false; + if let Some(style) = style { + let display = style.display(); + if style.table_display() == FfiTableDisplay::TableRoot || display.is_flex_inside() || display.is_grid_inside() + { + return false; + } + if (style.is_floating() && !has_flag(data, NodeFlag::IsFlexItem)) + || style.own_style_establishes_block_formatting_context() + { + return true; + } } if has_flag(data, NodeFlag::IsHtmlHtmlElement) - || (has_display_flag(data, NodeDisplayFlag::Floating) && !has_flag(data, NodeFlag::IsFlexItem)) - || has_flag(data, NodeFlag::OwnStyleEstablishesBlockFormattingContext) || data.kind == NodeKind::FieldSetBox || has_flag(data, NodeFlag::UsesButtonLayout) { return true; } - parent_data.is_some_and(|parent| { - has_display_flag(parent, NodeDisplayFlag::FlexInside) || has_display_flag(parent, NodeDisplayFlag::GridInside) - }) + parent_style.is_some_and(|parent| parent.display().is_flex_inside() || parent.display().is_grid_inside()) } pub(crate) fn has_flag(data: &NodeData, flag: NodeFlag) -> bool { data.flags & flag as u32 != 0 } -pub(crate) fn has_display_flag(data: &NodeData, flag: NodeDisplayFlag) -> bool { - data.display_bits & flag as u8 != 0 -} - pub(crate) fn kind_is_text(kind: NodeKind) -> bool { matches!( kind, @@ -198,8 +202,8 @@ fn kind_is_svg_box(kind: NodeKind) -> bool { #[cfg(test)] mod node_facts_tests { - use crate::layout::node_data::{NodeData, NodeDisplayFlag, NodeFlag, NodeKind}; - use crate::layout::{node_can_have_children, node_creates_block_formatting_context}; + use crate::layout::node_data::{NodeData, NodeFlag, NodeKind}; + use crate::layout::node_can_have_children; fn data_with_kind(kind: NodeKind) -> NodeData { NodeData { @@ -227,59 +231,4 @@ mod node_facts_tests { media.kind = NodeKind::VideoBox; assert!(node_can_have_children(&media)); } - - #[test] - fn block_formatting_context_composite_matches_the_retired_predicate() { - let mut replaced = data_with_kind(NodeKind::ImageBox); - replaced.flags = NodeFlag::OwnStyleEstablishesBlockFormattingContext as u32; - assert!(!node_creates_block_formatting_context(&replaced, None)); - - assert!(node_creates_block_formatting_context( - &data_with_kind(NodeKind::SVGForeignObjectBox), - None - )); - - let mut table_root = data_with_kind(NodeKind::BlockContainer); - table_root.table_display = crate::layout::node_data::FfiTableDisplay::TableRoot; - table_root.flags = NodeFlag::OwnStyleEstablishesBlockFormattingContext as u32; - assert!(!node_creates_block_formatting_context(&table_root, None)); - - let mut flex_container = data_with_kind(NodeKind::BlockContainer); - flex_container.display_bits = NodeDisplayFlag::FlexInside as u8; - flex_container.flags = NodeFlag::OwnStyleEstablishesBlockFormattingContext as u32; - assert!(!node_creates_block_formatting_context(&flex_container, None)); - - let mut root_element = data_with_kind(NodeKind::BlockContainer); - root_element.flags = NodeFlag::IsHtmlHtmlElement as u32; - assert!(node_creates_block_formatting_context(&root_element, None)); - - let mut floated = data_with_kind(NodeKind::BlockContainer); - floated.display_bits = NodeDisplayFlag::Floating as u8; - assert!(node_creates_block_formatting_context(&floated, None)); - floated.flags = NodeFlag::IsFlexItem as u32; - assert!(!node_creates_block_formatting_context(&floated, None)); - - assert!(node_creates_block_formatting_context( - &data_with_kind(NodeKind::FieldSetBox), - None - )); - - let mut button = data_with_kind(NodeKind::BlockContainer); - button.flags = NodeFlag::UsesButtonLayout as u32; - assert!(node_creates_block_formatting_context(&button, None)); - - let mut own_style = data_with_kind(NodeKind::BlockContainer); - own_style.flags = NodeFlag::OwnStyleEstablishesBlockFormattingContext as u32; - assert!(node_creates_block_formatting_context(&own_style, None)); - - let plain = data_with_kind(NodeKind::BlockContainer); - let mut grid_parent = data_with_kind(NodeKind::BlockContainer); - grid_parent.display_bits = NodeDisplayFlag::GridInside as u8; - assert!(!node_creates_block_formatting_context(&plain, None)); - assert!(!node_creates_block_formatting_context( - &plain, - Some(&data_with_kind(NodeKind::BlockContainer)) - )); - assert!(node_creates_block_formatting_context(&plain, Some(&grid_parent))); - } } diff --git a/Libraries/LibWeb/Rust/src/layout/style_facts.rs b/Libraries/LibWeb/Rust/src/layout/style_facts.rs index 9e547b3a162bf..e4e042c371a64 100644 --- a/Libraries/LibWeb/Rust/src/layout/style_facts.rs +++ b/Libraries/LibWeb/Rust/src/layout/style_facts.rs @@ -243,13 +243,65 @@ pub(crate) enum SizeField { VerticalAlign, } +/// Classification of a computed display value into the table-structure roles +/// the tree builder and table layout consult, derived on demand from the box +/// group payload. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FfiTableDisplay { + Other, + TableRoot, + TableRowGroup, + TableHeaderGroup, + TableFooterGroup, + TableColumnGroup, + TableColumn, + TableRow, + TableCell, + TableCaption, +} + +// https://drafts.csswg.org/css-contain-2/#containment-types +fn containment_applies_to_principal_box(display: crate::css::display::FfiDisplay) -> bool { + if display.is_internal_table() && !display.is_table_cell() { + return false; + } + if display.is_inline_outside() && display.is_flow_inside() { + return false; + } + true +} + +pub(crate) fn table_display_of(display: crate::css::display::FfiDisplay) -> FfiTableDisplay { + if display.is_table_inside() { + FfiTableDisplay::TableRoot + } else if display.is_table_row_group() { + FfiTableDisplay::TableRowGroup + } else if display.is_table_header_group() { + FfiTableDisplay::TableHeaderGroup + } else if display.is_table_footer_group() { + FfiTableDisplay::TableFooterGroup + } else if display.is_table_column_group() { + FfiTableDisplay::TableColumnGroup + } else if display.is_table_column() { + FfiTableDisplay::TableColumn + } else if display.is_table_row() { + FfiTableDisplay::TableRow + } else if display.is_table_cell() { + FfiTableDisplay::TableCell + } else if display.is_table_caption() { + FfiTableDisplay::TableCaption + } else { + FfiTableDisplay::Other + } +} + #[derive(Clone, Copy)] pub(crate) struct StyleReader<'a> { payloads: &'a FfiStylePayloads, } impl<'a> StyleReader<'a> { - fn new(payloads: &'a FfiStylePayloads) -> Self { + pub(crate) fn new(payloads: &'a FfiStylePayloads) -> Self { Self { payloads } } @@ -312,6 +364,95 @@ impl<'a> StyleReader<'a> { fn font_facts(&self) -> &'a crate::layout::FontLayoutFacts { self.native_group(STYLE_GROUP_INDEX_FONT) } + + pub(crate) fn display(&self) -> crate::css::display::FfiDisplay { + self.box_values().display + } + + pub(crate) fn display_before_box_type_transformation(&self) -> crate::css::display::FfiDisplay { + self.box_values().display_before_box_type_transformation + } + + pub(crate) fn table_display(&self) -> FfiTableDisplay { + table_display_of(self.display()) + } + + pub(crate) fn table_display_before(&self) -> FfiTableDisplay { + table_display_of(self.display_before_box_type_transformation()) + } + + pub(crate) fn is_floating(&self) -> bool { + self.box_values().float_ != crate::css::css_enums::float::NONE + } + + pub(crate) fn is_absolutely_positioned(&self) -> bool { + matches!( + self.box_values().position, + crate::css::css_enums::positioning::ABSOLUTE | crate::css::css_enums::positioning::FIXED + ) + } + + // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context + // The computed-style-only half of the block-formatting-context predicate; + // node_creates_block_formatting_context adds the terms that need the node + // kind, stamped DOM identity, the live IsFlexItem flag, or the parent's + // display. The float term is deliberately absent for the same reason: only + // non-flex-items establish one by floating. + pub(crate) fn own_style_establishes_block_formatting_context(&self) -> bool { + let box_values = self.box_values(); + let display = box_values.display; + + if self.is_absolutely_positioned() { + return true; + } + + if display.is_inline_block() { + return true; + } + + if display.is_table_cell() || display.is_table_caption() { + return true; + } + + let overflow_establishes_context = |overflow: u8| { + overflow != crate::css::css_enums::overflow::VISIBLE && overflow != crate::css::css_enums::overflow::CLIP + }; + if overflow_establishes_context(box_values.overflow_x) || overflow_establishes_context(box_values.overflow_y) { + return true; + } + + if display.is_flow_root_inside() { + return true; + } + + // https://drafts.csswg.org/css-contain-2/#containment-types + // 1. The layout containment box establishes an independent formatting context. + // 4. The paint containment box establishes an independent formatting context. + let content_visibility_forces_containment = self.inherited_box().content_visibility + == crate::css::css_enums::content_visibility::AUTO; + if (box_values.layout_containment || box_values.paint_containment || content_visibility_forces_containment) + && containment_applies_to_principal_box(display) + { + return true; + } + + // https://drafts.csswg.org/css-conditional-5/#valdef-container-type-size + // Applies style containment and size containment to the principal box, and establishes an independent + // formatting context. + if box_values.is_size_container || box_values.is_inline_size_container { + return true; + } + + // https://drafts.csswg.org/css-multicol-2/#the-multi-column-model + // An element whose 'column-width', 'column-count', or 'column-height' property is not 'auto' establishes a + // multi-column container (or multicol container for short), and therefore acts as a container for + // multi-column layout. + if box_values.column_width.kind != crate::layout::ComputedSizeKind::Auto || box_values.column_count_has_value { + return true; + } + + false + } } fn anchor_inset_field_index(field: SizeField) -> usize { diff --git a/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs index 69d8788c4d033..e2a5f24e86b09 100644 --- a/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs @@ -587,12 +587,15 @@ pub(crate) struct TableGrid { pub(crate) occupancy: HashSet<(usize, usize)>, } -fn matching_children(tree: &T, parent: Node, predicate: impl Fn(&NodeData) -> bool) -> Vec { +fn matching_children( + tree: &T, + parent: Node, + predicate: impl Fn(FfiTableDisplay) -> bool, +) -> Vec { let mut result = Vec::new(); let mut child = tree.first_child(parent); while !child.is_invalid() { - let data = tree.node_data(child); - if kind_is_box(data.kind) && predicate(data) { + if kind_is_box(tree.node_data(child).kind) && predicate(tree.table_display(child)) { result.push(child); } child = tree.next_sibling(child); @@ -611,8 +614,7 @@ fn count_columns_in_subtree(tree: &T, root: Node) -> usize { child = tree.next_sibling(child); } for child in children.into_iter().rev() { - let data = tree.node_data(child); - if kind_is_box(data.kind) && data.table_display == FfiTableDisplay::TableColumn { + if kind_is_box(tree.node_data(child).kind) && tree.table_display(child) == FfiTableDisplay::TableColumn { count = count.saturating_add(tree.table_column_span(child)); } stack.push(child); @@ -629,8 +631,8 @@ pub(crate) fn calculate_table_grid(tree: &T, table: Node) -> Table let mut row_count = 0usize; let mut current_row = 0usize; - for column_group in matching_children(tree, table, |data| { - data.table_display == FfiTableDisplay::TableColumnGroup + for column_group in matching_children(tree, table, |table_display| { + table_display == FfiTableDisplay::TableColumnGroup }) { column_count = column_count.saturating_add(count_columns_in_subtree(tree, column_group)); } @@ -648,7 +650,7 @@ pub(crate) fn calculate_table_grid(tree: &T, table: Node) -> Table *row_count += 1; } let mut current_column = 0usize; - for cell_box in matching_children(tree, row, |data| data.table_display == FfiTableDisplay::TableCell) { + for cell_box in matching_children(tree, row, |table_display| table_display == FfiTableDisplay::TableCell) { while current_column < *column_count && occupancy.contains(&(current_column, *current_row)) { current_column += 1; } @@ -690,15 +692,16 @@ pub(crate) fn calculate_table_grid(tree: &T, table: Node) -> Table let mut child = tree.first_child(table); while !child.is_invalid() { - let data = tree.node_data(child); - if kind_is_box(data.kind) + let child_is_box = kind_is_box(tree.node_data(child).kind); + let child_table_display = tree.table_display(child); + if child_is_box && matches!( - data.table_display, + child_table_display, FfiTableDisplay::TableRowGroup | FfiTableDisplay::TableHeaderGroup | FfiTableDisplay::TableFooterGroup ) { - for row in matching_children(tree, child, |row_data| { - row_data.table_display == FfiTableDisplay::TableRow + for row in matching_children(tree, child, |table_display| { + table_display == FfiTableDisplay::TableRow }) { process_row( tree, @@ -712,7 +715,7 @@ pub(crate) fn calculate_table_grid(tree: &T, table: Node) -> Table &mut current_row, ); } - } else if kind_is_box(data.kind) && data.table_display == FfiTableDisplay::TableRow { + } else if child_is_box && child_table_display == FfiTableDisplay::TableRow { process_row( tree, child, @@ -755,6 +758,7 @@ pub(crate) trait TableTree { fn first_child(&self, node: Node) -> Node; fn next_sibling(&self, node: Node) -> Node; fn node_data(&self, node: Node) -> &NodeData; + fn table_display(&self, node: Node) -> FfiTableDisplay; fn table_column_span(&self, node: Node) -> usize { self.node_data(node).table_column_span as usize @@ -807,6 +811,12 @@ impl TableTree for TableFormattingContext<'_> { self.callbacks.node_data(node) } + fn table_display(&self, node: Node) -> FfiTableDisplay { + self.callbacks + .style_reader_if_styled(node) + .map_or(FfiTableDisplay::Other, |style| style.table_display()) + } + fn row_is_collapsed(&self, row: Node, row_group: Option) -> bool { // CSS::Visibility::Collapse is pinned to zero in // LayoutRustBridge.cpp. diff --git a/Libraries/LibWeb/Rust/src/layout/tree_builder.rs b/Libraries/LibWeb/Rust/src/layout/tree_builder.rs index fd0957af4e16c..3c8c525a70053 100644 --- a/Libraries/LibWeb/Rust/src/layout/tree_builder.rs +++ b/Libraries/LibWeb/Rust/src/layout/tree_builder.rs @@ -6,10 +6,8 @@ use crate::abort_on_panic; use crate::layout::layout_node_arena::LayoutNodeArena; -use crate::layout::node_data::{ - FfiTableDisplay, GENERATED_FOR_MARKER, NodeData, NodeDisplayFlag, NodeFlag, NodeKind, NodeSlotId, -}; -use crate::layout::{kind_is_replaced_box, node_can_have_children}; +use crate::layout::node_data::{GENERATED_FOR_MARKER, NodeData, NodeFlag, NodeKind, NodeSlotId}; +use crate::layout::{FfiTableDisplay, StyleReader, kind_is_replaced_box, node_can_have_children}; use std::ffi::c_void; type LayoutNode = NodeSlotId; @@ -1369,7 +1367,7 @@ fn update_principal_node_after_entry( // SAFETY: `has_layout_node` guarantees that the frame owns a live principal layout node. let layout_node = unsafe { (host.callbacks.principal_layout_node)(frame) }; - let adjustment = replaced_element_display_adjustment(host.layout().data(layout_node)); + let adjustment = replaced_element_display_adjustment(&host.layout(), layout_node); if adjustment != FfiReplacedElementDisplayAdjustment::None { // SAFETY: The frame owns a live NodeWithStyle. unsafe { (host.callbacks.apply_replaced_display_adjustment)(frame, adjustment) }; @@ -1444,7 +1442,7 @@ fn update_principal_node_after_entry( }; if placement.placement == FfiPrincipalBoxPlacement::NormalInsertion { let layout_host = host.layout(); - let is_inline_outside = node_is_inline_outside(layout_host.data(layout_node)); + let is_inline_outside = node_is_inline_outside(&layout_host, layout_node); insert_node_into_inline_or_block_ancestor( &layout_host, update.state, @@ -1799,7 +1797,7 @@ fn create_pseudo_element_with_frame( // SAFETY: The frame owns a live pseudo-element layout node. unsafe { (callbacks.attach_style_resources)(frame) }; if decision == FfiPseudoElementDecision::ContentReplacement { - let adjustment = replaced_element_display_adjustment(host.layout().data(layout_node)); + let adjustment = replaced_element_display_adjustment(&host.layout(), layout_node); if adjustment != FfiReplacedElementDisplayAdjustment::None { // SAFETY: The frame owns a live NodeWithStyle. unsafe { (callbacks.apply_replaced_display_adjustment)(frame, adjustment) }; @@ -1819,7 +1817,7 @@ fn create_pseudo_element_with_frame( if let Some(insertion_mode) = insertion_mode { let layout_host = host.layout(); let current_parent = state.current_parent(); - let is_inline_outside = node_is_inline_outside(layout_host.data(layout_node)); + let is_inline_outside = node_is_inline_outside(&layout_host, layout_node); insert_node_into_inline_or_block_ancestor( &layout_host, state, @@ -1847,7 +1845,7 @@ fn create_pseudo_element_with_frame( assert!(!content_item.is_invalid()); let layout_host = host.layout(); let current_parent = state.current_parent(); - let is_inline_outside = node_is_inline_outside(layout_host.data(content_item)); + let is_inline_outside = node_is_inline_outside(&layout_host, content_item); insert_node_into_inline_or_block_ancestor( &layout_host, state, @@ -1867,15 +1865,21 @@ fn is_internal_table_display(display: FfiTableDisplay) -> bool { is_table_track(display) || is_table_track_group(display) || display == FfiTableDisplay::TableCell } -fn replaced_element_display_adjustment(data: &NodeData) -> FfiReplacedElementDisplayAdjustment { - if !node_has_flag(data, NodeFlag::IsReplacedElement) { +fn replaced_element_display_adjustment( + host: &TreeBuilderHost<'_>, + node: LayoutNode, +) -> FfiReplacedElementDisplayAdjustment { + if !node_has_flag(host.data(node), NodeFlag::IsReplacedElement) { return FfiReplacedElementDisplayAdjustment::None; } + let table_display = host.table_display(node); adjusted_table_display_for_replaced_element( - data.table_display == FfiTableDisplay::TableRoot, - !node_has_display_flag(data, NodeDisplayFlag::InlineOutside), - is_internal_table_display(data.table_display), - data.table_display == FfiTableDisplay::TableCaption, + table_display == FfiTableDisplay::TableRoot, + !host + .style(node) + .is_some_and(|style| style.display().is_inline_outside()), + is_internal_table_display(table_display), + table_display == FfiTableDisplay::TableCaption, ) } @@ -2000,10 +2004,6 @@ fn node_has_flag(data: &NodeData, flag: NodeFlag) -> bool { data.flags & flag as u32 != 0 } -fn node_has_display_flag(data: &NodeData, flag: NodeDisplayFlag) -> bool { - data.display_bits & flag as u8 != 0 -} - #[derive(Clone, Copy, PartialEq, Eq)] #[repr(C)] pub struct FfiNodeKindFacts { @@ -2109,24 +2109,32 @@ fn node_is_generated_for_pseudo_element(data: &NodeData) -> bool { data.generated_for != 0 } -fn node_is_inline_outside(data: &NodeData) -> bool { - node_kind_is_text(data.kind) || node_has_display_flag(data, NodeDisplayFlag::InlineOutside) +fn node_is_inline_outside(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool { + node_kind_is_text(host.data(node).kind) + || host + .style(node) + .is_some_and(|style| style.display().is_inline_outside()) } -fn node_is_out_of_flow(data: &NodeData) -> bool { - (node_has_display_flag(data, NodeDisplayFlag::Floating) && !node_has_flag(data, NodeFlag::IsFlexItem)) - || node_has_display_flag(data, NodeDisplayFlag::AbsolutelyPositioned) +fn node_is_out_of_flow(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool { + crate::layout::node_is_out_of_flow(host.data(node), host.style(node)) } -fn node_has_replaced_element_table_display_adjustment(data: &NodeData) -> bool { - node_has_flag(data, NodeFlag::IsReplacedElement) && data.table_display_before != FfiTableDisplay::Other +fn node_has_replaced_element_table_display_adjustment(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool { + node_has_flag(host.data(node), NodeFlag::IsReplacedElement) + && host + .style(node) + .is_some_and(|style| style.table_display_before() != FfiTableDisplay::Other) } -fn node_is_fragmented_inline(data: &NodeData) -> bool { +fn node_is_fragmented_inline(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool { + let data = host.data(node); data.kind == NodeKind::InlineNode || (data.kind == NodeKind::ListItemBox - && node_has_display_flag(data, NodeDisplayFlag::InlineOutside) - && node_has_display_flag(data, NodeDisplayFlag::FlowInside)) + && host.style(node).is_some_and(|style| { + let display = style.display(); + display.is_inline_outside() && display.is_flow_inside() + })) } impl TreeBuilderHost<'_> { @@ -2137,6 +2145,23 @@ impl TreeBuilderHost<'_> { unsafe { &*(*self.arena).data(node) } } + fn style(&self, node: LayoutNode) -> Option> { + assert!(!node.is_invalid()); + // SAFETY: Entry points guarantee that the arena remains live, and callers only retain the reader until the + // next mutation callback. + unsafe { (*self.arena).style_payloads(node) }.map(StyleReader::new) + } + + fn table_display(&self, node: LayoutNode) -> FfiTableDisplay { + self.style(node) + .map_or(FfiTableDisplay::Other, |style| style.table_display()) + } + + fn table_display_before(&self, node: LayoutNode) -> FfiTableDisplay { + self.style(node) + .map_or(FfiTableDisplay::Other, |style| style.table_display_before()) + } + fn shell(&self, node: LayoutNode) -> *mut c_void { let shell = self.data(node).shell; assert!(!shell.is_null()); @@ -2254,6 +2279,10 @@ impl crate::layout::TableTree for TreeBuilderHost<'_> { fn node_data(&self, node: LayoutNode) -> &NodeData { self.data(node) } + + fn table_display(&self, node: LayoutNode) -> FfiTableDisplay { + TreeBuilderHost::table_display(self, node) + } } fn is_inclusive_layout_ancestor_of(host: &TreeBuilderHost<'_>, ancestor: LayoutNode, node: LayoutNode) -> bool { @@ -2281,8 +2310,7 @@ fn note_layout_tree_restructuring_at(host: &TreeBuilderHost<'_>, state: &mut Tre fn has_inline_or_in_flow_block_children(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool { let mut child = host.first_child(node); while !child.is_invalid() { - let data = host.data(child); - if node_is_inline_outside(data) || !node_is_out_of_flow(data) { + if node_is_inline_outside(host, child) || !node_is_out_of_flow(host, child) { return true; } child = host.next_sibling(child); @@ -2296,8 +2324,7 @@ fn has_in_flow_block_children(host: &TreeBuilderHost<'_>, node: LayoutNode) -> b } let mut child = host.first_child(node); while !child.is_invalid() { - let data = host.data(child); - if !node_is_inline_outside(data) && !node_is_out_of_flow(data) { + if !node_is_inline_outside(host, child) && !node_is_out_of_flow(host, child) { return true; } child = host.next_sibling(child); @@ -2310,14 +2337,13 @@ fn is_out_of_flow_table_internal_child_of_table_root( parent: LayoutNode, child: LayoutNode, ) -> bool { - let parent_data = host.data(parent); let child_data = host.data(child); - parent_data.table_display == FfiTableDisplay::TableRoot + host.table_display(parent) == FfiTableDisplay::TableRoot && node_has_flag(child_data, NodeFlag::HasStyle) && !node_has_flag(child_data, NodeFlag::Anonymous) - && node_is_out_of_flow(child_data) - && !node_has_replaced_element_table_display_adjustment(child_data) - && is_table_non_root_box_with_display(child_data.table_display_before) + && node_is_out_of_flow(host, child) + && !node_has_replaced_element_table_display_adjustment(host, child) + && is_table_non_root_box_with_display(host.table_display_before(child)) } fn create_anonymous_wrapper(host: &TreeBuilderHost<'_>, parent: LayoutNode) -> LayoutNode { @@ -2357,13 +2383,12 @@ fn insertion_parent_for_inline_node(host: &TreeBuilderHost<'_>, parent: LayoutNo return parent; } - if node_is_inline_outside(data) && node_has_display_flag(data, NodeDisplayFlag::FlowInside) { + let parent_display = host.style(parent).map(|style| style.display()); + if node_is_inline_outside(host, parent) && parent_display.is_some_and(|display| display.is_flow_inside()) { return parent; } - if node_has_display_flag(data, NodeDisplayFlag::FlexInside) - || node_has_display_flag(data, NodeDisplayFlag::GridInside) - { + if parent_display.is_some_and(|display| display.is_flex_inside() || display.is_grid_inside()) { return last_child_creating_anonymous_wrapper_if_needed(host, parent); } @@ -2387,8 +2412,8 @@ fn insertion_parent_for_block_node( // Inline is fine for in-flow block children (interrupting blocks) and for out-of-flow children; // the inline formatting context emits items for both. if !node_has_flag(host.data(node), NodeFlag::Anonymous) - && node_is_inline_outside(parent_data) - && node_has_display_flag(parent_data, NodeDisplayFlag::FlowInside) + && node_is_inline_outside(host, parent) + && host.style(parent).is_some_and(|style| style.display().is_flow_inside()) { return parent; } @@ -2417,11 +2442,10 @@ fn insertion_parent_for_block_node( return new_parent; } - let node_data = host.data(node); let new_parent_data = host.data(new_parent); // If the block is out-of-flow, - if node_is_out_of_flow(node_data) { + if node_is_out_of_flow(host, node) { let last_child = host.last_child(new_parent); assert!(!last_child.is_invalid()); let last_child_data = host.data(last_child); @@ -2429,9 +2453,9 @@ fn insertion_parent_for_block_node( // And we're appending while the parent's last child is an anonymous block, join that // anonymous block. Prepended boxes (e.g. an absolutely positioned ::before) belong at the // very start of the parent, not at the start of its trailing inline run. + let new_parent_display = host.style(new_parent).map(|style| style.display()); if mode == FfiInsertionMode::Append - && !node_has_display_flag(new_parent_data, NodeDisplayFlag::FlexInside) - && !node_has_display_flag(new_parent_data, NodeDisplayFlag::GridInside) + && !new_parent_display.is_some_and(|display| display.is_flex_inside() || display.is_grid_inside()) && !node_is_generated_for_pseudo_element(last_child_data) && node_has_flag(last_child_data, NodeFlag::Anonymous) && node_has_flag(last_child_data, NodeFlag::ChildrenAreInline) @@ -2505,11 +2529,12 @@ fn insert_node_into_inline_or_block_ancestor( if is_inline_outside { // After inserting an inline-level box into a parent, mark the parent as having inline children. host.set_children_are_inline(insertion_point, true); - } else if !node_is_out_of_flow(host.data(node)) { - let insertion_point_data = host.data(insertion_point); + } else if !node_is_out_of_flow(host, node) { // Inline-flow parents keep their inline children flag; their IFC may contain interrupting blocks. - if !node_is_inline_outside(insertion_point_data) - || !node_has_display_flag(insertion_point_data, NodeDisplayFlag::FlowInside) + if !node_is_inline_outside(host, insertion_point) + || !host + .style(insertion_point) + .is_some_and(|style| style.display().is_flow_inside()) { host.set_children_are_inline(insertion_point, false); } @@ -2673,7 +2698,7 @@ fn find_first_letter_in_block(host: &DomTreeBuilderHost<'_>, block: LayoutNode) return TraversalDecision::Continue; } let data = layout_host.data(node); - if is_marker_content(data) || node_is_out_of_flow(data) { + if is_marker_content(data) || node_is_out_of_flow(&layout_host, node) { return TraversalDecision::SkipChildrenAndContinue; } if node_kind_is_text(data.kind) { @@ -2684,7 +2709,7 @@ fn find_first_letter_in_block(host: &DomTreeBuilderHost<'_>, block: LayoutNode) TraversalDecision::Continue }; } - if node_is_fragmented_inline(data) { + if node_is_fragmented_inline(&layout_host, node) { return TraversalDecision::Continue; } TraversalDecision::Break @@ -2698,7 +2723,7 @@ fn find_first_letter_in_block(host: &DomTreeBuilderHost<'_>, block: LayoutNode) while !child.is_invalid() { let data = layout_host.data(child); let is_anonymous = node_has_flag(data, NodeFlag::Anonymous); - if is_marker_content(data) || node_is_out_of_flow(data) { + if is_marker_content(data) || node_is_out_of_flow(&layout_host, child) { child = layout_host.next_sibling(child); continue; } @@ -2733,11 +2758,9 @@ fn wrap_button_contents_if_needed(host: &TreeBuilderHost<'_>, layout_node: Layou // If the element is an input element, or if it is a button element and its computed value for 'display' is not // 'inline-grid', 'grid', 'inline-flex', or 'flex', then the element's box has a child anonymous button content // box with the following behaviors: - let data = host.data(layout_node); - if !node_has_display_flag(data, NodeDisplayFlag::GridInside) - && !node_has_display_flag(data, NodeDisplayFlag::FlexInside) - { - let children_are_inline = node_has_flag(data, NodeFlag::ChildrenAreInline); + let display = host.style(layout_node).map(|style| style.display()); + if !display.is_some_and(|display| display.is_grid_inside() || display.is_flex_inside()) { + let children_are_inline = node_has_flag(host.data(layout_node), NodeFlag::ChildrenAreInline); let mut child_shells = Vec::new(); let mut child = host.first_child(layout_node); while !child.is_invalid() { @@ -2769,8 +2792,7 @@ fn wrap_button_contents_if_needed(host: &TreeBuilderHost<'_>, layout_node: Layou fn rendered_legend(host: &TreeBuilderHost<'_>, fieldset: LayoutNode) -> LayoutNode { let mut child = host.first_child(fieldset); while !child.is_invalid() { - let data = host.data(child); - if data.kind == NodeKind::LegendBox && !node_is_out_of_flow(data) { + if host.data(child).kind == NodeKind::LegendBox && !node_is_out_of_flow(host, child) { return child; } child = host.next_sibling(child); @@ -2833,22 +2855,24 @@ fn is_table_track_group(display: FfiTableDisplay) -> bool { ) } -fn display_for_table_fixup(data: &NodeData) -> FfiTableDisplay { +fn display_for_table_fixup(host: &TreeBuilderHost<'_>, node: LayoutNode) -> FfiTableDisplay { // https://drafts.csswg.org/css-tables-3/#fixup-algorithm // For the purposes of these rules, out-of-flow elements are represented as inline elements of zero width and // height. Their containing blocks are chosen accordingly. // // AD-HOC: Table-internal boxes can be blockified before fixup. Use the pre-transformation display for authored // boxes so an out-of-flow table-header-group is still recognized as a proper table child during fixup. - if node_has_replaced_element_table_display_adjustment(data) || node_has_flag(data, NodeFlag::Anonymous) { - data.table_display + if node_has_replaced_element_table_display_adjustment(host, node) + || node_has_flag(host.data(node), NodeFlag::Anonymous) + { + host.table_display(node) } else { - data.table_display_before + host.table_display_before(node) } } -fn is_proper_table_child(data: &NodeData) -> bool { - let display = display_for_table_fixup(data); +fn is_proper_table_child(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool { + let display = display_for_table_fixup(host, node); is_table_track_group(display) || is_table_track(display) || display == FfiTableDisplay::TableCaption } @@ -2866,14 +2890,14 @@ fn is_table_non_root_box_with_display(display: FfiTableDisplay) -> bool { ) } -fn is_table_non_root_box(data: &NodeData) -> bool { - is_table_non_root_box_with_display(data.table_display) +fn is_table_non_root_box(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool { + is_table_non_root_box_with_display(host.table_display(node)) } -fn is_tabular_container(data: &NodeData) -> bool { +fn is_tabular_container(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool { // https://drafts.csswg.org/css-tables-3/#tabular-container matches!( - data.table_display, + host.table_display(node), FfiTableDisplay::TableRoot | FfiTableDisplay::TableRow | FfiTableDisplay::TableRowGroup @@ -2903,7 +2927,7 @@ fn is_ignorable_whitespace(host: &TreeBuilderHost<'_>, node: LayoutNode) -> bool contains_only_whitespace = false; return TraversalDecision::Break; } - } else if node_is_out_of_flow(descendant_data) || !node_has_flag(descendant_data, NodeFlag::Anonymous) { + } else if node_is_out_of_flow(host, descendant) || !node_has_flag(descendant_data, NodeFlag::Anonymous) { contains_only_whitespace = false; return TraversalDecision::Break; } @@ -2921,10 +2945,10 @@ fn is_first_or_last_child_with_table_non_root_sibling_if_any(host: &TreeBuilderH if !previous_sibling.is_invalid() && !next_sibling.is_invalid() { return false; } - if !previous_sibling.is_invalid() && !is_table_non_root_box(host.data(previous_sibling)) { + if !previous_sibling.is_invalid() && !is_table_non_root_box(host, previous_sibling) { return false; } - if !next_sibling.is_invalid() && !is_table_non_root_box(host.data(next_sibling)) { + if !next_sibling.is_invalid() && !is_table_non_root_box(host, next_sibling) { return false; } true @@ -2933,13 +2957,13 @@ fn is_first_or_last_child_with_table_non_root_sibling_if_any(host: &TreeBuilderH fn for_each_sequence_of_consecutive_children_matching( host: &TreeBuilderHost<'_>, parent: LayoutNode, - matcher: impl Fn(&NodeData) -> bool, + matcher: impl Fn(LayoutNode) -> bool, mut callback: impl FnMut(&[LayoutNode], LayoutNode), ) { let mut sequence = Vec::new(); let mut child = host.first_child(parent); while !child.is_invalid() { - if matcher(host.data(child)) || (!sequence.is_empty() && is_ignorable_whitespace(host, child)) { + if matcher(child) || (!sequence.is_empty() && is_ignorable_whitespace(host, child)) { sequence.push(child); } else if !sequence.is_empty() { if !sequence.iter().all(|&node| is_ignorable_whitespace(host, node)) { @@ -2963,7 +2987,7 @@ fn remove_irrelevant_boxes(host: &TreeBuilderHost<'_>, root: LayoutNode) { let data = host.data(node); // 1. Children of a table-column. - if node_kind_is_box(data.kind) && data.table_display == FfiTableDisplay::TableColumn { + if node_kind_is_box(data.kind) && host.table_display(node) == FfiTableDisplay::TableColumn { let mut child = host.first_child(node); while !child.is_invalid() { to_remove.push(child); @@ -2972,10 +2996,10 @@ fn remove_irrelevant_boxes(host: &TreeBuilderHost<'_>, root: LayoutNode) { } // 2. Children of a table-column-group which are not a table-column. - if node_kind_is_box(data.kind) && data.table_display == FfiTableDisplay::TableColumnGroup { + if node_kind_is_box(data.kind) && host.table_display(node) == FfiTableDisplay::TableColumnGroup { let mut child = host.first_child(node); while !child.is_invalid() { - if host.data(child).table_display != FfiTableDisplay::TableColumn { + if host.table_display(child) != FfiTableDisplay::TableColumn { to_remove.push(child); } child = host.next_sibling(child); @@ -2992,7 +3016,7 @@ fn remove_irrelevant_boxes(host: &TreeBuilderHost<'_>, root: LayoutNode) { let parent = host.parent(node); if node_kind_is_box(data.kind) && !parent.is_invalid() - && is_tabular_container(host.data(parent)) + && is_tabular_container(host, parent) && is_first_or_last_child_with_table_non_root_sibling_if_any(host, node) && is_ignorable_whitespace(host, node) { @@ -3013,14 +3037,14 @@ fn generate_missing_child_wrappers(host: &TreeBuilderHost<'_>, root: LayoutNode) return TraversalDecision::Continue; } - match data.table_display { + match host.table_display(parent) { FfiTableDisplay::TableRoot => { // 1. An anonymous table-row box must be generated around each sequence of consecutive children of a // table-root box which are not proper table child boxes. for_each_sequence_of_consecutive_children_matching( host, parent, - |child| !node_has_flag(child, NodeFlag::HasStyle) || !is_proper_table_child(child), + |child| !node_has_flag(host.data(child), NodeFlag::HasStyle) || !is_proper_table_child(host, child), |sequence, nearest_sibling| { host.wrap_in_anonymous(sequence, nearest_sibling, FfiAnonymousTableBoxKind::TableRow); }, @@ -3033,7 +3057,8 @@ fn generate_missing_child_wrappers(host: &TreeBuilderHost<'_>, root: LayoutNode) host, parent, |child| { - !node_has_flag(child, NodeFlag::HasStyle) || child.table_display != FfiTableDisplay::TableRow + !node_has_flag(host.data(child), NodeFlag::HasStyle) + || host.table_display(child) != FfiTableDisplay::TableRow }, |sequence, nearest_sibling| { host.wrap_in_anonymous(sequence, nearest_sibling, FfiAnonymousTableBoxKind::TableRow); @@ -3047,7 +3072,8 @@ fn generate_missing_child_wrappers(host: &TreeBuilderHost<'_>, root: LayoutNode) host, parent, |child| { - !node_has_flag(child, NodeFlag::HasStyle) || child.table_display != FfiTableDisplay::TableCell + !node_has_flag(host.data(child), NodeFlag::HasStyle) + || host.table_display(child) != FfiTableDisplay::TableCell }, |sequence, nearest_sibling| { host.wrap_in_anonymous(sequence, nearest_sibling, FfiAnonymousTableBoxKind::TableCell); @@ -3065,16 +3091,16 @@ fn generate_missing_parents(host: &TreeBuilderHost<'_>, root: LayoutNode) -> Vec // 3. Generate missing parents: let mut table_roots_to_wrap = Vec::new(); host.for_each_in_inclusive_subtree(root, |parent| { - let (has_style, current_display, is_inline_outside, is_box, has_been_wrapped_in_table_wrapper) = { + let (has_style, is_box, has_been_wrapped_in_table_wrapper) = { let data = host.data(parent); ( node_has_flag(data, NodeFlag::HasStyle), - data.table_display, - node_is_inline_outside(data), node_kind_is_box(data.kind), node_has_flag(data, NodeFlag::HasBeenWrappedInTableWrapper), ) }; + let current_display = host.table_display(parent); + let is_inline_outside = node_is_inline_outside(host, parent); if !has_style { return TraversalDecision::Continue; } @@ -3085,7 +3111,10 @@ fn generate_missing_parents(host: &TreeBuilderHost<'_>, root: LayoutNode) -> Vec for_each_sequence_of_consecutive_children_matching( host, parent, - |child| node_has_flag(child, NodeFlag::HasStyle) && child.table_display == FfiTableDisplay::TableCell, + |child| { + node_has_flag(host.data(child), NodeFlag::HasStyle) + && host.table_display(child) == FfiTableDisplay::TableCell + }, |sequence, nearest_sibling| { host.wrap_in_anonymous(sequence, nearest_sibling, FfiAnonymousTableBoxKind::TableRow); }, @@ -3112,7 +3141,10 @@ fn generate_missing_parents(host: &TreeBuilderHost<'_>, root: LayoutNode) -> Vec for_each_sequence_of_consecutive_children_matching( host, parent, - |child| node_has_flag(child, NodeFlag::HasStyle) && child.table_display == FfiTableDisplay::TableRow, + |child| { + node_has_flag(host.data(child), NodeFlag::HasStyle) + && host.table_display(child) == FfiTableDisplay::TableRow + }, |sequence, nearest_sibling| { host.wrap_in_anonymous(sequence, nearest_sibling, anonymous_table_kind); }, @@ -3124,7 +3156,10 @@ fn generate_missing_parents(host: &TreeBuilderHost<'_>, root: LayoutNode) -> Vec for_each_sequence_of_consecutive_children_matching( host, parent, - |child| node_has_flag(child, NodeFlag::HasStyle) && child.table_display == FfiTableDisplay::TableColumn, + |child| { + node_has_flag(host.data(child), NodeFlag::HasStyle) + && host.table_display(child) == FfiTableDisplay::TableColumn + }, |sequence, nearest_sibling| { host.wrap_in_anonymous(sequence, nearest_sibling, anonymous_table_kind); }, @@ -3138,10 +3173,10 @@ fn generate_missing_parents(host: &TreeBuilderHost<'_>, root: LayoutNode) -> Vec host, parent, |child| { - if !node_has_flag(child, NodeFlag::HasStyle) { + if !node_has_flag(host.data(child), NodeFlag::HasStyle) { return false; } - let display = display_for_table_fixup(child); + let display = display_for_table_fixup(host, child); is_table_track_group(display) || display == FfiTableDisplay::TableCaption }, |sequence, nearest_sibling| {