Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/javascript/atoms/keyboard.js
4009 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
/**
19
* @fileoverview The file contains an abstraction of a keyboard
20
* for simulating the pressing and releasing of keys.
21
*/
22
23
goog.provide('bot.Keyboard');
24
goog.provide('bot.Keyboard.Key');
25
goog.provide('bot.Keyboard.Keys');
26
27
goog.require('bot.Device');
28
goog.require('bot.Error');
29
goog.require('bot.ErrorCode');
30
goog.require('bot.dom');
31
goog.require('bot.events.EventType');
32
goog.require('bot.userAgent');
33
goog.require('goog.array');
34
goog.require('goog.dom.TagName');
35
goog.require('goog.dom.selection');
36
goog.require('goog.structs.Map');
37
goog.require('goog.structs.Set');
38
goog.require('goog.userAgent');
39
goog.require('goog.utils');
40
41
42
43
/**
44
* A keyboard that provides atomic typing actions.
45
*
46
* @constructor
47
* @param {bot.Keyboard.State=} opt_state Optional keyboard state.
48
* @extends {bot.Device}
49
* @suppress {deprecated}
50
*/
51
bot.Keyboard = function (opt_state) {
52
bot.Device.call(this);
53
54
/** @private {boolean} */
55
this.editable_ = bot.dom.isEditable(this.getElement());
56
57
/** @private {number} */
58
this.currentPos_ = 0;
59
60
/** @private {!goog.structs.Set.<!bot.Keyboard.Key>} */
61
this.pressed_ = new goog.structs.Set();
62
63
if (opt_state) {
64
// If a state is passed, let's assume we were passed an object with
65
// the correct properties.
66
goog.array.forEach(opt_state['pressed'], function (key) {
67
this.setKeyPressed_(/** @type {!bot.Keyboard.Key} */(key), true);
68
}, this);
69
70
this.currentPos_ = opt_state['currentPos'] || 0;
71
}
72
};
73
goog.utils.inherits(bot.Keyboard, bot.Device);
74
75
76
/**
77
* Describes the current state of a keyboard.
78
* @typedef {{pressed: !Array.<!bot.Keyboard.Key>,
79
* currentPos: number}}
80
*/
81
bot.Keyboard.State;
82
83
84
/**
85
* Maps characters to (key,boolean) pairs, where the key generates the
86
* character and the boolean is true when the shift must be pressed.
87
* @private {!Object.<string, {key: !bot.Keyboard.Key, shift: boolean}>}
88
* @const
89
*/
90
bot.Keyboard.CHAR_TO_KEY_ = {};
91
92
93
/**
94
* Constructs a new key and, if it is a character key, adds a mapping from the
95
* character to is in the CHAR_TO_KEY_ map. Using this factory function instead
96
* of the new keyword, also helps reduce the size of the compiled Js fragment.
97
*
98
* @param {null|number|
99
* {gecko: (?number), ieWebkit: (?number)}} code
100
* Either a single keycode or a record of per-browser keycodes.
101
* @param {string=} opt_char Character when shift is not pressed.
102
* @param {string=} opt_shiftChar Character when shift is pressed.
103
* @return {!bot.Keyboard.Key} The new key.
104
* @private
105
*/
106
bot.Keyboard.newKey_ = function (code, opt_char, opt_shiftChar) {
107
if (goog.utils.isObject(code)) {
108
if (goog.userAgent.GECKO) {
109
code = code.gecko;
110
} else { // IE and Webkit
111
code = code.ieWebkit;
112
}
113
}
114
var key = new bot.Keyboard.Key(/** @type {?number} */ (code), opt_char, opt_shiftChar);
115
116
// For a character key, potentially map the character to the key in the
117
// CHAR_TO_KEY_ map. Because of numpad, multiple keys may have the same
118
// character. To avoid mapping numpad keys, we overwrite a mapping only if
119
// the key has a distinct shift character.
120
if (opt_char && (!(opt_char in bot.Keyboard.CHAR_TO_KEY_) || opt_shiftChar)) {
121
bot.Keyboard.CHAR_TO_KEY_[opt_char] = { key: key, shift: false };
122
if (opt_shiftChar) {
123
bot.Keyboard.CHAR_TO_KEY_[opt_shiftChar] = { key: key, shift: true };
124
}
125
}
126
127
return key;
128
};
129
130
131
132
/**
133
* A key on the keyboard.
134
*
135
* @constructor
136
* @param {?number} code Keycode for the key; null for the (rare) case
137
* that pressing the key issues no key events.
138
* @param {string=} opt_char Character when shift is not pressed; null
139
* when the key does not cause a character to be typed.
140
* @param {string=} opt_shiftChar Character when shift is pressed; null
141
* when the key does not cause a character to be typed.
142
*/
143
bot.Keyboard.Key = function (code, opt_char, opt_shiftChar) {
144
/** @type {?number} */
145
this.code = code;
146
147
/** @type {?string} */
148
this.character = opt_char || null;
149
150
/** @type {?string} */
151
this.shiftChar = opt_shiftChar || this.character;
152
};
153
154
155
/**
156
* Type definition for the keyboard keys object.
157
* @typedef {{
158
* BACKSPACE: !bot.Keyboard.Key,
159
* TAB: !bot.Keyboard.Key,
160
* ENTER: !bot.Keyboard.Key,
161
* SHIFT: !bot.Keyboard.Key,
162
* CONTROL: !bot.Keyboard.Key,
163
* ALT: !bot.Keyboard.Key,
164
* PAUSE: !bot.Keyboard.Key,
165
* CAPS_LOCK: !bot.Keyboard.Key,
166
* ESC: !bot.Keyboard.Key,
167
* SPACE: !bot.Keyboard.Key,
168
* PAGE_UP: !bot.Keyboard.Key,
169
* PAGE_DOWN: !bot.Keyboard.Key,
170
* END: !bot.Keyboard.Key,
171
* HOME: !bot.Keyboard.Key,
172
* LEFT: !bot.Keyboard.Key,
173
* UP: !bot.Keyboard.Key,
174
* RIGHT: !bot.Keyboard.Key,
175
* DOWN: !bot.Keyboard.Key,
176
* PRINT_SCREEN: !bot.Keyboard.Key,
177
* INSERT: !bot.Keyboard.Key,
178
* DELETE: !bot.Keyboard.Key,
179
* ZERO: !bot.Keyboard.Key,
180
* ONE: !bot.Keyboard.Key,
181
* TWO: !bot.Keyboard.Key,
182
* THREE: !bot.Keyboard.Key,
183
* FOUR: !bot.Keyboard.Key,
184
* FIVE: !bot.Keyboard.Key,
185
* SIX: !bot.Keyboard.Key,
186
* SEVEN: !bot.Keyboard.Key,
187
* EIGHT: !bot.Keyboard.Key,
188
* NINE: !bot.Keyboard.Key,
189
* A: !bot.Keyboard.Key,
190
* B: !bot.Keyboard.Key,
191
* C: !bot.Keyboard.Key,
192
* D: !bot.Keyboard.Key,
193
* E: !bot.Keyboard.Key,
194
* F: !bot.Keyboard.Key,
195
* G: !bot.Keyboard.Key,
196
* H: !bot.Keyboard.Key,
197
* I: !bot.Keyboard.Key,
198
* J: !bot.Keyboard.Key,
199
* K: !bot.Keyboard.Key,
200
* L: !bot.Keyboard.Key,
201
* M: !bot.Keyboard.Key,
202
* N: !bot.Keyboard.Key,
203
* O: !bot.Keyboard.Key,
204
* P: !bot.Keyboard.Key,
205
* Q: !bot.Keyboard.Key,
206
* R: !bot.Keyboard.Key,
207
* S: !bot.Keyboard.Key,
208
* T: !bot.Keyboard.Key,
209
* U: !bot.Keyboard.Key,
210
* V: !bot.Keyboard.Key,
211
* W: !bot.Keyboard.Key,
212
* X: !bot.Keyboard.Key,
213
* Y: !bot.Keyboard.Key,
214
* Z: !bot.Keyboard.Key,
215
* META: !bot.Keyboard.Key,
216
* META_RIGHT: !bot.Keyboard.Key,
217
* CONTEXT_MENU: !bot.Keyboard.Key,
218
* NUM_ZERO: !bot.Keyboard.Key,
219
* NUM_ONE: !bot.Keyboard.Key,
220
* NUM_TWO: !bot.Keyboard.Key,
221
* NUM_THREE: !bot.Keyboard.Key,
222
* NUM_FOUR: !bot.Keyboard.Key,
223
* NUM_FIVE: !bot.Keyboard.Key,
224
* NUM_SIX: !bot.Keyboard.Key,
225
* NUM_SEVEN: !bot.Keyboard.Key,
226
* NUM_EIGHT: !bot.Keyboard.Key,
227
* NUM_NINE: !bot.Keyboard.Key,
228
* NUM_MULTIPLY: !bot.Keyboard.Key,
229
* NUM_PLUS: !bot.Keyboard.Key,
230
* NUM_MINUS: !bot.Keyboard.Key,
231
* NUM_PERIOD: !bot.Keyboard.Key,
232
* NUM_DIVISION: !bot.Keyboard.Key,
233
* NUM_LOCK: !bot.Keyboard.Key,
234
* F1: !bot.Keyboard.Key,
235
* F2: !bot.Keyboard.Key,
236
* F3: !bot.Keyboard.Key,
237
* F4: !bot.Keyboard.Key,
238
* F5: !bot.Keyboard.Key,
239
* F6: !bot.Keyboard.Key,
240
* F7: !bot.Keyboard.Key,
241
* F8: !bot.Keyboard.Key,
242
* F9: !bot.Keyboard.Key,
243
* F10: !bot.Keyboard.Key,
244
* F11: !bot.Keyboard.Key,
245
* F12: !bot.Keyboard.Key,
246
* EQUALS: !bot.Keyboard.Key,
247
* SEPARATOR: !bot.Keyboard.Key,
248
* HYPHEN: !bot.Keyboard.Key,
249
* COMMA: !bot.Keyboard.Key,
250
* PERIOD: !bot.Keyboard.Key,
251
* SLASH: !bot.Keyboard.Key,
252
* BACKTICK: !bot.Keyboard.Key,
253
* OPEN_BRACKET: !bot.Keyboard.Key,
254
* BACKSLASH: !bot.Keyboard.Key,
255
* CLOSE_BRACKET: !bot.Keyboard.Key,
256
* SEMICOLON: !bot.Keyboard.Key,
257
* APOSTROPHE: !bot.Keyboard.Key
258
* }}
259
*/
260
bot.Keyboard.KeysType;
261
262
263
/**
264
* The set of keys known to this module.
265
*
266
* @const {!bot.Keyboard.KeysType}
267
*/
268
bot.Keyboard.Keys = /** @type {!bot.Keyboard.KeysType} */ ({
269
BACKSPACE: bot.Keyboard.newKey_(8),
270
TAB: bot.Keyboard.newKey_(9),
271
ENTER: bot.Keyboard.newKey_(13),
272
SHIFT: bot.Keyboard.newKey_(16),
273
CONTROL: bot.Keyboard.newKey_(17),
274
ALT: bot.Keyboard.newKey_(18),
275
PAUSE: bot.Keyboard.newKey_(19),
276
CAPS_LOCK: bot.Keyboard.newKey_(20),
277
ESC: bot.Keyboard.newKey_(27),
278
SPACE: bot.Keyboard.newKey_(32, ' '),
279
PAGE_UP: bot.Keyboard.newKey_(33),
280
PAGE_DOWN: bot.Keyboard.newKey_(34),
281
END: bot.Keyboard.newKey_(35),
282
HOME: bot.Keyboard.newKey_(36),
283
LEFT: bot.Keyboard.newKey_(37),
284
UP: bot.Keyboard.newKey_(38),
285
RIGHT: bot.Keyboard.newKey_(39),
286
DOWN: bot.Keyboard.newKey_(40),
287
PRINT_SCREEN: bot.Keyboard.newKey_(44),
288
INSERT: bot.Keyboard.newKey_(45),
289
DELETE: bot.Keyboard.newKey_(46),
290
291
// Number keys
292
ZERO: bot.Keyboard.newKey_(48, '0', ')'),
293
ONE: bot.Keyboard.newKey_(49, '1', '!'),
294
TWO: bot.Keyboard.newKey_(50, '2', '@'),
295
THREE: bot.Keyboard.newKey_(51, '3', '#'),
296
FOUR: bot.Keyboard.newKey_(52, '4', '$'),
297
FIVE: bot.Keyboard.newKey_(53, '5', '%'),
298
SIX: bot.Keyboard.newKey_(54, '6', '^'),
299
SEVEN: bot.Keyboard.newKey_(55, '7', '&'),
300
EIGHT: bot.Keyboard.newKey_(56, '8', '*'),
301
NINE: bot.Keyboard.newKey_(57, '9', '('),
302
303
// Letter keys
304
A: bot.Keyboard.newKey_(65, 'a', 'A'),
305
B: bot.Keyboard.newKey_(66, 'b', 'B'),
306
C: bot.Keyboard.newKey_(67, 'c', 'C'),
307
D: bot.Keyboard.newKey_(68, 'd', 'D'),
308
E: bot.Keyboard.newKey_(69, 'e', 'E'),
309
F: bot.Keyboard.newKey_(70, 'f', 'F'),
310
G: bot.Keyboard.newKey_(71, 'g', 'G'),
311
H: bot.Keyboard.newKey_(72, 'h', 'H'),
312
I: bot.Keyboard.newKey_(73, 'i', 'I'),
313
J: bot.Keyboard.newKey_(74, 'j', 'J'),
314
K: bot.Keyboard.newKey_(75, 'k', 'K'),
315
L: bot.Keyboard.newKey_(76, 'l', 'L'),
316
M: bot.Keyboard.newKey_(77, 'm', 'M'),
317
N: bot.Keyboard.newKey_(78, 'n', 'N'),
318
O: bot.Keyboard.newKey_(79, 'o', 'O'),
319
P: bot.Keyboard.newKey_(80, 'p', 'P'),
320
Q: bot.Keyboard.newKey_(81, 'q', 'Q'),
321
R: bot.Keyboard.newKey_(82, 'r', 'R'),
322
S: bot.Keyboard.newKey_(83, 's', 'S'),
323
T: bot.Keyboard.newKey_(84, 't', 'T'),
324
U: bot.Keyboard.newKey_(85, 'u', 'U'),
325
V: bot.Keyboard.newKey_(86, 'v', 'V'),
326
W: bot.Keyboard.newKey_(87, 'w', 'W'),
327
X: bot.Keyboard.newKey_(88, 'x', 'X'),
328
Y: bot.Keyboard.newKey_(89, 'y', 'Y'),
329
Z: bot.Keyboard.newKey_(90, 'z', 'Z'),
330
331
// Branded keys
332
META: bot.Keyboard.newKey_(
333
goog.userAgent.WINDOWS ? { gecko: 91, ieWebkit: 91 } :
334
(goog.userAgent.MAC ? { gecko: 224, ieWebkit: 91 } :
335
{ gecko: 0, ieWebkit: 91 })), // Linux
336
META_RIGHT: bot.Keyboard.newKey_(
337
goog.userAgent.WINDOWS ? { gecko: 92, ieWebkit: 92 } :
338
(goog.userAgent.MAC ? { gecko: 224, ieWebkit: 93 } :
339
{ gecko: 0, ieWebkit: 92 })), // Linux
340
CONTEXT_MENU: bot.Keyboard.newKey_(
341
goog.userAgent.WINDOWS ? { gecko: 93, ieWebkit: 93 } :
342
(goog.userAgent.MAC ? { gecko: 0, ieWebkit: 0 } :
343
{ gecko: 93, ieWebkit: null })), // Linux
344
345
// Numpad keys
346
NUM_ZERO: bot.Keyboard.newKey_({ gecko: 96, ieWebkit: 96 }, '0'),
347
NUM_ONE: bot.Keyboard.newKey_({ gecko: 97, ieWebkit: 97 }, '1'),
348
NUM_TWO: bot.Keyboard.newKey_({ gecko: 98, ieWebkit: 98 }, '2'),
349
NUM_THREE: bot.Keyboard.newKey_({ gecko: 99, ieWebkit: 99 }, '3'),
350
NUM_FOUR: bot.Keyboard.newKey_({ gecko: 100, ieWebkit: 100 }, '4'),
351
NUM_FIVE: bot.Keyboard.newKey_({ gecko: 101, ieWebkit: 101 }, '5'),
352
NUM_SIX: bot.Keyboard.newKey_({ gecko: 102, ieWebkit: 102 }, '6'),
353
NUM_SEVEN: bot.Keyboard.newKey_({ gecko: 103, ieWebkit: 103 }, '7'),
354
NUM_EIGHT: bot.Keyboard.newKey_({ gecko: 104, ieWebkit: 104 }, '8'),
355
NUM_NINE: bot.Keyboard.newKey_({ gecko: 105, ieWebkit: 105 }, '9'),
356
NUM_MULTIPLY: bot.Keyboard.newKey_(
357
{ gecko: 106, ieWebkit: 106 }, '*'),
358
NUM_PLUS: bot.Keyboard.newKey_(
359
{ gecko: 107, ieWebkit: 107 }, '+'),
360
NUM_MINUS: bot.Keyboard.newKey_(
361
{ gecko: 109, ieWebkit: 109 }, '-'),
362
NUM_PERIOD: bot.Keyboard.newKey_(
363
{ gecko: 110, ieWebkit: 110 }, '.'),
364
NUM_DIVISION: bot.Keyboard.newKey_(
365
{ gecko: 111, ieWebkit: 111 }, '/'),
366
NUM_LOCK: bot.Keyboard.newKey_(144),
367
368
// Function keys
369
F1: bot.Keyboard.newKey_(112),
370
F2: bot.Keyboard.newKey_(113),
371
F3: bot.Keyboard.newKey_(114),
372
F4: bot.Keyboard.newKey_(115),
373
F5: bot.Keyboard.newKey_(116),
374
F6: bot.Keyboard.newKey_(117),
375
F7: bot.Keyboard.newKey_(118),
376
F8: bot.Keyboard.newKey_(119),
377
F9: bot.Keyboard.newKey_(120),
378
F10: bot.Keyboard.newKey_(121),
379
F11: bot.Keyboard.newKey_(122),
380
F12: bot.Keyboard.newKey_(123),
381
382
// Punctuation keys
383
EQUALS: bot.Keyboard.newKey_(
384
{ gecko: 107, ieWebkit: 187 }, '=', '+'),
385
SEPARATOR: bot.Keyboard.newKey_(108, ','),
386
HYPHEN: bot.Keyboard.newKey_(
387
{ gecko: 109, ieWebkit: 189 }, '-', '_'),
388
COMMA: bot.Keyboard.newKey_(188, ',', '<'),
389
PERIOD: bot.Keyboard.newKey_(190, '.', '>'),
390
SLASH: bot.Keyboard.newKey_(191, '/', '?'),
391
BACKTICK: bot.Keyboard.newKey_(192, '`', '~'),
392
OPEN_BRACKET: bot.Keyboard.newKey_(219, '[', '{'),
393
BACKSLASH: bot.Keyboard.newKey_(220, '\\', '|'),
394
CLOSE_BRACKET: bot.Keyboard.newKey_(221, ']', '}'),
395
SEMICOLON: bot.Keyboard.newKey_(
396
{ gecko: 59, ieWebkit: 186 }, ';', ':'),
397
APOSTROPHE: bot.Keyboard.newKey_(222, '\'', '"')
398
});
399
400
401
/**
402
* Given a character, returns a pair of a key and a boolean: the key being one
403
* that types the character and the boolean indicating whether the key must be
404
* shifted to type it. This function will never return a numpad key; that is,
405
* it will always return a symbol key when given a number or math symbol.
406
*
407
* If given a character for which this module does not know the key (the key
408
* is not in the bot.Keyboard.Keys enumeration), returns a key that types the
409
* given character but has a (likely incorrect) keycode of zero.
410
*
411
* @param {string} ch Single character.
412
* @return {{key: !bot.Keyboard.Key, shift: boolean}} A pair of a key and
413
* a boolean indicating whether shift must be pressed for the character.
414
*/
415
bot.Keyboard.Key.fromChar = function (ch) {
416
if (ch.length != 1) {
417
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
418
'Argument not a single character: ' + ch);
419
}
420
var keyShiftPair = bot.Keyboard.CHAR_TO_KEY_[ch];
421
if (!keyShiftPair) {
422
// We don't know the true keycode of non-US keyboard characters, but
423
// ch.toUpperCase().charCodeAt(0) should occasionally be right, and
424
// at least yield a positive number.
425
var upperCase = ch.toUpperCase();
426
var keyCode = upperCase.charCodeAt(0);
427
var key = bot.Keyboard.newKey_(keyCode, ch.toLowerCase(), upperCase);
428
keyShiftPair = { key: key, shift: (ch != key.character) };
429
}
430
return keyShiftPair;
431
};
432
433
434
/**
435
* Array of modifier keys.
436
*
437
* @type {!Array.<!bot.Keyboard.Key>}
438
* @const
439
*/
440
bot.Keyboard.MODIFIERS = [
441
bot.Keyboard.Keys.ALT,
442
bot.Keyboard.Keys.CONTROL,
443
bot.Keyboard.Keys.META,
444
bot.Keyboard.Keys.SHIFT
445
];
446
447
448
/**
449
* Map of modifier to key.
450
* @private {!goog.structs.Map.<!bot.Device.Modifier, !bot.Keyboard.Key>}
451
* @suppress {deprecated}
452
*/
453
bot.Keyboard.MODIFIER_TO_KEY_MAP_ = (function () {
454
var modifiersMap = new goog.structs.Map();
455
modifiersMap.set(bot.Device.Modifier.SHIFT,
456
bot.Keyboard.Keys.SHIFT);
457
modifiersMap.set(bot.Device.Modifier.CONTROL,
458
bot.Keyboard.Keys.CONTROL);
459
modifiersMap.set(bot.Device.Modifier.ALT,
460
bot.Keyboard.Keys.ALT);
461
modifiersMap.set(bot.Device.Modifier.META,
462
bot.Keyboard.Keys.META);
463
464
return modifiersMap;
465
})();
466
467
468
/**
469
* The reverse map - key to modifier.
470
* @private {!goog.structs.Map.<number, !bot.Device.Modifier>}
471
* @suppress {deprecated}
472
*/
473
bot.Keyboard.KEY_TO_MODIFIER_ = (function (modifiersMap) {
474
var keyToModifierMap = new goog.structs.Map();
475
goog.array.forEach(modifiersMap.getKeys(), function (m) {
476
keyToModifierMap.set(modifiersMap.get(m).code, m);
477
});
478
479
return keyToModifierMap;
480
})(bot.Keyboard.MODIFIER_TO_KEY_MAP_);
481
482
483
/**
484
* Set the modifier state if the provided key is one, otherwise just add
485
* to the list of pressed keys.
486
* @param {!bot.Keyboard.Key} key The key to update.
487
* @param {boolean} isPressed Whether the key is pressed.
488
* @private
489
*/
490
bot.Keyboard.prototype.setKeyPressed_ = function (key, isPressed) {
491
if (goog.array.contains(bot.Keyboard.MODIFIERS, key)) {
492
var modifier = /** @type {bot.Device.Modifier}*/ (
493
bot.Keyboard.KEY_TO_MODIFIER_.get(key.code));
494
this.modifiersState.setPressed(modifier, isPressed);
495
}
496
497
if (isPressed) {
498
this.pressed_.add(key);
499
} else {
500
this.pressed_.remove(key);
501
}
502
};
503
504
505
/**
506
* The value used for newlines in the current browser/OS combination. Although
507
* the line endings look platform dependent, they are browser dependent.
508
*
509
* @private {string}
510
* @const
511
*/
512
bot.Keyboard.NEW_LINE_ = goog.userAgent.IE ? '\r\n' : '\n';
513
514
515
/**
516
* Returns whether the key is currently pressed.
517
*
518
* @param {!bot.Keyboard.Key} key Key.
519
* @return {boolean} Whether the key is pressed.
520
*/
521
bot.Keyboard.prototype.isPressed = function (key) {
522
return this.pressed_.contains(key);
523
};
524
525
526
/**
527
* Presses the given key on the keyboard. Keys that are pressed can be pressed
528
* again before releasing, to simulate repeated keys, except for modifier keys,
529
* which must be released before they can be pressed again.
530
*
531
* @param {!bot.Keyboard.Key} key Key to press.
532
*/
533
bot.Keyboard.prototype.pressKey = function (key) {
534
if (goog.array.contains(bot.Keyboard.MODIFIERS, key) && this.isPressed(key)) {
535
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
536
'Cannot press a modifier key that is already pressed.');
537
}
538
539
// Note that GECKO is special-cased below because of
540
// https://bugzilla.mozilla.org/show_bug.cgi?id=501496. "preventDefault on
541
// keydown does not cancel following keypress"
542
var performDefault = key.code !== null &&
543
this.fireKeyEvent_(bot.events.EventType.KEYDOWN, key);
544
545
// Fires keydown and stops if unsuccessful.
546
if (performDefault || goog.userAgent.GECKO) {
547
// Fires keypress if required and stops if unsuccessful.
548
if (!this.requiresKeyPress_(key) ||
549
this.fireKeyEvent_(
550
bot.events.EventType.KEYPRESS, key, !performDefault)) {
551
if (performDefault) {
552
this.maybeSubmitForm_(key);
553
if (this.editable_) {
554
this.maybeEditText_(key);
555
}
556
}
557
}
558
}
559
560
this.setKeyPressed_(key, true);
561
};
562
563
564
/**
565
* Whether the given key currently requires a keypress.
566
* TODO: Make this dependent on the state of the modifier keys.
567
*
568
* @param {bot.Keyboard.Key} key Key.
569
* @return {boolean} Whether it requires a keypress event.
570
* @private
571
*/
572
bot.Keyboard.prototype.requiresKeyPress_ = function (key) {
573
if (key.character || key == bot.Keyboard.Keys.ENTER) {
574
return true;
575
} else if (goog.userAgent.WEBKIT || goog.userAgent.EDGE) {
576
return false;
577
} else if (goog.userAgent.IE) {
578
return key == bot.Keyboard.Keys.ESC;
579
} else { // Gecko
580
switch (key) {
581
case bot.Keyboard.Keys.SHIFT:
582
case bot.Keyboard.Keys.CONTROL:
583
case bot.Keyboard.Keys.ALT:
584
return false;
585
case bot.Keyboard.Keys.META:
586
case bot.Keyboard.Keys.META_RIGHT:
587
case bot.Keyboard.Keys.CONTEXT_MENU:
588
return goog.userAgent.GECKO;
589
default:
590
return true;
591
}
592
}
593
};
594
595
596
/**
597
* Maybe submit a form if the ENTER key is released. On non-FF browsers, firing
598
* the keyPress and keyRelease events for the ENTER key does not result in a
599
* form being submitted so we have to fire the form submit event as well.
600
*
601
* @param {bot.Keyboard.Key} key Key.
602
* @private
603
*/
604
bot.Keyboard.prototype.maybeSubmitForm_ = function (key) {
605
if (key != bot.Keyboard.Keys.ENTER) {
606
return;
607
}
608
if ((goog.userAgent.GECKO && !bot.userAgent.isEngineVersion(93)) ||
609
!bot.dom.isElement(this.getElement(), goog.dom.TagName.INPUT)) {
610
return;
611
}
612
613
var form = bot.Device.findAncestorForm(this.getElement());
614
if (form) {
615
var inputs = form.getElementsByTagName('input');
616
var hasSubmit = goog.array.some(inputs, function (e) {
617
return bot.Device.isFormSubmitElement(e);
618
});
619
// The second part of this if statement will always include forms on Safari
620
// version < 5.
621
if (hasSubmit || inputs.length == 1 ||
622
(goog.userAgent.WEBKIT && !bot.userAgent.isEngineVersion(534))) {
623
this.submitForm(form);
624
}
625
}
626
};
627
628
629
/**
630
* Maybe edit text when a key is pressed in an editable form.
631
*
632
* @param {!bot.Keyboard.Key} key Key that was pressed.
633
* @private
634
*/
635
bot.Keyboard.prototype.maybeEditText_ = function (key) {
636
if (key.character) {
637
this.updateOnCharacter_(key);
638
} else {
639
switch (key) {
640
case bot.Keyboard.Keys.ENTER:
641
this.updateOnEnter_();
642
break;
643
case bot.Keyboard.Keys.BACKSPACE:
644
case bot.Keyboard.Keys.DELETE:
645
this.updateOnBackspaceOrDelete_(key);
646
break;
647
case bot.Keyboard.Keys.LEFT:
648
case bot.Keyboard.Keys.RIGHT:
649
this.updateOnLeftOrRight_(key);
650
break;
651
case bot.Keyboard.Keys.HOME:
652
case bot.Keyboard.Keys.END:
653
this.updateOnHomeOrEnd_(key);
654
break;
655
}
656
}
657
};
658
659
660
/**
661
* Releases the given key on the keyboard. Releasing a key that is not
662
* pressed results in an exception.
663
*
664
* @param {!bot.Keyboard.Key} key Key to release.
665
*/
666
bot.Keyboard.prototype.releaseKey = function (key) {
667
if (!this.isPressed(key)) {
668
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
669
'Cannot release a key that is not pressed. (' + key.code + ')');
670
}
671
if (key.code !== null) {
672
this.fireKeyEvent_(bot.events.EventType.KEYUP, key);
673
}
674
675
this.setKeyPressed_(key, false);
676
};
677
678
679
/**
680
* Given the current state of the SHIFT and CAPS_LOCK key, returns the
681
* character that will be typed is the specified key is pressed.
682
*
683
* @param {!bot.Keyboard.Key} key Key.
684
* @return {string} Character to be typed.
685
* @private
686
*/
687
bot.Keyboard.prototype.getChar_ = function (key) {
688
if (!key.character) {
689
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR, 'not a character key');
690
}
691
var shiftPressed = this.isPressed(bot.Keyboard.Keys.SHIFT);
692
return /** @type {string} */ (shiftPressed ? key.shiftChar : key.character);
693
};
694
695
696
/**
697
* Whether firing a keypress event causes text to be edited without any
698
* additional logic to surgically apply the edit.
699
* @private {boolean}
700
* @const
701
*/
702
bot.Keyboard.KEYPRESS_EDITS_TEXT_ = goog.userAgent.GECKO &&
703
!bot.userAgent.isEngineVersion(12);
704
705
706
/**
707
* @param {!bot.Keyboard.Key} key Key with character to insert.
708
* @private
709
*/
710
bot.Keyboard.prototype.updateOnCharacter_ = function (key) {
711
if (bot.Keyboard.KEYPRESS_EDITS_TEXT_) {
712
return;
713
}
714
715
var character = this.getChar_(key);
716
var newPos = goog.dom.selection.getStart(this.getElement()) + 1;
717
if (bot.Keyboard.supportsSelection(this.getElement())) {
718
goog.dom.selection.setText(this.getElement(), character);
719
goog.dom.selection.setStart(this.getElement(), newPos);
720
} else {
721
this.getElement().value += character;
722
}
723
if (goog.userAgent.WEBKIT) {
724
this.fireHtmlEvent(bot.events.EventType.TEXTINPUT);
725
}
726
if (!bot.userAgent.IE_DOC_PRE9) {
727
this.fireHtmlEvent(bot.events.EventType.INPUT);
728
}
729
this.updateCurrentPos_(newPos);
730
};
731
732
733
/** @private */
734
bot.Keyboard.prototype.updateOnEnter_ = function () {
735
if (bot.Keyboard.KEYPRESS_EDITS_TEXT_) {
736
return;
737
}
738
739
// WebKit fires text input regardless of whether a new line is added, see:
740
// https://bugs.webkit.org/show_bug.cgi?id=54152
741
if (goog.userAgent.WEBKIT) {
742
this.fireHtmlEvent(bot.events.EventType.TEXTINPUT);
743
}
744
if (bot.dom.isElement(this.getElement(), goog.dom.TagName.TEXTAREA)) {
745
var newPos = goog.dom.selection.getStart(this.getElement()) +
746
bot.Keyboard.NEW_LINE_.length;
747
if (bot.Keyboard.supportsSelection(this.getElement())) {
748
goog.dom.selection.setText(this.getElement(), bot.Keyboard.NEW_LINE_);
749
goog.dom.selection.setStart(this.getElement(), newPos);
750
} else {
751
this.getElement().value += bot.Keyboard.NEW_LINE_;
752
}
753
if (!goog.userAgent.IE) {
754
this.fireHtmlEvent(bot.events.EventType.INPUT);
755
}
756
this.updateCurrentPos_(newPos);
757
}
758
};
759
760
761
/**
762
* @param {!bot.Keyboard.Key} key Backspace or delete key.
763
* @private
764
*/
765
bot.Keyboard.prototype.updateOnBackspaceOrDelete_ = function (key) {
766
if (bot.Keyboard.KEYPRESS_EDITS_TEXT_) {
767
return;
768
}
769
770
// Determine what should be deleted. If text is already selected, that
771
// text is deleted, else we move left/right from the current cursor.
772
bot.Keyboard.checkCanUpdateSelection_(this.getElement());
773
var endpoints = goog.dom.selection.getEndPoints(this.getElement());
774
if (endpoints[0] == endpoints[1]) {
775
if (key == bot.Keyboard.Keys.BACKSPACE) {
776
goog.dom.selection.setStart(this.getElement(), endpoints[1] - 1);
777
// On IE, changing goog.dom.selection.setStart also changes the end.
778
goog.dom.selection.setEnd(this.getElement(), endpoints[1]);
779
} else {
780
goog.dom.selection.setEnd(this.getElement(), endpoints[1] + 1);
781
}
782
}
783
784
// If the endpoints are equal (e.g., the cursor was at the beginning/end
785
// of the input), the text field won't be changed.
786
endpoints = goog.dom.selection.getEndPoints(this.getElement());
787
var textChanged = !(endpoints[0] == this.getElement().value.length ||
788
endpoints[1] == 0);
789
goog.dom.selection.setText(this.getElement(), '');
790
791
// Except for IE and GECKO, we need to fire the input event manually, but
792
// only if the text was actually changed.
793
// Note: Gecko has some strange behavior with the input event. In a
794
// textarea, backspace always sends an input event, while delete only
795
// sends one if you actually change the text.
796
// In a textbox/password box, backspace always sends an input event unless
797
// the box has no text. Delete behaves the same way in Firefox 3.0, but
798
// in later versions it only fires an input event if no text changes.
799
if (!goog.userAgent.IE && textChanged ||
800
(goog.userAgent.GECKO && key == bot.Keyboard.Keys.BACKSPACE)) {
801
this.fireHtmlEvent(bot.events.EventType.INPUT);
802
}
803
804
// Update the cursor position
805
endpoints = goog.dom.selection.getEndPoints(this.getElement());
806
this.updateCurrentPos_(endpoints[1]);
807
};
808
809
810
/**
811
* @param {!bot.Keyboard.Key} key Special key to press.
812
* @private
813
*/
814
bot.Keyboard.prototype.updateOnLeftOrRight_ = function (key) {
815
bot.Keyboard.checkCanUpdateSelection_(this.getElement());
816
var element = this.getElement();
817
var start = goog.dom.selection.getStart(element);
818
var end = goog.dom.selection.getEnd(element);
819
820
var newPos, startPos = 0, endPos = 0;
821
if (key == bot.Keyboard.Keys.LEFT) {
822
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
823
// If the current position of the cursor is at the start of the
824
// selection, pressing left expands the selection one character to the
825
// left; otherwise, pressing left collapses it one character to the
826
// left.
827
if (this.currentPos_ == start) {
828
// Never attempt to move further left than the beginning of the text.
829
startPos = Math.max(start - 1, 0);
830
endPos = end;
831
newPos = startPos;
832
} else {
833
startPos = start;
834
endPos = end - 1;
835
newPos = endPos;
836
}
837
} else {
838
// With no current selection, pressing left moves the cursor one
839
// character to the left; with an existing selection, it collapses the
840
// selection to the beginning of the selection.
841
newPos = start == end ? Math.max(start - 1, 0) : start;
842
}
843
} else { // (key == bot.Keyboard.Keys.RIGHT)
844
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
845
// If the current position of the cursor is at the end of the selection,
846
// pressing right expands the selection one character to the right;
847
// otherwise, pressing right collapses it one character to the right.
848
if (this.currentPos_ == end) {
849
startPos = start;
850
// Never attempt to move further right than the end of the text.
851
endPos = Math.min(end + 1, element.value.length);
852
newPos = endPos;
853
} else {
854
startPos = start + 1;
855
endPos = end;
856
newPos = startPos;
857
}
858
} else {
859
// With no current selection, pressing right moves the cursor one
860
// character to the right; with an existing selection, it collapses the
861
// selection to the end of the selection.
862
newPos = start == end ? Math.min(end + 1, element.value.length) : end;
863
}
864
}
865
866
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
867
goog.dom.selection.setStart(element, startPos);
868
// On IE, changing goog.dom.selection.setStart also changes the end.
869
goog.dom.selection.setEnd(element, endPos);
870
} else {
871
goog.dom.selection.setCursorPosition(element, newPos);
872
}
873
this.updateCurrentPos_(newPos);
874
};
875
876
877
/**
878
* @param {!bot.Keyboard.Key} key Special key to press.
879
* @private
880
*/
881
bot.Keyboard.prototype.updateOnHomeOrEnd_ = function (key) {
882
bot.Keyboard.checkCanUpdateSelection_(this.getElement());
883
var element = this.getElement();
884
var start = goog.dom.selection.getStart(element);
885
var end = goog.dom.selection.getEnd(element);
886
// TODO: Handle multiline (TEXTAREA) elements.
887
if (key == bot.Keyboard.Keys.HOME) {
888
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
889
goog.dom.selection.setStart(element, 0);
890
// If current position is at the end of the selection, typing home
891
// changes the selection to begin at the beginning of the text, running
892
// to the where the current selection begins.
893
var endPos = this.currentPos_ == start ? end : start;
894
// On IE, changing goog.dom.selection.setStart also changes the end.
895
goog.dom.selection.setEnd(element, endPos);
896
} else {
897
goog.dom.selection.setCursorPosition(element, 0);
898
}
899
this.updateCurrentPos_(0);
900
} else { // (key == bot.Keyboard.Keys.END)
901
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
902
if (this.currentPos_ == start) {
903
// Current position is at the beginning of the selection. Typing end
904
// changes the selection to begin where the current selection ends,
905
// running to the end of the text.
906
goog.dom.selection.setStart(element, end);
907
}
908
goog.dom.selection.setEnd(element, element.value.length);
909
} else {
910
goog.dom.selection.setCursorPosition(element, element.value.length);
911
}
912
this.updateCurrentPos_(element.value.length);
913
}
914
};
915
916
917
/**
918
* Checks that the cursor position can be updated for the given element.
919
* @param {!Element} element The element to test.
920
* @throws {Error} If the cursor position cannot be updated for the given
921
* element.
922
* @see https://code.google.com/p/chromium/issues/detail?id=330456
923
* @private
924
* @suppress {uselessCode}
925
*/
926
bot.Keyboard.checkCanUpdateSelection_ = function (element) {
927
try {
928
if (typeof element.selectionStart == 'number') {
929
return;
930
}
931
} catch (ex) {
932
// The native error message is actually pretty informative, just add a
933
// reference to the relevant Chrome bug to provide more context.
934
if (ex.message.indexOf('does not support selection.') != -1) {
935
// message is a readonly property, so need to rethrow.
936
throw Error(ex.message + ' (For more information, see ' +
937
'https://code.google.com/p/chromium/issues/detail?id=330456)');
938
}
939
throw ex;
940
}
941
throw Error('Element does not support selection');
942
};
943
944
945
/**
946
* @param {!Element} element The element to test.
947
* @return {boolean} Whether the given element supports the input element
948
* selection API.
949
* @see https://code.google.com/p/chromium/issues/detail?id=330456
950
*/
951
bot.Keyboard.supportsSelection = function (element) {
952
try {
953
bot.Keyboard.checkCanUpdateSelection_(element);
954
} catch (ex) {
955
return false;
956
}
957
return true;
958
};
959
960
961
/**
962
* @param {number} pos New position of the cursor.
963
* @private
964
*/
965
bot.Keyboard.prototype.updateCurrentPos_ = function (pos) {
966
this.currentPos_ = pos;
967
};
968
969
970
/**
971
* @param {!bot.events.EventFactory_} type Event type.
972
* @param {!bot.Keyboard.Key} key Key.
973
* @param {boolean=} opt_preventDefault Whether the default event should be
974
* prevented. Defaults to false.
975
* @return {boolean} Whether the event fired successfully or was cancelled.
976
* @private
977
*/
978
bot.Keyboard.prototype.fireKeyEvent_ = function (type, key, opt_preventDefault) {
979
if (key.code === null) {
980
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
981
'Key must have a keycode to be fired.');
982
}
983
984
var args = {
985
altKey: this.isPressed(bot.Keyboard.Keys.ALT),
986
ctrlKey: this.isPressed(bot.Keyboard.Keys.CONTROL),
987
metaKey: this.isPressed(bot.Keyboard.Keys.META),
988
shiftKey: this.isPressed(bot.Keyboard.Keys.SHIFT),
989
keyCode: key.code,
990
charCode: (key.character && type == bot.events.EventType.KEYPRESS) ?
991
this.getChar_(key).charCodeAt(0) : 0,
992
preventDefault: !!opt_preventDefault
993
};
994
995
return this.fireKeyboardEvent(type, args);
996
};
997
998
999
/**
1000
* Sets focus to the element. If the element does not have focus, place cursor
1001
* at the end of the text in the element.
1002
*
1003
* @param {!Element} element Element that is moved to.
1004
*/
1005
bot.Keyboard.prototype.moveCursor = function (element) {
1006
this.setElement(element);
1007
this.editable_ = bot.dom.isEditable(element);
1008
1009
var focusChanged = this.focusOnElement();
1010
if (this.editable_ && focusChanged) {
1011
goog.dom.selection.setCursorPosition(element, element.value.length);
1012
this.updateCurrentPos_(element.value.length);
1013
}
1014
};
1015
1016
1017
/**
1018
* Serialize the current state of the keyboard.
1019
*
1020
* @return {bot.Keyboard.State} The current keyboard state.
1021
*/
1022
bot.Keyboard.prototype.getState = function () {
1023
// Need to use quoted literals here, so the compiler will not rename the
1024
// properties of the emitted object. When the object is created via the
1025
// "constructor", we will look for these *specific* properties. Everywhere
1026
// else internally, we use the dot-notation, so it's okay if the compiler
1027
// renames the internal variable name.
1028
return {
1029
'pressed': this.pressed_.getValues(),
1030
'currentPos': this.currentPos_
1031
};
1032
};
1033
1034
1035
/**
1036
* Returns the state of the modifier keys, to be shared with other input
1037
* devices.
1038
*
1039
* @return {bot.Device.ModifiersState} Modifiers state.
1040
*/
1041
bot.Keyboard.prototype.getModifiersState = function () {
1042
return this.modifiersState;
1043
};
1044
1045