Skip to content

Commit

Permalink
refactor(firefox): migrate onto Juggler flatten protocol (#4033)
Browse files Browse the repository at this point in the history
Juggler now implements the same "flatten" protocol as CDP.
This patch:

* copies `Connection.js` from original Puppeteer (with a few renames, e.g. `CDPSesssion` -> `JugglerSession`).
* migrates code to support protocol-level sessions
  • Loading branch information
aslushnikov committed Feb 19, 2019
1 parent 4a4793a commit 3b18092
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 71 deletions.
24 changes: 14 additions & 10 deletions experimental/puppeteer-firefox/lib/Browser.js
Expand Up @@ -11,9 +11,9 @@ class Browser extends EventEmitter {
* @param {function():void} closeCallback
*/
static async create(connection, defaultViewport, process, closeCallback) {
const {browserContextIds} = await connection.send('Browser.getBrowserContexts');
const {browserContextIds} = await connection.send('Target.getBrowserContexts');
const browser = new Browser(connection, browserContextIds, defaultViewport, process, closeCallback);
await connection.send('Browser.enable');
await connection.send('Target.enable');
return browser;
}

Expand Down Expand Up @@ -43,9 +43,9 @@ class Browser extends EventEmitter {
this._connection.on(Events.Connection.Disconnected, () => this.emit(Events.Browser.Disconnected));

this._eventListeners = [
helper.addEventListener(this._connection, 'Browser.targetCreated', this._onTargetCreated.bind(this)),
helper.addEventListener(this._connection, 'Browser.targetDestroyed', this._onTargetDestroyed.bind(this)),
helper.addEventListener(this._connection, 'Browser.targetInfoChanged', this._onTargetInfoChanged.bind(this)),
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)),
];
}

Expand All @@ -61,7 +61,7 @@ class Browser extends EventEmitter {
* @return {!BrowserContext}
*/
async createIncognitoBrowserContext() {
const {browserContextId} = await this._connection.send('Browser.createBrowserContext');
const {browserContextId} = await this._connection.send('Target.createBrowserContext');
const context = new BrowserContext(this._connection, this, browserContextId);
this._contexts.set(browserContextId, context);
return context;
Expand All @@ -79,7 +79,7 @@ class Browser extends EventEmitter {
}

async _disposeContext(browserContextId) {
await this._connection.send('Browser.removeBrowserContext', {browserContextId});
await this._connection.send('Target.removeBrowserContext', {browserContextId});
this._contexts.delete(browserContextId);
}

Expand Down Expand Up @@ -152,7 +152,7 @@ class Browser extends EventEmitter {
* @return {Promise<Page>}
*/
async _createPageInContext(browserContextId) {
const {targetId} = await this._connection.send('Browser.newPage', {
const {targetId} = await this._connection.send('Target.newPage', {
browserContextId: browserContextId || undefined
});
const target = this._targets.get(targetId);
Expand Down Expand Up @@ -190,6 +190,7 @@ class Browser extends EventEmitter {
_onTargetDestroyed({targetId}) {
const target = this._targets.get(targetId);
this._targets.delete(targetId);
target._closedCallback();
this.emit(Events.Browser.TargetDestroyed, target);
target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
}
Expand Down Expand Up @@ -228,6 +229,7 @@ class Target {
this._pagePromise = null;
this._url = url;
this._openerId = openerId;
this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill);
}

/**
Expand Down Expand Up @@ -256,8 +258,10 @@ class Target {
}

async page() {
if (this._type === 'page' && !this._pagePromise)
this._pagePromise = Page.create(this._connection, this, this._targetId, this._browser._defaultViewport);
if (this._type === 'page' && !this._pagePromise) {
const session = await this._connection.createSession(this._targetId);
this._pagePromise = Page.create(session, this, this._browser._defaultViewport);
}
return this._pagePromise;
}

Expand Down
139 changes: 128 additions & 11 deletions experimental/puppeteer-firefox/lib/Connection.js
Expand Up @@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const debugProtocol = require('debug')('hdfox:protocol');
const EventEmitter = require('events');
const {assert} = require('./helper');
const {Events} = require('./Events');
const debugProtocol = require('debug')('puppeteer:protocol');
const EventEmitter = require('events');

/**
* @internal
*/
class Connection extends EventEmitter {
/**
* @param {string} url
* @param {!Puppeteer.ConnectionTransport} transport
* @param {number=} delay
*/
Expand All @@ -36,9 +35,30 @@ class Connection extends EventEmitter {
this._transport = transport;
this._transport.onmessage = this._onMessage.bind(this);
this._transport.onclose = this._onClose.bind(this);
/** @type {!Map<string, !JugglerSession>}*/
this._sessions = new Map();
this._closed = false;
}

/**
* @param {!JugglerSession} session
* @return {!Connection}
*/
static fromSession(session) {
return session._connection;
}

/**
* @param {string} sessionId
* @return {?JugglerSession}
*/
session(sessionId) {
return this._sessions.get(sessionId) || null;
}

/**
* @return {string}
*/
url() {
return this._url;
}
Expand All @@ -49,15 +69,24 @@ class Connection extends EventEmitter {
* @return {!Promise<?Object>}
*/
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
*/
Expand All @@ -66,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 JugglerSession(this, object.params.targetInfo.type, sessionId);
this._sessions.set(sessionId, session);
} else if (object.method === 'Browser.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) {
Expand All @@ -90,13 +134,86 @@ class Connection extends EventEmitter {
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();
this.emit(Events.Connection.Disconnected);
}

dispose() {
this._onClose();
this._transport.close();
}

/**
* @param {string} targetId
* @return {!Promise<!JugglerSession>}
*/
async createSession(targetId) {
const {sessionId} = await this.send('Target.attachToTarget', {targetId});
return this._sessions.get(sessionId);
}
}

class JugglerSession extends EventEmitter {
/**
* @param {!Connection} connection
* @param {string} targetType
* @param {string} sessionId
*/
constructor(connection, targetType, sessionId) {
super();
/** @type {!Map<number, {resolve: function, reject: function, error: !Error, method: string}>}*/
this._callbacks = new Map();
this._connection = connection;
this._targetType = targetType;
this._sessionId = sessionId;
}

/**
* @param {string} method
* @param {!Object=} params
* @return {!Promise<?Object>}
*/
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._connection._rawSend({sessionId: this._sessionId, method, params});
return new Promise((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
});
}

/**
* @param {{id?: number, method: string, params: Object, error: {message: string, data: any}, result?: *}} object
*/
_onMessage(object) {
if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id);
this._callbacks.delete(object.id);
if (object.error)
callback.reject(createProtocolError(callback.error, callback.method, object));
else
callback.resolve(object.result);
} else {
assert(!object.id);
this.emit(object.method, object.params);
}
}

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});
}

_onClosed() {
for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
this._callbacks.clear();
this._connection = null;
this.emit(Events.JugglerSession.Disconnected);
}
}

/**
Expand All @@ -122,4 +239,4 @@ function rewriteError(error, message) {
return error;
}

module.exports = {Connection};
module.exports = {Connection, JugglerSession};
4 changes: 4 additions & 0 deletions experimental/puppeteer-firefox/lib/Events.js
Expand Up @@ -31,6 +31,10 @@ const Events = {
Disconnected: Symbol('Events.Connection.Disconnected'),
},

JugglerSession: {
Disconnected: Symbol('Events.JugglerSession.Disconnected'),
},

FrameManager: {
Load: Symbol('Events.FrameManager.Load'),
DOMContentLoaded: Symbol('Events.FrameManager.DOMContentLoaded'),
Expand Down

0 comments on commit 3b18092

Please sign in to comment.