mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
3335d369d3
This changes sendCharacter to use document.execCommand instead of sending a `'char'` event from the protocol. This is more aligned with how input would come in from emoji keyboards, and removes the 3ish byte limit on characters that can be sent which prevented larger emoji from being rendered correctly. Emoji will still fail to type correctly if typing them into an iframe that is in shadow dom. fixes #1096
304 lines
7.6 KiB
JavaScript
304 lines
7.6 KiB
JavaScript
/**
|
|
* 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, assert} = require('./helper');
|
|
const keyDefinitions = require('./USKeyboardLayout');
|
|
|
|
/**
|
|
* @typedef {Object} KeyDescription
|
|
* @property {number} keyCode
|
|
* @property {string} key
|
|
* @property {string} text
|
|
* @property {string} code
|
|
* @property {number} location
|
|
*/
|
|
|
|
class Keyboard {
|
|
/**
|
|
* @param {!Puppeteer.CDPSession} 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 }) {
|
|
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} keyString
|
|
* @return {KeyDescription}
|
|
*/
|
|
_keyDescriptionForString(keyString) {
|
|
const shift = this._modifiers & 8;
|
|
const description = {
|
|
key: '',
|
|
keyCode: 0,
|
|
code: '',
|
|
text: '',
|
|
location: 0
|
|
};
|
|
|
|
const definition = keyDefinitions[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 (const char of text) {
|
|
if (keyDefinitions[char])
|
|
await this.press(char, {delay});
|
|
else
|
|
await this.sendCharacter(char);
|
|
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.CDPSession} 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.CDPSession} client
|
|
* @param {Keyboard} keyboard
|
|
*/
|
|
constructor(client, keyboard) {
|
|
this._client = client;
|
|
this._keyboard = keyboard;
|
|
}
|
|
|
|
/**
|
|
* @param {number} x
|
|
* @param {number} y
|
|
*/
|
|
async tap(x, y) {
|
|
// Touches appear to be lost during the first frame after navigation.
|
|
// This waits a frame before sending the tap.
|
|
// @see https://crbug.com/613219
|
|
await this._client.send('Runtime.evaluate', {
|
|
expression: 'new Promise(x => requestAnimationFrame(() => requestAnimationFrame(x)))',
|
|
awaitPromise: true
|
|
});
|
|
|
|
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
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = { Keyboard, Mouse, Touchscreen};
|
|
helper.tracePublicAPI(Keyboard);
|
|
helper.tracePublicAPI(Mouse);
|
|
helper.tracePublicAPI(Touchscreen);
|