/** * 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 {!Connection} client */ constructor(client) { this._client = client; this._modifiers = 0; this._pressedKeys = new Set(); } /** * @param {string} key * @param {{text: (string|undefined)}} options * @return {!Promise} */ async down(key, options) { let {text} = options || {}; let 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: codeForKey(key), key: key, text: 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 * @return {!Promise} */ 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: codeForKey(key), }); } /** * @param {string} char * @return {!Promise} */ async sendCharacter(char) { await this._client.send('Input.dispatchKeyEvent', { type: 'char', modifiers: this._modifiers, text: char, key: char, unmodifiedText: char }); } } class Mouse { /** * @param {!Connection} 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 * @return {!Promise} */ async move(x, y) { this._x = x; this._y = y; await this._client.send('Input.dispatchMouseEvent', { type: 'mouseMoved', button: this._button, x, y, modifiers: this._keyboard._modifiers }); await this._doubleRaf(); } /** * @param {number} x * @param {number} y * @param {!Object=} options */ async click(x, y, options) { this.move(x, y); this.down(options); await this.up(options); } async _doubleRaf() { // This is a hack for now, to make clicking less race-prone. See crbug.com/747647 await this._client.send('Runtime.evaluate', { expression: 'new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))', awaitPromise: true, returnByValue: true }); } /** * @param {!Object=} options */ async down(options) { if (!options) 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) }); await this._doubleRaf(); } /** * @param {!Object=} options */ async up(options) { if (!options) 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) }); await this._doubleRaf(); } } let keys = { 'Cancel': 3, 'Help': 6, 'Backspace': 8, 'Tab': 9, 'Clear': 12, 'Enter': 13, 'Shift': 16, 'Control': 17, 'Alt': 18, 'Pause': 19, 'CapsLock': 20, 'Escape': 27, 'Convert': 28, 'NonConvert': 29, 'Accept': 30, 'ModeChange': 31, 'PageUp': 33, 'PageDown': 34, 'End': 35, 'Home': 36, 'ArrowLeft': 37, 'ArrowUp': 38, 'ArrowRight': 39, 'ArrowDown': 40, 'Select': 41, 'Print': 42, 'Execute': 43, 'PrintScreen': 44, 'Insert': 45, 'Delete': 46, ')': 48, '!': 49, '@': 50, '#': 51, '$': 52, '%': 53, '^': 54, '&': 55, '*': 56, '(': 57, 'Meta': 91, 'ContextMenu': 93, 'F1': 112, 'F2': 113, 'F3': 114, 'F4': 115, 'F5': 116, 'F6': 117, 'F7': 118, 'F8': 119, 'F9': 120, 'F10': 121, 'F11': 122, 'F12': 123, 'F13': 124, 'F14': 125, 'F15': 126, 'F16': 127, 'F17': 128, 'F18': 129, 'F19': 130, 'F20': 131, 'F21': 132, 'F22': 133, 'F23': 134, 'F24': 135, 'NumLock': 144, 'ScrollLock': 145, 'AudioVolumeMute': 173, 'AudioVolumeDown': 174, 'AudioVolumeUp': 175, 'MediaTrackNext': 176, 'MediaTrackPrevious': 177, 'MediaStop': 178, 'MediaPlayPause': 179, ';': 186, ':': 186, '=': 187, '+': 187, ',': 188, '<': 188, '-': 189, '_': 189, '.': 190, '>': 190, '/': 191, '?': 191, '`': 192, '~': 192, '[': 219, '{': 219, '\\': 220, '|': 220, ']': 221, '}': 221, '\'': 222, '"': 222, 'AltGraph': 225, 'Attn': 246, 'CrSel': 247, 'ExSel': 248, 'EraseEof': 249, 'Play': 250, 'ZoomOut': 251 }; /** * @param {string} key * @return {number} */ function codeForKey(key) { if (keys[key]) return keys[key]; if (key.length === 1) return key.toUpperCase().charCodeAt(0); return 0; } module.exports = { Keyboard, Mouse }; helper.tracePublicAPI(Keyboard); helper.tracePublicAPI(Mouse);