tor-browser

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

CustomQueryHandler.ts (4824B)


      1 /**
      2 * @license
      3 * Copyright 2023 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import type PuppeteerUtil from '../injected/injected.js';
      8 import {assert} from '../util/assert.js';
      9 import {interpolateFunction, stringifyFunction} from '../util/Function.js';
     10 
     11 import {
     12  QueryHandler,
     13  type QuerySelector,
     14  type QuerySelectorAll,
     15 } from './QueryHandler.js';
     16 import {scriptInjector} from './ScriptInjector.js';
     17 
     18 /**
     19 * @public
     20 */
     21 export interface CustomQueryHandler {
     22  /**
     23   * Searches for a {@link https://developer.mozilla.org/en-US/docs/Web/API/Node | Node} matching the given `selector` from {@link https://developer.mozilla.org/en-US/docs/Web/API/Node | node}.
     24   */
     25  queryOne?: (node: Node, selector: string) => Node | null;
     26  /**
     27   * Searches for some {@link https://developer.mozilla.org/en-US/docs/Web/API/Node | Nodes} matching the given `selector` from {@link https://developer.mozilla.org/en-US/docs/Web/API/Node | node}.
     28   */
     29  queryAll?: (node: Node, selector: string) => Iterable<Node>;
     30 }
     31 
     32 /**
     33 * The registry of {@link CustomQueryHandler | custom query handlers}.
     34 *
     35 * @example
     36 *
     37 * ```ts
     38 * Puppeteer.customQueryHandlers.register('lit', { … });
     39 * const aHandle = await page.$('lit/…');
     40 * ```
     41 *
     42 * @internal
     43 */
     44 export class CustomQueryHandlerRegistry {
     45  #handlers = new Map<
     46    string,
     47    [registerScript: string, Handler: typeof QueryHandler]
     48  >();
     49 
     50  get(name: string): typeof QueryHandler | undefined {
     51    const handler = this.#handlers.get(name);
     52    return handler ? handler[1] : undefined;
     53  }
     54 
     55  /**
     56   * Registers a {@link CustomQueryHandler | custom query handler}.
     57   *
     58   * @remarks
     59   * After registration, the handler can be used everywhere where a selector is
     60   * expected by prepending the selection string with `<name>/`. The name is
     61   * only allowed to consist of lower- and upper case latin letters.
     62   *
     63   * @example
     64   *
     65   * ```ts
     66   * Puppeteer.customQueryHandlers.register('lit', { … });
     67   * const aHandle = await page.$('lit/…');
     68   * ```
     69   *
     70   * @param name - Name to register under.
     71   * @param queryHandler - {@link CustomQueryHandler | Custom query handler} to
     72   * register.
     73   */
     74  register(name: string, handler: CustomQueryHandler): void {
     75    assert(
     76      !this.#handlers.has(name),
     77      `Cannot register over existing handler: ${name}`,
     78    );
     79    assert(
     80      /^[a-zA-Z]+$/.test(name),
     81      `Custom query handler names may only contain [a-zA-Z]`,
     82    );
     83    assert(
     84      handler.queryAll || handler.queryOne,
     85      `At least one query method must be implemented.`,
     86    );
     87 
     88    const Handler = class extends QueryHandler {
     89      static override querySelectorAll: QuerySelectorAll = interpolateFunction(
     90        (node, selector, PuppeteerUtil) => {
     91          return PuppeteerUtil.customQuerySelectors
     92            .get(PLACEHOLDER('name'))!
     93            .querySelectorAll(node, selector);
     94        },
     95        {name: JSON.stringify(name)},
     96      );
     97      static override querySelector: QuerySelector = interpolateFunction(
     98        (node, selector, PuppeteerUtil) => {
     99          return PuppeteerUtil.customQuerySelectors
    100            .get(PLACEHOLDER('name'))!
    101            .querySelector(node, selector);
    102        },
    103        {name: JSON.stringify(name)},
    104      );
    105    };
    106    const registerScript = interpolateFunction(
    107      (PuppeteerUtil: PuppeteerUtil) => {
    108        PuppeteerUtil.customQuerySelectors.register(PLACEHOLDER('name'), {
    109          queryAll: PLACEHOLDER('queryAll'),
    110          queryOne: PLACEHOLDER('queryOne'),
    111        });
    112      },
    113      {
    114        name: JSON.stringify(name),
    115        queryAll: handler.queryAll
    116          ? stringifyFunction(handler.queryAll)
    117          : String(undefined),
    118        queryOne: handler.queryOne
    119          ? stringifyFunction(handler.queryOne)
    120          : String(undefined),
    121      },
    122    ).toString();
    123 
    124    this.#handlers.set(name, [registerScript, Handler]);
    125    scriptInjector.append(registerScript);
    126  }
    127 
    128  /**
    129   * Unregisters the {@link CustomQueryHandler | custom query handler} for the
    130   * given name.
    131   *
    132   * @throws `Error` if there is no handler under the given name.
    133   */
    134  unregister(name: string): void {
    135    const handler = this.#handlers.get(name);
    136    if (!handler) {
    137      throw new Error(`Cannot unregister unknown handler: ${name}`);
    138    }
    139    scriptInjector.pop(handler[0]);
    140    this.#handlers.delete(name);
    141  }
    142 
    143  /**
    144   * Gets the names of all {@link CustomQueryHandler | custom query handlers}.
    145   */
    146  names(): string[] {
    147    return [...this.#handlers.keys()];
    148  }
    149 
    150  /**
    151   * Unregisters all custom query handlers.
    152   */
    153  clear(): void {
    154    for (const [registerScript] of this.#handlers) {
    155      scriptInjector.pop(registerScript);
    156    }
    157    this.#handlers.clear();
    158  }
    159 }
    160 
    161 /**
    162 * @internal
    163 */
    164 export const customQueryHandlers = new CustomQueryHandlerRegistry();