Shadow Devlog #2

What's new in Shadow from last week (Oct 31-Nov 6)

Welcome to the second Shadow devlog! If you don’t know it, Shadow is my novel browser engine made almost entirely in JS. Shadow is 2 weeks old as of this post! 🎂

Shadow was (accidentally) in the news again! This time in JavaScript Weekly #661.


(left/top: now, right/bottom: last post)


It was completely busted before, but now is quite good! The header looks broken as we do not have flex support yet.

SunSpider Results

The results did not display before as it used unsupported DOM APIs, but we now implement them so it shows fine now. Also looks a bit better due to some layout fixes.


Before it was completely broken, but now it no longer crashes! It doesn’t look great but progress is progress :)


I did a lot of work on performance and debug tools to help investigate it. I mostly cared about load time performance but did a bit of work on rendering too.

New Debug Overlay

Shadow has a new debug overlay, showing frame time and delta time (accessed via Shift+Z).

Screenshot of new debug overlay

Page Load Profiler

Additionally, we now profile page loads to see what takes up the time.

Screenshot of page load profiler

~3x Faster Page Load

Compared to before, page loads are now ~3x faster! This is mostly thanks to rewriting script execution and removing dead text nodes.

~2x Faster Rendering

Rendering the page is also ~2x faster than before, thanks to adding offscreen culling (not rendering elements outside of the viewport).



Shadow is now beginning to implement pseudo-classes. We currently implement :link (+ :any-link) and :root.


Shadow now supports the var() CSS function, including a default value (var(foo, 2px)).

CSS Parser

The CSS parser now also supports psuedo-classes, and can now parse at-rules so no longer crashes when encountering them (although they are just ignored in layout for now). It also now bails instead of crashing the entire tab when it errors.

~8x Faster Dead Text Node Removal

Removing dead text nodes is a pass layout does after assembling the layout tree to simplify it for us later. It removes unneeded text nodes (empty, or pure whitespace which can be appended to others).

I rewrote it to be ~8x faster which noticably helps page load times with big pages.

Many Fixes

  • Fixed some CSS selector matching bugs
  • Fixed % values for position absolute
  • Rewrote computing element’s height


~5x Faster Worker Execution Loop

The worker execution loop (how the worker loops into getting JS to eval) is now async, so it no longer sits there constantly using CPU when not executing JS. This also makes execution ~5x faster due to not having to wait a loop cycle.

New APIs

appendChild, createElement, and createTextNode are now implemented, also some of performance, plus queueMicrotask.

HTML Parser

Attributes Without Value

The HTML parser can now parse attributes without a value, eg <div foo>.

Autofix Mismatching Closing Tags

The HTML parser now also tries to automatically fix mismatching closing tags. For example:


Gets transformed into:


Thanks for reading! Hope you found it interesting. Tune in next week (🤞) for this week’s progress, or watch my Twitter for “live” updates.