Moving code to Web Workers
Multi-process is not always multi-threaded
Using Web Workers also avoids this problem and allows pages and frames to all run in parallel, with no risk of being held up by other pages or frames.
Using a library to fill the gaps
It's possible to use Proxies to automatically redirect accesses to another context. For example, Proxies in a Web Worker can create a 'document' object and redirect all calls to the real document on the main thread. Now you can use the full DOM APIs in a worker! Alternatively it can go the other way round: Proxies on the main thread can redirect calls to a Worker, more conveniently offloading heavy processing to a Worker without having to manage a message-passing system.
This is not just a theory: I wrote an experimental library called via.js that uses this approach to give workers full access to DOM APIs. It has real working examples and in many cases you can use identical code in both the main thread and worker. Google also developed Comlink that lets you create and use objects in a worker, more or less just by prefixing calls with the 'await' keyword.
Unfortunately, both libraries currently have a fatal flaw.
The inherent memory leak
The remote context has to remember the association. For example via.js sends messages like "call function on object ID 3", and has to find the real object with that ID. In the case of Comlink, it gives every object pair its own MessageChannel.
If we don't want to allow pages to observe GC, then there's only one other option: the JS engine itself must know that the Proxy references the real object, even though they're in different contexts. Then if all references to the Proxy are dropped, then the GC knows that the corresponding object in a different heap is also unreferenced, and can now be collected. Hence, cross-heap collection.
Unfortunately I'm told engines aren't currently architected for this and it would be very difficult to add. The lowest-hanging fruit is probably to fix Comlink's approach. This only requires one special case in the GC: if both ends of a MessageChannel are unreferenced, then allow the MessageChannel to be collected. This appears to be the absolute minimal feature necessary to properly collect both objects. The downside is creating a MessageChannel per object likely adds a significant perfomance overhead, which may discourage moving code to a worker. It would be great to add a low-overhead alternative to ensure worker code is still performant.
Finally we could bite the bullet and make GC observable, so we can send explicit "object was collected" messages. The WeakRef proposal would make this possible, but as mentioned many browser engineers are sceptical.
In the mean time, the only good solution is still to roll your own message posting system. In a lot of cases this is pretty inconvenient, so it's a shame we can't yet build a library to make this easier.
Libraries could make it far easier to move code to a worker, which would bring great performance benefits and matches the standard architecture in many existing UI frameworks. Unfortunately memory management for these libraries is currently an unsolved problem. There are a variety of possible solutions, but they have various trade-offs or involve a lot of work. So this could take a while to solve. Meanwhile we'll have to keep running code on the main thread, or managing message-posting systems.
I filed crbug.com/798855 to follow up on cross-heap collection with MessageChannel. As far as I know other browsers aren't working on this yet, so more bugs need to be filed!