Add DOMContentLoaded, load, and readystatechange events with correct readyState transitions (loading→interactive→complete). Includes Window as a first-class event target, body onload spec quirk, and idempotency guards to prevent double-firing. Code review hardened the API surface by enforcing forward-only state transitions, eliminating a redundant wrapper function, and requesting a redraw after load handler DOM mutations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
383 lines
18 KiB
Markdown
383 lines
18 KiB
Markdown
# DOM Implementation Checklist
|
|
|
|
Checked items mean the feature is implemented end-to-end: internal Rust API exists, JS binding is exposed (where applicable), and behavior is correct. Partial support is left unchecked and called out inline.
|
|
|
|
## Phase 0: Architecture & Infrastructure
|
|
|
|
- [x] Arena-based DOM tree using `NodeId` indices (no lifetimes in cross-crate APIs)
|
|
- [x] Document dirty flag for mutation tracking
|
|
- [ ] DOM conformance test harness (WPT DOM suite integration)
|
|
- [ ] IDL attribute reflection framework (auto-generate JS getters/setters from IDL)
|
|
|
|
## Phase 1: Node Types
|
|
|
|
- [x] `Document` node
|
|
- [x] `Element` node
|
|
- [x] `Text` node (CharacterData)
|
|
- [x] `Comment` node (CharacterData)
|
|
- [ ] `DocumentFragment`
|
|
- Current state: not implemented; needed for `template` contents, `createDocumentFragment()`, and Range operations.
|
|
- [ ] `DocumentType` (doctype node)
|
|
- [ ] `ProcessingInstruction`
|
|
- [ ] `CDATASection` (XML only; low priority)
|
|
- [ ] `Attr` as a node type (attributes are stored as key-value pairs on `ElementData`, not as separate nodes)
|
|
|
|
## Phase 2: Node Interface
|
|
|
|
### 2.1 Properties
|
|
- [x] `nodeType` (internal — used by the engine)
|
|
- Current state: Rust-level only; not exposed to JS as a numeric constant.
|
|
- [ ] `nodeName` / `nodeValue`
|
|
- [ ] `ownerDocument`
|
|
- [ ] `parentNode` / `parentElement`
|
|
- Current state: Rust-level `parent()` / `parentElement()` exist; not JS-exposed.
|
|
- [x] `childNodes` (internal iteration via `children()`)
|
|
- Current state: Rust-level only; not returned as a live `NodeList`.
|
|
- [ ] `firstChild` / `lastChild`
|
|
- Current state: `first_child()` exists in Rust; `lastChild` is missing. Neither JS-exposed.
|
|
- [ ] `nextSibling` / `previousSibling`
|
|
- Current state: `next_sibling()` exists in Rust; `previousSibling` is missing. Neither JS-exposed.
|
|
- [ ] `textContent` (full Node-level semantics)
|
|
- Current state: getter/setter works on Elements via JS; full Node-level semantics (null for Document, concatenation for DocumentFragment) not complete.
|
|
- [ ] `isConnected`
|
|
- [ ] `baseURI`
|
|
|
|
### 2.2 Node Constants
|
|
- [ ] `Node.ELEMENT_NODE` (1), `Node.TEXT_NODE` (3), `Node.COMMENT_NODE` (8), etc.
|
|
|
|
### 2.3 Mutation Methods
|
|
- [x] `appendChild(child)` (Rust + JS)
|
|
- [x] `removeChild(child)` (Rust + JS)
|
|
- [ ] `insertBefore(newNode, referenceNode)`
|
|
- [ ] `replaceChild(newChild, oldChild)`
|
|
- [x] `set_text_content()` (replaces all children with text; Rust-level, JS setter on Element)
|
|
|
|
### 2.4 Query & Comparison Methods
|
|
- [ ] `cloneNode(deep)`
|
|
- [ ] `contains(node)`
|
|
- [ ] `hasChildNodes()`
|
|
- [ ] `compareDocumentPosition(other)`
|
|
- [ ] `isEqualNode(other)` / `isSameNode(other)`
|
|
- [ ] `normalize()` (merge adjacent Text nodes)
|
|
- [ ] `lookupPrefix()` / `lookupNamespaceURI()` / `isDefaultNamespace()` (XML namespace; low priority)
|
|
|
|
## Phase 3: Document Interface
|
|
|
|
### 3.1 Node Creation
|
|
- [x] `document.createElement(tagName)`
|
|
- [x] `document.createTextNode(data)` (Rust-level `create_text()`)
|
|
- Current state: Rust-level only; not JS-exposed.
|
|
- [x] `document.createComment(data)` (Rust-level `create_comment()`)
|
|
- Current state: Rust-level only; not JS-exposed.
|
|
- [ ] `document.createDocumentFragment()`
|
|
- [ ] `document.createEvent(type)` (legacy but widely used)
|
|
- [ ] `document.createTreeWalker(root, whatToShow, filter)`
|
|
- [ ] `document.createNodeIterator(root, whatToShow, filter)`
|
|
- [ ] `document.createRange()`
|
|
- [ ] `document.importNode(node, deep)` / `document.adoptNode(node)`
|
|
|
|
### 3.2 Query Methods
|
|
- [x] `document.getElementById(id)` (Rust + JS)
|
|
- [ ] `document.getElementsByTagName(tagName)` (JS binding)
|
|
- Current state: Rust-level implementation exists; not exposed to JS.
|
|
- [ ] `document.getElementsByClassName(className)` (JS binding)
|
|
- Current state: Rust-level implementation exists; not exposed to JS.
|
|
- [ ] `document.getElementsByName(name)`
|
|
- [ ] `document.querySelector(selectors)`
|
|
- [ ] `document.querySelectorAll(selectors)`
|
|
- Current state: selector matching engine exists in the `selectors` crate; not wired to JS query APIs.
|
|
|
|
### 3.3 Document Properties
|
|
- [ ] `document.documentElement` (root `<html>` element)
|
|
- [ ] `document.head` / `document.body`
|
|
- [ ] `document.title` (get/set)
|
|
- [ ] `document.URL` / `document.documentURI`
|
|
- [ ] `document.domain` (deprecated but commonly accessed)
|
|
- [ ] `document.referrer`
|
|
- [ ] `document.cookie` (get/set)
|
|
- [x] `document.readyState`
|
|
- [ ] `document.characterSet` / `document.charset`
|
|
- [ ] `document.contentType`
|
|
- [ ] `document.compatMode` (quirks vs standards)
|
|
- [ ] `document.doctype`
|
|
- [ ] `document.activeElement`
|
|
- [ ] `document.visibilityState` / `document.hidden`
|
|
|
|
### 3.4 Document Lifecycle Events
|
|
- [x] `DOMContentLoaded` event
|
|
- [x] `load` event
|
|
- [x] `readystatechange` event
|
|
- [ ] `visibilitychange` event
|
|
|
|
## Phase 4: Element Interface
|
|
|
|
### 4.1 Core Properties
|
|
- [x] `element.id` (get; JS-exposed)
|
|
- [x] `element.tagName` (JS-exposed)
|
|
- [x] `element.className` (JS-exposed)
|
|
- [ ] `element.classList` (returns `DOMTokenList`)
|
|
- [ ] `element.slot`
|
|
- [ ] `element.localName` / `element.namespaceURI` / `element.prefix`
|
|
|
|
### 4.2 Attribute Methods
|
|
- [x] `element.getAttribute(name)` (internal storage; JS-exposed as property access)
|
|
- Current state: attributes stored as Vec of name-value pairs; JS reads attributes via property access, not a dedicated `getAttribute()` call.
|
|
- [x] `element.setAttribute(name, value)` (Rust-level)
|
|
- Current state: Rust-level only; not exposed as a JS method.
|
|
- [ ] `element.getAttribute(name)` (JS method)
|
|
- [ ] `element.setAttribute(name, value)` (JS method)
|
|
- [ ] `element.removeAttribute(name)`
|
|
- [ ] `element.hasAttribute(name)`
|
|
- [ ] `element.toggleAttribute(name, force)`
|
|
- [ ] `element.getAttributeNames()`
|
|
- [ ] `element.attributes` (returns `NamedNodeMap`)
|
|
- [ ] `element.dataset` (data-* attributes as DOMStringMap)
|
|
|
|
### 4.3 Content Properties
|
|
- [x] `element.textContent` (getter/setter; JS-exposed)
|
|
- [x] `element.innerHTML` (getter/setter; JS-exposed)
|
|
- Current state: wired to fragment parse/serialize; not fully spec-compliant context-sensitive parsing.
|
|
- [ ] `element.outerHTML` (getter/setter)
|
|
- [ ] `element.innerText` (getter/setter; layout-aware)
|
|
- [ ] `element.insertAdjacentHTML(position, text)`
|
|
- [ ] `element.insertAdjacentElement(position, element)`
|
|
- [ ] `element.insertAdjacentText(position, text)`
|
|
|
|
### 4.4 Traversal Properties
|
|
- [ ] `element.children` (returns live `HTMLCollection` of child elements)
|
|
- [ ] `element.childElementCount`
|
|
- [ ] `element.firstElementChild` / `element.lastElementChild`
|
|
- [ ] `element.nextElementSibling` / `element.previousElementSibling`
|
|
- Current state: Rust-level `element_siblings()` exists for CSS selector matching; not JS-exposed.
|
|
- [ ] `element.closest(selectors)`
|
|
- [ ] `element.matches(selectors)`
|
|
- Current state: selector matching logic exists internally; not JS-exposed.
|
|
|
|
### 4.5 Query Methods (on Element)
|
|
- [ ] `element.querySelector(selectors)`
|
|
- [ ] `element.querySelectorAll(selectors)`
|
|
- [ ] `element.getElementsByTagName(tagName)`
|
|
- [ ] `element.getElementsByClassName(className)`
|
|
|
|
### 4.6 Geometry & Scroll
|
|
- [ ] `element.getBoundingClientRect()` → `DOMRect`
|
|
- [ ] `element.getClientRects()` → `DOMRectList`
|
|
- [ ] `element.clientWidth` / `element.clientHeight`
|
|
- [ ] `element.clientTop` / `element.clientLeft`
|
|
- [ ] `element.scrollWidth` / `element.scrollHeight`
|
|
- [ ] `element.scrollTop` / `element.scrollLeft` (get/set)
|
|
- [ ] `element.scrollIntoView()`
|
|
- [ ] `element.offsetWidth` / `element.offsetHeight`
|
|
- [ ] `element.offsetTop` / `element.offsetLeft`
|
|
- [ ] `element.offsetParent`
|
|
|
|
### 4.7 Focus & Interaction
|
|
- [ ] `element.focus()` / `element.blur()`
|
|
- [ ] `element.click()`
|
|
- [ ] `element.tabIndex`
|
|
|
|
## Phase 5: DOM Collections
|
|
|
|
- [ ] `NodeList` (live and static variants)
|
|
- Live: returned by `childNodes`, `getElementsByTagName`, `getElementsByClassName`
|
|
- Static: returned by `querySelectorAll`
|
|
- Required methods: `item(index)`, `length`, `forEach()`, `entries()`, `keys()`, `values()`
|
|
- [ ] `HTMLCollection` (live, returned by `children`, `getElementsByTagName` on Element)
|
|
- Required methods: `item(index)`, `namedItem(name)`, `length`
|
|
- [ ] `DOMTokenList` (for `classList`, `relList`, `sandbox`, etc.)
|
|
- Required methods: `add()`, `remove()`, `toggle()`, `contains()`, `replace()`, `item()`, `length`, `value`, `forEach()`
|
|
- [ ] `NamedNodeMap` (for `element.attributes`)
|
|
- Required methods: `getNamedItem()`, `setNamedItem()`, `removeNamedItem()`, `item()`, `length`
|
|
|
|
## Phase 6: Events
|
|
|
|
### 6.1 EventTarget Interface
|
|
- [x] `addEventListener(type, listener, options/useCapture)`
|
|
- [x] `removeEventListener(type, listener)`
|
|
- [ ] `dispatchEvent(event)` (programmatic dispatch)
|
|
|
|
### 6.2 Event Interface
|
|
- [x] `event.type`
|
|
- [x] `event.target` / `event.currentTarget`
|
|
- [x] `event.bubbles` / `event.cancelable`
|
|
- [x] `event.defaultPrevented`
|
|
- [x] `event.eventPhase`
|
|
- [x] `event.preventDefault()`
|
|
- [x] `event.stopPropagation()`
|
|
- [ ] `event.stopImmediatePropagation()`
|
|
- [ ] `event.composed` / `event.composedPath()`
|
|
- [ ] `event.isTrusted`
|
|
- [ ] `event.timeStamp`
|
|
|
|
### 6.3 Event Dispatch Algorithm
|
|
- [x] Target phase
|
|
- [x] Bubble phase
|
|
- Current state: implemented for click events.
|
|
- [ ] Capture phase
|
|
- [ ] Correct event path construction (including shadow DOM composed path)
|
|
- [ ] Passive event listeners (`{ passive: true }`)
|
|
- [ ] `once` option (`{ once: true }`)
|
|
- [ ] Event listener `signal` option (AbortSignal integration)
|
|
|
|
### 6.4 Event Constructors
|
|
- [ ] `new Event(type, options)`
|
|
- [ ] `new CustomEvent(type, options)` with `detail` property
|
|
- [ ] `new MouseEvent(type, options)`
|
|
- [ ] `new KeyboardEvent(type, options)`
|
|
- [ ] `new FocusEvent(type, options)`
|
|
- [ ] `new InputEvent(type, options)`
|
|
- [ ] `new WheelEvent(type, options)`
|
|
- [ ] `new PointerEvent(type, options)`
|
|
|
|
### 6.5 Event Types
|
|
- [x] `click`
|
|
- [ ] Mouse: `mousedown`, `mouseup`, `mousemove`, `mouseenter`, `mouseleave`, `mouseover`, `mouseout`, `dblclick`, `contextmenu`
|
|
- [ ] Keyboard: `keydown`, `keyup`, `keypress` (deprecated)
|
|
- [ ] Focus: `focus`, `blur`, `focusin`, `focusout`
|
|
- [ ] Input: `input`, `change`, `beforeinput`
|
|
- [ ] Form: `submit`, `reset`, `invalid`
|
|
- [ ] Drag: `dragstart`, `drag`, `dragend`, `dragenter`, `dragleave`, `dragover`, `drop`
|
|
- [ ] Touch: `touchstart`, `touchmove`, `touchend`, `touchcancel`
|
|
- [ ] Pointer: `pointerdown`, `pointerup`, `pointermove`, `pointerenter`, `pointerleave`, `pointerover`, `pointerout`, `pointercancel`, `gotpointercapture`, `lostpointercapture`
|
|
- [ ] Scroll: `scroll`, `scrollend`
|
|
- [ ] Resize: `resize`
|
|
- [ ] Clipboard: `copy`, `cut`, `paste`
|
|
- [ ] Animation: `animationstart`, `animationend`, `animationiteration`, `transitionend`
|
|
|
|
## Phase 7: DOM Traversal
|
|
|
|
- [ ] `TreeWalker` interface
|
|
- `createTreeWalker(root, whatToShow, filter)`
|
|
- Methods: `parentNode()`, `firstChild()`, `lastChild()`, `previousSibling()`, `nextSibling()`, `previousNode()`, `nextNode()`
|
|
- `whatToShow` bitmask filtering
|
|
- [ ] `NodeIterator` interface
|
|
- `createNodeIterator(root, whatToShow, filter)`
|
|
- Methods: `nextNode()`, `previousNode()`, `detach()`
|
|
- [x] Internal tree iteration (Rust-level `TreeIterator` for pre-order traversal)
|
|
- Current state: used by the engine for style/layout; not JS-exposed.
|
|
|
|
## Phase 8: Ranges
|
|
|
|
- [ ] `Range` interface
|
|
- Construction: `document.createRange()`, `new Range()`
|
|
- Boundary: `setStart()`, `setEnd()`, `setStartBefore()`, `setStartAfter()`, `setEndBefore()`, `setEndAfter()`
|
|
- Properties: `startContainer`, `startOffset`, `endContainer`, `endOffset`, `collapsed`, `commonAncestorContainer`
|
|
- Comparison: `compareBoundaryPoints()`, `comparePoint()`, `isPointInRange()`, `intersectsNode()`
|
|
- Content: `cloneContents()`, `extractContents()`, `deleteContents()`, `insertNode()`, `surroundContents()`
|
|
- Utility: `cloneRange()`, `detach()`, `toString()`
|
|
- `createContextualFragment(html)`
|
|
- [ ] `StaticRange` interface
|
|
- [ ] `AbstractRange` base interface
|
|
|
|
## Phase 9: Selection API
|
|
|
|
- [ ] `window.getSelection()` → `Selection`
|
|
- [ ] `Selection` interface
|
|
- Properties: `anchorNode`, `anchorOffset`, `focusNode`, `focusOffset`, `isCollapsed`, `rangeCount`, `type`
|
|
- Methods: `getRangeAt()`, `addRange()`, `removeRange()`, `removeAllRanges()`, `collapse()`, `collapseToStart()`, `collapseToEnd()`, `extend()`, `selectAllChildren()`, `deleteFromDocument()`, `containsNode()`, `toString()`
|
|
- [ ] Input selection: `selectionStart`, `selectionEnd`, `selectionDirection` on input/textarea
|
|
- [ ] `select` event
|
|
|
|
## Phase 10: MutationObserver
|
|
|
|
- [ ] `new MutationObserver(callback)`
|
|
- [ ] `.observe(target, options)` — options: `childList`, `attributes`, `characterData`, `subtree`, `attributeFilter`, `attributeOldValue`, `characterDataOldValue`
|
|
- [ ] `.disconnect()`
|
|
- [ ] `.takeRecords()`
|
|
- [ ] `MutationRecord` interface
|
|
- Properties: `type`, `target`, `addedNodes`, `removedNodes`, `previousSibling`, `nextSibling`, `attributeName`, `attributeNamespace`, `oldValue`
|
|
|
|
## Phase 11: CSSOM (CSS Object Model)
|
|
|
|
### 11.1 Inline Style Access
|
|
- [ ] `element.style` → `CSSStyleDeclaration`
|
|
- Property access: `element.style.color`, `element.style.fontSize`, etc.
|
|
- Methods: `getPropertyValue()`, `setProperty()`, `removeProperty()`, `getPropertyPriority()`
|
|
- `element.style.cssText` (get/set)
|
|
- `element.style.length` / `element.style.item(index)`
|
|
|
|
### 11.2 Computed Style
|
|
- [ ] `window.getComputedStyle(element, pseudoElt)` → `CSSStyleDeclaration`
|
|
- Current state: computed styles exist in the `style` crate; not exposed to JS.
|
|
|
|
### 11.3 Stylesheet Access
|
|
- [ ] `document.styleSheets` → `StyleSheetList`
|
|
- [ ] `CSSStyleSheet` interface
|
|
- `.cssRules` / `.insertRule()` / `.deleteRule()`
|
|
- [ ] `CSSRule` / `CSSStyleRule` / `CSSMediaRule` / `CSSImportRule` interfaces
|
|
|
|
## Phase 12: DOM Parsing & Serialization
|
|
|
|
- [ ] `DOMParser`
|
|
- `new DOMParser()`
|
|
- `.parseFromString(str, type)` — types: `text/html`, `text/xml`, `application/xml`, `application/xhtml+xml`, `image/svg+xml`
|
|
- [ ] `XMLSerializer`
|
|
- `new XMLSerializer()`
|
|
- `.serializeToString(node)`
|
|
- [x] `innerHTML` setter (uses HTML parser for fragment parsing)
|
|
- Current state: basic fragment parse/serialize; not fully spec-compliant.
|
|
- [x] `innerHTML` getter (serializes child nodes to HTML string)
|
|
|
|
## Phase 13: HTML-Specific Element Interfaces
|
|
|
|
### 13.1 HTMLElement Base
|
|
- [ ] `HTMLElement` interface (extends `Element`)
|
|
- Properties: `title`, `lang`, `dir`, `hidden`, `draggable`, `contentEditable`, `isContentEditable`, `spellcheck`, `tabIndex`
|
|
- Style: `.style` property → `CSSStyleDeclaration`
|
|
- Data: `.dataset` → `DOMStringMap`
|
|
- Interaction: `.click()`, `.focus()`, `.blur()`
|
|
- Offset: `.offsetTop`, `.offsetLeft`, `.offsetWidth`, `.offsetHeight`, `.offsetParent`
|
|
- Scroll: `.scrollTop`, `.scrollLeft`, `.scrollWidth`, `.scrollHeight`
|
|
|
|
### 13.2 Specific Element Interfaces
|
|
- [ ] `HTMLAnchorElement` — `href`, `target`, `rel`, `download`, `origin`, `protocol`, `hostname`, `pathname`, etc.
|
|
- [ ] `HTMLImageElement` — `src`, `alt`, `width`, `height`, `naturalWidth`, `naturalHeight`, `complete`, `currentSrc`, `decode()`
|
|
- [ ] `HTMLInputElement` — `value`, `type`, `name`, `checked`, `disabled`, `readOnly`, `placeholder`, `required`, `validity`, `checkValidity()`, `setCustomValidity()`
|
|
- [ ] `HTMLSelectElement` — `value`, `selectedIndex`, `options`, `selectedOptions`, `multiple`, `add()`, `remove()`
|
|
- [ ] `HTMLTextAreaElement` — `value`, `rows`, `cols`, `selectionStart`, `selectionEnd`, `select()`
|
|
- [ ] `HTMLFormElement` — `elements`, `action`, `method`, `submit()`, `reset()`, `checkValidity()`
|
|
- [ ] `HTMLButtonElement` — `type`, `value`, `disabled`, `form`
|
|
- [ ] `HTMLCanvasElement` — `getContext()`, `width`, `height`, `toDataURL()`, `toBlob()`
|
|
- [ ] `HTMLMediaElement` — `src`, `play()`, `pause()`, `currentTime`, `duration`, `paused`, `volume`, `muted`
|
|
- [ ] `HTMLVideoElement` — `videoWidth`, `videoHeight`, `poster`
|
|
- [ ] `HTMLTableElement` — `rows`, `tBodies`, `tHead`, `tFoot`, `caption`, `insertRow()`, `deleteRow()`, `createTHead()`, `createTBody()`, `createTFoot()`, `createCaption()`
|
|
- [ ] `HTMLScriptElement` — `src`, `type`, `async`, `defer`, `text`
|
|
- [ ] `HTMLStyleElement` — `sheet`
|
|
- [ ] `HTMLLinkElement` — `href`, `rel`, `type`, `sheet`
|
|
- [ ] `HTMLTemplateElement` — `content` (DocumentFragment)
|
|
|
|
## Phase 14: AbortController / AbortSignal
|
|
|
|
- [ ] `new AbortController()`
|
|
- [ ] `AbortController.prototype.signal` → `AbortSignal`
|
|
- [ ] `AbortController.prototype.abort(reason)`
|
|
- [ ] `AbortSignal.prototype.aborted`
|
|
- [ ] `AbortSignal.prototype.reason`
|
|
- [ ] `AbortSignal.prototype.onabort` / `addEventListener('abort', ...)`
|
|
- [ ] `AbortSignal.abort(reason)` (static)
|
|
- [ ] `AbortSignal.timeout(ms)` (static)
|
|
- [ ] Integration with `fetch()`, event listeners, and other abortable APIs
|
|
|
|
## Phase 15: Intersection & Resize Observers
|
|
|
|
- [ ] `IntersectionObserver`
|
|
- `new IntersectionObserver(callback, options)`
|
|
- `.observe(target)`, `.unobserve(target)`, `.disconnect()`, `.takeRecords()`
|
|
- `IntersectionObserverEntry` — `target`, `isIntersecting`, `intersectionRatio`, `intersectionRect`, `boundingClientRect`, `rootBounds`, `time`
|
|
- [ ] `ResizeObserver`
|
|
- `new ResizeObserver(callback)`
|
|
- `.observe(target, options)`, `.unobserve(target)`, `.disconnect()`
|
|
- `ResizeObserverEntry` — `target`, `contentRect`, `borderBoxSize`, `contentBoxSize`
|
|
|
|
## Phase 16: Conformance Exit Criteria
|
|
|
|
- [ ] All Node types per DOM Living Standard implemented
|
|
- [ ] DOM mutation methods complete and correct (`appendChild`, `insertBefore`, `replaceChild`, `removeChild`, `cloneNode`)
|
|
- [ ] Query APIs wired to JS (`querySelector`, `querySelectorAll`, `getElementById`, `getElementsByTagName`, `getElementsByClassName`)
|
|
- [ ] DOM collections implemented (`NodeList`, `HTMLCollection`, `DOMTokenList`)
|
|
- [ ] Event dispatch algorithm spec-compliant (capture → target → bubble)
|
|
- [ ] CSSOM basics exposed to JS (`element.style`, `getComputedStyle`)
|
|
- [ ] Pass WPT DOM test suite at target threshold
|
|
- [ ] No crashers: malformed DOM operations must not crash the engine
|
|
- [ ] Publish DOM conformance report with pass rates and remaining gaps
|