Index: chrome/browser/resources/access_chromevox/chromevox/injected/navigation_manager.js |
=================================================================== |
--- chrome/browser/resources/access_chromevox/chromevox/injected/navigation_manager.js (revision 0) |
+++ chrome/browser/resources/access_chromevox/chromevox/injected/navigation_manager.js (revision 0) |
@@ -0,0 +1,516 @@ |
+// Copyright (c) 2011 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 Manages navigation within a page. |
+ * This unifies navigation by the DOM walker and by WebKit selection. |
+ */ |
+ |
+goog.provide('cvox.ChromeVoxNavigationManager'); |
+ |
+goog.require('cvox.ChromeVoxChoiceWidget'); |
+goog.require('cvox.DomUtil'); |
+goog.require('cvox.LinearDomWalker'); |
+goog.require('cvox.SelectionUtil'); |
+goog.require('cvox.SelectionWalker'); |
+ |
+/** |
+ * @constructor |
+ */ |
+cvox.ChromeVoxNavigationManager = function() { |
+ this.currentNode = null; |
+ this.nodeInformationArray = new Array(); |
+ this.currentNavStrategy = 2; |
+ this.lastUsedNavStrategy = 2; |
+ this.linearDomWalker = new cvox.LinearDomWalker(); |
+ this.selectionWalker = new cvox.SelectionWalker(); |
+ this.customWalker = null; |
+ this.selectionUniqueAncestors = []; |
+ this.choiceWidget = new cvox.ChromeVoxChoiceWidget(); |
+}; |
+ |
+/** |
+ * @type {Object.<string, number>} |
+ */ |
+cvox.ChromeVoxNavigationManager.STRATEGIES = |
+ {'SELECTION' : 0, 'LINEARDOM' : 1, 'SMART' : 2, 'CUSTOM' : 3}; |
+ |
+/** |
+ * @type {Array.<string>} |
+ */ |
+cvox.ChromeVoxNavigationManager.STRATEGY_NAMES = |
+ ['SELECTION', 'OBJECT', 'GROUP', 'CUSTOM']; |
+ |
+/** |
+ * Moves forward using the current navigation strategy. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.next = function() { |
+ switch (this.currentNavStrategy) { |
+ default: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.CUSTOM: |
+ this.customWalker.next(); |
+ break; |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SMART: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM: |
+ var node = this.linearDomWalker.next(); |
+ if (node) { |
+ cvox.SelectionUtil.selectAllTextInNode(node); |
+ this.currentNode = node; |
+ } |
+ break; |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SELECTION: |
+ this.selectionUniqueAncestors = []; |
+ var movedOk = this.selectionWalker.next(); |
+ if (!movedOk) { |
+ var selectionNode = this.linearDomWalker.next(); |
+ this.selectionUniqueAncestors = |
+ this.linearDomWalker.getUniqueAncestors(); |
+ if (selectionNode) { |
+ this.currentNode = selectionNode; |
+ this.selectionWalker.setCurrentNode(this.currentNode); |
+ this.selectionWalker.next(); |
+ } |
+ } |
+ break; |
+ } |
+}; |
+ |
+/** |
+ * Moves backward using the current navigation strategy. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.previous = function() { |
+ switch (this.currentNavStrategy) { |
+ default: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.CUSTOM: |
+ this.customWalker.previous(); |
+ break; |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SMART: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM: |
+ var node = this.linearDomWalker.previous(); |
+ if (node) { |
+ cvox.SelectionUtil.selectAllTextInNode(node); |
+ this.currentNode = node; |
+ } |
+ break; |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SELECTION: |
+ this.selectionUniqueAncestors = []; |
+ var movedOk = this.selectionWalker.previous(); |
+ if (!movedOk) { |
+ var selectionNode = this.linearDomWalker.previous(); |
+ this.selectionUniqueAncestors = |
+ this.linearDomWalker.getUniqueAncestors(); |
+ if (selectionNode) { |
+ this.currentNode = selectionNode; |
+ this.selectionWalker.setCurrentNode(this.currentNode); |
+ cvox.SelectionUtil.selectAllTextInNode(this.currentNode); |
+ window.getSelection().collapseToEnd(); |
+ this.selectionWalker.previous(); |
+ } |
+ } |
+ break; |
+ } |
+}; |
+ |
+/** |
+ * Moves up a level of granularity. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.up = function() { |
+ switch (this.currentNavStrategy) { |
+ default: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.CUSTOM: |
+ break; |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SMART: |
+ if (!!this.customWalker) { |
+ this.lastUsedNavStrategy = this.currentNavStrategy; |
+ this.currentNavStrategy = 3; |
+ this.customWalker.setCurrentNode(this.currentNode); |
+ this.customWalker.goToCurrentItem(); |
+ } |
+ break; |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM: |
+ this.lastUsedNavStrategy = this.currentNavStrategy; |
+ this.currentNavStrategy = 2; |
+ this.linearDomWalker.setSmartNavEnabled(true); |
+ var node = this.currentNode; |
+ while (this.linearDomWalker.isLeafNode(node)) { |
+ this.currentNode = node; |
+ node = node.parentNode; |
+ } |
+ this.linearDomWalker.setCurrentNode(this.currentNode); |
+ if (this.currentNode !== null) { |
+ this.previous(); |
+ } |
+ this.next(); |
+ break; |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SELECTION: |
+ var changed = this.selectionWalker.lessGranular(); |
+ if (!changed) { |
+ this.lastUsedNavStrategy = this.currentNavStrategy; |
+ this.currentNavStrategy = 1; |
+ cvox.SelectionUtil.selectAllTextInNode(this.currentNode); |
+ } else { |
+ this.selectionWalker.previous(); |
+ this.selectionWalker.next(); |
+ } |
+ break; |
+ } |
+}; |
+ |
+ |
+/** |
+ * Moves down a level of granularity. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.down = function() { |
+ switch (this.currentNavStrategy) { |
+ default: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.CUSTOM: |
+ this.lastUsedNavStrategy = this.currentNavStrategy; |
+ this.currentNavStrategy = 2; |
+ this.linearDomWalker.setSmartNavEnabled(true); |
+ if (this.customWalker.getCurrentNode() != null) { |
+ this.currentNode = this.customWalker.getCurrentNode(); |
+ this.linearDomWalker.setCurrentNode(this.currentNode); |
+ } |
+ break; |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SMART: |
+ this.lastUsedNavStrategy = this.currentNavStrategy; |
+ this.currentNavStrategy = 1; |
+ this.linearDomWalker.setSmartNavEnabled(false); |
+ if (this.currentNode !== null) { |
+ this.previous(); |
+ } |
+ this.next(); |
+ break; |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM: |
+ this.lastUsedNavStrategy = this.currentNavStrategy; |
+ this.currentNavStrategy = 0; |
+ this.selectionWalker.setCurrentNode(this.currentNode); |
+ this.selectionWalker.next(); |
+ break; |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SELECTION: |
+ var changed = this.selectionWalker.moreGranular(); |
+ if (changed) { |
+ this.selectionWalker.previous(); |
+ this.selectionWalker.next(); |
+ } |
+ break; |
+ } |
+}; |
+ |
+/** |
+ * Moves to the next occurrence of a node that matches the given predicate, |
+ * if one exists, using the linearDomWalker. |
+ * @param {function(Array.<Node>)} predicate A function taking an array |
+ * of unique ancestor nodes as a parameter and returning true if it's |
+ * what to search for. |
+ * @return {boolean} True if a match was found. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.findNext = function(predicate) { |
+ this.syncPosition(); |
+ var node = undefined; |
+ while (true) { |
+ node = this.linearDomWalker.next(); |
+ if (!node) { |
+ break; |
+ } |
+ |
+ if (predicate(this.linearDomWalker.getUniqueAncestors())) { |
+ break; |
+ } |
+ } |
+ |
+ if (node) { |
+ cvox.SelectionUtil.selectAllTextInNode(node); |
+ this.currentNode = node; |
+ return true; |
+ } |
+ |
+ return false; |
+}; |
+ |
+/** |
+ * Moves to the previous occurrence of a node that matches the given predicate, |
+ * if one exists, using the linearDomWalker. |
+ * @param {function(Array.<Node>)} predicate A function taking an array |
+ * of unique ancestor nodes as a parameter and returning true if it's |
+ * what to search for. |
+ * @return {boolean} True if a match was found. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.findPrevious = function(predicate) { |
+ this.syncPosition(); |
+ var node = undefined; |
+ while (true) { |
+ node = this.linearDomWalker.previous(); |
+ if (!node) { |
+ break; |
+ } |
+ |
+ if (predicate(this.linearDomWalker.getUniqueAncestors())) { |
+ break; |
+ } |
+ } |
+ |
+ if (node) { |
+ cvox.SelectionUtil.selectAllTextInNode(node); |
+ this.currentNode = node; |
+ return true; |
+ } |
+ |
+ return false; |
+}; |
+ |
+/** |
+ * Returns the current navigation strategy. |
+ * |
+ * @return {string} The strategy that is being used. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.getStrategy = function() { |
+ return cvox.ChromeVoxNavigationManager.STRATEGY_NAMES[ |
+ this.currentNavStrategy]; |
+}; |
+ |
+/** |
+ * Returns the current selection granularity. |
+ * |
+ * @return {string} The selection granularity that is being used. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.getGranularity = function() { |
+ return this.selectionWalker.getGranularity(); |
+}; |
+ |
+/** |
+ * Synchronizes the current position between the different navigation |
+ * strategies. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.syncPosition = function() { |
+ if (!this.currentNode) { |
+ this.currentNode = document.body; |
+ } |
+ if (this.currentNavStrategy != this.lastUsedNavStrategy) { |
+ if ((this.lastUsedNavStrategy == |
+ cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM) || |
+ (this.lastUsedNavStrategy == |
+ cvox.ChromeVoxNavigationManager.STRATEGIES.SMART)) { |
+ this.syncToNode(this.currentNode); |
+ } else { |
+ this.syncToSelection(); |
+ } |
+ } |
+ this.lastUsedNavStrategy = this.currentNavStrategy; |
+}; |
+ |
+/** |
+ * Synchronizes the navigation strategies to the current selection. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.syncToSelection = function() { |
+ if (window.getSelection() && window.getSelection().anchorNode) { |
+ this.currentNode = window.getSelection().anchorNode; |
+ this.linearDomWalker.setCurrentNode(this.currentNode); |
+ } |
+}; |
+ |
+/** |
+ * Synchronizes the navigation strategies to the targetNode. |
+ * |
+ * @param {Node} targetNode The node that the navigation strategies should be |
+ * synced to. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.syncToNode = function(targetNode) { |
+ if (cvox.DomUtil.isDescendantOfNode(this.currentNode, targetNode)) { |
+ // User is already synced at a more specific level than the target; |
+ // therefore ignore the sync request. |
+ return; |
+ } |
+ this.currentNode = targetNode; |
+ this.linearDomWalker.setCurrentNode(targetNode); |
+ var range = document.createRange(); |
+ range.selectNode(this.currentNode); |
+ window.getSelection().removeAllRanges(); |
+ window.getSelection().addRange(range); |
+ window.getSelection().collapseToStart(); |
+}; |
+ |
+/** |
+ * Returns only the text content for the current position. |
+ * |
+ * @return {string} The current text content. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.getCurrentContent = function() { |
+ switch (this.currentNavStrategy) { |
+ default: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.CUSTOM: |
+ return this.customWalker.getCurrentContent(); |
+ break; |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SMART: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM: |
+ return cvox.DomUtil.getText(this.currentNode); |
+ break; |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SELECTION: |
+ return this.selectionWalker.getCurrentContent(); |
+ break; |
+ } |
+}; |
+ |
+/** |
+ * Returns a complete description of the current position, including |
+ * the text content and annotations such as "link", "button", etc. |
+ * |
+ * @return {string} The summary of the current position. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.getCurrentDescription = function() { |
+ switch (this.currentNavStrategy) { |
+ default: |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.CUSTOM: |
+ return this.customWalker.getCurrentDescription(); |
+ break; |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SMART: |
+ // Use a linear DOM walker in non-smart mode to traverse all of the |
+ // nodes inside the current smart node and append all of their |
+ // descriptions. |
+ var description = ''; |
+ var walker = new cvox.LinearDomWalker(); |
+ walker.currentNode = this.linearDomWalker.currentNode; |
+ walker.useSmartNav = false; |
+ walker.previous(); |
+ walker.next(); |
+ while (cvox.DomUtil.isDescendantOfNode( |
+ walker.currentNode, this.linearDomWalker.currentNode)) { |
+ description = description + ' ' + |
+ cvox.DomUtil.getText(walker.currentNode) + ' ' + |
+ cvox.DomUtil.getInformationFromAncestors( |
+ walker.getUniqueAncestors()); |
+ walker.next(); |
+ } |
+ return description; |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM: |
+ return this.getCurrentContent() + ' ' + |
+ cvox.DomUtil.getInformationFromAncestors(this.getChangedAncestors()); |
+ |
+ case cvox.ChromeVoxNavigationManager.STRATEGIES.SELECTION: |
+ return this.getCurrentContent() + ' ' + |
+ cvox.DomUtil.getInformationFromAncestors( |
+ this.selectionUniqueAncestors); |
+ } |
+}; |
+ |
+/** |
+ * Returns an array of ancestor nodes that have been changed between the |
+ * previous position and the current current position. |
+ * |
+ * @return {Array.<Node>} The current content. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.getChangedAncestors = function() { |
+ return this.linearDomWalker.getUniqueAncestors(); |
+}; |
+ |
+/** |
+ * Sets the browser's focus to the current node. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.setFocus = function() { |
+ cvox.DomUtil.setFocus(this.linearDomWalker.getCurrentNode()); |
+}; |
+ |
+/** |
+ * Acts on the current item and displays a disambiguation dialog |
+ * if more than one action is possible. |
+ * |
+ * @return {boolean} True if an action was taken. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.actOnCurrentItem = function() { |
+ if (this.currentNavStrategy == |
+ cvox.ChromeVoxNavigationManager.STRATEGIES.CUSTOM) { |
+ return this.customWalker.actOnCurrentItem(); |
+ } else if (this.currentNavStrategy == |
+ cvox.ChromeVoxNavigationManager.STRATEGIES.SMART) { |
+ if (this.currentNode && this.currentNode.tagName && |
+ (this.currentNode.tagName == 'A')) { |
+ cvox.DomUtil.clickElem(this.currentNode, false); |
+ return true; |
+ } else { |
+ var aNodes = this.currentNode.getElementsByTagName('A'); |
+ if (aNodes.length == 1) { |
+ cvox.DomUtil.clickElem(aNodes[0], false); |
+ return true; |
+ } else if (aNodes.length > 1) { |
+ var descriptions = new Array(); |
+ var functions = new Array(); |
+ for (var i = 0, link; link = aNodes[i]; i++) { |
+ if (cvox.DomUtil.hasContent(link)) { |
+ descriptions.push(cvox.DomUtil.getText(link)); |
+ functions.push(cvox.ChromeVoxNavigationManager |
+ .createSimpleClickFunction(link)); |
+ } |
+ } |
+ this.choiceWidget.show(descriptions, functions, |
+ descriptions.toString()); |
+ return true; |
+ } |
+ } |
+ } |
+ return false; |
+}; |
+ |
+/** |
+ * Checks if the navigation manager is able to act on the current item. |
+ * |
+ * @return {boolean} True if some action is possible. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.canActOnCurrentItem = function() { |
+ if (this.currentNavStrategy == |
+ cvox.ChromeVoxNavigationManager.STRATEGIES.CUSTOM) { |
+ return this.customWalker.canActOnCurrentItem(); |
+ } |
+ if (this.currentNavStrategy == |
+ cvox.ChromeVoxNavigationManager.STRATEGIES.SMART) { |
+ if (this.currentNode && this.currentNode.tagName && |
+ (this.currentNode.tagName == 'A')) { |
+ return true; |
+ } else { |
+ var aNodes = this.currentNode.getElementsByTagName('A'); |
+ if (aNodes.length > 0) { |
+ return true; |
+ } |
+ } |
+ } |
+ // Anything that is DOM level or lower will always be handled by the browser. |
+ return false; |
+}; |
+ |
+/** |
+ * Creates a simple function that will click on the given targetNode when |
+ * invoked. |
+ * Note that we are using this function because functions created inside a loop |
+ * have to be created by another function and not within the loop directly. |
+ * |
+ * See: http://joust.kano.net/weblog/archive/2005/08/08/ |
+ * a-huge-gotcha-with-javascript-closures/ |
+ * @param {Node} targetNode The target node to click on. |
+ * @return {function()} A function that will click on the given targetNode. |
+ */ |
+cvox.ChromeVoxNavigationManager.createSimpleClickFunction = function( |
+ targetNode) { |
+ var target = targetNode.cloneNode(true); |
+ return function() { cvox.DomUtil.clickElem(target, false); }; |
+}; |
+ |
+/** |
+ * Sets the custom walker to use for the current site. |
+ * |
+ * @param {Object} customWalkerObj The custom walker to use. |
+ */ |
+cvox.ChromeVoxNavigationManager.prototype.setCustomWalker = |
+ function(customWalkerObj) { |
+ this.customWalker = customWalkerObj; |
+ this.currentNavStrategy = 3; |
+ this.lastUsedNavStrategy = 3; |
+}; |
+ |
Property changes on: chrome/browser/resources/access_chromevox/chromevox/injected/navigation_manager.js |
___________________________________________________________________ |
Added: svn:executable |
+ * |
Added: svn:eol-style |
+ LF |