Skip to content

feat(rust): a DOM for @lexical/html, with DOMParser and jsdom's inline style declaration - #151

Open
tmm wants to merge 1 commit into
rust-decorator-nodefrom
rust-dom
Open

tmm wants to merge 1 commit into
rust-decorator-nodefrom
rust-dom

Conversation

@tmm

@tmm tmm commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

Description

The Rust port had no DOM, so nothing could run createDOM, exportDOM
or HTML import and export, and the html/* and css/* vectors were not
replayed.

A new html feature brings lexical_editor::dom, a small DOM ported from
the Swift port's (swift/Sources/Lexical/DOM): DomNode (one shared handle
for Node, Element, Text, Comment, DocumentFragment and
Document), DOMTokenList, an inline CSSStyleDeclaration that
normalizes values as jsdom's does and keeps the style attribute in step,
DOMParser (a WHATWG tokenizer and the tree builder HTML import meets),
the HTML fragment serializer, and one document per thread
(get_document). The property and entity tables are generated from the
vectors by rust/scripts/generate-dom-tables.mjs, as Swift's are.

The core gets utils/setDOMStyle.ts: getStyleObjectFromCSS (always,
with JS's property order for array-index keys) and, with html,
setDOMStyleObject and setDOMStyleFromCSS.

The conformance runner replays html/parse, html/entities,
css/properties and css/values. For css/properties it checks the
declaration's behavior with the same probes css.test.ts asks jsdom, not
only the table; that showed that jsdom rejects a shadow of nothing but
identifiers (box-shadow: Foo) and lower-cases CSS-wide keywords for every
property, which the declaration now does. Swift's declaration keeps such
shadows (its test compares only the table); #159 fixes that in Swift.

Test plan

Before

$ cargo run -q --release -p lexical-conformance | grep -E 'css/|html/|Total'
| `css/properties` | – | – | – | – | – | not implemented |
| `css/values` | – | – | – | – | – | not implemented |
| `html/entities` | – | – | – | – | – | not implemented |
| `html/export` | – | – | – | – | – | not implemented |
| `html/import` | – | – | – | – | – | not implemented |
| `html/parse` | – | – | – | – | – | not implemented |
| **Total** | 488 | 488 | 0 | 0 | 0 | |

After

$ cargo run -q --release -p lexical-conformance | grep -E 'css/|html/|Total'
| `css/properties` | 814 | 814 | 0 | 0 | 0 | pass |
| `css/values` | 69 | 69 | 0 | 0 | 0 | pass |
| `html/entities` | 2125 | 2125 | 0 | 0 | 0 | pass |
| `html/export` | – | – | – | – | – | not implemented |
| `html/import` | – | – | – | – | – | not implemented |
| `html/parse` | 37 | 37 | 0 | 0 | 0 | pass |
| **Total** | 3533 | 3533 | 0 | 0 | 0 | |
$ cargo clippy --workspace --all-targets --all-features --locked -- -D warnings   # clean
$ cargo test --workspace --all-features --locked                                # all pass

Before the shadow fix, css/properties failed box-shadow,
-webkit-box-shadow and text-shadow (preserveCase: Rust true, JS false).

…e style declaration

## Description

The Rust port had no DOM, so nothing could run `createDOM`, `exportDOM`
or HTML import and export, and the `html/*` and `css/*` vectors were not
replayed.

A new `html` feature brings `lexical_editor::dom`, a small DOM ported from
the Swift port's (`swift/Sources/Lexical/DOM`): `DomNode` (one shared handle
for `Node`, `Element`, `Text`, `Comment`, `DocumentFragment` and
`Document`), `DOMTokenList`, an inline `CSSStyleDeclaration` that
normalizes values as jsdom's does and keeps the `style` attribute in step,
`DOMParser` (a WHATWG tokenizer and the tree builder HTML import meets),
the HTML fragment serializer, and one `document` per thread
(`get_document`). The property and entity tables are generated from the
vectors by `rust/scripts/generate-dom-tables.mjs`, as Swift's are.

The core gets `utils/setDOMStyle.ts`: `getStyleObjectFromCSS` (always,
with JS's property order for array-index keys) and, with `html`,
`setDOMStyleObject` and `setDOMStyleFromCSS`.

The conformance runner replays `html/parse`, `html/entities`,
`css/properties` and `css/values`. For `css/properties` it checks the
declaration's behavior with the same probes `css.test.ts` asks jsdom, not
only the table; that showed that jsdom rejects a shadow of nothing but
identifiers (`box-shadow: Foo`) and lower-cases CSS-wide keywords for every
property, which the declaration now does. Swift's declaration keeps such
shadows (its test compares only the table); that is fixed in Swift
separately.

## Test plan

### Before

```
$ cargo run -q --release -p lexical-conformance | grep -E 'css/|html/|Total'
| `css/properties` | – | – | – | – | – | not implemented |
| `css/values` | – | – | – | – | – | not implemented |
| `html/entities` | – | – | – | – | – | not implemented |
| `html/export` | – | – | – | – | – | not implemented |
| `html/import` | – | – | – | – | – | not implemented |
| `html/parse` | – | – | – | – | – | not implemented |
| **Total** | 488 | 488 | 0 | 0 | 0 | |
```

### After

```
$ cargo run -q --release -p lexical-conformance | grep -E 'css/|html/|Total'
| `css/properties` | 814 | 814 | 0 | 0 | 0 | pass |
| `css/values` | 69 | 69 | 0 | 0 | 0 | pass |
| `html/entities` | 2125 | 2125 | 0 | 0 | 0 | pass |
| `html/export` | – | – | – | – | – | not implemented |
| `html/import` | – | – | – | – | – | not implemented |
| `html/parse` | 37 | 37 | 0 | 0 | 0 | pass |
| **Total** | 3533 | 3533 | 0 | 0 | 0 | |
$ cargo clippy --workspace --all-targets --all-features --locked -- -D warnings   # clean
$ cargo test --workspace --all-features --locked                                # all pass
```

Before the shadow fix, `css/properties` failed `box-shadow`,
`-webkit-box-shadow` and `text-shadow` (`preserveCase: Rust true, JS
false`).

Amp-Thread-ID: https://ampcode.com/threads/T-506c8fa1-4309-40f1-9810-732b311eaeab

This branch has not been deployed

No deployments
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.

2 participants