diff options
Diffstat (limited to 'remote/test/puppeteer/packages/puppeteer-core/src/bidi/Input.ts')
-rw-r--r-- | remote/test/puppeteer/packages/puppeteer-core/src/bidi/Input.ts | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Input.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Input.ts new file mode 100644 index 0000000000..5406556d64 --- /dev/null +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Input.ts @@ -0,0 +1,732 @@ +/** + * @license + * Copyright 2017 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; + +import type {Point} from '../api/ElementHandle.js'; +import { + Keyboard, + Mouse, + MouseButton, + Touchscreen, + type KeyDownOptions, + type KeyPressOptions, + type KeyboardTypeOptions, + type MouseClickOptions, + type MouseMoveOptions, + type MouseOptions, + type MouseWheelOptions, +} from '../api/Input.js'; +import {UnsupportedOperation} from '../common/Errors.js'; +import type {KeyInput} from '../common/USKeyboardLayout.js'; + +import type {BrowsingContext} from './BrowsingContext.js'; +import type {BidiPage} from './Page.js'; + +const enum InputId { + Mouse = '__puppeteer_mouse', + Keyboard = '__puppeteer_keyboard', + Wheel = '__puppeteer_wheel', + Finger = '__puppeteer_finger', +} + +enum SourceActionsType { + None = 'none', + Key = 'key', + Pointer = 'pointer', + Wheel = 'wheel', +} + +enum ActionType { + Pause = 'pause', + KeyDown = 'keyDown', + KeyUp = 'keyUp', + PointerUp = 'pointerUp', + PointerDown = 'pointerDown', + PointerMove = 'pointerMove', + Scroll = 'scroll', +} + +const getBidiKeyValue = (key: KeyInput) => { + switch (key) { + case '\r': + case '\n': + key = 'Enter'; + break; + } + // Measures the number of code points rather than UTF-16 code units. + if ([...key].length === 1) { + return key; + } + switch (key) { + case 'Cancel': + return '\uE001'; + case 'Help': + return '\uE002'; + case 'Backspace': + return '\uE003'; + case 'Tab': + return '\uE004'; + case 'Clear': + return '\uE005'; + case 'Enter': + return '\uE007'; + case 'Shift': + case 'ShiftLeft': + return '\uE008'; + case 'Control': + case 'ControlLeft': + return '\uE009'; + case 'Alt': + case 'AltLeft': + return '\uE00A'; + case 'Pause': + return '\uE00B'; + case 'Escape': + return '\uE00C'; + case 'PageUp': + return '\uE00E'; + case 'PageDown': + return '\uE00F'; + case 'End': + return '\uE010'; + case 'Home': + return '\uE011'; + case 'ArrowLeft': + return '\uE012'; + case 'ArrowUp': + return '\uE013'; + case 'ArrowRight': + return '\uE014'; + case 'ArrowDown': + return '\uE015'; + case 'Insert': + return '\uE016'; + case 'Delete': + return '\uE017'; + case 'NumpadEqual': + return '\uE019'; + case 'Numpad0': + return '\uE01A'; + case 'Numpad1': + return '\uE01B'; + case 'Numpad2': + return '\uE01C'; + case 'Numpad3': + return '\uE01D'; + case 'Numpad4': + return '\uE01E'; + case 'Numpad5': + return '\uE01F'; + case 'Numpad6': + return '\uE020'; + case 'Numpad7': + return '\uE021'; + case 'Numpad8': + return '\uE022'; + case 'Numpad9': + return '\uE023'; + case 'NumpadMultiply': + return '\uE024'; + case 'NumpadAdd': + return '\uE025'; + case 'NumpadSubtract': + return '\uE027'; + case 'NumpadDecimal': + return '\uE028'; + case 'NumpadDivide': + return '\uE029'; + case 'F1': + return '\uE031'; + case 'F2': + return '\uE032'; + case 'F3': + return '\uE033'; + case 'F4': + return '\uE034'; + case 'F5': + return '\uE035'; + case 'F6': + return '\uE036'; + case 'F7': + return '\uE037'; + case 'F8': + return '\uE038'; + case 'F9': + return '\uE039'; + case 'F10': + return '\uE03A'; + case 'F11': + return '\uE03B'; + case 'F12': + return '\uE03C'; + case 'Meta': + case 'MetaLeft': + return '\uE03D'; + case 'ShiftRight': + return '\uE050'; + case 'ControlRight': + return '\uE051'; + case 'AltRight': + return '\uE052'; + case 'MetaRight': + return '\uE053'; + case 'Digit0': + return '0'; + case 'Digit1': + return '1'; + case 'Digit2': + return '2'; + case 'Digit3': + return '3'; + case 'Digit4': + return '4'; + case 'Digit5': + return '5'; + case 'Digit6': + return '6'; + case 'Digit7': + return '7'; + case 'Digit8': + return '8'; + case 'Digit9': + return '9'; + case 'KeyA': + return 'a'; + case 'KeyB': + return 'b'; + case 'KeyC': + return 'c'; + case 'KeyD': + return 'd'; + case 'KeyE': + return 'e'; + case 'KeyF': + return 'f'; + case 'KeyG': + return 'g'; + case 'KeyH': + return 'h'; + case 'KeyI': + return 'i'; + case 'KeyJ': + return 'j'; + case 'KeyK': + return 'k'; + case 'KeyL': + return 'l'; + case 'KeyM': + return 'm'; + case 'KeyN': + return 'n'; + case 'KeyO': + return 'o'; + case 'KeyP': + return 'p'; + case 'KeyQ': + return 'q'; + case 'KeyR': + return 'r'; + case 'KeyS': + return 's'; + case 'KeyT': + return 't'; + case 'KeyU': + return 'u'; + case 'KeyV': + return 'v'; + case 'KeyW': + return 'w'; + case 'KeyX': + return 'x'; + case 'KeyY': + return 'y'; + case 'KeyZ': + return 'z'; + case 'Semicolon': + return ';'; + case 'Equal': + return '='; + case 'Comma': + return ','; + case 'Minus': + return '-'; + case 'Period': + return '.'; + case 'Slash': + return '/'; + case 'Backquote': + return '`'; + case 'BracketLeft': + return '['; + case 'Backslash': + return '\\'; + case 'BracketRight': + return ']'; + case 'Quote': + return '"'; + default: + throw new Error(`Unknown key: "${key}"`); + } +}; + +/** + * @internal + */ +export class BidiKeyboard extends Keyboard { + #page: BidiPage; + + constructor(page: BidiPage) { + super(); + this.#page = page; + } + + override async down( + key: KeyInput, + _options?: Readonly<KeyDownOptions> + ): Promise<void> { + await this.#page.connection.send('input.performActions', { + context: this.#page.mainFrame()._id, + actions: [ + { + type: SourceActionsType.Key, + id: InputId.Keyboard, + actions: [ + { + type: ActionType.KeyDown, + value: getBidiKeyValue(key), + }, + ], + }, + ], + }); + } + + override async up(key: KeyInput): Promise<void> { + await this.#page.connection.send('input.performActions', { + context: this.#page.mainFrame()._id, + actions: [ + { + type: SourceActionsType.Key, + id: InputId.Keyboard, + actions: [ + { + type: ActionType.KeyUp, + value: getBidiKeyValue(key), + }, + ], + }, + ], + }); + } + + override async press( + key: KeyInput, + options: Readonly<KeyPressOptions> = {} + ): Promise<void> { + const {delay = 0} = options; + const actions: Bidi.Input.KeySourceAction[] = [ + { + type: ActionType.KeyDown, + value: getBidiKeyValue(key), + }, + ]; + if (delay > 0) { + actions.push({ + type: ActionType.Pause, + duration: delay, + }); + } + actions.push({ + type: ActionType.KeyUp, + value: getBidiKeyValue(key), + }); + await this.#page.connection.send('input.performActions', { + context: this.#page.mainFrame()._id, + actions: [ + { + type: SourceActionsType.Key, + id: InputId.Keyboard, + actions, + }, + ], + }); + } + + override async type( + text: string, + options: Readonly<KeyboardTypeOptions> = {} + ): Promise<void> { + const {delay = 0} = options; + // This spread separates the characters into code points rather than UTF-16 + // code units. + const values = ([...text] as KeyInput[]).map(getBidiKeyValue); + const actions: Bidi.Input.KeySourceAction[] = []; + if (delay <= 0) { + for (const value of values) { + actions.push( + { + type: ActionType.KeyDown, + value, + }, + { + type: ActionType.KeyUp, + value, + } + ); + } + } else { + for (const value of values) { + actions.push( + { + type: ActionType.KeyDown, + value, + }, + { + type: ActionType.Pause, + duration: delay, + }, + { + type: ActionType.KeyUp, + value, + } + ); + } + } + await this.#page.connection.send('input.performActions', { + context: this.#page.mainFrame()._id, + actions: [ + { + type: SourceActionsType.Key, + id: InputId.Keyboard, + actions, + }, + ], + }); + } + + override async sendCharacter(char: string): Promise<void> { + // Measures the number of code points rather than UTF-16 code units. + if ([...char].length > 1) { + throw new Error('Cannot send more than 1 character.'); + } + const frame = await this.#page.focusedFrame(); + await frame.isolatedRealm().evaluate(async char => { + document.execCommand('insertText', false, char); + }, char); + } +} + +/** + * @internal + */ +export interface BidiMouseClickOptions extends MouseClickOptions { + origin?: Bidi.Input.Origin; +} + +/** + * @internal + */ +export interface BidiMouseMoveOptions extends MouseMoveOptions { + origin?: Bidi.Input.Origin; +} + +/** + * @internal + */ +export interface BidiTouchMoveOptions { + origin?: Bidi.Input.Origin; +} + +const getBidiButton = (button: MouseButton) => { + switch (button) { + case MouseButton.Left: + return 0; + case MouseButton.Middle: + return 1; + case MouseButton.Right: + return 2; + case MouseButton.Back: + return 3; + case MouseButton.Forward: + return 4; + } +}; + +/** + * @internal + */ +export class BidiMouse extends Mouse { + #context: BrowsingContext; + #lastMovePoint: Point = {x: 0, y: 0}; + + constructor(context: BrowsingContext) { + super(); + this.#context = context; + } + + override async reset(): Promise<void> { + this.#lastMovePoint = {x: 0, y: 0}; + await this.#context.connection.send('input.releaseActions', { + context: this.#context.id, + }); + } + + override async move( + x: number, + y: number, + options: Readonly<BidiMouseMoveOptions> = {} + ): Promise<void> { + const from = this.#lastMovePoint; + const to = { + x: Math.round(x), + y: Math.round(y), + }; + const actions: Bidi.Input.PointerSourceAction[] = []; + const steps = options.steps ?? 0; + for (let i = 0; i < steps; ++i) { + actions.push({ + type: ActionType.PointerMove, + x: from.x + (to.x - from.x) * (i / steps), + y: from.y + (to.y - from.y) * (i / steps), + origin: options.origin, + }); + } + actions.push({ + type: ActionType.PointerMove, + ...to, + origin: options.origin, + }); + // https://w3c.github.io/webdriver-bidi/#command-input-performActions:~:text=input.PointerMoveAction%20%3D%20%7B%0A%20%20type%3A%20%22pointerMove%22%2C%0A%20%20x%3A%20js%2Dint%2C + this.#lastMovePoint = to; + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: SourceActionsType.Pointer, + id: InputId.Mouse, + actions, + }, + ], + }); + } + + override async down(options: Readonly<MouseOptions> = {}): Promise<void> { + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: SourceActionsType.Pointer, + id: InputId.Mouse, + actions: [ + { + type: ActionType.PointerDown, + button: getBidiButton(options.button ?? MouseButton.Left), + }, + ], + }, + ], + }); + } + + override async up(options: Readonly<MouseOptions> = {}): Promise<void> { + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: SourceActionsType.Pointer, + id: InputId.Mouse, + actions: [ + { + type: ActionType.PointerUp, + button: getBidiButton(options.button ?? MouseButton.Left), + }, + ], + }, + ], + }); + } + + override async click( + x: number, + y: number, + options: Readonly<BidiMouseClickOptions> = {} + ): Promise<void> { + const actions: Bidi.Input.PointerSourceAction[] = [ + { + type: ActionType.PointerMove, + x: Math.round(x), + y: Math.round(y), + origin: options.origin, + }, + ]; + const pointerDownAction = { + type: ActionType.PointerDown, + button: getBidiButton(options.button ?? MouseButton.Left), + } as const; + const pointerUpAction = { + type: ActionType.PointerUp, + button: pointerDownAction.button, + } as const; + for (let i = 1; i < (options.count ?? 1); ++i) { + actions.push(pointerDownAction, pointerUpAction); + } + actions.push(pointerDownAction); + if (options.delay) { + actions.push({ + type: ActionType.Pause, + duration: options.delay, + }); + } + actions.push(pointerUpAction); + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: SourceActionsType.Pointer, + id: InputId.Mouse, + actions, + }, + ], + }); + } + + override async wheel( + options: Readonly<MouseWheelOptions> = {} + ): Promise<void> { + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: SourceActionsType.Wheel, + id: InputId.Wheel, + actions: [ + { + type: ActionType.Scroll, + ...(this.#lastMovePoint ?? { + x: 0, + y: 0, + }), + deltaX: options.deltaX ?? 0, + deltaY: options.deltaY ?? 0, + }, + ], + }, + ], + }); + } + + override drag(): never { + throw new UnsupportedOperation(); + } + + override dragOver(): never { + throw new UnsupportedOperation(); + } + + override dragEnter(): never { + throw new UnsupportedOperation(); + } + + override drop(): never { + throw new UnsupportedOperation(); + } + + override dragAndDrop(): never { + throw new UnsupportedOperation(); + } +} + +/** + * @internal + */ +export class BidiTouchscreen extends Touchscreen { + #context: BrowsingContext; + + constructor(context: BrowsingContext) { + super(); + this.#context = context; + } + + override async touchStart( + x: number, + y: number, + options: BidiTouchMoveOptions = {} + ): Promise<void> { + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: SourceActionsType.Pointer, + id: InputId.Finger, + parameters: { + pointerType: Bidi.Input.PointerType.Touch, + }, + actions: [ + { + type: ActionType.PointerMove, + x: Math.round(x), + y: Math.round(y), + origin: options.origin, + }, + { + type: ActionType.PointerDown, + button: 0, + }, + ], + }, + ], + }); + } + + override async touchMove( + x: number, + y: number, + options: BidiTouchMoveOptions = {} + ): Promise<void> { + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: SourceActionsType.Pointer, + id: InputId.Finger, + parameters: { + pointerType: Bidi.Input.PointerType.Touch, + }, + actions: [ + { + type: ActionType.PointerMove, + x: Math.round(x), + y: Math.round(y), + origin: options.origin, + }, + ], + }, + ], + }); + } + + override async touchEnd(): Promise<void> { + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: SourceActionsType.Pointer, + id: InputId.Finger, + parameters: { + pointerType: Bidi.Input.PointerType.Touch, + }, + actions: [ + { + type: ActionType.PointerUp, + button: 0, + }, + ], + }, + ], + }); + } +} |