I have been watching the current discussions about running a virtual DOM in a web worker with a great deal of interest. In 2011, I built a research project, Treehouse (USENIX Talk (DOMTRIS demo at 20:25), paper), which ran a hacked-up version of jsdom in a worker. My goal was fine-grained containment of untrusted scripts, while still providing access to browser APIs that existing code expected.
Treehouse achieved a small amount of influence in academic circles, but it had problems and was ultimately unsuccessful. Virtual DOMs were not a widespread or well-understood idea at the time, so the advantages of running one in a worker were difficult to communicate.
I'm very excited that the idea has found new traction. I think it promises huge performance and security wins. With that said, it's quite a bit trickier to get right than it appears on the surface. In particular, building a worker VDOM system that will work for a large set of web content is a much harder engineering problem than building one that works for a single app.
Here are a few ways that a worker VDOM system may get into an inconsistent state or behave in ways that surprise the user.
An important challenge is that some browser APIs are synchronous, but the
postMessage
interface is asynchronous. Fortunately, the virtual DOM abstracts
away the largest synchronous API surface: the DOM. However, some tricky
synchronous bits remain.
Other than the DOM itself, synchronous APIs are fortunately relatively rare.
Those that are present can usually be virtualized. Others, like window.prompt
cannot.
These can probably be safely ignored.
There was a good twitter discussion about this. The crux is that event cancellation is synchronous in the parent page, but the application's event handlers should run in the worker, and will thus not have an opportunity to cancel the event.
The proposed solution is to cancel all events at the root of the virtual DOM. This will likely work, though I fear it will have tricky semantics for things like clicks on links.
Difficult and hidden state
There is a surprising amount of state in the browser that is hidden or difficult to monitor for changes. This state will need to be synchronized to the worker.
Changes to the value of form inputs do not trigger DOM attribute changes (though
they do fire input
events in modern browsers). So, how does the worker's
virtual DOM get updated with the latest input values? Dispatching only input
events the app listens for is insufficient, as the app may examine the value of
an input that it is not listening to.
I believe it will be necessary to stream input
events for every input element
that is in the worker's VDOM. It may be necessary to stream key events as well.
This is a tough one. How will the worker's VDOM know what to return if the app
ask for something like an element's style.color
. Similarly, what will it
return if the app asks for an element's scrollHeight
?
I believe this will require apps to use some new asynchronous interface instead of the usual DOM APIs.
This is perhaps the hardest challenge. When I designed Treehouse, I thought that I avoided the problem of handling concurrent access to VDOM nodes by requiring that a particular DOM node appear in at most one VDOM. Unfortunately, James Mickens pointed out in Pivot that I missed an additional writer: the user.
It's possible for the user to interact with a DOM that is out of sync with the VDOM in the worker. How the app would handle a UI event in this situation is unclear. I emailed with Brian Ford about this, in regards to Angular 2's plans to support worker apps. He argued that limiting the capabilities of the app mitigates this concern. In the more general case, something like Operational Transforms may work.
I will flesh out the outline below sometime tomorrow.
- High-thoughput events
- Mouse movement
- Scrolling
- Latency-sensitive events
- key events
- mouse events
- scrolling
- onunload
- CSS
- event capture and bubbling