VS Code for the web
Generated from docs/compat/vscode-web.md when the site was built at 7aa23e2.
-
Build: vscode-web 1.145.2 (commit c4453f8ea430795f941c1b2ff6c9321135a74a02), the standalone web build (
web-standalonefrom update.code.visualstudio.com) served from a directory, with its own index.html. -
Run: VS Code is an app of the corpus (docs/compat/apps.md):
tests/apps/run.sh vscode-web [--engine quickjs|node|bun] [--screenshot](tests/vscode-web/run.shstill works and runs it), aftertests/apps/run.sh vscode-web --fetchdownloads the build, checks its sha256 and writes its index.html (tests/apps/vscode-web/fetch.shandmake-index.mjs: the builtin extensions). Each run serves it withtests/apps/vscode-web/driver.jsadded, asTerminal window niso-run --page niso-app.html --root . --url http://localhost/ --dump --max-ms 20000driver.jsopens an untitled JavaScript file through theworkbench.action.files.newUntitledFilecommand, types a function into it withtype, and checks the editor’s tokens (NISO-APP ok highlights the file).
Status
Section titled “Status”Rechecked on 2026-11-09 at intl 93473a9 (main 1d8ddc8 with niso-intl),
with nothing stubbed: the page is make-index.mjs’s index.html, whose
driver only wraps WebAssembly’s functions to log them. The same page
ran on all three engines, each with a storage directory of its own (a
reused one restores the last session’s editors):
niso-run --page index.html --root . --url http://localhost/ --dump --max-ms 20000 # QuickJSnode --experimental-vm-modules bindings/js/napi/niso-node.js --page index.html --root . \ --url http://localhost/ --dump --max-ms 20000 # V8bun bindings/js/napi/niso-node.js --page index.html ... # JavaScriptCoreOn QuickJS, Node and Bun alike the workbench comes up, the driver’s
newUntitledFile and type commands run without throwing, onig.wasm
instantiates, and the typed file’s tokens are the same on all three,
bracket pair colors included:
mtk23="function" mtk27="greet" mtk25="name" mtk22="// say hello" mtk17="return"mtk29="\"Hello, \"" mtk3="+" mtk7="42" (and bracket-highlighting-0 on ( ) { })
That is niso-iced running the page on QuickJS (--page index.html --root . --url http://localhost/ --max-ms 20000 --size 1280x800 --screenshot).
niso-iced only runs QuickJS, so for Node and Bun these are the DOM each run
ended with (--dump, scripts removed) rendered by niso-iced at
niso-run’s 800x600; canvases, such as the minimap, are blank in them:
| Node 26.10.0 (V8) | Bun 1.4.4 (JavaScriptCore) |
|---|---|
![]() |
![]() |
The only visible difference is that Node and Bun keep the Welcome tab open beside the untitled file.
What still breaks, on all three engines unless noted:
- blitz panics with “invalid SlotMap key used” at
layout/mod.rs:160, three times in each Node and Bun run and not in this QuickJS run (it varies from run to run, as before). - The language detection worker starts, but its model loader, the
webpack runtime of
@vscode/vscode-languagedetection’s Node build, loads a chunk withrequireand fails with “ReferenceError: require is not defined”. This was not compared with a browser. - ENOPRO for
file:///.copilot/config.jsonand the.copilot,.claudeand.agentsfolders: the workbench’s own rejections when no folder is open, which make niso-run and niso-node exit with status 1. - The extension host: nothing logs a failure, but the driver doesn’t check language features, so whether it runs is not known.
Fixed since the previous list: HTMLTextAreaElement.setSelectionRange,
Worker (no run falls back to the main thread with “Could not create web
worker(s)” any more, and the language detection worker runs),
globalThis.origin, CanvasRenderingContext2D’s drawing calls (the overview
ruler and minimap draw), and Intl.
Before Node and Bun (2026-11-08 to 2026-11-09, QuickJS only)
Section titled “Before Node and Bun (2026-11-08 to 2026-11-09, QuickJS only)”Syntax highlighting worked (2026-11-08, the WebAssembly branch). On the main
thread, vscode-oniguruma’s onig.wasm (466 KB) instantiates on wasmi through
WebAssembly.instantiate, and the TextMate grammar tokenizes the typed file:
mtk23="function" mtk27="greet" mtk25="name" mtk22="// say hello" mtk17="return"mtk29="\"Hello, \"" mtk3="+" mtk7="42"The theme’s CSS colors those classes (keyword #cf222e, function name #8250df,
comment #6e7781, string #0a3069, number #098658). The tokenization worker
cannot start (no Worker), so VS Code tokenizes on the main thread.
Next, in the order they matter for editing:
- Fixed: blitz panicked with “invalid SlotMap key used” in
layout/mod.rs:160whenoffsetWidth/offsetHeight/getBoundingClientRectran after the editor opened (about 200 times a run). The tab’s.tab-fillis a containing block that turnsdisplay: noneand then loses a::before; blitz keeps the freed pseudo-element in the hidden block’s hoisted list (repros/blitz-dom-hidden-containing-block-keeps-hoisted), and niso now drops freed boxes from those lists and lays out again. Not fully: rechecked on plat-workers after merging main (4071d11), the panic still happens in some runs, on main too (two runs each: main 0 and 2 panics, plat-workers 0 and 6), still atlayout/mod.rs:160from anoffsetHeightread. A debugniso-runalso needs a 64 MB main-thread stack (ulimit -s 65520) or wasmi overflows it while onig.wasm runs. HTMLTextAreaElement.setSelectionRangeis not a function, so the editor’s hidden textarea throws after each edit (the edit itself lands).- Fixed:
Intlwas not defined (QuickJS has none), which broke the highlighter in one contribution. With niso-intl (docs/engines/intl.md), rechecked on 2026-11-09, nothing in the run touches Intl and fails, and the typed file’s tokens carry bracket pair colors (bracket-highlighting-0on the parentheses and braces). - No Worker: the editor and tokenization workers fall back to the main thread, and the extension host does not start (so no language features).
- IndexedDB, as before.
globalThis.originis undefined, so the worker factory (tLr) throws “cannot read property ‘length’ of undefined” before it gets as far asnew Worker, for the tokenizer and the language detection workers.- CanvasRenderingContext2D has no
fillRect, so the editor’s overview ruler throws “not a function” each time it renders.
Before WebAssembly
Section titled “Before WebAssembly”Unmodified, the first screen renders: the activity bar, the Explorer with “No Folder Opened” and its Open Folder / Open Recent buttons, the Outline and Timeline panes, the Welcome editor (Start, Recent, Walkthroughs), the panel and the status bar.
Blob, AbortController and crypto.subtle no longer fail: the Blob rejection, the
crypto.subtle rejection (“webviews will not work”) and the two AbortController
errors are gone. On 2026-11-08, with the standalone build and an index.html
that only creates the workbench (its builtin extensions in the
vscode-workbench-builtin-extensions meta), the dumped DOM has 338 divs both
before and after these APIs, and the run still exits with status 1. Two
rejections remain unhandled: Worker is not defined (the web worker extension
host, the same blocker as the Svelte playground), and ENOPRO for
file:///.copilot/config.json, which the workbench rejects itself when no
folder is open. With main’s fetch, the theme JSON and the other resources it
fetches load from the served directory.
Blockers, in the order they were hit
Section titled “Blockers, in the order they were hit”| # | What failed | Cause | Fix |
|---|---|---|---|
| 1 | customElements.get("connection-observer") threw at startup |
No Custom Elements | Custom Elements in the core (api/custom_elements.rs): definitions, upgrades, the reaction queues run when each C API call returns, the HTMLElement constructor. The binding does the steps of define that read the constructor. |
| 2 | crypto.randomUUID undefined |
No Web Crypto | crypto.getRandomValues and randomUUID (idl/webcrypto.webidl); new QuotaExceededError and TypeMismatchError paths. |
| 3 | performance.getEntriesByType is not a function |
Performance was a stub in the glue | The Performance timeline and User Timing in the core (api/timing.rs, idl/performance.webidl). |
| 4 | CSS.escape undefined |
No CSS namespace | CSS.escape and CSS.supports (stylo’s parser), plus WebIDL namespaces in the generator. |
| 5 | canvas.getContext (pixel ratio probe) |
No HTMLCanvasElement | Main’s HTMLCanvasElement and 2D context replaced the width/height reflection this effort added. |
| 6 | this.element.dataset.activeComposite = … |
No dataset |
DOMStringMap backed by data-* attributes, through new generator support for anonymous named getters, setters and deleters and [LegacyOverrideBuiltIns]. |
| 7 | WebAssembly is not defined, so onig.wasm never started and nothing was highlighted |
No WebAssembly, and fetch’s Response kept bodies as strings | The WebAssembly JS API on wasmi (docs/platform/webassembly.md), and binary response bodies. |
| 8 | Blob is not defined, an unhandled rejection |
No File API | Blob, File, FileReader and blob URLs in the binding, with fetch’s Request and byte bodies. |
| 9 | AbortController is not defined (a workbench contribution and the chat control request) |
No aborting | AbortController and AbortSignal in the core (api/abort.rs), with fetch’s and addEventListener’s signal. |
| 10 | 'crypto.subtle' is not available so webviews will not work, an unhandled rejection |
No SubtleCrypto | crypto.subtle on AWS-LC (crates/niso-crypto), in secure contexts, which http://localhost is. |
Remaining errors before the editor opens (none blocks the first screen)
Section titled “Remaining errors before the editor opens (none blocks the first screen)”- Workers: on plat-workers the LocalWebWorker extension host did not start
because it hashes its iframe’s source with
crypto.subtlebefore it makes a worker, and Monaco fell back to running its editor workers on the main thread because it creates them fromblob:URLs and niso had no Blob. Both now exist after the merge of plat-workers into plat-blob-abort-crypto, and a window and its workers share one blob URL store, so a blob URL made in any of them resolves in all of them until any of them revokes it, or the worker that made it ends (closes or is terminated). Tested (crates/niso-quickjs/tests/messaging.rs): a classicnew Workerof a blob URL the window made,importScriptsandfetchof the window’s blob URLs inside that worker, a blob URL the worker made loading as an<img>src in the window, and a worker revoking a window’s blob URL so the window’sfetchof it fails, and terminated or closed workers’ blob URLs failing to load in the window. A module worker (type: 'module') from a blob URL, and a module script’s imports of blob URLs, are untested. The workbench has not been rerun since. - The driver’s typing into an untitled file stops at
HTMLTextAreaElement.setSelectionRange, which niso does not have; after it, anoffsetHeightread panics in blitz’s layout (“invalid SlotMap key used”). The task fails and the run goes on. - IndexedDB works now: VS Code opens its storage databases (SQLite files in the storage directory), and the seven IndexedDB errors it logged without it are gone.
Known limitations of the fixes
Section titled “Known limitations of the fixes”- Customized built-in elements (
extends) are rejected with NotSupportedError, andadoptedCallbacknever runs because niso has one document. - The wrapper of a custom element is held for as long as the binding lives, because a collected wrapper would lose its class and constructor state. Custom elements that pages discard are never freed.
- Blob has no
stream()and Blob-constructor reads of detached buffers do not throw: niso has no ReadableStream. - Whether a page is a secure context, and so whether
crypto.subtle,SubtleCrypto,CryptoKeyandcrypto.randomUUIDexist, follows the document’s URL until the page’s own first script runs, and is frozen from then on, because niso’s realm exists before the document’s URL is known. A host script that runs before the host sets the URL sees what about:blank gives, and the members change when the URL does. Onlylocalhostand loopback addresses count as trustworthy hosts; names under.localhostdo not, because niso’s network resolves them like any other name. - SubtleCrypto’s work runs in the task that settles its promise, on the event loop’s thread, so a long PBKDF2 holds the page meanwhile.

