mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
718 lines
15 KiB
TypeScript
718 lines
15 KiB
TypeScript
/**
|
|
* Copyright 2017 Google Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|
|
|
import {Point} from '../../api/ElementHandle.js';
|
|
import {
|
|
Keyboard as BaseKeyboard,
|
|
Mouse as BaseMouse,
|
|
Touchscreen as BaseTouchscreen,
|
|
KeyDownOptions,
|
|
KeyPressOptions,
|
|
KeyboardTypeOptions,
|
|
MouseButton,
|
|
MouseClickOptions,
|
|
MouseMoveOptions,
|
|
MouseOptions,
|
|
MouseWheelOptions,
|
|
} from '../../api/Input.js';
|
|
import {KeyInput} from '../USKeyboardLayout.js';
|
|
|
|
import {BrowsingContext} from './BrowsingContext.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 Keyboard extends BaseKeyboard {
|
|
#context: BrowsingContext;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
constructor(context: BrowsingContext) {
|
|
super();
|
|
this.#context = context;
|
|
}
|
|
|
|
override async down(
|
|
key: KeyInput,
|
|
_options?: Readonly<KeyDownOptions>
|
|
): Promise<void> {
|
|
await this.#context.connection.send('input.performActions', {
|
|
context: this.#context.id,
|
|
actions: [
|
|
{
|
|
type: SourceActionsType.Key,
|
|
id: InputId.Keyboard,
|
|
actions: [
|
|
{
|
|
type: ActionType.KeyDown,
|
|
value: getBidiKeyValue(key),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
override async up(key: KeyInput): Promise<void> {
|
|
await this.#context.connection.send('input.performActions', {
|
|
context: this.#context.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.#context.connection.send('input.performActions', {
|
|
context: this.#context.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.#context.connection.send('input.performActions', {
|
|
context: this.#context.id,
|
|
actions: [
|
|
{
|
|
type: SourceActionsType.Key,
|
|
id: InputId.Keyboard,
|
|
actions,
|
|
},
|
|
],
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
interface BidiMouseClickOptions extends MouseClickOptions {
|
|
origin?: Bidi.Input.Origin;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
interface BidiMouseMoveOptions extends MouseMoveOptions {
|
|
origin?: Bidi.Input.Origin;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
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 Mouse extends BaseMouse {
|
|
#context: BrowsingContext;
|
|
#lastMovePoint?: Point;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
constructor(context: BrowsingContext) {
|
|
super();
|
|
this.#context = context;
|
|
}
|
|
|
|
override async reset(): Promise<void> {
|
|
this.#lastMovePoint = undefined;
|
|
await this.#context.connection.send('input.releaseActions', {
|
|
context: this.#context.id,
|
|
});
|
|
}
|
|
|
|
override async move(
|
|
x: number,
|
|
y: number,
|
|
options: Readonly<BidiMouseMoveOptions> = {}
|
|
): Promise<void> {
|
|
this.#lastMovePoint = {
|
|
x,
|
|
y,
|
|
};
|
|
await this.#context.connection.send('input.performActions', {
|
|
context: this.#context.id,
|
|
actions: [
|
|
{
|
|
type: SourceActionsType.Pointer,
|
|
id: InputId.Mouse,
|
|
actions: [
|
|
{
|
|
type: ActionType.PointerMove,
|
|
x,
|
|
y,
|
|
duration: (options.steps ?? 0) * 50,
|
|
origin: options.origin,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
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,
|
|
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,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
export class Touchscreen extends BaseTouchscreen {
|
|
#context: BrowsingContext;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
constructor(context: BrowsingContext) {
|
|
super();
|
|
this.#context = context;
|
|
}
|
|
|
|
override async tap(
|
|
x: number,
|
|
y: number,
|
|
options: BidiTouchMoveOptions = {}
|
|
): Promise<void> {
|
|
await this.touchStart(x, y, options);
|
|
await this.touchEnd();
|
|
}
|
|
|
|
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,
|
|
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,
|
|
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,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
}
|