fix(extensions): honor transient popup dismissal (#35360)

* fix(extensions): honor transient popup dismissal

* refactor(extensions): reuse allowPageUnload for the action popup

* test(extensions): cover action popup close despite a beforeunload veto

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-28 19:06:51 -07:00
committed by GitHub
co-authored by OrcaWin
parent c8b6d38bd4
commit cb49a37c5c
3 changed files with 22 additions and 0 deletions
@@ -4,6 +4,7 @@ import {
getChromeExtensionOrigin,
isSameExtensionNavigation
} from '../../../shared/browser-extension-url'
import { allowPageUnload } from '../browser-guest-unload-policy'
import { BrowserExtensionApiError } from './browser-extension-namespace-registry'
import {
BROWSER_EXTENSION_POPUP_MAX,
@@ -135,6 +136,7 @@ export function openBrowserExtensionPopup(options: BrowserExtensionPopupOptions)
}
current = popup
const contents = window.webContents
contents.on('will-prevent-unload', allowPageUnload)
// Why measure in the page: preferred-size-changed only reports while the window paints, and its
// height never drops below the viewport, so a hidden or late-rendering popup kept its first size.
@@ -15,6 +15,7 @@ import {
getBrowserExtensionToolbarState
} from './browser-extension-toolbar'
import {
closeBrowserExtensionPopup,
getOpenBrowserExtensionPopup,
openBrowserExtensionPopup
} from './browser-extension-popup-host'
@@ -237,6 +238,19 @@ async function run(): Promise<void> {
const { width, height } = quirks.getBounds()
return width === 170 ? { width, height } : null
})
// A page that vetoes unload after a user gesture still closes when the popup is dismissed.
const veto = openBrowserExtensionPopup({
session: extensionSession,
profileId: profile.id,
extensionId,
popupPath: 'veto.html',
anchor,
openTab: () => {}
})
await waitForReport('veto')
await veto.webContents.executeJavaScript('1', true)
closeBrowserExtensionPopup()
status.vetoPopupClosed = await waitFor(() => (veto.isDestroyed() ? true : null))
status.done = true
writeStatus()
}
@@ -119,6 +119,11 @@ beforeAll(async () => {
join(extensionDir, 'quirks.html'),
'<html><body><div style="width:154px;height:30px">Vue.js not detected</div></body></html>'
)
writeFileSync(join(extensionDir, 'veto.html'), '<!doctype html><script src="veto.js"></script>')
writeFileSync(
join(extensionDir, 'veto.js'),
"onbeforeunload = (event) => { event.preventDefault(); event.returnValue = '' }; console.log('ORCA_EXT_VETO true')"
)
writeFileSync(
join(extensionDir, 'late.js'),
"setTimeout(() => { const box = document.createElement('div'); box.style.cssText = 'width:300px;height:180px'; document.body.append(box) }, 300)"
@@ -236,6 +241,7 @@ describe('extension toolbar, action popup and tab activation in a real Electron
expect(status.openAfterSecondClick).toBeNull()
expect(status.latePopupBounds).toEqual({ width: 300, height: 180 })
expect(status.quirksPopupBounds).toEqual({ width: 170, height: 46 })
expect(status.vetoPopupClosed).toBe(true)
expect(status.windowsHidden).toBe(true)
expect(status.presentationCalls).toEqual([])