demo · v138

paintsubtree event loop

The third new primitive of HTML-in-canvas is the paintsubtree event. It fires when the engine has re-laid-out the captured subtree — the signal you need to schedule a re-upload to the canvas / texture without polling. This is what makes the pipeline live.

Behind a flag: ships in Chrome 138 with chrome://flags/#enable-experimental-web-platform-features. Fallback: a polling loop at 30Hz mirrors the event so the demo is meaningful without the flag.
probing paintsubtree event…

Source DOM — a live dashboard

Production status

CPU 0%

Memory 0%

Network 0 req/s

last tick: never

2D canvas — re-uploaded on paintsubtree

paintsubtree fires 0×/s

Event log

The shape of the loop

const canvas = document.getElementById('canvas');
canvas.layoutSubtree = true;
const ctx = canvas.getContext('2d');

const src = document.getElementById('src');

canvas.addEventListener('paintsubtree', () => {
  // engine just re-laid-out the subtree
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawElement(src, 0, 0);   // re-rasterise into the canvas
});

// Updates to src — text content, css, style — implicitly schedule
// a paintsubtree event on the next frame. No polling needed.
setInterval(() => {
  document.getElementById('cpu').textContent = Math.random() * 100 | 0;
}, 500);

What's happening

  1. The dashboard's numbers, bars, and timestamp update every tick rate ms via setInterval.
  2. Each mutation invalidates the captured subtree. The engine waits until the next frame, lays it out, then fires paintsubtree on the canvas.
  3. The event handler re-rasterises (via ctx.drawElement) — exactly once per frame even if multiple mutations happened. No double-rendering, no missed updates.
  4. The event-rate counter shows the actual fire rate matches the mutation rate up to vsync (~60Hz).
  5. This is the "loop" that lets canvas-based apps composite live data — game HUDs, chart annotations, AR overlays — without the choice between "JS polyfill" and "static screenshot".

see also