fix(browser): restore configured zoom after page reload (#10800)

Reviewed with an independent reproduction. Rewrote the reload-zoom reassert to be per-pane instead of sharing the value zoom in/out writes, fixing Cmd/Ctrl+0 reset and cross-tab zoom leakage, with E2E coverage proven to fail on revert.
This commit is contained in:
Luiz Eduardo de Oliveira Fonseca
2026-07-27 18:42:51 -07:00
committed by GitHub
parent 872a9c3930
commit 60c7faf930
3 changed files with 284 additions and 12 deletions
@@ -2827,8 +2827,10 @@ function BrowserPagePane({
const setBrowserDefaultZoomLevel = useAppStore((state) => state.setBrowserDefaultZoomLevel)
const normalizedBrowserDefaultZoomLevel = normalizeBrowserPageZoomLevel(browserDefaultZoomLevel)
const browserDefaultZoomPercent = browserPageZoomLevelToPercent(normalizedBrowserDefaultZoomLevel)
const browserDefaultZoomLevelRef = useRef(normalizedBrowserDefaultZoomLevel)
browserDefaultZoomLevelRef.current = normalizedBrowserDefaultZoomLevel
// Why: the level THIS pane should hold. Seeded once from the configured default ("applied to newly
// opened browser tabs") and moved only by zooming this pane, so a reload can't broadcast another
// tab's zoom through the shared setting.
const paneZoomLevelRef = useRef(normalizedBrowserDefaultZoomLevel)
const grabElementShortcut = useShortcutLabel('browser.grabElement')
const faviconUrlRef = useRef<string | null>(browserTab.faviconUrl)
const initialBrowserUrlRef = useRef(browserTab.url)
@@ -3560,8 +3562,10 @@ function BrowserPagePane({
if (!isActiveRef.current) {
return
}
// Why: reset targets 100% like Chromium; the configured default is a new-tab seed, not a reset target.
const nextLevel = applyBrowserPageZoom(webviewRef.current, direction)
if (nextLevel !== null) {
paneZoomLevelRef.current = nextLevel
setBrowserDefaultZoomLevel(nextLevel)
showBrowserZoomFeedback(nextLevel)
}
@@ -3659,7 +3663,6 @@ function BrowserPagePane({
container = ensuredWebview.container
const webview = ensuredWebview.webview
const needsInitialNavigation = ensuredWebview.created
let needsInitialDefaultZoom = ensuredWebview.created
if (!ensuredWebview.created) {
// pointerEvents already applied inside ensureBrowserPageWebview for the reused-webview path.
@@ -3739,12 +3742,12 @@ function BrowserPagePane({
if (!queuedAnnotationViewportBridgeSync) {
syncBrowserAnnotationViewportBridge()
}
if (needsInitialDefaultZoom) {
const appliedLevel = setBrowserPageZoomLevel(webview, browserDefaultZoomLevelRef.current)
if (appliedLevel !== null) {
setBrowserZoomPercent(browserPageZoomLevelToPercent(appliedLevel))
}
needsInitialDefaultZoom = false
// Why: Chromium restores per-origin zoom on reload/navigation, so reassert THIS pane's level after
// every guest load. Uses the pane-local level, not the shared setting, so reloading one tab never
// adopts a zoom the user applied to a different tab.
const appliedLevel = setBrowserPageZoomLevel(webview, paneZoomLevelRef.current)
if (appliedLevel !== null) {
setBrowserZoomPercent(browserPageZoomLevelToPercent(appliedLevel))
}
// Why: CDP viewport overrides are scoped to the debugger session and don't survive cross-origin nav, so reapply (idempotently) on dom-ready.
const presetId = viewportPresetIdRef.current
@@ -106,6 +106,121 @@ describe('setBrowserPageZoomLevel', () => {
expect(setBrowserPageZoomLevel(webview, 1.26)).toBe(1.5)
expect(webview.setZoomLevel).toHaveBeenCalledWith(1.5)
})
it('restores the configured level when Chromium carries zoom across reloads', () => {
const webview = {
getZoomLevel: vi.fn(() => 0.5),
setZoomLevel: vi.fn()
}
expect(setBrowserPageZoomLevel(webview, 0)).toBe(0)
expect(setBrowserPageZoomLevel(webview, 0)).toBe(0)
expect(webview.setZoomLevel).toHaveBeenNthCalledWith(1, 0)
expect(webview.setZoomLevel).toHaveBeenNthCalledWith(2, 0)
})
})
/**
* Models BrowserPagePane's zoom wiring: each pane keeps its own level, dom-ready reasserts
* that level, and zooming also writes the shared `browserDefaultZoomLevel` setting.
*/
describe('browser pane zoom across reloads', () => {
function createPane(sharedSetting: { level: number }) {
// Chromium remembers zoom per origin and replays it on reload.
const originZoom = new Map<string, number>()
let url = 'https://a.example'
let live = 0
const webview = {
getZoomLevel: () => live,
setZoomLevel: (level: number) => {
live = level
originZoom.set(url, level)
}
}
let paneLevel = sharedSetting.level
webview.setZoomLevel(paneLevel)
return {
get level() {
return live
},
zoom(direction: 'in' | 'out' | 'reset') {
const next = applyBrowserPageZoom(webview, direction)
if (next !== null) {
paneLevel = next
sharedSetting.level = next
}
},
load(nextUrl = url) {
url = nextUrl
live = originZoom.get(url) ?? 0
setBrowserPageZoomLevel(webview, paneLevel)
}
}
}
it('reasserts the pane level after normal and hard reloads', () => {
const setting = { level: 0 }
const pane = createPane(setting)
// Chromium hands back a stale 150% for this origin on reload.
pane.load()
expect(pane.level).toBe(0)
pane.load()
expect(pane.level).toBe(0)
})
it('keeps an explicit non-default configured zoom across a reload', () => {
const setting = { level: 1.5 }
const pane = createPane(setting)
expect(pane.level).toBe(1.5)
pane.load()
expect(pane.level).toBe(1.5)
})
it('resets to 100% on reset even after zooming moved the shared setting', () => {
const setting = { level: 0 }
const pane = createPane(setting)
pane.zoom('in')
pane.zoom('in')
expect(pane.level).toBe(1)
expect(setting.level).toBe(1)
// Regression: resetting toward the shared setting would be a fixed point and never move.
pane.zoom('reset')
expect(pane.level).toBe(0)
})
it('does not adopt another tab zoom when reloading an untouched tab', () => {
const setting = { level: 0 }
const tabA = createPane(setting)
const tabB = createPane(setting)
tabB.zoom('in')
tabB.zoom('in')
expect(tabB.level).toBe(1)
expect(setting.level).toBe(1)
expect(tabA.level).toBe(0)
// Regression: reasserting the shared setting would silently zoom tab A to tab B's level.
tabA.load()
expect(tabA.level).toBe(0)
})
it('keeps a zoomed tab at its own level across reload and cross-origin navigation', () => {
const setting = { level: 0 }
const pane = createPane(setting)
pane.zoom('in')
expect(pane.level).toBe(0.5)
pane.load()
expect(pane.level).toBe(0.5)
pane.load('https://b.example')
expect(pane.level).toBe(0.5)
})
})
describe('getBrowserPageZoomIndicatorState', () => {