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 An abstract class for walking at the sub-element level.
7 * For example, walking at the sentence, word, or character level.
8 * This class is an adapter around TraverseContent which exposes the interface
9 * required by walkers. Subclasses must override the this.grain attribute
10 * on initialization.
11 */
12
13
14goog.provide('cvox.AbstractSelectionWalker');
15
16goog.require('cvox.AbstractWalker');
17goog.require('cvox.BareObjectWalker');
18goog.require('cvox.DescriptionUtil');
19goog.require('cvox.DomUtil');
20goog.require('cvox.Spannable');
21goog.require('cvox.TraverseContent');
22
23/**
24 * @constructor
25 * @extends {cvox.AbstractWalker}
26 */
27cvox.AbstractSelectionWalker = function() {
28  cvox.AbstractWalker.call(this);
29  this.objWalker_ = new cvox.BareObjectWalker();
30  this.tc_ = cvox.TraverseContent.getInstance();
31  this.grain /** @protected */ = ''; // child must override
32};
33goog.inherits(cvox.AbstractSelectionWalker, cvox.AbstractWalker);
34
35/**
36 * @override
37 */
38cvox.AbstractSelectionWalker.prototype.next = function(sel) {
39  var r = sel.isReversed();
40  this.tc_.syncToCursorSelection(sel.clone().setReversed(false));
41  var ret = r ? this.tc_.prevElement(this.grain) :
42      this.tc_.nextElement(this.grain);
43  if (ret == null) {
44    // Unfortunately, we can't trust TraverseContent; fall back to ObjectWalker.
45    return this.objWalker_.next(sel);
46  }
47  var retSel = this.tc_.getCurrentCursorSelection().setReversed(r);
48  var objSel = this.objWalker_.next(sel);
49  objSel = objSel ? objSel.setReversed(r) : null;
50
51  // ObjectWalker wins when there's a discrepancy between it and
52  // TraverseContent. The only exception is with an end cursor on a text node.
53  // In all other cases, this makes sure we visit the same selections as
54  // object walker.
55  if (objSel &&
56      (retSel.end.node.constructor.name != 'Text' ||
57          objSel.end.node.constructor.name != 'Text') &&
58      !cvox.DomUtil.isDescendantOfNode(retSel.end.node, sel.end.node) &&
59      !cvox.DomUtil.isDescendantOfNode(retSel.end.node, objSel.end.node)) {
60    return objSel;
61  }
62  return retSel;
63};
64
65/**
66 * @override
67 */
68cvox.AbstractSelectionWalker.prototype.sync = function(sel) {
69  var r = sel.isReversed();
70  var newSel = null;
71  if (sel.start.equals(sel.end) && sel.start.node.constructor.name != 'Text') {
72    var node = sel.start.node;
73
74    // Find the deepest visible node; written specifically here because we want
75    // to move across siblings if necessary and take the deepest node which can
76    // be BODY.
77    while (node &&
78        cvox.DomUtil.directedFirstChild(node, r) &&
79        !cvox.TraverseUtil.treatAsLeafNode(node)) {
80      var child = cvox.DomUtil.directedFirstChild(node, r);
81
82      // Find the first visible child.
83      while (child) {
84        if (cvox.DomUtil.isVisible(child,
85            {checkAncestors: false, checkDescendants: false})) {
86          node = child;
87          break;
88        } else {
89          child = cvox.DomUtil.directedNextSibling(child, r);
90        }
91      }
92
93      // node has no visible children; it's therefore the deepest visible node.
94      if (!child) {
95        break;
96      }
97    }
98    newSel = cvox.CursorSelection.fromNode(node);
99  } else {
100    newSel = sel.clone();
101    if (r) {
102      newSel.start = newSel.end;
103    } else {
104      newSel.end = newSel.start;
105    }
106  }
107
108  // This.next places us at the correct initial position (except below).
109  newSel = this.next(newSel.setReversed(false));
110
111  // ObjectWalker wins when there's a discrepancy between it and
112  // TraverseContent. The only exception is with an end cursor on a text node.
113  // In all other cases, this makes sure we visit the same selections as
114  // object walker.
115  var objSel = this.objWalker_.sync(sel);
116  objSel = objSel ? objSel.setReversed(r) : null;
117
118  if (!newSel) {
119    return objSel;
120  }
121
122  newSel.setReversed(r);
123
124  if (objSel &&
125      (newSel.end.node.constructor.name != 'Text' ||
126          objSel.end.node.constructor.name != 'Text') &&
127      !cvox.DomUtil.isDescendantOfNode(newSel.end.node, sel.end.node) &&
128      !cvox.DomUtil.isDescendantOfNode(newSel.end.node, objSel.end.node)) {
129    return objSel;
130  }
131  return newSel;
132};
133
134/**
135 * @override
136 */
137cvox.AbstractSelectionWalker.prototype.getDescription = function(prevSel, sel) {
138  var description = cvox.DescriptionUtil.getDescriptionFromAncestors(
139      cvox.DomUtil.getUniqueAncestors(prevSel.end.node, sel.start.node),
140      true,
141      cvox.ChromeVox.verbosity);
142  description.text = sel.getText() || description.text;
143  return [description];
144};
145
146/**
147 * @override
148 */
149cvox.AbstractSelectionWalker.prototype.getBraille = function(prevSel, sel) {
150  var node = sel.absStart().node;
151  var text = cvox.TraverseUtil.getNodeText(node);
152  var spannable = new cvox.Spannable(text);
153  spannable.setSpan(node, 0, text.length);
154  return new cvox.NavBraille({
155    text: spannable,
156    startIndex: sel.absStart().index,
157    endIndex: sel.absEnd().index
158  });
159};
160