feat(firefox): support Browser.target() (#4028)

Support browser target.

Drive-by: switch over to a more devtools'ish protocol:

- use `targetId` instead of `pageId` everywhere
- use target events instead of tab events
This commit is contained in:
Andrey Lushnikov 2019-02-17 10:23:48 -08:00 committed by GitHub
parent ea482c4751
commit 4a4793a5e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 42 deletions

View File

@ -31,8 +31,8 @@ class Browser extends EventEmitter {
this._process = process;
this._closeCallback = closeCallback;
/** @type {!Map<string, ?Target>} */
this._pageTargets = new Map();
/** @type {!Map<string, !Target>} */
this._targets = new Map();
this._defaultContext = new BrowserContext(this._connection, this, null);
/** @type {!Map<string, !BrowserContext>} */
@ -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.tabOpened', this._onTabOpened.bind(this)),
helper.addEventListener(this._connection, 'Browser.tabClosed', this._onTabClosed.bind(this)),
helper.addEventListener(this._connection, 'Browser.tabNavigated', this._onTabNavigated.bind(this)),
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)),
];
}
@ -152,29 +152,32 @@ class Browser extends EventEmitter {
* @return {Promise<Page>}
*/
async _createPageInContext(browserContextId) {
const {pageId} = await this._connection.send('Browser.newPage', {
const {targetId} = await this._connection.send('Browser.newPage', {
browserContextId: browserContextId || undefined
});
const target = this._pageTargets.get(pageId);
const target = this._targets.get(targetId);
return await target.page();
}
async pages() {
const pageTargets = Array.from(this._pageTargets.values());
const pageTargets = Array.from(this._targets.values()).filter(target => target.type() === 'page');
return await Promise.all(pageTargets.map(target => target.page()));
}
targets() {
return Array.from(this._pageTargets.values());
return Array.from(this._targets.values());
}
async _onTabOpened({pageId, url, browserContextId, openerId}) {
target() {
return this.targets().find(target => target.type() === 'browser');
}
async _onTargetCreated({targetId, url, browserContextId, openerId, type}) {
const context = browserContextId ? this._contexts.get(browserContextId) : this._defaultContext;
const opener = openerId ? this._pageTargets.get(openerId) : null;
const target = new Target(this._connection, this, context, pageId, url, opener);
this._pageTargets.set(pageId, target);
if (opener && opener._pagePromise) {
const openerPage = await opener._pagePromise;
const target = new Target(this._connection, this, context, targetId, type, url, openerId);
this._targets.set(targetId, target);
if (target.opener() && target.opener()._pagePromise) {
const openerPage = await target.opener()._pagePromise;
if (openerPage.listenerCount(Events.Page.Popup)) {
const popupPage = await target.page();
openerPage.emit(Events.Page.Popup, popupPage);
@ -184,15 +187,15 @@ class Browser extends EventEmitter {
context.emit(Events.BrowserContext.TargetCreated, target);
}
_onTabClosed({pageId}) {
const target = this._pageTargets.get(pageId);
this._pageTargets.delete(pageId);
_onTargetDestroyed({targetId}) {
const target = this._targets.get(targetId);
this._targets.delete(targetId);
this.emit(Events.Browser.TargetDestroyed, target);
target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
}
_onTabNavigated({pageId, url}) {
const target = this._pageTargets.get(pageId);
_onTargetInfoChanged({targetId, url}) {
const target = this._targets.get(targetId);
target._url = url;
this.emit(Events.Browser.TargetChanged, target);
target.browserContext().emit(Events.BrowserContext.TargetChanged, target);
@ -210,33 +213,35 @@ class Target {
* @param {*} connection
* @param {!Browser} browser
* @param {!BrowserContext} context
* @param {string} pageId
* @param {string} targetId
* @param {string} type
* @param {string} url
* @param {?Target} opener
* @param {string=} openerId
*/
constructor(connection, browser, context, pageId, url, opener) {
constructor(connection, browser, context, targetId, type, url, openerId) {
this._browser = browser;
this._context = context;
this._connection = connection;
this._pageId = pageId;
this._targetId = targetId;
this._type = type;
/** @type {?Promise<!Page>} */
this._pagePromise = null;
this._url = url;
this._opener = opener;
this._openerId = openerId;
}
/**
* @return {?Target}
*/
opener() {
return this._opener;
return this._openerId ? this._browser._targets.get(this._openerId) : null;
}
/**
* @return {"page"|"background_page"|"service_worker"|"other"|"browser"}
* @return {"page"|"browser"}
*/
type() {
return 'page';
return this._type;
}
url() {
@ -251,8 +256,8 @@ class Target {
}
async page() {
if (!this._pagePromise)
this._pagePromise = Page.create(this._connection, this, this._pageId, this._browser._defaultViewport);
if (this._type === 'page' && !this._pagePromise)
this._pagePromise = Page.create(this._connection, this, this._targetId, this._browser._defaultViewport);
return this._pagePromise;
}

View File

@ -19,14 +19,14 @@ const writeFileAsync = util.promisify(fs.writeFile);
* @internal
*/
class PageSession extends EventEmitter {
constructor(connection, pageId) {
constructor(connection, targetId) {
super();
this._connection = connection;
this._pageId = pageId;
this._targetId = targetId;
const wrapperSymbol = Symbol('listenerWrapper');
function wrapperListener(listener, params) {
if (params.pageId === pageId)
if (params.targetId === targetId)
listener.call(null, params);
}
@ -41,7 +41,7 @@ class PageSession extends EventEmitter {
}
async send(method, params = {}) {
params = Object.assign({}, params, {pageId: this._pageId});
params = Object.assign({}, params, {targetId: this._targetId});
return await this._connection.send(method, params);
}
}
@ -51,11 +51,11 @@ class Page extends EventEmitter {
*
* @param {!Puppeteer.Connection} connection
* @param {!Puppeteer.Target} target
* @param {string} pageId
* @param {string} targetId
* @param {?Puppeteer.Viewport} defaultViewport
*/
static async create(connection, target, pageId, defaultViewport) {
const session = new PageSession(connection, pageId);
static async create(connection, target, targetId, defaultViewport) {
const session = new PageSession(connection, targetId);
const page = new Page(session, target);
await session.send('Page.enable');
if (defaultViewport)
@ -82,7 +82,7 @@ class Page extends EventEmitter {
helper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)),
helper.addEventListener(this._session, 'Page.console', this._onConsole.bind(this)),
helper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)),
helper.addEventListener(this._session, 'Browser.tabClosed', this._onClosed.bind(this)),
helper.addEventListener(this._session, 'Browser.targetDestroyed', this._onClosed.bind(this)),
helper.addEventListener(this._frameManager, Events.FrameManager.Load, () => this.emit(Events.Page.Load)),
helper.addEventListener(this._frameManager, Events.FrameManager.DOMContentLoaded, () => this.emit(Events.Page.DOMContentLoaded)),
helper.addEventListener(this._frameManager, Events.FrameManager.FrameAttached, frame => this.emit(Events.Page.FrameAttached, frame)),

View File

@ -9,7 +9,7 @@
"node": ">=8.9.4"
},
"puppeteer": {
"firefox_revision": "10282bfac697c69a6fdfeec4cddae7caf98e1969"
"firefox_revision": "2ede4ae19f39ec7a1b73162a6004235908260dfe"
},
"scripts": {
"install": "node install.js",

View File

@ -20,7 +20,7 @@ module.exports.addTests = function({testRunner, expect, headless, puppeteer}) {
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Browser.target', function() {
it_fails_ffox('should return browser target', async({browser}) => {
it('should return browser target', async({browser}) => {
const target = browser.target();
expect(target.type()).toBe('browser');
});

View File

@ -24,7 +24,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer, Errors}) {
const {TimeoutError} = Errors;
describe('Target', function() {
it_fails_ffox('Browser.targets should return all of the targets', async({page, server, browser}) => {
it('Browser.targets should return all of the targets', async({page, server, browser}) => {
// The pages will be the testing page and the original newtab page
const targets = browser.targets();
expect(targets.some(target => target.type() === 'page' &&
@ -38,7 +38,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer, Errors}) {
expect(allPages).toContain(page);
expect(allPages[0]).not.toBe(allPages[1]);
});
it_fails_ffox('should contain browser target', async({browser}) => {
it('should contain browser target', async({browser}) => {
const targets = browser.targets();
const browserTarget = targets.find(target => target.type() === 'browser');
expect(browserTarget).toBeTruthy();