4.16 kBPlain TextView Raw
1/**
2 * @license
3 * Copyright 2023 Google Inc.
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
8
9import {
10 bindIsolatedHandle,
11 ElementHandle,
12 type AutofillData,
13} from '../api/ElementHandle.js';
14import {UnsupportedOperation} from '../common/Errors.js';
15import type {AwaitableIterable} from '../common/types.js';
16import {environment} from '../environment.js';
17import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
18import {throwIfDisposed} from '../util/decorators.js';
19
20import type {BidiFrame} from './Frame.js';
21import {BidiJSHandle} from './JSHandle.js';
22import type {BidiFrameRealm} from './Realm.js';
23
24/**
25 * @internal
26 */
27export 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}