#51900: fix: emit render-process-gone outside the process-death notification
Description of Change
Fixes a browser-process crash (CHECK failure in extensions::RendererStartupHelper::OnRenderProcessLaunched, surfacing as EXC_BREAKPOINT on macOS / EXCEPTION_BREAKPOINT on Windows on the CrBrowserMain thread) that occurs when an app reacts synchronously to the render-process-gone event, most commonly by calling webContents.reload() from the handler.
render-process-gone was emitted synchronously from WebContents::PrimaryMainFrameRenderProcessGone, which is invoked while RenderProcessHostImpl::ProcessDied() is still iterating the host's observer list. A reload (or any navigation) from the handler re-enters RenderProcessHostImpl::Init() from inside that loop; the resulting OnRenderProcessHostCreated() dispatch is ignored by RendererStartupHelper because the stale registration from the previous renderer is still present, the helper then untracks the process when its own RenderProcessExited() runs, and when the relaunched process finishes launching, OnRenderProcessLaunched() finds no registration and the CHECK brings down the browser process.
Per review feedback, this no longer touches the upstream CHECK (originally a patches/chromium change). Instead, the render-process-gone emit is deferred by one task, so the process-death notification fully unwinds before app code can react — covering reload(), loadURL(), window recreation, and any other synchronous reaction. Chromium relaxed the same CHECK for this race on Android (https://crrev.com/c/7330559) and ChromeOS (https://crrev.com/c/7862000, https://crbug.com/512916518); extending that to desktop platforms will be pursued upstream separately, since a content-internal path to the same re-entrancy exists that embedder-side deferral cannot reach.
Two intentional behavior notes for review:
- The event now fires one task later than before. The
exitCodeis captured at observer time, so the reported details are unchanged. - If the
WebContentsis destroyed within that one-task window, the event is not emitted (the JS wrapper is gone;destroyedfires as usual).
Verified locally on macOS (arm64) with the CHECK intact: a standalone repro (synchronous reload() inside render-process-gone + forcefullyCrashRenderer()) reliably crashes an unpatched build of main with FATAL:extensions/browser/renderer_startup_helper.cc:285] Check failed: GetRenderer(host) != nullptr || !client->IsSameContext(...), and survives with the deferred emit (reload completes normally). The added regression spec exercises the same sequence.
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: Fixed a browser process crash when calling webContents.reload() or navigating synchronously from the render-process-gone event; the event is now emitted after the renderer's teardown notification has completed.
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