Chromium Code Reviews| 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 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 68 */ | 68 */ |
| 69 this.currentRange_ = null; | 69 this.currentRange_ = null; |
| 70 | 70 |
| 71 /** | 71 /** |
| 72 * Which variant of ChromeVox is active. | 72 * Which variant of ChromeVox is active. |
| 73 * @type {ChromeVoxMode} | 73 * @type {ChromeVoxMode} |
| 74 * @private | 74 * @private |
| 75 */ | 75 */ |
| 76 this.mode_ = ChromeVoxMode.COMPAT; | 76 this.mode_ = ChromeVoxMode.COMPAT; |
| 77 | 77 |
| 78 /** | |
| 79 * The time the last live region event was output. | |
| 80 * @type {Date} | |
| 81 * @private | |
| 82 */ | |
| 83 this.lastLiveRegionTime_ = new Date(0); | |
| 84 | |
| 85 /** | |
| 86 * Ids of nodes that have been announced as part of a live region since | |
|
Peter Lundblad
2015/11/20 13:42:58
nit: there are no ids stored in the set.
dmazzoni
2015/11/23 20:16:50
Done.
| |
| 87 * |this.lastLiveRegionTime_|, to prevent duplicate announcements. | |
| 88 * @type {WeakSet} | |
|
Peter Lundblad
2015/11/20 13:42:58
{!WeakSet}
Does the compiler support
{!WeakSet<Aut
dmazzoni
2015/11/23 20:16:50
It worked! Thanks.
| |
| 89 * @private | |
| 90 */ | |
| 91 this.liveRegionNodeSet_ = new WeakSet(); | |
| 92 | |
| 78 // Manually bind all functions to |this|. | 93 // Manually bind all functions to |this|. |
| 79 for (var func in this) { | 94 for (var func in this) { |
| 80 if (typeof(this[func]) == 'function') | 95 if (typeof(this[func]) == 'function') |
| 81 this[func] = this[func].bind(this); | 96 this[func] = this[func].bind(this); |
| 82 } | 97 } |
| 83 | 98 |
| 84 /** @type {!cvox.AbstractEarcons} @private */ | 99 /** @type {!cvox.AbstractEarcons} @private */ |
| 85 this.classicEarcons_ = cvox.ChromeVox.earcons || new cvox.ClassicEarcons(); | 100 this.classicEarcons_ = cvox.ChromeVox.earcons || new cvox.ClassicEarcons(); |
| 86 | 101 |
| 87 /** @type {!cvox.AbstractEarcons} @private */ | 102 /** @type {!cvox.AbstractEarcons} @private */ |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 118 }); | 133 }); |
| 119 | 134 |
| 120 cvox.ExtensionBridge.addMessageListener(this.onMessage_); | 135 cvox.ExtensionBridge.addMessageListener(this.onMessage_); |
| 121 | 136 |
| 122 document.addEventListener('keydown', this.onKeyDown.bind(this), true); | 137 document.addEventListener('keydown', this.onKeyDown.bind(this), true); |
| 123 cvox.ChromeVoxKbHandler.commandHandler = this.onGotCommand.bind(this); | 138 cvox.ChromeVoxKbHandler.commandHandler = this.onGotCommand.bind(this); |
| 124 | 139 |
| 125 // Classic keymap. | 140 // Classic keymap. |
| 126 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromDefaults(); | 141 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromDefaults(); |
| 127 | 142 |
| 143 chrome.automation.setTreeChangeObserverMask('liveRegionTreeChanges'); | |
| 128 chrome.automation.addTreeChangeObserver(this.onTreeChange); | 144 chrome.automation.addTreeChangeObserver(this.onTreeChange); |
| 129 }; | 145 }; |
| 130 | 146 |
| 147 /** | |
| 148 * Live region events received in fewer than this many milliseconds will | |
| 149 * queue, otherwise they'll be output with a category flush. | |
| 150 * @type {number} | |
| 151 * @const | |
| 152 */ | |
| 153 Background.LIVE_REGION_QUEUE_TIME_MS = 500; | |
| 154 | |
| 131 Background.prototype = { | 155 Background.prototype = { |
| 132 /** Forces ChromeVox Next to be active for all tabs. */ | 156 /** Forces ChromeVox Next to be active for all tabs. */ |
| 133 forceChromeVoxNextActive: function() { | 157 forceChromeVoxNextActive: function() { |
| 134 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT); | 158 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT); |
| 135 }, | 159 }, |
| 136 | 160 |
| 137 /** @type {ChromeVoxMode} */ | 161 /** @type {ChromeVoxMode} */ |
| 138 get mode() { | 162 get mode() { |
| 139 return this.mode_; | 163 return this.mode_; |
| 140 }, | 164 }, |
| (...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 417 * Handles key down events. | 441 * Handles key down events. |
| 418 * @param {Event} evt The key down event to process. | 442 * @param {Event} evt The key down event to process. |
| 419 * @return {boolean} True if the default action should be performed. | 443 * @return {boolean} True if the default action should be performed. |
| 420 */ | 444 */ |
| 421 onKeyDown: function(evt) { | 445 onKeyDown: function(evt) { |
| 422 if (this.mode_ != ChromeVoxMode.CLASSIC && | 446 if (this.mode_ != ChromeVoxMode.CLASSIC && |
| 423 !cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt)) { | 447 !cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt)) { |
| 424 evt.preventDefault(); | 448 evt.preventDefault(); |
| 425 evt.stopPropagation(); | 449 evt.stopPropagation(); |
| 426 } | 450 } |
| 451 | |
| 452 Output.flushNextSpeechUtterance(); | |
| 427 }, | 453 }, |
| 428 | 454 |
| 429 /** | 455 /** |
| 430 * Open the options page in a new tab. | 456 * Open the options page in a new tab. |
| 431 */ | 457 */ |
| 432 showOptionsPage: function() { | 458 showOptionsPage: function() { |
| 433 var optionsPage = {url: 'chromevox/background/options.html'}; | 459 var optionsPage = {url: 'chromevox/background/options.html'}; |
| 434 chrome.tabs.create(optionsPage); | 460 chrome.tabs.create(optionsPage); |
| 435 }, | 461 }, |
| 436 | 462 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 491 } | 517 } |
| 492 | 518 |
| 493 this.setChromeVoxMode(mode); | 519 this.setChromeVoxMode(mode); |
| 494 }, | 520 }, |
| 495 | 521 |
| 496 /** | 522 /** |
| 497 * Called when the automation tree is changed. | 523 * Called when the automation tree is changed. |
| 498 * @param {chrome.automation.TreeChange} treeChange | 524 * @param {chrome.automation.TreeChange} treeChange |
| 499 */ | 525 */ |
| 500 onTreeChange: function(treeChange) { | 526 onTreeChange: function(treeChange) { |
| 501 if (this.mode_ === ChromeVoxMode.CLASSIC || !cvox.ChromeVox.isActive) | |
| 502 return; | |
| 503 | |
| 504 var node = treeChange.target; | 527 var node = treeChange.target; |
| 505 if (!node.containerLiveStatus) | 528 if (!node.containerLiveStatus) |
| 506 return; | 529 return; |
| 507 | 530 |
| 508 if (node.containerLiveRelevant.indexOf('additions') >= 0 && | 531 var skip = false; |
|
Peter Lundblad
2015/11/20 13:42:58
Get rid of this and return early instead?
dmazzoni
2015/11/23 20:16:50
Done.
| |
| 509 treeChange.type == 'nodeCreated') | 532 |
| 510 this.outputLiveRegionChange_(node, null); | 533 if (this.mode_ === ChromeVoxMode.CLASSIC || !cvox.ChromeVox.isActive) { |
| 511 if (node.containerLiveRelevant.indexOf('text') >= 0 && | 534 skip = true; |
| 512 treeChange.type == 'nodeChanged') | 535 } else if (!this.currentRange_) { |
| 513 this.outputLiveRegionChange_(node, null); | 536 skip = true; |
| 514 if (node.containerLiveRelevant.indexOf('removals') >= 0 && | 537 } else if (!AutomationUtil.isInSameWebpage( |
| 515 treeChange.type == 'nodeRemoved') | 538 node, this.currentRange_.start.node)) { |
| 516 this.outputLiveRegionChange_(node, '@live_regions_removed'); | 539 skip = true; |
| 540 } | |
| 541 | |
| 542 if (!skip) { | |
| 543 var type = treeChange.type; | |
| 544 var relevant = node.containerLiveRelevant; | |
| 545 if (relevant.indexOf('additions') >= 0 && | |
|
David Tseng
2015/11/24 18:46:30
Where do relevant strings come from? This looks pr
dmazzoni
2015/11/30 22:02:47
This is from the ARIA spec, I'm not sure we should
| |
| 546 (type == 'nodeCreated' || type == 'subtreeCreated')) { | |
| 547 this.outputLiveRegionChange_(node, null); | |
| 548 } | |
| 549 if (relevant.indexOf('text') >= 0 && type == 'nodeChanged') | |
| 550 this.outputLiveRegionChange_(node, null); | |
| 551 if (relevant.indexOf('removals') >= 0 && type == 'nodeRemoved') | |
| 552 this.outputLiveRegionChange_(node, '@live_regions_removed'); | |
| 553 } | |
| 517 }, | 554 }, |
| 518 | 555 |
| 519 /** | 556 /** |
| 520 * Given a node that needs to be spoken as part of a live region | 557 * Given a node that needs to be spoken as part of a live region |
| 521 * change and an additional optional format string, output the | 558 * change and an additional optional format string, output the |
| 522 * live region description. | 559 * live region description. |
| 523 * @param {!chrome.automation.AutomationNode} node The changed node. | 560 * @param {!chrome.automation.AutomationNode} node The changed node. |
| 524 * @param {?string} opt_prependFormatStr If set, a format string for | 561 * @param {?string} opt_prependFormatStr If set, a format string for |
| 525 * cvox2.Output to prepend to the output. | 562 * cvox2.Output to prepend to the output. |
| 526 * @private | 563 * @private |
| 527 */ | 564 */ |
| 528 outputLiveRegionChange_: function(node, opt_prependFormatStr) { | 565 outputLiveRegionChange_: function(node, opt_prependFormatStr) { |
| 566 if (node.containerLiveBusy) | |
| 567 return; | |
| 568 | |
| 569 if (node.containerLiveAtomic && !node.liveAtomic) { | |
| 570 if (node.parent) | |
| 571 this.outputLiveRegionChange_(node.parent, opt_prependFormatStr); | |
| 572 return; | |
| 573 } | |
| 574 | |
| 529 var range = cursors.Range.fromNode(node); | 575 var range = cursors.Range.fromNode(node); |
| 530 var output = new Output(); | 576 var output = new Output(); |
| 531 if (opt_prependFormatStr) { | 577 if (opt_prependFormatStr) { |
| 532 output.format(opt_prependFormatStr); | 578 output.format(opt_prependFormatStr); |
| 533 } | 579 } |
| 534 output.withSpeech(range, null, Output.EventType.NAVIGATE); | 580 output.withSpeech(range, range, Output.EventType.NAVIGATE); |
| 581 | |
| 582 if (output.empty && node.liveAtomic) { | |
| 583 output.format('$descendants', node); | |
| 584 } | |
| 585 | |
| 535 output.withSpeechCategory(cvox.TtsCategory.LIVE); | 586 output.withSpeechCategory(cvox.TtsCategory.LIVE); |
| 587 | |
| 588 if (output.empty) | |
| 589 return; | |
| 590 | |
| 591 // Enqueue live region updates that were received at approximately | |
| 592 // the same time, otherwise flush previous live region updates. | |
| 593 var currentTime = new Date(); | |
| 594 var queueTime = Background.LIVE_REGION_QUEUE_TIME_MS; | |
| 595 if (currentTime - this.lastLiveRegionTime_ > queueTime) { | |
| 596 this.liveRegionNodeSet_ = new WeakSet(); | |
| 597 output.withQueueMode(cvox.QueueMode.CATEGORY_FLUSH); | |
| 598 this.lastLiveRegionTime_ = currentTime; | |
| 599 } else { | |
| 600 output.withQueueMode(cvox.QueueMode.QUEUE); | |
| 601 } | |
| 602 | |
| 603 var parent = node; | |
| 604 while (parent) { | |
| 605 if (this.liveRegionNodeSet_.has(parent)) | |
| 606 return; | |
| 607 parent = parent.parent; | |
| 608 } | |
| 609 | |
| 610 this.liveRegionNodeSet_.add(node); | |
| 536 output.go(); | 611 output.go(); |
| 537 }, | 612 }, |
| 538 | 613 |
| 539 /** | 614 /** |
| 540 * Returns true if the url should have Classic running. | 615 * Returns true if the url should have Classic running. |
| 541 * @return {boolean} | 616 * @return {boolean} |
| 542 * @private | 617 * @private |
| 543 */ | 618 */ |
| 544 shouldEnableClassicForUrl_: function(url) { | 619 shouldEnableClassicForUrl_: function(url) { |
| 545 return this.mode_ != ChromeVoxMode.FORCE_NEXT && | 620 return this.mode_ != ChromeVoxMode.FORCE_NEXT && |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 686 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&') | 761 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&') |
| 687 .replace(/\*/g, '.*') | 762 .replace(/\*/g, '.*') |
| 688 .replace(/\?/g, '.'); | 763 .replace(/\?/g, '.'); |
| 689 }).join('|') + ')$'); | 764 }).join('|') + ')$'); |
| 690 }; | 765 }; |
| 691 | 766 |
| 692 /** @type {Background} */ | 767 /** @type {Background} */ |
| 693 global.backgroundObj = new Background(); | 768 global.backgroundObj = new Background(); |
| 694 | 769 |
| 695 }); // goog.scope | 770 }); // goog.scope |
| OLD | NEW |