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