MarshallOfSound

#51703: perf: boot the browser process from an embedded Node startup snapshot

Merged
Created: May 19, 2026, 2:56:42 PM
Merged: May 27, 2026, 7:44:30 PM
6 comments
Target: main

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-JSready (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, CreateIsolateHolder feeds its blob + external references into the isolate CreateParams. The ctor skips node::NewContext; the main context is materialized from the snapshot inside node::CreateEnvironment.
  • electron_browser_main_parts.cc — passes an empty context to Initialize/CreateEnvironment in the snapshot path, then enters the deserialized context after.
  • node_bindings.ccCreateEnvironment defers context-dependent setup until the snapshot's main context exists; CreateIsolateData receives the snapshot's per-isolate data; LoadEnvironment merges the build-time electron/js2c/* cache with the snapshot's per-builtin cache.
Testing
  • Release build boots; sandboxed BrowserWindow smoke 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.ts from #51697 passes: the browser process's browser_init/node_init build-time code caches are consumed (verified via NODE_DEBUG_NATIVE=CODE_CACHE — both is 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 test passes

Notes: Improved app startup time by booting the main process from an embedded Node.js startup snapshot.

Backports

42-x-y
Merged
PR Number
#51831
Merged At
Jun 3, 2026, 1:22:49 AM
Released In
v42.3.3
Release Date
Jun 3, 2026, 2:18:37 PM
43-x-y
Merged
PR Number
#51792
Merged At
Jun 1, 2026, 11:28:12 AM
Released In
v43.0.0-beta.1
Release Date
Jun 4, 2026, 3:31:55 PM

Semver Impact

Major
Breaking changes
Minor
New features
Patch
Bug fixes
None
Docs, tests, etc.

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