#52847: fix: keep remote api::WebContents alive while their content::WebContents lives
Description of Change
Fixes #52158, the recurring SIGSEGV in WebContents::DevToolsOpened() on the macOS CI shards (same bug as #29890 / #34042 / #47187).
The api::WebContents for the DevTools content::WebContents is created up front in InspectableWebContents::ShowDevTools and the handle is dropped. It's a kRemote WebContents so nothing pins it, and nothing references its wrapper again until the frontend has loaded and DevToolsOpened() stashes it in devtools_web_contents_. If a GC lands in between, the wrapper is collected but the C++ object is still findable via From() until the deferred delete runs, and FromOrCreate() hands DevToolsOpened() an empty handle.
openDevTools() devtools frontend "loadCompleted"
| |
ShowDevTools: FromOrCreate(devtools wc) -> api::WebContents #2 LoadCompleted -> DevToolsOpened
| (handle dropped, wrapper only weakly held) | FromOrCreate(devtools wc)
| |
| GC: wrapper of #2 collected |
| FirstWeakCallback: dead_ = true, wrapper_.Reset() |
| (delete is posted, From() still finds #2) |
| | |
| +--- delete task runs before loadCompleted ------------>| From() finds nothing, creates #3
| | | (web-contents-created fires twice,
| | | devToolsWebContents.id != #2)
| | |
| +--- loadCompleted arrives before the delete ---------->| From() returns zombie #2
| GetWrapper() empty -> handle.get() == nullptr
| handle->owner_window() => SEGV_ACCERR
| (0x1e0 on main, 0x1d0 on 42-x-y = offsetof(owner_window_))
- Fix:
Pin()remote api::WebContents in the constructor andUnpin()inWebContentsDestroyed(), so a remote api::WebContents lives exactly as long as itscontent::WebContents. SamePinnablemechanism background pages already use, no newv8::Global. - Regression spec forces a major GC between
openDevTools()anddevtools-openedand asserts exactly one DevTools api::WebContents was created and it's the one ondevToolsWebContents. Fails on the pre-fix build (expected [ 2, 3 ] to deeply equal [ 3 ]), passes with the fix. - #52166 wasn't the fix for this, it protects the inspected api::WebContents. Here that one is alive and the DevTools one is the zombie; the crash reproduced on 42-x-y with #52511 present.
History of api::WebContents lifetime management (for the curious)
- Pre gin (mate::TrackableObject): weak wrapper + a per class weak map. Owned WebContents were kept alive by their window / webview, everything else was collectable whenever JS dropped it.
- #24651 (2020) ginified WebContents, added the
UserDataLinkWeakPtr soFrom()can find the api object from acontent::WebContents. Still nothing strong for remotes. - #23128 (2021-04) removed the
RenderProcessReadybackstop and started eagerly creating api::WebContents for DevTools / extension hosts / mime guests. This is where "created early, referenced late" starts, #29890 shows up two months later. - #30030 (2021-07,
ccb16e2921) pinned background page api::WebContents inInitWithExtensionViewfor exactly this reason ("live until the underlying content::WebContents is destroyed").kRemotereturns early right above that Pin, so it was left out. That's the only Pin WebContents ever had, and there was never anUnpin(). - #46389 (2025) blamed a dangling
owner_window_WeakPtr, closed. #47243 added theDCHECKs inDevToolsOpened(theDCHECK_EQ(handle->owner_window(), nullptr)is now the line we crash on in CI builds). #49406 / #51420 / #52166 all guard the inspected WebContents being freed, which is a different bug. - Why it's timing dependent: V8 only runs second pass phantom callbacks synchronously for forced GCs, for a natural GC they're a posted task, so the zombie window has been at least one task since 2021. #50688 (2026-04) added
DeleteSoonon top and widened it by another task, which lines up with #52158 showing up in June.
Checklist
- I have built and tested this change
- I have filled out the PR description
-
npm testpasses (webContents module,chrome extensions,session module,webFrameMain module,BrowserWindow module devtools,MenuItems: 558 ok / 0 failing locally) - tests are changed or added
Release Notes
Notes: Fixed a rare crash in the main process when DevTools were opened and a garbage collection ran before the DevTools frontend finished loading.
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