UNPKG

7.85 kBJavaScriptView Raw
1/**
2 * Copyright 2017 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the 'License');
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an 'AS IS' BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17const {helper, assert} = require('./helper');
18const keyDefinitions = require('./USKeyboardLayout');
19
20/**
21 * @typedef {Object} KeyDescription
22 * @property {number} keyCode
23 * @property {string} key
24 * @property {string} text
25 * @property {string} code
26 * @property {number} location
27 */
28
29class Keyboard {
30 /**
31 * @param {!Puppeteer.CDPSession} client
32 */
33 constructor(client) {
34 this._client = client;
35 this._modifiers = 0;
36 this._pressedKeys = new Set();
37 }
38
39 /**
40 * @param {string} key
41 * @param {{text: string}=} options
42 */
43 async down(key, options = { text: undefined }) {
44 const description = this._keyDescriptionForString(key);
45
46 const autoRepeat = this._pressedKeys.has(description.code);
47 this._pressedKeys.add(description.code);
48 this._modifiers |= this._modifierBit(description.key);
49
50 const text = options.text === undefined ? description.text : options.text;
51 await this._client.send('Input.dispatchKeyEvent', {
52 type: text ? 'keyDown' : 'rawKeyDown',
53 modifiers: this._modifiers,
54 windowsVirtualKeyCode: description.keyCode,
55 code: description.code,
56 key: description.key,
57 text: text,
58 unmodifiedText: text,
59 autoRepeat,
60 location: description.location,
61 isKeypad: description.location === 3
62 });
63 }
64
65 /**
66 * @param {string} key
67 * @return {number}
68 */
69 _modifierBit(key) {
70 if (key === 'Alt')
71 return 1;
72 if (key === 'Control')
73 return 2;
74 if (key === 'Meta')
75 return 4;
76 if (key === 'Shift')
77 return 8;
78 return 0;
79 }
80
81 /**
82 * @param {string} keyString
83 * @return {KeyDescription}
84 */
85 _keyDescriptionForString(keyString) {
86 const shift = this._modifiers & 8;
87 const description = {
88 key: '',
89 keyCode: 0,
90 code: '',
91 text: '',
92 location: 0
93 };
94
95 const definition = keyDefinitions[keyString];
96 assert(definition, `Unknown key: "${keyString}"`);
97
98 if (definition.key)
99 description.key = definition.key;
100 if (shift && definition.shiftKey)
101 description.key = definition.shiftKey;
102
103 if (definition.keyCode)
104 description.keyCode = definition.keyCode;
105 if (shift && definition.shiftKeyCode)
106 description.keyCode = definition.shiftKeyCode;
107
108 if (definition.code)
109 description.code = definition.code;
110
111 if (definition.location)
112 description.location = definition.location;
113
114 if (description.key.length === 1)
115 description.text = description.key;
116
117 if (definition.text)
118 description.text = definition.text;
119 if (shift && definition.shiftText)
120 description.text = definition.shiftText;
121
122 // if any modifiers besides shift are pressed, no text should be sent
123 if (this._modifiers & ~8)
124 description.text = '';
125
126 return description;
127 }
128
129 /**
130 * @param {string} key
131 */
132 async up(key) {
133 const description = this._keyDescriptionForString(key);
134
135 this._modifiers &= ~this._modifierBit(description.key);
136 this._pressedKeys.delete(description.code);
137 await this._client.send('Input.dispatchKeyEvent', {
138 type: 'keyUp',
139 modifiers: this._modifiers,
140 key: description.key,
141 windowsVirtualKeyCode: description.keyCode,
142 code: description.code,
143 location: description.location
144 });
145 }
146
147 /**
148 * @param {string} char
149 */
150 async sendCharacter(char) {
151 await this._client.send('Input.insertText', {text: char});
152 }
153
154 /**
155 * @param {string} text
156 * @param {{delay: (number|undefined)}=} options
157 */
158 async type(text, options) {
159 let delay = 0;
160 if (options && options.delay)
161 delay = options.delay;
162 for (const char of text) {
163 if (keyDefinitions[char])
164 await this.press(char, {delay});
165 else
166 await this.sendCharacter(char);
167 if (delay)
168 await new Promise(f => setTimeout(f, delay));
169 }
170 }
171
172 /**
173 * @param {string} key
174 * @param {!Object=} options
175 */
176 async press(key, options) {
177 await this.down(key, options);
178 if (options && options.delay)
179 await new Promise(f => setTimeout(f, options.delay));
180 await this.up(key);
181 }
182}
183
184class Mouse {
185 /**
186 * @param {Puppeteer.CDPSession} client
187 * @param {!Keyboard} keyboard
188 */
189 constructor(client, keyboard) {
190 this._client = client;
191 this._keyboard = keyboard;
192 this._x = 0;
193 this._y = 0;
194 /** @type {'none'|'left'|'right'|'middle'} */
195 this._button = 'none';
196 }
197
198 /**
199 * @param {number} x
200 * @param {number} y
201 * @param {Object=} options
202 * @return {!Promise}
203 */
204 async move(x, y, options = {}) {
205 const fromX = this._x, fromY = this._y;
206 this._x = x;
207 this._y = y;
208 const steps = options.steps || 1;
209 for (let i = 1; i <= steps; i++) {
210 await this._client.send('Input.dispatchMouseEvent', {
211 type: 'mouseMoved',
212 button: this._button,
213 x: fromX + (this._x - fromX) * (i / steps),
214 y: fromY + (this._y - fromY) * (i / steps),
215 modifiers: this._keyboard._modifiers
216 });
217 }
218 }
219
220 /**
221 * @param {number} x
222 * @param {number} y
223 * @param {!Object=} options
224 */
225 async click(x, y, options = {}) {
226 this.move(x, y);
227 this.down(options);
228 if (typeof options.delay === 'number')
229 await new Promise(f => setTimeout(f, options.delay));
230 await this.up(options);
231 }
232
233 /**
234 * @param {!Object=} options
235 */
236 async down(options = {}) {
237 this._button = (options.button || 'left');
238 await this._client.send('Input.dispatchMouseEvent', {
239 type: 'mousePressed',
240 button: this._button,
241 x: this._x,
242 y: this._y,
243 modifiers: this._keyboard._modifiers,
244 clickCount: (options.clickCount || 1)
245 });
246 }
247
248 /**
249 * @param {!Object=} options
250 */
251 async up(options = {}) {
252 this._button = 'none';
253 await this._client.send('Input.dispatchMouseEvent', {
254 type: 'mouseReleased',
255 button: (options.button || 'left'),
256 x: this._x,
257 y: this._y,
258 modifiers: this._keyboard._modifiers,
259 clickCount: (options.clickCount || 1)
260 });
261 }
262}
263
264class Touchscreen {
265 /**
266 * @param {Puppeteer.CDPSession} client
267 * @param {Keyboard} keyboard
268 */
269 constructor(client, keyboard) {
270 this._client = client;
271 this._keyboard = keyboard;
272 }
273
274 /**
275 * @param {number} x
276 * @param {number} y
277 */
278 async tap(x, y) {
279 // Touches appear to be lost during the first frame after navigation.
280 // This waits a frame before sending the tap.
281 // @see https://crbug.com/613219
282 await this._client.send('Runtime.evaluate', {
283 expression: 'new Promise(x => requestAnimationFrame(() => requestAnimationFrame(x)))',
284 awaitPromise: true
285 });
286
287 const touchPoints = [{x: Math.round(x), y: Math.round(y)}];
288 await this._client.send('Input.dispatchTouchEvent', {
289 type: 'touchStart',
290 touchPoints,
291 modifiers: this._keyboard._modifiers
292 });
293 await this._client.send('Input.dispatchTouchEvent', {
294 type: 'touchEnd',
295 touchPoints: [],
296 modifiers: this._keyboard._modifiers
297 });
298 }
299}
300
301module.exports = { Keyboard, Mouse, Touchscreen};
302helper.tracePublicAPI(Keyboard);
303helper.tracePublicAPI(Mouse);
304helper.tracePublicAPI(Touchscreen);