Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(45)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js

Issue 637223006: Initial support for Ranges over automation nodes (used to track ChromeVox focus). (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add tests for range. Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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');
11 goog.provide('global'); 11 goog.provide('global');
12 12
13 goog.require('AutomationPredicate'); 13 goog.require('AutomationPredicate');
14 goog.require('AutomationUtil'); 14 goog.require('AutomationUtil');
15 goog.require('cursors.Cursor'); 15 goog.require('cursors.Cursor');
16 goog.require('cvox.TabsApiHandler'); 16 goog.require('cvox.TabsApiHandler');
17 17
18 goog.scope(function() { 18 goog.scope(function() {
19 var AutomationNode = chrome.automation.AutomationNode;
19 var Dir = AutomationUtil.Dir; 20 var Dir = AutomationUtil.Dir;
20 var EventType = chrome.automation.EventType; 21 var EventType = chrome.automation.EventType;
21 var AutomationNode = chrome.automation.AutomationNode;
22 22
23 /** Classic Chrome accessibility API. */ 23 /** Classic Chrome accessibility API. */
24 global.accessibility = 24 global.accessibility =
25 chrome.accessibilityPrivate || chrome.experimental.accessibility; 25 chrome.accessibilityPrivate || chrome.experimental.accessibility;
26 26
27 /** 27 /**
28 * ChromeVox2 background page. 28 * ChromeVox2 background page.
29 * @constructor 29 * @constructor
30 */ 30 */
31 Background = function() { 31 Background = function() {
32 /** 32 /**
33 * A list of site substring patterns to use with ChromeVox next. Keep these 33 * A list of site substring patterns to use with ChromeVox next. Keep these
34 * strings relatively specific. 34 * strings relatively specific.
35 * @type {!Array.<string>} 35 * @type {!Array.<string>}
36 * @private 36 * @private
37 */ 37 */
38 this.whitelist_ = ['http://www.chromevox.com/', 'chromevox_next_test']; 38 this.whitelist_ = ['http://www.chromevox.com/', 'chromevox_next_test'];
39 39
40 /** 40 /**
41 * @type {cvox.TabsApiHandler} 41 * @type {cvox.TabsApiHandler}
42 * @private 42 * @private
43 */ 43 */
44 this.tabsHandler_ = new cvox.TabsApiHandler(cvox.ChromeVox.tts, 44 this.tabsHandler_ = new cvox.TabsApiHandler(cvox.ChromeVox.tts,
45 cvox.ChromeVox.braille, 45 cvox.ChromeVox.braille,
46 cvox.ChromeVox.earcons); 46 cvox.ChromeVox.earcons);
47 47
48 /** 48 /**
49 * @type {chrome.automation.AutomationNode} 49 * @type {cursors.Range}
50 * @private 50 * @private
51 */ 51 */
52 this.currentNode_ = null; 52 this.activeRange_ = null;
dmazzoni 2014/10/24 16:13:30 Since we use "active" to mean whether ChromeVox is
53 53
54 /** 54 /**
55 * Whether ChromeVox Next is active. 55 * Whether ChromeVox Next is active.
56 * @type {boolean} 56 * @type {boolean}
57 * @private 57 * @private
58 */ 58 */
59 this.active_ = false; 59 this.active_ = false;
60 60
61 // Only needed with unmerged ChromeVox classic loaded before. 61 // Only needed with unmerged ChromeVox classic loaded before.
62 global.accessibility.setAccessibilityEnabled(false); 62 global.accessibility.setAccessibilityEnabled(false);
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
122 /** 122 /**
123 * Handles chrome.commands.onCommand. 123 * Handles chrome.commands.onCommand.
124 * @param {string} command 124 * @param {string} command
125 */ 125 */
126 onGotCommand: function(command) { 126 onGotCommand: function(command) {
127 if (command == 'toggleChromeVoxVersion') { 127 if (command == 'toggleChromeVoxVersion') {
128 this.toggleChromeVoxVersion(); 128 this.toggleChromeVoxVersion();
129 return; 129 return;
130 } 130 }
131 131
132 if (!this.active_ || !this.current_) 132 if (!this.active_ || !this.activeRange_)
133 return; 133 return;
134 134
135 var previous = this.current_; 135 var current = this.activeRange_;
136 var current = this.current_;
137
138 var dir = Dir.FORWARD; 136 var dir = Dir.FORWARD;
139 var pred = null; 137 var pred = null;
140 switch (command) { 138 switch (command) {
141 case 'nextHeading': 139 case 'nextHeading':
142 dir = Dir.FORWARD; 140 dir = Dir.FORWARD;
143 pred = AutomationPredicate.heading; 141 pred = AutomationPredicate.heading;
144 break; 142 break;
145 case 'previousHeading': 143 case 'previousHeading':
146 dir = Dir.BACKWARD; 144 dir = Dir.BACKWARD;
147 pred = AutomationPredicate.heading; 145 pred = AutomationPredicate.heading;
148 break; 146 break;
149 case 'nextLine': 147 case 'nextLine':
150 dir = Dir.FORWARD; 148 current = current.move(cursors.Unit.LINE, Dir.FORWARD);
151 pred = AutomationPredicate.inlineTextBox;
152 break; 149 break;
153 case 'previousLine': 150 case 'previousLine':
154 dir = Dir.BACKWARD; 151 current = current.move(cursors.Unit.LINE, Dir.BACKWARD);
155 pred = AutomationPredicate.inlineTextBox;
156 break; 152 break;
157 case 'nextLink': 153 case 'nextLink':
158 dir = Dir.FORWARD; 154 dir = Dir.FORWARD;
159 pred = AutomationPredicate.link; 155 pred = AutomationPredicate.link;
160 break; 156 break;
161 case 'previousLink': 157 case 'previousLink':
162 dir = Dir.BACKWARD; 158 dir = Dir.BACKWARD;
163 pred = AutomationPredicate.link; 159 pred = AutomationPredicate.link;
164 break; 160 break;
165 case 'nextElement': 161 case 'nextElement':
166 current = current.role == chrome.automation.RoleType.inlineTextBox ? 162 current = current.move(cursors.Unit.NODE, Dir.FORWARD);
167 current.parent() : current;
168 current = AutomationUtil.findNextNode(current,
169 Dir.FORWARD,
170 AutomationPredicate.inlineTextBox);
171 current = current ? current.parent() : current;
172 break; 163 break;
173 case 'previousElement': 164 case 'previousElement':
174 current = current.role == chrome.automation.RoleType.inlineTextBox ? 165 current = current.move(cursors.Unit.NODE, Dir.BACKWARD);
175 current.parent() : current;
176 current = AutomationUtil.findNextNode(current,
177 Dir.BACKWARD,
178 AutomationPredicate.inlineTextBox);
179 current = current ? current.parent() : current;
180 break; 166 break;
181 case 'goToBeginning': 167 case 'goToBeginning':
182 current = AutomationUtil.findNodePost(current.root, 168 var node = AutomationUtil.findNodePost(current.getStart().getNode().root,
183 Dir.FORWARD, 169 Dir.FORWARD,
184 AutomationPredicate.inlineTextBox); 170 AutomationPredicate.leaf);
171 if (node)
172 current = cursors.Range.fromNode(node);
185 break; 173 break;
186 case 'goToEnd': 174 case 'goToEnd':
187 current = AutomationUtil.findNodePost(current.root, 175 var node =
176 AutomationUtil.findNodePost(current.getStart().getNode().root,
188 Dir.BACKWARD, 177 Dir.BACKWARD,
189 AutomationPredicate.inlineTextBox); 178 AutomationPredicate.leaf);
179 if (node)
180 current = cursors.Range.fromNode(node);
190 break; 181 break;
191 } 182 }
192 183
193 if (pred) 184 if (pred) {
194 current = AutomationUtil.findNextNode(current, dir, pred); 185 var node = AutomationUtil.findNextNode(
186 current.getBound(dir).getNode(), dir, pred);
187
188 if (node)
189 current = cursors.Range.fromNode(node);
190 }
195 191
196 if (current) { 192 if (current) {
197 current.focus(); 193 current.getStart().getNode().focus();
dmazzoni 2014/10/24 16:13:29 Perhaps this should call a method on current that
198 194
199 this.onFocus({target: current}); 195 this.activeRange_ = current;
196 this.handleOutput(this.activeRange_);
200 } 197 }
201 }, 198 },
202 199
203 /** 200 /**
204 * Provides all feedback once ChromeVox's focus changes. 201 * Provides all feedback once ChromeVox's focus changes.
205 * @param {Object} evt 202 * @param {Object} evt
206 */ 203 */
207 onFocus: function(evt) { 204 onFocus: function(evt) {
208 var node = evt.target; 205 var node = evt.target;
209 if (!node) 206 if (!node)
210 return; 207 return;
211 208
212 this.current_ = node; 209 this.activeRange_ = cursors.Range.fromNode(node);
213 var container = node; 210 this.handleOutput(this.activeRange_);
214 while (container &&
215 (container.role == chrome.automation.RoleType.inlineTextBox ||
216 container.role == chrome.automation.RoleType.staticText))
217 container = container.parent();
218
219 var role = container ? container.role : node.role;
220
221 var output =
222 [node.attributes.name, node.attributes.value, role].join(', ');
223 cvox.ChromeVox.tts.speak(output, cvox.QueueMode.FLUSH);
224 cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(output));
225 chrome.accessibilityPrivate.setFocusRing([evt.target.location]);
226 }, 211 },
227 212
228 /** 213 /**
229 * Provides all feedback once a load complete event fires. 214 * Provides all feedback once a load complete event fires.
230 * @param {Object} evt 215 * @param {Object} evt
231 */ 216 */
232 onLoadComplete: function(evt) { 217 onLoadComplete: function(evt) {
233 if (this.current_) 218 if (this.activeRange_)
234 return; 219 return;
235 220
236 this.current_ = AutomationUtil.findNodePost(evt.target, 221 var node = AutomationUtil.findNodePost(evt.target,
237 Dir.FORWARD, 222 Dir.FORWARD,
238 AutomationPredicate.inlineTextBox); 223 AutomationPredicate.leaf);
239 this.onFocus({target: this.current_}); 224 if (node)
225 this.activeRange_ = cursors.Range.fromNode(node);
226
227 if (this.activeRange_)
228 this.handleOutput(this.activeRange_);
240 }, 229 },
241 230
242 /** 231 /**
243 * @private 232 * @private
244 * @param {string} url 233 * @param {string} url
245 * @return {boolean} Whether the given |url| is whitelisted. 234 * @return {boolean} Whether the given |url| is whitelisted.
246 */ 235 */
247 isWhitelisted_: function(url) { 236 isWhitelisted_: function(url) {
248 return this.whitelist_.some(function(item) { 237 return this.whitelist_.some(function(item) {
249 return url.indexOf(item) != -1; 238 return url.indexOf(item) != -1;
(...skipping 21 matching lines...) Expand all
271 opt_options.next = !this.active_; 260 opt_options.next = !this.active_;
272 opt_options.classic = !opt_options.next; 261 opt_options.classic = !opt_options.next;
273 } 262 }
274 263
275 if (opt_options.next) { 264 if (opt_options.next) {
276 chrome.automation.getTree(this.onGotTree); 265 chrome.automation.getTree(this.onGotTree);
277 this.active_ = true; 266 this.active_ = true;
278 } else { 267 } else {
279 if (this.active_) { 268 if (this.active_) {
280 for (var eventType in this.listeners_) { 269 for (var eventType in this.listeners_) {
281 this.current_.root.removeEventListener( 270 this.activeRange_.getStart().getNode().root.removeEventListener(
282 eventType, this.listeners_[eventType], true); 271 eventType, this.listeners_[eventType], true);
283 } 272 }
284 } 273 }
285 this.active_ = false; 274 this.active_ = false;
286 } 275 }
287 276
288 chrome.tabs.query({active: true}, function(tabs) { 277 chrome.tabs.query({active: true}, function(tabs) {
289 if (opt_options.classic) { 278 if (opt_options.classic) {
290 cvox.ChromeVox.injectChromeVoxIntoTabs(tabs); 279 cvox.ChromeVox.injectChromeVoxIntoTabs(tabs);
291 } else { 280 } else {
292 tabs.forEach(function(tab) { 281 tabs.forEach(function(tab) {
293 this.disableClassicChromeVox_(tab.id); 282 this.disableClassicChromeVox_(tab.id);
294 }.bind(this)); 283 }.bind(this));
295 } 284 }
296 }.bind(this)); 285 }.bind(this));
286 },
287
288 /**
289 * Handles output of a Range.
290 * @param {!cursors.Range} range Current location.
291 */
292 handleOutput: function(range) {
293 // TODO(dtseng): This is just placeholder logic for generating descriptions
294 // pending further design discussion.
295 function getCursorDesc(cursor) {
296 var node = cursor.getNode();
297 var container = node;
298 while (container &&
299 (container.role == chrome.automation.RoleType.inlineTextBox ||
300 container.role == chrome.automation.RoleType.staticText))
301 container = container.parent();
302
303 var role = container ? container.role : node.role;
304 return [node.attributes.name, node.attributes.value, role].join(', ');
305 }
306
307 // Walk the range and collect descriptions.
308 var output = '';
309 var cursor = range.getStart();
310 var nodeLocations = [];
311 while (cursor.getNode() != range.getEnd().getNode()) {
312 output += getCursorDesc(cursor);
313 nodeLocations.push(cursor.getNode().location);
314 cursor = cursor.move(
315 cursors.Unit.NODE, cursors.Movement.DIRECTIONAL, Dir.FORWARD);
316 }
317 output += getCursorDesc(range.getEnd());
318 nodeLocations.push(range.getEnd().getNode().location);
319
320 cvox.ChromeVox.tts.speak(output, cvox.QueueMode.FLUSH);
321 cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(output));
322 chrome.accessibilityPrivate.setFocusRing(nodeLocations);
297 } 323 }
298 }; 324 };
299 325
300 /** @type {Background} */ 326 /** @type {Background} */
301 global.backgroundObj = new Background(); 327 global.backgroundObj = new Background();
302 328
303 }); // goog.scope 329 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698