mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 00:03:46 +00:00
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:
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user