From 0a874f9a9bbc80581c49ff79e70189169265f556 Mon Sep 17 00:00:00 2001 From: Nadja Yang Date: Tue, 31 Mar 2026 01:26:04 -0400 Subject: [PATCH] fix(firefox): wire screen context option through to Juggler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires the screen context option through Juggler to Firefox's native SetScreenAreaOverride API (https://phabricator.services.mozilla.com/D273425, https://github.com/mozilla-firefox/firefox/commit/a689946d0f7a6ca9b5b7aee00826cac863a6c63b) to decouple screen dimensions from Responsive Design Mode emulation. When screen differs from viewport, Juggler now uses setScreenAreaOverride instead of inRDMPane — achieving parity with how Chromium handles this via setDeviceMetricsOverride and WebKit via setScreenSizeOverride. The Firefox revision now pinned in UPSTREAM_CONFIG.sh (https://github.com/mozilla-firefox/firefox/commit/4eb5a4f7e5a3bea3de2e2bfc541e1bc122731518) includes this API natively, so bootstrap.diff changes are no longer required (cf. https://github.com/microsoft/playwright/pull/39843/commits/33679324bd51b4a04f4445c3d17f93083040beba). Fixes https://github.com/microsoft/playwright/issues/39841 --- .../firefox/juggler/TargetRegistry.js | 17 +++++++++++++++-- .../firefox/juggler/protocol/PageHandler.js | 4 ++-- .../firefox/juggler/protocol/Protocol.js | 2 ++ .../src/server/firefox/ffBrowser.ts | 6 +++++- .../src/server/firefox/ffPage.ts | 11 +++++++++-- tests/library/browsercontext-viewport.spec.ts | 3 +-- 6 files changed, 34 insertions(+), 9 deletions(-) diff --git a/browser_patches/firefox/juggler/TargetRegistry.js b/browser_patches/firefox/juggler/TargetRegistry.js index f28e675c0bee6..d41a0a726dd33 100644 --- a/browser_patches/firefox/juggler/TargetRegistry.js +++ b/browser_patches/firefox/juggler/TargetRegistry.js @@ -597,6 +597,7 @@ export class PageTarget { // Otherwise, explicitly set page viewport prevales over browser context // default viewport. const viewportSize = this._viewportSize || this._browserContext.defaultViewportSize; + const screenSize = this._screenSize || this._browserContext.defaultScreenSize; if (viewportSize) { const {width, height} = viewportSize; this._linkedBrowser.style.setProperty('width', width + 'px'); @@ -605,7 +606,15 @@ export class PageTarget { this._linkedBrowser.closest('.browserStack').style.setProperty('overflow', 'auto'); this._linkedBrowser.closest('.browserStack').style.setProperty('contain', 'size'); this._linkedBrowser.closest('.browserStack').style.setProperty('scrollbar-width', 'none'); - this._linkedBrowser.browsingContext.inRDMPane = true; + + const bc = this._linkedBrowser.browsingContext; + if (screenSize) { + bc.inRDMPane = false; + bc.setScreenAreaOverride(screenSize.width, screenSize.height); + } else { + bc.inRDMPane = true; + bc.resetScreenAreaOverride(); + } const stackRect = this._linkedBrowser.closest('.browserStack').getBoundingClientRect(); const toolbarTop = stackRect.y; @@ -620,6 +629,7 @@ export class PageTarget { this._linkedBrowser.closest('.browserStack').style.removeProperty('contain'); this._linkedBrowser.closest('.browserStack').style.removeProperty('scrollbar-width'); this._linkedBrowser.browsingContext.inRDMPane = false; + this._linkedBrowser.browsingContext.resetScreenAreaOverride(); const actualSize = this._linkedBrowser.getBoundingClientRect(); await this._channel.connect('').send('awaitViewportDimensions', { @@ -681,8 +691,9 @@ export class PageTarget { await this._channel.connect('').send('setInterceptFileChooserDialog', enabled).catch(e => {}); } - async setViewportSize(viewportSize) { + async setViewportSize(viewportSize, screenSize) { this._viewportSize = viewportSize; + this._screenSize = screenSize; await this.updateViewportSize(); } @@ -890,6 +901,7 @@ class BrowserContext { this.ignoreHTTPSErrors = undefined; this.downloadOptions = undefined; this.defaultViewportSize = undefined; + this.defaultScreenSize = undefined; this.deviceScaleFactor = undefined; this.defaultUserAgent = null; this.timezoneOverride = undefined; @@ -1029,6 +1041,7 @@ class BrowserContext { async setDefaultViewport(viewport) { this.defaultViewportSize = viewport ? viewport.viewportSize : undefined; + this.defaultScreenSize = viewport ? viewport.screenSize : undefined; this.deviceScaleFactor = viewport ? viewport.deviceScaleFactor : undefined; await Promise.all(Array.from(this.pages).map(page => page.updateViewportSize())); } diff --git a/browser_patches/firefox/juggler/protocol/PageHandler.js b/browser_patches/firefox/juggler/protocol/PageHandler.js index 267358890f0b6..09e1330e0de9b 100644 --- a/browser_patches/firefox/juggler/protocol/PageHandler.js +++ b/browser_patches/firefox/juggler/protocol/PageHandler.js @@ -227,8 +227,8 @@ export class PageHandler { }); } - async ['Page.setViewportSize']({viewportSize}) { - await this._pageTarget.setViewportSize(viewportSize === null ? undefined : viewportSize); + async ['Page.setViewportSize']({viewportSize, screenSize}) { + await this._pageTarget.setViewportSize(viewportSize === null ? undefined : viewportSize, screenSize === null ? undefined : screenSize); } async ['Page.setZoom']({zoom}) { diff --git a/browser_patches/firefox/juggler/protocol/Protocol.js b/browser_patches/firefox/juggler/protocol/Protocol.js index e1589b0cf74ca..17202e2139721 100644 --- a/browser_patches/firefox/juggler/protocol/Protocol.js +++ b/browser_patches/firefox/juggler/protocol/Protocol.js @@ -77,6 +77,7 @@ pageTypes.Size = { pageTypes.Viewport = { viewportSize: pageTypes.Size, deviceScaleFactor: t.Optional(t.Number), + screenSize: t.Optional(pageTypes.Size), }; pageTypes.DOMQuad = { @@ -753,6 +754,7 @@ const Page = { 'setViewportSize': { params: { viewportSize: t.Nullable(pageTypes.Size), + screenSize: t.Optional(t.Nullable(pageTypes.Size)), }, }, 'setZoom': { diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index c171c0713c064..38a4d65f1cc7f 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -362,9 +362,13 @@ export class FFBrowserContext extends BrowserContext { override async doUpdateDefaultViewport() { if (!this._options.viewport) return; + const screen = this._options.screen; + const defaultViewport = this._options.viewport; const viewport = { - viewportSize: { width: this._options.viewport.width, height: this._options.viewport.height }, + viewportSize: { width: defaultViewport.width, height: defaultViewport.height }, deviceScaleFactor: this._options.deviceScaleFactor || 1, + screenSize: screen && (screen.width !== defaultViewport.width || screen.height !== defaultViewport.height) + ? { width: screen.width, height: screen.height } : undefined, }; await this._browser.session.send('Browser.setDefaultViewport', { browserContextId: this._browserContextId, viewport }); } diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 5eb75453f6a7d..95334e1f452c5 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -331,8 +331,15 @@ export class FFPage implements PageDelegate { } async updateEmulatedViewportSize(): Promise { - const viewportSize = this._page.emulatedSize()?.viewport ?? null; - await this._session.send('Page.setViewportSize', { viewportSize }); + const emulatedSize = this._page.emulatedSize(); + const viewportSize = emulatedSize?.viewport ?? null; + const screenSize = emulatedSize?.screen ?? null; + const hasExplicitScreen = screenSize && viewportSize && + (screenSize.width !== viewportSize.width || screenSize.height !== viewportSize.height); + await this._session.send('Page.setViewportSize', { + viewportSize, + screenSize: hasExplicitScreen ? screenSize : undefined, + }); } async bringToFront(): Promise { diff --git a/tests/library/browsercontext-viewport.spec.ts b/tests/library/browsercontext-viewport.spec.ts index 4d70520968a94..27eceea61f5f9 100644 --- a/tests/library/browsercontext-viewport.spec.ts +++ b/tests/library/browsercontext-viewport.spec.ts @@ -128,8 +128,7 @@ browserTest('should support touch with null viewport', async ({ browser, server await context.close(); }); -it('should set both screen and viewport options', async ({ contextFactory, browserName, isBidi }) => { - it.fail(browserName === 'firefox' && !isBidi, 'Screen size is reset to viewport'); +it('should set both screen and viewport options', async ({ contextFactory }) => { const context = await contextFactory({ screen: { 'width': 1280, 'height': 720 }, viewport: { 'width': 1000, 'height': 600 },