From 89a5c396bf71fdf6d2dccc935190e353d6eb671f Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Tue, 22 Jan 2019 18:10:11 -0500 Subject: [PATCH] refactor: move to flatten protocol (#3827) DevTools protocol is dropping nested targets and switching to flatten protocol. This patch adopts the new scheme. Once this change lands, tip-of-tree Puppeteer will be incompatible with Chromium below 72.0.3606.0. Chromium 72 goes stable on [Jan, 29](https://www.chromestatus.com/features/schedule) - the same time we release the next version of Puppeteer, so this change won't hurt those clients who try using tip-of-tree Puppeteer with stable chrome. For the record: the previous attempt to land this was https://github.com/GoogleChrome/puppeteer/pull/3524. --- lib/Connection.js | 113 ++++++++++++++++++---------------------------- lib/Page.js | 6 +-- 2 files changed, 48 insertions(+), 71 deletions(-) diff --git a/lib/Connection.js b/lib/Connection.js index 9dfcccac417..616974a65af 100644 --- a/lib/Connection.js +++ b/lib/Connection.js @@ -16,7 +16,6 @@ const {helper, assert} = require('./helper'); const {Events} = require('./Events'); const debugProtocol = require('debug')('puppeteer:protocol'); -const debugSession = require('debug')('puppeteer:session'); const EventEmitter = require('events'); class Connection extends EventEmitter { @@ -46,11 +45,15 @@ class Connection extends EventEmitter { * @return {!Connection} */ static fromSession(session) { - let connection = session._connection; - // TODO(lushnikov): move to flatten protocol to avoid this. - while (connection instanceof CDPSession) - connection = connection._connection; - return connection; + return session._connection; + } + + /** + * @param {string} sessionId + * @return {?CDPSession} + */ + session(sessionId) { + return this._sessions.get(sessionId) || null; } /** @@ -66,15 +69,24 @@ class Connection extends EventEmitter { * @return {!Promise} */ send(method, params = {}) { - const id = ++this._lastId; - const message = JSON.stringify({id, method, params}); - debugProtocol('SEND ► ' + message); - this._transport.send(message); + const id = this._rawSend({method, params}); return new Promise((resolve, reject) => { this._callbacks.set(id, {resolve, reject, error: new Error(), method}); }); } + /** + * @param {*} message + * @return {number} + */ + _rawSend(message) { + const id = ++this._lastId; + message = JSON.stringify(Object.assign({}, message, {id})); + debugProtocol('SEND ► ' + message); + this._transport.send(message); + return id; + } + /** * @param {string} message */ @@ -83,7 +95,22 @@ class Connection extends EventEmitter { await new Promise(f => setTimeout(f, this._delay)); debugProtocol('◀ RECV ' + message); const object = JSON.parse(message); - if (object.id) { + if (object.method === 'Target.attachedToTarget') { + const sessionId = object.params.sessionId; + const session = new CDPSession(this, object.params.targetInfo.type, 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) + 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) { @@ -94,18 +121,7 @@ class Connection extends EventEmitter { callback.resolve(object.result); } } else { - if (object.method === 'Target.receivedMessageFromTarget') { - const session = this._sessions.get(object.params.sessionId); - if (session) - session._onMessage(object.params.message); - } else if (object.method === 'Target.detachedFromTarget') { - const session = this._sessions.get(object.params.sessionId); - if (session) - session._onClosed(); - this._sessions.delete(object.params.sessionId); - } else { - this.emit(object.method, object.params); - } + this.emit(object.method, object.params); } } @@ -134,30 +150,24 @@ class Connection extends EventEmitter { * @return {!Promise} */ async createSession(targetInfo) { - const {sessionId} = await this.send('Target.attachToTarget', {targetId: targetInfo.targetId}); - const session = new CDPSession(this, targetInfo.type, sessionId); - this._sessions.set(sessionId, session); - return session; + const {sessionId} = await this.send('Target.attachToTarget', {targetId: targetInfo.targetId, flatten: true}); + return this._sessions.get(sessionId); } } class CDPSession extends EventEmitter { /** - * @param {!Connection|!CDPSession} connection + * @param {!Connection} connection * @param {string} targetType * @param {string} sessionId */ constructor(connection, targetType, sessionId) { super(); - this._lastId = 0; /** @type {!Map}*/ this._callbacks = new Map(); - /** @type {null|Connection|CDPSession} */ this._connection = connection; this._targetType = targetType; this._sessionId = sessionId; - /** @type {!Map}*/ - this._sessions = new Map(); } /** @@ -168,28 +178,16 @@ class CDPSession extends EventEmitter { send(method, params = {}) { if (!this._connection) return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`)); - const id = ++this._lastId; - const message = JSON.stringify({id, method, params}); - debugSession('SEND ► ' + message); - this._connection.send('Target.sendMessageToTarget', {sessionId: this._sessionId, message}).catch(e => { - // The response from target might have been already dispatched. - if (!this._callbacks.has(id)) - return; - const callback = this._callbacks.get(id); - this._callbacks.delete(id); - callback.reject(rewriteError(callback.error, e && e.message)); - }); + const id = this._connection._rawSend({sessionId: this._sessionId, method, params}); return new Promise((resolve, reject) => { this._callbacks.set(id, {resolve, reject, error: new Error(), method}); }); } /** - * @param {string} message + * @param {{id?: number, method: string, params: Object, error: {message: string, data: any}, result?: *}} object */ - _onMessage(message) { - debugSession('◀ RECV ' + message); - const object = JSON.parse(message); + _onMessage(object) { if (object.id && this._callbacks.has(object.id)) { const callback = this._callbacks.get(object.id); this._callbacks.delete(object.id); @@ -198,17 +196,6 @@ class CDPSession extends EventEmitter { else callback.resolve(object.result); } else { - if (object.method === 'Target.receivedMessageFromTarget') { - const session = this._sessions.get(object.params.sessionId); - if (session) - session._onMessage(object.params.message); - } else if (object.method === 'Target.detachedFromTarget') { - const session = this._sessions.get(object.params.sessionId); - if (session) { - session._onClosed(); - this._sessions.delete(object.params.sessionId); - } - } assert(!object.id); this.emit(object.method, object.params); } @@ -227,16 +214,6 @@ class CDPSession extends EventEmitter { this._connection = null; this.emit(Events.CDPSession.Disconnected); } - - /** - * @param {string} targetType - * @param {string} sessionId - */ - _createSession(targetType, sessionId) { - const session = new CDPSession(this, targetType, sessionId); - this._sessions.set(sessionId, session); - return session; - } } helper.tracePublicAPI(CDPSession); diff --git a/lib/Page.js b/lib/Page.js index 63e63d67865..1bd7334bc88 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -18,6 +18,7 @@ const fs = require('fs'); const EventEmitter = require('events'); const mime = require('mime'); const {Events} = require('./Events'); +const {Connection} = require('./Connection'); const {NetworkManager} = require('./NetworkManager'); const {Dialog} = require('./Dialog'); const {EmulationManager} = require('./EmulationManager'); @@ -47,7 +48,7 @@ class Page extends EventEmitter { const page = new Page(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue); await Promise.all([ - client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false}), + client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}), client.send('Page.setLifecycleEventsEnabled', { enabled: true }), client.send('Network.enable', {}), client.send('Runtime.enable', {}).then(() => page._frameManager.ensureSecondaryDOMWorld()), @@ -106,11 +107,10 @@ class Page extends EventEmitter { }).catch(debugError); return; } - const session = client._createSession(event.targetInfo.type, event.sessionId); + const session = Connection.fromSession(client).session(event.sessionId); const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this)); this._workers.set(event.sessionId, worker); this.emit(Events.Page.WorkerCreated, worker); - }); client.on('Target.detachedFromTarget', event => { const worker = this._workers.get(event.sessionId);