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