diff options
Diffstat (limited to 'remote/test/puppeteer/src/common/JSHandle.ts')
-rw-r--r-- | remote/test/puppeteer/src/common/JSHandle.ts | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/remote/test/puppeteer/src/common/JSHandle.ts b/remote/test/puppeteer/src/common/JSHandle.ts new file mode 100644 index 0000000000..55613eb610 --- /dev/null +++ b/remote/test/puppeteer/src/common/JSHandle.ts @@ -0,0 +1,332 @@ +/** + * Copyright 2019 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 {Protocol} from 'devtools-protocol'; +import {assert} from '../util/assert.js'; +import {CDPSession} from './Connection.js'; +import type {ElementHandle} from './ElementHandle.js'; +import {ExecutionContext} from './ExecutionContext.js'; +import {MouseButton} from './Input.js'; +import {EvaluateFunc, HandleFor, HandleOr} from './types.js'; +import {createJSHandle, releaseObject, valueFromRemoteObject} from './util.js'; + +declare const __JSHandleSymbol: unique symbol; + +/** + * @public + */ +export interface BoxModel { + content: Point[]; + padding: Point[]; + border: Point[]; + margin: Point[]; + width: number; + height: number; +} + +/** + * @public + */ +export interface BoundingBox extends Point { + /** + * the width of the element in pixels. + */ + width: number; + /** + * the height of the element in pixels. + */ + height: number; +} + +/** + * Represents a reference to a JavaScript object. Instances can be created using + * {@link Page.evaluateHandle}. + * + * Handles prevent the referenced JavaScript object from being garbage-collected + * unless the handle is purposely {@link JSHandle.dispose | disposed}. JSHandles + * are auto-disposed when their associated frame is navigated away or the parent + * context gets destroyed. + * + * Handles can be used as arguments for any evaluation function such as + * {@link Page.$eval}, {@link Page.evaluate}, and {@link Page.evaluateHandle}. + * They are resolved to their referenced object. + * + * @example + * + * ```ts + * const windowHandle = await page.evaluateHandle(() => window); + * ``` + * + * @public + */ +export class JSHandle<T = unknown> { + /** + * Used for nominally typing {@link JSHandle}. + */ + [__JSHandleSymbol]?: T; + + #disposed = false; + #context: ExecutionContext; + #remoteObject: Protocol.Runtime.RemoteObject; + + /** + * @internal + */ + get client(): CDPSession { + return this.#context._client; + } + + /** + * @internal + */ + get disposed(): boolean { + return this.#disposed; + } + + /** + * @internal + */ + constructor( + context: ExecutionContext, + remoteObject: Protocol.Runtime.RemoteObject + ) { + this.#context = context; + this.#remoteObject = remoteObject; + } + + /** + * @internal + */ + executionContext(): ExecutionContext { + return this.#context; + } + + /** + * Evaluates the given function with the current handle as its first argument. + * + * @see {@link ExecutionContext.evaluate} for more details. + */ + async evaluate< + Params extends unknown[], + Func extends EvaluateFunc<[this, ...Params]> = EvaluateFunc< + [this, ...Params] + > + >( + pageFunction: Func | string, + ...args: Params + ): // @ts-expect-error Circularity here is okay because we only need the return + // type which doesn't use `this`. + Promise<Awaited<ReturnType<Func>>> { + return await this.executionContext().evaluate(pageFunction, this, ...args); + } + + /** + * Evaluates the given function with the current handle as its first argument. + * + * @see {@link ExecutionContext.evaluateHandle} for more details. + */ + async evaluateHandle< + Params extends unknown[], + Func extends EvaluateFunc<[this, ...Params]> = EvaluateFunc< + [this, ...Params] + > + >( + pageFunction: Func | string, + ...args: Params + ): // @ts-expect-error Circularity here is okay because we only need the return + // type which doesn't use `this`. + Promise<HandleFor<Awaited<ReturnType<Func>>>> { + return await this.executionContext().evaluateHandle( + pageFunction, + this, + ...args + ); + } + + /** + * Fetches a single property from the referenced object. + */ + async getProperty<K extends keyof T>( + propertyName: HandleOr<K> + ): Promise<HandleFor<T[K]>>; + async getProperty(propertyName: string): Promise<JSHandle<unknown>>; + async getProperty<K extends keyof T>( + propertyName: HandleOr<K> + ): Promise<HandleFor<T[K]>> { + return this.evaluateHandle((object, propertyName) => { + return object[propertyName]; + }, propertyName); + } + + /** + * Gets a map of handles representing the properties of the current handle. + * + * @example + * + * ```ts + * const listHandle = await page.evaluateHandle(() => document.body.children); + * const properties = await listHandle.getProperties(); + * const children = []; + * for (const property of properties.values()) { + * const element = property.asElement(); + * if (element) { + * children.push(element); + * } + * } + * children; // holds elementHandles to all children of document.body + * ``` + */ + async getProperties(): Promise<Map<string, JSHandle>> { + assert(this.#remoteObject.objectId); + // We use Runtime.getProperties rather than iterative building because the + // iterative approach might create a distorted snapshot. + const response = await this.client.send('Runtime.getProperties', { + objectId: this.#remoteObject.objectId, + ownProperties: true, + }); + const result = new Map<string, JSHandle>(); + for (const property of response.result) { + if (!property.enumerable || !property.value) { + continue; + } + result.set(property.name, createJSHandle(this.#context, property.value)); + } + return result; + } + + /** + * @returns A vanilla object representing the serializable portions of the + * referenced object. + * @throws Throws if the object cannot be serialized due to circularity. + * + * @remarks + * If the object has a `toJSON` function, it **will not** be called. + */ + async jsonValue(): Promise<T> { + if (!this.#remoteObject.objectId) { + return valueFromRemoteObject(this.#remoteObject); + } + const value = await this.evaluate(object => { + return object; + }); + if (value === undefined) { + throw new Error('Could not serialize referenced object'); + } + return value; + } + + /** + * @returns Either `null` or the handle itself if the handle is an + * instance of {@link ElementHandle}. + */ + asElement(): ElementHandle<Node> | null { + return null; + } + + /** + * Releases the object referenced by the handle for garbage collection. + */ + async dispose(): Promise<void> { + if (this.#disposed) { + return; + } + this.#disposed = true; + await releaseObject(this.client, this.#remoteObject); + } + + /** + * Returns a string representation of the JSHandle. + * + * @remarks + * Useful during debugging. + */ + toString(): string { + if (!this.#remoteObject.objectId) { + return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject); + } + const type = this.#remoteObject.subtype || this.#remoteObject.type; + return 'JSHandle@' + type; + } + + /** + * Provides access to the + * [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject) + * backing this handle. + */ + remoteObject(): Protocol.Runtime.RemoteObject { + return this.#remoteObject; + } +} + +/** + * @public + */ +export interface Offset { + /** + * x-offset for the clickable point relative to the top-left corner of the border box. + */ + x: number; + /** + * y-offset for the clickable point relative to the top-left corner of the border box. + */ + y: number; +} + +/** + * @public + */ +export interface ClickOptions { + /** + * Time to wait between `mousedown` and `mouseup` in milliseconds. + * + * @defaultValue 0 + */ + delay?: number; + /** + * @defaultValue 'left' + */ + button?: MouseButton; + /** + * @defaultValue 1 + */ + clickCount?: number; + /** + * Offset for the clickable point relative to the top-left corner of the border box. + */ + offset?: Offset; +} + +/** + * @public + */ +export interface PressOptions { + /** + * Time to wait between `keydown` and `keyup` in milliseconds. Defaults to 0. + */ + delay?: number; + /** + * If specified, generates an input event with this text. + */ + text?: string; +} + +/** + * @public + */ +export interface Point { + x: number; + y: number; +} |