key_sequence.js revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
1ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com// Copyright 2014 The Chromium Authors. All rights reserved.
2ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com// Use of this source code is governed by a BSD-style license that can be
3ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com// found in the LICENSE file.
4ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com
5ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com/**
6ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * @fileoverview A JavaScript class that represents a sequence of keys entered
7ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * by the user.
8ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com */
9ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com
10ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com
11ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.comgoog.provide('cvox.KeySequence');
12ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com
13e8fe4bc3efa8f18f5651c5d005fba1935a741be0robertphillips@google.comgoog.require('cvox.ChromeVox');
14e8fe4bc3efa8f18f5651c5d005fba1935a741be0robertphillips@google.com
15ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com
16ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com/**
17607357fde8a9c4c70549d4223e0bd1181b012e0echudy@google.com * A class to represent a sequence of keys entered by a user or affiliated with
18ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * a ChromeVox command.
19ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * This class can represent the data from both types of key sequences:
20ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com *
21ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * COMMAND KEYS SPECIFIED IN A KEYMAP:
22ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * - Two discrete keys (at most): [Down arrow], [A, A] or [O, W] etc. Can
23ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com *   specify one or both.
24ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * - Modifiers (like ctrl, alt, meta, etc)
25ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * - Whether or not the ChromeVox modifier key is required with the command.
26a9e937c7b712b024de108fa963f92d0e70e4a296chudy@google.com *
27ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * USER INPUT:
28ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * - Two discrete keys (at most): [Down arrow], [A, A] or [O, W] etc.
29607357fde8a9c4c70549d4223e0bd1181b012e0echudy@google.com * - Modifiers (like ctlr, alt, meta, etc)
30ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * - Whether or not the ChromeVox modifier key was active when the keys were
31ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com *   entered.
32ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * - Whether or not a prefix key was entered before the discrete keys.
33607357fde8a9c4c70549d4223e0bd1181b012e0echudy@google.com * - Whether sticky mode was active.
34ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * @param {Event|Object} originalEvent The original key event entered by a user.
35ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * The originalEvent may or may not have parameters stickyMode and keyPrefix
36ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * specified. We will also accept an event-shaped object.
37a9e937c7b712b024de108fa963f92d0e70e4a296chudy@google.com * @param {boolean=} opt_cvoxModifier Whether or not the ChromeVox modifier key
38a9e937c7b712b024de108fa963f92d0e70e4a296chudy@google.com * is active. If not specified, we will try to determine whether the modifier
39a9e937c7b712b024de108fa963f92d0e70e4a296chudy@google.com * was active by looking at the originalEvent.
40ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * @param {boolean=} opt_skipStripping Skips stripping of ChromeVox modifiers
41ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * from key events when the cvox modifiers are set. Defaults to false.
42ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * @param {boolean=} opt_doubleTap Whether this is triggered via double tap.
43ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com * @constructor
44ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com */
45ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.comcvox.KeySequence = function(
46ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com    originalEvent, opt_cvoxModifier, opt_skipStripping, opt_doubleTap) {
47ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com  /** @type {boolean} */
48ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com  this.doubleTap = !!opt_doubleTap;
49ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com
50ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com  if (opt_cvoxModifier == undefined) {
51607357fde8a9c4c70549d4223e0bd1181b012e0echudy@google.com    this.cvoxModifier = this.isCVoxModifierActive(originalEvent);
5216e3ddea6a80972aced04b21b1d66377fa95e7c7bsalomon@google.com  } else {
53ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com    this.cvoxModifier = opt_cvoxModifier;
54ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com  }
55e8fe4bc3efa8f18f5651c5d005fba1935a741be0robertphillips@google.com  this.stickyMode = !!originalEvent['stickyMode'];
56e8fe4bc3efa8f18f5651c5d005fba1935a741be0robertphillips@google.com  this.prefixKey = !!originalEvent['keyPrefix'];
57ea5488b9655fc7d71345c3a823de85f8b74e3279chudy@google.com  this.skipStripping = !!opt_skipStripping;
58
59  if (this.stickyMode && this.prefixKey) {
60    throw 'Prefix key and sticky mode cannot both be enabled: ' + originalEvent;
61  }
62
63  var event = this.resolveChromeOSSpecialKeys_(originalEvent);
64
65  // TODO (rshearer): We should take the user out of sticky mode if they
66  // try to use the CVox modifier or prefix key.
67
68  /**
69   * Stores the key codes and modifiers for the keys in the key sequence.
70   * TODO(rshearer): Consider making this structure an array of minimal
71   * keyEvent-like objects instead so we don't have to worry about what happens
72   * when ctrlKey.length is different from altKey.length.
73   *
74   * NOTE: If a modifier key is pressed by itself, we will store the keyCode
75   * *and* set the appropriate modKey to be true. This mirrors the way key
76   * events are created on Mac and Windows. For example, if the Meta key was
77   * pressed by itself, the keys object will have:
78   * {metaKey: [true], keyCode:[91]}
79   *
80   * @type {Object}
81   */
82  this.keys = {
83    ctrlKey: [],
84    searchKeyHeld: [],
85    altKey: [],
86    altGraphKey: [],
87    shiftKey: [],
88    metaKey: [],
89    keyCode: []
90  };
91
92  this.extractKey_(event);
93};
94
95
96// TODO(dtseng): This is incomplete; pull once we have appropriate libs.
97/**
98 * Maps a keypress keycode to a keydown or keyup keycode.
99 * @type {Object.<number, number>}
100 */
101cvox.KeySequence.KEY_PRESS_CODE = {
102  39: 222,
103  44: 188,
104  45: 189,
105  46: 190,
106  47: 191,
107  59: 186,
108  91: 219,
109  92: 220,
110  93: 221
111};
112
113/**
114 * A cache of all key sequences that have been set as double-tappable. We need
115 * this cache because repeated key down computations causes ChromeVox to become
116 * less responsive. This list is small so we currently use an array.
117 * @type {!Array.<cvox.KeySequence>}
118 */
119cvox.KeySequence.doubleTapCache = [];
120
121
122/**
123 * Adds an additional key onto the original sequence, for use when the user
124 * is entering two shortcut keys. This happens when the user presses a key,
125 * releases it, and then presses a second key. Those two keys together are
126 * considered part of the sequence.
127 * @param {Event|Object} additionalKeyEvent The additional key to be added to
128 * the original event. Should be an event or an event-shaped object.
129 * @return {boolean} Whether or not we were able to add a key. Returns false
130 * if there are already two keys attached to this event.
131 */
132cvox.KeySequence.prototype.addKeyEvent = function(additionalKeyEvent) {
133  if (this.keys.keyCode.length > 1) {
134    return false;
135  }
136  this.extractKey_(additionalKeyEvent);
137  return true;
138};
139
140
141/**
142 * Check for equality. Commands are matched based on the actual key codes
143 * involved and on whether or not they both require a ChromeVox modifier key.
144 *
145 * If sticky mode or a prefix is active on one of the commands but not on
146 * the other, then we try and match based on key code first.
147 * - If both commands have the same key code and neither of them have the
148 * ChromeVox modifier active then we have a match.
149 * - Next we try and match with the ChromeVox modifier. If both commands have
150 * the same key code, and one of them has the ChromeVox modifier and the other
151 * has sticky mode or an active prefix, then we also have a match.
152 * @param {!cvox.KeySequence} rhs The key sequence to compare against.
153 * @return {boolean} True if equal.
154 */
155cvox.KeySequence.prototype.equals = function(rhs) {
156  // Check to make sure the same keys with the same modifiers were pressed.
157  if (!this.checkKeyEquality_(rhs)) {
158    return false;
159  }
160
161  if (this.doubleTap != rhs.doubleTap) {
162    return false;
163  }
164
165  // So now we know the actual keys are the same.
166  // If they both have the ChromeVox modifier, or they both don't have the
167  // ChromeVox modifier, then they are considered equal.
168  if (this.cvoxModifier === rhs.cvoxModifier) {
169    return true;
170  }
171
172  // So only one of them has the ChromeVox modifier. If the one that doesn't
173  // have the ChromeVox modifier has sticky mode or the prefix key then the
174  // keys are still considered equal.
175  var unmodified = this.cvoxModifier ? rhs : this;
176  return unmodified.stickyMode || unmodified.prefixKey;
177};
178
179
180/**
181 * Utility method that extracts the key code and any modifiers from a given
182 * event and adds them to the object map.
183 * @param {Event|Object} keyEvent The keyEvent or event-shaped object to extract
184 * from.
185 * @private
186 */
187cvox.KeySequence.prototype.extractKey_ = function(keyEvent) {
188  for (var prop in this.keys) {
189    if (prop == 'keyCode') {
190      var keyCode;
191      // TODO (rshearer): This is temporary until we find a library that can
192      // convert between ASCII charcodes and keycodes.
193      if (keyEvent.type == 'keypress' && keyEvent[prop] >= 97 &&
194          keyEvent[prop] <= 122) {
195        // Alphabetic keypress. Convert to the upper case ASCII code.
196        keyCode = keyEvent[prop] - 32;
197      } else if (keyEvent.type == 'keypress') {
198        keyCode = cvox.KeySequence.KEY_PRESS_CODE[keyEvent[prop]];
199      }
200      this.keys[prop].push(keyCode || keyEvent[prop]);
201    } else {
202      if (this.isKeyModifierActive(keyEvent, prop)) {
203        this.keys[prop].push(true);
204      } else {
205        this.keys[prop].push(false);
206      }
207    }
208  }
209  if (this.cvoxModifier) {
210    this.rationalizeKeys_();
211  }
212};
213
214
215/**
216 * Rationalizes the key codes and the ChromeVox modifier for this keySequence.
217 * This means we strip out the key codes and key modifiers stored for this
218 * KeySequence that are also present in the ChromeVox modifier. For example, if
219 * the ChromeVox modifier keys are Ctrl+Alt, and we've determined that the
220 * ChromeVox modifier is active (meaning the user has pressed Ctrl+Alt), we
221 * don't want this.keys.ctrlKey = true also because that implies that this
222 * KeySequence involves the ChromeVox modifier and the ctrl key being held down
223 * together, which doesn't make any sense.
224 * @private
225 */
226cvox.KeySequence.prototype.rationalizeKeys_ = function() {
227  if (this.skipStripping) {
228    return;
229  }
230
231  // TODO (rshearer): This is a hack. When the modifier key becomes customizable
232  // then we will not have to deal with strings here.
233  var modifierKeyCombo = cvox.ChromeVox.modKeyStr.split(/\+/g);
234
235  var index = this.keys.keyCode.length - 1;
236  // For each modifier that is part of the CVox modifier, remove it from keys.
237  if (modifierKeyCombo.indexOf('Ctrl') != -1) {
238    this.keys.ctrlKey[index] = false;
239  }
240  if (modifierKeyCombo.indexOf('Alt') != -1) {
241    this.keys.altKey[index] = false;
242  }
243  if (modifierKeyCombo.indexOf('Shift') != -1) {
244    this.keys.shiftKey[index] = false;
245  }
246  var metaKeyName = this.getMetaKeyName_();
247  if (modifierKeyCombo.indexOf(metaKeyName) != -1) {
248    if (metaKeyName == 'Search') {
249      this.keys.searchKeyHeld[index] = false;
250      // TODO(dmazzoni): http://crbug.com/404763 Get rid of the code that
251      // tracks the search key and just use meta everywhere.
252      this.keys.metaKey[index] = false;
253    } else if (metaKeyName == 'Cmd' || metaKeyName == 'Win') {
254      this.keys.metaKey[index] = false;
255    }
256  }
257};
258
259
260/**
261 * Get the user-facing name for the meta key (keyCode = 91), which varies
262 * depending on the platform.
263 * @return {string} The user-facing string name for the meta key.
264 * @private
265 */
266cvox.KeySequence.prototype.getMetaKeyName_ = function() {
267  if (cvox.ChromeVox.isChromeOS) {
268    return 'Search';
269  } else if (cvox.ChromeVox.isMac) {
270    return 'Cmd';
271  } else {
272    return 'Win';
273  }
274};
275
276
277/**
278 * Utility method that checks for equality of the modifiers (like shift and alt)
279 * and the equality of key codes.
280 * @param {!cvox.KeySequence} rhs The key sequence to compare against.
281 * @return {boolean} True if the modifiers and key codes in the key sequence are
282 * the same.
283 * @private
284 */
285cvox.KeySequence.prototype.checkKeyEquality_ = function(rhs) {
286  for (var i in this.keys) {
287    for (var j = this.keys[i].length; j--;) {
288      if (this.keys[i][j] !== rhs.keys[i][j])
289        return false;
290    }
291  }
292  return true;
293};
294
295
296/**
297 * Gets first key code
298 * @return {number} The first key code.
299 */
300cvox.KeySequence.prototype.getFirstKeyCode = function() {
301  return this.keys.keyCode[0];
302};
303
304
305/**
306 * Gets the number of keys in the sequence. Should be 1 or 2.
307 * @return {number} The number of keys in the sequence.
308 */
309cvox.KeySequence.prototype.length = function() {
310  return this.keys.keyCode.length;
311};
312
313
314
315/**
316 * Checks if the specified key code represents a modifier key, i.e. Ctrl, Alt,
317 * Shift, Search (on ChromeOS) or Meta.
318 *
319 * @param {number} keyCode key code.
320 * @return {boolean} true if it is a modifier keycode, false otherwise.
321 */
322cvox.KeySequence.prototype.isModifierKey = function(keyCode) {
323  // Shift, Ctrl, Alt, Search/LWin
324  return keyCode == 16 || keyCode == 17 || keyCode == 18 || keyCode == 91 ||
325      keyCode == 93;
326};
327
328
329/**
330 * Determines whether the Cvox modifier key is active during the keyEvent.
331 * @param {Event|Object} keyEvent The keyEvent or event-shaped object to check.
332 * @return {boolean} Whether or not the modifier key was active during the
333 * keyEvent.
334 */
335cvox.KeySequence.prototype.isCVoxModifierActive = function(keyEvent) {
336  // TODO (rshearer): Update this when the modifier key becomes customizable
337  var modifierKeyCombo = cvox.ChromeVox.modKeyStr.split(/\+/g);
338
339  // For each modifier that is held down, remove it from the combo.
340  // If the combo string becomes empty, then the user has activated the combo.
341  if (this.isKeyModifierActive(keyEvent, 'ctrlKey')) {
342    modifierKeyCombo = modifierKeyCombo.filter(function(modifier) {
343                              return modifier != 'Ctrl';
344                            });
345  }
346  if (this.isKeyModifierActive(keyEvent, 'altKey')) {
347    modifierKeyCombo = modifierKeyCombo.filter(function(modifier) {
348                                                 return modifier != 'Alt';
349                                               });
350  }
351  if (this.isKeyModifierActive(keyEvent, 'shiftKey')) {
352    modifierKeyCombo = modifierKeyCombo.filter(function(modifier) {
353                                                 return modifier != 'Shift';
354                                               });
355  }
356  if (this.isKeyModifierActive(keyEvent, 'metaKey') ||
357      this.isKeyModifierActive(keyEvent, 'searchKeyHeld')) {
358    var metaKeyName = this.getMetaKeyName_();
359    modifierKeyCombo = modifierKeyCombo.filter(function(modifier) {
360                                                 return modifier != metaKeyName;
361                                               });
362  }
363  return (modifierKeyCombo.length == 0);
364};
365
366
367/**
368 * Determines whether a particular key modifier (for example, ctrl or alt) is
369 * active during the keyEvent.
370 * @param {Event|Object} keyEvent The keyEvent or Event-shaped object to check.
371 * @param {string} modifier The modifier to check.
372 * @return {boolean} Whether or not the modifier key was active during the
373 * keyEvent.
374 */
375cvox.KeySequence.prototype.isKeyModifierActive = function(keyEvent, modifier) {
376  // We need to check the key event modifier and the keyCode because Linux will
377  // not set the keyEvent.modKey property if it is the modKey by itself.
378  // This bug filed as crbug.com/74044
379  switch (modifier) {
380    case 'ctrlKey':
381      return (keyEvent.ctrlKey || keyEvent.keyCode == 17);
382      break;
383    case 'altKey':
384      return (keyEvent.altKey || (keyEvent.keyCode == 18));
385      break;
386    case 'shiftKey':
387      return (keyEvent.shiftKey || (keyEvent.keyCode == 16));
388      break;
389    case 'metaKey':
390      return (keyEvent.metaKey ||
391          (!cvox.ChromeVox.isChromeOS && keyEvent.keyCode == 91));
392      break;
393    case 'searchKeyHeld':
394      return ((cvox.ChromeVox.isChromeOS && keyEvent.keyCode == 91) ||
395          keyEvent['searchKeyHeld']);
396      break;
397  }
398  return false;
399};
400
401/**
402 * Returns if any modifier is active in this sequence.
403 * @return {boolean} The result.
404 */
405cvox.KeySequence.prototype.isAnyModifierActive = function() {
406  for (var modifierType in this.keys) {
407    for (var i = 0; i < this.length(); i++) {
408      if (this.keys[modifierType][i] && modifierType != 'keyCode') {
409        return true;
410      }
411    }
412  }
413  return false;
414};
415
416
417/**
418 * Creates a KeySequence event from a generic object.
419 * @param {Object} sequenceObject The object.
420 * @return {cvox.KeySequence} The created KeySequence object.
421 */
422cvox.KeySequence.deserialize = function(sequenceObject) {
423  var firstSequenceEvent = {};
424
425  firstSequenceEvent['stickyMode'] = (sequenceObject.stickyMode == undefined) ?
426      false : sequenceObject.stickyMode;
427  firstSequenceEvent['prefixKey'] = (sequenceObject.prefixKey == undefined) ?
428      false : sequenceObject.prefixKey;
429
430
431  var secondKeyPressed = sequenceObject.keys.keyCode.length > 1;
432  var secondSequenceEvent = {};
433
434  for (var keyPressed in sequenceObject.keys) {
435    firstSequenceEvent[keyPressed] = sequenceObject.keys[keyPressed][0];
436    if (secondKeyPressed) {
437      secondSequenceEvent[keyPressed] = sequenceObject.keys[keyPressed][1];
438    }
439  }
440
441  var keySeq = new cvox.KeySequence(firstSequenceEvent,
442      sequenceObject.cvoxModifier, true, sequenceObject.doubleTap);
443  if (secondKeyPressed) {
444    cvox.ChromeVox.sequenceSwitchKeyCodes.push(
445        new cvox.KeySequence(firstSequenceEvent, sequenceObject.cvoxModifier));
446    keySeq.addKeyEvent(secondSequenceEvent);
447  }
448
449  if (sequenceObject.doubleTap) {
450    cvox.KeySequence.doubleTapCache.push(keySeq);
451  }
452
453  return keySeq;
454};
455
456
457/**
458 * Creates a KeySequence event from a given string. The string should be in the
459 * standard key sequence format described in keyUtil.keySequenceToString and
460 * used in the key map JSON files.
461 * @param {string} keyStr The string representation of a key sequence.
462 * @return {!cvox.KeySequence} The created KeySequence object.
463 */
464cvox.KeySequence.fromStr = function(keyStr) {
465  var sequenceEvent = {};
466  var secondSequenceEvent = {};
467
468  var secondKeyPressed;
469  if (keyStr.indexOf('>') == -1) {
470    secondKeyPressed = false;
471  } else {
472    secondKeyPressed = true;
473  }
474
475  var cvoxPressed = false;
476  sequenceEvent['stickyMode'] = false;
477  sequenceEvent['prefixKey'] = false;
478
479  var tokens = keyStr.split('+');
480  for (var i = 0; i < tokens.length; i++) {
481    var seqs = tokens[i].split('>');
482    for (var j = 0; j < seqs.length; j++) {
483      if (seqs[j].charAt(0) == '#') {
484        var keyCode = parseInt(seqs[j].substr(1), 10);
485        if (j > 0) {
486          secondSequenceEvent['keyCode'] = keyCode;
487        } else {
488          sequenceEvent['keyCode'] = keyCode;
489        }
490      }
491      var keyName = seqs[j];
492      if (seqs[j].length == 1) {
493        // Key is A/B/C...1/2/3 and we don't need to worry about setting
494        // modifiers.
495        if (j > 0) {
496          secondSequenceEvent['keyCode'] = seqs[j].charCodeAt(0);
497        } else {
498          sequenceEvent['keyCode'] = seqs[j].charCodeAt(0);
499        }
500      } else {
501        // Key is a modifier key
502        if (j > 0) {
503          cvox.KeySequence.setModifiersOnEvent_(keyName, secondSequenceEvent);
504          if (keyName == 'Cvox') {
505            cvoxPressed = true;
506          }
507        } else {
508          cvox.KeySequence.setModifiersOnEvent_(keyName, sequenceEvent);
509          if (keyName == 'Cvox') {
510            cvoxPressed = true;
511          }
512        }
513      }
514    }
515  }
516  var keySeq = new cvox.KeySequence(sequenceEvent, cvoxPressed);
517  if (secondKeyPressed) {
518    keySeq.addKeyEvent(secondSequenceEvent);
519  }
520  return keySeq;
521};
522
523
524/**
525 * Utility method for populating the modifiers on an event object that will be
526 * used to create a KeySequence.
527 * @param {string} keyName A particular modifier key name (such as 'Ctrl').
528 * @param {Object} seqEvent The event to populate.
529 * @private
530 */
531cvox.KeySequence.setModifiersOnEvent_ = function(keyName, seqEvent) {
532  if (keyName == 'Ctrl') {
533    seqEvent['ctrlKey'] = true;
534    seqEvent['keyCode'] = 17;
535  } else if (keyName == 'Alt') {
536    seqEvent['altKey'] = true;
537    seqEvent['keyCode'] = 18;
538  } else if (keyName == 'Shift') {
539    seqEvent['shiftKey'] = true;
540    seqEvent['keyCode'] = 16;
541  } else if (keyName == 'Search') {
542    seqEvent['searchKeyHeld'] = true;
543    seqEvent['keyCode'] = 91;
544  } else if (keyName == 'Cmd') {
545    seqEvent['metaKey'] = true;
546    seqEvent['keyCode'] = 91;
547  } else if (keyName == 'Win') {
548    seqEvent['metaKey'] = true;
549    seqEvent['keyCode'] = 91;
550  } else if (keyName == 'Insert') {
551    seqEvent['keyCode'] = 45;
552  }
553};
554
555
556/**
557 * Used to resolve special ChromeOS keys (see link for more detail).
558 * http://crbug.com/162268
559 * @param {Object} originalEvent The event.
560 * @return {Object} The resolved event.
561 * @private
562 */
563cvox.KeySequence.prototype.resolveChromeOSSpecialKeys_ =
564    function(originalEvent) {
565  if (!this.cvoxModifier || this.stickyMode || this.prefixKey ||
566      !cvox.ChromeVox.isChromeOS) {
567    return originalEvent;
568  }
569  var evt = {};
570  for (var key in originalEvent) {
571    evt[key] = originalEvent[key];
572  }
573  switch (evt['keyCode']) {
574    case 33:  // Page up.
575      evt['keyCode'] = 38;  // Up arrow.
576      break;
577    case 34:  // Page down.
578      evt['keyCode'] = 40;  // Down arrow.
579      break;
580    case 35:  // End.
581      evt['keyCode'] = 39;  // Right arrow.
582      break;
583    case 36:  // Home.
584      evt['keyCode'] = 37;  // Left arrow.
585      break;
586    case 45:  // Insert.
587      evt['keyCode'] = 190;  // Period.
588      break;
589    case 46:  // Delete.
590      evt['keyCode'] = 8;  // Backspace.
591      break;
592    case 112:  // F1.
593      evt['keyCode'] = 49;  // 1.
594      break;
595    case 113:  // F2.
596      evt['keyCode'] = 50;  // 2.
597      break;
598    case 114:  // F3.
599      evt['keyCode'] = 51;  // 3.
600      break;
601    case 115:  // F4.
602      evt['keyCode'] = 52;  // 4.
603      break;
604    case 116:  // F5.
605      evt['keyCode'] = 53;  // 5.
606      break;
607    case 117:  // F6.
608      evt['keyCode'] = 54;  // 6.
609      break;
610    case 118:  // F7.
611      evt['keyCode'] = 55;  // 7.
612      break;
613    case 119:  // F8.
614      evt['keyCode'] = 56;  // 8.
615      break;
616    case 120:  // F9.
617      evt['keyCode'] = 57;  // 9.
618      break;
619    case 121:  // F10.
620      evt['keyCode'] = 48;  // 0.
621      break;
622    case 122:  // F11
623      evt['keyCode'] = 189;  // Hyphen.
624      break;
625    case 123:  // F12
626      evt['keyCode'] = 187;  // Equals.
627      break;
628  }
629  return evt;
630};
631