/** * 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. */ const {helper} = require('./helper'); class Keyboard { /** * @param {!Puppeteer.Session} client */ constructor(client) { this._client = client; this._modifiers = 0; this._pressedKeys = new Set(); } /** * @param {string} key * @param {{text: string}=} options */ async down(key, options = {text: undefined}) { let { text } = options; // If the key is a single character, and no modifiers are pressed except shift, a keypress should be generated by default. if (text === undefined) text = (key.length === 1 && !(this._modifiers & ~8)) ? key : ''; const autoRepeat = this._pressedKeys.has(key); this._pressedKeys.add(key); this._modifiers |= this._modifierBit(key); await this._client.send('Input.dispatchKeyEvent', { type: text ? 'keyDown' : 'rawKeyDown', modifiers: this._modifiers, windowsVirtualKeyCode: keyCodeForKey(key), code: codeForKey(key), key, text, unmodifiedText: text, autoRepeat }); } /** * @param {string} key * @return {number} */ _modifierBit(key) { if (key === 'Alt') return 1; if (key === 'Control') return 2; if (key === 'Meta') return 4; if (key === 'Shift') return 8; return 0; } /** * @param {string} key */ async up(key) { this._modifiers &= ~this._modifierBit(key); this._pressedKeys.delete(key); await this._client.send('Input.dispatchKeyEvent', { type: 'keyUp', modifiers: this._modifiers, key, windowsVirtualKeyCode: keyCodeForKey(key), code: codeForKey(key) }); } /** * @param {string} char */ async sendCharacter(char) { await this._client.send('Input.dispatchKeyEvent', { type: 'char', modifiers: this._modifiers, text: char, key: char, unmodifiedText: char }); } /** * @param {string} text * @param {{delay: (number|undefined)}=} options */ async type(text, options) { let delay = 0; if (options && options.delay) delay = options.delay; for (const char of text) { await this.press(char, {text: char, delay}); if (delay) await new Promise(f => setTimeout(f, delay)); } } /** * @param {string} key * @param {!Object=} options */ async press(key, options) { await this.down(key, options); if (options && options.delay) await new Promise(f => setTimeout(f, options.delay)); await this.up(key); } } class Mouse { /** * @param {Puppeteer.Session} client * @param {!Keyboard} keyboard */ constructor(client, keyboard) { this._client = client; this._keyboard = keyboard; this._x = 0; this._y = 0; this._button = 'none'; } /** * @param {number} x * @param {number} y * @param {Object=} options * @return {!Promise} */ async move(x, y, options = {}) { const fromX = this._x, fromY = this._y; this._x = x; this._y = y; const steps = options.steps || 1; for (let i = 1; i <= steps; i++) { await this._client.send('Input.dispatchMouseEvent', { type: 'mouseMoved', button: this._button, x: fromX + (this._x - fromX) * (i / steps), y: fromY + (this._y - fromY) * (i / steps), modifiers: this._keyboard._modifiers }); } } /** * @param {number} x * @param {number} y * @param {!Object=} options */ async click(x, y, options = {}) { this.move(x, y); this.down(options); if (typeof options.delay === 'number') await new Promise(f => setTimeout(f, options.delay)); await this.up(options); } /** * @param {!Object=} options */ async down(options = {}) { this._button = (options.button || 'left'); await this._client.send('Input.dispatchMouseEvent', { type: 'mousePressed', button: this._button, x: this._x, y: this._y, modifiers: this._keyboard._modifiers, clickCount: (options.clickCount || 1) }); } /** * @param {!Object=} options */ async up(options = {}) { this._button = 'none'; await this._client.send('Input.dispatchMouseEvent', { type: 'mouseReleased', button: (options.button || 'left'), x: this._x, y: this._y, modifiers: this._keyboard._modifiers, clickCount: (options.clickCount || 1) }); } } class Touchscreen { /** * @param {Puppeteer.Session} client * @param {Keyboard} keyboard */ constructor(client, keyboard) { this._client = client; this._keyboard = keyboard; } /** * @param {number} x * @param {number} y */ async tap(x, y) { const touchPoints = [{x: Math.round(x), y: Math.round(y)}]; await this._client.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints, modifiers: this._keyboard._modifiers }); await this._client.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [], modifiers: this._keyboard._modifiers }); } } /** * @type {Object} */ const keys = { 'Cancel': {keyCode: 3, code: 'Abort'}, 'Help': {keyCode: 6, code: 'Help'}, 'Backspace': {keyCode: 8, code: 'Backspace'}, 'Tab': {keyCode: 9, code: 'Tab'}, 'Clear': {keyCode: 12, code: ''}, 'Enter': {keyCode: 13, code: 'Enter'}, 'Shift': {keyCode: 16, code: 'ShiftLeft'}, 'Control': {keyCode: 17, code: 'ControlLeft'}, 'Alt': {keyCode: 18, code: 'AltLeft'}, 'Pause': {keyCode: 19, code: 'Pause'}, 'CapsLock': {keyCode: 20, code: 'CapsLock'}, 'Escape': {keyCode: 27, code: 'Escape'}, 'Convert': {keyCode: 28, code: 'Convert'}, 'NonConvert': {keyCode: 29, code: 'NonConvert'}, 'Accept': {keyCode: 30, code: ''}, 'ModeChange': {keyCode: 31, code: ''}, 'PageUp': {keyCode: 33, code: 'PageUp'}, 'PageDown': {keyCode: 34, code: 'PageDown'}, 'End': {keyCode: 35, code: 'End'}, 'Home': {keyCode: 36, code: 'Home'}, 'ArrowLeft': {keyCode: 37, code: 'ArrowLeft'}, 'ArrowUp': {keyCode: 38, code: 'ArrowUp'}, 'ArrowRight': {keyCode: 39, code: 'ArrowRight'}, 'ArrowDown': {keyCode: 40, code: 'ArrowDown'}, 'Select': {keyCode: 41, code: 'Select'}, 'Print': {keyCode: 42, code: ''}, 'Execute': {keyCode: 43, code: 'Open'}, 'PrintScreen': {keyCode: 44, code: 'PrintScreen'}, 'Insert': {keyCode: 45, code: 'Insert'}, 'Delete': {keyCode: 46, code: 'Delete'}, ')': {keyCode: 48, code: 'Digit0'}, '!': {keyCode: 49, code: 'Digit1'}, '@': {keyCode: 50, code: 'Digit2'}, '#': {keyCode: 51, code: 'Digit3'}, '$': {keyCode: 52, code: 'Digit4'}, '%': {keyCode: 53, code: 'Digit5'}, '^': {keyCode: 54, code: 'Digit6'}, '&': {keyCode: 55, code: 'Digit7'}, '*': {keyCode: 56, code: 'Digit8'}, '(': {keyCode: 57, code: 'Digit9'}, 'Meta': {keyCode: 91, code: 'MetaLeft'}, 'ContextMenu': {keyCode: 93, code: 'ContextMenu'}, 'F1': {keyCode: 112, code: 'F1'}, 'F2': {keyCode: 113, code: 'F2'}, 'F3': {keyCode: 114, code: 'F3'}, 'F4': {keyCode: 115, code: 'F4'}, 'F5': {keyCode: 116, code: 'F5'}, 'F6': {keyCode: 117, code: 'F6'}, 'F7': {keyCode: 118, code: 'F7'}, 'F8': {keyCode: 119, code: 'F8'}, 'F9': {keyCode: 120, code: 'F9'}, 'F10': {keyCode: 121, code: 'F10'}, 'F11': {keyCode: 122, code: 'F11'}, 'F12': {keyCode: 123, code: 'F12'}, 'F13': {keyCode: 124, code: 'F13'}, 'F14': {keyCode: 125, code: 'F14'}, 'F15': {keyCode: 126, code: 'F15'}, 'F16': {keyCode: 127, code: 'F16'}, 'F17': {keyCode: 128, code: 'F17'}, 'F18': {keyCode: 129, code: 'F18'}, 'F19': {keyCode: 130, code: 'F19'}, 'F20': {keyCode: 131, code: 'F20'}, 'F21': {keyCode: 132, code: 'F21'}, 'F22': {keyCode: 133, code: 'F22'}, 'F23': {keyCode: 134, code: 'F23'}, 'F24': {keyCode: 135, code: 'F24'}, 'NumLock': {keyCode: 144, code: 'NumLock'}, 'ScrollLock': {keyCode: 145, code: 'ScrollLock'}, 'AudioVolumeMute': {keyCode: 173, code: 'AudioVolumeMute'}, 'AudioVolumeDown': {keyCode: 174, code: 'AudioVolumeDown'}, 'AudioVolumeUp': {keyCode: 175, code: 'AudioVolumeUp'}, 'MediaTrackNext': {keyCode: 176, code: 'MediaTrackNext'}, 'MediaTrackPrevious': {keyCode: 177, code: 'MediaTrackPrevious'}, 'MediaStop': {keyCode: 178, code: 'MediaStop'}, 'MediaPlayPause': {keyCode: 179, code: 'MediaPlayPause'}, ';': {keyCode: 186, code: 'Semicolon'}, ':': {keyCode: 186, code: 'Semicolon'}, '=': {keyCode: 187, code: 'Equal'}, '+': {keyCode: 187, code: 'Equal'}, ',': {keyCode: 188, code: 'Comma'}, '<': {keyCode: 188, code: 'Comma'}, '-': {keyCode: 189, code: 'Minus'}, '_': {keyCode: 189, code: 'Minus'}, '.': {keyCode: 190, code: 'Period'}, '>': {keyCode: 190, code: 'Period'}, '/': {keyCode: 191, code: 'Slash'}, '?': {keyCode: 191, code: 'Slash'}, '`': {keyCode: 192, code: 'Backquote'}, '~': {keyCode: 192, code: 'Backquote'}, '[': {keyCode: 219, code: 'BracketLeft'}, '{': {keyCode: 219, code: 'BracketLeft'}, '\\': {keyCode: 220, code: 'Backslash'}, '|': {keyCode: 220, code: 'Backslash'}, ']': {keyCode: 221, code: 'BracketRight'}, '}': {keyCode: 221, code: 'BracketRight'}, '\'': {keyCode: 222, code: 'Quote'}, '"': {keyCode: 222, code: 'Quote'}, 'AltGraph': {keyCode: 225, code: 'AltGraph'}, 'Attn': {keyCode: 246, code: ''}, 'CrSel': {keyCode: 247, code: 'Props'}, 'ExSel': {keyCode: 248, code: ''}, 'EraseEof': {keyCode: 249, code: ''}, 'Play': {keyCode: 250, code: ''}, 'ZoomOut': {keyCode: 251, code: ''}, '0': { keyCode: 48, code: 'Digit0'}, '1': { keyCode: 49, code: 'Digit1'}, '2': { keyCode: 50, code: 'Digit2'}, '3': { keyCode: 51, code: 'Digit3'}, '4': { keyCode: 52, code: 'Digit4'}, '5': { keyCode: 53, code: 'Digit5'}, '6': { keyCode: 54, code: 'Digit6'}, '7': { keyCode: 55, code: 'Digit7'}, '8': { keyCode: 56, code: 'Digit8'}, '9': { keyCode: 57, code: 'Digit9'}, 'q': { keyCode: 81, code: 'KeyQ'}, 'w': { keyCode: 87, code: 'KeyW'}, 'e': { keyCode: 69, code: 'KeyE'}, 'r': { keyCode: 82, code: 'KeyR'}, 't': { keyCode: 84, code: 'KeyT'}, 'y': { keyCode: 89, code: 'KeyY'}, 'u': { keyCode: 85, code: 'KeyU'}, 'i': { keyCode: 73, code: 'KeyI'}, 'o': { keyCode: 79, code: 'KeyO'}, 'p': { keyCode: 80, code: 'KeyP'}, 'a': { keyCode: 65, code: 'KeyA'}, 's': { keyCode: 83, code: 'KeyS'}, 'd': { keyCode: 68, code: 'KeyD'}, 'f': { keyCode: 70, code: 'KeyF'}, 'g': { keyCode: 71, code: 'KeyG'}, 'h': { keyCode: 72, code: 'KeyH'}, 'j': { keyCode: 74, code: 'KeyJ'}, 'k': { keyCode: 75, code: 'KeyK'}, 'l': { keyCode: 76, code: 'KeyL'}, 'z': { keyCode: 90, code: 'KeyZ'}, 'x': { keyCode: 88, code: 'KeyX'}, 'c': { keyCode: 67, code: 'KeyC'}, 'v': { keyCode: 86, code: 'KeyV'}, 'b': { keyCode: 66, code: 'KeyB'}, 'n': { keyCode: 78, code: 'KeyN'}, 'm': { keyCode: 77, code: 'KeyM'}, 'Q': { keyCode: 81, code: 'KeyQ'}, 'W': { keyCode: 87, code: 'KeyW'}, 'E': { keyCode: 69, code: 'KeyE'}, 'R': { keyCode: 82, code: 'KeyR'}, 'T': { keyCode: 84, code: 'KeyT'}, 'Y': { keyCode: 89, code: 'KeyY'}, 'U': { keyCode: 85, code: 'KeyU'}, 'I': { keyCode: 73, code: 'KeyI'}, 'O': { keyCode: 79, code: 'KeyO'}, 'P': { keyCode: 80, code: 'KeyP'}, 'A': { keyCode: 65, code: 'KeyA'}, 'S': { keyCode: 83, code: 'KeyS'}, 'D': { keyCode: 68, code: 'KeyD'}, 'F': { keyCode: 70, code: 'KeyF'}, 'G': { keyCode: 71, code: 'KeyG'}, 'H': { keyCode: 72, code: 'KeyH'}, 'J': { keyCode: 74, code: 'KeyJ'}, 'K': { keyCode: 75, code: 'KeyK'}, 'L': { keyCode: 76, code: 'KeyL'}, 'Z': { keyCode: 90, code: 'KeyZ'}, 'X': { keyCode: 88, code: 'KeyX'}, 'C': { keyCode: 67, code: 'KeyC'}, 'V': { keyCode: 86, code: 'KeyV'}, 'B': { keyCode: 66, code: 'KeyB'}, 'N': { keyCode: 78, code: 'KeyN'}, 'M': { keyCode: 77, code: 'KeyM'} }; /** * @param {string} key * @return {number} */ function keyCodeForKey(key) { if (keys[key]) return keys[key].keyCode; if (key.length === 1) return key.toUpperCase().charCodeAt(0); return 0; } /** * @param {string} key * @return {string} */ function codeForKey(key) { if (!keys[key]) return ''; return keys[key].code; } module.exports = { Keyboard, Mouse, Touchscreen}; helper.tracePublicAPI(Keyboard); helper.tracePublicAPI(Mouse); helper.tracePublicAPI(Touchscreen);