recordCanvas: true never captures canvas frames on pages that create their context before document_idle
Summary
recordCanvas: true is set in the recorder extension (api/extensions/recorder/src/inject.js), but no CanvasMutation events (rrweb IncrementalSource.CanvasMutation = 9) are ever captured for pages that create a canvas/WebGL context during their own initial render — e.g. any map widget (Leaflet, MapLibre GL, Mapbox GL) or chart library that draws to <canvas> on load.
Root cause
api/extensions/recorder/manifest.json registers inject.js as a content script with no run_at:
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["dist/inject.js"]
}
]With no run_at, Chrome defaults content scripts to document_idle — after the page's own synchronous script execution has typically already run. rrweb's canvas recording works by monkey-patching HTMLCanvasElement.prototype.getContext before the page calls it, so it can wrap the context and start sampling frames. If the page (e.g. a map library) has already called canvas.getContext('webgl') before document_idle, which is the common case for most SPAs, rrweb's patch is installed too late to ever see or wrap that context — so no canvas frames are ever captured, silently, despite recordCanvas: true being set.
Evidence
Recorded a real session against a page containing a MapLibre GL map (<canvas class="maplibregl-canvas">, confirmed present in the rrweb full snapshot). Pulled the full event stream back via GET /v1/sessions/:id/events (Cloud) — 530 IncrementalSnapshot events total, broken down by data.source:
{ '0': 293, '1': 25, '2': 175, '5': 35, '14': 2 }(0 = Mutation, 1 = MouseMove, 2 = MouseInteraction, 5 = Input, 14 = StyleSheetRule). Zero events with source: 9 (CanvasMutation) anywhere in the stream, for the full duration of the session (~4.5 min), despite the map remaining visible and interactive on screen throughout.
Suggested fix
Add "run_at": "document_start" to the content script registration in manifest.json, so inject.js (and therefore record({ recordCanvas: true, ... })) installs before the page's own scripts run and can intercept getContext calls made during initial render.
Environment
- Self-hosted
steel-browser(also reproduced against Steel Cloud sessions using the same recorder, if the same extension code is used there) - rrweb version pinned by this repo's
inject.js
Source: steel-dev/steel-browser