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

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: Indent; TODO. 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 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 (var i = 0;
132 if (this.index >= this.node.attributes.wordStarts[i] && 159 i < this.node_.attributes.wordStarts.length;
133 this.index <= this.node.attributes.wordEnds[i]) { 160 i++) {
134 start = this.node.attributes.wordStarts[i]; 161 if (this.index_ >= this.node_.attributes.wordStarts[i] &&
135 end = this.node.attributes.wordEnds[i]; 162 this.index_ <= this.node_.attributes.wordEnds[i]) {
163 start = this.node_.attributes.wordStarts[i];
164 end = this.node_.attributes.wordEnds[i];
136 break; 165 break;
137 } 166 }
138 } 167 }
139 if (goog.isDef(start) && goog.isDef(end)) 168 if (goog.isDef(start) && goog.isDef(end))
140 this.index = dir == Dir.FORWARD ? end : start; 169 newIndex = dir == Dir.FORWARD ? end : start;
141 } else { 170 } else {
142 // TODO(dtseng): Figure out what to do in this case. 171 // TODO(dtseng): Figure out what to do in this case.
143 } 172 }
144 break; 173 break;
145 case Movement.DIRECTIONAL: 174 case Movement.DIRECTIONAL:
146 if (this.node.role == Role.inlineTextBox) { 175 if (this.node_.role == Role.inlineTextBox) {
147 var start, end; 176 var start, end;
148 for (var i = 0; i < this.node.attributes.wordStarts.length; i++) { 177 for (var i = 0;
149 if (this.index >= this.node.attributes.wordStarts[i] && 178 i < this.node_.attributes.wordStarts.length;
150 this.index <= this.node.attributes.wordEnds[i]) { 179 i++) {
180 if (this.index_ >= this.node_.attributes.wordStarts[i] &&
181 this.index_ <= this.node_.attributes.wordEnds[i]) {
151 var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1; 182 var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1;
152 start = this.node.attributes.wordStarts[nextIndex]; 183 start = this.node_.attributes.wordStarts[nextIndex];
153 end = this.node.attributes.wordEnds[nextIndex]; 184 end = this.node_.attributes.wordEnds[nextIndex];
154 break; 185 break;
155 } 186 }
156 } 187 }
157 if (goog.isDef(start)) { 188 if (goog.isDef(start)) {
158 this.index = start; 189 newIndex = start;
159 } else { 190 } else {
160 var node = AutomationUtil.findNextNode(this.node, dir, 191 // The backward case is special at the beginning of nodes.
161 AutomationPredicate.leaf); 192 if (dir == Dir.BACKWARD && this.index_ != 0) {
162 if (node) { 193 this.index_ = 0;
163 this.node = node; 194 } else {
164 this.index = 0; 195 var node = AutomationUtil.findNextNode(this.node_, dir,
165 if (dir == Dir.BACKWARD && node.role == Role.inlineTextBox) { 196 AutomationPredicate.leaf);
166 var starts = node.attributes.wordStarts; 197 if (node) {
167 this.index = starts[starts.length - 1] || 0; 198 newNode = node;
168 } else { 199 newIndex = 0;
169 // TODO(dtseng): Figure out what to do for general nodes. 200 if (dir == Dir.BACKWARD &&
201 node.role == Role.inlineTextBox) {
202 var starts = node.attributes.wordStarts;
203 newIndex = starts[starts.length - 1] || 0;
204 } else {
205 // TODO(dtseng): Figure out what to do for general nodes.
206 }
170 } 207 }
171 } 208 }
172 } 209 }
173 } else { 210 } else {
174 // TODO(dtseng): Figure out what to do in this case. 211 // TODO(dtseng): Figure out what to do in this case.
175 } 212 }
176 } 213 }
177 break; 214 break;
178 case Unit.NODE: 215 case Unit.NODE:
179 switch (movement) { 216 switch (movement) {
180 case Movement.BOUND: 217 case Movement.BOUND:
181 this.index = dir == Dir.FORWARD ? this.getText().length - 1 : 0; 218 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0;
182 break; 219 break;
183 case Movement.DIRECTIONAL: 220 case Movement.DIRECTIONAL:
184 this.node = AutomationUtil.findNextNode( 221 newNode = AutomationUtil.findNextNode(
185 this.node, dir, AutomationPredicate.leaf) || this.node; 222 this.node_, dir, AutomationPredicate.leaf) || this.node_;
186 this.index = cursors.NODE_INDEX; 223 newIndex = cursors.NODE_INDEX;
187 break; 224 break;
188 } 225 }
189 break; 226 break;
190 case Unit.LINE: 227 case Unit.LINE:
191 this.index = 0; 228 newIndex = 0;
192 switch (movement) { 229 switch (movement) {
193 case Movement.BOUND: 230 case Movement.BOUND:
194 var node = this.node; 231 newNode = AutomationUtil.findNodeUntil(this.node_, dir,
195 node = AutomationUtil.findNodeUntil(node, dir,
196 AutomationPredicate.linebreak, {before: true}); 232 AutomationPredicate.linebreak, {before: true});
197 this.node = node || this.node; 233 newNode = newNode || this.node_;
234 newIndex =
235 dir == Dir.FORWARD ? this.getText(newNode).length - 1 : 0;
198 break; 236 break;
199 case Movement.DIRECTIONAL: 237 case Movement.DIRECTIONAL:
200 var node = this.node; 238 newNode = AutomationUtil.findNodeUntil(
201 node = AutomationUtil.findNodeUntil( 239 this.node_, dir, AutomationPredicate.linebreak);
202 node, dir, AutomationPredicate.linebreak);
203 240
204 // We stick to the beginning of lines out of convention. 241 // We stick to the beginning of lines out of convention.
205 if (node && dir == Dir.BACKWARD) { 242 if (newNode && dir == Dir.BACKWARD) {
206 node = AutomationUtil.findNodeUntil(node, dir, 243 newNode = AutomationUtil.findNodeUntil(newNode, dir,
207 AutomationPredicate.linebreak, {before: true}) || node; 244 AutomationPredicate.linebreak, {before: true}) || node;
208 } 245 }
209 this.node = node || this.node;
210 break; 246 break;
211 } 247 }
212 break; 248 break;
249 default:
250 throw 'Unrecognized unit: ' + unit;
213 } 251 }
252 newNode = newNode || this.node_;
253 newIndex = goog.isDef(newIndex) ? newIndex : this.index_;
254 return new cursors.Cursor(newNode, newIndex);
214 } 255 }
215 }; 256 };
216 257
258 /**
259 * Represents a range in the automation tree. There is no visible selection on
260 * the page caused by usage of this object.
261 * It is assumed that the caller provides |start| and |end| in document order.
262 * @param {!cursors.Cursor} start
263 * @param {!cursors.Cursor} end
264 * @constructor
265 */
266 cursors.Range = function(start, end) {
267 /** @type {!cursors.Cursor} @private */
268 this.start_ = start;
269 /** @type {!cursors.Cursor} @private */
270 this.end_ = end;
271 };
272
273 /**
274 * Convenience method to construct a Range surrounding one node.
275 * @param {!AutomationNode} node
276 * @return {!cursors.Range}
277 */
278 cursors.Range.fromNode = function(node) {
279 var cursor = cursors.Cursor.fromNode(node);
280 return new cursors.Range(cursor, cursor);
281 };
282
283 cursors.Range.prototype = {
284 /**
285 * Returns true if |rhs| is equal to this range.
286 * @param {!cursors.Range} rhs
287 * @return {boolean}
288 */
289 equals: function(rhs) {
290 return this.start_.equals(rhs.getStart()) &&
291 this.end_.equals(rhs.getEnd());
292 },
293
294 /**
295 * Gets a cursor bounding this range.
296 * @param {Dir} dir Which endpoint cursor to return; Dir.FORWARD for end,
297 * Dir.BACKWARD for start.
298 * @return {!cursors.Cursor}
299 */
300 getBound: function(dir) {
301 return dir == Dir.FORWARD ? this.end_ : this.start_;
302 },
303
304 /**
305 * @return {!cursors.Cursor}
306 */
307 getStart: function() {
308 return this.start_;
309 },
310
311 /**
312 * @return {!cursors.Cursor}
313 */
314 getEnd: function() {
315 return this.end_;
316 },
317
318 /**
319 * Makes a Range which has been moved from this range by the given unit and
320 * direction.
321 * @param {Unit} unit
322 * @param {Dir} dir
323 * @return {cursors.Range}
324 */
325 move: function(unit, dir) {
326 var newStart = this.start_;
327 var newEnd = newStart;
328 switch (unit) {
329 case Unit.CHARACTER:
330 newStart = newStart.move(unit, Movement.BOUND, dir);
331 newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD);
332 // Character crossed a node; collapses to the end of the node.
333 if (newStart.getNode() !== newEnd.getNode())
334 newEnd = newStart;
335 break;
336 case Unit.WORD:
337 case Unit.LINE:
338 case Unit.NODE:
339 newEnd = newEnd.move(unit, Movement.DIRECTIONAL, dir);
340 newStart = newEnd;
341 newEnd = newEnd.move(unit, Movement.BOUND, Dir.FORWARD);
342 break;
343 }
344 return new cursors.Range(newStart, newEnd);
345 }
346 };
347
217 }); // goog.scope 348 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698