Index: chrome/browser/resources/access_chromevox/common/dom_util.js |
=================================================================== |
--- chrome/browser/resources/access_chromevox/common/dom_util.js (revision 0) |
+++ chrome/browser/resources/access_chromevox/common/dom_util.js (revision 0) |
@@ -0,0 +1,837 @@ |
+// 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 collection of JavaScript utilities used to simplify working |
+ * with the DOM. |
+ */ |
+ |
+ |
+goog.provide('cvox.DomUtil'); |
+ |
+goog.require('cvox.AriaUtil'); |
+goog.require('cvox.XpathUtil'); |
+ |
+/** |
+ * Create the namespace |
+ * @constructor |
+ */ |
+cvox.DomUtil = function() { |
+}; |
+ |
+ |
+/** |
+ * @type {Object} |
+ */ |
+cvox.DomUtil.INPUT_TYPE_TO_INFORMATION_TABLE = { |
+ 'button' : 'Button', |
+ 'checkbox' : 'Check box', |
+ 'color' : 'Color picker', |
+ 'datetime' : 'Date time control', |
+ 'datetime-local' : 'Date time control', |
+ 'date' : 'Date control', |
+ 'email' : 'Edit text for email', |
+ 'file' : 'File selection', |
+ 'hidden' : '', |
+ 'image' : 'Button', |
+ 'month' : 'Month control', |
+ 'number' : 'Edit text numeric only', |
+ 'password' : 'Password edit text', |
+ 'radio' : 'Radio button', |
+ 'range' : 'Slider', |
+ 'reset' : 'Reset', |
+ 'search' : 'Edit text for search', |
+ 'submit' : 'Button', |
+ 'tel' : 'Edit text for telephone number', |
+ 'text' : 'Edit text', |
+ 'url' : 'Edit text for URL', |
+ 'week' : 'Week of the year control' |
+}; |
+/** |
+ * @type {Object} |
+ */ |
+cvox.DomUtil.TAG_TO_INFORMATION_TABLE = { |
+ 'A' : 'Link', |
+ 'H1' : 'Heading 1', |
+ 'H2' : 'Heading 2', |
+ 'H3' : 'Heading 3', |
+ 'H4' : 'Heading 4', |
+ 'H5' : 'Heading 5', |
+ 'H6' : 'Heading 6', |
+ 'BUTTON' : 'Button', |
+ 'SELECT' : 'Combo box', |
+ 'TEXTAREA' : 'Text area' |
+}; |
+ |
+ |
+/** |
+ * Determines whether or not a style is invisible according to any CSS |
+ * criteria that can hide a node. |
+ * |
+ * @param {Object} style An object's style. |
+ * @return {boolean} True if the style is invisible. |
+ */ |
+cvox.DomUtil.isInvisibleStyle = function(style) { |
+ if (!style) { |
+ return false; |
+ } |
+ if (style.display == 'none') { |
+ return true; |
+ } |
+ if (style.visibility == 'hidden') { |
+ return true; |
+ } |
+ if (style.opacity == 0) { |
+ return true; |
+ } |
+ return false; |
+}; |
+ |
+ |
+/** |
+ * Determines whether or not a node is a leaf node. |
+ * |
+ * @param {Node} node The node to be checked. |
+ * @return {boolean} True if the node is a leaf node. |
+ */ |
+cvox.DomUtil.isLeafNode = function(node) { |
+ // TODO (clchen): account for widgets that should be treated as leaf nodes. |
+ if (!node.firstChild) { |
+ return true; |
+ } |
+ // Think of hidden nodes as spacer nodes; leaf node with no content. |
+ if (node.nodeType == 1) { // nodeType:1 == ELEMENT_NODE |
+ var style = document.defaultView.getComputedStyle(node, null); |
+ if (cvox.DomUtil.isInvisibleStyle(style)) { |
+ return true; |
+ } |
+ } |
+ if (cvox.AriaUtil.isHidden(node)) { |
+ return true; |
+ } |
+ if (node.tagName) { |
+ if (node.tagName == 'SELECT') { |
+ return true; |
+ } |
+ if (node.tagName == 'TEXTAREA') { |
+ return true; |
+ } |
+ if (node.tagName == 'LABEL') { |
+ return true; |
+ } |
+ } |
+ if (cvox.DomUtil.getTitle(node)) { |
+ return true; |
+ } |
+ return false; |
+}; |
+ |
+ |
+/** |
+ * Determines whether or not a node is or is the descendant of a node |
+ * with a particular tag or class name. |
+ * |
+ * @param {Node} node The node to be checked. |
+ * @param {?string} tagName The tag to check for, or null if the tag |
+ * doesn't matter. |
+ * @param {?string=} className The class to check for, or null if the class |
+ * doesn't matter. |
+ * @return {boolean} True if the node or one of its ancestor has the specified |
+ * tag. |
+ */ |
+cvox.DomUtil.isDescendantOf = function(node, tagName, className) { |
+ while (node) { |
+ |
+ if (tagName && className && |
+ (node.tagName && (node.tagName == tagName)) && |
+ (node.className && (node.className == className))) { |
+ return true; |
+ } else if (tagName && !className && |
+ (node.tagName && (node.tagName == tagName))) { |
+ return true; |
+ } else if (!tagName && className && |
+ (node.className && (node.className == className))) { |
+ return true; |
+ } |
+ node = node.parentNode; |
+ } |
+ return false; |
+}; |
+ |
+ |
+/** |
+ * Determines whether or not a node is or is the descendant of another node. |
+ * |
+ * @param {Object} node The node to be checked. |
+ * @param {Object} ancestor The node to see if it's a descendant of. |
+ * @return {boolean} True if the node is ancestor or is a descendant of it. |
+ */ |
+cvox.DomUtil.isDescendantOfNode = function(node, ancestor) { |
+ while (node && ancestor) { |
+ if (node == ancestor) { |
+ return true; |
+ } |
+ node = node.parentNode; |
+ } |
+ return false; |
+}; |
+ |
+/** |
+ * Get the label of a node. |
+ * |
+ * Not recursive. |
+ * |
+ * @param {Node} node The node to get the title from. |
+ * @param {boolean} useHeuristics Whether or not to use heuristics to guess at |
+ * the label if one is not explicitly set in the DOM. |
+ * @return {string} The label of the node. |
+ */ |
+cvox.DomUtil.getLabel = function(node, useHeuristics) { |
+ var label = ''; |
+ if (!node) { |
+ return ''; |
+ } |
+ // Find any labels that are associated with this text control. |
+ // aria-labelledby takes precedence and overrides any label for= elements. |
+ if (node.hasAttribute && node.hasAttribute('aria-labelledby')) { |
+ var labelNodeIds = node.getAttribute('aria-labelledby').split(' '); |
+ for (var labelNodeId, i = 0; labelNodeId = labelNodeIds[i]; i++) { |
+ var labelNode = document.getElementById(labelNodeId); |
+ label += cvox.DomUtil.getText(labelNode) + ' '; |
+ } |
+ } else if (node && node.id) { |
+ var labels = cvox.XpathUtil.evalXPath('//label[@for="' + |
+ node.id + '"]', document.body); |
+ if (labels.length > 0) { |
+ label += cvox.DomUtil.getText(labels[0]) + ' '; |
+ } |
+ } |
+ |
+ // If no description has been found yet and heuristics are enabled, |
+ // then try getting the content from the previous node. |
+ if (useHeuristics && (label.length < 1)) { |
+ var tempNode = cvox.DomUtil.previousLeafNode(node); |
+ while (tempNode && !cvox.DomUtil.hasContent(tempNode)) { |
+ tempNode = cvox.DomUtil.previousLeafNode(tempNode); |
+ } |
+ if (tempNode) { |
+ if (!cvox.DomUtil.isControl(tempNode)) { |
+ label += cvox.DomUtil.getText(tempNode) + ' '; |
+ } |
+ } |
+ } |
+ |
+ return label; |
+}; |
+ |
+/** |
+ * Get the title of a node. In many cases this is equivalent to the |
+ * text of the node, but it can be overridden by a title tag or alt tag, |
+ * and for some form controls (like submit buttons) the title is actually |
+ * the value. |
+ * |
+ * Not recursive. |
+ * |
+ * @param {Node} node The node to get the title from. |
+ * @return {string} The title of the node. |
+ */ |
+cvox.DomUtil.getTitle = function(node) { |
+ if (node.constructor == Text) { |
+ return node.data; |
+ } else if (node.constructor == HTMLImageElement) { |
+ return cvox.DomUtil.getImageTitle(node); |
+ } else if (node.hasAttribute && node.hasAttribute('title')) { |
+ return node.getAttribute('title'); |
+ } else if (node.constructor == HTMLInputElement) { |
+ if (node.type == 'image') { |
+ return cvox.DomUtil.getImageTitle(node); |
+ } else if (node.type == 'submit') { |
+ if (node.hasAttribute && node.hasAttribute('value')) { |
+ return node.getAttribute('value'); |
+ } else { |
+ return 'Submit'; |
+ } |
+ } else if (node.type == 'reset') { |
+ if (node.hasAttribute && node.hasAttribute('value')) { |
+ return node.getAttribute('value'); |
+ } else { |
+ return 'Reset'; |
+ } |
+ } |
+ } |
+ return ''; |
+}; |
+ |
+/** |
+ * Get the text value of a node: the selected value of a select control or the |
+ * current text of a text control. Does not return the state of a checkbox |
+ * or radio button. |
+ * |
+ * Not recursive. |
+ * |
+ * @param {Node} node The node to get the value from. |
+ * @return {String} The value of the node. |
+ */ |
+cvox.DomUtil.getValue = function(node) { |
+ if (node.constructor == HTMLSelectElement) { |
+ if (node.selectedIndex >= 0 && |
+ node.selectedIndex < node.options.length) { |
+ return node.options[node.selectedIndex].text + ''; |
+ } else { |
+ return ''; |
+ } |
+ } |
+ |
+ if (node.constructor == HTMLTextAreaElement) { |
+ return node.value; |
+ } |
+ |
+ if (node.constructor == HTMLInputElement) { |
+ switch (node.type) { |
+ // Returning '' for the submit button since it is covered by getText. |
+ case 'hidden': |
+ case 'image': |
+ case 'submit': |
+ case 'reset': |
+ case 'checkbox': |
+ case 'radio': |
+ return ''; |
+ case 'password': |
+ return node.value.replace(/./g, '*'); |
+ default: |
+ return node.value; |
+ } |
+ } |
+ |
+ return ''; |
+}; |
+ |
+ |
+/** |
+ * Given a node, return its complete text as a string. This is recursive; |
+ * it will extract the text from all child nodes and concatenate it, |
+ * removing extraneous whitespace. |
+ * |
+ * This is similar to accessing the textContent property of a node, but |
+ * that doesn't handle nodes that override the text with a title attribute, |
+ * or nodes that can have both a title and a value. |
+ * |
+ * This function concatenates the value and title of a node (value first, |
+ * if both are present) and recursively concatenates the text of children |
+ * of any node that doesn't have either a value or title. |
+ |
+ * @param {Node} node The node to extract the text from. |
+ * @return {string} The text of the node. |
+ */ |
+cvox.DomUtil.getText = function(node) { |
+ var title = cvox.DomUtil.getTitle(node); |
+ var value = cvox.DomUtil.getValue(node); |
+ var text = ''; |
+ if (title && value) { |
+ text = value + ' ' + title; |
+ } else if (title) { |
+ text = title; |
+ } else if (value) { |
+ text = value; |
+ } else if (!cvox.DomUtil.isControl(node)) { |
+ for (var i = 0; i < node.childNodes.length; i++) { |
+ var child = node.childNodes[i]; |
+ var childStyle = window.getComputedStyle(child, null); |
+ if (!cvox.DomUtil.isInvisibleStyle(childStyle) && |
+ !cvox.AriaUtil.isHidden(node)) { |
+ text += ' ' + cvox.DomUtil.getText(child); |
+ } |
+ } |
+ } |
+ // Remove all whitespace from the beginning and end, and collapse all |
+ // inner strings of whitespace to a single space. |
+ text = text.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, ''); |
+ |
+ return text; |
+}; |
+ |
+ |
+/** |
+ * Given an image node, return its title as a string. The preferred title |
+ * is always the alt text, and if that's not available, then the title |
+ * attribute. If neither of those are available, it attempts to construct |
+ * a title from the filename, and if all else fails returns the word Image. |
+ * @param {Node} node The image node. |
+ * @return {string} The title of the image. |
+ */ |
+cvox.DomUtil.getImageTitle = function(node) { |
+ var text; |
+ if (node.hasAttribute('alt')) { |
+ text = node.alt; |
+ } else if (node.hasAttribute('title')) { |
+ text = node.title; |
+ } else { |
+ var url = node.src; |
+ if (url.substring(0, 4) != 'data') { |
+ var filename = url.substring( |
+ url.lastIndexOf('/') + 1, url.lastIndexOf('.')); |
+ |
+ // Hack to not speak the filename if it's ridiculously long. |
+ if (filename.length >= 1 && filename.length <= 16) { |
+ text = filename + ' Image'; |
+ } else { |
+ text = 'Image'; |
+ } |
+ } else { |
+ text = 'Image'; |
+ } |
+ } |
+ return text; |
+}; |
+ |
+ |
+/** |
+ * Determines whether or not a node has content. |
+ * |
+ * @param {Node} node The node to be checked. |
+ * @return {boolean} True if the node has content. |
+ */ |
+cvox.DomUtil.hasContent = function(node) { |
+ // nodeType:8 == COMMENT_NODE |
+ if (node.nodeType == 8) { |
+ return false; |
+ } |
+ |
+ // Exclude anything in the head |
+ if (cvox.DomUtil.isDescendantOf(node, 'HEAD')) { |
+ return false; |
+ } |
+ |
+ // Exclude script nodes |
+ if (cvox.DomUtil.isDescendantOf(node, 'SCRIPT')) { |
+ return false; |
+ } |
+ |
+ // Exclude noscript nodes |
+ if (cvox.DomUtil.isDescendantOf(node, 'NOSCRIPT')) { |
+ return false; |
+ } |
+ |
+ // Exclude style nodes that have been dumped into the body |
+ if (cvox.DomUtil.isDescendantOf(node, 'STYLE')) { |
+ return false; |
+ } |
+ |
+ // Check the style to exclude undisplayed/hidden nodes |
+ var closestStyledParent = node; |
+ // nodeType:3 == TEXT_NODE |
+ while (closestStyledParent && (closestStyledParent.nodeType == 3)) { |
+ closestStyledParent = closestStyledParent.parentNode; |
+ } |
+ if (closestStyledParent) { |
+ var style = |
+ document.defaultView.getComputedStyle(closestStyledParent, null); |
+ if (cvox.DomUtil.isInvisibleStyle(style)) { |
+ return false; |
+ } |
+ // TODO (clchen, raman): Look into why WebKit has a problem here. |
+ // The issue is that getComputedStyle does not always return the correct |
+ // result; manually going up the parent chain sometimes produces a different |
+ // result than just using getComputedStyle. |
+ var tempNode = closestStyledParent; |
+ while (tempNode && tempNode.tagName != 'BODY') { |
+ style = document.defaultView.getComputedStyle(tempNode, null); |
+ if (cvox.DomUtil.isInvisibleStyle(style)) { |
+ return false; |
+ } |
+ tempNode = tempNode.parentNode; |
+ } |
+ } |
+ |
+ // Ignore anything that is hidden by ARIA |
+ if (cvox.AriaUtil.isHidden(node)) { |
+ return false; |
+ } |
+ |
+ // We need to speak controls, including those with no value entered. We |
+ // therefore treat visible controls as if they had content, and return true |
+ // below. |
+ if (cvox.DomUtil.isControl(node)) { |
+ return true; |
+ } |
+ |
+ var text = cvox.DomUtil.getText(node); |
+ if (text === '') { |
+ // Text only contains whitespace |
+ return false; |
+ } |
+ |
+ return true; |
+}; |
+ |
+/** |
+ * Returns a list of all the ancestors of a given node. |
+ * |
+ * @param {Object} targetNode The node to get ancestors for. |
+ * @return {Object} An array of ancestors for the targetNode. |
+ */ |
+cvox.DomUtil.getAncestors = function(targetNode) { |
+ var ancestors = new Array(); |
+ while (targetNode) { |
+ ancestors.push(targetNode); |
+ targetNode = targetNode.parentNode; |
+ } |
+ ancestors.reverse(); |
+ while (ancestors.length && !ancestors[0].tagName && !ancestors[0].nodeValue) { |
+ ancestors.shift(); |
+ } |
+ return ancestors; |
+}; |
+ |
+/** |
+ * Compares Ancestors of A with Ancestors of B and returns |
+ * the index value in B at which B diverges from A. |
+ * If there is no divergence, the result will be -1. |
+ * Note that if B is the same as A except B has more nodes |
+ * even after A has ended, that is considered a divergence. |
+ * The first node that B has which A does not have will |
+ * be treated as the divergence point. |
+ * |
+ * @param {Object} ancestorsA The array of ancestors for Node A. |
+ * @param {Object} ancestorsB The array of ancestors for Node B. |
+ * @return {number} The index of the divergence point (the first node that B has |
+ * which A does not have in B's list of ancestors). |
+ */ |
+cvox.DomUtil.compareAncestors = function(ancestorsA, ancestorsB) { |
+ var i = 0; |
+ while (ancestorsA[i] && ancestorsB[i] && (ancestorsA[i] == ancestorsB[i])) { |
+ i++; |
+ } |
+ if (!ancestorsA[i] && !ancestorsB[i]) { |
+ i = -1; |
+ } |
+ return i; |
+}; |
+ |
+/** |
+ * Returns an array of ancestors that are unique for the currentNode when |
+ * compared to the previousNode. Having such an array is useful in generating |
+ * the node information (identifying when interesting node boundaries have been |
+ * crossed, etc.). |
+ * |
+ * @param {Object} previousNode The previous node. |
+ * @param {Object} currentNode The current node. |
+ * @return {Array.<Node>} An array of unique ancestors for the current node. |
+ */ |
+cvox.DomUtil.getUniqueAncestors = function(previousNode, currentNode) { |
+ var prevAncestors = cvox.DomUtil.getAncestors(previousNode); |
+ var currentAncestors = cvox.DomUtil.getAncestors(currentNode); |
+ var divergence = cvox.DomUtil.compareAncestors(prevAncestors, |
+ currentAncestors); |
+ return currentAncestors.slice(divergence); |
+}; |
+ |
+/** |
+ * Returns a string of basic information about the target node. |
+ * This information is only about the node itself and does not take into |
+ * account any of the node's ancestors. |
+ * |
+ * @param {Object} targetNode The node to get information about. |
+ * @return {string} A string of basic information about the current node. |
+ */ |
+cvox.DomUtil.getBasicNodeInformation = function(targetNode) { |
+ var info = cvox.DomUtil.getBasicNodeRole(targetNode); |
+ if (info.length > 0) { |
+ info = info + ' ' + cvox.DomUtil.getBasicNodeState(targetNode); |
+ } |
+ return info; |
+}; |
+ |
+/** |
+ * Returns a string to be presented to the user that identifies what the |
+ * targetNode's role is. |
+ * ARIA roles are given priority; if there is no ARIA role set, the role |
+ * will be determined by the HTML tag for the node. |
+ * |
+ * @param {Object} targetNode The node to get the role name for. |
+ * @return {string} The role name for the targetNode. |
+ */ |
+cvox.DomUtil.getBasicNodeRole = function(targetNode) { |
+ var info; |
+ info = cvox.AriaUtil.getRoleName(targetNode); |
+ if (!info) { |
+ if (targetNode.tagName == 'INPUT') { |
+ info = cvox.DomUtil.INPUT_TYPE_TO_INFORMATION_TABLE[targetNode.type]; |
+ } else { |
+ info = cvox.DomUtil.TAG_TO_INFORMATION_TABLE[targetNode.tagName]; |
+ } |
+ } |
+ if (!info) { |
+ info = ''; |
+ } |
+ return info; |
+}; |
+ |
+/** |
+ * Returns a string that gives information about the state of the targetNode. |
+ * |
+ * @param {Object} targetNode The node to get the state information for. |
+ * @return {string} The status information about the node. |
+ */ |
+cvox.DomUtil.getBasicNodeState = function(targetNode) { |
+ var info; |
+ info = cvox.AriaUtil.getState(targetNode); |
+ if (!info) { |
+ info = ''; |
+ } else { |
+ info = info + ' '; |
+ } |
+ |
+ if (targetNode.tagName == 'INPUT') { |
+ if (targetNode.type == 'checkbox' || targetNode.type == 'radio') { |
+ if (targetNode.checked) { |
+ info = info + ' checked'; |
+ } else { |
+ info = info + ' not checked'; |
+ } |
+ } |
+ } else if (targetNode.tagName == 'SELECT') { |
+ info = info + ' ' + (targetNode.selectedIndex + 1) + ' of ' + |
+ targetNode.options.length; |
+ } |
+ |
+ return info; |
+}; |
+ |
+ |
+/** |
+ * Returns a string of detailed information given an array of |
+ * ancestor nodes. |
+ * |
+ * @param {Object} ancestorsArray An array of ancestor nodes. |
+ * @return {string} A string of detailed information given the |
+ * array of ancestor nodes. |
+ */ |
+cvox.DomUtil.getInformationFromAncestors = function(ancestorsArray) { |
+ var info = ''; |
+ for (var i = 0, node; node = ancestorsArray[i]; i++) { |
+ var nodeInfo = cvox.DomUtil.getBasicNodeInformation(node); |
+ if (nodeInfo.length > 0) { |
+ info = info + ' ' + nodeInfo; |
+ } |
+ } |
+ return info; |
+}; |
+ |
+ |
+/** |
+ * Sets the browser focus to the targetNode or its closest ancestor that is |
+ * able to get focus. |
+ * |
+ * @param {Object} targetNode The node to move the browser focus to. |
+ */ |
+cvox.DomUtil.setFocus = function(targetNode) { |
+ while (targetNode && ((typeof(targetNode.tabIndex) == 'undefined') || |
+ (targetNode.tabIndex == -1))) { |
+ // If the target is a label for a control, focus the control. |
+ if (targetNode.tagName && (targetNode.tagName == 'LABEL')) { |
+ if (targetNode.htmlFor && document.getElementById(targetNode.htmlFor)) { |
+ targetNode = document.getElementById(targetNode.htmlFor); |
+ } else { |
+ // Handle the case if a label is wrapping a control |
+ var inputElems = targetNode.getElementsByTagName('INPUT'); |
+ if (inputElems && (inputElems.length > 0)) { |
+ // In case there are multiple controls, focus on the first one. |
+ // The user can always read through to the next control. |
+ targetNode = inputElems[0]; |
+ } else { |
+ // No wrapped controls found. In this case, keep moving |
+ // because it means the page author was misusing label |
+ // and failed to associate it with anything. |
+ targetNode = targetNode.parentNode; |
+ } |
+ } |
+ } else { |
+ targetNode = targetNode.parentNode; |
+ } |
+ |
+ } |
+ if (targetNode && (typeof(targetNode.tabIndex) != 'undefined') && |
+ (targetNode.tabIndex != -1)) { |
+ targetNode.focus(); |
+ } else { |
+ if (document.activeElement && (document.activeElement.tagName != 'BODY')) { |
+ // Chrome will lose the selection if there is a blur, even if the blur |
+ // does not touch the selection. To work around this, backup the |
+ // selection, do the blur, then restore the selection. |
+ var sel = window.getSelection(); |
+ if (sel.rangeCount > 0) { |
+ var range = sel.getRangeAt(0); |
+ document.activeElement.blur(); |
+ sel.removeAllRanges(); |
+ sel.addRange(range); |
+ } |
+ } |
+ } |
+}; |
+ |
+/** |
+ * Checks if the targetNode is still attached to the document. |
+ * A node can become detached because of AJAX changes. |
+ * |
+ * @param {Object} targetNode The node to check. |
+ * @return {boolean} True if the targetNode is still attached. |
+ */ |
+cvox.DomUtil.isAttachedToDocument = function(targetNode) { |
+ while (targetNode) { |
+ if (targetNode.tagName && (targetNode.tagName == 'HTML')) { |
+ return true; |
+ } |
+ targetNode = targetNode.parentNode; |
+ } |
+ return false; |
+}; |
+ |
+ |
+/** |
+ * Dispatches a left click event on the element that is the targetNode. |
+ * Clicks go in the sequence of mousedown, mouseup, and click. |
+ * @param {Node} targetNode The target node of this operation. |
+ * @param {boolean} shiftKey Specifies if shift is held down. |
+ */ |
+cvox.DomUtil.clickElem = function(targetNode, shiftKey) { |
+ //Send a mousedown |
+ var evt = document.createEvent('MouseEvents'); |
+ evt.initMouseEvent('mousedown', true, true, document.defaultView, |
+ 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); |
+ //Use a try block here so that if the AJAX fails and it is a link, |
+ //it can still fall through and retry by setting the document.location. |
+ try { |
+ targetNode.dispatchEvent(evt); |
+ } catch (e) {} |
+ //Send a mouse up |
+ evt = document.createEvent('MouseEvents'); |
+ evt.initMouseEvent('mouseup', true, true, document.defaultView, |
+ 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); |
+ //Use a try block here so that if the AJAX fails and it is a link, |
+ //it can still fall through and retry by setting the document.location. |
+ try { |
+ targetNode.dispatchEvent(evt); |
+ } catch (e) {} |
+ //Send a click |
+ evt = document.createEvent('MouseEvents'); |
+ evt.initMouseEvent('click', true, true, document.defaultView, |
+ 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); |
+ //Use a try block here so that if the AJAX fails and it is a link, |
+ //it can still fall through and retry by setting the document.location. |
+ try { |
+ targetNode.dispatchEvent(evt); |
+ } catch (e) {} |
+ //Clicking on a link does not cause traversal because of script |
+ //privilege limitations. The traversal has to be done by setting |
+ //document.location. |
+ var href = targetNode.getAttribute('href'); |
+ if ((targetNode.tagName == 'A') && |
+ href && |
+ (href != '#')) { |
+ if (shiftKey) { |
+ window.open(targetNode.href); |
+ } else { |
+ document.location = targetNode.href; |
+ } |
+ } |
+ |
+}; |
+ |
+ |
+/** |
+ * Given an HTMLInputElement, returns true if it's an editable text type. |
+ * This includes input type='text' and input type='password' and a few |
+ * others. |
+ * |
+ * @param {Node} node The node to check. |
+ * @return {boolean} True if the node is an INPUT with an editable text type. |
+ */ |
+cvox.DomUtil.isInputTypeText = function(node) { |
+ if (node.constructor != HTMLInputElement) { |
+ return false; |
+ } |
+ |
+ switch (node.type) { |
+ case 'email': |
+ case 'number': |
+ case 'password': |
+ case 'search': |
+ case 'text': |
+ case 'tel': |
+ case 'url': |
+ case '': |
+ return true; |
+ default: |
+ return false; |
+ } |
+}; |
+ |
+/** |
+ * Given a node, returns true if it's a control. |
+ * Note that controls are all leaf level widgets; they |
+ * are NOT containers. |
+ * |
+ * @param {Node} node The node to check. |
+ * @return {boolean} True if the node is a control. |
+ */ |
+cvox.DomUtil.isControl = function(node) { |
+ if (cvox.AriaUtil.isControlWidget(node)) { |
+ return true; |
+ } |
+ if (node.tagName) { |
+ switch (node.tagName) { |
+ case 'BUTTON': |
+ case 'INPUT': |
+ case 'TEXTAREA': |
+ case 'SELECT': |
+ return true; |
+ } |
+ } |
+ return false; |
+}; |
+ |
+/** |
+ * Given a node, returns the next leaf node. |
+ * |
+ * @param {Node} node The node from which to start looking |
+ * for the next leaf node. |
+ * @return {Node} The next leaf node. |
+ * Null if there is no next leaf node. |
+ */ |
+cvox.DomUtil.nextLeafNode = function(node) { |
+ var tempNode = node; |
+ while (tempNode && (!tempNode.nextSibling)) { |
+ tempNode = tempNode.parentNode; |
+ } |
+ if (tempNode && tempNode.nextSibling) { |
+ tempNode = tempNode.nextSibling; |
+ while (!cvox.DomUtil.isLeafNode(tempNode)) { |
+ tempNode = tempNode.firstChild; |
+ } |
+ } |
+ return tempNode; |
+}; |
+ |
+/** |
+ * Given a node, returns the previous leaf node. |
+ * |
+ * @param {Node} node The node from which to start looking |
+ * for the previous leaf node. |
+ * @return {Node} The previous leaf node. |
+ * Null if there is no previous leaf node. |
+ */ |
+cvox.DomUtil.previousLeafNode = function(node) { |
+ var tempNode = node; |
+ while (tempNode && (!tempNode.previousSibling)) { |
+ tempNode = tempNode.parentNode; |
+ } |
+ if (tempNode && tempNode.previousSibling) { |
+ tempNode = tempNode.previousSibling; |
+ while (!cvox.DomUtil.isLeafNode(tempNode)) { |
+ tempNode = tempNode.lastChild; |
+ } |
+ } |
+ return tempNode; |
+}; |
Property changes on: chrome/browser/resources/access_chromevox/common/dom_util.js |
___________________________________________________________________ |
Added: svn:executable |
+ * |
Added: svn:eol-style |
+ LF |