| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview The entry point for all ChromeVox2 related code for the | 6 * @fileoverview The entry point for all ChromeVox2 related code for the |
| 7 * background page. | 7 * background page. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 goog.provide('Background'); | 10 goog.provide('Background'); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 this.currentRange_ = null; | 54 this.currentRange_ = null; |
| 55 | 55 |
| 56 /** | 56 /** |
| 57 * Which variant of ChromeVox is active. | 57 * Which variant of ChromeVox is active. |
| 58 * @type {ChromeVoxMode} | 58 * @type {ChromeVoxMode} |
| 59 * @private | 59 * @private |
| 60 */ | 60 */ |
| 61 this.mode_ = ChromeVoxMode.CLASSIC; | 61 this.mode_ = ChromeVoxMode.CLASSIC; |
| 62 | 62 |
| 63 /** @type {!ClassicCompatibility} @private */ | 63 /** @type {!ClassicCompatibility} @private */ |
| 64 this.compat_ = new ClassicCompatibility(); | 64 this.compat_ = new ClassicCompatibility(this.mode_ === ChromeVoxMode.COMPAT); |
| 65 | 65 |
| 66 // Manually bind all functions to |this|. | 66 // Manually bind all functions to |this|. |
| 67 for (var func in this) { | 67 for (var func in this) { |
| 68 if (typeof(this[func]) == 'function') | 68 if (typeof(this[func]) == 'function') |
| 69 this[func] = this[func].bind(this); | 69 this[func] = this[func].bind(this); |
| 70 } | 70 } |
| 71 | 71 |
| 72 /** | 72 /** |
| 73 * Maps an automation event to its listener. | 73 * Maps an automation event to its listener. |
| 74 * @type {!Object<EventType, function(Object) : void>} | 74 * @type {!Object<EventType, function(Object) : void>} |
| 75 */ | 75 */ |
| 76 this.listeners_ = { | 76 this.listeners_ = { |
| 77 alert: this.onEventDefault, | 77 alert: this.onEventDefault, |
| 78 focus: this.onEventDefault, | 78 focus: this.onEventDefault, |
| 79 hover: this.onEventDefault, | 79 hover: this.onEventDefault, |
| 80 loadComplete: this.onLoadComplete, | 80 loadComplete: this.onLoadComplete, |
| 81 menuStart: this.onEventDefault, | 81 menuStart: this.onEventDefault, |
| 82 menuEnd: this.onEventDefault, | 82 menuEnd: this.onEventDefault, |
| 83 menuListValueChanged: this.onEventDefault, | 83 menuListValueChanged: this.onEventDefault, |
| 84 textChanged: this.onTextOrTextSelectionChanged, | 84 textChanged: this.onTextOrTextSelectionChanged, |
| 85 textSelectionChanged: this.onTextOrTextSelectionChanged, | 85 textSelectionChanged: this.onTextOrTextSelectionChanged, |
| 86 valueChanged: this.onValueChanged | 86 valueChanged: this.onValueChanged |
| 87 }; | 87 }; |
| 88 | 88 |
| 89 // Register listeners for ... |
| 90 // Desktop. |
| 89 chrome.automation.getDesktop(this.onGotDesktop); | 91 chrome.automation.getDesktop(this.onGotDesktop); |
| 90 }; | 92 }; |
| 91 | 93 |
| 92 Background.prototype = { | 94 Background.prototype = { |
| 93 /** Forces ChromeVox Next to be active for all tabs. */ | 95 /** Forces ChromeVox Next to be active for all tabs. */ |
| 94 forceChromeVoxNextActive: function() { | 96 forceChromeVoxNextActive: function() { |
| 95 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT); | 97 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT); |
| 96 }, | 98 }, |
| 97 | 99 |
| 98 /** | 100 /** |
| (...skipping 19 matching lines...) Expand all Loading... |
| 118 type: chrome.automation.EventType.loadComplete}); | 120 type: chrome.automation.EventType.loadComplete}); |
| 119 } | 121 } |
| 120 } | 122 } |
| 121 }, | 123 }, |
| 122 | 124 |
| 123 /** | 125 /** |
| 124 * Handles chrome.commands.onCommand. | 126 * Handles chrome.commands.onCommand. |
| 125 * @param {string} command | 127 * @param {string} command |
| 126 * @param {boolean=} opt_skipCompat Whether to skip compatibility checks. | 128 * @param {boolean=} opt_skipCompat Whether to skip compatibility checks. |
| 127 */ | 129 */ |
| 128 onGotCommand: function(command, opt_skipCompat) { | 130 onGotCommand: function(command, opt_skipCompat) { |
| 129 if (!this.currentRange_) | 131 if (!this.currentRange_) |
| 130 return; | 132 return; |
| 131 | 133 |
| 132 if (!opt_skipCompat && this.mode_ === ChromeVoxMode.COMPAT) { | 134 if (!opt_skipCompat) { |
| 133 if (this.compat_.onGotCommand(command)) | 135 if (this.compat_.onGotCommand(command)) |
| 134 return; | 136 return; |
| 135 } | 137 } |
| 136 | 138 |
| 139 if (this.mode_ === ChromeVoxMode.CLASSIC) |
| 140 return; |
| 141 |
| 137 var current = this.currentRange_; | 142 var current = this.currentRange_; |
| 138 var dir = Dir.FORWARD; | 143 var dir = Dir.FORWARD; |
| 139 var pred = null; | 144 var pred = null; |
| 140 var predErrorMsg = undefined; | 145 var predErrorMsg = undefined; |
| 141 switch (command) { | 146 switch (command) { |
| 142 case 'nextCharacter': | 147 case 'nextCharacter': |
| 143 current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD); | 148 current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD); |
| 144 break; | 149 break; |
| 145 case 'previousCharacter': | 150 case 'previousCharacter': |
| 146 current = current.move(cursors.Unit.CHARACTER, Dir.BACKWARD); | 151 current = current.move(cursors.Unit.CHARACTER, Dir.BACKWARD); |
| (...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 316 current = cursors.Range.fromNode(node); | 321 current = cursors.Range.fromNode(node); |
| 317 } else { | 322 } else { |
| 318 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg(predErrorMsg), | 323 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg(predErrorMsg), |
| 319 cvox.QueueMode.FLUSH); | 324 cvox.QueueMode.FLUSH); |
| 320 return; | 325 return; |
| 321 } | 326 } |
| 322 } | 327 } |
| 323 | 328 |
| 324 if (current) { | 329 if (current) { |
| 325 // TODO(dtseng): Figure out what it means to focus a range. | 330 // TODO(dtseng): Figure out what it means to focus a range. |
| 326 var actionNode = current.getStart().getNode(); | 331 current.getStart().getNode().focus(); |
| 327 if (actionNode.role == chrome.automation.RoleType.inlineTextBox) | |
| 328 actionNode = actionNode.parent; | |
| 329 actionNode.focus(); | |
| 330 | 332 |
| 331 var prevRange = this.currentRange_; | 333 var prevRange = this.currentRange_; |
| 332 this.currentRange_ = current; | 334 this.currentRange_ = current; |
| 333 | |
| 334 new Output().withSpeechAndBraille( | 335 new Output().withSpeechAndBraille( |
| 335 this.currentRange_, prevRange, Output.EventType.NAVIGATE) | 336 this.currentRange_, prevRange, Output.EventType.NAVIGATE) |
| 336 .go(); | 337 .go(); |
| 337 } | 338 } |
| 338 }, | 339 }, |
| 339 | 340 |
| 340 /** | 341 /** |
| 341 * Provides all feedback once ChromeVox's focus changes. | 342 * Provides all feedback once ChromeVox's focus changes. |
| 342 * @param {Object} evt | 343 * @param {Object} evt |
| 343 */ | 344 */ |
| 344 onEventDefault: function(evt) { | 345 onEventDefault: function(evt) { |
| 345 var node = evt.target; | 346 var node = evt.target; |
| 346 | 347 |
| 347 if (!node) | 348 if (!node) |
| 348 return; | 349 return; |
| 349 | 350 |
| 350 var prevRange = this.currentRange_; | 351 var prevRange = this.currentRange_; |
| 351 | 352 |
| 352 this.currentRange_ = cursors.Range.fromNode(node); | 353 this.currentRange_ = cursors.Range.fromNode(node); |
| 353 | 354 |
| 354 // Check to see if we've crossed roots. Continue if we've crossed roots or | 355 // Check to see if we've crossed roots. Only care about focused roots. |
| 355 // are not within web content. | 356 if (!prevRange || |
| 356 if (node.root.role == 'desktop' || | |
| 357 !prevRange || | |
| 358 (prevRange.getStart().getNode().root != node.root && | 357 (prevRange.getStart().getNode().root != node.root && |
| 359 node.state.focused)) | 358 node.root.focused)) |
| 360 this.setupChromeVoxVariants_(node.root.docUrl || ''); | 359 this.setupChromeVoxVariants_(node.root.docUrl || ''); |
| 361 | 360 |
| 362 // Don't process nodes inside of web content if ChromeVox Next is inactive. | 361 // Don't process nodes inside of web content if ChromeVox Next is inactive. |
| 363 if (node.root.role != chrome.automation.RoleType.desktop && | 362 if (node.root.role != chrome.automation.RoleType.desktop && |
| 364 this.mode_ === ChromeVoxMode.CLASSIC) { | 363 this.mode_ === ChromeVoxMode.CLASSIC) { |
| 365 chrome.accessibilityPrivate.setFocusRing([]); | 364 chrome.accessibilityPrivate.setFocusRing([]); |
| 366 return; | 365 return; |
| 367 } | 366 } |
| 368 | 367 |
| 369 // Don't output if focused node hasn't changed. | |
| 370 if (prevRange && | |
| 371 evt.type == 'focus' && | |
| 372 this.currentRange_.equals(prevRange)) | |
| 373 return; | |
| 374 | |
| 375 new Output().withSpeechAndBraille( | 368 new Output().withSpeechAndBraille( |
| 376 this.currentRange_, prevRange, evt.type) | 369 this.currentRange_, prevRange, evt.type) |
| 377 .go(); | 370 .go(); |
| 378 }, | 371 }, |
| 379 | 372 |
| 380 /** | 373 /** |
| 381 * Provides all feedback once a load complete event fires. | 374 * Provides all feedback once a load complete event fires. |
| 382 * @param {Object} evt | 375 * @param {Object} evt |
| 383 */ | 376 */ |
| 384 onLoadComplete: function(evt) { | 377 onLoadComplete: function(evt) { |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 512 } | 505 } |
| 513 output.withSpeech(range, null, Output.EventType.NAVIGATE); | 506 output.withSpeech(range, null, Output.EventType.NAVIGATE); |
| 514 output.go(); | 507 output.go(); |
| 515 }, | 508 }, |
| 516 | 509 |
| 517 /** | 510 /** |
| 518 * @return {boolean} | 511 * @return {boolean} |
| 519 * @private | 512 * @private |
| 520 */ | 513 */ |
| 521 isWhitelistedForCompat_: function(url) { | 514 isWhitelistedForCompat_: function(url) { |
| 522 return url.indexOf('chrome://md-settings') != -1 || url === ''; | 515 return url.indexOf('chrome://md-settings') != -1; |
| 523 }, | 516 }, |
| 524 | 517 |
| 525 /** | 518 /** |
| 526 * @private | 519 * @private |
| 527 * @param {string} url | 520 * @param {string} url |
| 528 * @return {boolean} Whether the given |url| is whitelisted. | 521 * @return {boolean} Whether the given |url| is whitelisted. |
| 529 */ | 522 */ |
| 530 isWhitelistedForNext_: function(url) { | 523 isWhitelistedForNext_: function(url) { |
| 531 return this.whitelist_.some(function(item) { | 524 return this.whitelist_.some(function(item) { |
| 532 return url.indexOf(item) != -1; | 525 return url.indexOf(item) != -1; |
| 533 }.bind(this)); | 526 }.bind(this)); |
| 534 }, | 527 }, |
| 535 | 528 |
| 536 /** | 529 /** |
| 537 * Setup ChromeVox variants. | 530 * Setup ChromeVox variants. |
| 538 * @param {string} url | 531 * @param {string} url |
| 539 * @private | 532 * @private |
| 540 */ | 533 */ |
| 541 setupChromeVoxVariants_: function(url) { | 534 setupChromeVoxVariants_: function(url) { |
| 535 this.compat_.active = this.isWhitelistedForCompat_(url); |
| 542 var mode = this.mode_; | 536 var mode = this.mode_; |
| 543 if (mode != ChromeVoxMode.FORCE_NEXT) { | 537 if (this.compat_.active) |
| 544 if (this.isWhitelistedForCompat_(url)) | 538 mode = ChromeVoxMode.COMPAT; |
| 545 mode = ChromeVoxMode.COMPAT; | 539 else if (this.isWhitelistedForNext_(url)) |
| 546 else if (this.isWhitelistedForNext_(url)) | 540 mode = ChromeVoxMode.NEXT; |
| 547 mode = ChromeVoxMode.NEXT; | 541 else if (mode != ChromeVoxMode.FORCE_NEXT) |
| 548 else | 542 mode = ChromeVoxMode.CLASSIC; |
| 549 mode = ChromeVoxMode.CLASSIC; | |
| 550 } | |
| 551 | 543 |
| 552 this.setChromeVoxMode(mode); | 544 this.setChromeVoxMode(mode); |
| 553 }, | 545 }, |
| 554 | 546 |
| 555 /** | 547 /** |
| 556 * Disables classic ChromeVox. | 548 * Disables classic ChromeVox. |
| 557 * @param {number} tabId The tab where ChromeVox classic is running in. | 549 * @param {number} tabId The tab where ChromeVox classic is running in. |
| 558 */ | 550 */ |
| 559 disableClassicChromeVox_: function(tabId) { | 551 disableClassicChromeVox_: function(tabId) { |
| 560 chrome.tabs.executeScript( | 552 chrome.tabs.executeScript( |
| (...skipping 17 matching lines...) Expand all Loading... |
| 578 chrome.commands.onCommand.removeListener(this.onGotCommand); | 570 chrome.commands.onCommand.removeListener(this.onGotCommand); |
| 579 } | 571 } |
| 580 | 572 |
| 581 chrome.tabs.query({active: true}, function(tabs) { | 573 chrome.tabs.query({active: true}, function(tabs) { |
| 582 if (mode === ChromeVoxMode.CLASSIC) { | 574 if (mode === ChromeVoxMode.CLASSIC) { |
| 583 // This case should do nothing because Classic gets injected by the | 575 // This case should do nothing because Classic gets injected by the |
| 584 // extension system via our manifest. Once ChromeVox Next is enabled | 576 // extension system via our manifest. Once ChromeVox Next is enabled |
| 585 // for tabs, re-enable. | 577 // for tabs, re-enable. |
| 586 // cvox.ChromeVox.injectChromeVoxIntoTabs(tabs); | 578 // cvox.ChromeVox.injectChromeVoxIntoTabs(tabs); |
| 587 } else { | 579 } else { |
| 588 // When in compat mode, if the focus is within the desktop tree proper, | |
| 589 // then do not disable content scripts. | |
| 590 if (this.currentRange_.getStart().getNode().root.role == 'desktop') | |
| 591 return; | |
| 592 | |
| 593 tabs.forEach(function(tab) { | 580 tabs.forEach(function(tab) { |
| 594 this.disableClassicChromeVox_(tab.id); | 581 this.disableClassicChromeVox_(tab.id); |
| 595 }.bind(this)); | 582 }.bind(this)); |
| 596 } | 583 } |
| 597 }.bind(this)); | 584 }.bind(this)); |
| 598 | 585 |
| 586 this.compat_.active = mode === ChromeVoxMode.COMPAT; |
| 599 this.mode_ = mode; | 587 this.mode_ = mode; |
| 600 } | 588 } |
| 601 }; | 589 }; |
| 602 | 590 |
| 603 /** @type {Background} */ | 591 /** @type {Background} */ |
| 604 global.backgroundObj = new Background(); | 592 global.backgroundObj = new Background(); |
| 605 | 593 |
| 606 }); // goog.scope | 594 }); // goog.scope |
| OLD | NEW |