DOM in JavaScript
Overview
In this proposal, we upload the four basic DOM pointers (firstChild, lastChild, nextSibling, previousSibling) into the JavaScript engine so that they can be accessed more quickly from JavaScript. We then provide C++ an optimized code path for reading and writing these properties.
JavaScript
The main change in this proposal is to move the four basic DOM pointers from C++ pointers to JavaScript properties. When accessing nextSibling, for example, from JavaScript, the JavaScript engine will simply read the nextSibling property as usual (and apply all its usual optimizations). Based on some experiments in Bug 97270, there is some reason to believe that this will make DOM traversal faster.
C++
After moving the DOM pointers into the JavaScript VM, we'll still need to provide a fast path for C++ code to access the pointers. Today, C++ simply reads a pointer from a fixed offset in the Node object. In this proposal, C++ code will need to take a slightly more indirect route:
Node* Node::nextSibling() { return m_wrapper->getPropertyAtKnownOffset(kNextSiblingOffset)->impl(); } void Note::setNextSibling(Node* node) { m_wrapper->setPropertyAtKnownOffset(kNextSiblingOffset, node->m_wrapper); }
In order to get or set the nextSibling property, the C++ code needs to consult its JavaScript wrapper. (Note: This implies that we'll need to eagerly create JavaScript wrappers for DOM nodes.) In this approach, the JavaScript wrapper stores the four DOM pointers at fixed offsets in memory, letting the C++ code read or write the property directly rather than having to do a hash table lookup.
My understanding is that getPropertyAtKnownOffset is similar to JSC::JSObject::getDirectOffset. The key is to create JavaScript wrappers for DOM nodes in such a way that initial offsets are know to correspond to particular properties (e.g., the four basic DOM pointers.)
The relationship between wrappers and DOM objects
Option 1: Wrappers are mandatory
This approach creates wrappers for all DOM objects.
Pros:
- GC is simplified. Since all DOM tree edges are represented in the JS side, GC does not need to transfer information between a JS engine and WebKit.
Cons:
- We need to create wrappers for DOM objects that are not touched by JS. This would not be a problem for JS-heavy workloads (because most of DOM objects will be anyway touched by JS) but might become a problem for JS-light workloads. For JS-light workloads, the mandatory-wrapper approach might end up increasing the number of wrappers in a JS heap and thus regress performance of GC.
Option 2: Wrappers are optional
This approach creates wrappers only for DOM objects that are touched by JS, as is done in the current JSC/V8 + WebKit. As a fast path, in a case where a wrapper exists, .nextSibling is realized by normal JavaScript property access. As a slow path, in a case where a wrapper does not exist, .nextSibling is realized by Node::nextSibling() as is done in the current WebKit.
Pros:
- For JS-light workloads, the optional-wrapper approach would be faster than the mandatory-wrapper approach. The optional-wrapper approach does not need to create wrappers just for representing tree edges.
Cons:
- GC is not simplified. Since some tree edges are represented in the JS side and other tree edges are represented in the WebKit side, GC still needs to transfer information between a JS engine and WebKit.
- We need to reserve space for .nextSibling etc on both WebKit and JavaScript.