From 09bd3b96d44f5bfb2907171bb60b8b503c55b433 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Wed, 28 Oct 2026 13:00:39 -0700 Subject: [PATCH] Avoid host-frame registry scans and retained frame wrappers (#35275) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- .../host-frame-navigation-guard.test.ts | 24 ++++++++++++++++++- .../window/host-frame-navigation-guard.ts | 23 +++++------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main/window/host-frame-navigation-guard.test.ts b/src/main/window/host-frame-navigation-guard.test.ts index 771aed18679..cbc393ff0e7 100644 --- a/src/main/window/host-frame-navigation-guard.test.ts +++ b/src/main/window/host-frame-navigation-guard.test.ts @@ -28,7 +28,7 @@ describe('HostFrameNavigationRegistry', () => { expect(registry.shouldBlock(notebook, notebook, 'https://example.com')).toBe(false) }) - it('keeps pre-parse identity after name mutation and prunes destroyed frames', () => { + it('keeps pre-parse identity after name mutation and ignores destroyed frames', () => { const registry = new HostFrameNavigationRegistry() const plugin = frame({ id: 1, name: `${PLUGIN_PANEL_FRAME_NAME_PREFIX}demo` }) registry.register(plugin) @@ -55,4 +55,26 @@ describe('HostFrameNavigationRegistry', () => { const child = frame({ id: 4 }) expect(registry.shouldBlock(child, visual, 'https://example.com')).toBe(true) }) + + it('does not inspect unrelated guarded frames during registration or navigation', () => { + const registry = new HostFrameNavigationRegistry() + let livenessChecks = 0 + const panels = Array.from({ length: 100 }, (_, id) => ({ + ...frame({ id, name: `${PLUGIN_PANEL_FRAME_NAME_PREFIX}${id}` }), + isDestroyed: () => { + livenessChecks++ + return false + } + })) + for (const panel of panels) { + registry.register(panel) + } + const ordinary = frame({ id: 101 }) + registry.register(ordinary) + expect(registry.shouldBlock(ordinary, null, 'https://example.com')).toBe(false) + expect(registry.shouldBlock(panels[0], null, 'about:srcdoc')).toBe(false) + expect(registry.shouldBlock(panels[0], panels[0], 'https://example.com')).toBe(true) + expect(registry.shouldBlock(ordinary, panels[1], 'https://example.com')).toBe(true) + expect(livenessChecks).toBeLessThanOrEqual(4) + }) }) diff --git a/src/main/window/host-frame-navigation-guard.ts b/src/main/window/host-frame-navigation-guard.ts index b7259142f6e..b40a491eaf9 100644 --- a/src/main/window/host-frame-navigation-guard.ts +++ b/src/main/window/host-frame-navigation-guard.ts @@ -13,7 +13,6 @@ const HOST_FRAME_KINDS = [ export type HostFrameKind = (typeof HOST_FRAME_KINDS)[number]['kind'] type RegisteredFrame = { - frame: NavigationFrame kind: HostFrameKind initialSrcdocPending: boolean } @@ -25,13 +24,12 @@ export function hostFrameKindForName(name: string): HostFrameKind | null { /** Records host-marked frame identities at browsing-context creation, before their content can * mutate window.name. */ export class HostFrameNavigationRegistry { - private readonly frames = new Map() + private frames = new WeakMap() register(frame: NavigationFrame): void { - this.prune() const kind = hostFrameKindForName(frame.name) if (kind) { - this.frames.set(frame.frameTreeNodeId, { frame, kind, initialSrcdocPending: true }) + this.frames.set(frame, { kind, initialSrcdocPending: true }) } } @@ -40,9 +38,8 @@ export class HostFrameNavigationRegistry { initiator: NavigationFrame | null, destinationUrl: string ): boolean { - this.prune() - const registeredTarget = frame ? this.frames.get(frame.frameTreeNodeId) : undefined - if (registeredTarget) { + const registeredTarget = frame ? this.frames.get(frame) : undefined + if (registeredTarget && frame && !frame.isDestroyed()) { // Registration precedes srcdoc commit; allow its initial document only, then contain all navigation. if (registeredTarget.initialSrcdocPending && destinationUrl === 'about:srcdoc') { registeredTarget.initialSrcdocPending = false @@ -50,19 +47,11 @@ export class HostFrameNavigationRegistry { } return true } - return Boolean(initiator && this.frames.has(initiator.frameTreeNodeId)) + return Boolean(initiator && this.frames.has(initiator) && !initiator.isDestroyed()) } clear(): void { - this.frames.clear() - } - - private prune(): void { - for (const [id, registered] of this.frames) { - if (registered.frame.isDestroyed()) { - this.frames.delete(id) - } - } + this.frames = new WeakMap() } }