// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview A base class for walkers that have a concept of lowest-level * node. Base classes must override the stopNodeDescent method to define * what a lowest-level node is. Then this walker will use those nodes as the * set of valid CursorSelections. */ goog.provide('cvox.AbstractNodeWalker'); goog.require('cvox.AbstractWalker'); goog.require('cvox.CursorSelection'); goog.require('cvox.DomUtil'); /** * @constructor * @extends {cvox.AbstractWalker} */ cvox.AbstractNodeWalker = function() { goog.base(this); /** * To keep track of and break infinite loops when trying to call next on * a body that does not DomUtil.hasContent(). * @type {boolean} * @private */ this.wasBegin_ = false; }; goog.inherits(cvox.AbstractNodeWalker, cvox.AbstractWalker); /** * @override */ cvox.AbstractNodeWalker.prototype.next = function(sel) { var r = sel.isReversed(); var node = sel.end.node || document.body; do { node = cvox.DomUtil.directedNextLeafLikeNode(node, r, goog.bind(this.stopNodeDescent, this)); if (!node) { return null; } // and repeat all of the above until we have a node that is not empty } while (node && !cvox.DomUtil.hasContent(node)); return cvox.CursorSelection.fromNode(node).setReversed(r); }; /** * @override */ cvox.AbstractNodeWalker.prototype.sync = function(sel) { var ret = this.privateSync_(sel); this.wasBegin_ = false; return ret; }; /** * Private version of sync to ensure that when a body has no content, we * don't do an infinite loop trying to find an empty node. * @param {!cvox.CursorSelection} sel The selection. * @return {cvox.CursorSelection} The synced selection. * @private */ cvox.AbstractNodeWalker.prototype.privateSync_ = function(sel) { var r = sel.isReversed(); if (sel.equals(cvox.CursorSelection.fromBody())) { if (this.wasBegin_) { // if body is empty, we return just the body selection return cvox.CursorSelection.fromBody().setReversed(r); } this.wasBegin_ = true; } var node = sel.start.node; while (node != document.body && node.parentNode && this.stopNodeDescent(node.parentNode)) { node = node.parentNode; } while (!this.stopNodeDescent(node)) { node = cvox.DomUtil.directedFirstChild(node, r); } var n = cvox.CursorSelection.fromNode(node); if (!cvox.DomUtil.hasContent(node)) { n = this.next(/** @type {!cvox.CursorSelection} */ (cvox.CursorSelection.fromNode(node)).setReversed(r)); } if (n) { return n.setReversed(r); } return this.begin({reversed: r}); }; /** * Returns true if this is "a leaf node" or lower. That is, * it is at the lowest valid level or lower for this granularity. * RESTRICTION: true for a node => true for all child nodes * RESTRICTION: true if node has no children * @param {!Node} node The node to check. * @return {boolean} true if this is at the "leaf node" level or lower * for this granularity. * @protected */ cvox.AbstractNodeWalker.prototype.stopNodeDescent = goog.abstractMethod;