Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(firefox): migrate onto Juggler flatten protocol #4033

Merged
merged 1 commit into from Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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