feat: support closing workers (#11870)

This commit is contained in:
Alex Rudenko 2024-02-13 13:40:59 +01:00 committed by GitHub
parent 928d14ac84
commit 1bdae40ec8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 698 additions and 274 deletions

View File

@ -0,0 +1,17 @@
---
sidebar_label: WebWorker.close
---
# WebWorker.close() method
#### Signature:
```typescript
class WebWorker {
close(): Promise<void>;
}
```
**Returns:**
Promise&lt;void&gt;

View File

@ -46,6 +46,7 @@ for (const worker of page.workers()) {
| Method | Modifiers | Description |
| --------------------------------------------------------------------- | --------- | --------------------------------------------------------------------- |
| [close()](./puppeteer.webworker.close.md) | | |
| [evaluate(func, args)](./puppeteer.webworker.evaluate.md) | | Evaluates a given function in the [worker](./puppeteer.webworker.md). |
| [evaluateHandle(func, args)](./puppeteer.webworker.evaluatehandle.md) | | Evaluates a given function in the [worker](./puppeteer.webworker.md). |
| [url()](./puppeteer.webworker.url.md) | | The URL of this web worker. |

821
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {UnsupportedOperation} from '../common/Errors.js';
import {EventEmitter, type EventType} from '../common/EventEmitter.js';
import {TimeoutSettings} from '../common/TimeoutSettings.js';
import type {EvaluateFunc, HandleFor} from '../common/types.js';
@ -131,4 +132,8 @@ export abstract class WebWorker extends EventEmitter<
func = withSourcePuppeteerURLIfNone(this.evaluateHandle.name, func);
return await this.mainRealm().evaluateHandle(func, ...args);
}
async close(): Promise<void> {
throw new UnsupportedOperation('WebWorker.close() is not supported');
}
}

View File

@ -358,6 +358,8 @@ export class CdpPage extends Page {
const worker = new CdpWebWorker(
session,
session._target().url(),
session._target()._targetId,
session._target().type(),
this.#addConsoleMessage.bind(this),
this.#handleException.bind(this)
);

View File

@ -290,6 +290,8 @@ export class WorkerTarget extends CdpTarget {
return new CdpWebWorker(
client,
this._getTargetInfo().url,
this._targetId,
this.type(),
() => {} /* consoleAPICalled */,
() => {} /* exceptionThrown */
);

View File

@ -7,6 +7,7 @@ import type {Protocol} from 'devtools-protocol';
import type {CDPSession} from '../api/CDPSession.js';
import type {Realm} from '../api/Realm.js';
import {TargetType} from '../api/Target.js';
import {WebWorker} from '../api/WebWorker.js';
import {TimeoutSettings} from '../common/TimeoutSettings.js';
import {debugError} from '../common/util.js';
@ -37,15 +38,21 @@ export type ExceptionThrownCallback = (
export class CdpWebWorker extends WebWorker {
#world: IsolatedWorld;
#client: CDPSession;
readonly #id: string;
readonly #targetType: TargetType;
constructor(
client: CDPSession,
url: string,
targetId: string,
targetType: TargetType,
consoleAPICalled: ConsoleAPICalledCallback,
exceptionThrown: ExceptionThrownCallback
) {
super(url);
this.#id = targetId;
this.#client = client;
this.#targetType = targetType;
this.#world = new IsolatedWorld(this, new TimeoutSettings());
this.#client.once('Runtime.executionContextCreated', async event => {
@ -79,4 +86,25 @@ export class CdpWebWorker extends WebWorker {
get client(): CDPSession {
return this.#client;
}
override async close(): Promise<void> {
switch (this.#targetType) {
case TargetType.SERVICE_WORKER:
case TargetType.SHARED_WORKER: {
// For service and shared workers we need to close the target and detach to allow
// the worker to stop.
await this.client.connection()?.send('Target.closeTarget', {
targetId: this.#id,
});
await this.client.connection()?.send('Target.detachFromTarget', {
sessionId: this.client.id(),
});
break;
}
default:
await this.evaluate(() => {
self.close();
});
}
}
}

View File

@ -1234,6 +1234,20 @@
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[target.spec] Target should close a service worker",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"],
"comment": "not supported"
},
{
"testIdPattern": "[target.spec] Target should close a shared worker",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"],
"comment": "not supported"
},
{
"testIdPattern": "[target.spec] Target should contain browser target",
"platforms": ["darwin", "linux", "win32"],
@ -4290,6 +4304,20 @@
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[target.spec] Target should close a service worker",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"],
"comment": "not supported"
},
{
"testIdPattern": "[target.spec] Target should close a shared worker",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"],
"comment": "not supported"
},
{
"testIdPattern": "[target.spec] Target should contain browser target",
"platforms": ["darwin", "linux", "win32"],
@ -4735,6 +4763,13 @@
"parameters": ["cdp", "firefox", "headless"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[target.spec] Target should close a service worker",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "chrome", "chrome-headless-shell"],
"expectations": ["SKIP"],
"comment": "For some reason service_workers do not close in chrome-headless-shell"
},
{
"testIdPattern": "[worker.spec] Workers Page.workers",
"platforms": ["darwin", "linux", "win32"],

View File

@ -179,6 +179,29 @@ describe('Target', function () {
})
).toBe('[object ServiceWorkerGlobalScope]');
});
it('should close a service worker', async () => {
const {page, server, context} = await getTestState();
await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
const target = await context.waitForTarget(
target => {
return target.type() === 'service_worker';
},
{timeout: 3000}
);
const worker = (await target.worker())!;
const onceDestroyed = new Promise(resolve => {
context.once('targetdestroyed', event => {
resolve(event);
});
});
await worker.close();
expect(await onceDestroyed).toBe(target);
});
it('should create a worker from a shared worker', async () => {
const {page, server, context} = await getTestState();
@ -199,6 +222,31 @@ describe('Target', function () {
})
).toBe('[object SharedWorkerGlobalScope]');
});
it('should close a shared worker', async () => {
const {page, server, context} = await getTestState();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
new SharedWorker('data:text/javascript,console.log("hi2")');
});
const target = await context.waitForTarget(
target => {
return target.type() === 'shared_worker';
},
{timeout: 3000}
);
const worker = (await target.worker())!;
const onceDestroyed = new Promise(resolve => {
context.once('targetdestroyed', event => {
resolve(event);
});
});
await worker.close();
expect(await onceDestroyed).toBe(target);
});
it('should report when a target url changes', async () => {
const {page, server, context} = await getTestState();

View File

@ -106,4 +106,17 @@ describe('Workers', function () {
const errorLog = await errorPromise;
expect(errorLog.message).toContain('this is my error');
});
it('can be closed', async () => {
const {page, server} = await getTestState();
await Promise.all([
waitEvent(page, 'workercreated'),
page.goto(server.PREFIX + '/worker/worker.html'),
]);
const worker = page.workers()[0]!;
expect(worker?.url()).toContain('worker.js');
await Promise.all([waitEvent(page, 'workerdestroyed'), worker?.close()]);
});
});