Skip to content

Replace the global UsedValues store with per run fragment trees - #11074

Merged
kalenikaliaksandr merged 14 commits into
LadybirdBrowser:masterfrom
kalenikaliaksandr:layout-fragment-tree
Aug 10, 2026
Merged

Replace the global UsedValues store with per run fragment trees#11074
kalenikaliaksandr merged 14 commits into
LadybirdBrowser:masterfrom
kalenikaliaksandr:layout-fragment-tree

Conversation

@kalenikaliaksandr

Copy link
Copy Markdown
Member

Layout no longer coordinates through a pass-global used-values table.
Each formatting context run owns the records it creates, and accessing
a record another run owns is a hard error in every build. In place of
the shared store, every run returns an immutable fragment tree: box
metrics are snapshotted from sealed records at placement, placement
data and payloads ride the fragments, and commit emits everything from
the returned trees without reaching back into layout state. Cross-run
concerns — absolutely positioned children, anchor candidates — travel
on the same structure and drain at their containing block's placement.

Together these give each formatting context run a closed contract:
inputs arrive as values, and everything the run produces leaves
through its returned result and fragment tree, with no reads or
writes against shared pass state. That is the precondition for FC run
memoization — a future change can skip a run whose inputs are
unchanged and reuse its fragment tree, which would be unsound as long
as runs communicated through a global store. Rendering is unchanged
across the layout test suite except one deliberate rebaseline (an
inline math box's auto height no longer absorbs the enclosing
context's pending collapsed margin), and new layout tests pin the
placement-time abspos drain paths.

Atomic roots that are not block formatting contexts by the facts
predicate (inline math, replaced boxes with children) ignored the
completed run's automatic content block size and fell through to the
CSS 2 section 10.6.3 walk, re-deriving the height from the finished
run's children while threading live collapsed-margin state across a
boundary margins never collapse through. The returned size now wins
before the walk, which fixes a masked bug — an inline <math> box's
height included the enclosing context's pending collapsed margin — and
css-namespace-tag-name-selector.html is rebaselined to the child's
exact border box. It also removes a reader of a finished run's
interior records ahead of records becoming private to their run.
The committed-metric seal fired only at placement, leaving a window
between a run returning and the parent placing its root in which the
root's sizes, margins, borders, and paddings could still change — a
window a full-suite probe confirmed nothing uses. The run tail now
seals those fourteen metrics and the parent keeps writing only the
offset family up to placement, gated to normal-mode commit runs since
intrinsic sizing and measurement legitimately rewrite the same record.
This makes "what a run returns is final at return" an enforced
invariant, the guarantee a later commit relies on to snapshot metrics
into immutable fragments at placement.
Two SVG sites wrote a box's payloads after placing the box: a
path-like element stored its computed path after place_child, and a
nested viewport with its own viewBox was placed before the inner run
whose percentage lengths must not see the new viewport size early.
Both writes move before the placement — run, then write, then place —
so a later commit can capture a box's payloads into an immutable
fragment at the moment it is placed.
FlexItem, GridItem, and FloatingBox carried &UsedValues borrows of
their box's record, and a stored borrow forces the record to live at
least as long as the borrowing structure — in practice, the whole
pass. They now carry just the box, consumers look the record up per
use through the accessors the contexts already funnel through, and the
lifetime parameters disappear. This clears the way for records to be
owned by their creating run and freed when the run completes.
A flow inline box's record is complete the moment the inline iterator
finishes writing its box model and insets: a flow inline is never a
containing block, its geometry is assigned from line pieces after
commit, and its committed offset is zero by construction. Placing the
record right there makes inline boxes placed and sealed like every
other box a committing run creates, with the seal enforcing in every
build that nothing writes the record afterwards; measurement runs skip
the placement so intrinsic re-runs keep an unsealed record.
Every committing run now owns a RunFragmentBuilder, and place_child
records each placement in it: placing a box folds the fragments its
child run handed over into the box's own pending fragment, converts
that single source into an immutable Fragment, and links it under the
containing block's pending fragment. A pending fragment may only open
for a containing block the run will still place; links naming a sealed
or foreign containing block attach under the run root, a layout
ancestor of every box the run places, and committed offsets stay
containing-block-relative wherever a link attaches. This records
structure only and changes no behavior — the foundation for committing
layout output from per-run results instead of a pass-global store.
Fragments now capture what commit emits for a box: the fourteen sealed
metric cells, the committed offset with the committed delta folded in,
the inset family, and the containing-line-box index, resolved at
placement where the containing block's line data is already final.
Never-placed records are swept into parentless links snapshotted from
their records, and a materialized subtree root builds its fragment
directly from its sealed record, so the recorded tree carries exactly
what the store-fed commit walk reads today. Nothing consumes the
captured fields yet; switching commit emission over comes next.
The per-containing-block registration store was the last pass-global
coordination structure. Registrations now travel on the same structure
the fragments do and drain at their containing block's placement, before
its fragment seals, so every drained child nests under the still-open
pending fragment. An entry rides the pending fragment of the box whose
content space its static position rect is expressed in, translates at
each placement, and is taken in document order by the first placement
whose builder placed the containing block — builder-scoped truth, since
a containing block placed by an outer scope still has unplaced boxes
between the entry and itself. Entries whose containing block lies
outside a finishing run return with the run's result, composed toward
the run root as far as the run's own placements allow; a grid stamps its
grid-area containing-block geometry onto contained entries at its
completion; the entry accumulator sweeps the rest under the same
predicate at pass end. The store, the target walk with its table-
internal escape hatch, and the completion-order override machinery are
deleted, and new layout tests pin the drain paths for relative table
rows and row groups, a cell inside a relative table, a four-deep abspos
chain, and a relative block inside a grid item.
An absolutely positioned box whose containing block is a relatively
positioned inline needed two pass-global side tables: a set gating
first/last-line rect collection and a per-box slot the producing
inline run wrote for positioning to read back through an ancestor
walk. The rect now travels on the registration entry itself: the
producing inline formatting context asks its builder whether any
pending registration names an inline containing block — the same
question the set answered — and writes the finished rect onto those
entries while their coordinate space is still the producing run's
containing block. The rect then translates together with the static
position rect, positioning receives it as a value, and the set, the
slot, its ancestor walk, and the ordering dependency between
registration and inline post-processing are deleted.
Boxes with anchor names registered into a pass-global registry at
record creation, and anchor_rect re-derived a candidate's geometry at
resolution time by walking records of runs that completed long before.
Candidates now travel on the builders like every other propagated
payload: a placed box with anchor names contributes its border-box
rect in its containing block's content space (boxes only — a flow
inline's zero-sized record would volunteer garbage geometry now that
inline boxes place too), and the run laying out an absolutely
positioned box answers name lookups from candidates placed in its own
subtree — a tighter set than "has a record", pinned equivalent by the
anchor resolution-timing tests. The registry, its record-creation
hook, and the resolution-time geometry helper are deleted; no
pass-global coordination tables remain.
Commit emission read every box's geometry back out of the pass store
and re-derived the containing-line-box index through an emission-time
line-fragment lookup. The commit walk now carries the pass's fragment
structure as open containing-block scopes: visiting a node with a
fragment opens its children for the node's layout subtree, and the
per-node probe reads a union map over the open scopes — sound because
every link attaches under a fragment whose box is a layout ancestor,
or at the pass's top level, which stays open throughout. One fragment
per box is asserted at scope opening, presence keys paintable
preparation, metrics come from the fragment and its link, the line
index is the one placement resolved, and the three FFI entry points
share one close-and-commit tail. Line data, rare payloads, and SVG
payloads still emit from the store; they move into fragments next.
With geometry already committed from fragments, the remaining store
reads on the commit path were the payload families. Placement now
moves line data, grid and flex layout data, used grid tracks, table
cell coordinates, and override borders out of the record into the
snapshot, commit emits them from the fragment, and push_line_data
becomes a non-destructive walk over the carried data. The saved
absolutely-positioned layout inputs ride the link, finalized before
the box is placed, and the offset family leaves the record: the
committed offset is computed at placement with the delta folded in as
a value, the containing-line-box coordinate arrives as a place_child
parameter, and the committed-delta cell, the line-coordinate cell, the
record's node back-pointer, and the emission-time line-fragment lookup
are deleted.
The SVG payload trio — computed transforms, viewport size, and the
retained path handle — was the last store read on the commit path. It
now travels on fragments: placement snapshots the trio, and because
SVG resource subtrees are laid out once per consumer and legally
rewrite these record fields after the owning box was placed, an SVG
run's close refreshes every fragment in its structure from the records
before returning, gated by a flag the payload writers set so non-SVG
runs never pay for the walk. The path handle stays move-only exactly
as the store owned it, and with commit's rare-data accessor deleted,
commit reads only fragments; the next commit deletes the store.
Every used-values access now goes through the run that owns the
record: a run scope registers each record it creates plus the root its
parent hands in, lookups return owning handles, and looking up a
record the run does not own panics in every build. Parents consume
only what completed child runs return — results, held fragments,
propagated registrations — so a finished run's interior records become
unreachable and are freed at the return, and peak record memory
follows the open containing-block chain instead of the whole pass. The
abspos drain predicate becomes ownership-scoped (a run places exactly
the records it owns, so the placed-slot set returns to a debug
assertion), drain-time containing-block geometry composes from owned
placed records, and the pass-global store with its lookup surface is
deleted — records become plain reference-counted allocations created
by their spawning side, with no lifetime parameter threading through
the scopes.
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The layout engine now uses FormattingContextRun, RunRecords, and RunFragmentBuilder for used values, placement, absolute positioning, anchor resolution, and commit traversal. Formatting contexts propagate run-owned state across block, flex, grid, inline, SVG, and table layout. Deferred absolute-positioned children are drained from fragment data. New layout tests cover table, grid, and nested containing-block cases.

Sequence Diagram(s)

sequenceDiagram
  participant LayoutEntry
  participant FormattingContextRun
  participant RunFragmentBuilder
  participant AbsposEngine
  participant LayoutState
  LayoutEntry->>FormattingContextRun: create records and run layout
  FormattingContextRun->>RunFragmentBuilder: build normal fragments and register pending children
  RunFragmentBuilder->>AbsposEngine: drain absolute-positioned payloads
  AbsposEngine->>RunFragmentBuilder: resolve and place child fragments
  RunFragmentBuilder->>LayoutState: commit completed pass fragments
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly matches the changes to per-run records, immutable fragment trees, absolute-positioned layout, and related tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
Libraries/LibWeb/Rust/src/layout/used_values.rs (1)

248-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse seal_own_metrics inside seal_committed_box_metrics.

seal_own_metrics repeats the first fourteen seals of seal_committed_box_metrics verbatim. A future field addition must be applied in both places.

♻️ Proposed deduplication
     pub(crate) fn seal_committed_box_metrics(&self) {
-        self.content_inline_size.seal();
-        self.content_block_size.seal();
-        self.margin_left.seal();
-        self.margin_right.seal();
-        self.margin_top.seal();
-        self.margin_bottom.seal();
-        self.border_left.seal();
-        self.border_right.seal();
-        self.border_top.seal();
-        self.border_bottom.seal();
-        self.padding_left.seal();
-        self.padding_right.seal();
-        self.padding_top.seal();
-        self.padding_bottom.seal();
+        self.seal_own_metrics();
         self.inset_left.seal();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Libraries/LibWeb/Rust/src/layout/used_values.rs` around lines 248 - 263,
Update seal_committed_box_metrics to call seal_own_metrics instead of
duplicating the fourteen own-metric seal operations, while preserving any
additional committed-box-specific sealing performed by
seal_committed_box_metrics.
Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs (1)

3067-3081: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Reconstructed runs are rebuilt on every loop iteration. formatting_context_run() clones two Rc handles per call, and both placement loops call it once per child. Build the run once before each loop.

  • Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs#L3067-L3081: bind the run before the for index in 0..self.flex_items.len() loop and pass the binding to place_child.
  • Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs#L3239-L3249: bind the run before the for item_index in 0..self.items.len() loop in layout_items and pass the binding to place_child.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs` around lines
3067 - 3081, Avoid rebuilding the formatting-context run for each child: in
flex_formatting_context.rs lines 3067-3081, bind formatting_context_run() once
before the loop and pass that binding to place_child; apply the same change in
grid_formatting_context.rs lines 3239-3249 within layout_items, with no direct
changes needed elsewhere.
Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs (1)

577-578: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Explain the discarded context.used(node) call.

The call result is unused. With records, used() panics when the run does not own the record, so this line is now an ownership assertion. State that intent in a comment, or remove the line if it is dead.

♻️ Proposed change
     for (index, node) in without_fragments.into_iter().enumerate() {
-        context.used(node);
+        // Assert that this run owns the record for every fragmented inline it emits a placeholder for.
+        context.used(node);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs` around lines
577 - 578, Clarify the intent of context.used(node) in the without_fragments
iteration: add a concise comment stating that the call intentionally asserts the
run owns each record because used() panics otherwise, or remove the call if that
ownership assertion is not required.
Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs (1)

975-979: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Record handles are re-derived while an equivalent handle is already in scope. After the migration, used_values() performs a RefCell borrow, a HashMap lookup, and an Rc clone. Each site below already holds the handle it needs.

  • Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs#L975-L979: write computed_svg_path through the existing used binding instead of calling self.used_values(graphics_box) again.
  • Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs#L1067-L1075: write override_borders_data through the existing used binding instead of calling self.used_values(cell.box_) again.
  • Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs#L839-L843: replace used_pointer.clone() with &used_pointer.
  • Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs#L1071-L1083: replace used_pointer.clone() with &used_pointer and drop the duplicate re-borrow three lines later.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs` around lines 975
- 979, Avoid re-deriving used-value handles: in
Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs:975-979, write
computed_svg_path through the existing used binding; in
Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs:1067-1075, write
override_borders_data through its existing used binding; in
svg_formatting_context.rs:839-843 and 1071-1083, replace used_pointer.clone()
with &used_pointer, removing the duplicate re-borrow in the latter site.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Libraries/LibWeb/Rust/src/layout/fragment_tree.rs`:
- Around line 127-183: Update the placement flow involving
PlacementData::from_record and snapshot_fragment so
used.rare_data.abspos_layout_inputs is read before snapshot_fragment drains the
owned rare payloads, or copied into an independent value first. Preserve the
resulting abspos layout input for PlacementData::from_record and avoid relying
on the emptied rare-data references after snapshot_fragment.

---

Nitpick comments:
In `@Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs`:
- Around line 3067-3081: Avoid rebuilding the formatting-context run for each
child: in flex_formatting_context.rs lines 3067-3081, bind
formatting_context_run() once before the loop and pass that binding to
place_child; apply the same change in grid_formatting_context.rs lines 3239-3249
within layout_items, with no direct changes needed elsewhere.

In `@Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs`:
- Around line 577-578: Clarify the intent of context.used(node) in the
without_fragments iteration: add a concise comment stating that the call
intentionally asserts the run owns each record because used() panics otherwise,
or remove the call if that ownership assertion is not required.

In `@Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs`:
- Around line 975-979: Avoid re-deriving used-value handles: in
Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs:975-979, write
computed_svg_path through the existing used binding; in
Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs:1067-1075, write
override_borders_data through its existing used binding; in
svg_formatting_context.rs:839-843 and 1071-1083, replace used_pointer.clone()
with &used_pointer, removing the duplicate re-borrow in the latter site.

In `@Libraries/LibWeb/Rust/src/layout/used_values.rs`:
- Around line 248-263: Update seal_committed_box_metrics to call
seal_own_metrics instead of duplicating the fourteen own-metric seal operations,
while preserving any additional committed-box-specific sealing performed by
seal_committed_box_metrics.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b9611a5d-127e-4585-a709-987de7b53472

📥 Commits

Reviewing files that changed from the base of the PR and between 5c4882c and f584c08.

📒 Files selected for processing (27)
  • Libraries/LibWeb/Rust/src/layout/abspos_engine.rs
  • Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/fragment_tree.rs
  • Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs
  • Libraries/LibWeb/Rust/src/layout/layout_state.rs
  • Libraries/LibWeb/Rust/src/layout/line_builder.rs
  • Libraries/LibWeb/Rust/src/layout/mod.rs
  • Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/sizing_context.rs
  • Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/used_values.rs
  • Tests/LibWeb/Layout/expected/abspos-direct-child-of-relative-table-row-group.txt
  • Tests/LibWeb/Layout/expected/abspos-direct-child-of-relative-table-row.txt
  • Tests/LibWeb/Layout/expected/abspos-in-cell-relative-to-relative-table.txt
  • Tests/LibWeb/Layout/expected/css-namespace-tag-name-selector.txt
  • Tests/LibWeb/Layout/expected/grid-abspos-under-relative-block-inside-item.txt
  • Tests/LibWeb/Layout/expected/nested-abspos-containing-block-chain.txt
  • Tests/LibWeb/Layout/input/abspos-direct-child-of-relative-table-row-group.html
  • Tests/LibWeb/Layout/input/abspos-direct-child-of-relative-table-row.html
  • Tests/LibWeb/Layout/input/abspos-in-cell-relative-to-relative-table.html
  • Tests/LibWeb/Layout/input/grid-abspos-under-relative-block-inside-item.html
  • Tests/LibWeb/Layout/input/nested-abspos-containing-block-chain.html

Comment on lines +127 to +183
fn snapshot_fragment(
node: crate::layout::node_data::NodeSlotId,
children: Vec<FragmentLink>,
used: &UsedValues,
) -> Box<Fragment> {
let line_data = used.line_data.get().map(|cell| Box::new(cell.take()));
let rare_payloads = used.rare_data.get().map(|cell| {
let mut rare = cell.borrow_mut();
(
rare.table_cell_coordinates,
rare.override_borders_data,
rare.grid_layout_data.take(),
rare.flex_layout_data.take(),
rare.used_grid_tracks.take(),
rare.computed_svg_transforms,
rare.svg_viewport_size,
rare.computed_svg_path.take(),
)
});
let (
table_cell_coordinates,
override_borders_data,
grid_layout_data,
flex_layout_data,
used_grid_tracks,
computed_svg_transforms,
svg_viewport_size,
computed_svg_path,
) = rare_payloads.unwrap_or_default();
Box::new(Fragment {
node,
content_inline_size: used.content_inline_size.get(),
content_block_size: used.content_block_size.get(),
margin_left: used.margin_left.get(),
margin_right: used.margin_right.get(),
margin_top: used.margin_top.get(),
margin_bottom: used.margin_bottom.get(),
border_left: used.border_left.get(),
border_right: used.border_right.get(),
border_top: used.border_top.get(),
border_bottom: used.border_bottom.get(),
padding_left: used.padding_left.get(),
padding_right: used.padding_right.get(),
padding_top: used.padding_top.get(),
padding_bottom: used.padding_bottom.get(),
table_cell_coordinates,
override_borders_data,
line_data,
grid_layout_data,
flex_layout_data,
used_grid_tracks,
computed_svg_transforms,
svg_viewport_size,
computed_svg_path: Cell::new(computed_svg_path),
children,
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: List readers of line data and rare payloads, to check for post-placement reads.
set -euo pipefail

rg -nP -C4 '\bline_data_ref\s*\(|\bline_data_cell\s*\(|\brare_data(_mut)?\s*\(' --type=rust Libraries/LibWeb/Rust/src/layout

Repository: LadybirdBrowser/ladybird

Length of output: 18331


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map and inspect the relevant fragment-snapshot and layout ordering code.
echo "== file sizes =="
wc -l Libraries/LibWeb/Rust/src/layout/fragment_tree.rs \
      Libraries/LibWeb/Rust/src/layout/used_values.rs \
      Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs \
      Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs \
      Libraries/LibWeb/Rust/src/layout/formatting_context.rs

echo
echo "== fragment_tree outline =="
ast-grep outline Libraries/LibWeb/Rust/src/layout/fragment_tree.rs --view expanded || true

echo
echo "== fragment_tree relevant excerpts =="
sed -n '1,240p' Libraries/LibWeb/Rust/src/layout/fragment_tree.rs | cat -n

echo
echo "== usages of snapshot_fragment and placement helpers =="
rg -nP -C3 '\bsnapshot_fragment\b|\bplace_fragment\s*\(|\bplace_child\s*\(' Libraries/LibWeb/Rust/src/layout --type=rust

Repository: LadybirdBrowser/ladybird

Length of output: 41678


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== build_fragment_for_placed_box excerpt =="
sed -n '530,615p' Libraries/LibWeb/Rust/src/layout/fragment_tree.rs | cat -n

echo
echo "== refresh_svg_payloads excerpt =="
sed -n '650,740p' Libraries/LibWeb/Rust/src/layout/fragment_tree.rs | cat -n

echo
echo "== line_data_ref readers context =="
for f in Libraries/LibWeb/Rust/src/layout/used_values.rs Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs Libraries/LibWeb/Rust/src/layout/layout_state.rs Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs Libraries/LibWeb/Rust/src/layout/formatting_context.rs Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs; do
  echo "--- $f"
  rg -n -C3 'line_data_ref\s*\(|line_data_cell\s*\(' "$f" || true
done

echo
echo "== rare_data readers context beyond write sites =="
python3 - <<'PY'
import pathlib, re
files = sorted(pathlib.Path('Libraries/LibWeb/Rust/src/layout').glob('*.rs'))
patterns = [re.compile(r'\brare_data\s*\('), re.compile(r'\brare_data_mut\s*\(')]
for f in files:
    text = f.read_text()
    hits = []
    for pat in patterns:
        hits.extend([(m.group(), m.start() + 1, m.end() + 1) for m in pat.finditer(text)])
    if hits:
        lines = text.splitlines()
        print(f'--- {f}')
        for s, e, raw in hits:
            line = text.count('\n', 0, e - 1) + 1
            print(f'line {line}: {raw.strip()}')
            start = max(1, line - 4)
            end = min(len(lines), line + 4)
            for i in range(start, end + 1):
                print(f'{i}: {lines[i-1]}')
            print()
PY

echo
echo "== derive_baselines line_data_ref context =="
sed -n '555,605p' Libraries/LibWeb/Rust/src/layout/formatting_context.rs | cat -n

Repository: LadybirdBrowser/ladybird

Length of output: 14012


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all snapshot_fragment callers =="
rg -n -C5 'snapshot_fragment\(' Libraries/LibWeb/Rust/src --type=rust

echo
echo "== line_data_ref usages with caller context =="
rg -n -C8 'line_data_ref\s*\(' Libraries/LibWeb/Rust/src --type=rust

echo
echo "== derive_baselines and content offset readers =="
rg -n -C6 'derive_baselines\s*\(|content_offset\.get\(\)' Libraries/LibWeb/Rust/src/layout --type=rust

Repository: LadybirdBrowser/ladybird

Length of output: 50380


Avoid draining rare payload reads after build_fragment_for_placed_box.

PlacementData::from_record reads used.rare_data.borrow().abspos_layout_inputs, but snapshot_fragment moves owned payloads and leaves empty references behind. The subsequent abspos_layout_inputs read returns None, so this box should not be treated as having an abspos layout input after placement. Adjust the order of these two reads or copy abspos_layout_inputs before draining.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Libraries/LibWeb/Rust/src/layout/fragment_tree.rs` around lines 127 - 183,
Update the placement flow involving PlacementData::from_record and
snapshot_fragment so used.rare_data.abspos_layout_inputs is read before
snapshot_fragment drains the owned rare payloads, or copied into an independent
value first. Preserve the resulting abspos layout input for
PlacementData::from_record and avoid relying on the emptied rare-data references
after snapshot_fragment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant