"use strict"; /** * Copyright 2019 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _ElementHandle_instances, _ElementHandle_frame, _ElementHandle_frameManager_get, _ElementHandle_page_get, _ElementHandle_scrollIntoViewIfNeeded, _ElementHandle_getOOPIFOffsets, _ElementHandle_getBoxModel, _ElementHandle_fromProtocolQuad, _ElementHandle_intersectQuadWithViewport; Object.defineProperty(exports, "__esModule", { value: true }); exports.ElementHandle = void 0; const assert_js_1 = require("../util/assert.js"); const JSHandle_js_1 = require("./JSHandle.js"); const QueryHandler_js_1 = require("./QueryHandler.js"); const util_js_1 = require("./util.js"); const applyOffsetsToQuad = (quad, offsetX, offsetY) => { return quad.map(part => { return { x: part.x + offsetX, y: part.y + offsetY }; }); }; /** * ElementHandle represents an in-page DOM element. * * @remarks * ElementHandles can be created with the {@link Page.$} method. * * ```ts * const puppeteer = require('puppeteer'); * * (async () => { * const browser = await puppeteer.launch(); * const page = await browser.newPage(); * await page.goto('https://example.com'); * const hrefElement = await page.$('a'); * await hrefElement.click(); * // ... * })(); * ``` * * ElementHandle prevents the DOM element from being garbage-collected unless the * handle is {@link JSHandle.dispose | disposed}. ElementHandles are auto-disposed * when their origin frame gets navigated. * * ElementHandle instances can be used as arguments in {@link Page.$eval} and * {@link Page.evaluate} methods. * * If you're using TypeScript, ElementHandle takes a generic argument that * denotes the type of element the handle is holding within. For example, if you * have a handle to a `` element matching `selector`, the method * throws an error. * * @example * * ```ts * handle.select('blue'); // single selection * handle.select('red', 'green', 'blue'); // multiple selections * ``` * * @param values - Values of options to select. If the ` element.'); } const selectedValues = new Set(); if (!element.multiple) { for (const option of element.options) { option.selected = false; } for (const option of element.options) { if (values.has(option.value)) { option.selected = true; selectedValues.add(option.value); break; } } } else { for (const option of element.options) { option.selected = values.has(option.value); if (option.selected) { selectedValues.add(option.value); } } } element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); return [...selectedValues.values()]; }, values); } /** * This method expects `elementHandle` to point to an * {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input | input element}. * * @param filePaths - Sets the value of the file input to these paths. * If a path is relative, then it is resolved against the * {@link https://nodejs.org/api/process.html#process_process_cwd | current working directory}. * Note for locals script connecting to remote chrome environments, * paths must be absolute. */ async uploadFile(...filePaths) { const isMultiple = await this.evaluate(element => { return element.multiple; }); (0, assert_js_1.assert)(filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with '); // Locate all files and confirm that they exist. let path; try { path = await Promise.resolve().then(() => __importStar(require('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); } }); const { objectId } = this.remoteObject(); const { node } = await this.client.send('DOM.describeNode', { objectId }); const { backendNodeId } = node; /* 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) { 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 })); element.dispatchEvent(new Event('change', { bubbles: true })); }); } else { await this.client.send('DOM.setFileInputFiles', { objectId, files, backendNodeId, }); } } /** * This method scrolls element into view if needed, and then uses * {@link Touchscreen.tap} to tap in the center of the element. * If the element is detached from DOM, the method throws an error. */ async tap() { await __classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_scrollIntoViewIfNeeded).call(this); const { x, y } = await this.clickablePoint(); await __classPrivateFieldGet(this, _ElementHandle_instances, "a", _ElementHandle_page_get).touchscreen.tap(x, y); } /** * Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element. */ async focus() { await this.evaluate(element => { if (!(element instanceof HTMLElement)) { throw new Error('Cannot focus non-HTMLElement'); } return element.focus(); }); } /** * Focuses the element, and then sends a `keydown`, `keypress`/`input`, and * `keyup` event for each character in the text. * * To press a special key, like `Control` or `ArrowDown`, * use {@link ElementHandle.press}. * * @example * * ```ts * await elementHandle.type('Hello'); // Types instantly * await elementHandle.type('World', {delay: 100}); // Types slower, like a user * ``` * * @example * An example of typing into a text field and then submitting the form: * * ```ts * const elementHandle = await page.$('input'); * await elementHandle.type('some text'); * await elementHandle.press('Enter'); * ``` */ async type(text, options) { await this.focus(); await __classPrivateFieldGet(this, _ElementHandle_instances, "a", _ElementHandle_page_get).keyboard.type(text, options); } /** * Focuses the element, and then uses {@link Keyboard.down} and {@link Keyboard.up}. * * @remarks * If `key` is a single character and no modifier keys besides `Shift` * are being held down, a `keypress`/`input` event will also be generated. * The `text` option can be specified to force an input event to be generated. * * **NOTE** Modifier keys DO affect `elementHandle.press`. Holding down `Shift` * will type the text in upper case. * * @param key - Name of key to press, such as `ArrowLeft`. * See {@link KeyInput} for a list of all key names. */ async press(key, options) { await this.focus(); await __classPrivateFieldGet(this, _ElementHandle_instances, "a", _ElementHandle_page_get).keyboard.press(key, options); } /** * This method returns the bounding box of the element (relative to the main frame), * or `null` if the element is not visible. */ async boundingBox() { const result = await __classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_getBoxModel).call(this); if (!result) { return null; } const { offsetX, offsetY } = await __classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_getOOPIFOffsets).call(this, __classPrivateFieldGet(this, _ElementHandle_frame, "f")); const quad = result.model.border; const x = Math.min(quad[0], quad[2], quad[4], quad[6]); const y = Math.min(quad[1], quad[3], quad[5], quad[7]); const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x; const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y; return { x: x + offsetX, y: y + offsetY, width, height }; } /** * This method returns boxes of the element, or `null` if the element is not visible. * * @remarks * * Boxes are represented as an array of points; * Each Point is an object `{x, y}`. Box points are sorted clock-wise. */ async boxModel() { const result = await __classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_getBoxModel).call(this); if (!result) { return null; } const { offsetX, offsetY } = await __classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_getOOPIFOffsets).call(this, __classPrivateFieldGet(this, _ElementHandle_frame, "f")); const { content, padding, border, margin, width, height } = result.model; return { content: applyOffsetsToQuad(__classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_fromProtocolQuad).call(this, content), offsetX, offsetY), padding: applyOffsetsToQuad(__classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_fromProtocolQuad).call(this, padding), offsetX, offsetY), border: applyOffsetsToQuad(__classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_fromProtocolQuad).call(this, border), offsetX, offsetY), margin: applyOffsetsToQuad(__classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_fromProtocolQuad).call(this, margin), offsetX, offsetY), width, height, }; } /** * This method scrolls element into view if needed, and then uses * {@link Page.screenshot} to take a screenshot of the element. * If the element is detached from DOM, the method throws an error. */ async screenshot(options = {}) { let needsViewportReset = false; let boundingBox = await this.boundingBox(); (0, assert_js_1.assert)(boundingBox, 'Node is either not visible or not an HTMLElement'); const viewport = __classPrivateFieldGet(this, _ElementHandle_instances, "a", _ElementHandle_page_get).viewport(); if (viewport && (boundingBox.width > viewport.width || boundingBox.height > viewport.height)) { const newViewport = { width: Math.max(viewport.width, Math.ceil(boundingBox.width)), height: Math.max(viewport.height, Math.ceil(boundingBox.height)), }; await __classPrivateFieldGet(this, _ElementHandle_instances, "a", _ElementHandle_page_get).setViewport(Object.assign({}, viewport, newViewport)); needsViewportReset = true; } await __classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_scrollIntoViewIfNeeded).call(this); boundingBox = await this.boundingBox(); (0, assert_js_1.assert)(boundingBox, 'Node is either not visible or not an HTMLElement'); (0, assert_js_1.assert)(boundingBox.width !== 0, 'Node has 0 width.'); (0, assert_js_1.assert)(boundingBox.height !== 0, 'Node has 0 height.'); const layoutMetrics = await this.client.send('Page.getLayoutMetrics'); // Fallback to `layoutViewport` in case of using Firefox. const { pageX, pageY } = layoutMetrics.cssVisualViewport || layoutMetrics.layoutViewport; const clip = Object.assign({}, boundingBox); clip.x += pageX; clip.y += pageY; const imageData = await __classPrivateFieldGet(this, _ElementHandle_instances, "a", _ElementHandle_page_get).screenshot(Object.assign({}, { clip, }, options)); if (needsViewportReset && viewport) { await __classPrivateFieldGet(this, _ElementHandle_instances, "a", _ElementHandle_page_get).setViewport(viewport); } return imageData; } /** * Resolves to true if the element is visible in the current viewport. */ async isIntersectingViewport(options) { const { threshold = 0 } = options !== null && options !== void 0 ? options : {}; return await this.evaluate(async (element, threshold) => { const visibleRatio = await new Promise(resolve => { const observer = new IntersectionObserver(entries => { resolve(entries[0].intersectionRatio); observer.disconnect(); }); observer.observe(element); }); return threshold === 1 ? visibleRatio === 1 : visibleRatio > threshold; }, threshold); } } exports.ElementHandle = ElementHandle; _ElementHandle_frame = new WeakMap(), _ElementHandle_instances = new WeakSet(), _ElementHandle_frameManager_get = function _ElementHandle_frameManager_get() { return __classPrivateFieldGet(this, _ElementHandle_frame, "f")._frameManager; }, _ElementHandle_page_get = function _ElementHandle_page_get() { return __classPrivateFieldGet(this, _ElementHandle_frame, "f").page(); }, _ElementHandle_scrollIntoViewIfNeeded = async function _ElementHandle_scrollIntoViewIfNeeded() { const error = await this.evaluate(async (element) => { if (!element.isConnected) { return 'Node is detached from document'; } if (element.nodeType !== Node.ELEMENT_NODE) { return 'Node is not of type HTMLElement'; } return; }); if (error) { throw new Error(error); } try { await this.client.send('DOM.scrollIntoViewIfNeeded', { objectId: this.remoteObject().objectId, }); } catch (_err) { // Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported await this.evaluate(async (element, pageJavascriptEnabled) => { const visibleRatio = async () => { return await new Promise(resolve => { const observer = new IntersectionObserver(entries => { resolve(entries[0].intersectionRatio); observer.disconnect(); }); observer.observe(element); }); }; if (!pageJavascriptEnabled || (await visibleRatio()) !== 1.0) { element.scrollIntoView({ block: 'center', inline: 'center', // @ts-expect-error Chrome still supports behavior: instant but // it's not in the spec so TS shouts We don't want to make this // breaking change in Puppeteer yet so we'll ignore the line. behavior: 'instant', }); } }, __classPrivateFieldGet(this, _ElementHandle_instances, "a", _ElementHandle_page_get).isJavaScriptEnabled()); } }, _ElementHandle_getOOPIFOffsets = async function _ElementHandle_getOOPIFOffsets(frame) { let offsetX = 0; let offsetY = 0; let currentFrame = frame; while (currentFrame && currentFrame.parentFrame()) { const parent = currentFrame.parentFrame(); if (!currentFrame.isOOPFrame() || !parent) { currentFrame = parent; continue; } const { backendNodeId } = await parent._client().send('DOM.getFrameOwner', { frameId: currentFrame._id, }); const result = await parent._client().send('DOM.getBoxModel', { backendNodeId: backendNodeId, }); if (!result) { break; } const contentBoxQuad = result.model.content; const topLeftCorner = __classPrivateFieldGet(this, _ElementHandle_instances, "m", _ElementHandle_fromProtocolQuad).call(this, contentBoxQuad)[0]; offsetX += topLeftCorner.x; offsetY += topLeftCorner.y; currentFrame = parent; } return { offsetX, offsetY }; }, _ElementHandle_getBoxModel = function _ElementHandle_getBoxModel() { const params = { objectId: this.remoteObject().objectId, }; return this.client.send('DOM.getBoxModel', params).catch(error => { return (0, util_js_1.debugError)(error); }); }, _ElementHandle_fromProtocolQuad = function _ElementHandle_fromProtocolQuad(quad) { return [ { x: quad[0], y: quad[1] }, { x: quad[2], y: quad[3] }, { x: quad[4], y: quad[5] }, { x: quad[6], y: quad[7] }, ]; }, _ElementHandle_intersectQuadWithViewport = function _ElementHandle_intersectQuadWithViewport(quad, width, height) { return quad.map(point => { return { x: Math.min(Math.max(point.x, 0), width), y: Math.min(Math.max(point.y, 0), height), }; }); }; function computeQuadArea(quad) { /* Compute sum of all directed areas of adjacent triangles https://en.wikipedia.org/wiki/Polygon#Simple_polygons */ let area = 0; for (let i = 0; i < quad.length; ++i) { const p1 = quad[i]; const p2 = quad[(i + 1) % quad.length]; area += (p1.x * p2.y - p2.x * p1.y) / 2; } return Math.abs(area); } //# sourceMappingURL=ElementHandle.js.map