CodeVetter · Repo Unpack · Behavior traces
electron/electron
Three flows define the system's shape. (1) Cold start: platform main → ElectronMainDelegate hooks → ElectronBrowserMainParts builds V8+Node, loads the app package, runs the bundled init which loads the app's main script, then Chromium's BrowserMain continues. (2) ipcRenderer.invoke: mojo message → ElectronApiIPCHandlerImpl → IpcMainEvent → JS ipcMain handler → reply through a ReplyChannel. (3) protocol.handle: a renderer request hits the session's protocol registry, calls the JS handler with a Request, and streams the Response back to the loader.
- Browser-process startup order: main() → content::ContentMain → ElectronMainDelegate::BasicStartupComplete (env vars ELECTRON_ENABLE_STACK_DUMPING/ELECTRON_DISABLE_SANDBOX, RegisterPathProvider, root-without-sandbox fatal check on Linux) → PreSandboxStartup (logging, crash keys, ResourceBundle for subprocesses, --allow-file-access-from-files) → PreBrowserMain (FieldTrialList, InitializeFeatureList, InitializeMojoCore).
- Startup continued: ElectronBrowserMainParts::PostEarlyInitialization sets up WASM trap handlers (fuse-gated), creates JavascriptEnvironment, node_bindings_->Initialize → CreateEnvironment → sets unhandled_rejections='warn-with-error-code' → ElectronBindings::BindTo → LoadAppPackage → node_bindings_->LoadEnvironment (runs the browser bundle = lib/browser/init.ts) → JoinAppCode → re-initializes the FeatureList so app-set command-line switches take effect.
- lib/browser/init.ts order: require common init (module aliases, uv-loop wrapping) → set EventEmitter prototype on the native binding → install uncaughtException→dialog.showErrorBox handler → map process.exit→app.exit → require ipc-dispatch, rpc-server, guest-view-manager → read 'appPackage' hidden value → app.setAppPath → pre-populate protocol/service-worker-main/web-contents/web-frame-main modules → default window-all-closed→app.quit handler → apply v8Flags → load main entry → appCodeLoaded.
- new BrowserWindow(): JS constructor reaches the native object from process._linkedBinding('electron_browser_window'), whose _init freeze-wraps id, merges setBounds args with getBounds, re-emits focus/blur on app as 'browser-window-focus/blur', debounces 'unresponsive' (50ms; 5s during close), tracks _browserViews, emits 'browser-window-created' on app, then loadURL etc. forward to webContents.
- Renderer script-context flow: Blink creates a script context → ElectronRendererClient::DidCreateScriptContext → ShouldLoadPreload gate → first-time NodeBindings::Initialize/SetUpIsolate (then restores Blink's WASM streaming callback) → per-frame FrameEnvironment + node::InitializeContext → document load deferred (kStrict) → CreateEnvironment registers UndeferLoad callback → BindTo + BindProcess → preload paths stored as hidden 'preloadPaths'.
- Preload execution order in lib/renderer/init.ts: patch Module._load to warn on 'vm' → override Module.wrapper to keep process/global/Buffer for preloads → common init → read webPreferences (nodeIntegration) → set up node globals only if nodeIntegration → load cjsPreloads synchronously (errors → BROWSER_PRELOAD_ERROR IPC) → esmPreloads via runEntryPointWithESMLoader → onPreloadsLoaded deletes node globals when !nodeIntegration && !contextIsolated → appCodeLoaded → UndeferLoad unfreezes the document.
- ipcRenderer.invoke('ch', ...args): the renderer binding sends ElectronApiIPC.Invoke over the associated mojo channel → ElectronApiIPCHandlerImpl::Invoke resolves api::Session from the frame's BrowserContext → MakeIPCEvent builds api::IpcMainEvent carrying the mojo InvokeCallback (ReplyChannel) → ipc_dispatch::Invoke looks up IpcMainImpl._invokeHandlers['ch'] → return value/promise serialized back through SerializedValue to the renderer promise.
- If the Session or WebContents wrapper is gone when an Invoke arrives, MakeIPCEvent still invokes the reply callback with an error via ReplyChannel::SendError so the renderer promise rejects instead of hanging.
- protocol.handle('app', handler) on a custom scheme: registerProtocol stores the callback → a request arrives as ProtocolRequest → convertToRequestBody rebuilds uploadData → a Request is constructed (initiatorOrigin attached as own prop, 'null' origin header stripped) → await handler(req) → validateResponse → if the Response is an untouched net.fetch result, relayUntouchedFetch holds the URL loader and streams prefix+remainder natively; otherwise res.body is wrapped with Readable.fromWeb → cb({data, headers, statusCode, statusText, mimeType}).
- utilityProcess.fork flow: JS validates options (execArgv array, serviceName string, stdio triple with stdin forced 'ignore') → native _fork launches a utility process → the returned handle emits 'stdout'/'stderr' events carrying fds that JS wraps in Socket→PassThrough pipes → 'exit' nulls the handle and tears down streams.
- ELECTRON_RUN_AS_NODE flow: lib/node/init.ts patches child_process.fork to inject ELECTRON_RUN_AS_NODE=1 (and helperExecPath on macOS); on Linux, spawn/spawnSync are wrapped to forward CRASHDUMP_SIGNAL_FD/CRASHPAD_HANDLER_PID env pairs to such children; shell/app/node_main.cc detects the env var and runs plain Node instead of Chromium.
- V8 snapshot integrity: when the embedded_asar_integrity_validation fuse is on, a validator registered via gin::SetV8SnapshotValidator CHECK-fails startup unless the snapshot's SHA-256 equals the compiled-in snapshot_checksum::kChecksum.