Skip to content

Commit aac5bf2

Browse files
authored
fix(popups): do not override popup size from window features (#2139)
We usually force window size from the browser context. However, popups that have window features insist on a specific window size, so we respect that.
1 parent e2972ad commit aac5bf2

File tree

5 files changed

+72
-1
lines changed

5 files changed

+72
-1
lines changed

src/chromium/crPage.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ export class CRPage implements PageDelegate {
5656
private readonly _pagePromise: Promise<Page | Error>;
5757
_initializedPage: Page | null = null;
5858

59+
// Holds window features for the next popup being opened via window.open,
60+
// until the popup target arrives. This could be racy if two oopifs
61+
// simultaneously call window.open with window features: the order
62+
// of their Page.windowOpen events is not guaranteed to match the order
63+
// of new popup targets.
64+
readonly _nextWindowOpenPopupFeatures: string[][] = [];
65+
5966
constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, hasUIWindow: boolean) {
6067
this._targetId = targetId;
6168
this._opener = opener;
@@ -68,6 +75,12 @@ export class CRPage implements PageDelegate {
6875
this._mainFrameSession = new FrameSession(this, client, targetId, null);
6976
this._sessions.set(targetId, this._mainFrameSession);
7077
client.once(CRSessionEvents.Disconnected, () => this._page._didDisconnect());
78+
if (opener && browserContext._options.viewport !== null) {
79+
const features = opener._nextWindowOpenPopupFeatures.shift() || [];
80+
const viewportSize = helper.getViewportSizeFromWindowFeatures(features);
81+
if (viewportSize)
82+
this._page._state.viewportSize = viewportSize;
83+
}
7184
this._pagePromise = this._mainFrameSession._initialize(hasUIWindow).then(() => this._initializedPage = this._page).catch(e => e);
7285
}
7386

@@ -372,6 +385,7 @@ class FrameSession {
372385
helper.addEventListener(this._client, 'Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()),
373386
helper.addEventListener(this._client, 'Target.attachedToTarget', event => this._onAttachedToTarget(event)),
374387
helper.addEventListener(this._client, 'Target.detachedFromTarget', event => this._onDetachedFromTarget(event)),
388+
helper.addEventListener(this._client, 'Page.windowOpen', event => this._onWindowOpen(event)),
375389
];
376390
}
377391

@@ -591,6 +605,10 @@ class FrameSession {
591605
this._page._removeWorker(event.sessionId);
592606
}
593607

608+
_onWindowOpen(event: Protocol.Page.windowOpenPayload) {
609+
this._crPage._nextWindowOpenPopupFeatures.push(event.windowFeatures);
610+
}
611+
594612
async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) {
595613
if (event.executionContextId === 0) {
596614
// DevTools protocol stores the last 1000 console messages. These

src/helper.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,16 @@ class Helper {
346346
}
347347
}
348348
}
349+
350+
static getViewportSizeFromWindowFeatures(features: string[]): types.Size | null {
351+
const widthString = features.find(f => f.startsWith('width='));
352+
const heightString = features.find(f => f.startsWith('height='));
353+
const width = widthString ? parseInt(widthString.substring(6), 10) : NaN;
354+
const height = heightString ? parseInt(heightString.substring(7), 10) : NaN;
355+
if (!Number.isNaN(width) && !Number.isNaN(height))
356+
return { width, height };
357+
return null;
358+
}
349359
}
350360

351361
export function assert(value: any, message?: string): asserts value {

src/webkit/wkBrowser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export class WKBrowser extends BrowserBase {
5757
helper.addEventListener(this._browserSession, 'Playwright.pageProxyCreated', this._onPageProxyCreated.bind(this)),
5858
helper.addEventListener(this._browserSession, 'Playwright.pageProxyDestroyed', this._onPageProxyDestroyed.bind(this)),
5959
helper.addEventListener(this._browserSession, 'Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)),
60+
helper.addEventListener(this._browserSession, 'Playwright.windowOpen', event => this._onWindowOpen(event)),
6061
helper.addEventListener(this._browserSession, 'Playwright.downloadCreated', this._onDownloadCreated.bind(this)),
6162
helper.addEventListener(this._browserSession, 'Playwright.downloadFilenameSuggested', this._onDownloadFilenameSuggested.bind(this)),
6263
helper.addEventListener(this._browserSession, 'Playwright.downloadFinished', this._onDownloadFinished.bind(this)),
@@ -180,6 +181,13 @@ export class WKBrowser extends BrowserBase {
180181
wkPage.handleProvisionalLoadFailed(event);
181182
}
182183

184+
_onWindowOpen(event: Protocol.Playwright.windowOpenPayload) {
185+
const wkPage = this._wkPages.get(event.pageProxyId);
186+
if (!wkPage)
187+
return;
188+
wkPage.handleWindowOpen(event);
189+
}
190+
183191
isConnected(): boolean {
184192
return !this._connection.isClosed();
185193
}

src/webkit/wkPage.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
import * as frames from '../frames';
19-
import { helper, RegisteredListener, assert } from '../helper';
19+
import { helper, RegisteredListener, assert, debugAssert } from '../helper';
2020
import * as dom from '../dom';
2121
import * as network from '../network';
2222
import { WKSession } from './wkConnection';
@@ -68,6 +68,10 @@ export class WKPage implements PageDelegate {
6868
_firstNonInitialNavigationCommittedReject = (e: Error) => {};
6969
private _lastConsoleMessage: { derivedType: string, text: string, handles: JSHandle[]; count: number, location: ConsoleMessageLocation; } | null = null;
7070

71+
// Holds window features for the next popup being opened via window.open,
72+
// until the popup page proxy arrives.
73+
private _nextWindowOpenPopupFeatures?: string[];
74+
7175
constructor(browserContext: WKBrowserContext, pageProxySession: WKSession, opener: WKPage | null) {
7276
this._pageProxySession = pageProxySession;
7377
this._opener = opener;
@@ -90,6 +94,12 @@ export class WKPage implements PageDelegate {
9094
this._firstNonInitialNavigationCommittedFulfill = f;
9195
this._firstNonInitialNavigationCommittedReject = r;
9296
});
97+
if (opener && browserContext._options.viewport !== null && opener._nextWindowOpenPopupFeatures) {
98+
const viewportSize = helper.getViewportSizeFromWindowFeatures(opener._nextWindowOpenPopupFeatures);
99+
opener._nextWindowOpenPopupFeatures = undefined;
100+
if (viewportSize)
101+
this._page._state.viewportSize = viewportSize;
102+
}
93103
}
94104

95105
private async _initializePageProxySession() {
@@ -243,6 +253,11 @@ export class WKPage implements PageDelegate {
243253
this._page._frameManager.provisionalLoadFailed(this._page.mainFrame(), event.loaderId, errorText);
244254
}
245255

256+
handleWindowOpen(event: Protocol.Playwright.windowOpenPayload) {
257+
debugAssert(!this._nextWindowOpenPopupFeatures);
258+
this._nextWindowOpenPopupFeatures = event.windowFeatures;
259+
}
260+
246261
async pageOrError(): Promise<Page | Error> {
247262
return this._pagePromise;
248263
}

test/popup.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,26 @@ describe('window.open', function() {
138138
await context.close();
139139
expect(size).toEqual({width: 400, height: 500});
140140
});
141+
it('should use viewport size from window features', async function({browser, server}) {
142+
const context = await browser.newContext({
143+
viewport: { width: 700, height: 700 }
144+
});
145+
const page = await context.newPage();
146+
await page.goto(server.EMPTY_PAGE);
147+
const [size, popup] = await Promise.all([
148+
page.evaluate(() => {
149+
const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0');
150+
return { width: win.innerWidth, height: win.innerHeight };
151+
}),
152+
page.waitForEvent('popup'),
153+
]);
154+
await popup.setViewportSize({ width: 500, height: 400 });
155+
await popup.waitForLoadState();
156+
const resized = await popup.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
157+
await context.close();
158+
expect(size).toEqual({width: 600, height: 300});
159+
expect(resized).toEqual({width: 500, height: 400});
160+
});
141161
it('should respect routes from browser context', async function({browser, server}) {
142162
const context = await browser.newContext();
143163
const page = await context.newPage();

0 commit comments

Comments
 (0)