tor-browser

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

QueryHandler.ts (5972B)


      1 /**
      2 * @license
      3 * Copyright 2023 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import type {ElementHandle} from '../api/ElementHandle.js';
      8 import {_isElementHandle} from '../api/ElementHandleSymbol.js';
      9 import type {Frame} from '../api/Frame.js';
     10 import type {WaitForSelectorOptions} from '../api/Page.js';
     11 import type PuppeteerUtil from '../injected/injected.js';
     12 import {isErrorLike} from '../util/ErrorLike.js';
     13 import {interpolateFunction, stringifyFunction} from '../util/Function.js';
     14 
     15 import {transposeIterableHandle} from './HandleIterator.js';
     16 import {LazyArg} from './LazyArg.js';
     17 import type {Awaitable, AwaitableIterable} from './types.js';
     18 
     19 /**
     20 * @internal
     21 */
     22 export type QuerySelectorAll = (
     23  node: Node,
     24  selector: string,
     25  PuppeteerUtil: PuppeteerUtil,
     26 ) => AwaitableIterable<Node>;
     27 
     28 /**
     29 * @internal
     30 */
     31 export type QuerySelector = (
     32  node: Node,
     33  selector: string,
     34  PuppeteerUtil: PuppeteerUtil,
     35 ) => Awaitable<Node | null>;
     36 
     37 /**
     38 * @internal
     39 */
     40 export const enum PollingOptions {
     41  RAF = 'raf',
     42  MUTATION = 'mutation',
     43 }
     44 
     45 /**
     46 * @internal
     47 */
     48 export class QueryHandler {
     49  // Either one of these may be implemented, but at least one must be.
     50  static querySelectorAll?: QuerySelectorAll;
     51  static querySelector?: QuerySelector;
     52 
     53  static get _querySelector(): QuerySelector {
     54    if (this.querySelector) {
     55      return this.querySelector;
     56    }
     57    if (!this.querySelectorAll) {
     58      throw new Error('Cannot create default `querySelector`.');
     59    }
     60 
     61    return (this.querySelector = interpolateFunction(
     62      async (node, selector, PuppeteerUtil) => {
     63        const querySelectorAll: QuerySelectorAll =
     64          PLACEHOLDER('querySelectorAll');
     65        const results = querySelectorAll(node, selector, PuppeteerUtil);
     66        for await (const result of results) {
     67          return result;
     68        }
     69        return null;
     70      },
     71      {
     72        querySelectorAll: stringifyFunction(this.querySelectorAll),
     73      },
     74    ));
     75  }
     76 
     77  static get _querySelectorAll(): QuerySelectorAll {
     78    if (this.querySelectorAll) {
     79      return this.querySelectorAll;
     80    }
     81    if (!this.querySelector) {
     82      throw new Error('Cannot create default `querySelectorAll`.');
     83    }
     84 
     85    return (this.querySelectorAll = interpolateFunction(
     86      async function* (node, selector, PuppeteerUtil) {
     87        const querySelector: QuerySelector = PLACEHOLDER('querySelector');
     88        const result = await querySelector(node, selector, PuppeteerUtil);
     89        if (result) {
     90          yield result;
     91        }
     92      },
     93      {
     94        querySelector: stringifyFunction(this.querySelector),
     95      },
     96    ));
     97  }
     98 
     99  /**
    100   * Queries for multiple nodes given a selector and {@link ElementHandle}.
    101   *
    102   * Akin to {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll | Document.querySelectorAll()}.
    103   */
    104  static async *queryAll(
    105    element: ElementHandle<Node>,
    106    selector: string,
    107  ): AwaitableIterable<ElementHandle<Node>> {
    108    using handle = await element.evaluateHandle(
    109      this._querySelectorAll,
    110      selector,
    111      LazyArg.create(context => {
    112        return context.puppeteerUtil;
    113      }),
    114    );
    115    yield* transposeIterableHandle(handle);
    116  }
    117 
    118  /**
    119   * Queries for a single node given a selector and {@link ElementHandle}.
    120   *
    121   * Akin to {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector}.
    122   */
    123  static async queryOne(
    124    element: ElementHandle<Node>,
    125    selector: string,
    126  ): Promise<ElementHandle<Node> | null> {
    127    using result = await element.evaluateHandle(
    128      this._querySelector,
    129      selector,
    130      LazyArg.create(context => {
    131        return context.puppeteerUtil;
    132      }),
    133    );
    134    if (!(_isElementHandle in result)) {
    135      return null;
    136    }
    137    return result.move();
    138  }
    139 
    140  /**
    141   * Waits until a single node appears for a given selector and
    142   * {@link ElementHandle}.
    143   *
    144   * This will always query the handle in the Puppeteer world and migrate the
    145   * result to the main world.
    146   */
    147  static async waitFor(
    148    elementOrFrame: ElementHandle<Node> | Frame,
    149    selector: string,
    150    options: WaitForSelectorOptions & {
    151      polling?: PollingOptions;
    152    },
    153  ): Promise<ElementHandle<Node> | null> {
    154    let frame!: Frame;
    155    using element = await (async () => {
    156      if (!(_isElementHandle in elementOrFrame)) {
    157        frame = elementOrFrame;
    158        return;
    159      }
    160      frame = elementOrFrame.frame;
    161      return await frame.isolatedRealm().adoptHandle(elementOrFrame);
    162    })();
    163 
    164    const {visible = false, hidden = false, timeout, signal} = options;
    165    const polling = visible || hidden ? PollingOptions.RAF : options.polling;
    166 
    167    try {
    168      signal?.throwIfAborted();
    169 
    170      using handle = await frame.isolatedRealm().waitForFunction(
    171        async (PuppeteerUtil, query, selector, root, visible) => {
    172          const querySelector = PuppeteerUtil.createFunction(
    173            query,
    174          ) as QuerySelector;
    175          const node = await querySelector(
    176            root ?? document,
    177            selector,
    178            PuppeteerUtil,
    179          );
    180          return PuppeteerUtil.checkVisibility(node, visible);
    181        },
    182        {
    183          polling,
    184          root: element,
    185          timeout,
    186          signal,
    187        },
    188        LazyArg.create(context => {
    189          return context.puppeteerUtil;
    190        }),
    191        stringifyFunction(this._querySelector),
    192        selector,
    193        element,
    194        visible ? true : hidden ? false : undefined,
    195      );
    196 
    197      if (signal?.aborted) {
    198        throw signal.reason;
    199      }
    200 
    201      if (!(_isElementHandle in handle)) {
    202        return null;
    203      }
    204      return await frame.mainRealm().transferHandle(handle);
    205    } catch (error) {
    206      if (!isErrorLike(error)) {
    207        throw error;
    208      }
    209      if (error.name === 'AbortError') {
    210        throw error;
    211      }
    212      error.message = `Waiting for selector \`${selector}\` failed: ${error.message}`;
    213      throw error;
    214    }
    215  }
    216 }