From abced7223c5fc724615ca9410896fe5874552f08 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 27 May 2022 13:04:58 -0700 Subject: [PATCH] fix: filechooser interception in OOPIFs (#14432) --- .../playwright-core/src/server/chromium/crPage.ts | 10 +++++++--- .../playwright-core/src/server/firefox/ffPage.ts | 2 +- packages/playwright-core/src/server/page.ts | 7 +++++-- .../playwright-core/src/server/webkit/wkPage.ts | 7 +++---- tests/library/chromium/oopif.spec.ts | 15 +++++++++++++++ tests/page/page-set-input-files.spec.ts | 14 ++++++++++++++ 6 files changed, 45 insertions(+), 10 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 0ed9ae5594..ae97243072 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -219,8 +219,8 @@ export class CRPage implements PageDelegate { await this._forAllFrameSessions(frame => frame._updateRequestInterception()); } - async setFileChooserIntercepted(enabled: boolean) { - await this._forAllFrameSessions(frame => frame.setFileChooserIntercepted(enabled)); + async updateFileChooserInterception() { + await this._forAllFrameSessions(frame => frame._updateFileChooserInterception(false)); } async reload(): Promise { @@ -559,6 +559,7 @@ class FrameSession { promises.push(this._updateOffline(true)); promises.push(this._updateHttpCredentials(true)); promises.push(this._updateEmulateMedia(true)); + promises.push(this._updateFileChooserInterception(true)); for (const binding of this._crPage._page.allBindings()) promises.push(this._initBinding(binding)); for (const source of this._crPage._browserContext.initScripts) @@ -1082,7 +1083,10 @@ class FrameSession { await this._networkManager.setRequestInterception(this._page._needsRequestInterception()); } - async setFileChooserIntercepted(enabled: boolean) { + async _updateFileChooserInterception(initial: boolean) { + const enabled = this._page._state.interceptFileChooser; + if (initial && !enabled) + return; await this._client.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed. } diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index ddb21be0e1..d09ea17f4f 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -386,7 +386,7 @@ export class FFPage implements PageDelegate { await this._networkManager.setRequestInterception(this._page._needsRequestInterception()); } - async setFileChooserIntercepted(enabled: boolean) { + async updateFileChooserInterception(enabled: boolean) { await this._session.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed. } diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 7e8b62d4ec..94a44d0052 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -67,7 +67,7 @@ export interface PageDelegate { setEmulatedSize(emulatedSize: types.EmulatedSize): Promise; updateEmulateMedia(): Promise; updateRequestInterception(): Promise; - setFileChooserIntercepted(enabled: boolean): Promise; + updateFileChooserInterception(enabled: boolean): Promise; bringToFront(): Promise; setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise; @@ -104,6 +104,7 @@ type PageState = { reducedMotion: types.ReducedMotion | null; forcedColors: types.ForcedColors | null; extraHTTPHeaders: types.HeadersArray | null; + interceptFileChooser: boolean; }; type ExpectScreenshotOptions = { @@ -183,6 +184,7 @@ export class Page extends SdkObject { reducedMotion: browserContext._options.reducedMotion !== undefined ? browserContext._options.reducedMotion : 'no-preference', forcedColors: browserContext._options.forcedColors !== undefined ? browserContext._options.forcedColors : 'none', extraHTTPHeaders: null, + interceptFileChooser: false, }; this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate)); this.keyboard = new input.Keyboard(delegate.rawKeyboard, this); @@ -620,7 +622,8 @@ export class Page extends SdkObject { } async setFileChooserIntercepted(enabled: boolean): Promise { - await this._delegate.setFileChooserIntercepted(enabled); + this._state.interceptFileChooser = enabled; + await this._delegate.updateFileChooserInterception(enabled); } frameNavigatedToNewDocument(frame: frames.Frame) { diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 6077850c35..e40beff66d 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -77,7 +77,6 @@ export class WKPage implements PageDelegate { private _nextWindowOpenPopupFeatures?: string[]; private _recordingVideoFile: string | null = null; private _screencastGeneration: number = 0; - private _interceptingFileChooser = false; constructor(browserContext: WKBrowserContext, pageProxySession: WKSession, opener: WKPage | null) { this._pageProxySession = pageProxySession; @@ -218,7 +217,7 @@ export class WKPage implements PageDelegate { promises.push(session.send('Page.setTimeZone', { timeZone: contextOptions.timezoneId }). catch(e => { throw new Error(`Invalid timezone ID: ${contextOptions.timezoneId}`); })); } - if (this._interceptingFileChooser) + if (this._page._state.interceptFileChooser) promises.push(session.send('Page.setInterceptFileChooserDialog', { enabled: true })); promises.push(session.send('Page.overrideSetting', { setting: 'DeviceOrientationEventEnabled' as any, value: contextOptions.isMobile })); promises.push(session.send('Page.overrideSetting', { setting: 'FullScreenEnabled' as any, value: !contextOptions.isMobile })); @@ -725,8 +724,8 @@ export class WKPage implements PageDelegate { await this._pageProxySession.send('Emulation.setAuthCredentials', { username: credentials.username, password: credentials.password }); } - async setFileChooserIntercepted(enabled: boolean) { - this._interceptingFileChooser = enabled; + async updateFileChooserInterception() { + const enabled = this._page._state.interceptFileChooser; await this._session.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed. } diff --git a/tests/library/chromium/oopif.spec.ts b/tests/library/chromium/oopif.spec.ts index 49855fb3f0..2d5671a219 100644 --- a/tests/library/chromium/oopif.spec.ts +++ b/tests/library/chromium/oopif.spec.ts @@ -311,6 +311,21 @@ it('should allow cdp sessions on oopifs', async function({ page, browser, server expect(JSON.stringify(oopif)).toContain('./digits/1.png'); }); +it('should emit filechooser event for iframe', async ({ page, server, browser }) => { + // Add listener before OOPIF is created. + const chooserPromise = page.waitForEvent('filechooser'); + await page.goto(server.PREFIX + '/dynamic-oopif.html'); + expect(await countOOPIFs(browser)).toBe(1); + expect(page.frames().length).toBe(2); + const frame = page.frames()[1]; + await frame.setContent(``); + const [chooser] = await Promise.all([ + chooserPromise, + frame.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + async function countOOPIFs(browser) { const browserSession = await browser.newBrowserCDPSession(); const oopifs = []; diff --git a/tests/page/page-set-input-files.spec.ts b/tests/page/page-set-input-files.spec.ts index c022f8af06..601428c9e8 100644 --- a/tests/page/page-set-input-files.spec.ts +++ b/tests/page/page-set-input-files.spec.ts @@ -517,3 +517,17 @@ it('should emit event after navigation', async ({ page, server, browserName, bro ]); expect(logs).toEqual(['filechooser', 'filechooser']); }); + +it('should trigger listener added before navigation', async ({ page, server }) => { + // Add listener before cross process navigation. + const chooserPromise = new Promise(f => page.once('filechooser', f)); + await page.goto(server.PREFIX + '/empty.html'); + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await page.setContent(``); + const [chooser] = await Promise.all([ + chooserPromise, + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); +