JSHandle.ts (3343B)
1 /** 2 * @license 3 * Copyright 2019 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import type {Protocol} from 'devtools-protocol'; 8 9 import type {CDPSession} from '../api/CDPSession.js'; 10 import {JSHandle} from '../api/JSHandle.js'; 11 import {debugError} from '../common/util.js'; 12 13 import type {CdpElementHandle} from './ElementHandle.js'; 14 import type {IsolatedWorld} from './IsolatedWorld.js'; 15 import {valueFromRemoteObject} from './utils.js'; 16 17 /** 18 * @internal 19 */ 20 export class CdpJSHandle<T = unknown> extends JSHandle<T> { 21 #disposed = false; 22 readonly #remoteObject: Protocol.Runtime.RemoteObject; 23 readonly #world: IsolatedWorld; 24 25 constructor( 26 world: IsolatedWorld, 27 remoteObject: Protocol.Runtime.RemoteObject, 28 ) { 29 super(); 30 this.#world = world; 31 this.#remoteObject = remoteObject; 32 } 33 34 override get disposed(): boolean { 35 return this.#disposed; 36 } 37 38 override get realm(): IsolatedWorld { 39 return this.#world; 40 } 41 42 get client(): CDPSession { 43 return this.realm.environment.client; 44 } 45 46 override async jsonValue(): Promise<T> { 47 if (!this.#remoteObject.objectId) { 48 return valueFromRemoteObject(this.#remoteObject); 49 } 50 const value = await this.evaluate(object => { 51 return object; 52 }); 53 if (value === undefined) { 54 throw new Error('Could not serialize referenced object'); 55 } 56 return value; 57 } 58 59 /** 60 * Either `null` or the handle itself if the handle is an 61 * instance of {@link ElementHandle}. 62 */ 63 override asElement(): CdpElementHandle<Node> | null { 64 return null; 65 } 66 67 override async dispose(): Promise<void> { 68 if (this.#disposed) { 69 return; 70 } 71 this.#disposed = true; 72 await releaseObject(this.client, this.#remoteObject); 73 } 74 75 override toString(): string { 76 if (!this.#remoteObject.objectId) { 77 return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject); 78 } 79 const type = this.#remoteObject.subtype || this.#remoteObject.type; 80 return 'JSHandle@' + type; 81 } 82 83 override get id(): string | undefined { 84 return this.#remoteObject.objectId; 85 } 86 87 override remoteObject(): Protocol.Runtime.RemoteObject { 88 return this.#remoteObject; 89 } 90 91 override async getProperties(): Promise<Map<string, JSHandle<unknown>>> { 92 // We use Runtime.getProperties rather than iterative version for 93 // improved performance as it allows getting everything at once. 94 const response = await this.client.send('Runtime.getProperties', { 95 objectId: this.#remoteObject.objectId!, 96 ownProperties: true, 97 }); 98 const result = new Map<string, JSHandle>(); 99 for (const property of response.result) { 100 if (!property.enumerable || !property.value) { 101 continue; 102 } 103 result.set(property.name, this.#world.createCdpHandle(property.value)); 104 } 105 return result; 106 } 107 } 108 109 /** 110 * @internal 111 */ 112 export async function releaseObject( 113 client: CDPSession, 114 remoteObject: Protocol.Runtime.RemoteObject, 115 ): Promise<void> { 116 if (!remoteObject.objectId) { 117 return; 118 } 119 await client 120 .send('Runtime.releaseObject', {objectId: remoteObject.objectId}) 121 .catch(error => { 122 // Exceptions might happen in case of a page been navigated or closed. 123 // Swallow these since they are harmless and we don't leak anything in this case. 124 debugError(error); 125 }); 126 }