Shadow Devlog #1

What's new in Shadow since the intro post (Oct 27-30)

Welcome to the first Shadow devlog! If you aren’t aware, Shadow is my novel browser engine made almost entirely in JS. I’m aiming to do these once a week to cover the last week’s progress, although here I’ll cover since the prior intro post (Oct 27-30). Shadow is 1 week old as of this post! 🎉


  • Minor rebrand from <shadow> to Shadow, mostly just to make things simpler.
  • The Introducing Shadow post hit the top of Hacker News, which was pretty neat (and luckily mostly nice).


JS support got completely rewritten (twice) and is now usable in websites! We now run the JS backend (host/eval, or SpiderMonkey/Kiesel via Wasm) in a web worker via a mix of postMessage and SharedArrayBuffer/Atomics.

<script> and <body onload=...> are also newly supported.

Bonus thanks to Linus for their work on Kiesel (supported JS backend by Shadow) to help it get working. They wrote a post on the work, check it out!

Slaying the Kraken 🐙

Most notably, we can now run the older JS benchmark, Kraken, even navigating and rendering results as expected! (Don’t take the results seriously, it is just an old synthetic JS benchmark).

Here’s a sped-up GIF of it running:

GIF showing Kraken running in Shadow


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

SerenityOS: 2nd birthday page

With text wrapping, position: absolute, and text-align support - it looks very good now!

Kraken: Start page

It was completely broken before, now is essentially perfect!


Text Wrapping

This was the most noticable update since the post. Shadow now wraps text! It was actually somewhat easier than I expected, although I had my fair share of debugging random position jank. It should be mostly stable now though. It’s even responsive/dynamic!

GIF showing text wrapping on a page changing depending on the width of the page

CSS Selectors Rewrite

I rewrote both parsing and testing CSS selectors to now support combinators (eg #a .b, div > span).

Screenshot of demo page

<div id="a">a
  <div id="b">b
    <div id="c">c
      <div id="d">d</div>

  <div id="e">e</div>

  #a {
    color: purple;

  #a div {
    color: red;

  #a > div {
    color: blue;

  #a > div > div div {
    color: green;

External Stylesheets

Shadow now (tries to) load external stylesheets if specified with <link rel="stylesheet" href="...">. This is good, but also sometimes unfortunate as we try to load CSS we don’t support and crash the tab (todo: gracefully handle CSS parser errors).

margin: auto

I added support for margin: auto so elements are now centered in the parent as expected.

Screenshot of the Kraken start page showing it centered with an inspect overlay

Beginning of <iframe>

We also now have the beginning of an <iframe> implementation! We currently do not respect src (!) rather just treat it as empty and as a separate container.


Shadow now supports the color-scheme CSS property, and <meta> name. This was added mostly to avoid old/light pages to use dark default colors if Shadow was in dark mode. We now respect the color-scheme of the page, and the user’s preferred scheme, but only if supported.

Rewritten <img>

Shadow now properly respects the image’s Content-Type, previously many images were broken as it was always expecting PNGs. Also, if an image is broken it no longer crashes the renderer. Example with an SVG:

Screenshot of the Shadow welcome page showing an SVG image

position: absolute

I added basic support for position: absolute, it is quite limited for now but does its job for now ;)


With quite a hack, text-align is now implemented too (center and right), still a bit unstable.

Screenshot of a page showing text and an image centered in a div


Alongside text wrapping, Shadow now supports breaking lines with <br>.

Screenshot showing terminal-like text with broken lines


  • Default unit to px (previously errored)
  • Unescape more encodings (eg �A0)
  • Tweaked when Shadow displays the page
  • Improved collapsing vertical margin


HTML Parser Improvements

  • Now ignores HTML comments
  • Now excludes content of <style> and <script> elements
  • No longer errors when trying to insert <html> if no child elements are generated

Loading Non-HTML

I started working on loading non-HTML files, like CSS or JS source files by just generating a small HTML wrapper of the text:

Screenshot of Shadow rendering it&rsquo;s own user agent CSS file

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