Further parallelism comes with a synchronisation overhead. Every time work is sent off to another core, there is a performance overhead of sending the work to another core, waiting for the core to context switch to the thread, probable cache misses while it "warms up" to the new work, and then the same context switch and sending overhead to send the work back. As a result it's actually slower to send work to another core if it's a small amount of work - the overhead of arranging the off-core work will eclipse any benefit. For example if you have 100 instances running a "Set X to 0" action (which is very quick), trying to split that work over 4 cores running 25 instances each is likely far slower than just running it on the same thread. So not only is it difficult to parallelise the whole event sheet, it's difficult to parallelise individial events as well. For other engines, replace "events" with "logic", and it's similarly challenging for them to get useful performance gains on multi-core systems.
That's not to say there isn't a lot of parallelism going on - here's a list of things which modern browser engines run in parallel:
- audio processing
- network requests
- image/video decoding
- input (e.g. mouse/keyboard/gamepad input)
- draw calls (e.g. Chrome bundles up all WebGL calls and runs them on a separate thread)
- compositing (browser-level rendering of elements)
- the GPU itself is a large parallel processor running in parallel to the CPU
- pathfinding is CPU-intensive enough to run on a web worker on another core and benefit performance (this is actually a very nice feature since intense pathfinding does not impact the game framerate)
Browser developers are well aware of the need to split as much work as possible over different cores to achieve maximum performance, so work is continuing to add more parallel features. We're watching this carefully and will add support where practical.