diff --git a/docs/api/index.md b/docs/api/index.md index e2a5bddc1d8..b3aa0bd6802 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -106,6 +106,7 @@ sidebar_label: API | [MouseMoveOptions](./puppeteer.mousemoveoptions.md) | | | [MouseOptions](./puppeteer.mouseoptions.md) | | | [MouseWheelOptions](./puppeteer.mousewheeloptions.md) | | +| [Moveable](./puppeteer.moveable.md) | | | [NetworkConditions](./puppeteer.networkconditions.md) | | | [NewDocumentScriptEvaluation](./puppeteer.newdocumentscriptevaluation.md) | | | [Offset](./puppeteer.offset.md) | | diff --git a/docs/api/puppeteer.elementhandle._symbol.asyncdispose_.md b/docs/api/puppeteer.elementhandle._symbol.asyncdispose_.md deleted file mode 100644 index e1302ac793a..00000000000 --- a/docs/api/puppeteer.elementhandle._symbol.asyncdispose_.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -sidebar_label: ElementHandle.[Symbol.asyncDispose] ---- - -# ElementHandle.\[Symbol.asyncDispose\]() method - -#### Signature: - -```typescript -class ElementHandle { - [Symbol.asyncDispose](): Promise; -} -``` - -**Returns:** - -Promise<void> diff --git a/docs/api/puppeteer.elementhandle._symbol.dispose_.md b/docs/api/puppeteer.elementhandle._symbol.dispose_.md deleted file mode 100644 index 8865139357c..00000000000 --- a/docs/api/puppeteer.elementhandle._symbol.dispose_.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -sidebar_label: ElementHandle.[Symbol.dispose] ---- - -# ElementHandle.\[Symbol.dispose\]() method - -#### Signature: - -```typescript -class ElementHandle { - [Symbol.dispose](): void; -} -``` - -**Returns:** - -void diff --git a/docs/api/puppeteer.elementhandle.md b/docs/api/puppeteer.elementhandle.md index 52099ddfa05..e30a42bf300 100644 --- a/docs/api/puppeteer.elementhandle.md +++ b/docs/api/puppeteer.elementhandle.md @@ -49,8 +49,6 @@ The constructor for this class is marked as internal. Third-party code should no | Method | Modifiers | Description | | -------------------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [\[Symbol.asyncDispose\]()](./puppeteer.elementhandle._symbol.asyncdispose_.md) | | | -| [\[Symbol.dispose\]()](./puppeteer.elementhandle._symbol.dispose_.md) | | | | [$(selector)](./puppeteer.elementhandle._.md) | | Queries the current element for an element matching the given selector. | | [$$(selector)](./puppeteer.elementhandle.__.md) | | Queries the current element for all elements matching the given selector. | | [$$eval(selector, pageFunction, args)](./puppeteer.elementhandle.__eval.md) | |

Runs the given function on an array of elements matching the given selector in the current element.

If the given function returns a promise, then this method will wait till the promise resolves.

| diff --git a/docs/api/puppeteer.jshandle.md b/docs/api/puppeteer.jshandle.md index cd1c7ecf657..f24c2207ebb 100644 --- a/docs/api/puppeteer.jshandle.md +++ b/docs/api/puppeteer.jshandle.md @@ -13,10 +13,10 @@ Handles can be used as arguments for any evaluation function such as [Page.$eval #### Signature: ```typescript -export declare abstract class JSHandle implements Disposable, AsyncDisposable +export declare abstract class JSHandle implements Disposable, AsyncDisposable, Moveable ``` -**Implements:** Disposable, AsyncDisposable +**Implements:** Disposable, AsyncDisposable, [Moveable](./puppeteer.moveable.md) ## Remarks @@ -30,9 +30,10 @@ const windowHandle = await page.evaluateHandle(() => window); ## Properties -| Property | Modifiers | Type | Description | -| -------- | --------------------- | ---- | -------------------------------------------------------------- | -| \_ | optional | T | Used for nominally typing [JSHandle](./puppeteer.jshandle.md). | +| Property | Modifiers | Type | Description | +| -------- | --------------------- | ------------- | -------------------------------------------------------------- | +| \_ | optional | T | Used for nominally typing [JSHandle](./puppeteer.jshandle.md). | +| move | | () => this | | ## Methods diff --git a/docs/api/puppeteer.moveable.md b/docs/api/puppeteer.moveable.md new file mode 100644 index 00000000000..3aebec4269c --- /dev/null +++ b/docs/api/puppeteer.moveable.md @@ -0,0 +1,17 @@ +--- +sidebar_label: Moveable +--- + +# Moveable interface + +#### Signature: + +```typescript +export interface Moveable +``` + +## Methods + +| Method | Description | +| -------------------------------------- | -------------------------------- | +| [move()](./puppeteer.moveable.move.md) | Moves the resource when 'using'. | diff --git a/docs/api/puppeteer.moveable.move.md b/docs/api/puppeteer.moveable.move.md new file mode 100644 index 00000000000..fdea997c79f --- /dev/null +++ b/docs/api/puppeteer.moveable.move.md @@ -0,0 +1,19 @@ +--- +sidebar_label: Moveable.move +--- + +# Moveable.move() method + +Moves the resource when 'using'. + +#### Signature: + +```typescript +interface Moveable { + move(): this; +} +``` + +**Returns:** + +this diff --git a/packages/puppeteer-core/src/api/ElementHandle.ts b/packages/puppeteer-core/src/api/ElementHandle.ts index dbac3dda7c2..1f5244ec6b9 100644 --- a/packages/puppeteer-core/src/api/ElementHandle.ts +++ b/packages/puppeteer-core/src/api/ElementHandle.ts @@ -140,7 +140,6 @@ export interface Point { * * @public */ - export abstract class ElementHandle< ElementType extends Node = Element, > extends JSHandle { @@ -1323,14 +1322,6 @@ export abstract class ElementHandle< * ``` */ abstract autofill(data: AutofillData): Promise; - - override [Symbol.dispose](): void { - return void this.dispose().catch(debugError); - } - - override [Symbol.asyncDispose](): Promise { - return this.dispose(); - } } /** diff --git a/packages/puppeteer-core/src/api/JSHandle.ts b/packages/puppeteer-core/src/api/JSHandle.ts index ef7f09affe1..cbfebd247da 100644 --- a/packages/puppeteer-core/src/api/JSHandle.ts +++ b/packages/puppeteer-core/src/api/JSHandle.ts @@ -17,8 +17,14 @@ import Protocol from 'devtools-protocol'; import {Symbol} from '../../third_party/disposablestack/disposablestack.js'; -import {EvaluateFuncWith, HandleFor, HandleOr} from '../common/types.js'; +import { + EvaluateFuncWith, + HandleFor, + HandleOr, + Moveable, +} from '../common/types.js'; import {debugError} from '../common/util.js'; +import {moveable} from '../util/decorators.js'; import {ElementHandle} from './ElementHandle.js'; @@ -43,9 +49,12 @@ import {ElementHandle} from './ElementHandle.js'; * * @public */ +@moveable export abstract class JSHandle - implements Disposable, AsyncDisposable + implements Disposable, AsyncDisposable, Moveable { + declare move: () => this; + /** * Used for nominally typing {@link JSHandle}. */ diff --git a/packages/puppeteer-core/src/common/types.ts b/packages/puppeteer-core/src/common/types.ts index a5d35b52088..cf861cd6be2 100644 --- a/packages/puppeteer-core/src/common/types.ts +++ b/packages/puppeteer-core/src/common/types.ts @@ -19,6 +19,16 @@ import type {JSHandle} from '../api/JSHandle.js'; import type {LazyArg} from './LazyArg.js'; +/** + * @public + */ +export interface Moveable { + /** + * Moves the resource when 'using'. + */ + move(): this; +} + /** * @internal */ diff --git a/packages/puppeteer-core/src/util/decorators.ts b/packages/puppeteer-core/src/util/decorators.ts new file mode 100644 index 00000000000..f7d201f8f07 --- /dev/null +++ b/packages/puppeteer-core/src/util/decorators.ts @@ -0,0 +1,59 @@ +/** + * 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. + */ + +import {Symbol} from '../../third_party/disposablestack/disposablestack.js'; +import {Moveable} from '../common/types.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 (instances.has(this)) { + instances.delete(this); + return; + } + return dispose.call(this); + }; + hasDispose = true; + } + if (Class.prototype[Symbol.asyncDispose]) { + const asyncDispose = Class.prototype[Symbol.asyncDispose]; + Class.prototype[Symbol.asyncDispose] = function ( + this: InstanceType + ) { + if (instances.has(this)) { + instances.delete(this); + return; + } + return asyncDispose.call(this); + }; + hasDispose = true; + } + if (hasDispose) { + Class.prototype.move = function ( + this: InstanceType + ): InstanceType { + instances.add(this); + return this; + }; + } + return Class; +} diff --git a/test/src/elementhandle.spec.ts b/test/src/elementhandle.spec.ts index 72c6d29c7b7..77dc14481b3 100644 --- a/test/src/elementhandle.spec.ts +++ b/test/src/elementhandle.spec.ts @@ -904,7 +904,7 @@ describe('ElementHandle specs', function () { }); }); - describe('Element.toElement', () => { + describe('ElementHandle.toElement', () => { it('should work', async () => { const {page} = await getTestState(); await page.setContent('
Foo1
'); @@ -913,4 +913,47 @@ describe('ElementHandle specs', function () { expect(div).toBeDefined(); }); }); + + describe('ElementHandle[Symbol.dispose]', () => { + it('should work', async () => { + const {page} = await getTestState(); + const handle = await page.evaluateHandle('document'); + const spy = sinon.spy(handle, Symbol.dispose); + { + using _ = handle; + } + expect(handle).toBeInstanceOf(ElementHandle); + expect(spy.calledOnce).toBeTruthy(); + expect(handle.disposed).toBeTruthy(); + }); + }); + + describe('ElementHandle[Symbol.asyncDispose]', () => { + it('should work', async () => { + const {page} = await getTestState(); + const handle = await page.evaluateHandle('document'); + const spy = sinon.spy(handle, Symbol.asyncDispose); + { + await using _ = handle; + } + expect(handle).toBeInstanceOf(ElementHandle); + expect(spy.calledOnce).toBeTruthy(); + expect(handle.disposed).toBeTruthy(); + }); + }); + + describe('ElementHandle.move', () => { + it('should work', async () => { + const {page} = await getTestState(); + const handle = await page.evaluateHandle('document'); + const spy = sinon.spy(handle, Symbol.dispose); + { + using _ = handle; + handle.move(); + } + expect(handle).toBeInstanceOf(ElementHandle); + expect(spy.calledOnce).toBeTruthy(); + expect(handle.disposed).toBeFalsy(); + }); + }); }); diff --git a/test/src/jshandle.spec.ts b/test/src/jshandle.spec.ts index 35040bad665..6d0ce33fa4d 100644 --- a/test/src/jshandle.spec.ts +++ b/test/src/jshandle.spec.ts @@ -15,6 +15,8 @@ */ import expect from 'expect'; +import {JSHandle} from 'puppeteer-core/internal/api/JSHandle.js'; +import sinon from 'sinon'; import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; @@ -331,4 +333,47 @@ describe('JSHandle', function () { ); }); }); + + describe('JSHandle[Symbol.dispose]', () => { + it('should work', async () => { + const {page} = await getTestState(); + const handle = await page.evaluateHandle('new Set()'); + const spy = sinon.spy(handle, Symbol.dispose); + { + using _ = handle; + } + expect(handle).toBeInstanceOf(JSHandle); + expect(spy.calledOnce).toBeTruthy(); + expect(handle.disposed).toBeTruthy(); + }); + }); + + describe('JSHandle[Symbol.asyncDispose]', () => { + it('should work', async () => { + const {page} = await getTestState(); + const handle = await page.evaluateHandle('new Set()'); + const spy = sinon.spy(handle, Symbol.asyncDispose); + { + await using _ = handle; + } + expect(handle).toBeInstanceOf(JSHandle); + expect(spy.calledOnce).toBeTruthy(); + expect(handle.disposed).toBeTruthy(); + }); + }); + + describe('JSHandle.move', () => { + it('should work', async () => { + const {page} = await getTestState(); + const handle = await page.evaluateHandle('new Set()'); + const spy = sinon.spy(handle, Symbol.dispose); + { + using _ = handle; + handle.move(); + } + expect(handle).toBeInstanceOf(JSHandle); + expect(spy.calledOnce).toBeTruthy(); + expect(handle.disposed).toBeFalsy(); + }); + }); });