From 164bdd29b01bda0c6bbc5b481e130b3fba09865c Mon Sep 17 00:00:00 2001 From: jrandolf <101637635+jrandolf@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:13:51 +0200 Subject: [PATCH] chore: use custom disposable stack (#10943) --- packages/puppeteer-core/package.json | 1 - packages/puppeteer-core/src/api/Browser.ts | 6 +- .../puppeteer-core/src/api/BrowserContext.ts | 5 +- packages/puppeteer-core/src/api/JSHandle.ts | 6 +- packages/puppeteer-core/src/api/Page.ts | 5 +- packages/puppeteer-core/src/api/Realm.ts | 3 +- packages/puppeteer-core/src/bidi/Frame.ts | 7 +- .../puppeteer-core/src/bidi/NetworkManager.ts | 1 + packages/puppeteer-core/src/bidi/Page.ts | 3 +- packages/puppeteer-core/src/bidi/Realm.ts | 3 +- packages/puppeteer-core/src/cdp/Binding.ts | 1 + packages/puppeteer-core/src/cdp/Coverage.ts | 1 + packages/puppeteer-core/src/cdp/Frame.ts | 7 +- .../puppeteer-core/src/cdp/FrameManager.ts | 3 +- .../puppeteer-core/src/cdp/IsolatedWorld.ts | 5 +- .../src/cdp/LifecycleWatcher.ts | 1 + .../puppeteer-core/src/cdp/NetworkManager.ts | 5 +- .../puppeteer-core/src/common/EventEmitter.ts | 4 +- .../src/common/HandleIterator.ts | 3 +- packages/puppeteer-core/src/common/util.ts | 3 +- .../puppeteer-core/src/node/PipeTransport.ts | 1 + .../puppeteer-core/src/util/decorators.ts | 17 +- .../puppeteer-core/src/util/disposable.ts | 169 ++++++++++++++++ .../disposablestack/disposablestack.ts | 191 ------------------ test/src/elementhandle.spec.ts | 10 +- test/src/jshandle.spec.ts | 10 +- 26 files changed, 236 insertions(+), 235 deletions(-) create mode 100644 packages/puppeteer-core/src/util/disposable.ts delete mode 100644 packages/puppeteer-core/third_party/disposablestack/disposablestack.ts diff --git a/packages/puppeteer-core/package.json b/packages/puppeteer-core/package.json index 39927a97..50448be2 100644 --- a/packages/puppeteer-core/package.json +++ b/packages/puppeteer-core/package.json @@ -148,7 +148,6 @@ "ws": "8.14.1" }, "devDependencies": { - "disposablestack": "1.1.1", "mitt": "3.0.1", "parsel-js": "1.1.2", "rxjs": "7.8.1" diff --git a/packages/puppeteer-core/src/api/Browser.ts b/packages/puppeteer-core/src/api/Browser.ts index 8c3b22b9..214f950e 100644 --- a/packages/puppeteer-core/src/api/Browser.ts +++ b/packages/puppeteer-core/src/api/Browser.ts @@ -18,10 +18,10 @@ import {type ChildProcess} from 'child_process'; import {type Protocol} from 'devtools-protocol'; -import {Symbol} from '../../third_party/disposablestack/disposablestack.js'; import {EventEmitter, type EventType} from '../common/EventEmitter.js'; import {debugError, waitWithTimeout} from '../common/util.js'; import {Deferred} from '../util/Deferred.js'; +import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js'; import type {BrowserContext} from './BrowserContext.js'; import type {Page} from './Page.js'; @@ -488,12 +488,12 @@ export abstract class Browser extends EventEmitter { abstract get connected(): boolean; /** @internal */ - [Symbol.dispose](): void { + [disposeSymbol](): void { return void this.close().catch(debugError); } /** @internal */ - [Symbol.asyncDispose](): Promise { + [asyncDisposeSymbol](): Promise { return this.close(); } } diff --git a/packages/puppeteer-core/src/api/BrowserContext.ts b/packages/puppeteer-core/src/api/BrowserContext.ts index 4d27f8e9..8d559852 100644 --- a/packages/puppeteer-core/src/api/BrowserContext.ts +++ b/packages/puppeteer-core/src/api/BrowserContext.ts @@ -16,6 +16,7 @@ import {EventEmitter, type EventType} from '../common/EventEmitter.js'; import {debugError} from '../common/util.js'; +import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js'; import type {Browser, Permission} from './Browser.js'; import {type Page} from './Page.js'; @@ -226,12 +227,12 @@ export abstract class BrowserContext extends EventEmitter } /** @internal */ - [Symbol.dispose](): void { + [disposeSymbol](): void { return void this.close().catch(debugError); } /** @internal */ - [Symbol.asyncDispose](): Promise { + [asyncDisposeSymbol](): Promise { return this.close(); } } diff --git a/packages/puppeteer-core/src/api/JSHandle.ts b/packages/puppeteer-core/src/api/JSHandle.ts index 1efb2504..5ace8bff 100644 --- a/packages/puppeteer-core/src/api/JSHandle.ts +++ b/packages/puppeteer-core/src/api/JSHandle.ts @@ -16,7 +16,6 @@ import type Protocol from 'devtools-protocol'; -import {Symbol} from '../../third_party/disposablestack/disposablestack.js'; import { type EvaluateFuncWith, type HandleFor, @@ -24,6 +23,7 @@ import { } from '../common/types.js'; import {debugError, withSourcePuppeteerURLIfNone} from '../common/util.js'; import {moveable, throwIfDisposed} from '../util/decorators.js'; +import {disposeSymbol, asyncDisposeSymbol} from '../util/disposable.js'; import {type ElementHandle} from './ElementHandle.js'; import {type Realm} from './Realm.js'; @@ -217,12 +217,12 @@ export abstract class JSHandle { abstract remoteObject(): Protocol.Runtime.RemoteObject; /** @internal */ - [Symbol.dispose](): void { + [disposeSymbol](): void { return void this.dispose().catch(debugError); } /** @internal */ - [Symbol.asyncDispose](): Promise { + [asyncDisposeSymbol](): Promise { return this.dispose(); } } diff --git a/packages/puppeteer-core/src/api/Page.ts b/packages/puppeteer-core/src/api/Page.ts index cffe72a5..eeb75273 100644 --- a/packages/puppeteer-core/src/api/Page.ts +++ b/packages/puppeteer-core/src/api/Page.ts @@ -82,6 +82,7 @@ import { import type {Viewport} from '../common/Viewport.js'; import {assert} from '../util/assert.js'; import {type Deferred} from '../util/Deferred.js'; +import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js'; import type {Browser} from './Browser.js'; import type {BrowserContext} from './BrowserContext.js'; @@ -2853,12 +2854,12 @@ export abstract class Page extends EventEmitter { } /** @internal */ - [Symbol.dispose](): void { + [disposeSymbol](): void { return void this.close().catch(debugError); } /** @internal */ - [Symbol.asyncDispose](): Promise { + [asyncDisposeSymbol](): Promise { return this.close(); } } diff --git a/packages/puppeteer-core/src/api/Realm.ts b/packages/puppeteer-core/src/api/Realm.ts index 07af4332..64e7f589 100644 --- a/packages/puppeteer-core/src/api/Realm.ts +++ b/packages/puppeteer-core/src/api/Realm.ts @@ -21,6 +21,7 @@ import { type InnerLazyParams, } from '../common/types.js'; import {TaskManager, WaitTask} from '../common/WaitTask.js'; +import {disposeSymbol} from '../util/disposable.js'; import {type ElementHandle} from './ElementHandle.js'; import {type Environment} from './Environment.js'; @@ -102,7 +103,7 @@ export abstract class Realm implements Disposable { #disposed = false; /** @internal */ - [Symbol.dispose](): void { + [disposeSymbol](): void { this.#disposed = true; this.taskManager.terminateAll( new Error('waitForFunction failed: frame got detached.') diff --git a/packages/puppeteer-core/src/bidi/Frame.ts b/packages/puppeteer-core/src/bidi/Frame.ts index 94381be1..fe83ce14 100644 --- a/packages/puppeteer-core/src/bidi/Frame.ts +++ b/packages/puppeteer-core/src/bidi/Frame.ts @@ -34,6 +34,7 @@ import { waitWithTimeout, } from '../common/util.js'; import {Deferred} from '../util/Deferred.js'; +import {disposeSymbol} from '../util/disposable.js'; import { getWaitUntilSingle, @@ -250,15 +251,15 @@ export class BidiFrame extends Frame { return this.#disposed; } - [Symbol.dispose](): void { + [disposeSymbol](): void { if (this.#disposed) { return; } this.#disposed = true; this.#abortDeferred.reject(new Error('Frame detached')); this.#context.dispose(); - this.sandboxes[MAIN_SANDBOX][Symbol.dispose](); - this.sandboxes[PUPPETEER_SANDBOX][Symbol.dispose](); + this.sandboxes[MAIN_SANDBOX][disposeSymbol](); + this.sandboxes[PUPPETEER_SANDBOX][disposeSymbol](); } #exposedFunctions = new Map>(); diff --git a/packages/puppeteer-core/src/bidi/NetworkManager.ts b/packages/puppeteer-core/src/bidi/NetworkManager.ts index 87c8cd6b..8af999d6 100644 --- a/packages/puppeteer-core/src/bidi/NetworkManager.ts +++ b/packages/puppeteer-core/src/bidi/NetworkManager.ts @@ -22,6 +22,7 @@ import { EventSubscription, type EventType, } from '../common/EventEmitter.js'; +import {DisposableStack} from '../util/disposable.js'; import {type BidiConnection} from './Connection.js'; import {type BidiFrame} from './Frame.js'; diff --git a/packages/puppeteer-core/src/bidi/Page.ts b/packages/puppeteer-core/src/bidi/Page.ts index b6c99ac8..619c4e44 100644 --- a/packages/puppeteer-core/src/bidi/Page.ts +++ b/packages/puppeteer-core/src/bidi/Page.ts @@ -59,6 +59,7 @@ import { import {type Viewport} from '../common/Viewport.js'; import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; +import {disposeSymbol} from '../util/disposable.js'; import {type BidiBrowser} from './Browser.js'; import {type BidiBrowserContext} from './BrowserContext.js'; @@ -314,7 +315,7 @@ export class BidiPage extends Page { for (const child of frame.childFrames()) { this.#removeFramesRecursively(child); } - frame[Symbol.dispose](); + frame[disposeSymbol](); this.#networkManager.clearMapAfterFrameDispose(frame); this.#frameTree.removeFrame(frame); this.emit(PageEvent.FrameDetached, frame); diff --git a/packages/puppeteer-core/src/bidi/Realm.ts b/packages/puppeteer-core/src/bidi/Realm.ts index cbf6b37f..cce4fd5f 100644 --- a/packages/puppeteer-core/src/bidi/Realm.ts +++ b/packages/puppeteer-core/src/bidi/Realm.ts @@ -11,6 +11,7 @@ import { isString, } from '../common/util.js'; import type PuppeteerUtil from '../injected/injected.js'; +import {disposeSymbol} from '../util/disposable.js'; import {stringifyFunction} from '../util/Function.js'; import {type BidiConnection} from './Connection.js'; @@ -198,7 +199,7 @@ export class BidiRealm extends EventEmitter> { : createBidiHandle(sandbox, result.result); } - [Symbol.dispose](): void { + [disposeSymbol](): void { this.connection.off( Bidi.ChromiumBidi.Script.EventNames.RealmCreated, this.handleRealmCreated diff --git a/packages/puppeteer-core/src/cdp/Binding.ts b/packages/puppeteer-core/src/cdp/Binding.ts index 0231fb4c..fa3240d9 100644 --- a/packages/puppeteer-core/src/cdp/Binding.ts +++ b/packages/puppeteer-core/src/cdp/Binding.ts @@ -1,5 +1,6 @@ import {JSHandle} from '../api/JSHandle.js'; import {debugError} from '../common/util.js'; +import {DisposableStack} from '../util/disposable.js'; import {isErrorLike} from '../util/ErrorLike.js'; import {type ExecutionContext} from './ExecutionContext.js'; diff --git a/packages/puppeteer-core/src/cdp/Coverage.ts b/packages/puppeteer-core/src/cdp/Coverage.ts index 54dc508b..923fe3db 100644 --- a/packages/puppeteer-core/src/cdp/Coverage.ts +++ b/packages/puppeteer-core/src/cdp/Coverage.ts @@ -20,6 +20,7 @@ import {type CDPSession} from '../api/CDPSession.js'; import {EventSubscription} from '../common/EventEmitter.js'; import {debugError, PuppeteerURL} from '../common/util.js'; import {assert} from '../util/assert.js'; +import {DisposableStack} from '../util/disposable.js'; /** * The CoverageEntry class represents one entry of the coverage report. diff --git a/packages/puppeteer-core/src/cdp/Frame.ts b/packages/puppeteer-core/src/cdp/Frame.ts index 82ddba36..d260a458 100644 --- a/packages/puppeteer-core/src/cdp/Frame.ts +++ b/packages/puppeteer-core/src/cdp/Frame.ts @@ -23,6 +23,7 @@ import {type Page, type WaitTimeoutOptions} from '../api/Page.js'; import {setPageContent} from '../common/util.js'; import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; +import {disposeSymbol} from '../util/disposable.js'; import {isErrorLike} from '../util/ErrorLike.js'; import { @@ -338,12 +339,12 @@ export class CdpFrame extends Frame { return this.#detached; } - [Symbol.dispose](): void { + [disposeSymbol](): void { if (this.#detached) { return; } this.#detached = true; - this.worlds[MAIN_WORLD][Symbol.dispose](); - this.worlds[PUPPETEER_WORLD][Symbol.dispose](); + this.worlds[MAIN_WORLD][disposeSymbol](); + this.worlds[PUPPETEER_WORLD][disposeSymbol](); } } diff --git a/packages/puppeteer-core/src/cdp/FrameManager.ts b/packages/puppeteer-core/src/cdp/FrameManager.ts index e76e91cb..4d457438 100644 --- a/packages/puppeteer-core/src/cdp/FrameManager.ts +++ b/packages/puppeteer-core/src/cdp/FrameManager.ts @@ -24,6 +24,7 @@ import {type TimeoutSettings} from '../common/TimeoutSettings.js'; import {debugError, PuppeteerURL, UTILITY_WORLD_NAME} from '../common/util.js'; import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; +import {disposeSymbol} from '../util/disposable.js'; import {isErrorLike} from '../util/ErrorLike.js'; import {CdpCDPSession} from './CDPSession.js'; @@ -561,7 +562,7 @@ export class FrameManager extends EventEmitter { for (const child of frame.childFrames()) { this.#removeFramesRecursively(child); } - frame[Symbol.dispose](); + frame[disposeSymbol](); this._frameTree.removeFrame(frame); this.emit(FrameManagerEvent.FrameDetached, frame); frame.emit(FrameEvent.FrameDetached, frame); diff --git a/packages/puppeteer-core/src/cdp/IsolatedWorld.ts b/packages/puppeteer-core/src/cdp/IsolatedWorld.ts index fc6e6625..67b50ac5 100644 --- a/packages/puppeteer-core/src/cdp/IsolatedWorld.ts +++ b/packages/puppeteer-core/src/cdp/IsolatedWorld.ts @@ -32,6 +32,7 @@ import { withSourcePuppeteerURLIfNone, } from '../common/util.js'; import {Deferred} from '../util/Deferred.js'; +import {disposeSymbol} from '../util/disposable.js'; import {type Binding} from './Binding.js'; import {type ExecutionContext, createCdpHandle} from './ExecutionContext.js'; @@ -278,8 +279,8 @@ export class IsolatedWorld extends Realm { return newHandle; } - [Symbol.dispose](): void { - super[Symbol.dispose](); + [disposeSymbol](): void { + super[disposeSymbol](); this.client.off('Runtime.bindingCalled', this.#onBindingCalled); } } diff --git a/packages/puppeteer-core/src/cdp/LifecycleWatcher.ts b/packages/puppeteer-core/src/cdp/LifecycleWatcher.ts index d4290199..df926deb 100644 --- a/packages/puppeteer-core/src/cdp/LifecycleWatcher.ts +++ b/packages/puppeteer-core/src/cdp/LifecycleWatcher.ts @@ -22,6 +22,7 @@ import {type TimeoutError} from '../common/Errors.js'; import {EventSubscription} from '../common/EventEmitter.js'; import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; +import {DisposableStack} from '../util/disposable.js'; import {type CdpFrame} from './Frame.js'; import {FrameManagerEvent} from './FrameManager.js'; diff --git a/packages/puppeteer-core/src/cdp/NetworkManager.ts b/packages/puppeteer-core/src/cdp/NetworkManager.ts index 896ec3e7..689e4376 100644 --- a/packages/puppeteer-core/src/cdp/NetworkManager.ts +++ b/packages/puppeteer-core/src/cdp/NetworkManager.ts @@ -16,7 +16,7 @@ import type {Protocol} from 'devtools-protocol'; -import {type CDPSession, CDPSessionEvent} from '../api/CDPSession.js'; +import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js'; import type {Frame} from '../api/Frame.js'; import { EventEmitter, @@ -25,12 +25,13 @@ import { } from '../common/EventEmitter.js'; import {debugError, isString} from '../common/util.js'; import {assert} from '../util/assert.js'; +import {DisposableStack} from '../util/disposable.js'; import {CdpHTTPRequest} from './HTTPRequest.js'; import {CdpHTTPResponse} from './HTTPResponse.js'; import { - type FetchRequestId, NetworkEventManager, + type FetchRequestId, } from './NetworkEventManager.js'; /** diff --git a/packages/puppeteer-core/src/common/EventEmitter.ts b/packages/puppeteer-core/src/common/EventEmitter.ts index 3e3e1141..6a157d0a 100644 --- a/packages/puppeteer-core/src/common/EventEmitter.ts +++ b/packages/puppeteer-core/src/common/EventEmitter.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import {Symbol} from '../../third_party/disposablestack/disposablestack.js'; import mitt, { type Emitter, type EventHandlerMap, type EventType, } from '../../third_party/mitt/index.js'; +import {disposeSymbol} from '../util/disposable.js'; export { /** @@ -230,7 +230,7 @@ export class EventSubscription< this.#target.on(this.#type, this.#handler); } - [Symbol.dispose](): void { + [disposeSymbol](): void { this.#target.off(this.#type, this.#handler); } } diff --git a/packages/puppeteer-core/src/common/HandleIterator.ts b/packages/puppeteer-core/src/common/HandleIterator.ts index 05423f18..a1c479b4 100644 --- a/packages/puppeteer-core/src/common/HandleIterator.ts +++ b/packages/puppeteer-core/src/common/HandleIterator.ts @@ -15,6 +15,7 @@ */ import {type JSHandle} from '../api/JSHandle.js'; +import {DisposableStack, disposeSymbol} from '../util/disposable.js'; import {type AwaitableIterable, type HandleFor} from './types.js'; @@ -47,7 +48,7 @@ async function* fastTransposeIteratorHandle( using stack = new DisposableStack(); stack.defer(() => { for (using handle of handles) { - handle[Symbol.dispose](); + handle[disposeSymbol](); } }); yield* handles; diff --git a/packages/puppeteer-core/src/common/util.ts b/packages/puppeteer-core/src/common/util.ts index 9bcaf145..0b16a3db 100644 --- a/packages/puppeteer-core/src/common/util.ts +++ b/packages/puppeteer-core/src/common/util.ts @@ -29,6 +29,7 @@ import {type Page} from '../api/Page.js'; import {isNode} from '../environment.js'; import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; +import {disposeSymbol} from '../util/disposable.js'; import {isErrorLike} from '../util/ErrorLike.js'; import {debug} from './Debug.js'; @@ -614,7 +615,7 @@ export class Mutex { constructor(mutex: Mutex) { this.#mutex = mutex; } - [Symbol.dispose](): void { + [disposeSymbol](): void { return this.#mutex.release(); } }; diff --git a/packages/puppeteer-core/src/node/PipeTransport.ts b/packages/puppeteer-core/src/node/PipeTransport.ts index 3b709609..d186ec84 100644 --- a/packages/puppeteer-core/src/node/PipeTransport.ts +++ b/packages/puppeteer-core/src/node/PipeTransport.ts @@ -17,6 +17,7 @@ import {type ConnectionTransport} from '../common/ConnectionTransport.js'; import {EventSubscription} from '../common/EventEmitter.js'; import {debugError} from '../common/util.js'; import {assert} from '../util/assert.js'; +import {DisposableStack} from '../util/disposable.js'; /** * @internal diff --git a/packages/puppeteer-core/src/util/decorators.ts b/packages/puppeteer-core/src/util/decorators.ts index 84041742..f2c3a88a 100644 --- a/packages/puppeteer-core/src/util/decorators.ts +++ b/packages/puppeteer-core/src/util/decorators.ts @@ -14,18 +14,19 @@ * limitations under the License. */ -import {Symbol} from '../../third_party/disposablestack/disposablestack.js'; import {type Disposed, type Moveable} from '../common/types.js'; +import {asyncDisposeSymbol, disposeSymbol} from './disposable.js'; + const instances = new WeakSet(); export function moveable< Class extends abstract new (...args: never[]) => Moveable, >(Class: Class, _: ClassDecoratorContext): Class { let hasDispose = false; - if (Class.prototype[Symbol.dispose]) { - const dispose = Class.prototype[Symbol.dispose]; - Class.prototype[Symbol.dispose] = function (this: InstanceType) { + if (Class.prototype[disposeSymbol]) { + const dispose = Class.prototype[disposeSymbol]; + Class.prototype[disposeSymbol] = function (this: InstanceType) { if (instances.has(this)) { instances.delete(this); return; @@ -34,11 +35,9 @@ export function moveable< }; hasDispose = true; } - if (Class.prototype[Symbol.asyncDispose]) { - const asyncDispose = Class.prototype[Symbol.asyncDispose]; - Class.prototype[Symbol.asyncDispose] = function ( - this: InstanceType - ) { + if (Class.prototype[asyncDisposeSymbol]) { + const asyncDispose = Class.prototype[asyncDisposeSymbol]; + Class.prototype[asyncDisposeSymbol] = function (this: InstanceType) { if (instances.has(this)) { instances.delete(this); return; diff --git a/packages/puppeteer-core/src/util/disposable.ts b/packages/puppeteer-core/src/util/disposable.ts new file mode 100644 index 00000000..a0c0f2ad --- /dev/null +++ b/packages/puppeteer-core/src/util/disposable.ts @@ -0,0 +1,169 @@ +/** + * Copyright 2023 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare global { + interface SymbolConstructor { + /** + * A method that is used to release resources held by an object. Called by + * the semantics of the `using` statement. + */ + readonly dispose: unique symbol; + + /** + * A method that is used to asynchronously release resources held by an + * object. Called by the semantics of the `await using` statement. + */ + readonly asyncDispose: unique symbol; + } + + interface Disposable { + [Symbol.dispose](): void; + } + + interface AsyncDisposable { + [Symbol.asyncDispose](): PromiseLike; + } +} + +(Symbol as any).dispose ??= Symbol('dispose'); +(Symbol as any).asyncDispose ??= Symbol('asyncDispose'); + +/** + * @internal + */ +export const disposeSymbol: typeof Symbol.dispose = Symbol.dispose; + +/** + * @internal + */ +export const asyncDisposeSymbol: typeof Symbol.asyncDispose = + Symbol.asyncDispose; + +/** + * @internal + */ +export class DisposableStack { + #disposed = false; + #stack: Disposable[] = []; + + /** + * Returns a value indicating whether this stack has been disposed. + */ + get disposed(): boolean { + return this.#disposed; + } + + /** + * Disposes each resource in the stack in the reverse order that they were added. + */ + dispose(): void { + if (this.#disposed) { + return; + } + this.#disposed = true; + for (const resource of this.#stack.reverse()) { + resource[disposeSymbol](); + } + } + + /** + * Adds a disposable resource to the stack, returning the resource. + * + * @param value - The resource to add. `null` and `undefined` will not be added, + * but will be returned. + * @returns The provided {@link value}. + */ + use(value: T): T { + if (value) { + this.#stack.push(value); + } + return value; + } + + /** + * Adds a value and associated disposal callback as a resource to the stack. + * + * @param value - The value to add. + * @param onDispose - The callback to use in place of a `[disposeSymbol]()` + * method. Will be invoked with `value` as the first parameter. + * @returns The provided {@link value}. + */ + adopt(value: T, onDispose: (value: T) => void): T { + this.#stack.push({ + [disposeSymbol]() { + onDispose(value); + }, + }); + return value; + } + + /** + * Adds a callback to be invoked when the stack is disposed. + */ + defer(onDispose: () => void): void { + this.#stack.push({ + [disposeSymbol]() { + onDispose(); + }, + }); + } + + /** + * Move all resources out of this stack and into a new `DisposableStack`, and + * marks this stack as disposed. + * + * @example + * + * ```ts + * class C { + * #res1: Disposable; + * #res2: Disposable; + * #disposables: DisposableStack; + * constructor() { + * // stack will be disposed when exiting constructor for any reason + * using stack = new DisposableStack(); + * + * // get first resource + * this.#res1 = stack.use(getResource1()); + * + * // get second resource. If this fails, both `stack` and `#res1` will be disposed. + * this.#res2 = stack.use(getResource2()); + * + * // all operations succeeded, move resources out of `stack` so that + * // they aren't disposed when constructor exits + * this.#disposables = stack.move(); + * } + * + * [disposeSymbol]() { + * this.#disposables.dispose(); + * } + * } + * ``` + */ + move(): DisposableStack { + if (this.#disposed) { + throw new ReferenceError('a disposed stack can not use anything new'); // step 3 + } + const stack = new DisposableStack(); // step 4-5 + stack.#stack = this.#stack; + this.#disposed = true; + return stack; + } + + [disposeSymbol] = this.dispose; + + readonly [Symbol.toStringTag] = 'DisposableStack'; +} diff --git a/packages/puppeteer-core/third_party/disposablestack/disposablestack.ts b/packages/puppeteer-core/third_party/disposablestack/disposablestack.ts deleted file mode 100644 index 87412364..00000000 --- a/packages/puppeteer-core/third_party/disposablestack/disposablestack.ts +++ /dev/null @@ -1,191 +0,0 @@ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ - -import 'disposablestack/auto'; - -declare global { - interface SymbolConstructor { - /** - * A method that is used to release resources held by an object. Called by the semantics of the `using` statement. - */ - readonly dispose: unique symbol; - - /** - * A method that is used to asynchronously release resources held by an object. Called by the semantics of the `await using` statement. - */ - readonly asyncDispose: unique symbol; - } - - interface Disposable { - [Symbol.dispose](): void; - } - - interface AsyncDisposable { - [Symbol.asyncDispose](): PromiseLike; - } - - interface SuppressedError extends Error { - error: any; - suppressed: any; - } - - interface SuppressedErrorConstructor extends ErrorConstructor { - new (error: any, suppressed: any, message?: string): SuppressedError; - (error: any, suppressed: any, message?: string): SuppressedError; - readonly prototype: SuppressedError; - } - var SuppressedError: SuppressedErrorConstructor; - - interface DisposableStack { - /** - * Returns a value indicating whether this stack has been disposed. - */ - readonly disposed: boolean; - /** - * Disposes each resource in the stack in the reverse order that they were added. - */ - dispose(): void; - /** - * Adds a disposable resource to the stack, returning the resource. - * @param value The resource to add. `null` and `undefined` will not be added, but will be returned. - * @returns The provided {@link value}. - */ - use(value: T): T; - /** - * Adds a value and associated disposal callback as a resource to the stack. - * @param value The value to add. - * @param onDispose The callback to use in place of a `[Symbol.dispose]()` method. Will be invoked with `value` - * as the first parameter. - * @returns The provided {@link value}. - */ - adopt(value: T, onDispose: (value: T) => void): T; - /** - * Adds a callback to be invoked when the stack is disposed. - */ - defer(onDispose: () => void): void; - /** - * Move all resources out of this stack and into a new `DisposableStack`, and marks this stack as disposed. - * @example - * ```ts - * class C { - * #res1: Disposable; - * #res2: Disposable; - * #disposables: DisposableStack; - * constructor() { - * // stack will be disposed when exiting constructor for any reason - * using stack = new DisposableStack(); - * - * // get first resource - * this.#res1 = stack.use(getResource1()); - * - * // get second resource. If this fails, both `stack` and `#res1` will be disposed. - * this.#res2 = stack.use(getResource2()); - * - * // all operations succeeded, move resources out of `stack` so that they aren't disposed - * // when constructor exits - * this.#disposables = stack.move(); - * } - * - * [Symbol.dispose]() { - * this.#disposables.dispose(); - * } - * } - * ``` - */ - move(): DisposableStack; - [Symbol.dispose](): void; - readonly [Symbol.toStringTag]: string; - } - - interface DisposableStackConstructor { - new (): DisposableStack; - readonly prototype: DisposableStack; - } - var DisposableStack: DisposableStackConstructor; - - interface AsyncDisposableStack { - /** - * Returns a value indicating whether this stack has been disposed. - */ - readonly disposed: boolean; - /** - * Disposes each resource in the stack in the reverse order that they were added. - */ - disposeAsync(): Promise; - /** - * Adds a disposable resource to the stack, returning the resource. - * @param value The resource to add. `null` and `undefined` will not be added, but will be returned. - * @returns The provided {@link value}. - */ - use(value: T): T; - /** - * Adds a value and associated disposal callback as a resource to the stack. - * @param value The value to add. - * @param onDisposeAsync The callback to use in place of a `[Symbol.asyncDispose]()` method. Will be invoked with `value` - * as the first parameter. - * @returns The provided {@link value}. - */ - adopt( - value: T, - onDisposeAsync: (value: T) => PromiseLike | void - ): T; - /** - * Adds a callback to be invoked when the stack is disposed. - */ - defer(onDisposeAsync: () => PromiseLike | void): void; - /** - * Move all resources out of this stack and into a new `DisposableStack`, and marks this stack as disposed. - * @example - * ```ts - * class C { - * #res1: Disposable; - * #res2: Disposable; - * #disposables: DisposableStack; - * constructor() { - * // stack will be disposed when exiting constructor for any reason - * using stack = new DisposableStack(); - * - * // get first resource - * this.#res1 = stack.use(getResource1()); - * - * // get second resource. If this fails, both `stack` and `#res1` will be disposed. - * this.#res2 = stack.use(getResource2()); - * - * // all operations succeeded, move resources out of `stack` so that they aren't disposed - * // when constructor exits - * this.#disposables = stack.move(); - * } - * - * [Symbol.dispose]() { - * this.#disposables.dispose(); - * } - * } - * ``` - */ - move(): AsyncDisposableStack; - [Symbol.asyncDispose](): Promise; - readonly [Symbol.toStringTag]: string; - } - - interface AsyncDisposableStackConstructor { - new (): AsyncDisposableStack; - readonly prototype: AsyncDisposableStack; - } - var AsyncDisposableStack: AsyncDisposableStackConstructor; -} - -export const Symbol = globalThis.Symbol; -export const DisposableStack = globalThis.DisposableStack; -export const AsyncDisposableStack = globalThis.AsyncDisposableStack; diff --git a/test/src/elementhandle.spec.ts b/test/src/elementhandle.spec.ts index 6ff34cf9..e8a94b73 100644 --- a/test/src/elementhandle.spec.ts +++ b/test/src/elementhandle.spec.ts @@ -17,6 +17,10 @@ import expect from 'expect'; import {Puppeteer} from 'puppeteer'; import {ElementHandle} from 'puppeteer-core/internal/api/ElementHandle.js'; +import { + asyncDisposeSymbol, + disposeSymbol, +} from 'puppeteer-core/internal/util/disposable.js'; import sinon from 'sinon'; import { @@ -918,7 +922,7 @@ describe('ElementHandle specs', function () { it('should work', async () => { const {page} = await getTestState(); using handle = await page.evaluateHandle('document'); - const spy = sinon.spy(handle, Symbol.dispose); + const spy = sinon.spy(handle, disposeSymbol); { using _ = handle; } @@ -932,7 +936,7 @@ describe('ElementHandle specs', function () { it('should work', async () => { const {page} = await getTestState(); using handle = await page.evaluateHandle('document'); - const spy = sinon.spy(handle, Symbol.asyncDispose); + const spy = sinon.spy(handle, asyncDisposeSymbol); { await using _ = handle; } @@ -946,7 +950,7 @@ describe('ElementHandle specs', function () { it('should work', async () => { const {page} = await getTestState(); using handle = await page.evaluateHandle('document'); - const spy = sinon.spy(handle, Symbol.dispose); + const spy = sinon.spy(handle, disposeSymbol); { using _ = handle; handle.move(); diff --git a/test/src/jshandle.spec.ts b/test/src/jshandle.spec.ts index b8ca4fd2..5bfff885 100644 --- a/test/src/jshandle.spec.ts +++ b/test/src/jshandle.spec.ts @@ -16,6 +16,10 @@ import expect from 'expect'; import {JSHandle} from 'puppeteer-core/internal/api/JSHandle.js'; +import { + asyncDisposeSymbol, + disposeSymbol, +} from 'puppeteer-core/internal/util/disposable.js'; import sinon from 'sinon'; import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; @@ -338,7 +342,7 @@ describe('JSHandle', function () { it('should work', async () => { const {page} = await getTestState(); using handle = await page.evaluateHandle('new Set()'); - const spy = sinon.spy(handle, Symbol.dispose); + const spy = sinon.spy(handle, disposeSymbol); { using _ = handle; } @@ -352,7 +356,7 @@ describe('JSHandle', function () { it('should work', async () => { const {page} = await getTestState(); using handle = await page.evaluateHandle('new Set()'); - const spy = sinon.spy(handle, Symbol.asyncDispose); + const spy = sinon.spy(handle, asyncDisposeSymbol); { await using _ = handle; } @@ -366,7 +370,7 @@ describe('JSHandle', function () { it('should work', async () => { const {page} = await getTestState(); using handle = await page.evaluateHandle('new Set()'); - const spy = sinon.spy(handle, Symbol.dispose); + const spy = sinon.spy(handle, disposeSymbol); { using _ = handle; handle.move();