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 |