util.js revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5 6/** 7 * @fileoverview Helper functions. 8 */ 9 10goog.provide('cvox.SearchUtil'); 11 12/** Utility functions. */ 13cvox.SearchUtil = function() { 14}; 15 16/** 17 * Extracts the first URL from an element. 18 * @param {Node} node DOM element to extract from. 19 * @return {?string} URL. 20 */ 21cvox.SearchUtil.extractURL = function(node) { 22 if (node) { 23 if (node.tagName === 'A') { 24 return node.href; 25 } 26 var anchor = node.querySelector('a'); 27 if (anchor) { 28 return anchor.href; 29 } 30 } 31 return null; 32}; 33 34/** 35 * Indicates whether or not the search widget has been activated. 36 * @return {boolean} Whether or not the search widget is active. 37 */ 38cvox.SearchUtil.isSearchWidgetActive = function() { 39 var SEARCH_WIDGET_SELECT = '#cvox-search'; 40 return document.querySelector(SEARCH_WIDGET_SELECT) !== null; 41}; 42 43/** 44 * Adds one to and index with wrapping. 45 * @param {number} index Index to add to. 46 * @param {number} length Length to wrap at. 47 * @return {number} The new index++, wrapped if exceeding length. 48 */ 49cvox.SearchUtil.addOneWrap = function(index, length) { 50 return (index + 1) % length; 51}; 52 53/** 54 * Subtracts one to and index with wrapping. 55 * @param {number} index Index to subtract from. 56 * @param {number} length Length to wrap at. 57 * @return {number} The new index--, wrapped if below 0. 58 */ 59cvox.SearchUtil.subOneWrap = function(index, length) { 60 return (index - 1 + length) % length; 61}; 62 63/** 64 * Returns the id of a node's active descendant 65 * @param {Node} targetNode The node. 66 * @return {?string} The id of the active descendant. 67 * @private 68 */ 69var getActiveDescendantId_ = function(targetNode) { 70 if (!targetNode.getAttribute) { 71 return null; 72 } 73 74 var activeId = targetNode.getAttribute('aria-activedescendant'); 75 if (!activeId) { 76 return null; 77 } 78 return activeId; 79}; 80 81/** 82 * If the node is an object with an active descendant, returns the 83 * descendant node. 84 * 85 * This function will fully resolve an active descendant chain. If a circular 86 * chain is detected, it will return null. 87 * 88 * @param {Node} targetNode The node to get descendant information for. 89 * @return {Node} The descendant node or null if no node exists. 90 */ 91var getActiveDescendant = function(targetNode) { 92 var seenIds = {}; 93 var node = targetNode; 94 95 while (node) { 96 var activeId = getActiveDescendantId_(node); 97 if (!activeId) { 98 break; 99 } 100 if (activeId in seenIds) { 101 // A circlar activeDescendant is an error, so return null. 102 return null; 103 } 104 seenIds[activeId] = true; 105 node = document.getElementById(activeId); 106 } 107 108 if (node == targetNode) { 109 return null; 110 } 111 return node; 112}; 113 114/** 115 * Dispatches a left click event on the element that is the targetNode. 116 * Clicks go in the sequence of mousedown, mouseup, and click. 117 * @param {Node} targetNode The target node of this operation. 118 * @param {boolean=} shiftKey Specifies if shift is held down. 119 * @param {boolean=} callOnClickDirectly Specifies whether or not to directly 120 * invoke the onclick method if there is one. 121 * @param {boolean=} opt_double True to issue a double click. 122 */ 123cvox.SearchUtil.clickElem = function( 124 targetNode, shiftKey, callOnClickDirectly, opt_double) { 125 // If there is an activeDescendant of the targetNode, then that is where the 126 // click should actually be targeted. 127 var activeDescendant = getActiveDescendant(targetNode); 128 if (activeDescendant) { 129 targetNode = activeDescendant; 130 } 131 if (callOnClickDirectly) { 132 var onClickFunction = null; 133 if (targetNode.onclick) { 134 onClickFunction = targetNode.onclick; 135 } 136 if (!onClickFunction && (targetNode.nodeType != 1) && 137 targetNode.parentNode && targetNode.parentNode.onclick) { 138 onClickFunction = targetNode.parentNode.onclick; 139 } 140 var keepGoing = true; 141 if (onClickFunction) { 142 try { 143 keepGoing = onClickFunction(); 144 } catch (exception) { 145 // Something went very wrong with the onclick method; we'll ignore it 146 // and just dispatch a click event normally. 147 } 148 } 149 if (!keepGoing) { 150 // The onclick method ran successfully and returned false, meaning the 151 // event should not bubble up, so we will return here. 152 return; 153 } 154 } 155 156 // Send a mousedown (or simply a double click if requested). 157 var evt = document.createEvent('MouseEvents'); 158 var evtType = opt_double ? 'dblclick' : 'mousedown'; 159 evt.initMouseEvent(evtType, true, true, document.defaultView, 160 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); 161 // Mark any events we generate so we don't try to process our own events. 162 evt.fromCvox = true; 163 try { 164 targetNode.dispatchEvent(evt); 165 } catch (e) {} 166 //Send a mouse up 167 evt = document.createEvent('MouseEvents'); 168 evt.initMouseEvent('mouseup', true, true, document.defaultView, 169 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); 170 // Mark any events we generate so we don't try to process our own events. 171 evt.fromCvox = true; 172 try { 173 targetNode.dispatchEvent(evt); 174 } catch (e) {} 175 //Send a click 176 evt = document.createEvent('MouseEvents'); 177 evt.initMouseEvent('click', true, true, document.defaultView, 178 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); 179 // Mark any events we generate so we don't try to process our own events. 180 evt.fromCvox = true; 181 try { 182 targetNode.dispatchEvent(evt); 183 } catch (e) {} 184}; 185