tor-browser

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

Input.ts (15661B)


      1 /**
      2 * @license
      3 * Copyright 2017 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import type {Protocol} from 'devtools-protocol';
      8 
      9 import {TouchError} from '../common/Errors.js';
     10 import type {KeyInput} from '../common/USKeyboardLayout.js';
     11 import {createIncrementalIdGenerator} from '../util/incremental-id-generator.js';
     12 
     13 import type {Point} from './ElementHandle.js';
     14 
     15 /**
     16 * @public
     17 */
     18 export interface KeyDownOptions {
     19  /**
     20   * @deprecated Do not use. This is automatically handled.
     21   */
     22  text?: string;
     23  /**
     24   * @deprecated Do not use. This is automatically handled.
     25   */
     26  commands?: string[];
     27 }
     28 
     29 /**
     30 * @public
     31 */
     32 export interface KeyboardTypeOptions {
     33  delay?: number;
     34 }
     35 
     36 /**
     37 * @public
     38 */
     39 export type KeyPressOptions = KeyDownOptions & KeyboardTypeOptions;
     40 
     41 /**
     42 * Keyboard provides an api for managing a virtual keyboard.
     43 * The high level api is {@link Keyboard."type"},
     44 * which takes raw characters and generates proper keydown, keypress/input,
     45 * and keyup events on your page.
     46 *
     47 * @remarks
     48 * For finer control, you can use {@link Keyboard.down},
     49 * {@link Keyboard.up}, and {@link Keyboard.sendCharacter}
     50 * to manually fire events as if they were generated from a real keyboard.
     51 *
     52 * On macOS, keyboard shortcuts like `⌘ A` -\> Select All do not work.
     53 * See {@link https://github.com/puppeteer/puppeteer/issues/1313 | #1313}.
     54 *
     55 * @example
     56 * An example of holding down `Shift` in order to select and delete some text:
     57 *
     58 * ```ts
     59 * await page.keyboard.type('Hello World!');
     60 * await page.keyboard.press('ArrowLeft');
     61 *
     62 * await page.keyboard.down('Shift');
     63 * for (let i = 0; i < ' World'.length; i++)
     64 *   await page.keyboard.press('ArrowLeft');
     65 * await page.keyboard.up('Shift');
     66 *
     67 * await page.keyboard.press('Backspace');
     68 * // Result text will end up saying 'Hello!'
     69 * ```
     70 *
     71 * @example
     72 * An example of pressing `A`
     73 *
     74 * ```ts
     75 * await page.keyboard.down('Shift');
     76 * await page.keyboard.press('KeyA');
     77 * await page.keyboard.up('Shift');
     78 * ```
     79 *
     80 * @public
     81 */
     82 export abstract class Keyboard {
     83  /**
     84   * @internal
     85   */
     86  constructor() {}
     87 
     88  /**
     89   * Dispatches a `keydown` event.
     90   *
     91   * @remarks
     92   * If `key` is a single character and no modifier keys besides `Shift`
     93   * are being held down, a `keypress`/`input` event will also generated.
     94   * The `text` option can be specified to force an input event to be generated.
     95   * If `key` is a modifier key, `Shift`, `Meta`, `Control`, or `Alt`,
     96   * subsequent key presses will be sent with that modifier active.
     97   * To release the modifier key, use {@link Keyboard.up}.
     98   *
     99   * After the key is pressed once, subsequent calls to
    100   * {@link Keyboard.down} will have
    101   * {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat | repeat}
    102   * set to true. To release the key, use {@link Keyboard.up}.
    103   *
    104   * Modifier keys DO influence {@link Keyboard.down}.
    105   * Holding down `Shift` will type the text in upper case.
    106   *
    107   * @param key - Name of key to press, such as `ArrowLeft`.
    108   * See {@link KeyInput} for a list of all key names.
    109   *
    110   * @param options - An object of options. Accepts text which, if specified,
    111   * generates an input event with this text. Accepts commands which, if specified,
    112   * is the commands of keyboard shortcuts,
    113   * see {@link https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/commands/editor_command_names.h | Chromium Source Code} for valid command names.
    114   */
    115  abstract down(
    116    key: KeyInput,
    117    options?: Readonly<KeyDownOptions>,
    118  ): Promise<void>;
    119 
    120  /**
    121   * Dispatches a `keyup` event.
    122   *
    123   * @param key - Name of key to release, such as `ArrowLeft`.
    124   * See {@link KeyInput | KeyInput}
    125   * for a list of all key names.
    126   */
    127  abstract up(key: KeyInput): Promise<void>;
    128 
    129  /**
    130   * Dispatches a `keypress` and `input` event.
    131   * This does not send a `keydown` or `keyup` event.
    132   *
    133   * @remarks
    134   * Modifier keys DO NOT effect {@link Keyboard.sendCharacter | Keyboard.sendCharacter}.
    135   * Holding down `Shift` will not type the text in upper case.
    136   *
    137   * @example
    138   *
    139   * ```ts
    140   * page.keyboard.sendCharacter('嗨');
    141   * ```
    142   *
    143   * @param char - Character to send into the page.
    144   */
    145  abstract sendCharacter(char: string): Promise<void>;
    146 
    147  /**
    148   * Sends a `keydown`, `keypress`/`input`,
    149   * and `keyup` event for each character in the text.
    150   *
    151   * @remarks
    152   * To press a special key, like `Control` or `ArrowDown`,
    153   * use {@link Keyboard.press}.
    154   *
    155   * Modifier keys DO NOT effect `keyboard.type`.
    156   * Holding down `Shift` will not type the text in upper case.
    157   *
    158   * @example
    159   *
    160   * ```ts
    161   * await page.keyboard.type('Hello'); // Types instantly
    162   * await page.keyboard.type('World', {delay: 100}); // Types slower, like a user
    163   * ```
    164   *
    165   * @param text - A text to type into a focused element.
    166   * @param options - An object of options. Accepts delay which,
    167   * if specified, is the time to wait between `keydown` and `keyup` in milliseconds.
    168   * Defaults to 0.
    169   */
    170  abstract type(
    171    text: string,
    172    options?: Readonly<KeyboardTypeOptions>,
    173  ): Promise<void>;
    174 
    175  /**
    176   * Shortcut for {@link Keyboard.down}
    177   * and {@link Keyboard.up}.
    178   *
    179   * @remarks
    180   * If `key` is a single character and no modifier keys besides `Shift`
    181   * are being held down, a `keypress`/`input` event will also generated.
    182   * The `text` option can be specified to force an input event to be generated.
    183   *
    184   * Modifier keys DO effect {@link Keyboard.press}.
    185   * Holding down `Shift` will type the text in upper case.
    186   *
    187   * @param key - Name of key to press, such as `ArrowLeft`.
    188   * See {@link KeyInput} for a list of all key names.
    189   *
    190   * @param options - An object of options. Accepts text which, if specified,
    191   * generates an input event with this text. Accepts delay which,
    192   * if specified, is the time to wait between `keydown` and `keyup` in milliseconds.
    193   * Defaults to 0. Accepts commands which, if specified,
    194   * is the commands of keyboard shortcuts,
    195   * see {@link https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/commands/editor_command_names.h | Chromium Source Code} for valid command names.
    196   */
    197  abstract press(
    198    key: KeyInput,
    199    options?: Readonly<KeyPressOptions>,
    200  ): Promise<void>;
    201 }
    202 
    203 /**
    204 * @public
    205 */
    206 export interface MouseOptions {
    207  /**
    208   * Determines which button will be pressed.
    209   *
    210   * @defaultValue `'left'`
    211   */
    212  button?: MouseButton;
    213  /**
    214   * Determines the click count for the mouse event. This does not perform
    215   * multiple clicks.
    216   *
    217   * @deprecated Use {@link MouseClickOptions.count}.
    218   * @defaultValue `1`
    219   */
    220  clickCount?: number;
    221 }
    222 
    223 /**
    224 * @public
    225 */
    226 export interface MouseClickOptions extends MouseOptions {
    227  /**
    228   * Time (in ms) to delay the mouse release after the mouse press.
    229   */
    230  delay?: number;
    231  /**
    232   * Number of clicks to perform.
    233   *
    234   * @defaultValue `1`
    235   */
    236  count?: number;
    237 }
    238 
    239 /**
    240 * @public
    241 */
    242 export interface MouseWheelOptions {
    243  deltaX?: number;
    244  deltaY?: number;
    245 }
    246 
    247 /**
    248 * @public
    249 */
    250 export interface MouseMoveOptions {
    251  /**
    252   * Determines the number of movements to make from the current mouse position
    253   * to the new one.
    254   *
    255   * @defaultValue `1`
    256   */
    257  steps?: number;
    258 }
    259 
    260 /**
    261 * Enum of valid mouse buttons.
    262 *
    263 * @public
    264 */
    265 export const MouseButton = Object.freeze({
    266  Left: 'left',
    267  Right: 'right',
    268  Middle: 'middle',
    269  Back: 'back',
    270  Forward: 'forward',
    271 }) satisfies Record<string, Protocol.Input.MouseButton>;
    272 
    273 /**
    274 * @public
    275 */
    276 export type MouseButton = (typeof MouseButton)[keyof typeof MouseButton];
    277 
    278 /**
    279 * The Mouse class operates in main-frame CSS pixels
    280 * relative to the top-left corner of the viewport.
    281 *
    282 * @remarks
    283 * Every `page` object has its own Mouse, accessible with {@link Page.mouse}.
    284 *
    285 * @example
    286 *
    287 * ```ts
    288 * // Using ‘page.mouse’ to trace a 100x100 square.
    289 * await page.mouse.move(0, 0);
    290 * await page.mouse.down();
    291 * await page.mouse.move(0, 100);
    292 * await page.mouse.move(100, 100);
    293 * await page.mouse.move(100, 0);
    294 * await page.mouse.move(0, 0);
    295 * await page.mouse.up();
    296 * ```
    297 *
    298 * **Note**: The mouse events trigger synthetic `MouseEvent`s.
    299 * This means that it does not fully replicate the functionality of what a normal user
    300 * would be able to do with their mouse.
    301 *
    302 * For example, dragging and selecting text is not possible using `page.mouse`.
    303 * Instead, you can use the {@link https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/getSelection | `DocumentOrShadowRoot.getSelection()`} functionality implemented in the platform.
    304 *
    305 * @example
    306 * For example, if you want to select all content between nodes:
    307 *
    308 * ```ts
    309 * await page.evaluate(
    310 *   (from, to) => {
    311 *     const selection = from.getRootNode().getSelection();
    312 *     const range = document.createRange();
    313 *     range.setStartBefore(from);
    314 *     range.setEndAfter(to);
    315 *     selection.removeAllRanges();
    316 *     selection.addRange(range);
    317 *   },
    318 *   fromJSHandle,
    319 *   toJSHandle,
    320 * );
    321 * ```
    322 *
    323 * If you then would want to copy-paste your selection, you can use the clipboard api:
    324 *
    325 * ```ts
    326 * // The clipboard api does not allow you to copy, unless the tab is focused.
    327 * await page.bringToFront();
    328 * await page.evaluate(() => {
    329 *   // Copy the selected content to the clipboard
    330 *   document.execCommand('copy');
    331 *   // Obtain the content of the clipboard as a string
    332 *   return navigator.clipboard.readText();
    333 * });
    334 * ```
    335 *
    336 * **Note**: If you want access to the clipboard API,
    337 * you have to give it permission to do so:
    338 *
    339 * ```ts
    340 * await browser
    341 *   .defaultBrowserContext()
    342 *   .overridePermissions('<your origin>', [
    343 *     'clipboard-read',
    344 *     'clipboard-write',
    345 *   ]);
    346 * ```
    347 *
    348 * @public
    349 */
    350 export abstract class Mouse {
    351  /**
    352   * @internal
    353   */
    354  constructor() {}
    355 
    356  /**
    357   * Resets the mouse to the default state: No buttons pressed; position at
    358   * (0,0).
    359   */
    360  abstract reset(): Promise<void>;
    361 
    362  /**
    363   * Moves the mouse to the given coordinate.
    364   *
    365   * @param x - Horizontal position of the mouse.
    366   * @param y - Vertical position of the mouse.
    367   * @param options - Options to configure behavior.
    368   */
    369  abstract move(
    370    x: number,
    371    y: number,
    372    options?: Readonly<MouseMoveOptions>,
    373  ): Promise<void>;
    374 
    375  /**
    376   * Presses the mouse.
    377   *
    378   * @param options - Options to configure behavior.
    379   */
    380  abstract down(options?: Readonly<MouseOptions>): Promise<void>;
    381 
    382  /**
    383   * Releases the mouse.
    384   *
    385   * @param options - Options to configure behavior.
    386   */
    387  abstract up(options?: Readonly<MouseOptions>): Promise<void>;
    388 
    389  /**
    390   * Shortcut for `mouse.move`, `mouse.down` and `mouse.up`.
    391   *
    392   * @param x - Horizontal position of the mouse.
    393   * @param y - Vertical position of the mouse.
    394   * @param options - Options to configure behavior.
    395   */
    396  abstract click(
    397    x: number,
    398    y: number,
    399    options?: Readonly<MouseClickOptions>,
    400  ): Promise<void>;
    401 
    402  /**
    403   * Dispatches a `mousewheel` event.
    404   * @param options - Optional: `MouseWheelOptions`.
    405   *
    406   * @example
    407   * An example of zooming into an element:
    408   *
    409   * ```ts
    410   * await page.goto(
    411   *   'https://mdn.mozillademos.org/en-US/docs/Web/API/Element/wheel_event$samples/Scaling_an_element_via_the_wheel?revision=1587366',
    412   * );
    413   *
    414   * const elem = await page.$('div');
    415   * const boundingBox = await elem.boundingBox();
    416   * await page.mouse.move(
    417   *   boundingBox.x + boundingBox.width / 2,
    418   *   boundingBox.y + boundingBox.height / 2,
    419   * );
    420   *
    421   * await page.mouse.wheel({deltaY: -100});
    422   * ```
    423   */
    424  abstract wheel(options?: Readonly<MouseWheelOptions>): Promise<void>;
    425 
    426  /**
    427   * Dispatches a `drag` event.
    428   * @param start - starting point for drag
    429   * @param target - point to drag to
    430   */
    431  abstract drag(start: Point, target: Point): Promise<Protocol.Input.DragData>;
    432 
    433  /**
    434   * Dispatches a `dragenter` event.
    435   * @param target - point for emitting `dragenter` event
    436   * @param data - drag data containing items and operations mask
    437   */
    438  abstract dragEnter(
    439    target: Point,
    440    data: Protocol.Input.DragData,
    441  ): Promise<void>;
    442 
    443  /**
    444   * Dispatches a `dragover` event.
    445   * @param target - point for emitting `dragover` event
    446   * @param data - drag data containing items and operations mask
    447   */
    448  abstract dragOver(
    449    target: Point,
    450    data: Protocol.Input.DragData,
    451  ): Promise<void>;
    452 
    453  /**
    454   * Performs a dragenter, dragover, and drop in sequence.
    455   * @param target - point to drop on
    456   * @param data - drag data containing items and operations mask
    457   */
    458  abstract drop(target: Point, data: Protocol.Input.DragData): Promise<void>;
    459 
    460  /**
    461   * Performs a drag, dragenter, dragover, and drop in sequence.
    462   * @param start - point to drag from
    463   * @param target - point to drop on
    464   * @param options - An object of options. Accepts delay which,
    465   * if specified, is the time to wait between `dragover` and `drop` in milliseconds.
    466   * Defaults to 0.
    467   */
    468  abstract dragAndDrop(
    469    start: Point,
    470    target: Point,
    471    options?: {delay?: number},
    472  ): Promise<void>;
    473 }
    474 /**
    475 * The TouchHandle interface exposes methods to manipulate touches that have been started
    476 * @public
    477 */
    478 export interface TouchHandle {
    479  /**
    480   * Dispatches a `touchMove` event for this touch.
    481   * @param x - Horizontal position of the move.
    482   * @param y - Vertical position of the move.
    483   */
    484  move(x: number, y: number): Promise<void>;
    485  /**
    486   * Dispatches a `touchend` event for this touch.
    487   */
    488  end(): Promise<void>;
    489 }
    490 /**
    491 * The Touchscreen class exposes touchscreen events.
    492 * @public
    493 */
    494 export abstract class Touchscreen {
    495  /**
    496   * @internal
    497   */
    498  idGenerator = createIncrementalIdGenerator();
    499  /**
    500   * @internal
    501   */
    502  touches: TouchHandle[] = [];
    503  /**
    504   * @internal
    505   */
    506  constructor() {}
    507 
    508  /**
    509   * @internal
    510   */
    511  removeHandle(handle: TouchHandle): void {
    512    const index = this.touches.indexOf(handle);
    513    if (index === -1) {
    514      return;
    515    }
    516    this.touches.splice(index, 1);
    517  }
    518 
    519  /**
    520   * Dispatches a `touchstart` and `touchend` event.
    521   * @param x - Horizontal position of the tap.
    522   * @param y - Vertical position of the tap.
    523   */
    524  async tap(x: number, y: number): Promise<void> {
    525    const touch = await this.touchStart(x, y);
    526    await touch.end();
    527  }
    528 
    529  /**
    530   * Dispatches a `touchstart` event.
    531   * @param x - Horizontal position of the tap.
    532   * @param y - Vertical position of the tap.
    533   * @returns A handle for the touch that was started.
    534   */
    535  abstract touchStart(x: number, y: number): Promise<TouchHandle>;
    536 
    537  /**
    538   * Dispatches a `touchMove` event on the first touch that is active.
    539   * @param x - Horizontal position of the move.
    540   * @param y - Vertical position of the move.
    541   *
    542   * @remarks
    543   *
    544   * Not every `touchMove` call results in a `touchmove` event being emitted,
    545   * depending on the browser's optimizations. For example, Chrome
    546   * {@link https://developer.chrome.com/blog/a-more-compatible-smoother-touch/#chromes-new-model-the-throttled-async-touchmove-model | throttles}
    547   * touch move events.
    548   */
    549  async touchMove(x: number, y: number): Promise<void> {
    550    const touch = this.touches[0];
    551    if (!touch) {
    552      throw new TouchError('Must start a new Touch first');
    553    }
    554    return await touch.move(x, y);
    555  }
    556 
    557  /**
    558   * Dispatches a `touchend` event on the first touch that is active.
    559   */
    560  async touchEnd(): Promise<void> {
    561    const touch = this.touches.shift();
    562    if (!touch) {
    563      throw new TouchError('Must start a new Touch first');
    564    }
    565    await touch.end();
    566  }
    567 }