From aee6324bbac1cccc68a912b5a3afc781c461fd8f Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 6 Mar 2020 16:49:48 -0800 Subject: [PATCH] feat(firefox): roll firefox (#1273) --- package.json | 2 +- src/firefox/ffBrowser.ts | 60 +++++++++++++++---------------------- src/firefox/ffConnection.ts | 32 +++++--------------- src/firefox/ffPage.ts | 2 +- src/server/firefox.ts | 6 ++-- src/web.ts | 2 +- 6 files changed, 38 insertions(+), 66 deletions(-) diff --git a/package.json b/package.json index 59a06503e5..9acb57e523 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": "index.js", "playwright": { "chromium_revision": "747023", - "firefox_revision": "1039", + "firefox_revision": "1040", "webkit_revision": "1168" }, "scripts": { diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 6d34d5a7f7..a67b6b15dd 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -29,6 +29,8 @@ import { headersArray } from './ffNetworkManager'; import { FFPage } from './ffPage'; import { Protocol } from './protocol'; +const kAttachedToTarget = Symbol('kAttachedToTarget'); + export class FFBrowser extends platform.EventEmitter implements Browser { _connection: FFConnection; _targets: Map; @@ -36,10 +38,10 @@ export class FFBrowser extends platform.EventEmitter implements Browser { readonly _contexts: Map; private _eventListeners: RegisteredListener[]; - static async connect(transport: ConnectionTransport, slowMo?: number): Promise { + static async connect(transport: ConnectionTransport, attachToDefaultContext: boolean, slowMo?: number): Promise { const connection = new FFConnection(SlowMoTransport.wrap(transport, slowMo)); const browser = new FFBrowser(connection); - await connection.send('Target.enable'); + await connection.send('Browser.enable', { attachToDefaultContext }); return browser; } @@ -56,10 +58,8 @@ export class FFBrowser extends platform.EventEmitter implements Browser { 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)), - helper.addEventListener(this._connection, 'Target.targetInfoChanged', this._onTargetInfoChanged.bind(this)), - helper.addEventListener(this._connection, 'Target.attachedToTarget', this._onAttachedToTarget.bind(this)), + helper.addEventListener(this._connection, 'Browser.attachedToTarget', this._onAttachedToTarget.bind(this)), + helper.addEventListener(this._connection, 'Browser.detachedFromTarget', this._onDetachedFromTarget.bind(this)), ]; } @@ -88,7 +88,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser { hasTouch: false, }; } - const { browserContextId } = await this._connection.send('Target.createBrowserContext', { + const { browserContextId } = await this._connection.send('Browser.createBrowserContext', { userAgent: options.userAgent, bypassCSP: options.bypassCSP, javaScriptDisabled: options.javaScriptEnabled === false ? true : undefined, @@ -121,13 +121,13 @@ export class FFBrowser extends platform.EventEmitter implements Browser { return existingTarget; let resolve: (t: Target) => void; const targetPromise = new Promise(x => resolve = x); - this.on('targetchanged', check); + this.on(kAttachedToTarget, check); try { if (!timeout) return await targetPromise; return await helper.waitWithTimeout(targetPromise, 'target', timeout); } finally { - this.removeListener('targetchanged', check); + this.removeListener(kAttachedToTarget, check); } function check(target: Target) { @@ -140,33 +140,23 @@ export class FFBrowser extends platform.EventEmitter implements Browser { return Array.from(this._targets.values()); } - async _onTargetCreated(payload: Protocol.Target.targetCreatedPayload) { - const {targetId, url, browserContextId, openerId, type} = payload; - const context = browserContextId ? this._contexts.get(browserContextId)! : this._defaultContext; - const target = new Target(this._connection, this, context, targetId, type, url, openerId); - this._targets.set(targetId, target); - } - - _onTargetDestroyed(payload: Protocol.Target.targetDestroyedPayload) { + _onDetachedFromTarget(payload: Protocol.Browser.detachedFromTargetPayload) { const {targetId} = payload; const target = this._targets.get(targetId)!; this._targets.delete(targetId); target._didClose(); } - _onTargetInfoChanged(payload: Protocol.Target.targetInfoChangedPayload) { - const {targetId, url} = payload; - const target = this._targets.get(targetId)!; - target._url = url; - } - - async _onAttachedToTarget(payload: Protocol.Target.attachedToTargetPayload) { - const {targetId} = payload.targetInfo; - const target = this._targets.get(targetId)!; - target._initPagePromise(this._connection.getSession(payload.sessionId)!); + async _onAttachedToTarget(payload: Protocol.Browser.attachedToTargetPayload) { + const {targetId, browserContextId, openerId, type} = payload.targetInfo; + const context = browserContextId ? this._contexts.get(browserContextId)! : this._defaultContext; + const target = new Target(this, context, type, '', openerId); + this._targets.set(targetId, target); + target._initPagePromise(this._connection.createSession(payload.sessionId, type)); const pageEvent = new PageEvent(target.pageOrError()); target.context().emit(Events.BrowserContext.Page, pageEvent); + this.emit(kAttachedToTarget, target); const opener = target.opener(); if (!opener) @@ -194,23 +184,22 @@ class Target { _ffPage: FFPage | null = null; private readonly _browser: FFBrowser; private readonly _context: FFBrowserContext; - private readonly _connection: FFConnection; - private readonly _targetId: string; private readonly _type: 'page' | 'browser'; _url: string; private readonly _openerId: string | undefined; + private _session?: FFSession; - constructor(connection: any, browser: FFBrowser, context: FFBrowserContext, targetId: string, type: 'page' | 'browser', url: string, openerId: string | undefined) { + constructor(browser: FFBrowser, context: FFBrowserContext, type: 'page' | 'browser', url: string, openerId: string | undefined) { this._browser = browser; this._context = context; - this._connection = connection; - this._targetId = targetId; this._type = type; this._url = url; this._openerId = openerId; } _didClose() { + if (this._session) + this._session.dispose(); if (this._ffPage) this._ffPage.didClose(); } @@ -234,12 +223,11 @@ class Target { async pageOrError(): Promise { if (this._type !== 'page') throw new Error(`Cannot create page for "${this._type}" target`); - if (!this._pagePromise) - await this._connection.send('Target.attachToTarget', {targetId: this._targetId}); return this._pagePromise!; } _initPagePromise(session: FFSession) { + this._session = session; this._pagePromise = new Promise(async f => { this._ffPage = new FFPage(session, this._context, async () => { const openerTarget = this.opener(); @@ -318,7 +306,7 @@ export class FFBrowserContext extends BrowserContextBase { async newPage(): Promise { assertBrowserContextIsNotOwned(this); - const {targetId} = await this._browser._connection.send('Target.newPage', { + const {targetId} = await this._browser._connection.send('Browser.newPage', { browserContextId: this._browserContextId || undefined }); const target = this._browser._targets.get(targetId)!; @@ -410,7 +398,7 @@ export class FFBrowserContext extends BrowserContextBase { if (this._closed) return; assert(this._browserContextId, 'Non-incognito profiles cannot be closed!'); - await this._browser._connection.send('Target.removeBrowserContext', { browserContextId: this._browserContextId }); + await this._browser._connection.send('Browser.removeBrowserContext', { browserContextId: this._browserContextId }); this._browser._contexts.delete(this._browserContextId); this._didCloseInternal(); } diff --git a/src/firefox/ffConnection.ts b/src/firefox/ffConnection.ts index 929aa83cce..120ab8efb4 100644 --- a/src/firefox/ffConnection.ts +++ b/src/firefox/ffConnection.ts @@ -32,7 +32,7 @@ export class FFConnection extends platform.EventEmitter { private _lastId: number; private _callbacks: Map; private _transport: ConnectionTransport; - private _sessions: Map; + readonly _sessions: Map; _debugProtocol: (message: string) => void = platform.debug('pw:protocol'); _closed: boolean; @@ -60,14 +60,6 @@ export class FFConnection extends platform.EventEmitter { this.once = super.once; } - static fromSession(session: FFSession): FFConnection { - return session._connection; - } - - session(sessionId: string): FFSession | null { - return this._sessions.get(sessionId) || null; - } - async send( method: T, params?: Protocol.CommandParameters[T] @@ -94,17 +86,6 @@ export class FFConnection extends platform.EventEmitter { const object = JSON.parse(message); if (object.id === kBrowserCloseMessageId) return; - if (object.method === 'Target.attachedToTarget') { - const sessionId = object.params.sessionId; - const session = new FFSession(this, object.params.targetInfo.type, sessionId, message => this._rawSend({...message, sessionId})); - this._sessions.set(sessionId, session); - } else if (object.method === 'Target.detachedFromTarget') { - const session = this._sessions.get(object.params.sessionId); - if (session) { - session._onClosed(); - this._sessions.delete(object.params.sessionId); - } - } if (object.sessionId) { const session = this._sessions.get(object.sessionId); if (session) @@ -132,7 +113,7 @@ export class FFConnection extends platform.EventEmitter { callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`)); this._callbacks.clear(); for (const session of this._sessions.values()) - session._onClosed(); + session.dispose(); this._sessions.clear(); Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected)); } @@ -142,8 +123,10 @@ export class FFConnection extends platform.EventEmitter { this._transport.close(); } - getSession(sessionId: string): FFSession | null { - return this._sessions.get(sessionId) || null; + createSession(sessionId: string, type: string): FFSession { + const session = new FFSession(this, type, sessionId, message => this._rawSend({...message, sessionId})); + this._sessions.set(sessionId, session); + return session; } } @@ -206,11 +189,12 @@ export class FFSession extends platform.EventEmitter { } } - _onClosed() { + dispose() { for (const callback of this._callbacks.values()) callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`)); this._callbacks.clear(); this._disposed = true; + this._connection._sessions.delete(this._sessionId); Promise.resolve().then(() => this.emit(FFSessionEvents.Disconnected)); } } diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts index f00616b6d2..574242c10f 100644 --- a/src/firefox/ffPage.ts +++ b/src/firefox/ffPage.ts @@ -226,7 +226,7 @@ export class FFPage implements PageDelegate { const worker = this._workers.get(workerId); if (!worker) return; - worker.session._onClosed(); + worker.session.dispose(); this._workers.delete(workerId); this._page._removeWorker(workerId); } diff --git a/src/server/firefox.ts b/src/server/firefox.ts index e89e4e92c1..7f04903f9c 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -65,7 +65,7 @@ export class Firefox implements BrowserType { throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistent` instead'); const browserServer = await this._launchServer(options, 'local'); const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => { - return FFBrowser.connect(transport, options && options.slowMo); + return FFBrowser.connect(transport, false, options && options.slowMo); }); // Hack: for typical launch scenario, ensure that close waits for actual process termination. browser.close = () => browserServer.close(); @@ -81,7 +81,7 @@ export class Firefox implements BrowserType { const { timeout = 30000 } = options || {}; const browserServer = await this._launchServer(options, 'persistent', userDataDir); const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => { - return FFBrowser.connect(transport); + return FFBrowser.connect(transport, true); }); await helper.waitWithTimeout(browser._waitForTarget(t => t.type() === 'page'), 'first page', timeout); // Hack: for typical launch scenario, ensure that close waits for actual process termination. @@ -166,7 +166,7 @@ export class Firefox implements BrowserType { async connect(options: ConnectOptions): Promise { return await platform.connectToWebsocket(options.wsEndpoint, transport => { - return FFBrowser.connect(transport, options.slowMo); + return FFBrowser.connect(transport, false, options.slowMo); }); } diff --git a/src/web.ts b/src/web.ts index 0eea8270e7..552c98d3c2 100644 --- a/src/web.ts +++ b/src/web.ts @@ -37,7 +37,7 @@ const connect = { firefox: { connect: async (url: string) => { return await platform.connectToWebsocket(url, transport => { - return FirefoxBrowser.connect(transport); + return FirefoxBrowser.connect(transport, false); }); } }