#51602: perf: push sandboxed renderer startup data via mojo and cache preload bytecode
Description of Change
Replaces the BROWSER_SANDBOX_LOAD sync IPC with browser→renderer mojo pushes and adds a persistent V8 code cache for preload scripts. Removes the largest renderer-side FCP cost that isn't present in stock Chromium.
The sync IPC fired from DidCreateScriptContext — before the HTML parser starts — and the browser-side handler awaited reading each preload file, yielding the UI thread back to whatever else the main process was doing at startup. Under cold-launch contention the renderer parked for ~5–8× the UI-thread busy-chunk size waiting on the reply. With two ~150 KB preloads and 20 ms busy chunks, FCP went from ~244 ms to ~140 ms.
| UI busy chunk | sync IPC pull | mojo push |
|---|---|---|
| idle | ~24 ms | ~22 ms |
| 10 ms | ~84 ms | ~22 ms |
| 20 ms | ~134 ms | ~22 ms |
| 40 ms | ~230 ms | ~25 ms |
Three pushes replace it, one per place a preload can run:
- Frames —
mojom::ElectronFrameStartup.SetStartupData()fromReadyToCommitNavigation(), on the same associated channel asCommitNavigation, so it's ordered ahead of the document needing it. window.open()popups — the synchronousabout:blankdocument is created before any async push can land, so the data rides theCreateNewWindowReply(new Chromium patch +ContentBrowserClient/ContentRendererClienthooks). Verified against stock Electron 42 thatsetWindowOpenHandler-overridden preloads behave identically.- Service-worker preload realms —
mojom::ElectronWorkerStartupper-process push fromRenderProcessReady(), behind aWaitableEventfor the worker thread.
Then it caches the preload's compiled V8 bytecode (userData/Code Cache/electron-preload/<sha256(id)>.cache), produced after the first compile and shipped with the push thereafter. Compilation moved to ScriptCompiler::CompileFunction() with a ScriptOrigin, which keeps the contents and cache bytes in native buffers (no V8 string churn) and fixes preload stack traces to show the real file and line number instead of <anonymous> and off-by-one. V8's CachedData validation self-heals stale blobs across upgrades.
The cache is two-tiered (in-memory per launch + disk per userData), so the win lands on every navigation after the first and on every launch after the first:
| navigation | pre-parse blocking | vs no cache |
|---|---|---|
| baseline (no cache feature) | ~22.0 ms | — |
| first-ever (cold compile + produce + ship) | 27.2 ms | +5.2 ms once |
| same launch, navs 1+ (in-memory hit) | 17.6 ms | −4.4 ms |
| next launch, nav 0 (disk hit) | 18.0 ms | −4.0 ms |
| next launch, navs 1+ | 17.2 ms | −4.8 ms |
Behavior notes:
process.envis captured fresh per push (uv_os_environ()), same timing as the legacy spread.process.versions/arch/platform/execPathare now filled renderer-side and are read-only (the legacy structured clone left them writable; mutating them now no-ops).- The disk cache has the same threat model as Chromium's
userData/Code Cache/js. Could gate it onembeddedAsarIntegrityValidationif reviewers prefer. PreloadScriptData.contentsisarray<uint8>notBigBufferso it flat-serializes into the popup reply blob — one extra ~150 KB memcpy/nav.
Checklist
- I have built and tested this change
- I have filled out the PR description
- I have reviewed and verified the changes
-
npm testpasses - tests are changed or added
- PR release notes describe the change in a way relevant to app developers, and are capitalized, punctuated, and past tense.
Release Notes
Notes: Improved sandboxed renderer startup performance — preload scripts and process info are now pushed ahead of navigation instead of fetched via blocking IPC, and preload compilation results are cached on disk. Preload stack traces now show the correct file path and line number.
Backports
Semver Impact
Semantic Versioning helps users understand the impact of updates:
- Major (X.y.z): Breaking changes that may require code modifications
- Minor (x.Y.z): New features that maintain backward compatibility
- Patch (x.y.Z): Bug fixes that don't change the API
- None: Changes that don't affect using facing parts of Electron