Skip to content

LibWeb: Port layout tree construction to Rust - #10838

Merged
awesomekling merged 29 commits into
LadybirdBrowser:masterfrom
awesomekling:tribi
Jul 24, 2026
Merged

LibWeb: Port layout tree construction to Rust#10838
awesomekling merged 29 commits into
LadybirdBrowser:masterfrom
awesomekling:tribi

Conversation

@awesomekling

Copy link
Copy Markdown
Member

We are incrementally moving LibWeb's style, layout, and display-list recording pipeline to Rust. This PR ports Layout::TreeBuilder, the stage that turns the styled DOM and flat tree into the layout tree, as the first complete layout-side step.

Rust now owns the traversal, transient builder state, incremental subtree reconstruction, principal and pseudo-element box decisions, shadow and slot traversal, top-layer and SVG handling, inline/block normalization, ::first-letter selection, specialized wrappers, and table fixup. C++ remains responsible for accessing existing DOM and style objects, constructing concrete layout nodes, and preserving RefPtr ownership through narrow callbacks.

The public C++ TreeBuilder class is removed, and an active build enters Rust once and stays there through recursive construction, insertion, pseudo-element creation, and postprocessing. Existing behavior is preserved, with Rust unit tests covering the ported decisions and LibWeb tests covering the resulting trees and incremental updates.

This gives future Rust formatting-context ports a Rust-owned tree construction pipeline. From there we can continue moving layout state and algorithms to Rust, followed by painting and display-list recording.

Add a dedicated Rust crate for layout and wire its generated FFI into
LibWeb. Move the replaced-element table display adjustment into Rust as
the first production TreeBuilder decision, with unit and layout test
coverage establishing the new boundary.
Move the CSS table-model fixup traversal and decisions into the Rust
layout crate. Keep C++ callbacks limited to querying layout-node facts,
mutating the existing tree, constructing style-backed anonymous boxes,
and exposing TableGrid occupancy.

This covers irrelevant-box removal, missing wrapper and parent
generation, table-wrapper reuse, and missing-cell generation. Existing
table layout coverage exercises the migrated path.
Move insertion-parent selection and inline/block normalization into the
Rust tree builder. Expose ownership-safe C++ callbacks for anonymous
wrapper construction, sibling migration, insertion, and tree update
confinement tracking.

Preserve special handling for fieldsets, flex and grid containers, SVG
layout, out-of-flow boxes, pseudo-elements, and table-internal boxes.
Move the CSS first-letter text pattern and layout-descendant search to
Rust. Keep Unicode category lookup, grapheme boundaries, and concrete
text-slice and wrapper construction behind the C++ bridge.

Preserve marker exclusion, nested block boundaries, generated text,
preserved-newline behavior, and trailing punctuation handling.
Store the active insertion ancestor stack and generated-content quote
depth in the Rust tree builder. Keep narrow C++ accessors for current
callers while making the state available to the upcoming recursive Rust
construction pass.

Destroy the state with its owning TreeBuilder and prevent accidental
copies of the opaque Rust allocation.
Move ordered traversal of DOM children, shadow-root children, assigned
slot nodes, and SVG switch children into the Rust tree builder. Keep the
per-node update and DOM access operations behind typed C++ callbacks.

Preserve forced slot-subtree creation, shadow-root flag clearing, and
cleanup of non-rendered SVG switch children.
Move generated pseudo-element suppression, normal marker handling,
content replacement, and display-contents decisions into the Rust tree
builder. Keep style lookup and concrete box construction in C++.

Cover the decision matrix with Rust unit tests while retaining existing
pseudo-element, marker, and CSS content rendering behavior.
Move button-content and fieldset-content wrapper selection into the Rust
tree builder. Retain C++ callbacks for computed-style construction and
ownership-safe migration of the existing child nodes.

Preserve button flex centering and fieldset legend exclusion behavior.
Move the complete display-contents update lifecycle into the Rust tree
builder. Rust now coordinates top-layer masking, stale subtree cleanup,
counter resolution, generated pseudo-elements, shadow and DOM children,
assigned slot nodes, and dirty-flag clearing.

Keep DOM access and pseudo-element construction behind typed C++
callbacks.
Move layout-node rebuild selection, top-layer admission, and SVG
container gating into the Rust tree builder. Preserve C++ operations for
zone-rebuild scheduling and temporary SVG context storage.

Cover incremental, top-layer, and SVG entry combinations with a Rust
unit test.
Move principal box suppression, display-contents selection, partial
replacement classification, rebuild-root tracking, top-layer flags,
and insertion selection into the Rust layout tree builder.

Keep ownership-sensitive box replacement and insertion in typed C++
callbacks.
Move the principal box descendant walk, top-layer processing, slot
projection, containment quote scoping, and final dirty-flag cleanup into
the Rust tree builder. Keep DOM and layout object access behind a C++
callback table while Rust owns the traversal order and decisions.

Snapshot the document top-layer set in one C++ iteration before Rust
walks it, keeping rebuilds linear in the number of top-layer members.
Move generated pseudo-element ordering, SVG resource recursion and cycle
checks, pattern deduplication, first-letter creation, and specialized
wrapper dispatch into the Rust tree builder. Remove the obsolete C++
before-children and after-children methods.
Let an opaque C++ frame retain old and new layout nodes. Rust now owns
entry filtering, box generation, SVG-root scoping, and placement. Rust
also owns rebuild-root confinement and stale cleanup while preserving
restoration order. The C++ method only sets up the frame and dispatches.
Move document build setup and table fixup dispatch into Rust. Delete the
C++ traversal and fixup forwarding methods that became unused after the
central update port.
Move pseudo-element generation, insertion, counters, content resolution,
quote updates, and generated child sequencing into Rust. Retain computed
styles and C++ nodes in an opaque frame. Expose them through focused
callbacks.
Move content replacement and SVG resource box selection into Rust. Move
the one-shot SVG construction context updates there as well. Delete the
superseded C++ element construction helpers and an unused declaration.
Move top-layer detachment, assigned-slot cleanup, SVG resource
ancestor checks, and remaining wrapper and backdrop decisions into
Rust. Keep C++ callbacks focused on object access and mutation, while
retaining new backdrop nodes across split creation and insertion.
Keep traversal and decision helpers private now that the complete tree
build is orchestrated within Rust. This removes unused functions and
decision types from the generated C++ interface.
Select and order the children of anonymous button and fieldset
wrappers in Rust. Keep C++ responsible for creating styled layout
nodes and moving retained nodes across ownership boundaries.
Return the layout root and incremental rebuild report together from a
free layout-tree build function. Stop making Document manage a C++
TreeBuilder object solely to retrieve the report after construction.
Make Rust own recursive traversal and transient context instead of
bouncing through C++ for every node. Keep reusable C++ frames in a stack
whose lifetime follows the Rust recursion.
Own the ancestor stack and quote nesting inside Rust. Pass only the
current parent to C++ operations that need it, and remove the state
lifecycle and accessor FFI.
Expose layout tree construction as a free function driven by Rust. Keep
C++ ownership and mutation callbacks in a private bridge, with a narrow
authority type for protected DOM and SVG operations.
Run first-letter traversal, button and fieldset wrapping, and table
fixup directly from the active Rust tree build. Remove the redundant
C++ callbacks and Rust entry points that re-entered these operations.
Create pseudo-elements and insert principal, generated, and content
nodes without re-entering Rust through C++. Retain C++ frame storage
only for objects whose ownership cannot cross the FFI boundary.
Traverse assigned slottables directly from the active Rust operation.
Share the C++ assigned-node accessors between normal tree building and
top-layer detachment instead of re-entering a separate Rust function.
Prepare Unicode segmentation through the layout-tree host, then perform
first-letter matching without re-entering a standalone Rust function.
Keep the prepared context alive for the duration of the Rust scan.
Collect the style and whitespace facts needed for text layout through
the active tree-build host. Decide whether display-contents text needs
an inline style wrapper without calling a standalone Rust function.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ede730b9-ef34-4260-a606-d23449d05435

📥 Commits

Reviewing files that changed from the base of the PR and between 78828cc and 97a17fa.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • Cargo.toml
  • Libraries/LibWeb/CMakeLists.txt
  • Libraries/LibWeb/DOM/Document.cpp
  • Libraries/LibWeb/DOM/Element.cpp
  • Libraries/LibWeb/DOM/Element.h
  • Libraries/LibWeb/DOM/Node.cpp
  • Libraries/LibWeb/DOM/Node.h
  • Libraries/LibWeb/Forward.h
  • Libraries/LibWeb/Layout/Rust/Cargo.toml
  • Libraries/LibWeb/Layout/Rust/build.rs
  • Libraries/LibWeb/Layout/Rust/cbindgen.toml
  • Libraries/LibWeb/Layout/Rust/src/lib.rs
  • Libraries/LibWeb/Layout/Rust/src/tree_builder.rs
  • Libraries/LibWeb/Layout/TreeBuilder.cpp
  • Libraries/LibWeb/Layout/TreeBuilder.h
  • Libraries/LibWeb/SVG/SVGElement.cpp
  • Libraries/LibWeb/SVG/SVGElement.h

📝 Walkthrough

Walkthrough

Changes

Layout tree builder Rust port

Layer / File(s) Summary
Rust crate and builder API contracts
Cargo.toml, Libraries/LibWeb/Layout/Rust/*, Libraries/LibWeb/Layout/TreeBuilder.h, Libraries/LibWeb/Forward.h
Adds the Rust crate, cbindgen configuration, FFI contracts, LayoutTreeBuildResult, and LayoutTreeBuilderAccess.
Rust DOM traversal and lifecycle operations
Libraries/LibWeb/Layout/Rust/src/tree_builder.rs
Implements Rust layout traversal, SVG handling, stale-node preservation, top-layer detachment, and layout-tree construction.
Pseudo-elements, insertion, and table fixups
Libraries/LibWeb/Layout/Rust/src/tree_builder.rs
Adds pseudo-element generation, first-letter handling, anonymous wrappers, table fixups, and unit tests.
C++ bridge and callback implementation
Libraries/LibWeb/Layout/TreeBuilder.cpp
Connects Rust traversal to C++ DOM and layout operations through LayoutTreeBuildBridge and FFI callbacks.
LibWeb call sites and build integration
Libraries/LibWeb/CMakeLists.txt, Libraries/LibWeb/DOM/*, Libraries/LibWeb/SVG/*
Links and merges the Rust archive, updates rebuild and top-layer call sites, and changes layout-builder badge contracts.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: kalenikaliaksandr, tcl3, shannonbooth

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly matches the changeset, describing the Rust port of Layout::TreeBuilder and the related layout pipeline work.
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.

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

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