Index: chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js |
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js |
index f38f7071b783b22e7fb106e627359b517d0ec1ec..8d9bf04ccbf8871f375aeb163d0f3ebbf3d6a1ca 100644 |
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js |
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js |
@@ -75,6 +75,21 @@ Background = function() { |
*/ |
this.mode_ = ChromeVoxMode.COMPAT; |
+ /** |
+ * The time the last live region event was output. |
+ * @type {Date} |
+ * @private |
+ */ |
+ this.lastLiveRegionTime_ = new Date(0); |
+ |
+ /** |
+ * 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.
|
+ * |this.lastLiveRegionTime_|, to prevent duplicate announcements. |
+ * @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.
|
+ * @private |
+ */ |
+ this.liveRegionNodeSet_ = new WeakSet(); |
+ |
// Manually bind all functions to |this|. |
for (var func in this) { |
if (typeof(this[func]) == 'function') |
@@ -125,9 +140,18 @@ Background = function() { |
// Classic keymap. |
cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromDefaults(); |
+ chrome.automation.setTreeChangeObserverMask('liveRegionTreeChanges'); |
chrome.automation.addTreeChangeObserver(this.onTreeChange); |
}; |
+/** |
+ * Live region events received in fewer than this many milliseconds will |
+ * queue, otherwise they'll be output with a category flush. |
+ * @type {number} |
+ * @const |
+ */ |
+Background.LIVE_REGION_QUEUE_TIME_MS = 500; |
+ |
Background.prototype = { |
/** Forces ChromeVox Next to be active for all tabs. */ |
forceChromeVoxNextActive: function() { |
@@ -424,6 +448,8 @@ Background.prototype = { |
evt.preventDefault(); |
evt.stopPropagation(); |
} |
+ |
+ Output.flushNextSpeechUtterance(); |
}, |
/** |
@@ -498,22 +524,33 @@ Background.prototype = { |
* @param {chrome.automation.TreeChange} treeChange |
*/ |
onTreeChange: function(treeChange) { |
- if (this.mode_ === ChromeVoxMode.CLASSIC || !cvox.ChromeVox.isActive) |
- return; |
- |
var node = treeChange.target; |
if (!node.containerLiveStatus) |
return; |
- if (node.containerLiveRelevant.indexOf('additions') >= 0 && |
- treeChange.type == 'nodeCreated') |
- this.outputLiveRegionChange_(node, null); |
- if (node.containerLiveRelevant.indexOf('text') >= 0 && |
- treeChange.type == 'nodeChanged') |
- this.outputLiveRegionChange_(node, null); |
- if (node.containerLiveRelevant.indexOf('removals') >= 0 && |
- treeChange.type == 'nodeRemoved') |
- this.outputLiveRegionChange_(node, '@live_regions_removed'); |
+ 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.
|
+ |
+ if (this.mode_ === ChromeVoxMode.CLASSIC || !cvox.ChromeVox.isActive) { |
+ skip = true; |
+ } else if (!this.currentRange_) { |
+ skip = true; |
+ } else if (!AutomationUtil.isInSameWebpage( |
+ node, this.currentRange_.start.node)) { |
+ skip = true; |
+ } |
+ |
+ if (!skip) { |
+ var type = treeChange.type; |
+ var relevant = node.containerLiveRelevant; |
+ 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
|
+ (type == 'nodeCreated' || type == 'subtreeCreated')) { |
+ this.outputLiveRegionChange_(node, null); |
+ } |
+ if (relevant.indexOf('text') >= 0 && type == 'nodeChanged') |
+ this.outputLiveRegionChange_(node, null); |
+ if (relevant.indexOf('removals') >= 0 && type == 'nodeRemoved') |
+ this.outputLiveRegionChange_(node, '@live_regions_removed'); |
+ } |
}, |
/** |
@@ -526,13 +563,51 @@ Background.prototype = { |
* @private |
*/ |
outputLiveRegionChange_: function(node, opt_prependFormatStr) { |
+ if (node.containerLiveBusy) |
+ return; |
+ |
+ if (node.containerLiveAtomic && !node.liveAtomic) { |
+ if (node.parent) |
+ this.outputLiveRegionChange_(node.parent, opt_prependFormatStr); |
+ return; |
+ } |
+ |
var range = cursors.Range.fromNode(node); |
var output = new Output(); |
if (opt_prependFormatStr) { |
output.format(opt_prependFormatStr); |
} |
- output.withSpeech(range, null, Output.EventType.NAVIGATE); |
+ output.withSpeech(range, range, Output.EventType.NAVIGATE); |
+ |
+ if (output.empty && node.liveAtomic) { |
+ output.format('$descendants', node); |
+ } |
+ |
output.withSpeechCategory(cvox.TtsCategory.LIVE); |
+ |
+ if (output.empty) |
+ return; |
+ |
+ // Enqueue live region updates that were received at approximately |
+ // the same time, otherwise flush previous live region updates. |
+ var currentTime = new Date(); |
+ var queueTime = Background.LIVE_REGION_QUEUE_TIME_MS; |
+ if (currentTime - this.lastLiveRegionTime_ > queueTime) { |
+ this.liveRegionNodeSet_ = new WeakSet(); |
+ output.withQueueMode(cvox.QueueMode.CATEGORY_FLUSH); |
+ this.lastLiveRegionTime_ = currentTime; |
+ } else { |
+ output.withQueueMode(cvox.QueueMode.QUEUE); |
+ } |
+ |
+ var parent = node; |
+ while (parent) { |
+ if (this.liveRegionNodeSet_.has(parent)) |
+ return; |
+ parent = parent.parent; |
+ } |
+ |
+ this.liveRegionNodeSet_.add(node); |
output.go(); |
}, |