Add indexed and named NamedNodeMap access - #693
Open
andrewiggins wants to merge 10 commits into
Open
Conversation
Baseline ESM bundle (esbuild --bundle --minify): 20,773 bytes minified, 7,029 bytes gzip, and 6,308 bytes brotli.
ESM bundle: 20,682 bytes minified, 6,991 bytes gzip, and 6,281 bytes brotli. Saves 91 minified bytes, 38 gzip bytes, and 27 brotli bytes versus 61b4bba.
Keep NamedNodeMap instances on their ordinary prototype path while resolving indexed and named properties through one shared proxy. Preserve indexed precedence and inherited-property masking. ESM bundle (esbuild --bundle --minify): 20,707 bytes minified, 7,012 bytes gzip, and 6,309 bytes brotli. This is 25 minified bytes, 21 gzip bytes, and 28 brotli bytes larger than the per-instance proxy in 6d2a7b3. Runtime benchmark (Node 24.19.0, Apple M4 Pro, three processes with 15 warmed samples each): getAttribute throughput is 11-15% above the pre-proxy dda8de3 implementation; replacement, removal, length, item, iteration, mixed access, and serialization are within about 1% of pre-proxy. Compared with 6d2a7b3, representative common operations improve by 2.2-12.5x. Creating an element, setting its first attribute, and reading map length remains about 14% below pre-proxy and 43% above 6d2a7b3. Direct indexed and named reads measure 12.5M and 20.6M operations/second respectively. Retained V8 heap usage returns to the pre-proxy baseline: approximately 344 bytes for an element with an empty materialized map and 882 bytes with four attributes, versus 376 and 914 bytes with the per-instance proxy. This removes about 32 bytes per materialized NamedNodeMap. Index validation benchmark: retaining the non-negative whole-number checks costs 15 minified bytes, 13 gzip bytes, and 11 brotli bytes versus a parsed canonical-number-only helper, or 36 minified bytes, 19 gzip bytes, and 15 brotli bytes versus the PR #619-style check. The stricter check matches or slightly exceeds valid-index throughput, improves ordinary named and missing access by 5-7%, and improves numeric-looking named access by 22-42% by avoiding unnecessary item() traversal. It has no retained-memory cost.
Move the strict canonical non-negative integer parser to shared.ts so NamedNodeMap and the DOMTokenList work in PR #619 can use the same property-index semantics. Keep each collection's shared-prototype proxy policy local until another implementation demonstrates a useful larger abstraction. ESM bundle (esbuild --bundle --minify): 20,780 bytes minified, 7,040 bytes gzip, and 6,332 bytes brotli. Extracting the helper adds 73 minified bytes, 28 gzip bytes, and 23 brotli bytes versus the inline implementation in 9e2a1c0. Runtime benchmark (Node 24.19.0, Apple M4 Pro, three processes with 15 warmed samples each): helper extraction changes valid-index throughput by -0.3% to -1.3%, ordinary named and missing access by -2.2% to -2.9%, and numeric-looking named access by -1.3% to -2.7%. It does not change retained object layout or memory usage. Compared with simpler index checks, the strict parser costs 15 minified bytes, 13 gzip bytes, and 11 brotli bytes versus a parsed canonical-number-only helper, or 36 minified bytes, 19 gzip bytes, and 15 brotli bytes versus the PR #619-style check. It matches or slightly exceeds valid-index throughput, improves ordinary named and missing access by 5-7%, and improves numeric-looking named access by 22-42% by avoiding unnecessary item() traversal.
Enable six supported attributes-namednodemap subtests and defer the two cases that still require Document.createAttribute, with one also requiring an exposed NamedNodeMap constructor. Promote the getElementById Attr.value mutation case now that indexed NamedNodeMap access is supported.
Limit indexed collection properties to canonical ECMAScript array indices and document why the numeric guards avoid slower coercion and linked-list traversal. Clarify the shared prototype fallback's deliberate Web IDL limitations and add boundary and liveness coverage.
Remove qualified-name matches regardless of namespace, detach removed and replaced Attr nodes, preserve list links when setting an Attr already in the map, and reject attributes owned by another element. Add hook and lifecycle regression coverage.
Update descendant nodes and attached attributes when adopting or inserting a subtree into another document, preserving Attr.ownerDocument and routing later attribute hooks through the new window. Add explicit adoption and cross-document insertion coverage.
andrewiggins
force-pushed
the
named-node-map
branch
from
September 4, 2026 03:59
9f7c638 to
6c2008f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Add live browser-style property access to the polyfilled
NamedNodeMapwithout making it inherit fromArray:NamedNodeMapidentity and avoiding a proxy allocation for every element.getNamedItem()by qualified name across namespaces while preserving methods and inherited properties.Attr.ownerElement, linked-list state, andownerDocumentcorrect when attributes are removed, replaced, directly adopted, or carried across documents with an adopted or inserted subtree. Directly adopted attributes are detached from their current element before their document changes.@remote-dom/polyfillchangeset.Design and performance
A constructor-returned proxy prototype was benchmarked first, but it substantially deoptimized existing methods and added about 32 bytes of retained V8 heap per materialized map. The shared prototype fallback restores ordinary
getAttribute, mutation,length,item, iteration, and serialization paths to approximately the pre-proxy baseline while retaining indexed and named reads.The strict index parser was benchmarked against simpler coercion and bitwise alternatives.
Number.isInteger()plus explicit bounds was the clearest option and performed at least as well overall, while avoiding duplicate linked-list traversal for unusual property names.The final size-limit run reports
@remote-dom/polyfillat 7.05 kB Brotli (+218 B, 3.19%) against an 8 kB limit.Web Platform Tests
dom/nodes/attributes-namednodemap.html; defer two cases that still requireDocument.createAttribute(), with one also requiring an exposedNamedNodeMapconstructor.Document-getElementById.htmlcase that mutates an ID throughelement.attributes[0].value.Current classified WPT result: 5/5 files passed with no supported failures or inventory drift.
Intentional scope
The shared prototype fallback provides ordinary indexed and named reads, but it is not a complete Web IDL legacy-platform-object implementation. This change does not add
has, own-key/descriptor reflection, assignment/deletion restrictions, alternateReflect.get()receiver behavior, or indexed precedence over properties placed directly onNamedNodeMap.prototype. In-use attributes currently throw a basicErrorbecause the polyfill does not exposeDOMException.Document.createAttribute(), globalNamedNodeMapexposure, complete namespace/local-name modeling, and HTML case-folding remain follow-up work.Validation
pnpm type-checkpnpm exec vitest run --project polyfill --project wpt-runner— 16 files, 136 tests passedpnpm test:wpt— 5/5 classified files passedpnpm lintpnpm size