Chrome extension
devui's Chrome extension puts devui on any page that draws with WebGL2 or WebGPU, with no change to the page's code: someone else's demo, a deployed site, or your own app before you've added devui. It runs before the page's own scripts, so every context and device the page makes has a frame scope and capture from its first call.
Installing it
Build it from a checkout of devui, then load it into Chrome:
npm run build:extension
- Open
chrome://extensionsand turn on Developer mode. - Load unpacked, and choose the
dist-extensionfolder. - Pin devui to the toolbar from the puzzle-piece menu.
After a rebuild, press the reload button on devui's card in chrome://extensions.
Or take it built: each push to main keeps the extension with its Release build run on GitHub (the run's devui-chrome-extension artifact, for 90 days). Unzip it and load that folder. Every build is the same extension to Chrome (one ID, wherever it's unpacked), so a newer one loaded over the old keeps the sites you switched on and what devui saved for them. (Don't remove the old one first: that deletes what it kept.) The popup says which build it is: its version, the commit and when.
Switching it on for a site
devui runs nowhere until you switch it on for a site. Click its toolbar button and turn the switch on. Chrome reloads the tab, so devui is there before the page's first script.
- localhost and 127.0.0.1 are allowed when it's installed, so your dev servers need no prompt.
- Any other site asks Chrome for access to that site alone when you switch it on.
- A site is its host: every port of
localhostat once,httpandhttps. - Its frames are covered too, where their own site is on (an embedded demo from another site needs that site switched on).
Switch it off and the tab reloads without devui.
What it shows
The panel is devui's own, in the page as an app's is. Each WebGL2 or WebGL1 context and WebGPU device the page draws with gets a frame scope (WebGL1's as the frame scope has it: no GPU time, and a capture without pictures), in the Frame folder: its frame scope, metrics, time plot and targets. A page with several shows one at a time: the select at the start of the scope's bar picks which, and the choice is kept for the site (its origin). capture in the scope, the popup's Capture frame, or the palette's commands take a frame capture.
- From the page's start. devui records each context and device as it's made: its textures and buffers are counted, and on WebGPU everything is made copyable, so a capture pictures a texture written before the first frame. A context made only to test for support never draws, and never shows.
- GPU time. On WebGL2 as in an app. On WebGPU, devui asks for the adapter's
timestamp-queryfeature with the page's device, where the page didn't ask for it itself. The page sees the device it asked for: itsdevice.featuresleaves out what devui added. A page that times its own passes keeps its timers, and the scope shows CPU time and says why. - three.js, even bundled: three announces each renderer on its devtools hook, and the scope names render targets, shadow maps and draws (
object · material) as three names them. The popup shows three's revision. - Other engines name what they tell WebGPU: Babylon's pipelines, Unity 6's draws by their shaders (
UI/Default) and its uniform buffers' members by name. On WebGL, which has no labels, lanes are named by their size and draws by number. Unity's and Godot's WebGL1 builds (Emscripten puts WebGL2's names on their contexts) are told as WebGL1. - Strict pages: a page's CSP doesn't stop devui. Its code is run by the extension, and its styles are adopted stylesheets, which a CSP on styles (
style-srcwithout'unsafe-inline') doesn't refuse. - Textures: each render target from the scope's targets, and each texture a captured draw read, in the texture inspector.
- Your own values: from the console,
__devui.gui.add(material)adds controls as code would, once devui has a scope on the page.
The popup
For the tab it's over: the site's switch, what devui found (each context and device that drew, three.js's revision), and Show panel (or Hide panel) and Capture frame. Alt+Shift+D shows or hides the panel; change it at chrome://extensions/shortcuts. The button's badge says on where devui runs.
The page's keys stay its own: the extension's panel has no H to toggle it or ` to compare, and / (search) and ? (shortcuts) work only while you're working in the panel.
What it keeps
What you'd change in the panel is kept per origin (https://example.com, a port its own: finer than the sites you switch on, each a host's every port and both schemes), once for all its pages, in the extension's own storage, and read back on the next load: the panel's place, open folders and windows, a hidden panel, the views' settings, the context picked. A demo in a frame keeps its own, apart from its site's pages. Two tabs of an origin each keep what they change apart (the context picked, say), but the panel's layout, its values and its presets are each kept whole: the last tab to change one keeps its own. The page's own localStorage is left as the page made it. An origin keeps at most about a megabyte, and only devui's own keys: a page can't fill the extension's storage.
AI agents
The agent tools are in the page, as in an app: await __devui.call('capture_frame'). An agent that drives a browser (Chrome DevTools MCP, Playwright MCP, Claude in Chrome) calls them through it. On a page with several contexts, capture_frame takes the one drawing. A page that draws on demand (a map, a model viewer at rest) draws nothing to capture until it's moved: capture_frame { when: 'work' }, then interact.
Limits
- A page with devui of its own keeps it. devui stands aside, and the popup says so.
- Drawing in a worker (an
OffscreenCanvashanded to one, as Google Maps draws its map) is out of reach: the extension's scripts run in the page. - The panel in a new window, and on another device, need devui in the app.
- three.js DevTools installed too: devui shares three's hook where three.js DevTools made it. Where devui's script runs first, three.js DevTools finds the hook there and stands aside.
- three.js on WebGPU names its draws by its pipelines' labels (
renderPipeline_MeshStandardNodeMaterial_12): the material, not the object, as WebGL's draws are named. - Demos in iframes (an engine's examples page) each get a panel of their own, in their frame, where their site is switched on. It starts minimised there, so it doesn't cover the demo (expanded, it's kept so). The popup lists every frame's contexts, and its buttons and Alt+Shift+D reach each.