Home | History | Annotate | Download | only in walkers
      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 
     14 goog.provide('cvox.AbstractSelectionWalker');
     15 
     16 goog.require('cvox.AbstractWalker');
     17 goog.require('cvox.BareObjectWalker');
     18 goog.require('cvox.DescriptionUtil');
     19 goog.require('cvox.DomUtil');
     20 goog.require('cvox.Spannable');
     21 goog.require('cvox.TraverseContent');
     22 
     23 /**
     24  * @constructor
     25  * @extends {cvox.AbstractWalker}
     26  */
     27 cvox.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 };
     33 goog.inherits(cvox.AbstractSelectionWalker, cvox.AbstractWalker);
     34 
     35 /**
     36  * @override
     37  */
     38 cvox.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  */
     68 cvox.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  */
    137 cvox.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  */
    149 cvox.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