summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/packages/puppeteer-core/src/cdp/ElementHandle.ts
diff options
context:
space:
mode:
Diffstat (limited to 'remote/test/puppeteer/packages/puppeteer-core/src/cdp/ElementHandle.ts')
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/cdp/ElementHandle.ts172
1 files changed, 172 insertions, 0 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/ElementHandle.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/ElementHandle.ts
new file mode 100644
index 0000000000..a47d546a87
--- /dev/null
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/ElementHandle.ts
@@ -0,0 +1,172 @@
+/**
+ * @license
+ * Copyright 2019 Google Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type Path from 'path';
+
+import type {Protocol} from 'devtools-protocol';
+
+import type {CDPSession} from '../api/CDPSession.js';
+import {ElementHandle, type AutofillData} from '../api/ElementHandle.js';
+import {debugError} from '../common/util.js';
+import {assert} from '../util/assert.js';
+import {throwIfDisposed} from '../util/decorators.js';
+
+import type {CdpFrame} from './Frame.js';
+import type {FrameManager} from './FrameManager.js';
+import type {IsolatedWorld} from './IsolatedWorld.js';
+import {CdpJSHandle} from './JSHandle.js';
+
+/**
+ * The CdpElementHandle extends ElementHandle now to keep compatibility
+ * with `instanceof` because of that we need to have methods for
+ * CdpJSHandle to in this implementation as well.
+ *
+ * @internal
+ */
+export class CdpElementHandle<
+ ElementType extends Node = Element,
+> extends ElementHandle<ElementType> {
+ protected declare readonly handle: CdpJSHandle<ElementType>;
+
+ constructor(
+ world: IsolatedWorld,
+ remoteObject: Protocol.Runtime.RemoteObject
+ ) {
+ super(new CdpJSHandle(world, remoteObject));
+ }
+
+ override get realm(): IsolatedWorld {
+ return this.handle.realm;
+ }
+
+ get client(): CDPSession {
+ return this.handle.client;
+ }
+
+ override remoteObject(): Protocol.Runtime.RemoteObject {
+ return this.handle.remoteObject();
+ }
+
+ get #frameManager(): FrameManager {
+ return this.frame._frameManager;
+ }
+
+ override get frame(): CdpFrame {
+ return this.realm.environment as CdpFrame;
+ }
+
+ override async contentFrame(
+ this: ElementHandle<HTMLIFrameElement>
+ ): Promise<CdpFrame>;
+
+ @throwIfDisposed()
+ override async contentFrame(): Promise<CdpFrame | null> {
+ const nodeInfo = await this.client.send('DOM.describeNode', {
+ objectId: this.id,
+ });
+ if (typeof nodeInfo.node.frameId !== 'string') {
+ return null;
+ }
+ return this.#frameManager.frame(nodeInfo.node.frameId);
+ }
+
+ @throwIfDisposed()
+ @ElementHandle.bindIsolatedHandle
+ override async scrollIntoView(
+ this: CdpElementHandle<Element>
+ ): Promise<void> {
+ await this.assertConnectedElement();
+ try {
+ await this.client.send('DOM.scrollIntoViewIfNeeded', {
+ objectId: this.id,
+ });
+ } catch (error) {
+ debugError(error);
+ // Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
+ await super.scrollIntoView();
+ }
+ }
+
+ @throwIfDisposed()
+ @ElementHandle.bindIsolatedHandle
+ override async uploadFile(
+ this: CdpElementHandle<HTMLInputElement>,
+ ...filePaths: string[]
+ ): Promise<void> {
+ const isMultiple = await this.evaluate(element => {
+ return element.multiple;
+ });
+ assert(
+ filePaths.length <= 1 || isMultiple,
+ 'Multiple file uploads only work with <input type=file multiple>'
+ );
+
+ // Locate all files and confirm that they exist.
+ let path: typeof Path;
+ try {
+ path = await import('path');
+ } catch (error) {
+ if (error instanceof TypeError) {
+ throw new Error(
+ `JSHandle#uploadFile can only be used in Node-like environments.`
+ );
+ }
+ throw error;
+ }
+ const files = filePaths.map(filePath => {
+ if (path.win32.isAbsolute(filePath) || path.posix.isAbsolute(filePath)) {
+ return filePath;
+ } else {
+ return path.resolve(filePath);
+ }
+ });
+
+ /**
+ * The zero-length array is a special case, it seems that
+ * DOM.setFileInputFiles does not actually update the files in that case, so
+ * the solution is to eval the element value to a new FileList directly.
+ */
+ if (files.length === 0) {
+ // XXX: These events should converted to trusted events. Perhaps do this
+ // in `DOM.setFileInputFiles`?
+ await this.evaluate(element => {
+ element.files = new DataTransfer().files;
+
+ // Dispatch events for this case because it should behave akin to a user action.
+ element.dispatchEvent(
+ new Event('input', {bubbles: true, composed: true})
+ );
+ element.dispatchEvent(new Event('change', {bubbles: true}));
+ });
+ return;
+ }
+
+ const {
+ node: {backendNodeId},
+ } = await this.client.send('DOM.describeNode', {
+ objectId: this.id,
+ });
+ await this.client.send('DOM.setFileInputFiles', {
+ objectId: this.id,
+ files,
+ backendNodeId,
+ });
+ }
+
+ @throwIfDisposed()
+ override async autofill(data: AutofillData): Promise<void> {
+ const nodeInfo = await this.client.send('DOM.describeNode', {
+ objectId: this.handle.id,
+ });
+ const fieldId = nodeInfo.node.backendNodeId;
+ const frameId = this.frame._id;
+ await this.client.send('Autofill.trigger', {
+ fieldId,
+ frameId,
+ card: data.creditCard,
+ });
+ }
+}