From 72b9cf010e1a5c5d3d21cfcc42308d800220093d Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 11 Feb 2020 10:27:19 -0800 Subject: [PATCH] feat(context): introduce BrowserContext close event (#918) --- docs/api.md | 16 ++++++++++++---- src/browser.ts | 1 - src/browserContext.ts | 11 ++++++++++- src/chromium/crBrowser.ts | 6 +++++- src/events.ts | 4 ++++ src/firefox/ffBrowser.ts | 7 +++++-- src/server/webkit.ts | 2 -- src/webkit/wkBrowser.ts | 2 ++ test/chromium/chromium.spec.js | 2 +- test/launcher.spec.js | 13 +++++++++++++ 10 files changed, 52 insertions(+), 12 deletions(-) diff --git a/docs/api.md b/docs/api.md index b946c1790a..e8b6f26907 100644 --- a/docs/api.md +++ b/docs/api.md @@ -159,7 +159,7 @@ See [ChromiumBrowser], [FirefoxBrowser] and [WebKitBrowser] for browser-specific #### event: 'disconnected' Emitted when Browser gets disconnected from the browser application. This might happen because of one of the following: - Browser application is closed or crashed. -- The [`browser.disconnect`](#browserdisconnect) method was called. +- The [`browser.close`](#browserclose) method was called. #### browser.close() - returns: <[Promise]> @@ -263,6 +263,7 @@ await context.close(); ``` +- [event: 'close'](#event-close) - [browserContext.clearCookies()](#browsercontextclearcookies) - [browserContext.clearPermissions()](#browsercontextclearpermissions) - [browserContext.close()](#browsercontextclose) @@ -274,6 +275,13 @@ await context.close(); - [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions) +#### event: 'close' + +Emitted when Browser context gets closed. This might happen because of one of the following: +- Browser context is closed. +- Browser application is closed or crashed. +- The [`browser.close`](#browserclose) method was called. + #### browserContext.clearCookies() - returns: <[Promise]> @@ -426,7 +434,7 @@ page.removeListener('request', logRequest); ``` -- [event: 'close'](#event-close) +- [event: 'close'](#event-close-1) - [event: 'console'](#event-console) - [event: 'dialog'](#event-dialog) - [event: 'domcontentloaded'](#event-domcontentloaded) @@ -3168,7 +3176,7 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk The [WebSocket] class represents websocket connections in the page. -- [event: 'close'](#event-close-1) +- [event: 'close'](#event-close-2) - [event: 'error'](#event-error) - [event: 'messageReceived'](#event-messagereceived) - [event: 'messageSent'](#event-messagesent) @@ -3423,7 +3431,7 @@ If the function passed to the `worker.evaluateHandle` returns a [Promise], then ### class: BrowserServer -- [event: 'close'](#event-close-2) +- [event: 'close'](#event-close-3) - [browserServer.close()](#browserserverclose) - [browserServer.kill()](#browserserverkill) - [browserServer.process()](#browserserverprocess) diff --git a/src/browser.ts b/src/browser.ts index 905f5ee2f8..3b6bbdf641 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -25,7 +25,6 @@ export interface Browser extends platform.EventEmitterType { newPage(options?: BrowserContextOptions): Promise; isConnected(): boolean; close(): Promise; - _defaultContext: BrowserContext | undefined; } export type ConnectOptions = { diff --git a/src/browserContext.ts b/src/browserContext.ts index 86753436ad..60326d94e3 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -19,6 +19,8 @@ import { Page } from './page'; import * as network from './network'; import * as types from './types'; import { helper } from './helper'; +import * as platform from './platform'; +import { Events } from './events'; export interface BrowserContextDelegate { pages(): Promise; @@ -47,12 +49,13 @@ export type BrowserContextOptions = { permissions?: { [key: string]: string[] }; }; -export class BrowserContext { +export class BrowserContext extends platform.EventEmitter { private readonly _delegate: BrowserContextDelegate; readonly _options: BrowserContextOptions; private _closed = false; constructor(delegate: BrowserContextDelegate, options: BrowserContextOptions) { + super(); this._delegate = delegate; this._options = { ...options }; if (!this._options.viewport && this._options.viewport !== null) @@ -114,12 +117,18 @@ export class BrowserContext { return; await this._delegate.close(); this._closed = true; + this.emit(Events.BrowserContext.Close); } static validateOptions(options: BrowserContextOptions) { if (options.geolocation) verifyGeolocation(options.geolocation); } + + _browserClosed() { + this._closed = true; + this.emit(Events.BrowserContext.Close); + } } function verifyGeolocation(geolocation: types.Geolocation): types.Geolocation { diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 5bc2987cb4..377fec31bb 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -56,7 +56,11 @@ export class CRBrowser extends platform.EventEmitter implements Browser { this._client = connection.rootSession; this._defaultContext = this._createBrowserContext(null, {}); - this._connection.on(ConnectionEvents.Disconnected, () => this.emit(CommonEvents.Browser.Disconnected)); + this._connection.on(ConnectionEvents.Disconnected, () => { + for (const context of this.contexts()) + context._browserClosed(); + this.emit(CommonEvents.Browser.Disconnected); + }); this._client.on('Target.targetCreated', this._targetCreated.bind(this)); this._client.on('Target.targetDestroyed', this._targetDestroyed.bind(this)); this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this)); diff --git a/src/events.ts b/src/events.ts index a5d9f751ea..462b772e51 100644 --- a/src/events.ts +++ b/src/events.ts @@ -20,6 +20,10 @@ export const Events = { Disconnected: 'disconnected' }, + BrowserContext: { + Close: 'close' + }, + BrowserServer: { Close: 'close', }, diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 499b7269a7..27625b29ac 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -50,8 +50,11 @@ export class FFBrowser extends platform.EventEmitter implements Browser { this._defaultContext = this._createBrowserContext(null, {}); this._contexts = new Map(); - this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected)); - + this._connection.on(ConnectionEvents.Disconnected, () => { + for (const context of this.contexts()) + context._browserClosed(); + this.emit(Events.Browser.Disconnected); + }); this._eventListeners = [ helper.addEventListener(this._connection, 'Target.targetCreated', this._onTargetCreated.bind(this)), helper.addEventListener(this._connection, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)), diff --git a/src/server/webkit.ts b/src/server/webkit.ts index d201e3ec65..ff45332816 100644 --- a/src/server/webkit.ts +++ b/src/server/webkit.ts @@ -84,7 +84,6 @@ export class WebKit implements BrowserType { handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, - timeout = 30000 } = options; let temporaryUserDataDir: string | null = null; @@ -136,7 +135,6 @@ export class WebKit implements BrowserType { }, }); - const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to WebKit!`); transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream); browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? await wrapTransportWithWebSocket(transport, port || 0) : null); return { browserServer, transport }; diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index 659ff0ff76..2018f1bd53 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -66,6 +66,8 @@ export class WKBrowser extends platform.EventEmitter implements Browser { } _onDisconnect() { + for (const context of this.contexts()) + context._browserClosed(); for (const pageProxy of this._pageProxies.values()) pageProxy.dispose(); this._pageProxies.clear(); diff --git a/test/chromium/chromium.spec.js b/test/chromium/chromium.spec.js index 5f106e9e1f..6b169fe382 100644 --- a/test/chromium/chromium.spec.js +++ b/test/chromium/chromium.spec.js @@ -192,7 +192,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI await page.goto(server.EMPTY_PAGE); const target = await targetPromise; expect(await target.page()).toBe(page); - await page.close(); + await page.context().close(); }); it('should fire target events', async function({browser, newContext, server}) { const context = await newContext(); diff --git a/test/launcher.spec.js b/test/launcher.spec.js index 63f4b9539f..5b604d5f86 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -172,6 +172,19 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p expect(error.message).toContain('has been closed'); await browserServer.close(); }); + it('should emit close events on pages and contexts', async({server}) => { + const browserServer = await playwright.launchServer({...defaultBrowserOptions }); + const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() }); + const context = await remote.newContext(); + const page = await context.newPage(); + let contextClosed = false; + let pageClosed = false; + context.on('close', e => contextClosed = true); + page.on('close', e => pageClosed = true); + await browserServer.close(); + expect(contextClosed).toBeTruthy(); + expect(pageClosed).toBeTruthy(); + }); }); describe('Browser.close', function() {