#51703: perf: boot the browser process from an embedded Node startup snapshot
Description of Change
Builds a Node.js startup snapshot for the browser (main) process at compile time and creates the browser-process isolate from it, so the Node bootstrap is deserialized instead of re-parsed and re-compiled from source on every app start. Only the browser process is touched — renderer / sandboxed renderer / utility / worker are unchanged.
In a Release build this cuts time-to-first-main-process-JS by ~40% (123 → 74 ms) and total spawn → app 'ready' by ~25% (198 → 149 ms), at a cost of +4.6 MB binary size.
Stacks on #51697: the browser process consumes both the build-time electron/js2c code cache from that PR and this Node snapshot (LoadEnvironment merges the two).
Benchmark detail
Release build (is_official_build=true, dcheck_always_on=false), x64 Linux, 24 cold samples/build, measured from process spawn:
| metric | before | after | Δ |
|---|---|---|---|
| spawn → first line of main-process JS | 123.2 ms | 73.8 ms | −49.4 ms (−40%) |
Node bootstrapComplete |
81.7 ms | 40.9 ms | −40.9 ms (−50%) |
spawn → app 'ready' (total) |
197.8 ms | 148.7 ms | −49.1 ms (−25%) |
first-JS → ready (Chromium browser-init) |
75.0 ms | 75.1 ms | unchanged |
The 4.4 MB snapshot adds ~4.6 ms of deserialize on the pre-ready path (already netted in). The first-JS → ready segment is Chromium browser-init, which this doesn't touch, so it's flat — the win is the Node/Electron bootstrap.
How it works
The snapshot extends the same V8 startup snapshot the rest of the process already uses (snapshot_blob.bin), so the read-only heap stays shared and V8's one-snapshot-per-process invariant holds.
node_snapshot (a separate source_set in third_party/electron_node:unofficial.gni) provides the strong SnapshotBuilder::GetEmbeddedSnapshotData over a now-weak no-op stub. electron_lib deps it, so the shipped framework links the real snapshot while node_mksnapshot (which links only libnode + the weak stub) does not — avoiding the GN dependency cycle that v8_snapshot_toolchain == default_toolchain would otherwise force on native builds. Snapshot generation is gated to native builds.
Browser-process wiring:
javascript_environment.cc— when the embedded snapshot is present,CreateIsolateHolderfeeds its blob + external references into the isolateCreateParams. The ctor skipsnode::NewContext; the main context is materialized from the snapshot insidenode::CreateEnvironment.electron_browser_main_parts.cc— passes an empty context toInitialize/CreateEnvironmentin the snapshot path, then enters the deserialized context after.node_bindings.cc—CreateEnvironmentdefers context-dependent setup until the snapshot's main context exists;CreateIsolateDatareceives the snapshot's per-isolate data;LoadEnvironmentmerges the build-timeelectron/js2c/*cache with the snapshot's per-builtin cache.
Testing
- Release build boots; sandboxed
BrowserWindowsmoke test (loads a data URL, runs JS, returns the Electron UA) passes — child processes are unaffected by the browser-only snapshot. spec/api-js2c-code-cache-spec.tsfrom #51697 passes: the browser process'sbrowser_init/node_initbuild-time code caches are consumed (verified viaNODE_DEBUG_NATIVE=CODE_CACHE— bothis accepted). This required generating the browser cache against the snapshot's isolate (see "Browser js2c code cache" below); the other flavors are byte-identical to #51697.
Browser js2c code cache
A SnapshotCreator-produced snapshot owns a unique read-only-heap checksum that matches no standalone blob (not the v8_context_snapshot, not the snapshot_blob.bin it extends). V8's code-cache key embeds that checksum, so the build-time js2c cache for browser_init/node_init is only valid if it's generated against the shipped snapshot's isolate. A dedicated electron_natives_codecache_snapshot host tool links the real node_snapshot and compiles those bundles via GetEmbeddedSnapshotData() + InitializeIsolateParams(), so the cache's read-only checksum matches the runtime browser isolate by construction. The four non-browser flavors keep using the v8 context snapshot.
Checklist
- PR description included and stakeholders cc'd
-
npm testpasses
Notes: Improved app startup time by booting the main process from an embedded Node.js startup snapshot.
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