api(popups): emit PageEvent immediately, and resolve page() once initialized (#1229)

This way we do not miss any popups, even immediately closed ones.
This commit is contained in:
Dmitry Gozman 2020-03-05 15:18:27 -08:00 committed by GitHub
parent c734b4b715
commit e5f82af47c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 162 additions and 119 deletions

View file

@ -304,7 +304,15 @@ Emitted when Browser context gets closed. This might happen because of one of th
- <[PageEvent]> - <[PageEvent]>
Emitted when a new Page is created in the BrowserContext. The event will also fire for popup Emitted when a new Page is created in the BrowserContext. The event will also fire for popup
pages. pages. See also [`Page.on('popup')`](#event-popup) to receive events about popups relevant to a specific page.
```js
const [event] = await Promise.all([
context.waitForEvent('page'),
page.click('a[target=_blank]'),
]);
const newPage = await event.page();
```
#### browserContext.addInitScript(script[, ...args]) #### browserContext.addInitScript(script[, ...args])
- `script` <[function]|[string]|[Object]> Script to be evaluated in all pages in the browser context. - `script` <[function]|[string]|[Object]> Script to be evaluated in all pages in the browser context.
@ -726,22 +734,24 @@ Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/We
Emitted when an uncaught exception happens within the page. Emitted when an uncaught exception happens within the page.
#### event: 'popup' #### event: 'popup'
- <[Page]> Page corresponding to "popup" window - <[PageEvent]> Page event corresponding to "popup" window
Emitted when the page opens a new tab or window. Emitted when the page opens a new tab or window. This event is emitted in addition to the [`browserContext.on('page')`](#event-page), but only for popups relevant to this page.
```js ```js
const [popup] = await Promise.all([ const [event] = await Promise.all([
new Promise(resolve => page.once('popup', resolve)), page.waitForEvent('popup'),
page.click('a[target=_blank]'), page.click('a[target=_blank]'),
]); ]);
const popup = await event.page();
``` ```
```js ```js
const [popup] = await Promise.all([ const [event] = await Promise.all([
new Promise(resolve => page.once('popup', resolve)), page.waitForEvent('popup'),
page.evaluate(() => window.open('https://example.com')), page.evaluate(() => window.open('https://example.com')),
]); ]);
const popup = await event.page();
``` ```
#### event: 'request' #### event: 'request'
@ -1753,8 +1763,7 @@ This method returns all of the dedicated [WebWorkers](https://developer.mozilla.
### class: PageEvent ### class: PageEvent
Event object passed to the listeners of ['page'](#event-page) on [`BrowserContext`](#class-browsercontext). Provides access Event object passed to the listeners of [`browserContext.on('page')`](#event-page) and [`page.on('popup')`](#event-popup) events. Provides access to the newly created page.
to the newly created page.
#### pageEvent.page() #### pageEvent.page()
- returns: <[Promise]<[Page]>> Promise which resolves to the created page. - returns: <[Promise]<[Page]>> Promise which resolves to the created page.

View file

@ -71,7 +71,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
constructor(connection: CRConnection) { constructor(connection: CRConnection) {
super(); super();
this._connection = connection; this._connection = connection;
this._client = connection.rootSession; this._client = this._connection.rootSession;
this._defaultContext = new CRBrowserContext(this, null, validateBrowserContextOptions({})); this._defaultContext = new CRBrowserContext(this, null, validateBrowserContextOptions({}));
this._connection.on(ConnectionEvents.Disconnected, () => { this._connection.on(ConnectionEvents.Disconnected, () => {
@ -120,14 +120,18 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
try { try {
switch (targetInfo.type) { switch (targetInfo.type) {
case 'page': { case 'page': {
const page = await target.page(); const event = new PageEvent(target.pageOrError());
const event = new PageEvent(page!);
context.emit(CommonEvents.BrowserContext.Page, event); context.emit(CommonEvents.BrowserContext.Page, event);
const opener = target.opener();
if (!opener)
break;
const openerPage = await opener.pageOrError();
if (openerPage instanceof Page && !openerPage.isClosed())
openerPage.emit(CommonEvents.Page.Popup, new PageEvent(target.pageOrError()));
break; break;
} }
case 'background_page': { case 'background_page': {
const page = await target.page(); const event = new PageEvent(target.pageOrError());
const event = new PageEvent(page!);
context.emit(Events.CRBrowserContext.BackgroundPage, event); context.emit(Events.CRBrowserContext.BackgroundPage, event);
break; break;
} }
@ -268,16 +272,21 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
async pages(): Promise<Page[]> { async pages(): Promise<Page[]> {
const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'page'); const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'page');
const pages = await Promise.all(targets.map(target => target.page())); const pages = await Promise.all(targets.map(target => target.pageOrError()));
return pages.filter(page => !!page) as Page[]; return pages.filter(page => (page instanceof Page) && !page.isClosed()) as Page[];
} }
async newPage(): Promise<Page> { async newPage(): Promise<Page> {
assertBrowserContextIsNotOwned(this); assertBrowserContextIsNotOwned(this);
const { targetId } = await this._browser._client.send('Target.createTarget', { url: 'about:blank', browserContextId: this._browserContextId || undefined }); const { targetId } = await this._browser._client.send('Target.createTarget', { url: 'about:blank', browserContextId: this._browserContextId || undefined });
const target = this._browser._targets.get(targetId)!; const target = this._browser._targets.get(targetId)!;
const page = await target.page(); const result = await target.pageOrError();
return page!; if (result instanceof Page) {
if (result.isClosed())
throw new Error('Page has been closed.');
return result;
}
throw result;
} }
async cookies(...urls: string[]): Promise<network.NetworkCookie[]> { async cookies(...urls: string[]): Promise<network.NetworkCookie[]> {
@ -382,8 +391,8 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
async backgroundPages(): Promise<Page[]> { async backgroundPages(): Promise<Page[]> {
const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'background_page'); const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'background_page');
const pages = await Promise.all(targets.map(target => target.page())); const pages = await Promise.all(targets.map(target => target.pageOrError()));
return pages.filter(page => !!page) as Page[]; return pages.filter(page => (page instanceof Page) && !page.isClosed()) as Page[];
} }
async createSession(page: Page): Promise<CRSession> { async createSession(page: Page): Promise<CRSession> {

View file

@ -388,7 +388,10 @@ export class CRPage implements PageDelegate {
const openerTarget = CRTarget.fromPage(this._page).opener(); const openerTarget = CRTarget.fromPage(this._page).opener();
if (!openerTarget) if (!openerTarget)
return null; return null;
return await openerTarget.page(); const openerPage = await openerTarget.pageOrError();
if (openerPage instanceof Page && !openerPage.isClosed())
return openerPage;
return null;
} }
async reload(): Promise<void> { async reload(): Promise<void> {

View file

@ -17,7 +17,6 @@
import { CRBrowser, CRBrowserContext } from './crBrowser'; import { CRBrowser, CRBrowserContext } from './crBrowser';
import { CRSession, CRSessionEvents } from './crConnection'; import { CRSession, CRSessionEvents } from './crConnection';
import { Events } from '../events';
import { Page, Worker } from '../page'; import { Page, Worker } from '../page';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { debugError } from '../helper'; import { debugError } from '../helper';
@ -32,9 +31,8 @@ export class CRTarget {
private readonly _browserContext: CRBrowserContext; private readonly _browserContext: CRBrowserContext;
readonly _targetId: string; readonly _targetId: string;
readonly sessionFactory: () => Promise<CRSession>; readonly sessionFactory: () => Promise<CRSession>;
private _pagePromiseFulfill: ((page: Page) => void) | null = null; private _pagePromiseCallback: ((pageOrError: Page | Error) => void) | null = null;
private _pagePromiseReject: ((error: Error) => void) | null = null; private _pagePromise: Promise<Page | Error> | null = null;
private _pagePromise: Promise<Page> | null = null;
_crPage: CRPage | null = null; _crPage: CRPage | null = null;
private _workerPromise: Promise<Worker> | null = null; private _workerPromise: Promise<Worker> | null = null;
@ -56,12 +54,8 @@ export class CRTarget {
this._browserContext = browserContext; this._browserContext = browserContext;
this._targetId = targetInfo.targetId; this._targetId = targetInfo.targetId;
this.sessionFactory = sessionFactory; this.sessionFactory = sessionFactory;
if (CRTarget.isPageType(targetInfo.type)) { if (CRTarget.isPageType(targetInfo.type))
this._pagePromise = new Promise<Page>((fulfill, reject) => { this._pagePromise = new Promise<Page | Error>(f => this._pagePromiseCallback = f);
this._pagePromiseFulfill = fulfill;
this._pagePromiseReject = reject;
});
}
} }
_didClose() { _didClose() {
@ -69,10 +63,6 @@ export class CRTarget {
this._crPage.didClose(); this._crPage.didClose();
} }
async page(): Promise<Page | null> {
return this._pagePromise;
}
async initializePageSession(session: CRSession) { async initializePageSession(session: CRSession) {
this._crPage = new CRPage(session, this._browser, this._browserContext); this._crPage = new CRPage(session, this._browser, this._browserContext);
const page = this._crPage.page(); const page = this._crPage.page();
@ -80,20 +70,16 @@ export class CRTarget {
session.once(CRSessionEvents.Disconnected, () => page._didDisconnect()); session.once(CRSessionEvents.Disconnected, () => page._didDisconnect());
try { try {
await this._crPage.initialize(); await this._crPage.initialize();
this._pagePromiseFulfill!(page); this._pagePromiseCallback!(page);
} catch (error) { } catch (e) {
this._pagePromiseReject!(error); this._pagePromiseCallback!(e);
} }
}
if (this.type() !== 'page') async pageOrError(): Promise<Page | Error> {
return; if (this._targetInfo.type !== 'page' && this._targetInfo.type !== 'background_page')
const opener = this.opener(); throw new Error('Not a page.');
if (!opener) return this._pagePromise!;
return;
const openerPage = await opener.page();
if (!openerPage)
return;
openerPage.emit(Events.Page.Popup, page);
} }
async serviceWorker(): Promise<Worker | null> { async serviceWorker(): Promise<Worker | null> {

View file

@ -18,7 +18,7 @@
import { Browser, createPageInNewContext } from '../browser'; import { Browser, createPageInNewContext } from '../browser';
import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned } from '../browserContext'; import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned } from '../browserContext';
import { Events } from '../events'; import { Events } from '../events';
import { assert, helper, RegisteredListener, debugError } from '../helper'; import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network'; import * as network from '../network';
import * as types from '../types'; import * as types from '../types';
import { Page, PageEvent, PageBinding } from '../page'; import { Page, PageEvent, PageBinding } from '../page';
@ -165,17 +165,16 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
const {targetId} = payload.targetInfo; const {targetId} = payload.targetInfo;
const target = this._targets.get(targetId)!; const target = this._targets.get(targetId)!;
target._initPagePromise(this._connection.getSession(payload.sessionId)!); target._initPagePromise(this._connection.getSession(payload.sessionId)!);
const page = await target.page();
if (!page) const pageEvent = new PageEvent(target.pageOrError());
return; target.context().emit(Events.BrowserContext.Page, pageEvent);
target.context().emit(Events.BrowserContext.Page, new PageEvent(page));
const opener = target.opener(); const opener = target.opener();
if (opener && opener._pagePromise) { if (!opener)
const openerPage = await opener._pagePromise; return;
if (openerPage.listenerCount(Events.Page.Popup)) const openerPage = await opener.pageOrError();
openerPage.emit(Events.Page.Popup, page); if (openerPage instanceof Page && !openerPage.isClosed())
} openerPage.emit(Events.Page.Popup, pageEvent);
} }
async close() { async close() {
@ -192,7 +191,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
} }
class Target { class Target {
_pagePromise?: Promise<Page>; _pagePromise?: Promise<Page | Error>;
_ffPage: FFPage | null = null; _ffPage: FFPage | null = null;
private readonly _browser: FFBrowser; private readonly _browser: FFBrowser;
private readonly _context: FFBrowserContext; private readonly _context: FFBrowserContext;
@ -233,7 +232,7 @@ class Target {
return this._context; return this._context;
} }
async page(): Promise<Page> { async pageOrError(): Promise<Page | Error> {
if (this._type !== 'page') if (this._type !== 'page')
throw new Error(`Cannot create page for "${this._type}" target`); throw new Error(`Cannot create page for "${this._type}" target`);
if (!this._pagePromise) if (!this._pagePromise)
@ -247,12 +246,21 @@ class Target {
const openerTarget = this.opener(); const openerTarget = this.opener();
if (!openerTarget) if (!openerTarget)
return null; return null;
return await openerTarget.page(); const result = await openerTarget.pageOrError();
if (result instanceof Page && !result.isClosed())
return result;
return null;
}); });
const page = this._ffPage._page; const page = this._ffPage._page;
session.once(FFSessionEvents.Disconnected, () => page._didDisconnect()); session.once(FFSessionEvents.Disconnected, () => page._didDisconnect());
await this._ffPage._initialize().catch(debugError); let pageOrError: Page | Error;
f(page); try {
await this._ffPage._initialize();
pageOrError = page;
} catch (e) {
pageOrError = e;
}
f(pageOrError);
}); });
} }
@ -309,8 +317,8 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
async pages(): Promise<Page[]> { async pages(): Promise<Page[]> {
const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'page'); const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'page');
const pages = await Promise.all(targets.map(target => target.page())); const pages = await Promise.all(targets.map(target => target.pageOrError()));
return pages.filter(page => !!page); return pages.filter(page => page instanceof Page && !page.isClosed()) as Page[];
} }
async newPage(): Promise<Page> { async newPage(): Promise<Page> {
@ -319,7 +327,13 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
browserContextId: this._browserContextId || undefined browserContextId: this._browserContextId || undefined
}); });
const target = this._browser._targets.get(targetId)!; const target = this._browser._targets.get(targetId)!;
return target.page(); const result = await target.pageOrError();
if (result instanceof Page) {
if (result.isClosed())
throw new Error('Page has been closed.');
return result;
}
throw result;
} }
async cookies(...urls: string[]): Promise<network.NetworkCookie[]> { async cookies(...urls: string[]): Promise<network.NetworkCookie[]> {

View file

@ -89,14 +89,20 @@ export type FileChooser = {
}; };
export class PageEvent { export class PageEvent {
private readonly _page: Page; private readonly _pageOrError: Promise<Page | Error>;
constructor(page: Page) { constructor(pageOrErrorPromise: Promise<Page | Error>) {
this._page = page; this._pageOrError = pageOrErrorPromise;
} }
async page(/* options?: frames.NavigateOptions */): Promise<Page> { async page(/* options?: frames.NavigateOptions */): Promise<Page> {
return this._page; const result = await this._pageOrError;
if (result instanceof Page) {
if (result.isClosed())
throw new Error('Page has been closed.');
return result;
}
throw result;
} }
} }

View file

@ -70,10 +70,16 @@ export class Chromium implements BrowserType {
const { timeout = 30000 } = options || {}; const { timeout = 30000 } = options || {};
const { browserServer, transport } = await this._launchServer(options, 'persistent', userDataDir); const { browserServer, transport } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await CRBrowser.connect(transport!, true); const browser = await CRBrowser.connect(transport!, true);
const firstPage = new Promise(r => browser._defaultContext.once(Events.BrowserContext.Page, r));
await helper.waitWithTimeout(firstPage, 'first page', timeout);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
const browserContext = browser._defaultContext; const browserContext = browser._defaultContext;
function targets() {
return browser._allTargets().filter(target => target.context() === browserContext && target.type() === 'page');
}
const firstTarget = targets().length ? Promise.resolve() : new Promise(f => browserContext.once('page', f));
const firstPage = firstTarget.then(() => targets()[0].pageOrError());
await helper.waitWithTimeout(firstPage, 'first page', timeout);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browserContext.close = () => browserServer.close(); browserContext.close = () => browserServer.close();
return browserContext; return browserContext;
} }

View file

@ -17,7 +17,7 @@
import { Browser, createPageInNewContext } from '../browser'; import { Browser, createPageInNewContext } from '../browser';
import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext'; import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext';
import { assert, helper, RegisteredListener, debugError } from '../helper'; import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network'; import * as network from '../network';
import { Page, PageBinding, PageEvent } from '../page'; import { Page, PageBinding, PageEvent } from '../page';
import { ConnectionTransport, SlowMoTransport } from '../transport'; import { ConnectionTransport, SlowMoTransport } from '../transport';
@ -126,17 +126,14 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
this._firstPageProxyCallback = undefined; this._firstPageProxyCallback = undefined;
} }
pageProxy.page().then(async page => { const pageEvent = new PageEvent(pageProxy.pageOrError());
if (!page) context.emit(Events.BrowserContext.Page, pageEvent);
return; if (!opener)
context!.emit(Events.BrowserContext.Page, new PageEvent(page)); return;
if (!opener) opener.pageOrError().then(openerPage => {
return; if (openerPage instanceof Page && !openerPage.isClosed())
const openerPage = await opener.page(); openerPage.emit(Events.Page.Popup, pageEvent);
if (!openerPage || page.isClosed()) });
return;
openerPage.emit(Events.Page.Popup, page);
}).catch(debugError); // Just not emit the event in case of initialization failure.
} }
_onPageProxyDestroyed(event: Protocol.Browser.pageProxyDestroyedPayload) { _onPageProxyDestroyed(event: Protocol.Browser.pageProxyDestroyedPayload) {
@ -233,16 +230,21 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
async pages(): Promise<Page[]> { async pages(): Promise<Page[]> {
const pageProxies = Array.from(this._browser._pageProxies.values()).filter(proxy => proxy._browserContext === this); const pageProxies = Array.from(this._browser._pageProxies.values()).filter(proxy => proxy._browserContext === this);
const pages = await Promise.all(pageProxies.map(proxy => proxy.page())); const pages = await Promise.all(pageProxies.map(proxy => proxy.pageOrError()));
return pages.filter(page => !!page) as Page[]; return pages.filter(page => page instanceof Page && !page.isClosed()) as Page[];
} }
async newPage(): Promise<Page> { async newPage(): Promise<Page> {
assertBrowserContextIsNotOwned(this); assertBrowserContextIsNotOwned(this);
const { pageProxyId } = await this._browser._browserSession.send('Browser.createPage', { browserContextId: this._browserContextId }); const { pageProxyId } = await this._browser._browserSession.send('Browser.createPage', { browserContextId: this._browserContextId });
const pageProxy = this._browser._pageProxies.get(pageProxyId)!; const pageProxy = this._browser._pageProxies.get(pageProxyId)!;
const page = await pageProxy.page(); const result = await pageProxy.pageOrError();
return page!; if (result instanceof Page) {
if (result.isClosed())
throw new Error('Page has been closed.');
return result;
}
throw result;
} }
async cookies(...urls: string[]): Promise<network.NetworkCookie[]> { async cookies(...urls: string[]): Promise<network.NetworkCookie[]> {

View file

@ -437,8 +437,12 @@ export class WKPage implements PageDelegate {
} }
async opener(): Promise<Page | null> { async opener(): Promise<Page | null> {
const openerPage = this._opener ? await this._opener.page() : null; if (!this._opener)
return openerPage && !openerPage.isClosed() ? openerPage : null; return null;
const openerPage = await this._opener.pageOrError();
if (openerPage instanceof Page && !openerPage.isClosed())
return openerPage;
return null;
} }
async reload(): Promise<void> { async reload(): Promise<void> {

View file

@ -27,9 +27,8 @@ export class WKPageProxy {
private readonly _pageProxySession: WKSession; private readonly _pageProxySession: WKSession;
readonly _browserContext: WKBrowserContext; readonly _browserContext: WKBrowserContext;
private readonly _opener: WKPageProxy | null; private readonly _opener: WKPageProxy | null;
private readonly _pagePromise: Promise<Page | null>; private readonly _pagePromise: Promise<Page | Error>;
private _pagePromiseFulfill: (page: Page | null) => void = () => {}; private _pagePromiseCallback: (page: Page | Error) => void = () => {};
private _pagePromiseReject: (error: Error) => void = () => {};
private readonly _wkPage: WKPage; private readonly _wkPage: WKPage;
private _initialized = false; private _initialized = false;
private readonly _sessions = new Map<string, WKSession>(); private readonly _sessions = new Map<string, WKSession>();
@ -45,10 +44,7 @@ export class WKPageProxy {
helper.addEventListener(this._pageProxySession, 'Target.dispatchMessageFromTarget', this._onDispatchMessageFromTarget.bind(this)), helper.addEventListener(this._pageProxySession, 'Target.dispatchMessageFromTarget', this._onDispatchMessageFromTarget.bind(this)),
helper.addEventListener(this._pageProxySession, 'Target.didCommitProvisionalTarget', this._onDidCommitProvisionalTarget.bind(this)), helper.addEventListener(this._pageProxySession, 'Target.didCommitProvisionalTarget', this._onDidCommitProvisionalTarget.bind(this)),
]; ];
this._pagePromise = new Promise((f, r) => { this._pagePromise = new Promise(f => this._pagePromiseCallback = f);
this._pagePromiseFulfill = f;
this._pagePromiseReject = r;
});
this._wkPage = new WKPage(this._browserContext, this._pageProxySession, this._opener); this._wkPage = new WKPage(this._browserContext, this._pageProxySession, this._opener);
} }
@ -89,7 +85,7 @@ export class WKPageProxy {
this._wkPage._page._frameManager.provisionalLoadFailed(this._wkPage._page.mainFrame(), event.loaderId, errorText); this._wkPage._page._frameManager.provisionalLoadFailed(this._wkPage._page.mainFrame(), event.loaderId, errorText);
} }
async page(): Promise<Page | null> { async pageOrError(): Promise<Page | Error> {
return this._pagePromise; return this._pagePromise;
} }
@ -112,21 +108,16 @@ export class WKPageProxy {
if (!this._initialized) { if (!this._initialized) {
assert(!targetInfo.isProvisional); assert(!targetInfo.isProvisional);
this._initialized = true; this._initialized = true;
let page: Page | null = null; let pageOrError: Page | Error;
let error: Error | undefined;
try { try {
await this._wkPage.initialize(session); await this._wkPage.initialize(session);
page = this._wkPage._page; pageOrError = this._wkPage._page;
} catch (e) { } catch (e) {
if (!this._pageProxySession.isDisposed()) pageOrError = e;
error = e;
} }
if (targetInfo.isPaused) if (targetInfo.isPaused)
this._resumeTarget(targetInfo.targetId); this._resumeTarget(targetInfo.targetId);
if (error) this._pagePromiseCallback(pageOrError);
this._pagePromiseReject(error);
else
this._pagePromiseFulfill(page);
} else { } else {
assert(targetInfo.isProvisional); assert(targetInfo.isProvisional);
(session as any)[isPovisionalSymbol] = true; (session as any)[isPovisionalSymbol] = true;

View file

@ -39,7 +39,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([ const [popup] = await Promise.all([
utils.waitEvent(page, 'popup'), utils.waitEvent(page, 'popup').then(e => e.page()),
page.evaluate(url => window.open(url), server.EMPTY_PAGE) page.evaluate(url => window.open(url), server.EMPTY_PAGE)
]); ]);
expect(popup.context()).toBe(context); expect(popup.context()).toBe(context);

View file

@ -209,7 +209,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
it('should work for adopted elements', async({page,server}) => { it('should work for adopted elements', async({page,server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([ const [popup] = await Promise.all([
page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }),
page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE), page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE),
]); ]);
const divHandle = await page.evaluateHandle(() => { const divHandle = await page.evaluateHandle(() => {

View file

@ -132,7 +132,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
describe('Page.opener', function() { describe('Page.opener', function() {
it('should provide access to the opener page', async({page}) => { it('should provide access to the opener page', async({page}) => {
const [popup] = await Promise.all([ const [popup] = await Promise.all([
new Promise(x => page.once('popup', x)), page.waitForEvent('popup').then(e => e.page()),
page.evaluate(() => window.open('about:blank')), page.evaluate(() => window.open('about:blank')),
]); ]);
const opener = await popup.opener(); const opener = await popup.opener();
@ -140,7 +140,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
}); });
it('should return null if parent page has been closed', async({page}) => { it('should return null if parent page has been closed', async({page}) => {
const [popup] = await Promise.all([ const [popup] = await Promise.all([
new Promise(x => page.once('popup', x)), page.waitForEvent('popup').then(e => e.page()),
page.evaluate(() => window.open('about:blank')), page.evaluate(() => window.open('about:blank')),
]); ]);
await page.close(); await page.close();
@ -1077,7 +1077,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
describe('Page.Events.Close', function() { describe('Page.Events.Close', function() {
it('should work with window.close', async function({ page, context, server }) { it('should work with window.close', async function({ page, context, server }) {
const newPagePromise = new Promise(f => page.once('popup', f)); const newPagePromise = page.waitForEvent('popup').then(e => e.page());
await page.evaluate(() => window['newPage'] = window.open('about:blank')); await page.evaluate(() => window['newPage'] = window.open('about:blank'));
const newPage = await newPagePromise; const newPage = await newPagePromise;
const closedPromise = new Promise(x => newPage.on('close', x)); const closedPromise = new Promise(x => newPage.on('close', x));

View file

@ -139,18 +139,31 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
const context = await browser.newContext(); const context = await browser.newContext();
const page = await context.newPage(); const page = await context.newPage();
const [popup] = await Promise.all([ const [popup] = await Promise.all([
new Promise(x => page.once('popup', x)), page.waitForEvent('popup').then(e => e.page()),
page.evaluate(() => window.__popup = window.open('about:blank')), page.evaluate(() => window.__popup = window.open('about:blank')),
]); ]);
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true); expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close(); await context.close();
}); });
it('should emit for immediately closed popups', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popupEvent] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => {
const win = window.open('about:blank');
win.close();
}),
]);
expect(popupEvent).toBeTruthy();
await context.close();
});
it('should work with empty url', async({browser}) => { it('should work with empty url', async({browser}) => {
const context = await browser.newContext(); const context = await browser.newContext();
const page = await context.newPage(); const page = await context.newPage();
const [popup] = await Promise.all([ const [popup] = await Promise.all([
new Promise(x => page.once('popup', x)), page.waitForEvent('popup').then(e => e.page()),
page.evaluate(() => window.__popup = window.open('')), page.evaluate(() => window.__popup = window.open('')),
]); ]);
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
@ -161,7 +174,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
const context = await browser.newContext(); const context = await browser.newContext();
const page = await context.newPage(); const page = await context.newPage();
const [popup] = await Promise.all([ const [popup] = await Promise.all([
new Promise(x => page.once('popup', x)), page.waitForEvent('popup').then(e => e.page()),
page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')), page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')),
]); ]);
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
@ -174,7 +187,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel="opener" href="/one-style.html">yo</a>'); await page.setContent('<a target=_blank rel="opener" href="/one-style.html">yo</a>');
const [popup] = await Promise.all([ const [popup] = await Promise.all([
page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }),
page.click('a'), page.click('a'),
]); ]);
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
@ -188,7 +201,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>'); await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([ const [popup] = await Promise.all([
page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }),
page.$eval('a', a => a.click()), page.$eval('a', a => a.click()),
]); ]);
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
@ -203,7 +216,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>'); await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([ const [popup] = await Promise.all([
page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }),
page.click('a'), page.click('a'),
]); ]);
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
@ -216,7 +229,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>'); await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([ const [popup] = await Promise.all([
page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }),
page.click('a'), page.click('a'),
]); ]);
let badSecondPopup = false; let badSecondPopup = false;