feat(worker): expose worker's owner frame

This commit is contained in:
Dmitry Gozman 2020-01-27 17:06:09 -08:00
parent a64fc0e45f
commit 5614c75751
9 changed files with 43 additions and 9 deletions

View file

@ -3248,6 +3248,7 @@ for (const worker of page.workers())
<!-- GEN:toc -->
- [worker.evaluate(pageFunction[, ...args])](#workerevaluatepagefunction-args)
- [worker.evaluateHandle(pageFunction[, ...args])](#workerevaluatehandlepagefunction-args)
- [worker.frame()](#workerframe)
- [worker.url()](#workerurl)
<!-- GEN:stop -->
@ -3269,6 +3270,9 @@ The only difference between `worker.evaluate` and `worker.evaluateHandle` is tha
If the function passed to the `worker.evaluateHandle` returns a [Promise], then `worker.evaluateHandle` would wait for the promise to resolve and return its value.
#### worker.frame()
- returns: <[Frame]> Frame which has created this worker.
#### worker.url()
- returns: <[string]>

View file

@ -10,7 +10,7 @@
"playwright": {
"chromium_revision": "733125",
"firefox_revision": "1018",
"webkit_revision": "1113"
"webkit_revision": "1118"
},
"scripts": {
"unit": "node test/test.js",

View file

@ -219,7 +219,7 @@ export class CRPage implements PageDelegate {
return;
const url = event.targetInfo.url;
const session = CRConnection.fromSession(this._client).session(event.sessionId)!;
const worker = new Worker(url);
const worker = new Worker(url, null as any);
this._page._addWorker(event.sessionId, worker);
session.once('Runtime.executionContextCreated', async event => {
worker._createExecutionContext(new CRExecutionContext(session, event.context));

View file

@ -104,7 +104,8 @@ export class CRTarget {
if (!this._workerPromise) {
// TODO(einbinder): Make workers send their console logs.
this._workerPromise = this._sessionFactory().then(session => {
const worker = new Worker(this._targetInfo.url);
// TODO: we should not reuse Worker class (which is web worker) for service workers.
const worker = new Worker(this._targetInfo.url, null as any);
session.once('Runtime.executionContextCreated', async event => {
worker._createExecutionContext(new CRExecutionContext(session, event.context));
});
@ -132,6 +133,8 @@ export class CRTarget {
}
opener(): CRTarget | null {
if (this._targetInfo.type === 'worker')
return null;
const { openerId } = this._targetInfo;
if (!openerId)
return null;

View file

@ -203,7 +203,7 @@ export class FFPage implements PageDelegate {
async _onWorkerCreated(event: Protocol.Page.workerCreatedPayload) {
const workerId = event.workerId;
const worker = new Worker(event.url);
const worker = new Worker(event.url, this._page._frameManager.frame(event.frameId)!);
const workerSession = new FFSession(this._session._connection, 'worker', workerId, (message: any) => {
this._session.send('Page.sendMessageToWorker', {
frameId: event.frameId,

View file

@ -120,6 +120,7 @@ export class FrameManager {
frame._lastDocumentId = documentId;
this.clearFrameLifecycle(frame);
this.clearWebSockets(frame);
this._page._clearWorkers(frame);
if (!initial) {
for (const watcher of this._lifecycleWatchers)
watcher._onCommittedNewDocumentNavigation(frame);

View file

@ -519,22 +519,26 @@ export class Page extends platform.EventEmitter {
this._workers.delete(workerId);
}
_clearWorkers() {
_clearWorkers(frame: frames.Frame) {
for (const [workerId, worker] of this._workers) {
this.emit(Events.Page.WorkerDestroyed, worker);
if (worker.frame() !== frame)
continue;
this._workers.delete(workerId);
this.emit(Events.Page.WorkerDestroyed, worker);
}
}
}
export class Worker {
private _url: string;
private _frame: frames.Frame;
private _executionContextPromise: Promise<js.ExecutionContext>;
private _executionContextCallback: (value?: js.ExecutionContext) => void;
_existingExecutionContext: js.ExecutionContext | null = null;
constructor(url: string) {
constructor(url: string, frame: frames.Frame) {
this._url = url;
this._frame = frame;
this._executionContextCallback = () => {};
this._executionContextPromise = new Promise(x => this._executionContextCallback = x);
}
@ -548,6 +552,10 @@ export class Worker {
return this._url;
}
frame(): frames.Frame {
return this._frame;
}
evaluate: types.Evaluate = async (pageFunction, ...args) => {
return (await this._executionContextPromise).evaluate(pageFunction, ...args as any);
}

View file

@ -34,7 +34,7 @@ export class WKWorkers {
this.clear();
this._sessionListeners = [
helper.addEventListener(session, 'Worker.workerCreated', async (event: Protocol.Worker.workerCreatedPayload) => {
const worker = new Worker(event.url);
const worker = new Worker(event.url, this._page._frameManager.frame(event.frameId));
const workerSession = new WKSession(session.connection, event.workerId, 'Most likely the worker has been closed.', (message: any) => {
session.send('Worker.sendMessageToWorker', {
workerId: event.workerId,
@ -77,7 +77,6 @@ export class WKWorkers {
}
clear() {
this._page._clearWorkers();
this._workerSessions.clear();
}

View file

@ -86,6 +86,25 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
expect(destroyed).toBe(true);
expect(page.workers().length).toBe(0);
});
it.skip(CHROMIUM)('should clear upon frame navigation', async function({server, page}) {
await page.goto(server.PREFIX + '/frames/one-frame.html');
const workerCreatedPromise = page.waitForEvent('workercreated');
const frame = page.mainFrame().childFrames()[0];
frame.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
await workerCreatedPromise;
expect(page.workers().length).toBe(1);
expect(page.workers()[0].frame()).toBe(frame);
let destroyed = false;
page.once('workerdestroyed', () => destroyed = true);
let navigated = false;
page.once('framenavigated', () => {
expect(destroyed).toBe(true);
expect(page.workers().length).toBe(0);
navigated = true;
});
await frame.goto(server.PREFIX + '/one-style.html');
expect(navigated).toBe(true);
});
it('should clear upon cross-process navigation', async function({server, page}) {
await page.goto(server.EMPTY_PAGE);
const workerCreatedPromise = page.waitForEvent('workercreated');