Index: chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions.js |
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..42ed81c6ecda49ee7baa36469546fd4ec998cbe8 |
--- /dev/null |
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions.js |
@@ -0,0 +1,159 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** |
+ * @fileoverview Implements support for live regions in ChromeVox Next. |
+ */ |
+ |
+goog.provide('LiveRegions'); |
+ |
+goog.require('ChromeVoxState'); |
+ |
+goog.scope(function() { |
+var AutomationNode = chrome.automation.AutomationNode; |
+var TreeChange = chrome.automation.TreeChange; |
+ |
+/** |
+ * ChromeVox2 live region handler. |
+ * @param {!ChromeVoxState} chromeVoxState The ChromeVox state object, |
+ * keeping track of the current mode and current range. |
+ * @constructor |
+ */ |
+LiveRegions = function(chromeVoxState) { |
+ /** |
+ * @type {!ChromeVoxState} |
+ * @private |
+ */ |
+ this.chromeVoxState_ = chromeVoxState; |
+ |
+ /** |
+ * The time the last live region event was output. |
+ * @type {!Date} |
+ * @private |
+ */ |
+ this.lastLiveRegionTime_ = new Date(0); |
+ |
+ /** |
+ * Set of nodes that have been announced as part of a live region since |
+ * |this.lastLiveRegionTime_|, to prevent duplicate announcements. |
+ * @type {!WeakSet<AutomationNode>} |
+ * @private |
+ */ |
+ this.liveRegionNodeSet_ = new WeakSet(); |
+ |
+ chrome.automation.addTreeChangeObserver( |
+ 'liveRegionTreeChanges', this.onTreeChange.bind(this)); |
+}; |
+ |
+/** |
+ * Live region events received in fewer than this many milliseconds will |
+ * queue, otherwise they'll be output with a category flush. |
+ * @type {number} |
+ * @const |
+ */ |
+LiveRegions.LIVE_REGION_QUEUE_TIME_MS = 500; |
+ |
+/** |
+ * Whether live regions from background tabs should be announced or not. |
+ * @type {boolean} |
+ * @private |
+ */ |
+LiveRegions.announceLiveRegionsFromBackgroundTabs_ = true; |
+ |
+LiveRegions.prototype = { |
+ /** |
+ * Called when the automation tree is changed. |
+ * @param {TreeChange} treeChange |
+ */ |
+ onTreeChange: function(treeChange) { |
+ var node = treeChange.target; |
+ if (!node.containerLiveStatus) |
+ return; |
+ |
+ var mode = this.chromeVoxState_.mode; |
+ var currentRange = this.chromeVoxState_.currentRange; |
+ |
+ if (mode === ChromeVoxMode.CLASSIC || !cvox.ChromeVox.isActive) |
+ return; |
+ |
+ if (!currentRange) |
+ return; |
+ |
+ if (!LiveRegions.announceLiveRegionsFromBackgroundTabs_ && |
+ !AutomationUtil.isInSameWebpage(node, currentRange.start.node)) { |
+ return; |
+ } |
+ |
+ var type = treeChange.type; |
+ var relevant = node.containerLiveRelevant; |
+ if (relevant.indexOf('additions') >= 0 && |
+ (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'); |
+ }, |
+ |
+ /** |
+ * Given a node that needs to be spoken as part of a live region |
+ * change and an additional optional format string, output the |
+ * live region description. |
+ * @param {!AutomationNode} node The changed node. |
+ * @param {?string=} opt_prependFormatStr If set, a format string for |
+ * cvox2.Output to prepend to the output. |
+ * @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, range, Output.EventType.NAVIGATE); |
+ |
+ if (!output.hasSpeech && node.liveAtomic) |
+ output.format('$descendants', node); |
+ |
+ output.withSpeechCategory(cvox.TtsCategory.LIVE); |
+ |
+ if (!output.hasSpeech) |
+ 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 = LiveRegions.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(); |
+ }, |
+}; |
+ |
+}); // goog.scope |