Expand description
The implementation of the DOM.
The DOM is comprised of interfaces (defined by specifications using WebIDL) that are implemented as Rust structs in submodules of this module. Its implementation is documented below.
§A DOM object and its reflector
The implementation of an interface Foo
in Servo’s DOM involves two
related but distinct objects:
- the DOM object: an instance of the Rust struct
dom::foo::Foo
(marked with the#[dom_struct]
attribute) on the Rust heap; - the reflector: a
JSObject
allocated by SpiderMonkey, that owns the DOM object.
§Memory management
Reflectors of DOM objects, and thus the DOM objects themselves, are managed by the SpiderMonkey Garbage Collector. Thus, keeping alive a DOM object is done through its reflector.
For more information, see:
- rooting pointers on the stack:
the
Root
smart pointer; - tracing pointers in member fields: the
Dom
,MutNullableDom
andMutDom
smart pointers and the tracing implementation; - rooting pointers from across thread boundaries or in channels: the
Trusted
smart pointer;
§Inheritance
Rust does not support struct inheritance, as would be used for the
object-oriented DOM APIs. To work around this issue, Servo stores an
instance of the superclass in the first field of its subclasses. (Note that
it is stored by value, rather than in a smart pointer such as Dom<T>
.)
This implies that a pointer to an object can safely be cast to a pointer to all its classes.
This invariant is enforced by the lint in
plugins::lints::inheritance_integrity
.
Interfaces which either derive from or are derived by other interfaces
implement the Castable
trait, which provides three methods is::<T>()
,
downcast::<T>()
and upcast::<T>()
to cast across the type hierarchy
and check whether a given instance is of a given type.
use dom::bindings::inheritance::Castable;
use dom::element::Element;
use dom::htmlelement::HTMLElement;
use dom::htmlinputelement::HTMLInputElement;
if let Some(elem) = node.downcast::<Element> {
if elem.is::<HTMLInputElement>() {
return elem.upcast::<HTMLElement>();
}
}
Furthermore, when discriminating a given instance against multiple
interface types, code generation provides a convenient TypeId enum
which can be used to write match
expressions instead of multiple
calls to Castable::is::<T>
. The type_id()
method of an instance is
provided by the farthest interface it derives from, e.g. EventTarget
for HTMLMediaElement
. For convenience, that method is also provided
on the Node
interface to avoid unnecessary upcasts to EventTarget
.
use dom::bindings::inheritance::{EventTargetTypeId, NodeTypeId};
match *node.type_id() {
EventTargetTypeId::Node(NodeTypeId::CharacterData(_)) => ...,
EventTargetTypeId::Node(NodeTypeId::Element(_)) => ...,
...,
}
§Construction
DOM objects of type T
in Servo have two constructors:
- a
T::new_inherited
static method that returns a plainT
, and - a
T::new
static method that returnsDomRoot<T>
.
(The result of either method can be wrapped in Result
, if that is
appropriate for the type in question.)
The latter calls the former, boxes the result, and creates a reflector
corresponding to it by calling dom::bindings::utils::reflect_dom_object
(which yields ownership of the object to the SpiderMonkey Garbage Collector).
This is the API to use when creating a DOM object.
The former should only be called by the latter, and by subclasses’
new_inherited
methods.
DOM object constructors in JavaScript correspond to a T::Constructor
static method. This method is always fallible.
§Destruction
When the SpiderMonkey Garbage Collector discovers that the reflector of a DOM object is garbage, it calls the reflector’s finalization hook. This function deletes the reflector’s DOM object, calling its destructor in the process.
§Mutability and aliasing
Reflectors are JavaScript objects, and as such can be freely aliased. As
Rust does not allow mutable aliasing, mutable borrows of DOM objects are
not allowed. In particular, any mutable fields use Cell
or DomRefCell
to manage their mutability.
§Reflector
and DomObject
Every DOM object has a Reflector
as its first (transitive) member field.
This contains a *mut JSObject
that points to its reflector.
The FooBinding::Wrap
function creates the reflector, stores a pointer to
the DOM object in the reflector, and initializes the pointer to the reflector
in the Reflector
field.
The DomObject
trait provides a reflector()
method that returns the
DOM object’s Reflector
. It is implemented automatically for DOM structs
through the #[dom_struct]
attribute.
§Implementing methods for a DOM object
dom::bindings::codegen::Bindings::FooBindings::FooMethods
for methods defined through IDL;&self
public methods for public helpers;&self
methods for private helpers.
§Accessing fields of a DOM object
All fields of DOM objects are private; accessing them from outside their module is done through explicit getter or setter methods.
§Inheritance and casting
All DOM interfaces part of an inheritance chain (i.e. interfaces
that derive others or are derived from) implement the trait Castable
which provides both downcast and upcasts.
fn f(element: &Element) {
let base = element.upcast::<Node>();
let derived = element.downcast::<HTMLElement>().unwrap();
}
§Adding a new DOM interface
Adding a new interface Foo
requires at least the following:
- adding the new IDL file at
components/script/dom/webidls/Foo.webidl
; - creating
components/script/dom/foo.rs
; - listing
foo.rs
incomponents/script/dom/mod.rs
; - defining the DOM struct
Foo
with a#[dom_struct]
attribute, a superclass orReflector
member, and other members as appropriate; - implementing the
dom::bindings::codegen::Bindings::FooBindings::FooMethods
trait forFoo
; - adding/updating the match arm in create_element in
components/script/dom/create.rs
(only applicable to new types inheriting fromHTMLElement
)
More information is available in the bindings module.
§Accessing DOM objects from layout
Layout code can access the DOM through the
LayoutDom
smart pointer. This does not
keep the DOM object alive; we ensure that no DOM code (Garbage Collection
in particular) runs while layout is accessing the DOM.
Methods accessible to layout are implemented on LayoutDom<Foo>
using
LayoutFooHelpers
traits.
Re-exports§
Modules§
- The code to expose the DOM to JavaScript through IDL bindings.
- DOM bindings for
CharacterData
. - create 🔒
- Element nodes.
- The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements.
- Native representation of JS Promise values.
- This is an abstraction used by
HTMLInputElement
andHTMLTextAreaElement
to implement the text control selection DOM API. - webxr 🔒
- An implementation of Houdini worklets.