Upstream version 11.39.266.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / chromeos / chromevox / chromevox / injected / live_regions_deprecated.js
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
3 // found in the LICENSE file.
4
5 /**
6  * @fileoverview Keeps track of live regions on the page and speaks updates
7  * when they change.
8  *
9  */
10
11 goog.provide('cvox.LiveRegionsDeprecated');
12
13 goog.require('cvox.AriaUtil');
14 goog.require('cvox.ChromeVox');
15 goog.require('cvox.DescriptionUtil');
16 goog.require('cvox.DomUtil');
17 goog.require('cvox.NavDescription');
18
19 /**
20  * @constructor
21  */
22 cvox.LiveRegionsDeprecated = function() {
23 };
24
25 /**
26  * An array of all of the elements on the page that are live regions.
27  * @type {Array.<Element>}
28  */
29 cvox.LiveRegionsDeprecated.trackedRegions = [];
30
31 /**
32  * A parallel array to trackedRegions that stores the previous value of
33  * each live region, represented as an array of NavDescriptions.
34  * @type {Array.<Array.<cvox.NavDescription> >}
35  */
36 cvox.LiveRegionsDeprecated.previousRegionValue = [];
37
38 /**
39  * @type {Date}
40  */
41 cvox.LiveRegionsDeprecated.pageLoadTime = null;
42
43 /**
44  * Time in milliseconds after initial page load to ignore live region
45  * updates, to avoid announcing regions as they're initially created.
46  * The exception is alerts, they're announced when a page is loaded.
47  * @type {number}
48  * @const
49  */
50 cvox.LiveRegionsDeprecated.INITIAL_SILENCE_MS = 2000;
51
52 /**
53  * @param {Date} pageLoadTime The time the page was loaded. Live region
54  *     updates within the first INITIAL_SILENCE_MS milliseconds are ignored.
55  * @param {number} queueMode Interrupt or flush.  Polite live region
56  *   changes always queue.
57  * @param {boolean} disableSpeak true if change announcement should be disabled.
58  * @return {boolean} true if any regions announced.
59  */
60 cvox.LiveRegionsDeprecated.init = function(pageLoadTime, queueMode, disableSpeak) {
61   if (queueMode == undefined) {
62     queueMode = cvox.AbstractTts.QUEUE_MODE_FLUSH;
63   }
64
65   cvox.LiveRegionsDeprecated.pageLoadTime = pageLoadTime;
66
67   var anyRegionsAnnounced = false;
68   var regions = cvox.AriaUtil.getLiveRegions(document.body);
69   for (var i = 0; i < regions.length; i++) {
70     if (cvox.LiveRegionsDeprecated.updateLiveRegion(regions[i], queueMode,
71                                           disableSpeak)) {
72       anyRegionsAnnounced = true;
73       queueMode = cvox.AbstractTts.QUEUE_MODE_QUEUE;
74     }
75   }
76
77   return anyRegionsAnnounced;
78 };
79
80 /**
81  * Speak relevant changes to a live region.
82  *
83  * @param {Node} region The live region node that changed.
84  * @param {number} queueMode Interrupt or queue. Polite live region
85  *   changes always queue.
86  * @param {boolean} disableSpeak true if change announcement should be disabled.
87  * @return {boolean} true if the region announced a change.
88  */
89 cvox.LiveRegionsDeprecated.updateLiveRegion = function(region, queueMode, disableSpeak) {
90   if (cvox.AriaUtil.getAriaBusy(region)) {
91     return false;
92   }
93
94   // Make sure it's visible.
95   if (!cvox.DomUtil.isVisible(region)) {
96     return false;
97   }
98
99   // Retrieve the previous value of this region if we've tracked it
100   // before, otherwise start tracking it.
101   var regionIndex = cvox.LiveRegionsDeprecated.trackedRegions.indexOf(region);
102   var previousValue;
103   if (regionIndex >= 0) {
104     previousValue = cvox.LiveRegionsDeprecated.previousRegionValue[regionIndex];
105   } else {
106     regionIndex = cvox.LiveRegionsDeprecated.trackedRegions.length;
107     previousValue = [];
108     cvox.LiveRegionsDeprecated.trackedRegions.push(region);
109     cvox.LiveRegionsDeprecated.previousRegionValue.push([]);
110   }
111
112   // Get the new value.
113   var currentValue = cvox.LiveRegionsDeprecated.buildCurrentLiveRegionValue(region);
114
115   // If the page just loaded and this is any region type other than 'alert',
116   // keep track of the new value but don't announce anything. Alerts are
117   // the exception, they're announced on page load.
118   var deltaTime = new Date() - cvox.LiveRegionsDeprecated.pageLoadTime;
119   if (cvox.AriaUtil.getRoleAttribute(region) != 'alert' &&
120       deltaTime < cvox.LiveRegionsDeprecated.INITIAL_SILENCE_MS) {
121     cvox.LiveRegionsDeprecated.previousRegionValue[regionIndex] = currentValue;
122     return false;
123   }
124
125   // Don't announce alerts on page load if their text and values consist of
126   // just whitespace.
127   if (cvox.AriaUtil.getRoleAttribute(region) == 'alert' &&
128       deltaTime < cvox.LiveRegionsDeprecated.INITIAL_SILENCE_MS) {
129     var regionText = '';
130     for (var i = 0; i < currentValue.length; i++) {
131       regionText += currentValue[i].text;
132       regionText += currentValue[i].userValue;
133     }
134     if (cvox.DomUtil.collapseWhitespace(regionText) == '') {
135       cvox.LiveRegionsDeprecated.previousRegionValue[regionIndex] = currentValue;
136       return false;
137     }
138   }
139
140   // Create maps of values in the live region for fast hash lookup.
141   var previousValueMap = {};
142   for (var i = 0; i < previousValue.length; i++) {
143     previousValueMap[previousValue[i].toString()] = true;
144   }
145   var currentValueMap = {};
146   for (i = 0; i < currentValue.length; i++) {
147     currentValueMap[currentValue[i].toString()] = true;
148   }
149
150   // Figure out the additions and removals.
151   var additions = [];
152   if (cvox.AriaUtil.getAriaRelevant(region, 'additions')) {
153     for (i = 0; i < currentValue.length; i++) {
154       if (!previousValueMap[currentValue[i].toString()]) {
155         additions.push(currentValue[i]);
156       }
157     }
158   }
159   var removals = [];
160   if (cvox.AriaUtil.getAriaRelevant(region, 'removals')) {
161     for (i = 0; i < previousValue.length; i++) {
162       if (!currentValueMap[previousValue[i].toString()]) {
163         removals.push(previousValue[i]);
164       }
165     }
166   }
167
168   // Only speak removals if they're the only change. Otherwise, when one or
169   // more removals and additions happen concurrently, treat it as a change
170   // and just speak any additions (which includes changed nodes).
171   var messages = [];
172   if (additions.length == 0 && removals.length > 0) {
173     messages = [new cvox.NavDescription({
174       context: cvox.ChromeVox.msgs.getMsg('live_regions_removed'), text: ''
175     })].concat(removals);
176   } else {
177     messages = additions;
178   }
179
180   // Store the new value of the live region.
181   cvox.LiveRegionsDeprecated.previousRegionValue[regionIndex] = currentValue;
182
183   // Return if speak is disabled or there's nothing to announce.
184   if (disableSpeak || messages.length == 0) {
185     return false;
186   }
187
188   // Announce the changes with the appropriate politeness level.
189   var live = cvox.AriaUtil.getAriaLive(region);
190   if (live == 'polite') {
191     queueMode = cvox.AbstractTts.QUEUE_MODE_QUEUE;
192   }
193   for (i = 0; i < messages.length; i++) {
194     messages[i].speak(queueMode);
195     queueMode = cvox.AbstractTts.QUEUE_MODE_QUEUE;
196   }
197
198   return true;
199 };
200
201 /**
202  * Recursively build up the value of a live region and return it as
203  * an array of NavDescriptions. Each atomic portion of the region gets a
204  * single string, otherwise each leaf node gets its own string. When a region
205  * changes, the sets of strings before and after are searched to determine
206  * which have changed.
207  *
208  * @param {Node} node The root node.
209  * @return {Array.<cvox.NavDescription>} An array of NavDescriptions
210  *     describing atomic nodes or leaf nodes in the subtree rooted
211  *     at this node.
212  */
213 cvox.LiveRegionsDeprecated.buildCurrentLiveRegionValue = function(node) {
214   if (cvox.AriaUtil.getAriaAtomic(node) ||
215       cvox.DomUtil.isLeafNode(node)) {
216     var description = cvox.DescriptionUtil.getDescriptionFromAncestors(
217         [node], true, cvox.ChromeVox.verbosity);
218     if (!description.isEmpty()) {
219       return [description];
220     } else {
221       return [];
222     }
223   }
224
225   var result = [];
226
227   // Start with the description of this node.
228   var description = cvox.DescriptionUtil.getDescriptionFromAncestors(
229       [node], false, cvox.ChromeVox.verbosity);
230   if (!description.isEmpty()) {
231     result.push(description);
232   }
233
234   // Recursively add descriptions of child nodes.
235   for (var i = 0; i < node.childNodes.length; i++) {
236     var child = node.childNodes[i];
237     if (cvox.DomUtil.isVisible(child, {checkAncestors: false}) &&
238         !cvox.AriaUtil.isHidden(child)) {
239       var recursiveArray = cvox.LiveRegionsDeprecated.buildCurrentLiveRegionValue(child);
240       result = result.concat(recursiveArray);
241     }
242   }
243   return result;
244 };