//! 1. The `layout_document` function takes the `viewport` as an argument. The `LayoutCache` stores
//! percentage-based sizes and repositions all elements according to the new viewport dimensions.
//! 4. The intrinsic size calculation (bottom-up) can often be skipped, as it's independent of the
get_overflow_y, get_scrollbar_gutter_property, get_text_align, get_white_space_property, get_writing_mode,
get_display_type, is_block_level, AnonymousBoxType, DirtyFlag, LayoutNode, LayoutNodeHot, LayoutTreeBuilder, SubtreeHash,
#[must_use] pub fn classify_size_key(containing_block_size: LogicalSize) -> (usize, LogicalSize) {
#[must_use] pub fn get_size(&self, slot: usize, known_dims: LogicalSize) -> Option<&SizingCacheEntry> {
#[must_use] pub fn get_layout(&self, known_dims: LogicalSize) -> Option<&LayoutCacheEntry> {
let tree_bytes = tree_report.as_ref().map_or(0, super::layout_tree::LayoutTreeMemoryReport::total_bytes);
#[allow(clippy::match_same_arms)] // enum/value mapping/dispatch table: one arm per input variant (or cross-type bindings that can't merge)
// +spec:text-alignment-spacing:43ea0a - text-align-all shorthand: aligns all lines except last (overridden by text-align-last)
#[must_use] pub fn collect_children_dom_ids(styled_dom: &StyledDom, parent_dom_id: NodeId) -> Vec<NodeId> {
crate::az_mark((0x40540 + pi * 4) as u32, (0xCC00_0000u32 | (children.len() as u32 & 0xffff)));
let writing_mode = get_writing_mode(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
let parent_state = &styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
let white_space = match get_white_space_property(styled_dom, parent_dom_id, parent_state) {
.all(|(&oc, &(_, nid))| t.get(LayoutNodeId::new(oc)).and_then(|n| n.dom_node_id) == Some(nid));
if let (Some(old_warm), Some(new_node)) = (t.warm(LayoutNodeId::new(old_anon)), new_tree_builder.get_mut(anon_idx))
/// Recursively traverses the new DOM and old tree, building a new tree and marking dirty nodes.
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] // large but cohesive: single-purpose layout/render/parse routine (one branch per case)
let old_cold = old_tree.and_then(|t| old_tree_idx.and_then(|idx| t.cold(LayoutNodeId::new(idx))));
let mut new_children_dom_ids: Vec<_> = collect_children_dom_ids(styled_dom, new_dom_id);
.filter(|&&cidx| t.warm(LayoutNodeId::new(cidx)).is_none_or(|w| w.pseudo_element.is_none()))
.filter_map(|&cidx| t.get(LayoutNodeId::new(cidx)).and_then(|n| n.dom_node_id).map(|did| (did, cidx)))
let new_layout_relevant_count = layout_relevant_child_count(styled_dom, &new_children_dom_ids, new_dom_id);
let mut children_are_different = new_layout_relevant_count != old_layout_relevant_count;
// +spec:display-property:42f9c0 - anonymous block boxes wrap inline runs when block container has mixed block/inline children
if old_tree.and_then(|t| t.cold(LayoutNodeId::new(old_child_idx?)).map(|n| n.subtree_hash))
// +spec:display-property:bef3fc - anonymous blocks of only collapsible whitespace removed from rendering tree
"[reconcile_recursive] Skipping whitespace-only inline run ({} nodes) between blocks in node {}",
"[reconcile_recursive] Created anonymous IFC wrapper (layout_idx={}) for {} inline children: {:?}",
#[allow(clippy::iter_with_drain)] // accumulator Vec reused across runs; drain(..) empties it while retaining the allocation
eprintln!("[recon] mid-loop wrapper ord {} NOT reused (run len {})", anon_ordinal - 1, inline_run.len());
if old_tree.and_then(|t| t.cold(LayoutNodeId::new(old_child_idx?)).map(|n| n.subtree_hash))
"[recon] block child dom {:?} under parent dom {:?} hash MISMATCH warm_pass={} old_idx={:?} (old {:?} vs new {:?})",
old_tree.and_then(|t| t.cold(LayoutNodeId::new(old_child_idx.unwrap_or(usize::MAX))).map(|n| n.subtree_hash)),
"[reconcile_recursive] Skipping trailing whitespace-only inline run ({} nodes) in node {}",
"[reconcile_recursive] Created trailing anonymous IFC wrapper (layout_idx={}) for {} inline children: {:?}",
#[allow(clippy::iter_with_drain)] // accumulator Vec reused across runs; drain(..) empties it while retaining the allocation
// +spec:overflow:08b60d - non-interactive media: UA may show scroll indicators but we skip them for print
// +spec:overflow:3a6966 - classic scrollbar gutter width == scrollbar width; overlay scrollbars have no gutter
let scrollbar_gutter = get_scrollbar_gutter_property(ctx.styled_dom, dom_id, styled_node_state)
let is_scroll_container = matches!(ob_y, fc::OverflowBehavior::Scroll | fc::OverflowBehavior::Auto);
compute_scrollbar_info_core(ctx, dom_id, styled_node_state, content_size, container_size)
warm_node.scrollbar_info.as_ref().map_or_else(|| scrollbar_info.needs_reflow(), |old_info| {
let child_warm = tree.warm_mut(LayoutNodeId::new(child_index)).ok_or(LayoutError::InvalidTree)?;
// self_content_box_pos is [CoordinateSpace::Window] - absolute position of parent's content-box
let child_node = tree.get(LayoutNodeId::new(child_index)).ok_or(LayoutError::InvalidTree)?;
let child_node = tree.get(LayoutNodeId::new(child_index)).ok_or(LayoutError::InvalidTree)?;
position_bfc_child_descendants(tree, child_index, child_content_box_pos, calculated_positions);
let current_node = tree.get(LayoutNodeId::new(node_index)).ok_or(LayoutError::InvalidTree)?;
#[allow(clippy::implicit_hasher)] // internal helper; only ever called with the default-hasher HashMap/HashSet
#[allow(clippy::too_many_lines)] // large but cohesive: single-purpose layout/render/parse routine (one branch per case)
// [g147b az-web-lift DIAG] per-node calculate_layout_for_subtree entry (0x60980+slot): records the
// last compute_mode that reached this node (PerformLayout=2 wins, runs after ComputeSize=1). If a div
// shows 0x...0002 here but its layout_formatting_context marker (0x609A0+) is UNSET → positioning
// reached calculate but short-circuited (cache hit) before dispatching to the formatting context.
let m = match compute_mode { ComputeMode::PerformLayout => 0xC0DE0002u32, _ => 0xC0DE0001u32 };
if let Some(adjusted) = tree.get(LayoutNodeId::new(node_index)).and_then(|n| n.used_size) {
// +spec:overflow:44ef3b - scroll container detection: overflow scroll/auto makes box a scroll container
let current_node = tree.get_mut(LayoutNodeId::new(node_index)).ok_or(LayoutError::InvalidTree)?;
// +spec:overflow:f28d6a - hanging glyphs should be ink overflow, not scrollable overflow (not yet subtracted from content_size)
// self_content_box_pos is [CoordinateSpace::Window] - the absolute position of this node's content-box
let current_node = tree.get(LayoutNodeId::new(node_index)).ok_or(LayoutError::InvalidTree)?;
// Positions in layout_result.output.positions are [CoordinateSpace::Parent] - relative to this node's content-box
let child_node = tree.get(LayoutNodeId::new(child_index)).ok_or(LayoutError::InvalidTree)?;
#[allow(clippy::match_same_arms)] // enum/value mapping/dispatch table: one arm per input variant (or cross-type bindings that can't merge)
let node_props = tree.get(LayoutNodeId::new(node_index)).ok_or(LayoutError::InvalidTree)?.box_props.unpack();
#[allow(clippy::implicit_hasher)] // internal helper; only ever called with the default-hasher HashMap/HashSet
#[allow(clippy::too_many_lines)] // large but cohesive: single-purpose layout/render/parse routine (one branch per case)
if tree.warm(LayoutNodeId::new(node_idx)).and_then(|w| w.pseudo_element.as_ref()).is_some() {