tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ElementHandle.ts (47432B)


      1 /**
      2 * @license
      3 * Copyright 2023 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import type {Protocol} from 'devtools-protocol';
      8 
      9 import type {Frame} from '../api/Frame.js';
     10 import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
     11 import {LazyArg} from '../common/LazyArg.js';
     12 import type {
     13  AwaitableIterable,
     14  ElementFor,
     15  EvaluateFuncWith,
     16  HandleFor,
     17  HandleOr,
     18  NodeFor,
     19 } from '../common/types.js';
     20 import type {KeyInput} from '../common/USKeyboardLayout.js';
     21 import {isString, withSourcePuppeteerURLIfNone} from '../common/util.js';
     22 import {assert} from '../util/assert.js';
     23 import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
     24 import {throwIfDisposed} from '../util/decorators.js';
     25 
     26 import {_isElementHandle} from './ElementHandleSymbol.js';
     27 import type {
     28  KeyboardTypeOptions,
     29  KeyPressOptions,
     30  MouseClickOptions,
     31  TouchHandle,
     32 } from './Input.js';
     33 import {JSHandle} from './JSHandle.js';
     34 import type {
     35  QueryOptions,
     36  ScreenshotOptions,
     37  WaitForSelectorOptions,
     38 } from './Page.js';
     39 
     40 /**
     41 * @public
     42 */
     43 export type Quad = [Point, Point, Point, Point];
     44 
     45 /**
     46 * @public
     47 */
     48 export interface BoxModel {
     49  content: Quad;
     50  padding: Quad;
     51  border: Quad;
     52  margin: Quad;
     53  width: number;
     54  height: number;
     55 }
     56 
     57 /**
     58 * @public
     59 */
     60 export interface BoundingBox extends Point {
     61  /**
     62   * the width of the element in pixels.
     63   */
     64  width: number;
     65  /**
     66   * the height of the element in pixels.
     67   */
     68  height: number;
     69 }
     70 
     71 /**
     72 * @public
     73 */
     74 export interface Offset {
     75  /**
     76   * x-offset for the clickable point relative to the top-left corner of the border box.
     77   */
     78  x: number;
     79  /**
     80   * y-offset for the clickable point relative to the top-left corner of the border box.
     81   */
     82  y: number;
     83 }
     84 
     85 /**
     86 * @public
     87 */
     88 export interface ClickOptions extends MouseClickOptions {
     89  /**
     90   * Offset for the clickable point relative to the top-left corner of the border box.
     91   */
     92  offset?: Offset;
     93 }
     94 
     95 /**
     96 * @public
     97 */
     98 export interface Point {
     99  x: number;
    100  y: number;
    101 }
    102 
    103 /**
    104 * @public
    105 */
    106 export interface ElementScreenshotOptions extends ScreenshotOptions {
    107  /**
    108   * @defaultValue `true`
    109   */
    110  scrollIntoView?: boolean;
    111 }
    112 
    113 /**
    114 * A given method will have it's `this` replaced with an isolated version of
    115 * `this` when decorated with this decorator.
    116 *
    117 * All changes of isolated `this` are reflected on the actual `this`.
    118 *
    119 * @internal
    120 */
    121 export function bindIsolatedHandle<This extends ElementHandle<Node>>(
    122  target: (this: This, ...args: any[]) => Promise<any>,
    123  _: unknown,
    124 ): typeof target {
    125  return async function (...args) {
    126    // If the handle is already isolated, then we don't need to adopt it
    127    // again.
    128    if (this.realm === this.frame.isolatedRealm()) {
    129      return await target.call(this, ...args);
    130    }
    131    let adoptedThis: This;
    132    if (this['isolatedHandle']) {
    133      adoptedThis = this['isolatedHandle'];
    134    } else {
    135      this['isolatedHandle'] = adoptedThis = await this.frame
    136        .isolatedRealm()
    137        .adoptHandle(this);
    138    }
    139    const result = await target.call(adoptedThis, ...args);
    140    // If the function returns `adoptedThis`, then we return `this`.
    141    if (result === adoptedThis) {
    142      return this;
    143    }
    144    // If the function returns a handle, transfer it into the current realm.
    145    if (result instanceof JSHandle) {
    146      return await this.realm.transferHandle(result);
    147    }
    148    // If the function returns an array of handlers, transfer them into the
    149    // current realm.
    150    if (Array.isArray(result)) {
    151      await Promise.all(
    152        result.map(async (item, index, result) => {
    153          if (item instanceof JSHandle) {
    154            result[index] = await this.realm.transferHandle(item);
    155          }
    156        }),
    157      );
    158    }
    159    if (result instanceof Map) {
    160      await Promise.all(
    161        [...result.entries()].map(async ([key, value]) => {
    162          if (value instanceof JSHandle) {
    163            result.set(key, await this.realm.transferHandle(value));
    164          }
    165        }),
    166      );
    167    }
    168    return result;
    169  };
    170 }
    171 
    172 /**
    173 * ElementHandle represents an in-page DOM element.
    174 *
    175 * @remarks
    176 * ElementHandles can be created with the {@link Page.$} method.
    177 *
    178 * ```ts
    179 * import puppeteer from 'puppeteer';
    180 *
    181 * (async () => {
    182 *   const browser = await puppeteer.launch();
    183 *   const page = await browser.newPage();
    184 *   await page.goto('https://example.com');
    185 *   const hrefElement = await page.$('a');
    186 *   await hrefElement.click();
    187 *   // ...
    188 * })();
    189 * ```
    190 *
    191 * ElementHandle prevents the DOM element from being garbage-collected unless the
    192 * handle is {@link JSHandle.dispose | disposed}. ElementHandles are auto-disposed
    193 * when their origin frame gets navigated.
    194 *
    195 * ElementHandle instances can be used as arguments in {@link Page.$eval} and
    196 * {@link Page.evaluate} methods.
    197 *
    198 * If you're using TypeScript, ElementHandle takes a generic argument that
    199 * denotes the type of element the handle is holding within. For example, if you
    200 * have a handle to a `<select>` element, you can type it as
    201 * `ElementHandle<HTMLSelectElement>` and you get some nicer type checks.
    202 *
    203 * @public
    204 */
    205 export abstract class ElementHandle<
    206  ElementType extends Node = Element,
    207 > extends JSHandle<ElementType> {
    208  /**
    209   * @internal
    210   */
    211  declare [_isElementHandle]: boolean;
    212 
    213  /**
    214   * @internal
    215   * Cached isolatedHandle to prevent
    216   * trying to adopt it multiple times
    217   */
    218  isolatedHandle?: typeof this;
    219 
    220  /**
    221   * @internal
    222   */
    223  protected readonly handle;
    224 
    225  /**
    226   * @internal
    227   */
    228  constructor(handle: JSHandle<ElementType>) {
    229    super();
    230    this.handle = handle;
    231    this[_isElementHandle] = true;
    232  }
    233 
    234  /**
    235   * @internal
    236   */
    237  override get id(): string | undefined {
    238    return this.handle.id;
    239  }
    240 
    241  /**
    242   * @internal
    243   */
    244  override get disposed(): boolean {
    245    return this.handle.disposed;
    246  }
    247 
    248  /**
    249   * @internal
    250   */
    251  @throwIfDisposed()
    252  @bindIsolatedHandle
    253  override async getProperty<K extends keyof ElementType>(
    254    propertyName: HandleOr<K>,
    255  ): Promise<HandleFor<ElementType[K]>> {
    256    return await this.handle.getProperty(propertyName);
    257  }
    258 
    259  /**
    260   * @internal
    261   */
    262  @throwIfDisposed()
    263  @bindIsolatedHandle
    264  override async getProperties(): Promise<Map<string, JSHandle>> {
    265    return await this.handle.getProperties();
    266  }
    267 
    268  /**
    269   * @internal
    270   */
    271  override async evaluate<
    272    Params extends unknown[],
    273    Func extends EvaluateFuncWith<ElementType, Params> = EvaluateFuncWith<
    274      ElementType,
    275      Params
    276    >,
    277  >(
    278    pageFunction: Func | string,
    279    ...args: Params
    280  ): Promise<Awaited<ReturnType<Func>>> {
    281    pageFunction = withSourcePuppeteerURLIfNone(
    282      this.evaluate.name,
    283      pageFunction,
    284    );
    285    return await this.handle.evaluate(pageFunction, ...args);
    286  }
    287 
    288  /**
    289   * @internal
    290   */
    291  override async evaluateHandle<
    292    Params extends unknown[],
    293    Func extends EvaluateFuncWith<ElementType, Params> = EvaluateFuncWith<
    294      ElementType,
    295      Params
    296    >,
    297  >(
    298    pageFunction: Func | string,
    299    ...args: Params
    300  ): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
    301    pageFunction = withSourcePuppeteerURLIfNone(
    302      this.evaluateHandle.name,
    303      pageFunction,
    304    );
    305    return await this.handle.evaluateHandle(pageFunction, ...args);
    306  }
    307 
    308  /**
    309   * @internal
    310   */
    311  @throwIfDisposed()
    312  @bindIsolatedHandle
    313  override async jsonValue(): Promise<ElementType> {
    314    return await this.handle.jsonValue();
    315  }
    316 
    317  /**
    318   * @internal
    319   */
    320  override toString(): string {
    321    return this.handle.toString();
    322  }
    323 
    324  /**
    325   * @internal
    326   */
    327  override remoteObject(): Protocol.Runtime.RemoteObject {
    328    return this.handle.remoteObject();
    329  }
    330 
    331  /**
    332   * @internal
    333   */
    334  override async dispose(): Promise<void> {
    335    await Promise.all([this.handle.dispose(), this.isolatedHandle?.dispose()]);
    336  }
    337 
    338  /**
    339   * @internal
    340   */
    341  override asElement(): ElementHandle<ElementType> {
    342    return this;
    343  }
    344 
    345  /**
    346   * Frame corresponding to the current handle.
    347   */
    348  abstract get frame(): Frame;
    349 
    350  /**
    351   * Queries the current element for an element matching the given selector.
    352   *
    353   * @param selector -
    354   * {@link https://pptr.dev/guides/page-interactions#selectors | selector}
    355   * to query the page for.
    356   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors}
    357   * can be passed as-is and a
    358   * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax}
    359   * allows querying by
    360   * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text},
    361   * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name},
    362   * and
    363   * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath}
    364   * and
    365   * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}.
    366   * Alternatively, you can specify the selector type using a
    367   * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}.
    368   * @returns A {@link ElementHandle | element handle} to the first element
    369   * matching the given selector. Otherwise, `null`.
    370   */
    371  @throwIfDisposed()
    372  @bindIsolatedHandle
    373  async $<Selector extends string>(
    374    selector: Selector,
    375  ): Promise<ElementHandle<NodeFor<Selector>> | null> {
    376    const {updatedSelector, QueryHandler} =
    377      getQueryHandlerAndSelector(selector);
    378    return (await QueryHandler.queryOne(
    379      this,
    380      updatedSelector,
    381    )) as ElementHandle<NodeFor<Selector>> | null;
    382  }
    383 
    384  /**
    385   * Queries the current element for all elements matching the given selector.
    386   *
    387   * @param selector -
    388   * {@link https://pptr.dev/guides/page-interactions#selectors | selector}
    389   * to query the page for.
    390   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors}
    391   * can be passed as-is and a
    392   * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax}
    393   * allows querying by
    394   * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text},
    395   * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name},
    396   * and
    397   * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath}
    398   * and
    399   * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}.
    400   * Alternatively, you can specify the selector type using a
    401   * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}.
    402   * @returns An array of {@link ElementHandle | element handles} that point to
    403   * elements matching the given selector.
    404   */
    405  @throwIfDisposed()
    406  async $$<Selector extends string>(
    407    selector: Selector,
    408    options?: QueryOptions,
    409  ): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
    410    if (options?.isolate === false) {
    411      return await this.#$$impl(selector);
    412    }
    413    return await this.#$$(selector);
    414  }
    415 
    416  /**
    417   * Isolates {@link ElementHandle.$$} if needed.
    418   *
    419   * @internal
    420   */
    421  @bindIsolatedHandle
    422  async #$$<Selector extends string>(
    423    selector: Selector,
    424  ): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
    425    return await this.#$$impl(selector);
    426  }
    427 
    428  /**
    429   * Implementation for {@link ElementHandle.$$}.
    430   *
    431   * @internal
    432   */
    433  async #$$impl<Selector extends string>(
    434    selector: Selector,
    435  ): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
    436    const {updatedSelector, QueryHandler} =
    437      getQueryHandlerAndSelector(selector);
    438    return await (AsyncIterableUtil.collect(
    439      QueryHandler.queryAll(this, updatedSelector),
    440    ) as Promise<Array<ElementHandle<NodeFor<Selector>>>>);
    441  }
    442 
    443  /**
    444   * Runs the given function on the first element matching the given selector in
    445   * the current element.
    446   *
    447   * If the given function returns a promise, then this method will wait till
    448   * the promise resolves.
    449   *
    450   * @example
    451   *
    452   * ```ts
    453   * const tweetHandle = await page.$('.tweet');
    454   * expect(await tweetHandle.$eval('.like', node => node.innerText)).toBe(
    455   *   '100',
    456   * );
    457   * expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe(
    458   *   '10',
    459   * );
    460   * ```
    461   *
    462   * @param selector -
    463   * {@link https://pptr.dev/guides/page-interactions#selectors | selector}
    464   * to query the page for.
    465   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors}
    466   * can be passed as-is and a
    467   * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax}
    468   * allows querying by
    469   * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text},
    470   * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name},
    471   * and
    472   * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath}
    473   * and
    474   * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}.
    475   * Alternatively, you can specify the selector type using a
    476   * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}.
    477   * @param pageFunction - The function to be evaluated in this element's page's
    478   * context. The first element matching the selector will be passed in as the
    479   * first argument.
    480   * @param args - Additional arguments to pass to `pageFunction`.
    481   * @returns A promise to the result of the function.
    482   */
    483  async $eval<
    484    Selector extends string,
    485    Params extends unknown[],
    486    Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
    487      NodeFor<Selector>,
    488      Params
    489    >,
    490  >(
    491    selector: Selector,
    492    pageFunction: Func | string,
    493    ...args: Params
    494  ): Promise<Awaited<ReturnType<Func>>> {
    495    pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
    496    using elementHandle = await this.$(selector);
    497    if (!elementHandle) {
    498      throw new Error(
    499        `Error: failed to find element matching selector "${selector}"`,
    500      );
    501    }
    502    return await elementHandle.evaluate(pageFunction, ...args);
    503  }
    504 
    505  /**
    506   * Runs the given function on an array of elements matching the given selector
    507   * in the current element.
    508   *
    509   * If the given function returns a promise, then this method will wait till
    510   * the promise resolves.
    511   *
    512   * @example
    513   * HTML:
    514   *
    515   * ```html
    516   * <div class="feed">
    517   *   <div class="tweet">Hello!</div>
    518   *   <div class="tweet">Hi!</div>
    519   * </div>
    520   * ```
    521   *
    522   * JavaScript:
    523   *
    524   * ```ts
    525   * const feedHandle = await page.$('.feed');
    526   * expect(
    527   *   await feedHandle.$$eval('.tweet', nodes => nodes.map(n => n.innerText)),
    528   * ).toEqual(['Hello!', 'Hi!']);
    529   * ```
    530   *
    531   * @param selector -
    532   * {@link https://pptr.dev/guides/page-interactions#selectors | selector}
    533   * to query the page for.
    534   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors}
    535   * can be passed as-is and a
    536   * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax}
    537   * allows querying by
    538   * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text},
    539   * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name},
    540   * and
    541   * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath}
    542   * and
    543   * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}.
    544   * Alternatively, you can specify the selector type using a
    545   * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}.
    546   * @param pageFunction - The function to be evaluated in the element's page's
    547   * context. An array of elements matching the given selector will be passed to
    548   * the function as its first argument.
    549   * @param args - Additional arguments to pass to `pageFunction`.
    550   * @returns A promise to the result of the function.
    551   */
    552  async $$eval<
    553    Selector extends string,
    554    Params extends unknown[],
    555    Func extends EvaluateFuncWith<
    556      Array<NodeFor<Selector>>,
    557      Params
    558    > = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>,
    559  >(
    560    selector: Selector,
    561    pageFunction: Func | string,
    562    ...args: Params
    563  ): Promise<Awaited<ReturnType<Func>>> {
    564    pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
    565    const results = await this.$$(selector);
    566    using elements = await this.evaluateHandle(
    567      (_, ...elements) => {
    568        return elements;
    569      },
    570      ...results,
    571    );
    572    const [result] = await Promise.all([
    573      elements.evaluate(pageFunction, ...args),
    574      ...results.map(results => {
    575        return results.dispose();
    576      }),
    577    ]);
    578    return result;
    579  }
    580 
    581  /**
    582   * Wait for an element matching the given selector to appear in the current
    583   * element.
    584   *
    585   * Unlike {@link Frame.waitForSelector}, this method does not work across
    586   * navigations or if the element is detached from DOM.
    587   *
    588   * @example
    589   *
    590   * ```ts
    591   * import puppeteer from 'puppeteer';
    592   *
    593   * (async () => {
    594   *   const browser = await puppeteer.launch();
    595   *   const page = await browser.newPage();
    596   *   let currentURL;
    597   *   page
    598   *     .mainFrame()
    599   *     .waitForSelector('img')
    600   *     .then(() => console.log('First URL with image: ' + currentURL));
    601   *
    602   *   for (currentURL of [
    603   *     'https://example.com',
    604   *     'https://google.com',
    605   *     'https://bbc.com',
    606   *   ]) {
    607   *     await page.goto(currentURL);
    608   *   }
    609   *   await browser.close();
    610   * })();
    611   * ```
    612   *
    613   * @param selector - The selector to query and wait for.
    614   * @param options - Options for customizing waiting behavior.
    615   * @returns An element matching the given selector.
    616   * @throws Throws if an element matching the given selector doesn't appear.
    617   */
    618  @throwIfDisposed()
    619  @bindIsolatedHandle
    620  async waitForSelector<Selector extends string>(
    621    selector: Selector,
    622    options: WaitForSelectorOptions = {},
    623  ): Promise<ElementHandle<NodeFor<Selector>> | null> {
    624    const {updatedSelector, QueryHandler, polling} =
    625      getQueryHandlerAndSelector(selector);
    626    return (await QueryHandler.waitFor(this, updatedSelector, {
    627      polling,
    628      ...options,
    629    })) as ElementHandle<NodeFor<Selector>> | null;
    630  }
    631 
    632  async #checkVisibility(visibility: boolean): Promise<boolean> {
    633    return await this.evaluate(
    634      async (element, PuppeteerUtil, visibility) => {
    635        return Boolean(PuppeteerUtil.checkVisibility(element, visibility));
    636      },
    637      LazyArg.create(context => {
    638        return context.puppeteerUtil;
    639      }),
    640      visibility,
    641    );
    642  }
    643 
    644  /**
    645   * An element is considered to be visible if all of the following is
    646   * true:
    647   *
    648   * - the element has
    649   *   {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle | computed styles}.
    650   *
    651   * - the element has a non-empty
    652   *   {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect | bounding client rect}.
    653   *
    654   * - the element's {@link https://developer.mozilla.org/en-US/docs/Web/CSS/visibility | visibility}
    655   *   is not `hidden` or `collapse`.
    656   */
    657  @throwIfDisposed()
    658  @bindIsolatedHandle
    659  async isVisible(): Promise<boolean> {
    660    return await this.#checkVisibility(true);
    661  }
    662 
    663  /**
    664   * An element is considered to be hidden if at least one of the following is true:
    665   *
    666   * - the element has no
    667   *   {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle | computed styles}.
    668   *
    669   * - the element has an empty
    670   *   {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect | bounding client rect}.
    671   *
    672   * - the element's {@link https://developer.mozilla.org/en-US/docs/Web/CSS/visibility | visibility}
    673   *   is `hidden` or `collapse`.
    674   */
    675  @throwIfDisposed()
    676  @bindIsolatedHandle
    677  async isHidden(): Promise<boolean> {
    678    return await this.#checkVisibility(false);
    679  }
    680 
    681  /**
    682   * Converts the current handle to the given element type.
    683   *
    684   * @example
    685   *
    686   * ```ts
    687   * const element: ElementHandle<Element> = await page.$(
    688   *   '.class-name-of-anchor',
    689   * );
    690   * // DO NOT DISPOSE `element`, this will be always be the same handle.
    691   * const anchor: ElementHandle<HTMLAnchorElement> =
    692   *   await element.toElement('a');
    693   * ```
    694   *
    695   * @param tagName - The tag name of the desired element type.
    696   * @throws An error if the handle does not match. **The handle will not be
    697   * automatically disposed.**
    698   */
    699  @throwIfDisposed()
    700  @bindIsolatedHandle
    701  async toElement<
    702    K extends keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap,
    703  >(tagName: K): Promise<HandleFor<ElementFor<K>>> {
    704    const isMatchingTagName = await this.evaluate((node, tagName) => {
    705      return node.nodeName === tagName.toUpperCase();
    706    }, tagName);
    707    if (!isMatchingTagName) {
    708      throw new Error(`Element is not a(n) \`${tagName}\` element`);
    709    }
    710    return this as unknown as HandleFor<ElementFor<K>>;
    711  }
    712 
    713  /**
    714   * Resolves the frame associated with the element, if any. Always exists for
    715   * HTMLIFrameElements.
    716   */
    717  abstract contentFrame(this: ElementHandle<HTMLIFrameElement>): Promise<Frame>;
    718  abstract contentFrame(): Promise<Frame | null>;
    719 
    720  /**
    721   * Returns the middle point within an element unless a specific offset is provided.
    722   */
    723  @throwIfDisposed()
    724  @bindIsolatedHandle
    725  async clickablePoint(offset?: Offset): Promise<Point> {
    726    const box = await this.#clickableBox();
    727    if (!box) {
    728      throw new Error('Node is either not clickable or not an Element');
    729    }
    730    if (offset !== undefined) {
    731      return {
    732        x: box.x + offset.x,
    733        y: box.y + offset.y,
    734      };
    735    }
    736    return {
    737      x: box.x + box.width / 2,
    738      y: box.y + box.height / 2,
    739    };
    740  }
    741 
    742  /**
    743   * This method scrolls element into view if needed, and then
    744   * uses {@link Page.mouse} to hover over the center of the element.
    745   * If the element is detached from DOM, the method throws an error.
    746   */
    747  @throwIfDisposed()
    748  @bindIsolatedHandle
    749  async hover(this: ElementHandle<Element>): Promise<void> {
    750    await this.scrollIntoViewIfNeeded();
    751    const {x, y} = await this.clickablePoint();
    752    await this.frame.page().mouse.move(x, y);
    753  }
    754 
    755  /**
    756   * This method scrolls element into view if needed, and then
    757   * uses {@link Page.mouse} to click in the center of the element.
    758   * If the element is detached from DOM, the method throws an error.
    759   */
    760  @throwIfDisposed()
    761  @bindIsolatedHandle
    762  async click(
    763    this: ElementHandle<Element>,
    764    options: Readonly<ClickOptions> = {},
    765  ): Promise<void> {
    766    await this.scrollIntoViewIfNeeded();
    767    const {x, y} = await this.clickablePoint(options.offset);
    768    await this.frame.page().mouse.click(x, y, options);
    769  }
    770 
    771  /**
    772   * Drags an element over the given element or point.
    773   *
    774   * @returns DEPRECATED. When drag interception is enabled, the drag payload is
    775   * returned.
    776   */
    777  @throwIfDisposed()
    778  @bindIsolatedHandle
    779  async drag(
    780    this: ElementHandle<Element>,
    781    target: Point | ElementHandle<Element>,
    782  ): Promise<Protocol.Input.DragData | void> {
    783    await this.scrollIntoViewIfNeeded();
    784    const page = this.frame.page();
    785    if (page.isDragInterceptionEnabled()) {
    786      const source = await this.clickablePoint();
    787      if (target instanceof ElementHandle) {
    788        target = await target.clickablePoint();
    789      }
    790      return await page.mouse.drag(source, target);
    791    }
    792    try {
    793      if (!page._isDragging) {
    794        page._isDragging = true;
    795        await this.hover();
    796        await page.mouse.down();
    797      }
    798      if (target instanceof ElementHandle) {
    799        await target.hover();
    800      } else {
    801        await page.mouse.move(target.x, target.y);
    802      }
    803    } catch (error) {
    804      page._isDragging = false;
    805      throw error;
    806    }
    807  }
    808 
    809  /**
    810   * @deprecated Do not use. `dragenter` will automatically be performed during dragging.
    811   */
    812  @throwIfDisposed()
    813  @bindIsolatedHandle
    814  async dragEnter(
    815    this: ElementHandle<Element>,
    816    data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1},
    817  ): Promise<void> {
    818    const page = this.frame.page();
    819    await this.scrollIntoViewIfNeeded();
    820    const target = await this.clickablePoint();
    821    await page.mouse.dragEnter(target, data);
    822  }
    823 
    824  /**
    825   * @deprecated Do not use. `dragover` will automatically be performed during dragging.
    826   */
    827  @throwIfDisposed()
    828  @bindIsolatedHandle
    829  async dragOver(
    830    this: ElementHandle<Element>,
    831    data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1},
    832  ): Promise<void> {
    833    const page = this.frame.page();
    834    await this.scrollIntoViewIfNeeded();
    835    const target = await this.clickablePoint();
    836    await page.mouse.dragOver(target, data);
    837  }
    838 
    839  /**
    840   * Drops the given element onto the current one.
    841   */
    842  async drop(
    843    this: ElementHandle<Element>,
    844    element: ElementHandle<Element>,
    845  ): Promise<void>;
    846 
    847  /**
    848   * @deprecated No longer supported.
    849   */
    850  async drop(
    851    this: ElementHandle<Element>,
    852    data?: Protocol.Input.DragData,
    853  ): Promise<void>;
    854 
    855  /**
    856   * @internal
    857   */
    858  @throwIfDisposed()
    859  @bindIsolatedHandle
    860  async drop(
    861    this: ElementHandle<Element>,
    862    dataOrElement: ElementHandle<Element> | Protocol.Input.DragData = {
    863      items: [],
    864      dragOperationsMask: 1,
    865    },
    866  ): Promise<void> {
    867    const page = this.frame.page();
    868    if ('items' in dataOrElement) {
    869      await this.scrollIntoViewIfNeeded();
    870      const destination = await this.clickablePoint();
    871      await page.mouse.drop(destination, dataOrElement);
    872    } else {
    873      // Note if the rest errors, we still want dragging off because the errors
    874      // is most likely something implying the mouse is no longer dragging.
    875      await dataOrElement.drag(this);
    876      page._isDragging = false;
    877      await page.mouse.up();
    878    }
    879  }
    880 
    881  /**
    882   * @deprecated Use `ElementHandle.drop` instead.
    883   */
    884  @throwIfDisposed()
    885  @bindIsolatedHandle
    886  async dragAndDrop(
    887    this: ElementHandle<Element>,
    888    target: ElementHandle<Node>,
    889    options?: {delay: number},
    890  ): Promise<void> {
    891    const page = this.frame.page();
    892    assert(
    893      page.isDragInterceptionEnabled(),
    894      'Drag Interception is not enabled!',
    895    );
    896    await this.scrollIntoViewIfNeeded();
    897    const startPoint = await this.clickablePoint();
    898    const targetPoint = await target.clickablePoint();
    899    await page.mouse.dragAndDrop(startPoint, targetPoint, options);
    900  }
    901 
    902  /**
    903   * Triggers a `change` and `input` event once all the provided options have been
    904   * selected. If there's no `<select>` element matching `selector`, the method
    905   * throws an error.
    906   *
    907   * @example
    908   *
    909   * ```ts
    910   * handle.select('blue'); // single selection
    911   * handle.select('red', 'green', 'blue'); // multiple selections
    912   * ```
    913   *
    914   * @param values - Values of options to select. If the `<select>` has the
    915   * `multiple` attribute, all values are considered, otherwise only the first
    916   * one is taken into account.
    917   */
    918  @throwIfDisposed()
    919  @bindIsolatedHandle
    920  async select(...values: string[]): Promise<string[]> {
    921    for (const value of values) {
    922      assert(
    923        isString(value),
    924        'Values must be strings. Found value "' +
    925          value +
    926          '" of type "' +
    927          typeof value +
    928          '"',
    929      );
    930    }
    931 
    932    return await this.evaluate((element, vals): string[] => {
    933      const values = new Set(vals);
    934      if (!(element instanceof HTMLSelectElement)) {
    935        throw new Error('Element is not a <select> element.');
    936      }
    937 
    938      const selectedValues = new Set<string>();
    939      if (!element.multiple) {
    940        for (const option of element.options) {
    941          option.selected = false;
    942        }
    943        for (const option of element.options) {
    944          if (values.has(option.value)) {
    945            option.selected = true;
    946            selectedValues.add(option.value);
    947            break;
    948          }
    949        }
    950      } else {
    951        for (const option of element.options) {
    952          option.selected = values.has(option.value);
    953          if (option.selected) {
    954            selectedValues.add(option.value);
    955          }
    956        }
    957      }
    958      element.dispatchEvent(new Event('input', {bubbles: true}));
    959      element.dispatchEvent(new Event('change', {bubbles: true}));
    960      return [...selectedValues.values()];
    961    }, values);
    962  }
    963 
    964  /**
    965   * Sets the value of an
    966   * {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input | input element}
    967   * to the given file paths.
    968   *
    969   * @remarks This will not validate whether the file paths exists. Also, if a
    970   * path is relative, then it is resolved against the
    971   * {@link https://nodejs.org/api/process.html#process_process_cwd | current working directory}.
    972   * For locals script connecting to remote chrome environments, paths must be
    973   * absolute.
    974   */
    975  abstract uploadFile(
    976    this: ElementHandle<HTMLInputElement>,
    977    ...paths: string[]
    978  ): Promise<void>;
    979 
    980  /**
    981   * @internal
    982   */
    983  abstract queryAXTree(
    984    name?: string,
    985    role?: string,
    986  ): AwaitableIterable<ElementHandle<Node>>;
    987 
    988  /**
    989   * This method scrolls element into view if needed, and then uses
    990   * {@link Touchscreen.tap} to tap in the center of the element.
    991   * If the element is detached from DOM, the method throws an error.
    992   */
    993  @throwIfDisposed()
    994  @bindIsolatedHandle
    995  async tap(this: ElementHandle<Element>): Promise<void> {
    996    await this.scrollIntoViewIfNeeded();
    997    const {x, y} = await this.clickablePoint();
    998    await this.frame.page().touchscreen.tap(x, y);
    999  }
   1000 
   1001  /**
   1002   * This method scrolls the element into view if needed, and then
   1003   * starts a touch in the center of the element.
   1004   * @returns A {@link TouchHandle} representing the touch that was started
   1005   */
   1006  @throwIfDisposed()
   1007  @bindIsolatedHandle
   1008  async touchStart(this: ElementHandle<Element>): Promise<TouchHandle> {
   1009    await this.scrollIntoViewIfNeeded();
   1010    const {x, y} = await this.clickablePoint();
   1011    return await this.frame.page().touchscreen.touchStart(x, y);
   1012  }
   1013 
   1014  /**
   1015   * This method scrolls the element into view if needed, and then
   1016   * moves the touch to the center of the element.
   1017   * @param touch - An optional {@link TouchHandle}. If provided, this touch
   1018   * will be moved. If not provided, the first active touch will be moved.
   1019   */
   1020  @throwIfDisposed()
   1021  @bindIsolatedHandle
   1022  async touchMove(
   1023    this: ElementHandle<Element>,
   1024    touch?: TouchHandle,
   1025  ): Promise<void> {
   1026    await this.scrollIntoViewIfNeeded();
   1027    const {x, y} = await this.clickablePoint();
   1028    if (touch) {
   1029      return await touch.move(x, y);
   1030    }
   1031    await this.frame.page().touchscreen.touchMove(x, y);
   1032  }
   1033 
   1034  @throwIfDisposed()
   1035  @bindIsolatedHandle
   1036  async touchEnd(this: ElementHandle<Element>): Promise<void> {
   1037    await this.scrollIntoViewIfNeeded();
   1038    await this.frame.page().touchscreen.touchEnd();
   1039  }
   1040 
   1041  /**
   1042   * Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element.
   1043   */
   1044  @throwIfDisposed()
   1045  @bindIsolatedHandle
   1046  async focus(): Promise<void> {
   1047    await this.evaluate(element => {
   1048      if (!(element instanceof HTMLElement)) {
   1049        throw new Error('Cannot focus non-HTMLElement');
   1050      }
   1051      return element.focus();
   1052    });
   1053  }
   1054 
   1055  /**
   1056   * Focuses the element, and then sends a `keydown`, `keypress`/`input`, and
   1057   * `keyup` event for each character in the text.
   1058   *
   1059   * To press a special key, like `Control` or `ArrowDown`,
   1060   * use {@link ElementHandle.press}.
   1061   *
   1062   * @example
   1063   *
   1064   * ```ts
   1065   * await elementHandle.type('Hello'); // Types instantly
   1066   * await elementHandle.type('World', {delay: 100}); // Types slower, like a user
   1067   * ```
   1068   *
   1069   * @example
   1070   * An example of typing into a text field and then submitting the form:
   1071   *
   1072   * ```ts
   1073   * const elementHandle = await page.$('input');
   1074   * await elementHandle.type('some text');
   1075   * await elementHandle.press('Enter');
   1076   * ```
   1077   *
   1078   * @param options - Delay in milliseconds. Defaults to 0.
   1079   */
   1080  @throwIfDisposed()
   1081  @bindIsolatedHandle
   1082  async type(
   1083    text: string,
   1084    options?: Readonly<KeyboardTypeOptions>,
   1085  ): Promise<void> {
   1086    await this.focus();
   1087    await this.frame.page().keyboard.type(text, options);
   1088  }
   1089 
   1090  /**
   1091   * Focuses the element, and then uses {@link Keyboard.down} and {@link Keyboard.up}.
   1092   *
   1093   * @remarks
   1094   * If `key` is a single character and no modifier keys besides `Shift`
   1095   * are being held down, a `keypress`/`input` event will also be generated.
   1096   * The `text` option can be specified to force an input event to be generated.
   1097   *
   1098   * **NOTE** Modifier keys DO affect `elementHandle.press`. Holding down `Shift`
   1099   * will type the text in upper case.
   1100   *
   1101   * @param key - Name of key to press, such as `ArrowLeft`.
   1102   * See {@link KeyInput} for a list of all key names.
   1103   */
   1104  @throwIfDisposed()
   1105  @bindIsolatedHandle
   1106  async press(
   1107    key: KeyInput,
   1108    options?: Readonly<KeyPressOptions>,
   1109  ): Promise<void> {
   1110    await this.focus();
   1111    await this.frame.page().keyboard.press(key, options);
   1112  }
   1113 
   1114  async #clickableBox(): Promise<BoundingBox | null> {
   1115    const boxes = await this.evaluate(element => {
   1116      if (!(element instanceof Element)) {
   1117        return null;
   1118      }
   1119      return [...element.getClientRects()].map(rect => {
   1120        return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
   1121      });
   1122    });
   1123    if (!boxes?.length) {
   1124      return null;
   1125    }
   1126    await this.#intersectBoundingBoxesWithFrame(boxes);
   1127    let frame = this.frame;
   1128    let parentFrame: Frame | null | undefined;
   1129    while ((parentFrame = frame?.parentFrame())) {
   1130      using handle = await frame.frameElement();
   1131      if (!handle) {
   1132        throw new Error('Unsupported frame type');
   1133      }
   1134      const parentBox = await handle.evaluate(element => {
   1135        // Element is not visible.
   1136        if (element.getClientRects().length === 0) {
   1137          return null;
   1138        }
   1139        const rect = element.getBoundingClientRect();
   1140        const style = window.getComputedStyle(element);
   1141        return {
   1142          left:
   1143            rect.left +
   1144            parseInt(style.paddingLeft, 10) +
   1145            parseInt(style.borderLeftWidth, 10),
   1146          top:
   1147            rect.top +
   1148            parseInt(style.paddingTop, 10) +
   1149            parseInt(style.borderTopWidth, 10),
   1150        };
   1151      });
   1152      if (!parentBox) {
   1153        return null;
   1154      }
   1155      for (const box of boxes) {
   1156        box.x += parentBox.left;
   1157        box.y += parentBox.top;
   1158      }
   1159      await handle.#intersectBoundingBoxesWithFrame(boxes);
   1160      frame = parentFrame;
   1161    }
   1162    const box = boxes.find(box => {
   1163      return box.width >= 1 && box.height >= 1;
   1164    });
   1165    if (!box) {
   1166      return null;
   1167    }
   1168    return {
   1169      x: box.x,
   1170      y: box.y,
   1171      height: box.height,
   1172      width: box.width,
   1173    };
   1174  }
   1175 
   1176  async #intersectBoundingBoxesWithFrame(boxes: BoundingBox[]) {
   1177    const {documentWidth, documentHeight} = await this.frame
   1178      .isolatedRealm()
   1179      .evaluate(() => {
   1180        return {
   1181          documentWidth: document.documentElement.clientWidth,
   1182          documentHeight: document.documentElement.clientHeight,
   1183        };
   1184      });
   1185    for (const box of boxes) {
   1186      intersectBoundingBox(box, documentWidth, documentHeight);
   1187    }
   1188  }
   1189 
   1190  /**
   1191   * This method returns the bounding box of the element (relative to the main frame),
   1192   * or `null` if the element is {@link https://drafts.csswg.org/css-display-4/#box-generation | not part of the layout}
   1193   * (example: `display: none`).
   1194   */
   1195  @throwIfDisposed()
   1196  @bindIsolatedHandle
   1197  async boundingBox(): Promise<BoundingBox | null> {
   1198    const box = await this.evaluate(element => {
   1199      if (!(element instanceof Element)) {
   1200        return null;
   1201      }
   1202      // Element is not visible.
   1203      if (element.getClientRects().length === 0) {
   1204        return null;
   1205      }
   1206      const rect = element.getBoundingClientRect();
   1207      return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
   1208    });
   1209    if (!box) {
   1210      return null;
   1211    }
   1212    const offset = await this.#getTopLeftCornerOfFrame();
   1213    if (!offset) {
   1214      return null;
   1215    }
   1216    return {
   1217      x: box.x + offset.x,
   1218      y: box.y + offset.y,
   1219      height: box.height,
   1220      width: box.width,
   1221    };
   1222  }
   1223 
   1224  /**
   1225   * This method returns boxes of the element,
   1226   * or `null` if the element is {@link https://drafts.csswg.org/css-display-4/#box-generation | not part of the layout}
   1227   * (example: `display: none`).
   1228   *
   1229   * @remarks
   1230   *
   1231   * Boxes are represented as an array of points;
   1232   * Each Point is an object `{x, y}`. Box points are sorted clock-wise.
   1233   */
   1234  @throwIfDisposed()
   1235  @bindIsolatedHandle
   1236  async boxModel(): Promise<BoxModel | null> {
   1237    const model = await this.evaluate(element => {
   1238      if (!(element instanceof Element)) {
   1239        return null;
   1240      }
   1241      // Element is not visible.
   1242      if (element.getClientRects().length === 0) {
   1243        return null;
   1244      }
   1245      const rect = element.getBoundingClientRect();
   1246      const style = window.getComputedStyle(element);
   1247      const offsets = {
   1248        padding: {
   1249          left: parseInt(style.paddingLeft, 10),
   1250          top: parseInt(style.paddingTop, 10),
   1251          right: parseInt(style.paddingRight, 10),
   1252          bottom: parseInt(style.paddingBottom, 10),
   1253        },
   1254        margin: {
   1255          left: -parseInt(style.marginLeft, 10),
   1256          top: -parseInt(style.marginTop, 10),
   1257          right: -parseInt(style.marginRight, 10),
   1258          bottom: -parseInt(style.marginBottom, 10),
   1259        },
   1260        border: {
   1261          left: parseInt(style.borderLeft, 10),
   1262          top: parseInt(style.borderTop, 10),
   1263          right: parseInt(style.borderRight, 10),
   1264          bottom: parseInt(style.borderBottom, 10),
   1265        },
   1266      };
   1267      const border: Quad = [
   1268        {x: rect.left, y: rect.top},
   1269        {x: rect.left + rect.width, y: rect.top},
   1270        {x: rect.left + rect.width, y: rect.top + rect.height},
   1271        {x: rect.left, y: rect.top + rect.height},
   1272      ];
   1273      const padding = transformQuadWithOffsets(border, offsets.border);
   1274      const content = transformQuadWithOffsets(padding, offsets.padding);
   1275      const margin = transformQuadWithOffsets(border, offsets.margin);
   1276      return {
   1277        content,
   1278        padding,
   1279        border,
   1280        margin,
   1281        width: rect.width,
   1282        height: rect.height,
   1283      };
   1284 
   1285      function transformQuadWithOffsets(
   1286        quad: Quad,
   1287        offsets: {top: number; left: number; right: number; bottom: number},
   1288      ): Quad {
   1289        return [
   1290          {
   1291            x: quad[0].x + offsets.left,
   1292            y: quad[0].y + offsets.top,
   1293          },
   1294          {
   1295            x: quad[1].x - offsets.right,
   1296            y: quad[1].y + offsets.top,
   1297          },
   1298          {
   1299            x: quad[2].x - offsets.right,
   1300            y: quad[2].y - offsets.bottom,
   1301          },
   1302          {
   1303            x: quad[3].x + offsets.left,
   1304            y: quad[3].y - offsets.bottom,
   1305          },
   1306        ];
   1307      }
   1308    });
   1309    if (!model) {
   1310      return null;
   1311    }
   1312    const offset = await this.#getTopLeftCornerOfFrame();
   1313    if (!offset) {
   1314      return null;
   1315    }
   1316    for (const attribute of [
   1317      'content',
   1318      'padding',
   1319      'border',
   1320      'margin',
   1321    ] as const) {
   1322      for (const point of model[attribute]) {
   1323        point.x += offset.x;
   1324        point.y += offset.y;
   1325      }
   1326    }
   1327    return model;
   1328  }
   1329 
   1330  async #getTopLeftCornerOfFrame() {
   1331    const point = {x: 0, y: 0};
   1332    let frame = this.frame;
   1333    let parentFrame: Frame | null | undefined;
   1334    while ((parentFrame = frame?.parentFrame())) {
   1335      using handle = await frame.frameElement();
   1336      if (!handle) {
   1337        throw new Error('Unsupported frame type');
   1338      }
   1339      const parentBox = await handle.evaluate(element => {
   1340        // Element is not visible.
   1341        if (element.getClientRects().length === 0) {
   1342          return null;
   1343        }
   1344        const rect = element.getBoundingClientRect();
   1345        const style = window.getComputedStyle(element);
   1346        return {
   1347          left:
   1348            rect.left +
   1349            parseInt(style.paddingLeft, 10) +
   1350            parseInt(style.borderLeftWidth, 10),
   1351          top:
   1352            rect.top +
   1353            parseInt(style.paddingTop, 10) +
   1354            parseInt(style.borderTopWidth, 10),
   1355        };
   1356      });
   1357      if (!parentBox) {
   1358        return null;
   1359      }
   1360      point.x += parentBox.left;
   1361      point.y += parentBox.top;
   1362      frame = parentFrame;
   1363    }
   1364    return point;
   1365  }
   1366 
   1367  /**
   1368   * This method scrolls element into view if needed, and then uses
   1369   * {@link Page.(screenshot:2) } to take a screenshot of the element.
   1370   * If the element is detached from DOM, the method throws an error.
   1371   */
   1372  async screenshot(
   1373    options: Readonly<ScreenshotOptions> & {encoding: 'base64'},
   1374  ): Promise<string>;
   1375  async screenshot(options?: Readonly<ScreenshotOptions>): Promise<Uint8Array>;
   1376  @throwIfDisposed()
   1377  @bindIsolatedHandle
   1378  async screenshot(
   1379    this: ElementHandle<Element>,
   1380    options: Readonly<ElementScreenshotOptions> = {},
   1381  ): Promise<string | Uint8Array> {
   1382    const {scrollIntoView = true, clip} = options;
   1383 
   1384    const page = this.frame.page();
   1385 
   1386    // Only scroll the element into view if the user wants it.
   1387    if (scrollIntoView) {
   1388      await this.scrollIntoViewIfNeeded();
   1389    }
   1390    const elementClip = await this.#nonEmptyVisibleBoundingBox();
   1391 
   1392    const [pageLeft, pageTop] = await this.evaluate(() => {
   1393      if (!window.visualViewport) {
   1394        throw new Error('window.visualViewport is not supported.');
   1395      }
   1396      return [
   1397        window.visualViewport.pageLeft,
   1398        window.visualViewport.pageTop,
   1399      ] as const;
   1400    });
   1401    elementClip.x += pageLeft;
   1402    elementClip.y += pageTop;
   1403    if (clip) {
   1404      elementClip.x += clip.x;
   1405      elementClip.y += clip.y;
   1406      elementClip.height = clip.height;
   1407      elementClip.width = clip.width;
   1408    }
   1409 
   1410    return await page.screenshot({...options, clip: elementClip});
   1411  }
   1412 
   1413  async #nonEmptyVisibleBoundingBox() {
   1414    const box = await this.boundingBox();
   1415    assert(box, 'Node is either not visible or not an HTMLElement');
   1416    assert(box.width !== 0, 'Node has 0 width.');
   1417    assert(box.height !== 0, 'Node has 0 height.');
   1418    return box;
   1419  }
   1420 
   1421  /**
   1422   * @internal
   1423   */
   1424  protected async assertConnectedElement(): Promise<void> {
   1425    const error = await this.evaluate(async element => {
   1426      if (!element.isConnected) {
   1427        return 'Node is detached from document';
   1428      }
   1429      if (element.nodeType !== Node.ELEMENT_NODE) {
   1430        return 'Node is not of type HTMLElement';
   1431      }
   1432      return;
   1433    });
   1434 
   1435    if (error) {
   1436      throw new Error(error);
   1437    }
   1438  }
   1439 
   1440  /**
   1441   * @internal
   1442   */
   1443  protected async scrollIntoViewIfNeeded(
   1444    this: ElementHandle<Element>,
   1445  ): Promise<void> {
   1446    if (
   1447      await this.isIntersectingViewport({
   1448        threshold: 1,
   1449      })
   1450    ) {
   1451      return;
   1452    }
   1453    await this.scrollIntoView();
   1454  }
   1455 
   1456  /**
   1457   * Resolves to true if the element is visible in the current viewport. If an
   1458   * element is an SVG, we check if the svg owner element is in the viewport
   1459   * instead. See https://crbug.com/963246.
   1460   *
   1461   * @param options - Threshold for the intersection between 0 (no intersection) and 1
   1462   * (full intersection). Defaults to 1.
   1463   */
   1464  @throwIfDisposed()
   1465  @bindIsolatedHandle
   1466  async isIntersectingViewport(
   1467    this: ElementHandle<Element>,
   1468    options: {
   1469      threshold?: number;
   1470    } = {},
   1471  ): Promise<boolean> {
   1472    await this.assertConnectedElement();
   1473    // eslint-disable-next-line rulesdir/use-using -- Returns `this`.
   1474    const handle = await this.#asSVGElementHandle();
   1475    using target = handle && (await handle.#getOwnerSVGElement());
   1476    return await ((target ?? this) as ElementHandle<Element>).evaluate(
   1477      async (element, threshold) => {
   1478        const visibleRatio = await new Promise<number>(resolve => {
   1479          const observer = new IntersectionObserver(entries => {
   1480            resolve(entries[0]!.intersectionRatio);
   1481            observer.disconnect();
   1482          });
   1483          observer.observe(element);
   1484        });
   1485        return threshold === 1 ? visibleRatio === 1 : visibleRatio > threshold;
   1486      },
   1487      options.threshold ?? 0,
   1488    );
   1489  }
   1490 
   1491  /**
   1492   * Scrolls the element into view using either the automation protocol client
   1493   * or by calling element.scrollIntoView.
   1494   */
   1495  @throwIfDisposed()
   1496  @bindIsolatedHandle
   1497  async scrollIntoView(this: ElementHandle<Element>): Promise<void> {
   1498    await this.assertConnectedElement();
   1499    await this.evaluate(async (element): Promise<void> => {
   1500      element.scrollIntoView({
   1501        block: 'center',
   1502        inline: 'center',
   1503        behavior: 'instant',
   1504      });
   1505    });
   1506  }
   1507 
   1508  /**
   1509   * Returns true if an element is an SVGElement (included svg, path, rect
   1510   * etc.).
   1511   */
   1512  async #asSVGElementHandle(
   1513    this: ElementHandle<Element>,
   1514  ): Promise<ElementHandle<SVGElement> | null> {
   1515    if (
   1516      await this.evaluate(element => {
   1517        return element instanceof SVGElement;
   1518      })
   1519    ) {
   1520      return this as ElementHandle<SVGElement>;
   1521    } else {
   1522      return null;
   1523    }
   1524  }
   1525 
   1526  async #getOwnerSVGElement(
   1527    this: ElementHandle<SVGElement>,
   1528  ): Promise<ElementHandle<SVGSVGElement>> {
   1529    // SVGSVGElement.ownerSVGElement === null.
   1530    return await this.evaluateHandle(element => {
   1531      if (element instanceof SVGSVGElement) {
   1532        return element;
   1533      }
   1534      return element.ownerSVGElement!;
   1535    });
   1536  }
   1537 
   1538  /**
   1539   * If the element is a form input, you can use {@link ElementHandle.autofill}
   1540   * to test if the form is compatible with the browser's autofill
   1541   * implementation. Throws an error if the form cannot be autofilled.
   1542   *
   1543   * @remarks
   1544   *
   1545   * Currently, Puppeteer supports auto-filling credit card information only and
   1546   * in Chrome in the new headless and headful modes only.
   1547   *
   1548   * ```ts
   1549   * // Select an input on the credit card form.
   1550   * const name = await page.waitForSelector('form #name');
   1551   * // Trigger autofill with the desired data.
   1552   * await name.autofill({
   1553   *   creditCard: {
   1554   *     number: '4444444444444444',
   1555   *     name: 'John Smith',
   1556   *     expiryMonth: '01',
   1557   *     expiryYear: '2030',
   1558   *     cvc: '123',
   1559   *   },
   1560   * });
   1561   * ```
   1562   */
   1563  abstract autofill(data: AutofillData): Promise<void>;
   1564 
   1565  /**
   1566   * When connected using Chrome DevTools Protocol, it returns a
   1567   * DOM.BackendNodeId for the element.
   1568   */
   1569  abstract backendNodeId(): Promise<number>;
   1570 }
   1571 
   1572 /**
   1573 * @public
   1574 */
   1575 export interface AutofillData {
   1576  /**
   1577   * See {@link https://chromedevtools.github.io/devtools-protocol/tot/Autofill/#type-CreditCard | Autofill.CreditCard}.
   1578   */
   1579  creditCard: {
   1580    number: string;
   1581    name: string;
   1582    expiryMonth: string;
   1583    expiryYear: string;
   1584    cvc: string;
   1585  };
   1586 }
   1587 
   1588 function intersectBoundingBox(
   1589  box: BoundingBox,
   1590  width: number,
   1591  height: number,
   1592 ): void {
   1593  box.width = Math.max(
   1594    box.x >= 0
   1595      ? Math.min(width - box.x, box.width)
   1596      : Math.min(width, box.width + box.x),
   1597    0,
   1598  );
   1599  box.height = Math.max(
   1600    box.y >= 0
   1601      ? Math.min(height - box.y, box.height)
   1602      : Math.min(height, box.height + box.y),
   1603    0,
   1604  );
   1605 }