diff --git a/src/chromium/Browser.ts b/src/chromium/Browser.ts index d030e22d29..d32dcc4107 100644 --- a/src/chromium/Browser.ts +++ b/src/chromium/Browser.ts @@ -47,15 +47,13 @@ export class Browser extends EventEmitter { defaultViewport: Viewport | null, process: childProcess.ChildProcess | null, closeCallback?: (() => Promise)) { - const session = await connection.createBrowserSession(); - const browser = new Browser(connection, session, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback); - await session.send('Target.setDiscoverTargets', {discover: true}); + const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback); + await connection.rootSession.send('Target.setDiscoverTargets', { discover: true }); return browser; } constructor( connection: Connection, - client: CDPSession, contextIds: string[], ignoreHTTPSErrors: boolean, defaultViewport: Viewport | null, @@ -63,7 +61,7 @@ export class Browser extends EventEmitter { closeCallback?: (() => Promise)) { super(); this._connection = connection; - this._client = client; + this._client = connection.rootSession; this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._defaultViewport = defaultViewport; this._process = process; @@ -151,13 +149,17 @@ export class Browser extends EventEmitter { } async _createPageInContext(contextId: string | null): Promise { - const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank', browserContextId: contextId || undefined}); + const { targetId } = await this._client.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined }); const target = await this._targets.get(targetId); assert(await target._initializedPromise, 'Failed to create target for page'); const page = await target.page(); return page; } + async _closeTarget(target: Target) { + await this._client.send('Target.closeTarget', { targetId: target._targetId }); + } + targets(): Target[] { return Array.from(this._targets.values()).filter(target => target._isInitialized); } @@ -222,6 +224,6 @@ export class Browser extends EventEmitter { } _getVersion(): Promise { - return this._connection.send('Browser.getVersion'); + return this._client.send('Browser.getVersion'); } } diff --git a/src/chromium/Connection.ts b/src/chromium/Connection.ts index 9586942d5c..a9fbe3bb1b 100644 --- a/src/chromium/Connection.ts +++ b/src/chromium/Connection.ts @@ -30,16 +30,11 @@ export const ConnectionEvents = { export class Connection extends EventEmitter { private _url: string; private _lastId = 0; - private _callbacks = new Map void, reject: (e: Error) => void, error: Error, method: string}>(); private _delay: number; private _transport: ConnectionTransport; private _sessions = new Map(); + readonly rootSession: CDPSession; _closed = false; - on: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; - addListener: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; - off: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; - removeListener: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; - once: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; constructor(url: string, transport: ConnectionTransport, delay: number | undefined = 0) { super(); @@ -49,6 +44,8 @@ export class Connection extends EventEmitter { this._transport = transport; this._transport.onmessage = this._onMessage.bind(this); this._transport.onclose = this._onClose.bind(this); + this.rootSession = new CDPSession(this, 'browser', ''); + this._sessions.set('', this.rootSession); } static fromSession(session: CDPSession): Connection { @@ -63,21 +60,14 @@ export class Connection extends EventEmitter { return this._url; } - send( - method: T, - params?: Protocol.CommandParameters[T] - ): Promise { - const id = this._rawSend({method, params}); - return new Promise((resolve, reject) => { - this._callbacks.set(id, {resolve, reject, error: new Error(), method}); - }); - } - - _rawSend(message: any): number { + _rawSend(sessionId: string, message: any): number { const id = ++this._lastId; - message = JSON.stringify(Object.assign({}, message, {id})); - debugProtocol('SEND ► ' + message); - this._transport.send(message); + message.id = id; + if (sessionId) + message.sessionId = sessionId; + const data = JSON.stringify(message); + debugProtocol('SEND ► ' + data); + this._transport.send(data); return id; } @@ -97,23 +87,9 @@ export class Connection extends EventEmitter { this._sessions.delete(object.params.sessionId); } } - if (object.sessionId) { - const session = this._sessions.get(object.sessionId); - if (session) - session._onMessage(object); - } else if (object.id) { - const callback = this._callbacks.get(object.id); - // Callbacks could be all rejected if someone has called `.dispose()`. - if (callback) { - this._callbacks.delete(object.id); - if (object.error) - callback.reject(createProtocolError(callback.error, callback.method, object)); - else - callback.resolve(object.result); - } - } else { - this.emit(object.method, object.params); - } + const session = this._sessions.get(object.sessionId || ''); + if (session) + session._onMessage(object); } _onClose() { @@ -122,9 +98,6 @@ export class Connection extends EventEmitter { this._closed = true; this._transport.onmessage = null; this._transport.onclose = null; - for (const callback of this._callbacks.values()) - callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`)); - this._callbacks.clear(); for (const session of this._sessions.values()) session._onClosed(); this._sessions.clear(); @@ -137,15 +110,13 @@ export class Connection extends EventEmitter { } async createSession(targetInfo: Protocol.Target.TargetInfo): Promise { - const {sessionId} = await this.send('Target.attachToTarget', {targetId: targetInfo.targetId, flatten: true}); + const { sessionId } = await this.rootSession.send('Target.attachToTarget', { targetId: targetInfo.targetId, flatten: true }); return this._sessions.get(sessionId); } async createBrowserSession(): Promise { - const { sessionId } = await this.send('Target.attachToBrowserTarget'); - const session = new CDPSession(this, 'browser', sessionId); - this._sessions.set(sessionId, session); - return session; + const { sessionId } = await this.rootSession.send('Target.attachToBrowserTarget'); + return this._sessions.get(sessionId); } } @@ -177,7 +148,7 @@ export class CDPSession extends EventEmitter { ): Promise { if (!this._connection) return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`)); - const id = this._connection._rawSend({sessionId: this._sessionId, method, params}); + const id = this._connection._rawSend(this._sessionId, { method, params }); return new Promise((resolve, reject) => { this._callbacks.set(id, {resolve, reject, error: new Error(), method}); }); @@ -200,7 +171,7 @@ export class CDPSession extends EventEmitter { async detach() { if (!this._connection) throw new Error(`Session already detached. Most likely the ${this._targetType} has been closed.`); - await this._connection.send('Target.detachFromTarget', {sessionId: this._sessionId}); + await this._connection.rootSession.send('Target.detachFromTarget', { sessionId: this._sessionId }); } _onClosed() { diff --git a/src/chromium/Launcher.ts b/src/chromium/Launcher.ts index d740b0e6d3..5f94c7b696 100644 --- a/src/chromium/Launcher.ts +++ b/src/chromium/Launcher.ts @@ -188,7 +188,7 @@ export class Launcher { killChrome(); } else if (connection) { // Attempt to close chrome gracefully - connection.send('Browser.close').catch(error => { + connection.rootSession.send('Browser.close').catch(error => { debugError(error); killChrome(); }); @@ -273,7 +273,7 @@ export class Launcher { connection = new Connection(connectionURL, connectionTransport, slowMo); } - const {browserContextIds} = await connection.send('Target.getBrowserContexts'); + const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts'); return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError)); } diff --git a/src/chromium/Page.ts b/src/chromium/Page.ts index c99a926a38..cae5d300ac 100644 --- a/src/chromium/Page.ts +++ b/src/chromium/Page.ts @@ -678,7 +678,7 @@ export class Page extends EventEmitter { if (runBeforeUnload) { await this._client.send('Page.close'); } else { - await this._client._connection.send('Target.closeTarget', { targetId: this._target._targetId }); + await this.browser()._closeTarget(this._target); await this._target._isClosedPromise; } }