Binding.ts (3663B)
1 /** 2 * @license 3 * Copyright 2024 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 import {JSHandle} from '../api/JSHandle.js'; 7 import {debugError} from '../common/util.js'; 8 import {DisposableStack} from '../util/disposable.js'; 9 import {isErrorLike} from '../util/ErrorLike.js'; 10 11 import type {ExecutionContext} from './ExecutionContext.js'; 12 13 /** 14 * @internal 15 */ 16 export class Binding { 17 #name: string; 18 #fn: (...args: unknown[]) => unknown; 19 #initSource: string; 20 constructor( 21 name: string, 22 fn: (...args: unknown[]) => unknown, 23 initSource: string, 24 ) { 25 this.#name = name; 26 this.#fn = fn; 27 this.#initSource = initSource; 28 } 29 30 get name(): string { 31 return this.#name; 32 } 33 34 get initSource(): string { 35 return this.#initSource; 36 } 37 38 /** 39 * @param context - Context to run the binding in; the context should have 40 * the binding added to it beforehand. 41 * @param id - ID of the call. This should come from the CDP 42 * `onBindingCalled` response. 43 * @param args - Plain arguments from CDP. 44 */ 45 async run( 46 context: ExecutionContext, 47 id: number, 48 args: unknown[], 49 isTrivial: boolean, 50 ): Promise<void> { 51 const stack = new DisposableStack(); 52 try { 53 if (!isTrivial) { 54 // Getting non-trivial arguments. 55 using handles = await context.evaluateHandle( 56 (name, seq) => { 57 // @ts-expect-error Code is evaluated in a different context. 58 return globalThis[name].args.get(seq); 59 }, 60 this.#name, 61 id, 62 ); 63 const properties = await handles.getProperties(); 64 for (const [index, handle] of properties) { 65 // This is not straight-forward since some arguments can stringify, but 66 // aren't plain objects so add subtypes when the use-case arises. 67 if (index in args) { 68 switch (handle.remoteObject().subtype) { 69 case 'node': 70 args[+index] = handle; 71 break; 72 default: 73 stack.use(handle); 74 } 75 } else { 76 stack.use(handle); 77 } 78 } 79 } 80 81 await context.evaluate( 82 (name, seq, result) => { 83 // @ts-expect-error Code is evaluated in a different context. 84 const callbacks = globalThis[name].callbacks; 85 callbacks.get(seq).resolve(result); 86 callbacks.delete(seq); 87 }, 88 this.#name, 89 id, 90 await this.#fn(...args), 91 ); 92 93 for (const arg of args) { 94 if (arg instanceof JSHandle) { 95 stack.use(arg); 96 } 97 } 98 } catch (error) { 99 if (isErrorLike(error)) { 100 await context 101 .evaluate( 102 (name, seq, message, stack) => { 103 const error = new Error(message); 104 error.stack = stack; 105 // @ts-expect-error Code is evaluated in a different context. 106 const callbacks = globalThis[name].callbacks; 107 callbacks.get(seq).reject(error); 108 callbacks.delete(seq); 109 }, 110 this.#name, 111 id, 112 error.message, 113 error.stack, 114 ) 115 .catch(debugError); 116 } else { 117 await context 118 .evaluate( 119 (name, seq, error) => { 120 // @ts-expect-error Code is evaluated in a different context. 121 const callbacks = globalThis[name].callbacks; 122 callbacks.get(seq).reject(error); 123 callbacks.delete(seq); 124 }, 125 this.#name, 126 id, 127 error, 128 ) 129 .catch(debugError); 130 } 131 } 132 } 133 }