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);