Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / common / key_util.js
blob5ff64d01d7aa1c0110e2827a588913faa36a2ae3
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview A collection of JavaScript utilities used to simplify working
7  * with keyboard events.
8  */
11 goog.provide('cvox.KeyUtil');
12 goog.provide('cvox.SimpleKeyEvent');
14 goog.require('cvox.ChromeVox');
15 goog.require('cvox.KeySequence');
17 /**
18  * @typedef {{ctrlKey: (boolean|undefined),
19  *            altKey: (boolean|undefined),
20  *            shiftKey: (boolean|undefined),
21  *            keyCode: (number|undefined)}}
22  */
23 cvox.SimpleKeyEvent;
25 /**
26  * Create the namespace
27  * @constructor
28  */
29 cvox.KeyUtil = function() {
32 /**
33  * The time in ms at which the ChromeVox Sticky Mode key was pressed.
34  * @type {number}
35  */
36 cvox.KeyUtil.modeKeyPressTime = 0;
38 /**
39  * Indicates if sequencing is currently active for building a keyboard shortcut.
40  * @type {boolean}
41  */
42 cvox.KeyUtil.sequencing = false;
44 /**
45  * The previous KeySequence when sequencing is ON.
46  * @type {cvox.KeySequence}
47  */
48 cvox.KeyUtil.prevKeySequence = null;
51 /**
52  * The sticky key sequence.
53  * @type {cvox.KeySequence}
54  */
55 cvox.KeyUtil.stickyKeySequence = null;
57 /**
58  * Maximum number of key codes the sequence buffer may hold. This is the max
59  * length of a sequential keyboard shortcut, i.e. the number of key that can be
60  * pressed one after the other while modifier keys (Cros+Shift) are held down.
61  * @const
62  * @type {number}
63  */
64 cvox.KeyUtil.maxSeqLength = 2;
67 /**
68  * Convert a key event into a Key Sequence representation.
69  *
70  * @param {Event|cvox.SimpleKeyEvent} keyEvent The keyEvent to convert.
71  * @return {cvox.KeySequence} A key sequence representation of the key event.
72  */
73 cvox.KeyUtil.keyEventToKeySequence = function(keyEvent) {
74   var util = cvox.KeyUtil;
75   if (util.prevKeySequence &&
76       (util.maxSeqLength == util.prevKeySequence.length())) {
77     // Reset the sequence buffer if max sequence length is reached.
78     util.sequencing = false;
79     util.prevKeySequence = null;
80   }
81   // Either we are in the middle of a key sequence (N > H), or the key prefix
82   // was pressed before (Ctrl+Z), or sticky mode is enabled
83   var keyIsPrefixed = util.sequencing || keyEvent['keyPrefix'] ||
84       keyEvent['stickyMode'];
86   // Create key sequence.
87   var keySequence = new cvox.KeySequence(keyEvent);
89   // Check if the Cvox key should be considered as pressed because the
90   // modifier key combination is active.
91   var keyWasCvox = keySequence.cvoxModifier;
93   if (keyIsPrefixed || keyWasCvox) {
94     if (!util.sequencing && util.isSequenceSwitchKeyCode(keySequence)) {
95       // If this is the beginning of a sequence.
96       util.sequencing = true;
97       util.prevKeySequence = keySequence;
98       return keySequence;
99     } else if (util.sequencing) {
100       if (util.prevKeySequence.addKeyEvent(keyEvent)) {
101         keySequence = util.prevKeySequence;
102         util.prevKeySequence = null;
103         util.sequencing = false;
104         return keySequence;
105       } else {
106         throw 'Think sequencing is enabled, yet util.prevKeySequence already' +
107             'has two key codes' + util.prevKeySequence;
108       }
109     }
110   } else {
111     util.sequencing = false;
112   }
114   // Repeated keys pressed.
115   var currTime = new Date().getTime();
116   if (cvox.KeyUtil.isDoubleTapKey(keySequence) &&
117       util.prevKeySequence &&
118       keySequence.equals(util.prevKeySequence)) {
119     var prevTime = util.modeKeyPressTime;
120     if (prevTime > 0 && currTime - prevTime < 300) {  // Double tap
121       keySequence = util.prevKeySequence;
122       keySequence.doubleTap = true;
123       util.prevKeySequence = null;
124       util.sequencing = false;
125       // Resets the search key state tracked for ChromeOS because in OOBE,
126       // we never get a key up for the key down (keyCode 91).
127       if (cvox.ChromeVox.isChromeOS &&
128           keyEvent.keyCode == cvox.KeyUtil.getStickyKeyCode()) {
129         cvox.ChromeVox.searchKeyHeld = false;
130       }
131       return keySequence;
132     }
133     // The user double tapped the sticky key but didn't do it within the
134     // required time. It's possible they will try again, so keep track of the
135     // time the sticky key was pressed and keep track of the corresponding
136     // key sequence.
137   }
138   util.prevKeySequence = keySequence;
139   util.modeKeyPressTime = currTime;
140   return keySequence;
144  * Returns the string representation of the specified key code.
146  * @param {number} keyCode key code.
147  * @return {string} A string representation of the key event.
148  */
149 cvox.KeyUtil.keyCodeToString = function(keyCode) {
150   if (keyCode == 17) {
151     return 'Ctrl';
152   }
153   if (keyCode == 18) {
154     return 'Alt';
155   }
156   if (keyCode == 16) {
157     return 'Shift';
158   }
159   if ((keyCode == 91) || (keyCode == 93)) {
160     if (cvox.ChromeVox.isChromeOS) {
161       return 'Search';
162     } else if (cvox.ChromeVox.isMac) {
163       return 'Cmd';
164     } else {
165       return 'Win';
166     }
167   }
168   // TODO(rshearer): This is a hack to work around the special casing of the
169   // sticky mode string that used to happen in keyEventToString. We won't need
170   // it once we move away from strings completely.
171   if (keyCode == 45) {
172     return 'Insert';
173   }
174   if (keyCode >= 65 && keyCode <= 90) {
175     // A - Z
176     return String.fromCharCode(keyCode);
177   } else if (keyCode >= 48 && keyCode <= 57) {
178     // 0 - 9
179     return String.fromCharCode(keyCode);
180   } else {
181     // Anything else
182     return '#' + keyCode;
183   }
187  * Returns the keycode of a string representation of the specified modifier.
189  * @param {string} keyString Modifier key.
190  * @return {number} Key code.
191  */
192 cvox.KeyUtil.modStringToKeyCode = function(keyString) {
193   switch (keyString) {
194   case 'Ctrl':
195     return 17;
196   case 'Alt':
197     return 18;
198   case 'Shift':
199     return 16;
200   case 'Cmd':
201   case 'Win':
202     return 91;
203   }
204   return -1;
208  * Returns the key codes of a string respresentation of the ChromeVox modifiers.
210  * @return {Array<number>} Array of key codes.
211  */
212 cvox.KeyUtil.cvoxModKeyCodes = function() {
213   var modKeyCombo = cvox.ChromeVox.modKeyStr.split(/\+/g);
214   var modKeyCodes = modKeyCombo.map(function(keyString) {
215     return cvox.KeyUtil.modStringToKeyCode(keyString);
216   });
217   return modKeyCodes;
221  * Checks if the specified key code is a key used for switching into a sequence
222  * mode. Sequence switch keys are specified in
223  * cvox.KeyUtil.sequenceSwitchKeyCodes
225  * @param {!cvox.KeySequence} rhKeySeq The key sequence to check.
226  * @return {boolean} true if it is a sequence switch keycode, false otherwise.
227  */
228 cvox.KeyUtil.isSequenceSwitchKeyCode = function(rhKeySeq) {
229   for (var i = 0; i < cvox.ChromeVox.sequenceSwitchKeyCodes.length; i++) {
230     var lhKeySeq = cvox.ChromeVox.sequenceSwitchKeyCodes[i];
231     if (lhKeySeq.equals(rhKeySeq)) {
232       return true;
233     }
234   }
235   return false;
240  * Get readable string description of the specified keycode.
242  * @param {number} keyCode The key code.
243  * @return {string} Returns a string description.
244  */
245 cvox.KeyUtil.getReadableNameForKeyCode = function(keyCode) {
246   if (keyCode == 0) {
247     return 'Power button';
248   } else if (keyCode == 17) {
249     return 'Control';
250   } else if (keyCode == 18) {
251     return 'Alt';
252   } else if (keyCode == 16) {
253     return 'Shift';
254   } else if (keyCode == 9) {
255     return 'Tab';
256   } else if ((keyCode == 91) || (keyCode == 93)) {
257     if (cvox.ChromeVox.isChromeOS) {
258       return 'Search';
259     } else if (cvox.ChromeVox.isMac) {
260       return 'Cmd';
261     } else {
262       return 'Win';
263     }
264   } else if (keyCode == 8) {
265     return 'Backspace';
266   } else if (keyCode == 32) {
267     return 'Space';
268   } else if (keyCode == 35) {
269     return'end';
270   } else if (keyCode == 36) {
271     return 'home';
272   } else if (keyCode == 37) {
273     return 'Left arrow';
274   } else if (keyCode == 38) {
275     return 'Up arrow';
276   } else if (keyCode == 39) {
277     return 'Right arrow';
278   } else if (keyCode == 40) {
279     return 'Down arrow';
280   } else if (keyCode == 45) {
281     return 'Insert';
282   } else if (keyCode == 13) {
283     return 'Enter';
284   } else if (keyCode == 27) {
285     return 'Escape';
286   } else if (keyCode == 112) {
287     return cvox.ChromeVox.isChromeOS ? 'Back' : 'F1';
288   } else if (keyCode == 113) {
289     return cvox.ChromeVox.isChromeOS ? 'Forward' : 'F2';
290   } else if (keyCode == 114) {
291     return cvox.ChromeVox.isChromeOS ? 'Refresh' : 'F3';
292   } else if (keyCode == 115) {
293     return cvox.ChromeVox.isChromeOS ? 'Toggle full screen' : 'F4';
294   } else if (keyCode == 116) {
295     return 'F5';
296   } else if (keyCode == 117) {
297     return 'F6';
298   } else if (keyCode == 118) {
299     return 'F7';
300   } else if (keyCode == 119) {
301     return 'F8';
302   } else if (keyCode == 120) {
303     return 'F9';
304   } else if (keyCode == 121) {
305     return 'F10';
306   } else if (keyCode == 122) {
307     return 'F11';
308   } else if (keyCode == 123) {
309     return 'F12';
310   } else if (keyCode == 186) {
311     return 'Semicolon';
312   } else if (keyCode == 187) {
313     return 'Equal sign';
314   } else if (keyCode == 188) {
315     return 'Comma';
316   } else if (keyCode == 189) {
317     return 'Dash';
318   } else if (keyCode == 190) {
319     return 'Period';
320   } else if (keyCode == 191) {
321     return 'Forward slash';
322   } else if (keyCode == 192) {
323     return 'Grave accent';
324   } else if (keyCode == 219) {
325     return 'Open bracket';
326   } else if (keyCode == 220) {
327     return 'Back slash';
328   } else if (keyCode == 221) {
329     return 'Close bracket';
330   } else if (keyCode == 222) {
331     return 'Single quote';
332   } else if (keyCode == 115) {
333     return 'Toggle full screen';
334   } else if (keyCode >= 48 && keyCode <= 90) {
335     return String.fromCharCode(keyCode);
336   }
340  * Get the platform specific sticky key keycode.
342  * @return {number} The platform specific sticky key keycode.
343  */
344 cvox.KeyUtil.getStickyKeyCode = function() {
345   // TODO (rshearer): This should not be hard-coded here.
346   var stickyKeyCode = 45; // Insert for Linux and Windows
347   if (cvox.ChromeVox.isChromeOS || cvox.ChromeVox.isMac) {
348     stickyKeyCode = 91; // GUI key (Search/Cmd) for ChromeOs and Mac
349   }
350   return stickyKeyCode;
355  * Get readable string description for an internal string representation of a
356  * key or a keyboard shortcut.
358  * @param {string} keyStr The internal string repsentation of a key or
359  *     a keyboard shortcut.
360  * @return {?string} Readable string representation of the input.
361  */
362 cvox.KeyUtil.getReadableNameForStr = function(keyStr) {
363   // TODO (clchen): Refactor this function away since it is no longer used.
364   return null;
369  * Creates a string representation of a KeySequence.
370  * A KeySequence  with a keyCode of 76 ('L') and the control and alt keys down
371  * would return the string 'Ctrl+Alt+L', for example. A key code that doesn't
372  * correspond to a letter or number will typically return a string with a
373  * pound and then its keyCode, like '#39' for Right Arrow. However,
374  * if the opt_readableKeyCode option is specified, the key code will return a
375  * readable string description like 'Right Arrow' instead of '#39'.
377  * The modifiers always come in this order:
379  *   Ctrl
380  *   Alt
381  *   Shift
382  *   Meta
384  * @param {cvox.KeySequence} keySequence The KeySequence object.
385  * @param {boolean=} opt_readableKeyCode Whether or not to return a readable
386  * string description instead of a string with a pound symbol and a keycode.
387  * Default is false.
388  * @param {boolean=} opt_modifiers Restrict printout to only modifiers. Defaults
389  * to false.
390  * @return {string} Readable string representation of the KeySequence object.
391  */
392 cvox.KeyUtil.keySequenceToString = function(
393     keySequence, opt_readableKeyCode, opt_modifiers) {
394   // TODO(rshearer): Move this method and the getReadableNameForKeyCode and the
395   // method to KeySequence after we refactor isModifierActive (when the modifie
396   // key becomes customizable and isn't stored as a string). We can't do it
397   // earlier because isModifierActive uses KeyUtil.getReadableNameForKeyCode,
398   // and I don't want KeySequence to depend on KeyUtil.
399   var str = '';
401   var numKeys = keySequence.length();
403   for (var index = 0; index < numKeys; index++) {
404     if (str != '' && !opt_modifiers) {
405       str += '>';
406     } else if (str != '') {
407       str += '+';
408     }
410     // This iterates through the sequence. Either we're on the first key
411     // pressed or the second
412     var tempStr = '';
413     for (var keyPressed in keySequence.keys) {
414       // This iterates through the actual key, taking into account any
415       // modifiers.
416       if (!keySequence.keys[keyPressed][index]) {
417         continue;
418       }
419       var modifier = '';
420       switch (keyPressed) {
421         case 'ctrlKey':
422         // TODO(rshearer): This is a hack to work around the special casing
423         // of the Ctrl key that used to happen in keyEventToString. We won't
424         // need it once we move away from strings completely.
425         modifier = 'Ctrl';
426         break;
427       case 'searchKeyHeld':
428         var searchKey = cvox.KeyUtil.getReadableNameForKeyCode(91);
429         modifier = searchKey;
430         break;
431       case 'altKey':
432         modifier = 'Alt';
433         break;
434       case 'altGraphKey':
435         modifier = 'AltGraph';
436         break;
437       case 'shiftKey':
438         modifier = 'Shift';
439         break;
440       case 'metaKey':
441         var metaKey = cvox.KeyUtil.getReadableNameForKeyCode(91);
442         modifier = metaKey;
443         break;
444       case 'keyCode':
445         var keyCode = keySequence.keys[keyPressed][index];
446         // We make sure the keyCode isn't for a modifier key. If it is, then
447         // we've already added that into the string above.
448         if (!keySequence.isModifierKey(keyCode) && !opt_modifiers) {
449           if (opt_readableKeyCode) {
450             tempStr += cvox.KeyUtil.getReadableNameForKeyCode(keyCode);
451           } else {
452             tempStr += cvox.KeyUtil.keyCodeToString(keyCode);
453           }
454         }
455       }
456       if (str.indexOf(modifier) == -1) {
457           tempStr += modifier + '+';
458       }
459     }
460     str += tempStr;
462     // Strip trailing +.
463     if (str[str.length - 1] == '+') {
464       str = str.slice(0, -1);
465     }
466   }
468   if (keySequence.cvoxModifier || keySequence.prefixKey) {
469     if (str != '') {
470       str = 'Cvox+' + str;
471     } else {
472       str = 'Cvox';
473     }
474   } else if (keySequence.stickyMode) {
475     if (str[str.length - 1] == '>') {
476       str = str.slice(0, -1);
477     }
478     str = str + '+' + str;
479   }
480   return str;
484  * Looks up if the given key sequence is triggered via double tap.
485  * @param {cvox.KeySequence} key The key.
486  * @return {boolean} True if key is triggered via double tap.
487  */
488 cvox.KeyUtil.isDoubleTapKey = function(key) {
489   var isSet = false;
490   var originalState = key.doubleTap;
491   key.doubleTap = true;
492   for (var i = 0, keySeq; keySeq = cvox.KeySequence.doubleTapCache[i]; i++) {
493     if (keySeq.equals(key)) {
494       isSet = true;
495       break;
496     }
497   }
498   key.doubleTap = originalState;
499   return isSet;