feat(firefox): support workers (#532)
This commit is contained in:
parent
bb3f12245c
commit
d64c38b586
|
|
@ -9,7 +9,7 @@
|
|||
"main": "index.js",
|
||||
"playwright": {
|
||||
"chromium_revision": "724623",
|
||||
"firefox_revision": "1014",
|
||||
"firefox_revision": "1016",
|
||||
"webkit_revision": "1099"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export class FFConnection extends platform.EventEmitter {
|
|||
}
|
||||
|
||||
static fromSession(session: FFSession): FFConnection {
|
||||
return session._connection!;
|
||||
return session._connection;
|
||||
}
|
||||
|
||||
session(sessionId: string): FFSession | null {
|
||||
|
|
@ -69,18 +69,21 @@ export class FFConnection extends platform.EventEmitter {
|
|||
method: T,
|
||||
params?: Protocol.CommandParameters[T]
|
||||
): Promise<Protocol.CommandReturnValues[T]> {
|
||||
const id = this._rawSend({method, params});
|
||||
const id = this.nextMessageId();
|
||||
this._rawSend({id, method, params});
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
|
||||
});
|
||||
}
|
||||
|
||||
_rawSend(message: any): number {
|
||||
const id = ++this._lastId;
|
||||
message = JSON.stringify(Object.assign({}, message, {id}));
|
||||
nextMessageId(): number {
|
||||
return ++this._lastId;
|
||||
}
|
||||
|
||||
_rawSend(message: any) {
|
||||
message = JSON.stringify(message);
|
||||
debugProtocol('SEND ► ' + message);
|
||||
this._transport.send(message);
|
||||
return id;
|
||||
}
|
||||
|
||||
async _onMessage(message: string) {
|
||||
|
|
@ -88,7 +91,7 @@ export class FFConnection extends platform.EventEmitter {
|
|||
const object = JSON.parse(message);
|
||||
if (object.method === 'Target.attachedToTarget') {
|
||||
const sessionId = object.params.sessionId;
|
||||
const session = new FFSession(this, object.params.targetInfo.type, sessionId);
|
||||
const session = new FFSession(this, object.params.targetInfo.type, sessionId, message => this._rawSend({...message, sessionId}));
|
||||
this._sessions.set(sessionId, session);
|
||||
} else if (object.method === 'Browser.detachedFromTarget') {
|
||||
const session = this._sessions.get(object.params.sessionId);
|
||||
|
|
@ -100,7 +103,7 @@ export class FFConnection extends platform.EventEmitter {
|
|||
if (object.sessionId) {
|
||||
const session = this._sessions.get(object.sessionId);
|
||||
if (session)
|
||||
session._onMessage(object);
|
||||
session.dispatchMessage(object);
|
||||
} else if (object.id) {
|
||||
const callback = this._callbacks.get(object.id);
|
||||
// Callbacks could be all rejected if someone has called `.dispose()`.
|
||||
|
|
@ -147,22 +150,25 @@ export const FFSessionEvents = {
|
|||
};
|
||||
|
||||
export class FFSession extends platform.EventEmitter {
|
||||
_connection: FFConnection | null;
|
||||
_connection: FFConnection;
|
||||
_disposed = false;
|
||||
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
|
||||
private _targetType: string;
|
||||
private _sessionId: string;
|
||||
private _rawSend: (message: any) => void;
|
||||
on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
|
||||
constructor(connection: FFConnection, targetType: string, sessionId: string) {
|
||||
constructor(connection: FFConnection, targetType: string, sessionId: string, rawSend: (message: any) => void) {
|
||||
super();
|
||||
this._callbacks = new Map();
|
||||
this._connection = connection;
|
||||
this._targetType = targetType;
|
||||
this._sessionId = sessionId;
|
||||
this._rawSend = rawSend;
|
||||
|
||||
this.on = super.on;
|
||||
this.addListener = super.addListener;
|
||||
|
|
@ -175,15 +181,16 @@ export class FFSession extends platform.EventEmitter {
|
|||
method: T,
|
||||
params?: Protocol.CommandParameters[T]
|
||||
): Promise<Protocol.CommandReturnValues[T]> {
|
||||
if (!this._connection)
|
||||
if (this._disposed)
|
||||
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});
|
||||
const id = this._connection.nextMessageId();
|
||||
this._rawSend({method, params, id});
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
|
||||
});
|
||||
}
|
||||
|
||||
_onMessage(object: { id?: number; method: string; params: object; error: { message: string; data: any; }; result?: any; }) {
|
||||
dispatchMessage(object: { id?: number; method: string; params: object; error: { message: string; data: any; }; result?: any; }) {
|
||||
if (object.id && this._callbacks.has(object.id)) {
|
||||
const callback = this._callbacks.get(object.id)!;
|
||||
this._callbacks.delete(object.id);
|
||||
|
|
@ -201,7 +208,7 @@ export class FFSession extends platform.EventEmitter {
|
|||
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._disposed = true;
|
||||
Promise.resolve().then(() => this.emit(FFSessionEvents.Disconnected));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { helper, RegisteredListener, debugError } from '../helper';
|
|||
import * as dom from '../dom';
|
||||
import { FFSession } from './ffConnection';
|
||||
import { FFExecutionContext } from './ffExecutionContext';
|
||||
import { Page, PageDelegate, Coverage } from '../page';
|
||||
import { Page, PageDelegate, Coverage, Worker } from '../page';
|
||||
import { FFNetworkManager } from './ffNetworkManager';
|
||||
import { Events } from '../events';
|
||||
import * as dialog from '../dialog';
|
||||
|
|
@ -43,6 +43,7 @@ export class FFPage implements PageDelegate {
|
|||
readonly _networkManager: FFNetworkManager;
|
||||
private readonly _contextIdToContext: Map<string, dom.FrameExecutionContext>;
|
||||
private _eventListeners: RegisteredListener[];
|
||||
private _workers = new Map<string, { frameId: string, session: FFSession }>();
|
||||
|
||||
constructor(session: FFSession, browserContext: BrowserContext) {
|
||||
this._session = session;
|
||||
|
|
@ -66,6 +67,9 @@ export class FFPage implements PageDelegate {
|
|||
helper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)),
|
||||
helper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)),
|
||||
helper.addEventListener(this._session, 'Page.fileChooserOpened', this._onFileChooserOpened.bind(this)),
|
||||
helper.addEventListener(this._session, 'Page.workerCreated', this._onWorkerCreated.bind(this)),
|
||||
helper.addEventListener(this._session, 'Page.workerDestroyed', this._onWorkerDestroyed.bind(this)),
|
||||
helper.addEventListener(this._session, 'Page.dispatchMessageFromWorker', this._onDispatchMessageFromWorker.bind(this)),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -128,6 +132,10 @@ export class FFPage implements PageDelegate {
|
|||
}
|
||||
|
||||
_onNavigationCommitted(params: Protocol.Page.navigationCommittedPayload) {
|
||||
for (const [workerId, worker] of this._workers) {
|
||||
if (worker.frameId === params.frameId)
|
||||
this._onWorkerDestroyed({ workerId });
|
||||
}
|
||||
this._page._frameManager.frameCommittedNewDocumentNavigation(params.frameId, params.url, params.name || '', params.navigationId || '', false);
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +193,48 @@ export class FFPage implements PageDelegate {
|
|||
this._page._onFileChooserOpened(handle);
|
||||
}
|
||||
|
||||
async _onWorkerCreated(event: Protocol.Page.workerCreatedPayload) {
|
||||
const workerId = event.workerId;
|
||||
const worker = new Worker(event.url);
|
||||
const workerSession = new FFSession(this._session._connection, 'worker', workerId, (message: any) => {
|
||||
this._session.send('Page.sendMessageToWorker', {
|
||||
frameId: event.frameId,
|
||||
workerId: workerId,
|
||||
message: JSON.stringify(message)
|
||||
}).catch(e => {
|
||||
workerSession.dispatchMessage({ id: message.id, method: '', params: {}, error: { message: e.message, data: undefined } });
|
||||
});
|
||||
});
|
||||
this._workers.set(workerId, { session: workerSession, frameId: event.frameId });
|
||||
this._page._addWorker(workerId, worker);
|
||||
workerSession.once('Runtime.executionContextCreated', event => {
|
||||
worker._createExecutionContext(new FFExecutionContext(workerSession, event.executionContextId));
|
||||
});
|
||||
workerSession.on('Runtime.console', event => {
|
||||
const {type, args, location} = event;
|
||||
const context = worker._existingExecutionContext!;
|
||||
this._page._addConsoleMessage(type, args.map(arg => context._createHandle(arg)), location);
|
||||
});
|
||||
// Note: we receive worker exceptions directly from the page.
|
||||
}
|
||||
|
||||
async _onWorkerDestroyed(event: Protocol.Page.workerDestroyedPayload) {
|
||||
const workerId = event.workerId;
|
||||
const worker = this._workers.get(workerId);
|
||||
if (!worker)
|
||||
return;
|
||||
worker.session._onClosed();
|
||||
this._workers.delete(workerId);
|
||||
this._page._removeWorker(workerId);
|
||||
}
|
||||
|
||||
async _onDispatchMessageFromWorker(event: Protocol.Page.dispatchMessageFromWorkerPayload) {
|
||||
const worker = this._workers.get(event.workerId);
|
||||
if (!worker)
|
||||
return;
|
||||
worker.session.dispatchMessage(JSON.parse(event.message));
|
||||
}
|
||||
|
||||
async exposeBinding(name: string, bindingFunction: string): Promise<void> {
|
||||
await this._session.send('Page.addBinding', {name: name});
|
||||
await this._session.send('Page.addScriptToEvaluateOnNewDocument', {script: bindingFunction});
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
|
|||
const {it, fit, xit, dit} = testRunner;
|
||||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
|
||||
describe.skip(FFOX)('Workers', function() {
|
||||
describe('Workers', function() {
|
||||
it('Page.workers', async function({page, server}) {
|
||||
await Promise.all([
|
||||
page.waitForEvent('workercreated'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue