ElementHandle.ts (4163B)
1 /** 2 * @license 3 * Copyright 2023 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; 8 9 import { 10 bindIsolatedHandle, 11 ElementHandle, 12 type AutofillData, 13 } from '../api/ElementHandle.js'; 14 import {UnsupportedOperation} from '../common/Errors.js'; 15 import type {AwaitableIterable} from '../common/types.js'; 16 import {environment} from '../environment.js'; 17 import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js'; 18 import {throwIfDisposed} from '../util/decorators.js'; 19 20 import type {BidiFrame} from './Frame.js'; 21 import {BidiJSHandle} from './JSHandle.js'; 22 import type {BidiFrameRealm} from './Realm.js'; 23 24 /** 25 * @internal 26 */ 27 export class BidiElementHandle< 28 ElementType extends Node = Element, 29 > extends ElementHandle<ElementType> { 30 #backendNodeId?: number; 31 32 static from<ElementType extends Node = Element>( 33 value: Bidi.Script.RemoteValue, 34 realm: BidiFrameRealm, 35 ): BidiElementHandle<ElementType> { 36 return new BidiElementHandle(value, realm); 37 } 38 39 declare handle: BidiJSHandle<ElementType>; 40 41 constructor(value: Bidi.Script.RemoteValue, realm: BidiFrameRealm) { 42 super(BidiJSHandle.from(value, realm)); 43 } 44 45 override get realm(): BidiFrameRealm { 46 // SAFETY: See the super call in the constructor. 47 return this.handle.realm as BidiFrameRealm; 48 } 49 50 override get frame(): BidiFrame { 51 return this.realm.environment; 52 } 53 54 remoteValue(): Bidi.Script.RemoteValue { 55 return this.handle.remoteValue(); 56 } 57 58 @throwIfDisposed() 59 override async autofill(data: AutofillData): Promise<void> { 60 const client = this.frame.client; 61 const nodeInfo = await client.send('DOM.describeNode', { 62 objectId: this.handle.id, 63 }); 64 const fieldId = nodeInfo.node.backendNodeId; 65 const frameId = this.frame._id; 66 await client.send('Autofill.trigger', { 67 fieldId, 68 frameId, 69 card: data.creditCard, 70 }); 71 } 72 73 override async contentFrame( 74 this: BidiElementHandle<HTMLIFrameElement>, 75 ): Promise<BidiFrame>; 76 @throwIfDisposed() 77 @bindIsolatedHandle 78 override async contentFrame(): Promise<BidiFrame | null> { 79 using handle = (await this.evaluateHandle(element => { 80 if ( 81 element instanceof HTMLIFrameElement || 82 element instanceof HTMLFrameElement 83 ) { 84 return element.contentWindow; 85 } 86 return; 87 })) as BidiJSHandle; 88 const value = handle.remoteValue(); 89 if (value.type === 'window') { 90 return ( 91 this.frame 92 .page() 93 .frames() 94 .find(frame => { 95 return frame._id === value.value.context; 96 }) ?? null 97 ); 98 } 99 return null; 100 } 101 102 override async uploadFile( 103 this: BidiElementHandle<HTMLInputElement>, 104 ...files: string[] 105 ): Promise<void> { 106 // Locate all files and confirm that they exist. 107 const path = environment.value.path; 108 if (path) { 109 files = files.map(file => { 110 if (path.win32.isAbsolute(file) || path.posix.isAbsolute(file)) { 111 return file; 112 } else { 113 return path.resolve(file); 114 } 115 }); 116 } 117 await this.frame.setFiles(this, files); 118 } 119 120 override async *queryAXTree( 121 this: BidiElementHandle<HTMLElement>, 122 name?: string | undefined, 123 role?: string | undefined, 124 ): AwaitableIterable<ElementHandle<Node>> { 125 const results = await this.frame.locateNodes(this, { 126 type: 'accessibility', 127 value: { 128 role, 129 name, 130 }, 131 }); 132 133 return yield* AsyncIterableUtil.map(results, node => { 134 // TODO: maybe change ownership since the default ownership is probably none. 135 return Promise.resolve(BidiElementHandle.from(node, this.realm)); 136 }); 137 } 138 139 override async backendNodeId(): Promise<number> { 140 if (!this.frame.page().browser().cdpSupported) { 141 throw new UnsupportedOperation(); 142 } 143 if (this.#backendNodeId) { 144 return this.#backendNodeId; 145 } 146 const {node} = await this.frame.client.send('DOM.describeNode', { 147 objectId: this.handle.id, 148 }); 149 this.#backendNodeId = node.backendNodeId; 150 return this.#backendNodeId; 151 } 152 }