From 3125222e67707e1bd6296a4ee30da2d91fa07ce8 Mon Sep 17 00:00:00 2001 From: Pierre Le Fevre Date: Fri, 19 Jun 2026 14:19:13 +0200 Subject: [PATCH] Improve relative positioning for Bing carousel Refs isu issue 280. Adds isu issue 378 for the remaining Bing modules-wrapper parity blocker. --- .isu/issues.json | 15 ++- crates/layout/src/lib.rs | 216 ++++++++++++++++++++++++++++++++------- 2 files changed, 195 insertions(+), 36 deletions(-) diff --git a/.isu/issues.json b/.isu/issues.json index 7f3822f..434cb19 100644 --- a/.isu/issues.json +++ b/.isu/issues.json @@ -1,5 +1,5 @@ { - "next_id": 378, + "next_id": 379, "issues": [ { "id": 1, @@ -4621,6 +4621,19 @@ "author": "piefev", "state": "closed", "created_at": "2026-06-19T11:21:38Z" + }, + { + "id": 378, + "repo": "we", + "title": "Bing real-web modules wrapper remains empty versus Chromium", + "body": "The Bing real-web scenario still cannot close isu issue 280 after the relative-positioning layout fix.\n\nRepro:\n```sh\ncargo run -p we-e2e -- --scenario crates/e2e/scenarios/real-web/bing.com.we --out-dir crates/e2e/artifacts\n```\n\nObserved:\n- DOM and interaction assertions pass.\n- Desktop screenshot assertion fails at line 147: 2.46% match; diff at crates/e2e/artifacts/real-web/bing.com/desktop.png.diff.png.\n- Mobile screenshot assertion fails at line 161: 5.46% match; diff at crates/e2e/artifacts/real-web/bing.com/mobile.png.diff.png.\n- Current we render now shows the carousel, but it sits around y=844 on desktop while a routed Chromium render of the same snapshot/cache puts the carousel around y=616.\n- The light DOM modules_wrapper remains effectively empty in we, while Chromium gives that region about 533px of content height. This keeps the Bing carousel/info modules from matching even after the abspos/relative offset bug is fixed.\n\nNotes:\n- The committed Chromium golden is also stale against the Sichuan Tea snapshot; see isu issue 305.\n- This is a narrower remaining blocker for isu issue 280 after the layout fix, likely in Bing widget/shadow/hydration support or fixture parity.", + "labels": [ + "real-web" + ], + "assigned": [], + "author": "piefev", + "state": "open", + "created_at": "2026-06-19T12:18:13Z" } ] } diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index 36815d2..3fb595c 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -562,6 +562,65 @@ fn resolve_length_against(value: LengthOrAuto, reference: f32) -> f32 { } } +fn resolve_content_height_value( + value: LengthOrAuto, + reference: f32, + box_sizing: BoxSizing, + vertical_extra: f32, +) -> Option { + let resolved = match value { + LengthOrAuto::Length(px) => px, + LengthOrAuto::Percentage(p) => p / 100.0 * reference, + LengthOrAuto::Calc(..) | LengthOrAuto::ClampCalc { .. } => { + resolve_length_against(value, reference) + } + LengthOrAuto::Auto => return None, + }; + + Some(match box_sizing { + BoxSizing::ContentBox => resolved.max(0.0), + BoxSizing::BorderBox => (resolved - vertical_extra).max(0.0), + }) +} + +fn resolve_absolute_content_height( + child: &LayoutBox, + cb_height: f32, + top_offset: Option, + bottom_offset: Option, + vertical_extra: f32, +) -> Option { + resolve_content_height_value( + child.css_height, + cb_height, + child.box_sizing, + vertical_extra, + ) + .or_else(|| { + let (top, bottom) = (top_offset?, bottom_offset?); + Some( + (cb_height - top - bottom - child.margin.top - child.margin.bottom - vertical_extra) + .max(0.0), + ) + }) +} + +fn resolved_box_content_height(b: &LayoutBox, reference: f32) -> Option { + let vertical_extra = b.border.top + b.border.bottom + b.padding.top + b.padding.bottom; + resolve_content_height_value(b.css_height, reference, b.box_sizing, vertical_extra) +} + +fn definite_positioned_child_cb_height(parent: &LayoutBox, viewport_height: f32) -> Option { + if let Some(height) = resolved_box_content_height(parent, viewport_height) { + return Some(height); + } + if matches!(parent.position, Position::Absolute | Position::Fixed) && parent.rect.height > 0.0 { + Some(parent.rect.height) + } else { + None + } +} + fn resolve_size_constraint( value: LengthOrAuto, reference: f32, @@ -1734,6 +1793,7 @@ fn compute_layout( available_width: f32, viewport_width: f32, viewport_height: f32, + position_cb_height: f32, font: &Font, doc: &Document, abs_cb: Rect, @@ -1938,10 +1998,22 @@ fn compute_layout( ); set_sticky_constraints(b); layout_abspos_children(b, abs_cb, viewport_width, viewport_height, font, doc); - apply_relative_offset(b, available_width, viewport_height); + apply_relative_offset(b, available_width, position_cb_height); return; } + if let Some(resolved_height) = resolved_box_content_height(b, viewport_height) { + let vertical_extra = b.border.top + b.border.bottom + b.padding.top + b.padding.bottom; + b.rect.height = clamp_dimension( + resolved_height, + b.css_min_height, + b.css_max_height, + viewport_height, + b.box_sizing, + vertical_extra, + ); + } + match &b.box_type { BoxType::Block(node_id) => { let is_fieldset = doc.tag_name(*node_id) == Some("fieldset"); @@ -2052,7 +2124,7 @@ fn compute_layout( // dimensions are fully resolved. layout_abspos_children(b, abs_cb, viewport_width, viewport_height, font, doc); - apply_relative_offset(b, available_width, viewport_height); + apply_relative_offset(b, available_width, position_cb_height); b.dirty = false; b.cached_available_width = available_width; @@ -2081,11 +2153,14 @@ fn apply_relative_offset(b: &mut LayoutBox, cb_width: f32, cb_height: f32) { let [top, right, bottom, left] = b.css_offsets; let dx = resolve_relative_horizontal(left, right, cb_width); let dy = resolve_relative_vertical(top, bottom, cb_height); + let (old_dx, old_dy) = b.relative_offset; + let delta_x = dx - old_dx; + let delta_y = dy - old_dy; b.relative_offset = (dx, dy); - if dx == 0.0 && dy == 0.0 { + if delta_x == 0.0 && delta_y == 0.0 { return; } - shift_box(b, dx, dy); + shift_box(b, delta_x, delta_y); } /// Recursively shift a box and all its descendants by (dx, dy). @@ -2311,6 +2386,19 @@ fn layout_absolute_child( cb.x + child.margin.left + child.border.left + child.padding.left }; + if let Some(resolved_height) = + resolve_absolute_content_height(child, cb.height, top_offset, bottom_offset, vert_extra) + { + child.rect.height = clamp_dimension( + resolved_height, + child.css_min_height, + child.css_max_height, + cb.height, + child.box_sizing, + vert_extra, + ); + } + // Set a temporary y for child content layout. child.rect.y = cb.y; @@ -2414,36 +2502,10 @@ fn layout_absolute_child( child.content_height = child.rect.height; // --- Resolve content height --- - match child.css_height { - LengthOrAuto::Length(h) => { - child.rect.height = match child.box_sizing { - BoxSizing::ContentBox => h.max(0.0), - BoxSizing::BorderBox => (h - vert_extra).max(0.0), - }; - } - LengthOrAuto::Percentage(p) => { - let resolved = p / 100.0 * cb.height; - child.rect.height = match child.box_sizing { - BoxSizing::ContentBox => resolved.max(0.0), - BoxSizing::BorderBox => (resolved - vert_extra).max(0.0), - }; - } - LengthOrAuto::Calc(..) | LengthOrAuto::ClampCalc { .. } => { - let resolved = resolve_length_against(child.css_height, cb.height); - child.rect.height = match child.box_sizing { - BoxSizing::ContentBox => resolved.max(0.0), - BoxSizing::BorderBox => (resolved - vert_extra).max(0.0), - }; - } - LengthOrAuto::Auto => { - // If both top and bottom are specified, stretch. - if let (Some(t), Some(b_val)) = (top_offset, bottom_offset) { - child.rect.height = - (cb.height - t - b_val - child.margin.top - child.margin.bottom - vert_extra) - .max(0.0); - } - // Otherwise keep content height. - } + if let Some(resolved_height) = + resolve_absolute_content_height(child, cb.height, top_offset, bottom_offset, vert_extra) + { + child.rect.height = resolved_height; } child.rect.height = clamp_dimension( child.rect.height, @@ -2654,6 +2716,7 @@ fn layout_block_children( let content_x = parent.rect.x; let content_width = parent.rect.width; let mut cursor_y = parent.rect.y; + let definite_position_cb_height = definite_positioned_child_cb_height(parent, viewport_height); let parent_top_open = parent.border.top == 0.0 && parent.padding.top == 0.0 && !establishes_bfc(parent); @@ -2678,6 +2741,7 @@ fn layout_block_children( // --- Handle floated children --- if is_floated(&parent.children[i]) { + let child_position_cb_height = definite_position_cb_height.unwrap_or(viewport_height); layout_float_child( &mut parent.children[i], &mut float_ctx, @@ -2686,6 +2750,7 @@ fn layout_block_children( content_width, viewport_width, viewport_height, + child_position_cb_height, font, doc, abs_cb, @@ -2742,6 +2807,7 @@ fn layout_block_children( // `compute_layout` adds `child.margin.top` internally, so compensate. let y_for_child = cursor_y + collapsed_top - child_top_margin; + let child_position_cb_height = definite_position_cb_height.unwrap_or(viewport_height); compute_layout( &mut parent.children[i], content_x, @@ -2749,6 +2815,7 @@ fn layout_block_children( content_width, viewport_width, viewport_height, + child_position_cb_height, font, doc, abs_cb, @@ -2788,6 +2855,11 @@ fn layout_block_children( parent.rect.height = needed; } } + + let final_position_cb_height = definite_position_cb_height.unwrap_or(parent.rect.height); + for child in &mut parent.children { + apply_relative_offset(child, content_width, final_position_cb_height); + } } /// Lay out a single floated child element. @@ -2800,6 +2872,7 @@ fn layout_float_child( container_width: f32, viewport_width: f32, viewport_height: f32, + position_cb_height: f32, font: &Font, doc: &Document, abs_cb: Rect, @@ -2987,7 +3060,7 @@ fn layout_float_child( // Set sticky constraints and handle abspos children. set_sticky_constraints(child); layout_abspos_children(child, abs_cb, viewport_width, viewport_height, font, doc); - apply_relative_offset(child, container_width, viewport_height); + apply_relative_offset(child, container_width, position_cb_height); } /// Measure the max-content width of a float's content (shrink-to-fit). @@ -3446,6 +3519,7 @@ fn layout_table_children( inner_w, viewport_width, viewport_height, + viewport_height, font, doc, abs_cb, @@ -3759,6 +3833,7 @@ fn layout_grid_children( inner_width, viewport_width, viewport_height, + viewport_height, font, doc, abs_cb, @@ -4709,6 +4784,7 @@ fn layout_flex_children( avail, viewport_width, viewport_height, + viewport_height, font, doc, abs_cb, @@ -4881,6 +4957,7 @@ fn layout_flex_children( target_main, viewport_width, viewport_height, + viewport_height, font, doc, abs_cb, @@ -4899,6 +4976,7 @@ fn layout_flex_children( avail, viewport_width, viewport_height, + viewport_height, font, doc, abs_cb, @@ -4935,6 +5013,7 @@ fn layout_flex_children( max_content, viewport_width, viewport_height, + viewport_height, font, doc, abs_cb, @@ -5017,6 +5096,7 @@ fn layout_flex_children( main, viewport_width, viewport_height, + viewport_height, font, doc, abs_cb, @@ -5039,6 +5119,7 @@ fn layout_flex_children( item_cross_space, viewport_width, viewport_height, + viewport_height, font, doc, abs_cb, @@ -5555,6 +5636,7 @@ fn layout_inline_block_child( layout_available_width, ctx.viewport_width, ctx.viewport_height, + ctx.viewport_height, ctx.font, ctx.doc, ctx.abs_cb, @@ -6406,6 +6488,7 @@ pub fn layout( viewport_width, viewport_width, viewport_height, + viewport_height, font, doc, viewport_cb, @@ -6703,6 +6786,7 @@ pub fn layout_incremental( viewport_width, viewport_width, viewport_height, + viewport_height, font, doc, viewport_cb, @@ -10133,6 +10217,68 @@ body { margin: 0; } ); } + #[test] + fn relative_top_percentage_inside_stretched_abspos_uses_stretched_height() { + let mut doc = Document::new(); + let (_, _, body) = make_html_body(&mut doc); + let container = doc.create_element("div"); + let abs_child = doc.create_element("div"); + let rel_child = doc.create_element("div"); + let nested_rel = doc.create_element("div"); + doc.append_child(body, container); + doc.append_child(container, abs_child); + doc.append_child(abs_child, rel_child); + doc.append_child(rel_child, nested_rel); + doc.set_attribute( + container, + "style", + "position: relative; width: 400px; height: 600px;", + ); + doc.set_attribute( + abs_child, + "style", + "position: absolute; top: 100px; bottom: 0; left: 0; right: 0;", + ); + doc.set_attribute( + rel_child, + "style", + "position: relative; top: calc(100% - 200px);", + ); + doc.set_attribute( + nested_rel, + "style", + "position: relative; top: calc(100% - 50px); height: 100px;", + ); + + let tree = layout_doc(&doc); + let abs_box = find_box(&tree.root, abs_child).unwrap(); + let rel_box = find_box(&tree.root, rel_child).unwrap(); + let nested_box = find_box(&tree.root, nested_rel).unwrap(); + + assert!( + (abs_box.rect.height - 500.0).abs() < 0.01, + "absolute child should stretch to 500px, got {}", + abs_box.rect.height + ); + let expected_y = abs_box.rect.y + abs_box.rect.height - 200.0; + assert!( + (rel_box.rect.y - expected_y).abs() < 0.01, + "relative child y should be {expected_y}, got {}", + rel_box.rect.y + ); + assert!( + (rel_box.rect.height - 100.0).abs() < 0.01, + "relative child should take its in-flow child height, got {}", + rel_box.rect.height + ); + let expected_nested_y = rel_box.rect.y + rel_box.rect.height - 50.0; + assert!( + (nested_box.rect.y - expected_nested_y).abs() < 0.01, + "nested relative child y should be {expected_nested_y}, got {}", + nested_box.rect.y + ); + } + #[test] fn nested_flex_row_items_do_not_overlap() { // Regression for isu issue 327: a flex row that is itself a flex item -- 2.51.2