Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: migrate src/Input to typescript #5710

Merged
merged 3 commits into from Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
133 changes: 47 additions & 86 deletions src/Input.js → src/Input.ts
Expand Up @@ -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);
Expand All @@ -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')
Expand All @@ -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: '',
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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([
Expand 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', {
Expand All @@ -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', {
Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -311,5 +274,3 @@ class Touchscreen {
});
}
}

module.exports = {Keyboard, Mouse, Touchscreen};
3 changes: 2 additions & 1 deletion src/JSHandle.ts
Expand Up @@ -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}>;
Expand Down Expand Up @@ -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);
}
Expand Down
9 changes: 6 additions & 3 deletions src/USKeyboardLayout.ts
Expand Up @@ -14,7 +14,8 @@
* limitations under the License.
*/

interface KeyDefinition {

export interface KeyDefinition {
keyCode?: number;
shiftKeyCode?: number;
key?: string;
Expand All @@ -23,9 +24,11 @@
text?: string;
shiftText?: string;
location?: number;
}
}

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';
mathiasbynens marked this conversation as resolved.
Show resolved Hide resolved

export const keyDefinitions: Readonly<Record<string, KeyDefinition>> = {
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'},
Expand Down
4 changes: 0 additions & 4 deletions src/externs.d.ts
@@ -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 {}
Expand Down
17 changes: 16 additions & 1 deletion utils/doclint/check_public_api/JSBuilder.js
Expand Up @@ -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
Expand All @@ -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)
Expand Down
37 changes: 32 additions & 5 deletions utils/doclint/check_public_api/index.js
Expand Up @@ -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);
Expand All @@ -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());
Expand Down