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>
18 KiB
18 KiB
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
- Arena-based DOM tree using
NodeIdindices (no lifetimes in cross-crate APIs) - 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
DocumentnodeElementnodeTextnode (CharacterData)Commentnode (CharacterData)DocumentFragment- Current state: not implemented; needed for
templatecontents,createDocumentFragment(), and Range operations.
- Current state: not implemented; needed for
DocumentType(doctype node)ProcessingInstructionCDATASection(XML only; low priority)Attras a node type (attributes are stored as key-value pairs onElementData, not as separate nodes)
Phase 2: Node Interface
2.1 Properties
nodeType(internal — used by the engine)- Current state: Rust-level only; not exposed to JS as a numeric constant.
nodeName/nodeValueownerDocumentparentNode/parentElement- Current state: Rust-level
parent()/parentElement()exist; not JS-exposed.
- Current state: Rust-level
childNodes(internal iteration viachildren())- Current state: Rust-level only; not returned as a live
NodeList.
- Current state: Rust-level only; not returned as a live
firstChild/lastChild- Current state:
first_child()exists in Rust;lastChildis missing. Neither JS-exposed.
- Current state:
nextSibling/previousSibling- Current state:
next_sibling()exists in Rust;previousSiblingis missing. Neither JS-exposed.
- Current state:
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.
isConnectedbaseURI
2.2 Node Constants
Node.ELEMENT_NODE(1),Node.TEXT_NODE(3),Node.COMMENT_NODE(8), etc.
2.3 Mutation Methods
appendChild(child)(Rust + JS)removeChild(child)(Rust + JS)insertBefore(newNode, referenceNode)replaceChild(newChild, oldChild)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
document.createElement(tagName)document.createTextNode(data)(Rust-levelcreate_text())- Current state: Rust-level only; not JS-exposed.
document.createComment(data)(Rust-levelcreate_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
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
selectorscrate; not wired to JS query APIs.
- Current state: selector matching engine exists in the
3.3 Document Properties
document.documentElement(root<html>element)document.head/document.bodydocument.title(get/set)document.URL/document.documentURIdocument.domain(deprecated but commonly accessed)document.referrerdocument.cookie(get/set)document.readyStatedocument.characterSet/document.charsetdocument.contentTypedocument.compatMode(quirks vs standards)document.doctypedocument.activeElementdocument.visibilityState/document.hidden
3.4 Document Lifecycle Events
DOMContentLoadedeventloadeventreadystatechangeeventvisibilitychangeevent
Phase 4: Element Interface
4.1 Core Properties
element.id(get; JS-exposed)element.tagName(JS-exposed)element.className(JS-exposed)element.classList(returnsDOMTokenList)element.slotelement.localName/element.namespaceURI/element.prefix
4.2 Attribute Methods
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.
- Current state: attributes stored as Vec of name-value pairs; JS reads attributes via property access, not a dedicated
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(returnsNamedNodeMap)element.dataset(data-* attributes as DOMStringMap)
4.3 Content Properties
element.textContent(getter/setter; JS-exposed)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 liveHTMLCollectionof child elements)element.childElementCountelement.firstElementChild/element.lastElementChildelement.nextElementSibling/element.previousElementSibling- Current state: Rust-level
element_siblings()exists for CSS selector matching; not JS-exposed.
- Current state: Rust-level
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()→DOMRectelement.getClientRects()→DOMRectListelement.clientWidth/element.clientHeightelement.clientTop/element.clientLeftelement.scrollWidth/element.scrollHeightelement.scrollTop/element.scrollLeft(get/set)element.scrollIntoView()element.offsetWidth/element.offsetHeightelement.offsetTop/element.offsetLeftelement.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()
- Live: returned by
HTMLCollection(live, returned bychildren,getElementsByTagNameon Element)- Required methods:
item(index),namedItem(name),length
- Required methods:
DOMTokenList(forclassList,relList,sandbox, etc.)- Required methods:
add(),remove(),toggle(),contains(),replace(),item(),length,value,forEach()
- Required methods:
NamedNodeMap(forelement.attributes)- Required methods:
getNamedItem(),setNamedItem(),removeNamedItem(),item(),length
- Required methods:
Phase 6: Events
6.1 EventTarget Interface
addEventListener(type, listener, options/useCapture)removeEventListener(type, listener)dispatchEvent(event)(programmatic dispatch)
6.2 Event Interface
event.typeevent.target/event.currentTargetevent.bubbles/event.cancelableevent.defaultPreventedevent.eventPhaseevent.preventDefault()event.stopPropagation()event.stopImmediatePropagation()event.composed/event.composedPath()event.isTrustedevent.timeStamp
6.3 Event Dispatch Algorithm
- Target phase
- Bubble phase
- Current state: implemented for click events.
- Capture phase
- Correct event path construction (including shadow DOM composed path)
- Passive event listeners (
{ passive: true }) onceoption ({ once: true })- Event listener
signaloption (AbortSignal integration)
6.4 Event Constructors
new Event(type, options)new CustomEvent(type, options)withdetailpropertynew 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
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
TreeWalkerinterfacecreateTreeWalker(root, whatToShow, filter)- Methods:
parentNode(),firstChild(),lastChild(),previousSibling(),nextSibling(),previousNode(),nextNode() whatToShowbitmask filtering
NodeIteratorinterfacecreateNodeIterator(root, whatToShow, filter)- Methods:
nextNode(),previousNode(),detach()
- Internal tree iteration (Rust-level
TreeIteratorfor pre-order traversal)- Current state: used by the engine for style/layout; not JS-exposed.
Phase 8: Ranges
Rangeinterface- 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)
- Construction:
StaticRangeinterfaceAbstractRangebase interface
Phase 9: Selection API
window.getSelection()→SelectionSelectioninterface- Properties:
anchorNode,anchorOffset,focusNode,focusOffset,isCollapsed,rangeCount,type - Methods:
getRangeAt(),addRange(),removeRange(),removeAllRanges(),collapse(),collapseToStart(),collapseToEnd(),extend(),selectAllChildren(),deleteFromDocument(),containsNode(),toString()
- Properties:
- Input selection:
selectionStart,selectionEnd,selectionDirectionon input/textarea selectevent
Phase 10: MutationObserver
new MutationObserver(callback).observe(target, options)— options:childList,attributes,characterData,subtree,attributeFilter,attributeOldValue,characterDataOldValue.disconnect().takeRecords()MutationRecordinterface- Properties:
type,target,addedNodes,removedNodes,previousSibling,nextSibling,attributeName,attributeNamespace,oldValue
- Properties:
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)
- Property access:
11.2 Computed Style
window.getComputedStyle(element, pseudoElt)→CSSStyleDeclaration- Current state: computed styles exist in the
stylecrate; not exposed to JS.
- Current state: computed styles exist in the
11.3 Stylesheet Access
document.styleSheets→StyleSheetListCSSStyleSheetinterface.cssRules/.insertRule()/.deleteRule()
CSSRule/CSSStyleRule/CSSMediaRule/CSSImportRuleinterfaces
Phase 12: DOM Parsing & Serialization
DOMParsernew DOMParser().parseFromString(str, type)— types:text/html,text/xml,application/xml,application/xhtml+xml,image/svg+xml
XMLSerializernew XMLSerializer().serializeToString(node)
innerHTMLsetter (uses HTML parser for fragment parsing)- Current state: basic fragment parse/serialize; not fully spec-compliant.
innerHTMLgetter (serializes child nodes to HTML string)
Phase 13: HTML-Specific Element Interfaces
13.1 HTMLElement Base
HTMLElementinterface (extendsElement)- Properties:
title,lang,dir,hidden,draggable,contentEditable,isContentEditable,spellcheck,tabIndex - Style:
.styleproperty →CSSStyleDeclaration - Data:
.dataset→DOMStringMap - Interaction:
.click(),.focus(),.blur() - Offset:
.offsetTop,.offsetLeft,.offsetWidth,.offsetHeight,.offsetParent - Scroll:
.scrollTop,.scrollLeft,.scrollWidth,.scrollHeight
- Properties:
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,formHTMLCanvasElement—getContext(),width,height,toDataURL(),toBlob()HTMLMediaElement—src,play(),pause(),currentTime,duration,paused,volume,mutedHTMLVideoElement—videoWidth,videoHeight,posterHTMLTableElement—rows,tBodies,tHead,tFoot,caption,insertRow(),deleteRow(),createTHead(),createTBody(),createTFoot(),createCaption()HTMLScriptElement—src,type,async,defer,textHTMLStyleElement—sheetHTMLLinkElement—href,rel,type,sheetHTMLTemplateElement—content(DocumentFragment)
Phase 14: AbortController / AbortSignal
new AbortController()AbortController.prototype.signal→AbortSignalAbortController.prototype.abort(reason)AbortSignal.prototype.abortedAbortSignal.prototype.reasonAbortSignal.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
IntersectionObservernew IntersectionObserver(callback, options).observe(target),.unobserve(target),.disconnect(),.takeRecords()IntersectionObserverEntry—target,isIntersecting,intersectionRatio,intersectionRect,boundingClientRect,rootBounds,time
ResizeObservernew 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