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