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