blob: c704cd9f437b2f22b678847f4f2b7e3926b55af6 [file] [log] [blame]
// 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 An interface (and partial implementation) for the basic
* traversal through some piece of the dom.
* For each different ordered (either in dom or by any other metric) set
* of "valid selections" (just set from now on), a new
* base class should be defined that implements this interface. For example,
* there are subclasses for words, sentences, and lowest-level dom nodes.
* These classes should all be stateless; this makes testing much more
* effective at pinpointing errors.
* For all of the operations in this interface, the position in the dom on
* which to operate is given by a CursorSelection, see that file for
* documentation.
* The two main operations that currently exist for walkers are sync and
* next. See the docs where those functions are defined.
* Since most operations are hard to even define if there is no root element,
* all operations may assume that the selection given is attached to the body
* node. The behavior is undefined if any part of the selection passed in
* is not attached to the body. As a user of this class, it is your
* responsibility to make sure the selection is attached.
* No operation may visibly modify any of its arguments. In particular, take
* care with CursorSelections, since setReversed modifies the selection.
* For all documentation, = refers to the method equals for CursorSelections
* comparison.
* Thinking of adding something in this class? Here are some good questions to
* ask:
* Is this an operation that applies to any element of any arbitrary set?
* If not, then it probably doesn't belong here.
* Does it need to know something other than the set that it operates on?
* If so, then it probably doesn't belong here.
*
* This interface resembles a C++ STL bidirectional iterator. Additions should
* keep this in mind.
*
*/
goog.provide('cvox.AbstractWalker');
goog.require('cvox.CursorSelection');
goog.require('cvox.NavBraille');
/**
* @constructor
*/
cvox.AbstractWalker = function() {
};
/**
* This takes a valid CursorSelection and returns the directed-next
* valid CursorSelection in the dom, or null. For example, if the walker
* navigates across sentences, this would return the selection of the sentence
* following the selection passed in. If sel is at the "end" of a section,
* this method may return null. In the example above, if we try to next on
* the last sentence in the dom, we would return null.
* Note that sel must be a valid selection. Undefined behavior if it isn't.
* There are several invariants that must hold for any subclasses. There may
* not be explicit tests for these, but subclasses are responsible for ensuring
* them and callers may assume them:
* 1) next(next(sel).setReversed(!sel.isReversed())) = sel for all sel if sel
* is a valid CursorSelection and next(sel) != null.
* That is, the valid elements for this walker are totally ordered; going
* forward and then backward returns us to the same cell.
* 2) next(sel).isReversed() = sel.isReversed() for all sel if sel is a
* valid CursorSelection and next(sel) != null.
* That is, next preserves direction.
* @param {!cvox.CursorSelection} sel The valid selection to start moving from.
* @return {cvox.CursorSelection} Returns the valid selection the walker moves
* to. null if directed end of section is reached.
*/
cvox.AbstractWalker.prototype.next = goog.abstractMethod;
/**
* Syncs and returns the first or last valid, non-null selection in the
* this walker's linearization of the DOM.
* @param {{reversed: (undefined|boolean)}=} kwargs Extra arguments.
* reversed: If true, syncs to the end and returns a reversed selection.
* False by default.
* @return {!cvox.CursorSelection} The valid selection.
*/
cvox.AbstractWalker.prototype.begin = function(kwargs) {
kwargs = kwargs || {reversed: false};
return /** @type {!cvox.CursorSelection} */ (this.sync(
cvox.CursorSelection.fromBody().setReversed(kwargs.reversed)));
};
/**
* This takes an arbitrary CursorSelection and returns a valid CursorSelection,
* or null. For example, if the walker navigates across
* text nodes, and the selection passed in is for a single character within a
* larger text node, this method should return a text node. No restrictions
* are made as to exactly what selection should be returned, but it should be
* something "reasonable", and from the user's point of view, "close" to the
* previous selection. If no such selection exists, null may be returned.
* Note that, since CursorSelection has a direction, syncing to a selection
* should make sense in either direction.
* Note also that, as mentioned in the file overview, this operation has
* undefined behavior if the input selection is not attached to the body.
* There are several invariants that must hold for any subclasses. While they
* may not all be tested for at the time, subclasses are responsible for
* making sure these hold, and any caller may assume these to be true:
* 1) sync(sel) = sel iff sel is a valid selection
* This defines the set of valid selections for this walker.
* Note, in particular, that this implies sync(sync(sel)) = sync(sel)
* whenever sync(sel) != null.
* 2) sync(sel).isReversed() = sel.isReversed() for all sel if sync(sel) != null
* That is, sync preserves direction.
* Why do these restrictions exist? Because it makes it much easier to reason
* about the effect (and intent) of an operation if we can make these
* assumptions.
* @param {!cvox.CursorSelection} sel The (possibly unsynched) selection.
* @return {cvox.CursorSelection} The synched selection.
*/
cvox.AbstractWalker.prototype.sync = goog.abstractMethod;
/**
* Returns an array of NavDescriptions that defines what should be said
* by the tts engine on traversal from prevSel to sel. While this is
* introducing knowledge (of NavDescriptions) into this class that
* it shouldn't know, this is currently the best place for this method
* to reside, as the set of valid CursorSelections must be known.
* sel must be valid CursorSelections for this walker, prevSel may be any
* selection. Undefined behavior otherwise.
* @param {!cvox.CursorSelection} prevSel The valid previous selection.
* @param {!cvox.CursorSelection} sel The valid current selection.
* @return {!Array<!cvox.NavDescription>} The description array.
*/
cvox.AbstractWalker.prototype.getDescription = goog.abstractMethod;
/**
* Returns a NavBraille that defines what should be brailled on traversal from
* prevSel to sel.
* sel must be valid CursorSelections for this walker, prevSel may be any
* selection. Undefined behavior otherwise.
* @param {!cvox.CursorSelection} prevSel The valid previous selection.
* @param {!cvox.CursorSelection} sel The valid current selection.
* @return {!cvox.NavBraille} The braille description.
*/
cvox.AbstractWalker.prototype.getBraille = goog.abstractMethod;
/**
* Returns if this walker supports the given action.
* @param {string} name Action name.
* @return {boolean} True if action supported.
*/
cvox.AbstractWalker.prototype.hasAction = function(name) {
return typeof(this[name]) == 'function';
};
/**
* Performs an action specific to the walker.
* @param {string} name Action name.
* @param {!cvox.CursorSelection} sel The current selection.
* @return {cvox.CursorSelection} Selection after action.
*/
cvox.AbstractWalker.prototype.performAction = function(name, sel) {
if (this.hasAction(name)) {
return this[name](sel);
}
return null;
};
/**
* Returns message string of the walker's granularity.
* @return {string} The message string.
*/
cvox.AbstractWalker.prototype.getGranularityMsg = goog.abstractMethod;