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

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.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, 1 month 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 Classes related to cursors that point to and select parts of 6 * @fileoverview Classes related to cursors that point to and select parts of
7 * the automation tree. 7 * the automation tree.
8 */ 8 */
9 9
10 goog.provide('cursors.Cursor'); 10 goog.provide('cursors.Cursor');
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
59 /** 59 /**
60 * Represents a position within the automation tree. 60 * Represents a position within the automation tree.
61 * @constructor 61 * @constructor
62 * @param {!AutomationNode} node 62 * @param {!AutomationNode} node
63 * @param {number} index A 0-based index into either this cursor's name or value 63 * @param {number} index A 0-based index into either this cursor's name or value
64 * attribute. Relies on the fact that a node has either a name or a value but 64 * attribute. Relies on the fact that a node has either a name or a value but
65 * not both. An index of |cursors.NODE_INDEX| means the node as a whole is 65 * not both. An index of |cursors.NODE_INDEX| means the node as a whole is
66 * pointed to and covers the case where the accessible text is empty. 66 * pointed to and covers the case where the accessible text is empty.
67 */ 67 */
68 cursors.Cursor = function(node, index) { 68 cursors.Cursor = function(node, index) {
69 /** @type {!AutomationNode} */ 69 /** @type {!AutomationNode} @private */
70 this.node = node; 70 this.node_ = node;
71 /** @type {number} */ 71 /** @type {number} @private */
72 this.index = index; 72 this.index_ = index;
73 };
74
75 /**
76 * Convenience method to construct a Cursor from a node.
77 * @param {!AutomationNode} node
78 * @return {!cursors.Cursor}
79 */
80 cursors.Cursor.fromNode = function(node) {
81 return new cursors.Cursor(node, cursors.NODE_INDEX);
73 }; 82 };
74 83
75 cursors.Cursor.prototype = { 84 cursors.Cursor.prototype = {
76 /** 85 /**
77 * Returns a copy of this cursor. 86 * Returns true if |rhs| is equal to this cursor.
78 * @return {cursors.Cursor} 87 * @param {!cursors.Cursor} rhs
88 * @return {boolean}
79 */ 89 */
80 clone: function() { 90 equals: function(rhs) {
81 return new cursors.Cursor(this.node, this.index); 91 return this.node_ === rhs.getNode() &&
92 this.index_ === rhs.getIndex();
82 }, 93 },
83 94
84 /** 95 /**
96 * @return {!AutomationNode}
97 */
98 getNode: function() {
99 return this.node_;
100 },
101
102 /**
103 * @return {number}
104 */
105 getIndex: function() {
106 return this.index_;
107 },
108
109 /**
85 * Gets the accessible text of the node associated with this cursor. 110 * Gets the accessible text of the node associated with this cursor.
86 * 111 *
87 * Note that only one of |name| or |value| attribute is ever nonempty on an 112 * Note that only one of |name| or |value| attribute is ever nonempty on an
88 * automation node. If either contains whitespace, we still treat it as we do 113 * automation node. If either contains whitespace, we still treat it as we do
89 * for a nonempty string. 114 * for a nonempty string.
90 * @param {!AutomationNode=} opt_node Use this node rather than this cursor's 115 * @param {!AutomationNode=} opt_node Use this node rather than this cursor's
91 * node. 116 * node.
92 * @return {string} 117 * @return {string}
93 */ 118 */
94 getText: function(opt_node) { 119 getText: function(opt_node) {
95 var node = opt_node || this.node; 120 var node = opt_node || this.node_;
96 return node.attributes.name || node.attributes.value || ''; 121 return node.attributes.name || node.attributes.value || '';
97 }, 122 },
98 123
99 /** 124 /**
100 * Moves this cursor by the unit in the given direction using the given 125 * Makes a Cursor which has been moved from this cursor by the unit in the
101 * movement type. 126 * given direction using the given movement type.
102 * @param {Unit} unit 127 * @param {Unit} unit
103 * @param {Movement} movement 128 * @param {Movement} movement
104 * @param {Dir} dir 129 * @param {Dir} dir
130 * @return {!cursors.Cursor} The moved cursor.
105 */ 131 */
106 move: function(unit, movement, dir) { 132 move: function(unit, movement, dir) {
133 var newNode, newIndex;
107 switch (unit) { 134 switch (unit) {
108 case Unit.CHARACTER: 135 case Unit.CHARACTER:
109 // BOUND and DIRECTIONAL are the same for characters. 136 // BOUND and DIRECTIONAL are the same for characters.
110 var node = this.node; 137 var node = this.node_;
111 var nextIndex = dir == Dir.FORWARD ? this.index + 1 : this.index - 1; 138 var nextIndex = dir == Dir.FORWARD ? this.index_ + 1 : this.index_ - 1;
112 if (nextIndex < 0 || nextIndex >= this.getText().length) { 139 if (nextIndex < 0 || nextIndex >= this.getText().length) {
113 node = AutomationUtil.findNextNode( 140 node = AutomationUtil.findNextNode(
114 node, dir, AutomationPredicate.leaf); 141 node, dir, AutomationPredicate.leaf);
115 if (node) { 142 if (node) {
116 nextIndex = 143 nextIndex =
117 dir == Dir.FORWARD ? 0 : this.getText(node).length - 1; 144 dir == Dir.FORWARD ? 0 : this.getText(node).length - 1;
118 } else { 145 } else {
119 node = this.node; 146 node = this.node_;
120 nextIndex = this.index; 147 nextIndex = this.index_;
121 } 148 }
122 } 149 }
123 this.node = node; 150 newNode = node;
124 this.index = nextIndex; 151 newIndex = nextIndex;
125 break; 152 break;
126 case Unit.WORD: 153 case Unit.WORD:
127 switch (movement) { 154 switch (movement) {
128 case Movement.BOUND: 155 case Movement.BOUND:
129 if (this.node.role == Role.inlineTextBox) { 156 if (this.node_.role == Role.inlineTextBox) {
130 var start, end; 157 var start, end;
131 for (var i = 0; i < this.node.attributes.wordStarts.length; i++) { 158 for (
132 if (this.index >= this.node.attributes.wordStarts[i] && 159 var i = 0; i < this.node_.attributes.wordStarts.length; i++) {
dmazzoni 2014/10/24 16:13:30 funny indentation?
133 this.index <= this.node.attributes.wordEnds[i]) { 160 if (this.index_ >= this.node_.attributes.wordStarts[i] &&
134 start = this.node.attributes.wordStarts[i]; 161 this.index_ <= this.node_.attributes.wordEnds[i]) {
135 end = this.node.attributes.wordEnds[i]; 162 start = this.node_.attributes.wordStarts[i];
163 end = this.node_.attributes.wordEnds[i];
136 break; 164 break;
137 } 165 }
138 } 166 }
139 if (goog.isDef(start) && goog.isDef(end)) 167 if (goog.isDef(start) && goog.isDef(end))
140 this.index = dir == Dir.FORWARD ? end : start; 168 newIndex = dir == Dir.FORWARD ? end : start;
141 } else { 169 } else {
142 // TODO(dtseng): Figure out what to do in this case. 170 // TODO(dtseng): Figure out what to do in this case.
143 } 171 }
144 break; 172 break;
145 case Movement.DIRECTIONAL: 173 case Movement.DIRECTIONAL:
146 if (this.node.role == Role.inlineTextBox) { 174 if (this.node_.role == Role.inlineTextBox) {
147 var start, end; 175 var start, end;
148 for (var i = 0; i < this.node.attributes.wordStarts.length; i++) { 176 for (
149 if (this.index >= this.node.attributes.wordStarts[i] && 177 var i = 0; i < this.node_.attributes.wordStarts.length; i++) {
150 this.index <= this.node.attributes.wordEnds[i]) { 178 if (this.index_ >= this.node_.attributes.wordStarts[i] &&
179 this.index_ <= this.node_.attributes.wordEnds[i]) {
151 var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1; 180 var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1;
152 start = this.node.attributes.wordStarts[nextIndex]; 181 start = this.node_.attributes.wordStarts[nextIndex];
153 end = this.node.attributes.wordEnds[nextIndex]; 182 end = this.node_.attributes.wordEnds[nextIndex];
154 break; 183 break;
155 } 184 }
156 } 185 }
157 if (goog.isDef(start)) { 186 if (goog.isDef(start)) {
158 this.index = start; 187 newIndex = start;
159 } else { 188 } else {
160 var node = AutomationUtil.findNextNode(this.node, dir, 189 // The backward case is special at the beginning of nodes.
161 AutomationPredicate.leaf); 190 if (dir == Dir.BACKWARD && this.index_ != 0) {
162 if (node) { 191 this.index_ = 0;
163 this.node = node; 192 } else {
164 this.index = 0; 193 var node = AutomationUtil.findNextNode(this.node_, dir,
165 if (dir == Dir.BACKWARD && node.role == Role.inlineTextBox) { 194 AutomationPredicate.leaf);
166 var starts = node.attributes.wordStarts; 195 if (node) {
167 this.index = starts[starts.length - 1] || 0; 196 newNode = node;
168 } else { 197 newIndex = 0;
169 // TODO(dtseng): Figure out what to do for general nodes. 198 if (dir == Dir.BACKWARD &&
199 node.role == Role.inlineTextBox) {
200 var starts = node.attributes.wordStarts;
201 newIndex = starts[starts.length - 1] || 0;
202 } else {
203 // TODO(dtseng): Figure out what to do for general nodes.
204 }
170 } 205 }
171 } 206 }
172 } 207 }
173 } else { 208 } else {
174 // TODO(dtseng): Figure out what to do in this case. 209 // TODO(dtseng): Figure out what to do in this case.
175 } 210 }
176 } 211 }
177 break; 212 break;
178 case Unit.NODE: 213 case Unit.NODE:
179 switch (movement) { 214 switch (movement) {
180 case Movement.BOUND: 215 case Movement.BOUND:
181 this.index = dir == Dir.FORWARD ? this.getText().length - 1 : 0; 216 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0;
182 break; 217 break;
183 case Movement.DIRECTIONAL: 218 case Movement.DIRECTIONAL:
184 this.node = AutomationUtil.findNextNode( 219 newNode = AutomationUtil.findNextNode(
185 this.node, dir, AutomationPredicate.leaf) || this.node; 220 this.node_, dir, AutomationPredicate.leaf) || this.node_;
186 this.index = cursors.NODE_INDEX; 221 newIndex = cursors.NODE_INDEX;
187 break; 222 break;
188 } 223 }
189 break; 224 break;
190 case Unit.LINE: 225 case Unit.LINE:
191 this.index = 0; 226 newIndex = 0;
192 switch (movement) { 227 switch (movement) {
193 case Movement.BOUND: 228 case Movement.BOUND:
194 var node = this.node; 229 newNode = AutomationUtil.findNodeUntil(this.node_, dir,
195 node = AutomationUtil.findNodeUntil(node, dir,
196 AutomationPredicate.linebreak, {before: true}); 230 AutomationPredicate.linebreak, {before: true});
197 this.node = node || this.node; 231 newNode = newNode || this.node_;
232 newIndex =
233 dir == Dir.FORWARD ? this.getText(newNode).length - 1 : 0;
198 break; 234 break;
199 case Movement.DIRECTIONAL: 235 case Movement.DIRECTIONAL:
200 var node = this.node; 236 newNode = AutomationUtil.findNodeUntil(
201 node = AutomationUtil.findNodeUntil( 237 this.node_, dir, AutomationPredicate.linebreak);
202 node, dir, AutomationPredicate.linebreak);
203 238
204 // We stick to the beginning of lines out of convention. 239 // We stick to the beginning of lines out of convention.
205 if (node && dir == Dir.BACKWARD) { 240 if (newNode && dir == Dir.BACKWARD) {
206 node = AutomationUtil.findNodeUntil(node, dir, 241 newNode = AutomationUtil.findNodeUntil(newNode, dir,
207 AutomationPredicate.linebreak, {before: true}) || node; 242 AutomationPredicate.linebreak, {before: true}) || node;
208 } 243 }
209 this.node = node || this.node;
210 break; 244 break;
211 } 245 }
212 break; 246 break;
247 default:
248 throw 'Unrecognized unit: ' + unit;
213 } 249 }
250 newNode = newNode || this.node_;
251 newIndex = goog.isDef(newIndex) ? newIndex : this.index_;
252 return new cursors.Cursor(newNode, newIndex);
214 } 253 }
215 }; 254 };
216 255
256 /**
257 * Represents a range in the automation tree. There is no visible selection on
258 * the page caused by usage of this object.
259 * @param {!cursors.Cursor} start
260 * @param {!cursors.Cursor} end
261 * @constructor
262 */
263 cursors.Range = function(start, end) {
264 /** @type {!cursors.Cursor} @private */
265 this.start_ = start;
266 /** @type {!cursors.Cursor} @private */
267 this.end_ = end;
268 };
269
270 /**
271 * Convenience method to construct a Range surrounding one node.
272 * @param {!AutomationNode} node
273 * @return {!cursors.Range}
274 */
275 cursors.Range.fromNode = function(node) {
276 return new cursors.Range(cursors.Cursor.fromNode(node),
277 cursors.Cursor.fromNode(node));
278 };
279
280 cursors.Range.prototype = {
281 /**
282 * Returns true if |rhs| is equal to this range.
283 * @param {!cursors.Range} rhs
284 * @return {boolean}
285 */
286 equals: function(rhs) {
287 return this.start_.equals(rhs.getStart()) &&
288 this.end_.equals(rhs.getEnd());
289 },
290
291 /**
292 * Gets a cursor bounding this range.
293 * @param {Dir} dir Which endpoint cursor to return; Dir.FORWARD for end,
294 * Dir.BACKWARD for start.
295 * @return {!cursors.Cursor}
296 */
297 getBound: function(dir) {
298 return dir == Dir.FORWARD ? this.end_ : this.start_;
299 },
300
301 /**
302 * @return {!cursors.Cursor}
303 */
304 getStart: function() {
305 return this.start_;
306 },
307
308 /**
309 * @return {!cursors.Cursor}
310 */
311 getEnd: function() {
312 return this.end_;
313 },
314
315 /**
316 * Makes a Range which has been moved from this range by the given unit and
317 * direction.
318 * @param {Unit} unit
319 * @param {Dir} dir
320 * @return {cursors.Range}
321 */
322 move: function(unit, dir) {
323 var newStart = this.start_;
324 var newEnd = newStart;
325 switch (unit) {
326 case Unit.CHARACTER:
327 newStart = newStart.move(unit, Movement.BOUND, dir);
328 newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD);
329 // Character crossed a node; collapses to the end of the node.
330 if (newStart.getNode() !== newEnd.getNode())
331 newEnd = newStart;
332 break;
333 case Unit.WORD:
334 case Unit.LINE:
335 case Unit.NODE:
336 newEnd = newEnd.move(unit, Movement.DIRECTIONAL, dir);
337 newStart = newEnd;
338 newEnd = newEnd.move(unit, Movement.BOUND, Dir.FORWARD);
339 break;
340 }
341 return new cursors.Range(newStart, newEnd);
342 }
343 };
344
217 }); // goog.scope 345 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698