Index: chrome/browser/resources/access_chromevox/common/linear_dom_walker.js |
=================================================================== |
--- chrome/browser/resources/access_chromevox/common/linear_dom_walker.js (revision 0) |
+++ chrome/browser/resources/access_chromevox/common/linear_dom_walker.js (revision 0) |
@@ -0,0 +1,317 @@ |
+// 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 A JavaScript class for walking the DOM. |
+ */ |
+ |
+ |
+goog.provide('cvox.LinearDomWalker'); |
+ |
+goog.require('cvox.DomUtil'); |
+goog.require('cvox.XpathUtil'); |
+ |
+/** |
+ * @constructor |
+ */ |
+cvox.LinearDomWalker = function() { |
+ this.currentNode = null; |
+ this.currentAncestors = new Array(); |
+ this.previousNode = null; |
+ this.useSmartNav = true; |
+}; |
+ |
+/** |
+ * @type {number} |
+ * If a node contains more characters than this, it should not be treated |
+ * as a leaf node by the smart navigation algorithm. |
+ * |
+ * This number was determined by looking at the average number of |
+ * characters in a paragraph: |
+ * http://www.fullondesign.co.uk/design/usability/ |
+ * 285-how-many-characters-per-a-page-is-normal.htm |
+ * and then trying it out on a few popular websites (CNN, BBC, |
+ * Google Search, etc.) and making sure it made sense. |
+ */ |
+cvox.LinearDomWalker.SMARTNAV_MAX_CHARCOUNT = 1500; |
+ |
+/** |
+ * @type {string} |
+ * If a node contains any of these elements, it should not be treated |
+ * as a leaf node by the smart navigation algorithm. |
+ */ |
+cvox.LinearDomWalker.SMARTNAV_BREAKOUT_XPATH = './/blockquote |' + |
+ './/button |' + |
+ './/code |' + |
+ './/form |' + |
+ './/frame |' + |
+ './/h1 |' + |
+ './/h2 |' + |
+ './/h3 |' + |
+ './/h4 |' + |
+ './/h5 |' + |
+ './/h6 |' + |
+ './/hr |' + |
+ './/iframe |' + |
+ './/input |' + |
+ './/object |' + |
+ './/ol |' + |
+ './/p |' + |
+ './/pre |' + |
+ './/select |' + |
+ './/table |' + |
+ './/tr |' + |
+ './/ul |' + |
+ // Aria widget roles |
+ './/*[@role="alert"] |' + |
+ './/*[@role="alertdialog"] |' + |
+ './/*[@role="button"] |' + |
+ './/*[@role="checkbox"] |' + |
+ './/*[@role="combobox"] |' + |
+ './/*[@role="dialog"] |' + |
+ './/*[@role="log"] |' + |
+ './/*[@role="marquee"] |' + |
+ './/*[@role="menubar"] |' + |
+ './/*[@role="progressbar"] |' + |
+ './/*[@role="radio"] |' + |
+ './/*[@role="radiogroup"] |' + |
+ './/*[@role="scrollbar"] |' + |
+ './/*[@role="slider"] |' + |
+ './/*[@role="spinbutton"] |' + |
+ './/*[@role="status"] |' + |
+ './/*[@role="tab"] |' + |
+ './/*[@role="tabpanel"] |' + |
+ './/*[@role="textbox"] |' + |
+ './/*[@role="toolbar"] |' + |
+ './/*[@role="tooltip"] |' + |
+ './/*[@role="treeitem"] |' + |
+ // Aria structure roles |
+ './/*[@role="article"] |' + |
+ './/*[@role="document"] |' + |
+ './/*[@role="group"] |' + |
+ './/*[@role="heading"] |' + |
+ './/*[@role="img"] |' + |
+ './/*[@role="list"] |' + |
+ './/*[@role="math"] |' + |
+ './/*[@role="region"] |' + |
+ './/*[@role="row"] |' + |
+ './/*[@role="separator"]'; |
+ |
+/** |
+ * Gets the currentNode for the cvox.LinearDomWalker. |
+ * @return {Node} The the current node. |
+ */ |
+cvox.LinearDomWalker.prototype.getCurrentNode = function() { |
+ return this.currentNode; |
+}; |
+ |
+/** |
+ * Sets the currentNode for the cvox.LinearDomWalker. |
+ * @param {Node} node The node that should be treated as the current node. |
+ */ |
+cvox.LinearDomWalker.prototype.setCurrentNode = function(node) { |
+ this.currentNode = node; |
+ this.currentAncestors = new Array(); |
+ var ancestor = this.currentNode; |
+ while (ancestor) { |
+ this.currentAncestors.push(ancestor); |
+ ancestor = ancestor.parentNode; |
+ } |
+ this.currentAncestors.reverse(); |
+}; |
+ |
+/** |
+ * Moves to the next node. |
+ * |
+ * @return {Node} The current node. |
+ */ |
+cvox.LinearDomWalker.prototype.next = function() { |
+ this.previousNode = this.currentNode; |
+ |
+ /* Make sure the handle to the current element is still valid (attached to the |
+ * document); if it isn't, use the cached list of ancestors to find a valid |
+ * node, then resume navigation from that point. |
+ * The current node can be invalidated by AJAX changing content. |
+ */ |
+ if (this.currentNode && |
+ !cvox.DomUtil.isAttachedToDocument(this.currentNode)) { |
+ for (var i = this.currentAncestors.length - 1, ancestor; |
+ ancestor = this.currentAncestors[i]; i--) { |
+ if (cvox.DomUtil.isAttachedToDocument(ancestor)) { |
+ this.setCurrentNode(ancestor); |
+ // Previous-Next sequence to put us back at the correct level. |
+ this.previous(); |
+ this.next(); |
+ break; |
+ } |
+ } |
+ } |
+ |
+ return this.nextContentNode(); |
+}; |
+ |
+/** |
+ * Moves to the previous node. |
+ * |
+ * @return {Node} The current node. |
+ */ |
+cvox.LinearDomWalker.prototype.previous = function() { |
+ this.previousNode = this.currentNode; |
+ |
+ /* Make sure the handle to the current element is still valid (attached to the |
+ * document); if it isn't, use the cached list of ancestors to find a valid |
+ * node, then resume navigation from that point. |
+ * The current node can be invalidated by AJAX changing content. |
+ */ |
+ if (this.currentNode && |
+ !cvox.DomUtil.isAttachedToDocument(this.currentNode)) { |
+ for (var i = this.currentAncestors.length - 1, ancestor; |
+ ancestor = this.currentAncestors[i]; i--) { |
+ if (cvox.DomUtil.isAttachedToDocument(ancestor)) { |
+ this.setCurrentNode(ancestor); |
+ // Next-previous sequence to put us back at the correct level. |
+ this.next(); |
+ this.previous(); |
+ break; |
+ } |
+ } |
+ } |
+ |
+ return this.prevContentNode(); |
+}; |
+ |
+/** |
+ * Moves to the next node. |
+ * @return {Node} The current node. |
+ */ |
+cvox.LinearDomWalker.prototype.nextNode = function() { |
+ if (!this.currentNode) { |
+ this.setCurrentNode(document.body); |
+ } else { |
+ while (this.currentNode && (!this.currentNode.nextSibling)) { |
+ this.setCurrentNode(this.currentNode.parentNode); |
+ } |
+ if (this.currentNode && this.currentNode.nextSibling) { |
+ this.setCurrentNode(this.currentNode.nextSibling); |
+ } |
+ } |
+ if (!this.currentNode) { |
+ return null; |
+ } |
+ while (!this.isLeafNode(this.currentNode)) { |
+ this.setCurrentNode(this.currentNode.firstChild); |
+ } |
+ return this.currentNode; |
+}; |
+ |
+/** |
+ * Moves to the next node that has content. |
+ * @return {Node} The current node. |
+ */ |
+cvox.LinearDomWalker.prototype.nextContentNode = function() { |
+ this.nextNode(); |
+ while (this.currentNode && !cvox.DomUtil.hasContent(this.currentNode)) { |
+ this.nextNode(); |
+ } |
+ return this.currentNode; |
+}; |
+ |
+/** |
+ * Moves to the previous node. |
+ * @return {Node} The current node. |
+ */ |
+cvox.LinearDomWalker.prototype.prevNode = function() { |
+ if (!this.currentNode) { |
+ this.setCurrentNode(document.body); |
+ } else { |
+ while (this.currentNode && (!this.currentNode.previousSibling)) { |
+ this.setCurrentNode(this.currentNode.parentNode); |
+ } |
+ if (this.currentNode && this.currentNode.previousSibling) { |
+ this.setCurrentNode(this.currentNode.previousSibling); |
+ } |
+ } |
+ if (!this.currentNode) { |
+ return null; |
+ } |
+ while (!this.isLeafNode(this.currentNode)) { |
+ this.setCurrentNode(this.currentNode.lastChild); |
+ } |
+ return this.currentNode; |
+}; |
+ |
+/** |
+ * Moves to the previous node that has content. |
+ * @return {Node} The current node. |
+ */ |
+cvox.LinearDomWalker.prototype.prevContentNode = function() { |
+ this.prevNode(); |
+ while (this.currentNode && !cvox.DomUtil.hasContent(this.currentNode)) { |
+ this.prevNode(); |
+ } |
+ return this.currentNode; |
+}; |
+ |
+/** |
+ * Returns an array of ancestors that are unique for the current node when |
+ * compared to the previous node. Having such an array is useful in generating |
+ * the node information (identifying when interesting node boundaries have been |
+ * crossed, etc.). |
+ * |
+ * @return {Array.<Node>} An array of unique ancestors for the current node. |
+ */ |
+cvox.LinearDomWalker.prototype.getUniqueAncestors = function() { |
+ return cvox.DomUtil.getUniqueAncestors(this.previousNode, |
+ this.currentNode); |
+}; |
+ |
+/** |
+ * Checks if Smart Nav is enabled. |
+ * @return {boolean} Whether Smart Nav is enabled. |
+ */ |
+cvox.LinearDomWalker.prototype.getSmartNavEnabled = function() { |
+ return this.useSmartNav; |
+}; |
+ |
+/** |
+ * Enables/disables Smart Nav. |
+ * @param {boolean} enableSmartNav - Whether Smart Nav should be enabled. |
+ */ |
+cvox.LinearDomWalker.prototype.setSmartNavEnabled = function(enableSmartNav) { |
+ this.useSmartNav = enableSmartNav; |
+}; |
+ |
+ |
+/** |
+ * Determines if the a node should be treated as a leaf node. |
+ * Based on DomUtil.isLeafNode - if SmartNav is enabled, then a few additional |
+ * heuristics will be applied to determine if a node can be treated as a leaf |
+ * node for smoother reading. |
+ * @param {Node} targetNode to check. |
+ * @return {boolean} True if targetNode can be considered a leaf node. |
+ */ |
+cvox.LinearDomWalker.prototype.isLeafNode = function(targetNode) { |
+ if (cvox.DomUtil.isLeafNode(targetNode)) { |
+ return true; |
+ } |
+ if (!this.useSmartNav) { |
+ return false; |
+ } |
+ var content = cvox.DomUtil.getText(targetNode); |
+ if (content.length > cvox.LinearDomWalker.SMARTNAV_MAX_CHARCOUNT) { |
+ return false; |
+ } |
+ if (content.replace(/\s/g, '') === '') { |
+ // Text only contains whitespace |
+ return false; |
+ } |
+ var breakingNodes = cvox.XpathUtil.evalXPath( |
+ cvox.LinearDomWalker.SMARTNAV_BREAKOUT_XPATH, targetNode); |
+ for (var i = 0, node; node = breakingNodes[i]; i++) { |
+ if (cvox.DomUtil.hasContent(node)) { |
+ return false; |
+ } |
+ } |
+ return true; |
+}; |
Property changes on: chrome/browser/resources/access_chromevox/common/linear_dom_walker.js |
___________________________________________________________________ |
Added: svn:executable |
+ * |
Added: svn:eol-style |
+ LF |