abstract_node_walker.js revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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 base class for walkers that have a concept of lowest-level
7 * node. Base classes must override the stopNodeDescent method to define
8 * what a lowest-level node is. Then this walker will use those nodes as the
9 * set of valid CursorSelections.
10 */
11
12
13goog.provide('cvox.AbstractNodeWalker');
14
15goog.require('cvox.AbstractWalker');
16goog.require('cvox.CursorSelection');
17goog.require('cvox.DomUtil');
18
19/**
20 * @constructor
21 * @extends {cvox.AbstractWalker}
22 */
23cvox.AbstractNodeWalker = function() {
24  goog.base(this);
25
26  /**
27   * To keep track of and break infinite loops when trying to call next on
28   * a body that does not DomUtil.hasContent().
29   * @type {boolean}
30   * @private
31   */
32  this.wasBegin_ = false;
33};
34goog.inherits(cvox.AbstractNodeWalker, cvox.AbstractWalker);
35
36/**
37 * @override
38 */
39cvox.AbstractNodeWalker.prototype.next = function(sel) {
40  var r = sel.isReversed();
41  var node = sel.end.node || document.body;
42
43  do {
44    node = cvox.DomUtil.directedNextLeafLikeNode(node, r,
45        goog.bind(this.stopNodeDescent, this));
46    if (!node) {
47      return null;
48    }
49    // and repeat all of the above until we have a node that is not empty
50  } while (node && !cvox.DomUtil.hasContent(node));
51
52  return cvox.CursorSelection.fromNode(node).setReversed(r);
53};
54
55/**
56 * @override
57 */
58cvox.AbstractNodeWalker.prototype.sync = function(sel) {
59  var ret = this.privateSync_(sel);
60  this.wasBegin_ = false;
61  return ret;
62};
63
64
65/**
66 * Private version of sync to ensure that when a body has no content, we
67 * don't do an infinite loop trying to find an empty node.
68 * @param {!cvox.CursorSelection} sel The selection.
69 * @return {cvox.CursorSelection} The synced selection.
70 * @private
71 */
72cvox.AbstractNodeWalker.prototype.privateSync_ = function(sel) {
73  var r = sel.isReversed();
74
75  if (sel.equals(cvox.CursorSelection.fromBody())) {
76    if (this.wasBegin_) {
77      // if body is empty, we return just the body selection
78      return cvox.CursorSelection.fromBody().setReversed(r);
79    }
80    this.wasBegin_ = true;
81  }
82
83  var node = sel.start.node;
84
85  while (node != document.body && node.parentNode &&
86      this.stopNodeDescent(node.parentNode)) {
87    node = node.parentNode;
88  }
89
90  while (!this.stopNodeDescent(node)) {
91    node = cvox.DomUtil.directedFirstChild(node, r);
92  }
93
94  var n = cvox.CursorSelection.fromNode(node);
95  if (!cvox.DomUtil.hasContent(node)) {
96    var n = this.next(/** @type {!cvox.CursorSelection} */
97        (cvox.CursorSelection.fromNode(node)).setReversed(r));
98  }
99  if (n) {
100    return n.setReversed(r);
101  }
102  return this.begin({reversed: r});
103};
104
105/**
106 * Returns true if this is "a leaf node" or lower. That is,
107 * it is at the lowest valid level or lower for this granularity.
108 * RESTRICTION: true for a node => true for all child nodes
109 * RESTRICTION: true if node has no children
110 * @param {!Node} node The node to check.
111 * @return {boolean} true if this is at the "leaf node" level or lower
112 * for this granularity.
113 * @protected
114 */
115cvox.AbstractNodeWalker.prototype.stopNodeDescent = goog.abstractMethod;
116