DOM Bindings
The DOM bindings are implementations of WebIDL interfaces in native Rust code.
A code generator is responsible for producing glue code that exposes these native Rust implementations to JavaScript via the SpiderMonkey API.
The WebIDL files themselves are located in components/script_bindings/webidls/.
These files contain the definition of each interface including their names, attributes, and methods.
The Rust implementations of these interfaces are contained in components/script/dom.
Each Rust implementation is a special Rust struct which holds the state of each DOM object.
Layout Wrappers
JavaScript runs in a single thread and the DOM interfaces are not thread-safe.
Layout needs to access the DOM, but is expected to run across different threads.
In theory, this can work as long as a thread does not try to read from or mutate a DOM object while another thread is mutating the same object.
We try to make this bit of unsafety easier to manage by limiting how layout can use DOM objects via a wrapper struct that exposes a limited, yet compatible, set of functionality.
There are two usage patterns:
- Layout itself assumes that only a single thread “owns” the DOM wrapper for each node and thus writing to the DOM should be safe. Children of the node may be processed in other threads concurrently. On the other hand, accessing the parent is highly unsafe, as another thread may be writing to and reading from the parent node.
styloandselectorsassume that nodes can be accessed from any thread, but only a single thread will write to a node at once. This means that accessing parents and children is safe, but writing to the node is highly unsafe.
In addition, layout depends on the script, so it is not possible to pass DOM instances directly from script to layout otherwise we would have a dependency cycle.
Instead layout-api exposes a trait-based interface and script implements it.
This allows the Layout interface to deal with nodes directly.
The four traits that we expose are:
LayoutNode: This is the basic interface for a DOM node that is used for layout.DangerousStyleNode: This is the interface which implementsstyloandselectorstraits for interacting with nodes. This can be created from aLayoutNodeby calling the unsafe methodLayoutNode::dangerous_style_node(). In general, these nodes should not be used by layout code, unless passing them directly to astyloorselectorscall.LayoutElement: This is the basic interface for a DOM element that is used for layout.DangerousStyleElement: This is the interface which implementsstyloandselectorstraits for interacting with elements. This can be created from aLayoutElementby calling the unsafe methodLayoutElement::dangerous_style_element(). In general, these elements should not be used by layout code, unless passing them directly to astyloorselectorscall.
script implements these traits with ServoLayoutNode, ServoDangerousStyleNode, ServoLayoutElement, and ServoDangerousLayoutElement.
In addition, script exposes two other structs which implement stylo traits: ServoDangerousStyleDocument and ServoDangerousStyleShadowRoot.
Rules for Layout Wrappers
- In order to keep things simple and build times faster, the trait definitions (
LayoutNodeandLayoutElement) should not contain any default methods. All implementation code should be inscript. - Layout should not use
DangerousStyleNodeandDangerousStyleElementunless calling intostyloorselectors. There are currently a few exceptions, but they will be eliminated gradually. - Layout should not rely on methods defined only on
ServoLayoutNodeandServoLayoutElement. Instead, new functionality should be added to theLayoutNodeorLayoutElementtraits and then implemented inServoLayoutNodeorServoLayoutElement. This will allow eliminatingTrustedNodeAddressin the future and passingLayoutNodes directly to layout, removing a source of unsafe code.