lib/input/Keyboard.js
const { assert } = require('../helper')
const keyDefinitions = require('./USKeyboardLayout')
class Keyboard {
/**
* @param {Chrome|CRIConnection|CDPSession|Object} client
*/
constructor (client) {
this._client = client
this._modifiers = 0
this._pressedKeys = new Set()
/**
* @type {?Object<string, KeyDefinition>}
* @private
*/
this._altKeyDef = null
}
/**
* @param {Object<string, KeyDefinition>} keyDef
*/
useAlternativeKeyDefinitions (keyDef) {
this._altKeyDef = keyDef
}
/**
* @param {string} key
* @param {{text?: string}=} options
*/
async down (key, options = { text: undefined }) {
const description = this._keyDescriptionForString(key)
const autoRepeat = this._pressedKeys.has(description.code)
this._pressedKeys.add(description.code)
this._modifiers |= this._modifierBit(description.key)
const text = options.text === undefined ? description.text : options.text
await this._client.send('Input.dispatchKeyEvent', {
type: text ? 'keyDown' : 'rawKeyDown',
modifiers: this._modifiers,
windowsVirtualKeyCode: description.keyCode,
code: description.code,
key: description.key,
text: text,
unmodifiedText: text,
autoRepeat,
location: description.location,
isKeypad: description.location === 3
})
}
/**
* @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 {KeyDefinition}
* @private
*/
_getKeyDefValue (key) {
if (this._altKeyDef != null) return this._altKeyDef[key]
return keyDefinitions[key]
}
/**
* @param {string} keyString
* @return {KeyDescription}
*/
_keyDescriptionForString (keyString) {
const shift = this._modifiers & 8
const description = {
key: '',
keyCode: 0,
code: '',
text: '',
location: 0
}
const definition = this._getKeyDefValue(keyString)
assert(definition, `Unknown key: "${keyString}"`)
if (definition.key) description.key = definition.key
if (shift && definition.shiftKey) description.key = definition.shiftKey
if (definition.keyCode) description.keyCode = definition.keyCode
if (shift && definition.shiftKeyCode) {
description.keyCode = definition.shiftKeyCode
}
if (definition.code) description.code = definition.code
if (definition.location) description.location = definition.location
if (description.key.length === 1) description.text = description.key
if (definition.text) description.text = definition.text
if (shift && definition.shiftText) description.text = definition.shiftText
// if any modifiers besides shift are pressed, no text should be sent
if (this._modifiers & ~8) description.text = ''
return description
}
/**
* @param {string} key
*/
async up (key) {
const description = this._keyDescriptionForString(key)
this._modifiers &= ~this._modifierBit(description.key)
this._pressedKeys.delete(description.code)
await this._client.send('Input.dispatchKeyEvent', {
type: 'keyUp',
modifiers: this._modifiers,
key: description.key,
windowsVirtualKeyCode: description.keyCode,
code: description.code,
location: description.location
})
}
/**
* @param {string} char
*/
async sendCharacter (char) {
await this._client.send('Input.insertText', { text: char })
}
/**
* @param {string} text
* @param {{delay: (number|undefined)}} [options]
*/
async type (text, options) {
let delay = 0
if (options && options.delay) delay = options.delay
for (let i = 0; i < text.length; i++) {
const char = text[i]
if (this._getKeyDefValue(char)) {
await this.press(char, { delay })
} else {
await this.sendCharacter(char)
}
if (delay) {
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}
/**
* @param {string} key
* @param {!{delay?: number, text?: string}} [options]
*/
async press (key, options = {}) {
const { delay = null } = options
await this.down(key, options)
if (delay !== null) {
await new Promise(resolve => setTimeout(resolve, options.delay))
}
await this.up(key)
}
}
/**
* @type {Keyboard}
*/
module.exports = Keyboard
/**
* @typedef {Object} KeyDescription
* @property {number} keyCode
* @property {string} key
* @property {string} text
* @property {string} code
* @property {number} location
*/