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 A JavaScript class for walking lines consisting of one or more
      7  * clickable nodes.
      8  */
      9 
     10 
     11 goog.provide('cvox.LayoutLineWalker');
     12 
     13 goog.require('cvox.AbstractWalker');
     14 goog.require('cvox.StructuralLineWalker');
     15 
     16 
     17 /**
     18  * @constructor
     19  * @extends {cvox.AbstractWalker}
     20  */
     21 cvox.LayoutLineWalker = function() {
     22   this.subWalker_ = new cvox.StructuralLineWalker();
     23 };
     24 goog.inherits(cvox.LayoutLineWalker, cvox.AbstractWalker);
     25 
     26 
     27 /**
     28  * @override
     29  */
     30 cvox.LayoutLineWalker.prototype.next = function(sel) {
     31   // Collapse selection to the directed end.
     32   var endSel = new cvox.CursorSelection(sel.end, sel.end, sel.isReversed());
     33 
     34   // Sync to the line.
     35   var sync = this.subWalker_.sync(endSel);
     36   if (!sync) {
     37     return null;
     38   }
     39 
     40   // Compute the next selection.
     41   var start = this.subWalker_.next(endSel);
     42   if (!start) {
     43     return null;
     44   }
     45   start.setReversed(sel.isReversed());
     46   return this.extend_(start).setReversed(false);
     47 };
     48 
     49 
     50 /**
     51  * @override
     52  */
     53 cvox.LayoutLineWalker.prototype.sync = function(sel) {
     54   var line = this.subWalker_.sync(sel);
     55   if (!line) {
     56     return null;
     57   }
     58 
     59   // Extend to both line breaks (in each direction).
     60   var end = this.extend_(line);
     61   var start = this.extend_(line.setReversed(!line.isReversed()));
     62 
     63   return new cvox.CursorSelection(start.end, end.end, sel.isReversed());
     64 };
     65 
     66 
     67 /**
     68  * @override
     69  */
     70 cvox.LayoutLineWalker.prototype.getDescription = function(prevSel, sel) {
     71   var descriptions = [];
     72   var prev = prevSel;
     73   var absSel = sel.clone().setReversed(false);
     74   var cur = new cvox.CursorSelection(absSel.start, absSel.start);
     75   cur = this.subWalker_.sync(cur);
     76   if (!cur) {
     77     return [];
     78   }
     79 
     80   // No need to accumulate descriptions.
     81   if (absSel.start.node == absSel.end.node) {
     82     return this.subWalker_.getDescription(prevSel, sel);
     83   }
     84 
     85   // Walk through and collect descriptions for each line.
     86   while (cur && !cur.end.equals(absSel.end)) {
     87     descriptions.push.apply(
     88         descriptions, this.subWalker_.getDescription(prev, cur));
     89     prev = cur;
     90     cur = this.subWalker_.next(cur);
     91   }
     92   if (cur) {
     93     descriptions.push.apply(
     94         descriptions, this.subWalker_.getDescription(prev, cur));
     95   }
     96   return descriptions;
     97 };
     98 
     99 
    100 /**
    101  * @override
    102  */
    103 cvox.LayoutLineWalker.prototype.getBraille = function(prevSel, sel) {
    104   var braille = new cvox.NavBraille({});
    105   var absSel = this.subWalker_.sync(sel.clone().setReversed(false));
    106   var layoutSel = this.sync(sel).setReversed(false);
    107   if (!layoutSel || !absSel) {
    108     return braille;
    109   }
    110   var cur = new cvox.CursorSelection(layoutSel.start, layoutSel.start);
    111   cur = this.subWalker_.sync(cur);
    112   if (!cur) {
    113     return braille;
    114   }
    115 
    116   // Walk through and collect braille for each line.
    117   while (cur && !cur.end.equals(layoutSel.end)) {
    118     this.appendBraille_(prevSel, absSel, cur, braille);
    119     prevSel = cur;
    120     cur = this.subWalker_.next(cur);
    121   }
    122   if (cur) {
    123     this.appendBraille_(prevSel, absSel, cur, braille);
    124   }
    125   return braille;
    126 };
    127 
    128 
    129 /**
    130  * @override
    131  */
    132 cvox.LayoutLineWalker.prototype.getGranularityMsg = function() {
    133   return cvox.ChromeVox.msgs.getMsg('layout_line');
    134 };
    135 
    136 
    137 /**
    138  * Compares two selections and determines if the lie on the same horizontal
    139  * line as determined by their bounding rectangles.
    140  * @param {!cvox.CursorSelection} lSel Left selection.
    141  * @param {!cvox.CursorSelection} rSel Right selection.
    142  * @return {boolean} Whether lSel and rSel are on different visual lines.
    143  * @private
    144  */
    145 cvox.LayoutLineWalker.prototype.isVisualLineBreak_ = function(lSel, rSel) {
    146   if (this.wantsOwnLine_(lSel.end.node) ||
    147       this.wantsOwnLine_(rSel.start.node)) {
    148     return true;
    149   }
    150   var lRect = lSel.getRange().getBoundingClientRect();
    151   var rRect = rSel.getRange().getBoundingClientRect();
    152 
    153   // Some ranges from the browser give us 0-sized rects (such as in the case of
    154   // select's). Detect these cases and use a more reliable method (take the
    155   // bounding rect of the actual element rather than the range).
    156   if (lRect.width == 0 &&
    157       lRect.height == 0 &&
    158       lSel.end.node.nodeType == Node.ELEMENT_NODE) {
    159     lRect = lSel.end.node.getBoundingClientRect();
    160   }
    161 
    162   if (rRect.width == 0 &&
    163       rRect.height == 0 &&
    164       rSel.start.node.nodeType == Node.ELEMENT_NODE) {
    165     rRect = rSel.start.node.getBoundingClientRect();
    166   }
    167   return lRect.bottom != rRect.bottom;
    168 };
    169 
    170 
    171 /**
    172  * Determines if node should force a line break.
    173  * This is used for elements with unusual semantics, such as multi-line
    174  * text fields, where the behaviour would otherwise be confusing.
    175  * @param {!Node} node Node.
    176  * @return {boolean} True if the node should appear next to a line break.
    177  * @private
    178  */
    179 cvox.LayoutLineWalker.prototype.wantsOwnLine_ = function(node) {
    180   return node instanceof HTMLTextAreaElement ||
    181       node.parentNode instanceof HTMLTextAreaElement;
    182 };
    183 
    184 
    185 /**
    186  * Extends a given cursor selection up to the next visual line break.
    187  * @param {!cvox.CursorSelection} start The selection.
    188  * @return {!cvox.CursorSelection} The resulting selection.
    189  * @private
    190  */
    191 cvox.LayoutLineWalker.prototype.extend_ = function(start) {
    192   // Extend the selection up to just before a new visual line break.
    193   var end = start;
    194   var next = start;
    195 
    196   do {
    197     end = next;
    198     next = this.subWalker_.next(end);
    199   } while (next && !this.isVisualLineBreak_(end, next));
    200   return new cvox.CursorSelection(start.start, end.end, start.isReversed());
    201 };
    202 
    203 
    204 /**
    205  * Private routine to append braille given three selections.
    206  * @param {!cvox.CursorSelection} prevSel A previous selection in walker
    207  * ordering.
    208  * @param {!cvox.CursorSelection} sel A selection that represents the location
    209  * of the braille cursor.
    210  * @param {!cvox.CursorSelection} cur The specific selection to append.
    211  * @param {!cvox.NavBraille} braille Braille on which to append.
    212  * @private
    213  */
    214 cvox.LayoutLineWalker.prototype.appendBraille_ = function(
    215     prevSel, sel, cur, braille) {
    216   var item = this.subWalker_.getBraille(prevSel, cur).text;
    217   var valueSelectionSpan = item.getSpanInstanceOf(
    218       cvox.BrailleUtil.ValueSelectionSpan);
    219 
    220   if (braille.text.getLength() > 0) {
    221     braille.text.append(cvox.BrailleUtil.ITEM_SEPARATOR);
    222   }
    223 
    224   // Find the surrounding logical "leaf node".
    225   // This prevents us from labelling the braille output with the wrong node,
    226   // such as a text node child of a <textarea>.
    227   var node = cur.start.node;
    228   while (node.parentNode && cvox.DomUtil.isLeafNode(node.parentNode)) {
    229     node = node.parentNode;
    230   }
    231 
    232   var nodeStart = braille.text.getLength();
    233   var nodeEnd = nodeStart + item.getLength();
    234   braille.text.append(item);
    235   braille.text.setSpan(node, nodeStart, nodeEnd);
    236 
    237   if (sel && cur.absEquals(sel)) {
    238     if (valueSelectionSpan) {
    239       braille.startIndex = nodeStart + item.getSpanStart(valueSelectionSpan);
    240       braille.endIndex = nodeStart + item.getSpanEnd(valueSelectionSpan);
    241     } else {
    242       braille.startIndex = nodeStart;
    243       braille.endIndex = nodeStart + 1;
    244     }
    245   }
    246 };
    247