From 7707e590cecbcde858833037c1f4946a449d20c8 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 31 Jul 2026 02:56:11 +0200 Subject: [PATCH] LibWeb: Derive node display facts from style payloads on demand Every style application re-derived four facts from the fresh computed values and stamped them into the arena NodeData record: the two table display classifications, a bitset of display predicates, and the own-style block-formatting-context flag. The stamps duplicated data the node's mirrored group payloads already carry, could drift from them if any write path forgot to restamp, and kept C++ copies of the block-formatting-context predicate and the table display classification alive purely to feed the mirror. Tree building, formatting-context classification, and table grid construction now read these facts straight from the box group payload: StyleReader classifies the display value into FfiTableDisplay and evaluates the computed-style half of the block-formatting-context predicate, and styleless nodes answer every predicate with the same defaults the zeroed stamps used to produce. The mirror shrinks to storing the style pointer and the payload array, NodeData drops the three stamp bytes, and the NodeDisplayFlag enum, the OwnStyleEstablishesBlockFormattingContext flag, and the C++ predicate and classification helpers are deleted. The NodeData layout tests follow the new field offsets, and the stamp-based composite test for the block-formatting-context predicate goes away with the bits it exercised. --- Libraries/LibWeb/Layout/Node.cpp | 127 ---------- Libraries/LibWeb/Layout/Node.h | 11 +- Libraries/LibWeb/Rust/build.rs | 2 - .../Rust/src/layout/formatting_context.rs | 56 +++-- .../LibWeb/Rust/src/layout/layout_state.rs | 94 +++++--- Libraries/LibWeb/Rust/src/layout/mod.rs | 2 - Libraries/LibWeb/Rust/src/layout/node_data.rs | 55 +---- .../LibWeb/Rust/src/layout/node_facts.rs | 107 +++------ .../LibWeb/Rust/src/layout/style_facts.rs | 143 +++++++++++- .../src/layout/table_formatting_context.rs | 38 +-- .../LibWeb/Rust/src/layout/tree_builder.rs | 221 ++++++++++-------- 11 files changed, 436 insertions(+), 420 deletions(-) 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| {