Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / cvox2 / background / cursors.js
blob059a0f1cd507ac973172a304a0fe62275bc1f445
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.
5 /**
6  * @fileoverview Classes related to cursors that point to and select parts of
7  * the automation tree.
8  */
10 goog.provide('cursors.Cursor');
11 goog.provide('cursors.Movement');
12 goog.provide('cursors.Range');
13 goog.provide('cursors.Unit');
15 goog.require('AutomationUtil');
17 /**
18  * The special index that represents a cursor pointing to a node without
19  * pointing to any part of its accessible text.
20  */
21 cursors.NODE_INDEX = -1;
23 /**
24  * Represents units of CursorMovement.
25  * @enum {string}
26  */
27 cursors.Unit = {
28   /** A single character within accessible name or value. */
29   CHARACTER: 'character',
31   /** A range of characters (given by attributes on automation nodes). */
32   WORD: 'word',
34   /** A leaf node. */
35   NODE: 'node',
37   /** Formed by a set of leaf nodes that are inline. */
38   LINE: 'line'
41 /**
42  * Represents the ways in which cursors can move given a cursor unit.
43  * @enum {string}
44  */
45 cursors.Movement = {
46   /** Move to the beginning or end of the current unit. */
47   BOUND: 'bound',
49   /** Move to the next unit in a particular direction. */
50   DIRECTIONAL: 'directional'
53 goog.scope(function() {
54 var AutomationNode = chrome.automation.AutomationNode;
55 var Dir = AutomationUtil.Dir;
56 var Movement = cursors.Movement;
57 var Role = chrome.automation.RoleType;
58 var Unit = cursors.Unit;
60 /**
61  * Represents a position within the automation tree.
62  * @constructor
63  * @param {!AutomationNode} node
64  * @param {number} index A 0-based index into either this cursor's name or value
65  * attribute. Relies on the fact that a node has either a name or a value but
66  * not both. An index of |cursors.NODE_INDEX| means the node as a whole is
67  * pointed to and covers the case where the accessible text is empty.
68  */
69 cursors.Cursor = function(node, index) {
70   /** @type {!AutomationNode} @private */
71   this.node_ = node;
72   /** @type {number} @private */
73   this.index_ = index;
76 /**
77  * Convenience method to construct a Cursor from a node.
78  * @param {!AutomationNode} node
79  * @return {!cursors.Cursor}
80  */
81 cursors.Cursor.fromNode = function(node) {
82   return new cursors.Cursor(node, cursors.NODE_INDEX);
85 cursors.Cursor.prototype = {
86   /**
87    * Returns true if |rhs| is equal to this cursor.
88    * @param {!cursors.Cursor} rhs
89    * @return {boolean}
90    */
91   equals: function(rhs) {
92     return this.node_ === rhs.node &&
93         this.index_ === rhs.getIndex();
94   },
96   /**
97    * @return {!AutomationNode}
98    */
99   get node() {
100     return this.node_;
101   },
103   /**
104    * @return {number}
105    */
106   getIndex: function() {
107     return this.index_;
108   },
110   /**
111    * Gets the accessible text of the node associated with this cursor.
112    *
113    * Note that only one of |name| or |value| attribute is ever nonempty on an
114    * automation node. If either contains whitespace, we still treat it as we do
115    * for a nonempty string.
116    * @param {!AutomationNode=} opt_node Use this node rather than this cursor's
117    * node.
118    * @return {string}
119    */
120   getText: function(opt_node) {
121     var node = opt_node || this.node_;
122     return node.name || node.value || '';
123   },
125   /**
126    * Makes a Cursor which has been moved from this cursor by the unit in the
127    * given direction using the given movement type.
128    * @param {Unit} unit
129    * @param {Movement} movement
130    * @param {Dir} dir
131    * @return {!cursors.Cursor} The moved cursor.
132    */
133   move: function(unit, movement, dir) {
134     var newNode = this.node_;
135     var newIndex = this.index_;
137     if (unit != Unit.NODE && newIndex === cursors.NODE_INDEX)
138       newIndex = 0;
140     switch (unit) {
141       case Unit.CHARACTER:
142         // BOUND and DIRECTIONAL are the same for characters.
143         newIndex = dir == Dir.FORWARD ? newIndex + 1 : newIndex - 1;
144         if (newIndex < 0 || newIndex >= this.getText().length) {
145           newNode = AutomationUtil.findNextNode(
146               newNode, dir, AutomationPredicate.leafWithText);
147           if (newNode) {
148             newIndex =
149                 dir == Dir.FORWARD ? 0 : this.getText(newNode).length - 1;
150             newIndex = newIndex == -1 ? 0 : newIndex;
151           } else {
152             newIndex = this.index_;
153           }
154         }
155         break;
156       case Unit.WORD:
157         switch (movement) {
158           case Movement.BOUND:
159             if (newNode.role == Role.inlineTextBox) {
160               var start, end;
161               for (var i = 0; i < newNode.wordStarts.length; i++) {
162                 if (newIndex >= newNode.wordStarts[i] &&
163                     newIndex <= newNode.wordEnds[i]) {
164                   start = newNode.wordStarts[i];
165                   end = newNode.wordEnds[i];
166                   break;
167                 }
168               }
169               if (goog.isDef(start) && goog.isDef(end))
170                 newIndex = dir == Dir.FORWARD ? end : start;
171             } else {
172               // TODO(dtseng): Figure out what to do in this case.
173             }
174             break;
175           case Movement.DIRECTIONAL:
176             if (newNode.role == Role.inlineTextBox) {
177               var start, end;
178               for (var i = 0; i < newNode.wordStarts.length; i++) {
179                 if (newIndex >= newNode.wordStarts[i] &&
180                     newIndex <= newNode.wordEnds[i]) {
181                   var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1;
182                   start = newNode.wordStarts[nextIndex];
183                   end = newNode.wordEnds[nextIndex];
184                   break;
185                 }
186               }
187               if (goog.isDef(start)) {
188                 newIndex = start;
189               } else {
190                 // The backward case is special at the beginning of nodes.
191                 if (dir == Dir.BACKWARD && newIndex != 0) {
192                   newIndex = 0;
193                 } else {
194                   newNode = AutomationUtil.findNextNode(newNode, dir,
195                       AutomationPredicate.leaf);
196                   if (newNode) {
197                     newIndex = 0;
198                     if (dir == Dir.BACKWARD &&
199                         newNode.role == Role.inlineTextBox) {
200                       var starts = newNode.wordStarts;
201                       newIndex = starts[starts.length - 1] || 0;
202                     } else {
203                       // TODO(dtseng): Figure out what to do for general nodes.
204                     }
205                   }
206                 }
207               }
208             } else {
209               // TODO(dtseng): Figure out what to do in this case.
210             }
211         }
212         break;
213       case Unit.NODE:
214         switch (movement) {
215           case Movement.BOUND:
216             newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0;
217             break;
218           case Movement.DIRECTIONAL:
219             newNode = AutomationUtil.findNextNode(
220                 newNode, dir, AutomationPredicate.leaf) || this.node_;
221             newIndex = cursors.NODE_INDEX;
222             break;
223         }
224         break;
225       case Unit.LINE:
226         newIndex = 0;
227         switch (movement) {
228           case Movement.BOUND:
229             newNode = AutomationUtil.findNodeUntil(newNode, dir,
230                 AutomationPredicate.linebreak, {before: true});
231             newNode = newNode || this.node_;
232             newIndex =
233                 dir == Dir.FORWARD ? this.getText(newNode).length : 0;
234             break;
235           case Movement.DIRECTIONAL:
236             newNode = AutomationUtil.findNodeUntil(
237                 newNode, dir, AutomationPredicate.linebreak);
238             break;
239           }
240       break;
241       default:
242         throw 'Unrecognized unit: ' + unit;
243     }
244     newNode = newNode || this.node_;
245     newIndex = goog.isDef(newIndex) ? newIndex : this.index_;
246     return new cursors.Cursor(newNode, newIndex);
247   }
251  * Represents a range in the automation tree. There is no visible selection on
252  * the page caused by usage of this object.
253  * It is assumed that the caller provides |start| and |end| in document order.
254  * @param {!cursors.Cursor} start
255  * @param {!cursors.Cursor} end
256  * @constructor
257  */
258 cursors.Range = function(start, end) {
259   /** @type {!cursors.Cursor} @private */
260   this.start_ = start;
261   /** @type {!cursors.Cursor} @private */
262   this.end_ = end;
266  * Convenience method to construct a Range surrounding one node.
267  * @param {!AutomationNode} node
268  * @return {!cursors.Range}
269  */
270 cursors.Range.fromNode = function(node) {
271   var cursor = cursors.Cursor.fromNode(node);
272   return new cursors.Range(cursor, cursor);
275  /**
276  * Given |rangeA| and |rangeB| in order, determine which |Dir|
277  * relates them.
278  * @param {!cursors.Range} rangeA
279  * @param {!cursors.Range} rangeB
280  * @return {Dir}
281  */
282 cursors.Range.getDirection = function(rangeA, rangeB) {
283   if (!rangeA || !rangeB)
284     return Dir.FORWARD;
286   // They are the same range.
287   if (rangeA.start.node === rangeB.start.node &&
288       rangeB.end.node === rangeA.end.node)
289     return Dir.FORWARD;
291   var testDirA =
292       AutomationUtil.getDirection(
293           rangeA.start.node, rangeB.end.node);
294   var testDirB =
295       AutomationUtil.getDirection(
296           rangeB.start.node, rangeA.end.node);
298   // The two ranges are either partly overlapping or non overlapping.
299   if (testDirA == Dir.FORWARD && testDirB == Dir.BACKWARD)
300     return Dir.FORWARD;
301   else if (testDirA == Dir.BACKWARD && testDirB == Dir.FORWARD)
302     return Dir.BACKWARD;
303   else
304     return testDirA;
307 cursors.Range.prototype = {
308   /**
309    * Returns true if |rhs| is equal to this range.
310    * @param {!cursors.Range} rhs
311    * @return {boolean}
312    */
313   equals: function(rhs) {
314     return this.start_.equals(rhs.start) &&
315         this.end_.equals(rhs.end);
316   },
318   /**
319    * Gets a cursor bounding this range.
320    * @param {Dir} dir Which endpoint cursor to return; Dir.FORWARD for end,
321    * Dir.BACKWARD for start.
322    * @param {boolean=} opt_reverse Specify to have Dir.BACKWARD return end,
323    * Dir.FORWARD return start.
324    * @return {!cursors.Cursor}
325    */
326   getBound: function(dir, opt_reverse) {
327     if (opt_reverse)
328       return dir == Dir.BACKWARD ? this.end_ : this.start_;
329     return dir == Dir.FORWARD ? this.end_ : this.start_;
330   },
332   /**
333    * @return {!cursors.Cursor}
334    */
335   get start() {
336     return this.start_;
337   },
339   /**
340    * @return {!cursors.Cursor}
341    */
342   get end() {
343     return this.end_;
344   },
346   /**
347    * Returns true if this range covers less than a node.
348    * @return {boolean}
349    */
350   isSubNode: function() {
351     return this.start.node === this.end.node &&
352         this.start.getIndex() > -1 &&
353         this.end.getIndex() > -1;
354   },
356   /**
357    * Makes a Range which has been moved from this range by the given unit and
358    * direction.
359    * @param {Unit} unit
360    * @param {Dir} dir
361    * @return {cursors.Range}
362    */
363   move: function(unit, dir) {
364     var newStart = this.start_;
365     var newEnd = newStart;
366     switch (unit) {
367       case Unit.CHARACTER:
368         newStart = newStart.move(unit, Movement.BOUND, dir);
369         newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD);
370         // Character crossed a node; collapses to the end of the node.
371         if (newStart.node !== newEnd.node)
372           newEnd = newStart;
373         break;
374       case Unit.WORD:
375       case Unit.LINE:
376         newStart = newStart.move(unit, Movement.DIRECTIONAL, dir);
377         newStart = newStart.move(unit, Movement.BOUND, Dir.BACKWARD);
378         newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD);
379         break;
380       case Unit.NODE:
381         newStart = newStart.move(unit, Movement.DIRECTIONAL, dir);
382         newEnd = newStart;
383         break;
384     }
385     return new cursors.Range(newStart, newEnd);
386   }
389 });  // goog.scope