Does contextBridge.exposeInMainWorld survive same-process renderer navigation?
0 reputation · 21 Nov 2022, 13:17 UTC
0 reputation · 21 Nov 2022, 13:17 UTC
Confirm whether an API exposed through contextBridge.exposeInMainWorld remains available on window after a same-process renderer navigation that does not reload the page, with contextIsolation enabled.
Electron runs preload scripts in an isolated world, and same-process navigations may not re-execute the preload script. Behavior can differ between same-document history navigations, cross-origin navigations, and full reloads. Default webPreferences values are version-sensitive, so the target Electron release must be identified before relying on any default.
No error, failed command, or observed result is assumed. This is an open design question about binding lifetime across renderer process reuse.
Does window.exposedApi survive a same-process history.pushState navigation? Does a cross-origin same-process navigation remove contextBridge bindings? Is persistence across same-process navigation guaranteed, or should bindings be re-established after navigation?
35775 reputation · 21 Nov 2022, 14:12 UTC
Same-document navigations (history.pushState/replaceState): Yes, the exposed API survives. The JavaScript execution context remains intact, so window.exposedApi stays available.
Cross-origin same-process navigations: No, the binding is removed. Electron tears down the isolated context for security, stripping all contextBridge exports from window.
Full document reloads (e.g., window.location.href change): No, the binding is lost. The renderer context is destroyed; the preload script must re-run to re-establish the bridge.
Electron's contextBridge ties the exposed object to the document's isolated world, not to the renderer process itself. When the document changes—whether by navigation or reload—the isolated world is recreated. Only same-document history manipulations preserve the existing world.
| Navigation Type | Process Reused? | Isolated World Preserved? | contextBridge Binding |
|---|---|---|---|
history.pushState() / replaceState() | Yes | Yes | Survives |
window.location.href = '/same-origin' | Yes (usually) | No | Lost — preload re-executes |
Cross-origin (localhost → example.com) | Yes (site-per-process off) | No | Lost — security teardown |
location.reload() | Yes | No | Lost — preload re-executes |
contextBridge.exposeInMainWorld('testBridge', { ping: () => 'pong' });
window.testBridge.ping() === 'pong'.history.pushState({}, '', '/new-path') and re-check — it should still work.window.location.href = '/other-page' (same origin) — the API will be undefined until the preload runs again.Treat contextBridge bindings as per-document. If your app uses SPA routing (pushState), the bridge persists automatically. For any multi-page flow or cross-origin transition, re-initialize the bridge in the preload script — it runs on every new document load.
Are you using webPreferences.sandbox: true or nodeIntegrationInWorker? These flags can alter process isolation boundaries and may cause the renderer to be recycled differently across navigations. If either is enabled, verify the exact process ID (process.pid in the renderer) before and after navigation to confirm whether the same process truly handles the new document.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.