Skip to main content
The Document Object Model (DOM) is the foundation of Ladybird’s web rendering. It represents HTML and XML documents as a tree of nodes, providing the structure that CSS styles and JavaScript manipulates.

Overview

LibWeb’s DOM implementation (LibWeb/DOM/) provides a complete, spec-compliant DOM with:

Node hierarchy

Elements, text, comments, and document nodes

Event system

Event dispatch, bubbling, and capturing

Tree manipulation

Insert, remove, and move nodes

Live collections

Dynamic element and node lists

Document and nodes

Document

The Document class (DOM/Document.h, DOM/Document.cpp) is the root of the DOM tree:
class Document : public ParentNode {
    // Document properties
    URL::URL const& url() const;
    String title() const;
    HTML::Window& window();
    
    // Tree manipulation
    Element* document_element();
    HTMLElement* body();
    HTMLHeadElement* head();
    
    // Element creation
    Element* create_element(String const& tag_name);
    Text* create_text_node(String const& data);
    
    // Queries
    Element* get_element_by_id(String const& id);
    Vector<Element*> get_elements_by_tag_name(String const& tag_name);
    Element* query_selector(String const& selector);
    Vector<Element*> query_selector_all(String const& selector);
};
Key responsibilities:
  • URL and origin: Security and resource loading
  • Quirks mode: Compatibility mode detection
  • Style scopes: Stylesheet management
  • Layout invalidation: Trigger re-layout when needed
  • Event dispatch: Root of event propagation

Node hierarchy

All DOM objects inherit from Node:
class Node : public EventTarget {
    enum class Type {
        Document,
        Element,
        Text,
        Comment,
        DocumentType,
        DocumentFragment,
        ProcessingInstruction,
        CDATASection
    };
    
    // Tree navigation
    Node* parent_node();
    Node* first_child();
    Node* last_child();
    Node* previous_sibling();
    Node* next_sibling();
    
    // Tree manipulation
    void append_child(Node& child);
    void insert_before(Node& node, Node* child);
    void remove_child(Node& child);
    void replace_child(Node& node, Node& child);
};
The node tree is implemented using TreeNode<Node> from LibWeb/TreeNode.h, providing efficient tree traversal and manipulation.

Element

Elements are the most common node type (DOM/Element.h, DOM/Element.cpp):
class Element : public ParentNode {
    // Tag and attributes
    String tag_name() const;
    String get_attribute(String const& name) const;
    void set_attribute(String const& name, String const& value);
    bool has_attribute(String const& name) const;
    void remove_attribute(String const& name);
    
    // Classes
    bool has_class(String const& name) const;
    DOMTokenList& class_list();
    
    // ID
    String id() const;
    void set_id(String const& id);
    
    // Style
    CSSStyleDeclaration& style();
    
    // Geometry
    int client_width() const;
    int client_height() const;
    int scroll_width() const;
    int scroll_height() const;
};

Text nodes

Text content is represented by Text nodes (DOM/CharacterData.h, DOM/Text.h):
class CharacterData : public Node {
    String data() const;
    void set_data(String const& data);
    u32 length() const;
};

class Text : public CharacterData {
    // Text-specific operations
    Text* split_text(u32 offset);
    String whole_text() const;
};

Comment nodes

Comments in HTML/XML (DOM/Comment.h):
class Comment : public CharacterData {
    // Represents <!-- comment -->
};

ParentNode and ChildNode

Mixin interfaces for nodes with children:

ParentNode

class ParentNode : public Node {
    // Query methods
    Element* query_selector(String const& selector);
    Vector<Element*> query_selector_all(String const& selector);
    Element* get_element_by_id(String const& id);
    
    // Child access
    HTMLCollection* children();
    Element* first_element_child();
    Element* last_element_child();
    u32 child_element_count();
};

ChildNode

class ChildNode {
    // Convenience methods
    void remove();
    void before(Vector<Node*> const& nodes);
    void after(Vector<Node*> const& nodes);
    void replace_with(Vector<Node*> const& nodes);
};

Event system

The DOM event system enables interactive web pages:

EventTarget

Base class for objects that can receive events (DOM/EventTarget.h):
class EventTarget : public JS::Cell {
    // Add event listeners
    void add_event_listener(
        String const& type,
        IDLEventListener* callback,
        Variant<bool, AddEventListenerOptions> options
    );
    
    // Remove listeners
    void remove_event_listener(
        String const& type,
        IDLEventListener* callback,
        Variant<bool, EventListenerOptions> options
    );
    
    // Dispatch events
    bool dispatch_event(Event& event);
};

Event

Base event class (DOM/Event.h, DOM/Event.idl):
class Event {
    String type() const;              // "click", "load", etc.
    EventTarget* target() const;      // Event target
    EventTarget* current_target() const;  // Current handler
    
    // Event phases
    enum class Phase {
        None = 0,
        CapturingPhase = 1,
        AtTarget = 2,
        BubblingPhase = 3
    };
    Phase event_phase() const;
    
    // Event flow control
    void stop_propagation();
    void stop_immediate_propagation();
    void prevent_default();
    
    bool bubbles() const;
    bool cancelable() const;
    bool default_prevented() const;
};

Event dispatch algorithm

Events propagate through the DOM tree in three phases:
  1. Capturing phase: From document to target
  2. At target phase: On the target element
  3. Bubbling phase: From target back to document
// Example event path:
// Document -> HTML -> Body -> Div -> Button (target)
//
// Capturing:  Document → HTML → Body → Div → Button
// At target:  Button
// Bubbling:   Button → Div → Body → HTML → Document
Not all events bubble. For example, focus and blur events don’t bubble, but focusin and focusout do.

Custom events

Create custom events with CustomEvent (DOM/CustomEvent.h):
class CustomEvent : public Event {
    JS::Value detail() const;  // Custom data
};

// Usage:
auto event = CustomEvent::create(realm, "my-event");
element.dispatch_event(event);

DOM collections

HTMLCollection

Live collection of elements (DOM/HTMLCollection.h):
class HTMLCollection {
    u32 length() const;
    Element* item(u32 index) const;
    Element* named_item(String const& name) const;
    
    // Automatically updates when DOM changes
};

// Example:
auto divs = document.get_elements_by_tag_name("div");
// divs.length() updates automatically as divs are added/removed

NodeList

List of nodes, can be live or static (DOM/NodeList.h):
class NodeList {
    u32 length() const;
    Node* item(u32 index) const;
};

DOMTokenList

Manage space-separated tokens like classes (DOM/DOMTokenList.h):
class DOMTokenList {
    void add(Vector<String> const& tokens);
    void remove(Vector<String> const& tokens);
    bool toggle(String const& token, Optional<bool> force);
    bool contains(String const& token) const;
    void replace(String const& old_token, String const& new_token);
};

// Usage:
element.class_list().add({ "active", "highlight" });
element.class_list().toggle("hidden");

Ranges and selections

Range

Represents a portion of the document (DOM/Range.h):
class Range : public AbstractRange {
    // Boundary points
    Node* start_container();
    u32 start_offset();
    Node* end_container();
    u32 end_offset();
    
    // Mutation
    void set_start(Node* node, u32 offset);
    void set_end(Node* node, u32 offset);
    DocumentFragment* extract_contents();
    void insert_node(Node& node);
    
    // Comparison
    bool is_point_in_range(Node* node, u32 offset);
};

Selection

User’s current selection (Selection/Selection.h):
class Selection {
    Range* get_range_at(u32 index);
    u32 range_count() const;
    
    Node* anchor_node();
    u32 anchor_offset();
    Node* focus_node();
    u32 focus_offset();
    
    bool is_collapsed() const;
    void collapse(Node* node, u32 offset);
    void select_all_children(Node& node);
};

Mutation observers

Observe changes to the DOM (DOM/MutationObserver.h):
class MutationObserver {
    void observe(
        Node& target,
        MutationObserverInit const& options
    );
    
    void disconnect();
    Vector<MutationRecord*> take_records();
};

class MutationRecord {
    String type();  // "attributes", "childList", "characterData"
    Node* target();
    Vector<Node*> added_nodes();
    Vector<Node*> removed_nodes();
    Node* previous_sibling();
    Node* next_sibling();
};
Mutation observers are perfect for reacting to DOM changes without polling. They’re used internally by frameworks and can be very powerful for custom components.

Attributes

Element attributes are managed through Attr nodes:

Attr

class Attr : public Node {
    String name() const;
    String value() const;
    void set_value(String const& value);
    
    Element* owner_element() const;
    String namespace_uri() const;
    String prefix() const;
    String local_name() const;
};

NamedNodeMap

Collection of attributes (DOM/NamedNodeMap.h):
class NamedNodeMap {
    u32 length() const;
    Attr* item(u32 index);
    Attr* get_named_item(String const& name);
    Attr* set_named_item(Attr& attr);
    Attr* remove_named_item(String const& name);
};

// Access via:
element.attributes().get_named_item("id");

Document fragments

Lightweight containers for nodes (DOM/DocumentFragment.h):
class DocumentFragment : public ParentNode {
    // Used for:
    // - Batch DOM operations
    // - Template content
    // - Range operations
};

// Example:
auto fragment = document.create_document_fragment();
for (auto item : items) {
    auto li = document.create_element("li");
    li.set_text_content(item);
    fragment.append_child(li);
}
list.append_child(fragment);  // Single reflow
Using DocumentFragment for batch insertions is much more efficient than inserting nodes one at a time, as it triggers only one layout invalidation.

Shadow DOM

Encapsulated DOM subtrees (DOM/ShadowRoot.h):
class ShadowRoot : public DocumentFragment {
    enum class Mode {
        Open,    // Accessible via element.shadowRoot
        Closed   // Not accessible from outside
    };
    
    Mode mode() const;
    Element& host() const;
    
    // Shadow tree has its own styles
    Vector<CSSStyleSheet*> style_sheets();
};

// Create shadow root:
auto shadow = element.attach_shadow({ .mode = ShadowRoot::Mode::Open });
shadow.set_inner_html("<style>...</style><slot></slot>");

Abort signals

Cancel asynchronous operations (DOM/AbortSignal.h, DOM/AbortController.h):
class AbortController {
    AbortSignal& signal();
    void abort(JS::Value reason);
};

class AbortSignal : public EventTarget {
    bool aborted() const;
    JS::Value reason() const;
    
    // Events:
    // - "abort" when aborted
};

// Usage:
auto controller = AbortController::create();
fetch(url, { .signal = controller.signal() });
controller.abort();  // Cancel the fetch

Tree traversal

Efficient tree walking with TreeWalker and NodeIterator:
class TreeWalker {
    Node* current_node();
    Node* parent_node();
    Node* first_child();
    Node* last_child();
    Node* previous_sibling();
    Node* next_sibling();
    Node* previous_node();
    Node* next_node();
};

Accessibility tree

The accessibility tree (DOM/AccessibilityTreeNode.h) provides structure for assistive technologies:
class AccessibilityTreeNode {
    // ARIA roles and properties
    String role() const;
    String label() const;
    
    // Tree structure
    Vector<AccessibilityTreeNode*> children();
};

Update and invalidation

The document tracks what needs updating:

Layout invalidation

enum class InvalidateLayoutTreeReason {
    DocumentAddAnElementToTheTopLayer,
    ShadowRootSetInnerHTML,
    // ...
};

document.invalidate_layout_tree(reason);

Update reasons

enum class UpdateLayoutReason {
    ElementClientHeight,
    ElementScrollIntoView,
    HTMLElementOffsetHeight,
    // ... many reasons
};

document.update_layout(reason);
Frequent layout invalidation can cause performance issues. Batch DOM changes when possible and avoid reading layout properties (like offsetHeight) during animations.

Integration with other systems

With CSS

class Element {
    // Computed style from CSS
    CSS::ComputedValues const& computed_values();
    
    // Inline style
    CSS::CSSStyleDeclaration& style();
};

With Layout

class Node {
    // Layout representation
    Layout::Node* layout_node();
};

With JavaScript

class Node : public JS::Cell {
    GC_CELL(Node, JS::Cell);
    // All DOM nodes are GC-managed
};

LibWeb

Overall rendering engine

CSS

Styling the DOM tree

HTML

HTML-specific elements and behaviors

Build docs developers (and LLMs) love