Skip to content
Tetsuharu OHZEKI edited this page Mar 21, 2014 · 11 revisions

What is this JS<T> type?

DOM objects have special lifetime requirements due to their interactions with the JavaScript engine, which requires us to use smart pointers to encapsulate them. That may sound complicated, but it's actually very easy in practice. JS<T> types should be passed by reference when possible, and cloned when necessary (this only clones the pointer container, not the underlying DOM object).

If I have a JS<Node>, how do I call a method/access a property on Node?

fn do_something(node: &JS<Node>) {
  node.get().parent_node().map(|parent| ...);
}

fn do_some_mutation(node: &mut JS<Node>) {
  node.get_mut().something = Some(5);
}

The get and get_mut methods will return a borrowed reference to the wrapped DOM object, allowing you to interact with it as you would expect.

How can I perform casts up and down a DOM hierarchy?

Let's say you have a JS<Element> type. Node is a parent type of Element, and HTMLTitleElement is a derived class of Element. Therefore, we can do the following:

let base: JS<Node> = NodeCast::from(element);
if base.get().type_id() == ElementNodeTypeId(HTMLTitleElementTypeId) {
  let derived: JS<HTMLTitleElement> = HTMLTitleElementCast::to(element);
}

Downcasting is an unsafe operation, so you must be confident that the cast will succeed or task failure will occur.

How can I make a JS<T> from a type T?

This is not possible, unfortunately, and is the reason for the abstract_self arguments in many methods. To obtain a JS type for a self value, you need to modify Bindings.conf and add the method to a needsAbstract list for the type (see the configuration for Node for examples), which will add an abstract_self argument to the method argument list - this corresponds to the self value wrapped in a JS<T> smart pointer. Any non-self values must be passed in by callers; in general, prefer passing &JS<T> instead of &T.

How do we provide object graph to SpiderMonkey’s Garbage Collection?

The current implementation is the following:

Create

When Servo creates a Rusty DOM object, the binding code creates a wrapper JSObject with SpiderMonkey, is correspond to each Rusty DOM Object. It’s produced and set to the Rusty object in FooBinding::Wrap.

In FooBinding::Wrap, the wrapper JSObject gets the pointer for Rusty Object to itself. And the same time, the wrapper JSObject are set to the Rusty Object’s Reflector field (All Rusty DOM objects have dom::bindings::utils::Reflector in their most basis field). These step are the “binding” work to create the relationship of both objects.

Trace object graph from SpiderMonkey GC.

This is very tricky and magically mechanism helped by Rust Compiler. The outline is:

  1. SpiderMonkey's GC calls JSClass.trace defined in FooBinding when marking phase. This JSClass is basis of each wrapper JSObject.
  2. JSClass.trace calls Foo::trace() defined in InhertTypes.rs.
  3. Foo::trace() calls Foo::encode(). This encode() method is derived by the annotation of #[deriving(Encodable)] for a Rust DOM Element struct.
  4. Foo::encode() calls JS<T>::encode() method of JS<T> which is contained to Foo’s member. So this is the compiler magic! Rust compiler generates codes like this for all structs annotated #[deriving(Encodable)]. This is based on the assumption.
  5. JS<T>::encode() calls dom::bindings::trace::trace_reflector().
  6. trace_reflector() fetches the reflector that is reachable from a Rust object, and notifies it to the GC with using JSTracer.
  7. This operation continues to the end of the graph.
  8. Finally, GC gets whether Rust object lives or not from JSObjects which is hold by Rust object.

Destruct

When destructing DOM objects (wrapper JSObjects) by SpiderMonkey, SpiderMonkey calls the JSClass.finalize() which is basis of each wrapper JSObjects. This method refers each FooBinding::_finalize().

In this function, the pointer of Rusty DOM Object that is contained in the wrapper JSObject is unwrapped, it cast to Rust owned pointer, and we assign its owned pointer to the empty local variable of FooBinding::_finalize(). Thus we can destruct the Rusty Object after we left from it.

Clone this wiki locally