diff --git a/docs/api.md b/docs/api.md index 7f7b6f0bfb7..7813c6b5bb1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -79,6 +79,7 @@ * [event: 'load'](#event-load) * [event: 'metrics'](#event-metrics) * [event: 'pageerror'](#event-pageerror) + * [event: 'popup'](#event-popup) * [event: 'request'](#event-request) * [event: 'requestfailed'](#event-requestfailed) * [event: 'requestfinished'](#event-requestfinished) @@ -963,6 +964,25 @@ of metrics see `page.metrics`. Emitted when an uncaught exception happens within the page. +#### event: 'popup' +- <[Page]> Page corresponding to "popup" window + +Emitted when the page opens a new tab or window. + +```js +const [popup] = await Promise.all([ + new Promise(resolve => page.once('popup', resolve)), + page.click('a[target=_blank]'), +]); +``` + +```js +const [popup] = await Promise.all([ + new Promise(resolve => page.once('popup', resolve)), + page.evaluate(() => window.open('https://example.com')), +]); +``` + #### event: 'request' - <[Request]> diff --git a/lib/Page.js b/lib/Page.js index 95052520d07..592cb3dfb01 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -1192,6 +1192,7 @@ Page.Events = { FrameNavigated: 'framenavigated', Load: 'load', Metrics: 'metrics', + Popup: 'popup', WorkerCreated: 'workercreated', WorkerDestroyed: 'workerdestroyed', }; diff --git a/lib/Target.js b/lib/Target.js index cd2cab172b3..21081ed5fac 100644 --- a/lib/Target.js +++ b/lib/Target.js @@ -20,7 +20,19 @@ class Target { this._screenshotTaskQueue = screenshotTaskQueue; /** @type {?Promise} */ this._pagePromise = null; - this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill); + this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill).then(async success => { + if (!success) + return false; + const opener = this.opener(); + if (!opener || !opener._pagePromise || this.type() !== 'page') + return true; + const openerPage = await opener._pagePromise; + if (!openerPage.listenerCount(Page.Events.Popup)) + return true; + const popupPage = await this.page(); + openerPage.emit(Page.Events.Popup, popupPage); + return true; + }); this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill); this._isInitialized = this._targetInfo.type !== 'page' || this._targetInfo.url !== ''; if (this._isInitialized) diff --git a/test/page.spec.js b/test/page.spec.js index 3709ae271d2..5ba42156054 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -93,6 +93,55 @@ module.exports.addTests = function({testRunner, expect, headless}) { }); }); + describe('Page.Events.Popup', function() { + it('should work', async({page}) => { + const [popup] = await Promise.all([ + new Promise(x => page.once('popup', x)), + page.evaluate(() => window.open('about:blank')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + }); + it('should work with noopener', async({page}) => { + const [popup] = await Promise.all([ + new Promise(x => page.once('popup', x)), + page.evaluate(() => window.open('about:blank', null, 'noopener')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + }); + it('should work with clicking target=_blank', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + new Promise(x => page.once('popup', x)), + page.click('a'), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + }); + it('should work with fake-clicking target=_blank and rel=noopener', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + new Promise(x => page.once('popup', x)), + page.$eval('a', a => a.click()), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + }); + it('should work with clicking target=_blank and rel=noopener', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + new Promise(x => page.once('popup', x)), + page.click('a'), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + }); + }); + describe('BrowserContext.overridePermissions', function() { function getPermission(page, name) { return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name);