diff --git a/src/Input.js b/src/Input.ts similarity index 74% rename from src/Input.js rename to src/Input.ts index 19777a4f356..7af22e9b0bd 100644 --- a/src/Input.js +++ b/src/Input.ts @@ -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>; -class Keyboard { - /** - * @param {!CDPSession} client - */ - constructor(client) { +export class Keyboard { + _client: CDPSession; + _modifiers = 0; + _pressedKeys = new Set(); + + 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 { 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 { 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 { 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 { 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 { 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; + +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 { 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 { 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 { 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 { 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 { // 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}; diff --git a/src/JSHandle.ts b/src/JSHandle.ts index c727b372d06..fea9f49108b 100644 --- a/src/JSHandle.ts +++ b/src/JSHandle.ts @@ -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 { + async press(key: KeyInput, options?: {delay?: number; text?: string}): Promise { await this.focus(); await this._page.keyboard.press(key, options); } diff --git a/src/USKeyboardLayout.ts b/src/USKeyboardLayout.ts index 5da20b8b11d..52a7f2a58f9 100644 --- a/src/USKeyboardLayout.ts +++ b/src/USKeyboardLayout.ts @@ -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> = { +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> = { '0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'}, '1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'}, '2': {'keyCode': 50, 'key': '2', 'code': 'Digit2'}, diff --git a/src/externs.d.ts b/src/externs.d.ts index 1a45f7a1597..78ee12ea0a0 100644 --- a/src/externs.d.ts +++ b/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 {} diff --git a/utils/doclint/check_public_api/JSBuilder.js b/utils/doclint/check_public_api/JSBuilder.js index 420be53ca43..acf6d983f6f 100644 --- a/utils/doclint/check_public_api/JSBuilder.js +++ b/utils/doclint/check_public_api/JSBuilder.js @@ -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) diff --git a/utils/doclint/check_public_api/index.js b/utils/doclint/check_public_api/index.js index 5f2ba1f9156..bfd6b7fa201 100644 --- a/utils/doclint/check_public_api/index.js +++ b/utils/doclint/check_public_api/index.js @@ -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());