mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: migrate src/Input to typescript (#5710)
* chore: migrate src/Input to typescript This moves `Keyboard`, `Mouse` and `Touchscreen` to TypeScript. We gain some nice TS benefits here; by creating a type for all the keycodes we support we can type the input args as that rather than `string` which will hopefully save some users some debugging once we ship our TS types in a future version. * Remove from externs file * Update utils/doclint/check_public_api/index.js Co-Authored-By: Mathias Bynens <mathias@qiwi.be> Co-authored-by: Mathias Bynens <mathias@qiwi.be>
This commit is contained in:
parent
11bc5a6450
commit
133abb07cf
@ -14,36 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {assert} = require('./helper');
|
||||
// CDPSession is used only as a typedef
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {CDPSession} = require('./Connection');
|
||||
const {keyDefinitions} = require('./USKeyboardLayout');
|
||||
import {assert} from './helper';
|
||||
import {CDPSession} from './Connection';
|
||||
import {keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout';
|
||||
|
||||
/**
|
||||
* @typedef {Object} KeyDescription
|
||||
* @property {number} keyCode
|
||||
* @property {string} key
|
||||
* @property {string} text
|
||||
* @property {string} code
|
||||
* @property {number} location
|
||||
*/
|
||||
type KeyDescription = Required<Pick<KeyDefinition, 'keyCode' | 'key' | 'text' | 'code' | 'location'>>;
|
||||
|
||||
class Keyboard {
|
||||
/**
|
||||
* @param {!CDPSession} client
|
||||
*/
|
||||
constructor(client) {
|
||||
export class Keyboard {
|
||||
_client: CDPSession;
|
||||
_modifiers = 0;
|
||||
_pressedKeys = new Set<string>();
|
||||
|
||||
constructor(client: CDPSession) {
|
||||
this._client = client;
|
||||
this._modifiers = 0;
|
||||
this._pressedKeys = new Set();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {{text?: string}=} options
|
||||
*/
|
||||
async down(key, options = {text: undefined}) {
|
||||
async down(key: KeyInput, options: { text?: string } = {text: undefined}): Promise<void> {
|
||||
const description = this._keyDescriptionForString(key);
|
||||
|
||||
const autoRepeat = this._pressedKeys.has(description.code);
|
||||
@ -65,11 +51,7 @@ class Keyboard {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @return {number}
|
||||
*/
|
||||
_modifierBit(key) {
|
||||
private _modifierBit(key: string): number {
|
||||
if (key === 'Alt')
|
||||
return 1;
|
||||
if (key === 'Control')
|
||||
@ -81,11 +63,7 @@ class Keyboard {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} keyString
|
||||
* @return {KeyDescription}
|
||||
*/
|
||||
_keyDescriptionForString(keyString) {
|
||||
private _keyDescriptionForString(keyString: KeyInput): KeyDescription {
|
||||
const shift = this._modifiers & 8;
|
||||
const description = {
|
||||
key: '',
|
||||
@ -129,10 +107,7 @@ class Keyboard {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
async up(key) {
|
||||
async up(key: KeyInput): Promise<void> {
|
||||
const description = this._keyDescriptionForString(key);
|
||||
|
||||
this._modifiers &= ~this._modifierBit(description.key);
|
||||
@ -147,21 +122,18 @@ class Keyboard {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} char
|
||||
*/
|
||||
async sendCharacter(char) {
|
||||
async sendCharacter(char: string): Promise<void> {
|
||||
await this._client.send('Input.insertText', {text: char});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {{delay: (number|undefined)}=} options
|
||||
*/
|
||||
async type(text, options) {
|
||||
private charIsKey(char: string): char is KeyInput {
|
||||
return !!keyDefinitions[char];
|
||||
}
|
||||
|
||||
async type(text: string, options: {delay?: number}): Promise<void> {
|
||||
const delay = (options && options.delay) || null;
|
||||
for (const char of text) {
|
||||
if (keyDefinitions[char]) {
|
||||
if (this.charIsKey(char)) {
|
||||
await this.press(char, {delay});
|
||||
} else {
|
||||
if (delay)
|
||||
@ -171,11 +143,7 @@ class Keyboard {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {!{delay?: number, text?: string}=} options
|
||||
*/
|
||||
async press(key, options = {}) {
|
||||
async press(key: KeyInput, options: {delay?: number; text?: string} = {}): Promise<void> {
|
||||
const {delay = null} = options;
|
||||
await this.down(key, options);
|
||||
if (delay)
|
||||
@ -184,26 +152,30 @@ class Keyboard {
|
||||
}
|
||||
}
|
||||
|
||||
class Mouse {
|
||||
type MouseButton = 'none' | 'left' | 'right' | 'middle';
|
||||
type MouseButtonInput = Exclude<MouseButton, 'none'>;
|
||||
|
||||
interface MouseOptions {
|
||||
button?: MouseButtonInput;
|
||||
clickCount?: number;
|
||||
}
|
||||
|
||||
export class Mouse {
|
||||
_client: CDPSession;
|
||||
_keyboard: Keyboard;
|
||||
_x = 0;
|
||||
_y = 0;
|
||||
_button: MouseButton = 'none';
|
||||
/**
|
||||
* @param {CDPSession} client
|
||||
* @param {!Keyboard} keyboard
|
||||
*/
|
||||
constructor(client, keyboard) {
|
||||
constructor(client: CDPSession, keyboard: Keyboard) {
|
||||
this._client = client;
|
||||
this._keyboard = keyboard;
|
||||
this._x = 0;
|
||||
this._y = 0;
|
||||
/** @type {'none'|'left'|'right'|'middle'} */
|
||||
this._button = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {!{steps?: number}=} options
|
||||
*/
|
||||
async move(x, y, options = {}) {
|
||||
async move(x: number, y: number, options: {steps?: number} = {}): Promise<void> {
|
||||
const {steps = 1} = options;
|
||||
const fromX = this._x, fromY = this._y;
|
||||
this._x = x;
|
||||
@ -219,12 +191,7 @@ class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
|
||||
*/
|
||||
async click(x, y, options = {}) {
|
||||
async click(x: number, y: number, options: MouseOptions & {delay?: number} = {}): Promise<void> {
|
||||
const {delay = null} = options;
|
||||
if (delay !== null) {
|
||||
await Promise.all([
|
||||
@ -242,10 +209,7 @@ class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
||||
*/
|
||||
async down(options = {}) {
|
||||
async down(options: MouseOptions = {}): Promise<void> {
|
||||
const {button = 'left', clickCount = 1} = options;
|
||||
this._button = button;
|
||||
await this._client.send('Input.dispatchMouseEvent', {
|
||||
@ -261,7 +225,7 @@ class Mouse {
|
||||
/**
|
||||
* @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
||||
*/
|
||||
async up(options = {}) {
|
||||
async up(options: MouseOptions = {}): Promise<void> {
|
||||
const {button = 'left', clickCount = 1} = options;
|
||||
this._button = 'none';
|
||||
await this._client.send('Input.dispatchMouseEvent', {
|
||||
@ -275,12 +239,11 @@ class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
class Touchscreen {
|
||||
/**
|
||||
* @param {CDPSession} client
|
||||
* @param {Keyboard} keyboard
|
||||
*/
|
||||
constructor(client, keyboard) {
|
||||
export class Touchscreen {
|
||||
_client: CDPSession;
|
||||
_keyboard: Keyboard;
|
||||
|
||||
constructor(client: CDPSession, keyboard: Keyboard) {
|
||||
this._client = client;
|
||||
this._keyboard = keyboard;
|
||||
}
|
||||
@ -289,7 +252,7 @@ class Touchscreen {
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
async tap(x, y) {
|
||||
async tap(x: number, y: number): Promise<void> {
|
||||
// Touches appear to be lost during the first frame after navigation.
|
||||
// This waits a frame before sending the tap.
|
||||
// @see https://crbug.com/613219
|
||||
@ -311,5 +274,3 @@ class Touchscreen {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {Keyboard, Mouse, Touchscreen};
|
@ -17,6 +17,7 @@
|
||||
import {helper, assert, debugError} from './helper';
|
||||
import {ExecutionContext} from './ExecutionContext';
|
||||
import {CDPSession} from './Connection';
|
||||
import {KeyInput} from './USKeyboardLayout';
|
||||
|
||||
interface BoxModel {
|
||||
content: Array<{x: number; y: number}>;
|
||||
@ -326,7 +327,7 @@ export class ElementHandle extends JSHandle {
|
||||
await this._page.keyboard.type(text, options);
|
||||
}
|
||||
|
||||
async press(key: string, options?: {delay?: number; text?: string}): Promise<void> {
|
||||
async press(key: KeyInput, options?: {delay?: number; text?: string}): Promise<void> {
|
||||
await this.focus();
|
||||
await this._page.keyboard.press(key, options);
|
||||
}
|
||||
|
@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface KeyDefinition {
|
||||
|
||||
export interface KeyDefinition {
|
||||
keyCode?: number;
|
||||
shiftKeyCode?: number;
|
||||
key?: string;
|
||||
@ -23,9 +24,11 @@
|
||||
text?: string;
|
||||
shiftText?: string;
|
||||
location?: number;
|
||||
}
|
||||
}
|
||||
|
||||
export const keyDefinitions: Readonly<Record<string, KeyDefinition>> = {
|
||||
export type KeyInput = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'Power'|'Eject'|'Abort'|'Help'|'Backspace'|'Tab'|'Numpad5'|'NumpadEnter'|'Enter'|'\r'|'\n'|'ShiftLeft'|'ShiftRight'|'ControlLeft'|'ControlRight'|'AltLeft'|'AltRight'|'Pause'|'CapsLock'|'Escape'|'Convert'|'NonConvert'|'Space'|'Numpad9'|'PageUp'|'Numpad3'|'PageDown'|'End'|'Numpad1'|'Home'|'Numpad7'|'ArrowLeft'|'Numpad4'|'Numpad8'|'ArrowUp'|'ArrowRight'|'Numpad6'|'Numpad2'|'ArrowDown'|'Select'|'Open'|'PrintScreen'|'Insert'|'Numpad0'|'Delete'|'NumpadDecimal'|'Digit0'|'Digit1'|'Digit2'|'Digit3'|'Digit4'|'Digit5'|'Digit6'|'Digit7'|'Digit8'|'Digit9'|'KeyA'|'KeyB'|'KeyC'|'KeyD'|'KeyE'|'KeyF'|'KeyG'|'KeyH'|'KeyI'|'KeyJ'|'KeyK'|'KeyL'|'KeyM'|'KeyN'|'KeyO'|'KeyP'|'KeyQ'|'KeyR'|'KeyS'|'KeyT'|'KeyU'|'KeyV'|'KeyW'|'KeyX'|'KeyY'|'KeyZ'|'MetaLeft'|'MetaRight'|'ContextMenu'|'NumpadMultiply'|'NumpadAdd'|'NumpadSubtract'|'NumpadDivide'|'F1'|'F2'|'F3'|'F4'|'F5'|'F6'|'F7'|'F8'|'F9'|'F10'|'F11'|'F12'|'F13'|'F14'|'F15'|'F16'|'F17'|'F18'|'F19'|'F20'|'F21'|'F22'|'F23'|'F24'|'NumLock'|'ScrollLock'|'AudioVolumeMute'|'AudioVolumeDown'|'AudioVolumeUp'|'MediaTrackNext'|'MediaTrackPrevious'|'MediaStop'|'MediaPlayPause'|'Semicolon'|'Equal'|'NumpadEqual'|'Comma'|'Minus'|'Period'|'Slash'|'Backquote'|'BracketLeft'|'Backslash'|'BracketRight'|'Quote'|'AltGraph'|'Props'|'Cancel'|'Clear'|'Shift'|'Control'|'Alt'|'Accept'|'ModeChange'|' '|'Print'|'Execute'|'\u0000'|'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'|'Meta'|'*'|'+'|'-'|'/'|';'|'='|','|'.'|'`'|'['|'\\'|']'|'\''|'Attn'|'CrSel'|'ExSel'|'EraseEof'|'Play'|'ZoomOut'|')'|'!'|'@'|'#'|'$'|'%'|'^'|'&'|'('|'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'|':'|'<'|'_'|'>'|'?'|'~'|'{'|'|'|'}'|'"'|'SoftLeft'|'SoftRight'|'Camera'|'Call'|'EndCall'|'VolumeDown'|'VolumeUp';
|
||||
|
||||
export const keyDefinitions: Readonly<Record<KeyInput, KeyDefinition>> = {
|
||||
'0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'},
|
||||
'1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'},
|
||||
'2': {'keyCode': 50, 'key': '2', 'code': 'Digit2'},
|
||||
|
4
src/externs.d.ts
vendored
4
src/externs.d.ts
vendored
@ -1,16 +1,12 @@
|
||||
import { Browser as RealBrowser, BrowserContext as RealBrowserContext} from './Browser.js';
|
||||
import {Target as RealTarget} from './Target.js';
|
||||
import {Page as RealPage} from './Page.js';
|
||||
import {Mouse as RealMouse, Keyboard as RealKeyboard, Touchscreen as RealTouchscreen} from './Input.js';
|
||||
import {Frame as RealFrame, FrameManager as RealFrameManager} from './FrameManager.js';
|
||||
import {DOMWorld as RealDOMWorld} from './DOMWorld.js';
|
||||
import { NetworkManager as RealNetworkManager, Request as RealRequest, Response as RealResponse } from './NetworkManager.js';
|
||||
import * as child_process from 'child_process';
|
||||
declare global {
|
||||
module Puppeteer {
|
||||
export class Mouse extends RealMouse {}
|
||||
export class Keyboard extends RealKeyboard {}
|
||||
export class Touchscreen extends RealTouchscreen {}
|
||||
export class Browser extends RealBrowser {}
|
||||
export class BrowserContext extends RealBrowserContext {}
|
||||
export class Target extends RealTarget {}
|
||||
|
@ -184,6 +184,15 @@ function checkSources(sources) {
|
||||
return new Documentation.Type(typeName, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ts.Symbol} symbol
|
||||
* @return {boolean}
|
||||
*/
|
||||
function symbolHasPrivateModifier(symbol) {
|
||||
const modifiers = symbol.valueDeclaration.modifiers || [];
|
||||
return modifiers.some(modifier => modifier.kind === ts.SyntaxKind.PrivateKeyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} className
|
||||
* @param {!ts.Symbol} symbol
|
||||
@ -194,8 +203,14 @@ function checkSources(sources) {
|
||||
const members = classEvents.get(className) || [];
|
||||
|
||||
for (const [name, member] of symbol.members || []) {
|
||||
if (name.startsWith('_'))
|
||||
|
||||
/* Before TypeScript we denoted private methods with an underscore
|
||||
* but in TypeScript we use the private keyword
|
||||
* hence we check for either here.
|
||||
*/
|
||||
if (name.startsWith('_') || symbolHasPrivateModifier(member))
|
||||
continue;
|
||||
|
||||
const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
|
||||
const signature = memberType.getCallSignatures()[0];
|
||||
if (signature)
|
||||
|
@ -271,6 +271,30 @@ function compareDocumentations(actual, expected) {
|
||||
actualName: 'Object',
|
||||
expectedName: 'CommandParameters[T]'
|
||||
}],
|
||||
['Method ElementHandle.press() key', {
|
||||
actualName: 'string',
|
||||
expectedName: 'KeyInput'
|
||||
}],
|
||||
['Method Keyboard.down() key', {
|
||||
actualName: 'string',
|
||||
expectedName: 'KeyInput'
|
||||
}],
|
||||
['Method Keyboard.press() key', {
|
||||
actualName: 'string',
|
||||
expectedName: 'KeyInput'
|
||||
}],
|
||||
['Method Keyboard.up() key', {
|
||||
actualName: 'string',
|
||||
expectedName: 'KeyInput'
|
||||
}],
|
||||
['Method Mouse.down() options', {
|
||||
actualName: 'Object',
|
||||
expectedName: 'MouseOptions'
|
||||
}],
|
||||
['Method Mouse.up() options', {
|
||||
actualName: 'Object',
|
||||
expectedName: 'MouseOptions'
|
||||
}],
|
||||
]);
|
||||
|
||||
const expectedForSource = expectedNamingMismatches.get(source);
|
||||
@ -295,12 +319,15 @@ function compareDocumentations(actual, expected) {
|
||||
const actualName = actual.name.replace(/[\? ]/g, '');
|
||||
// TypeScript likes to add some spaces
|
||||
const expectedName = expected.name.replace(/\ /g, '');
|
||||
if (expectedName !== actualName) {
|
||||
const namingMismatchIsExpected = namingMisMatchInTypeIsExpected(source, actualName, expectedName);
|
||||
const namingMismatchIsExpected = namingMisMatchInTypeIsExpected(source, actualName, expectedName);
|
||||
if (expectedName !== actualName && !namingMismatchIsExpected)
|
||||
errors.push(`${source} ${actualName} != ${expectedName}`);
|
||||
|
||||
if (!namingMismatchIsExpected)
|
||||
errors.push(`${source} ${actualName} != ${expectedName}`);
|
||||
}
|
||||
|
||||
/* If we got a naming mismatch and it was expected, don't check the properties
|
||||
* as they will likely be considered "wrong" by DocLint too.
|
||||
*/
|
||||
if (namingMismatchIsExpected) return;
|
||||
const actualPropertiesMap = new Map(actual.properties.map(property => [property.name, property.type]));
|
||||
const expectedPropertiesMap = new Map(expected.properties.map(property => [property.name, property.type]));
|
||||
const propertiesDiff = diff(Array.from(actualPropertiesMap.keys()).sort(), Array.from(expectedPropertiesMap.keys()).sort());
|
||||
|
Loading…
Reference in New Issue
Block a user