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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer, Errors}) {
const {TimeoutError} = Errors; const {TimeoutError} = Errors;
describe('Target', function() { 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 // The pages will be the testing page and the original newtab page
const targets = browser.targets(); const targets = browser.targets();
expect(targets.some(target => target.type() === 'page' && 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).toContain(page);
expect(allPages[0]).not.toBe(allPages[1]); 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 targets = browser.targets();
const browserTarget = targets.find(target => target.type() === 'browser'); const browserTarget = targets.find(target => target.type() === 'browser');
expect(browserTarget).toBeTruthy(); expect(browserTarget).toBeTruthy();