blob: 9d409c9769236418936405bf4e1221d852da2c93 [file] [log] [blame]
// Copyright 2014 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 Helper functions.
*/
goog.provide('cvox.SearchUtil');
/** Utility functions. */
cvox.SearchUtil = function() {};
/**
* Extracts the first URL from an element.
* @param {Node} node DOM element to extract from.
* @return {?string} URL.
*/
cvox.SearchUtil.extractURL = function(node) {
if (node) {
if (node.tagName === 'A') {
return node.href;
}
var anchor = node.querySelector('a');
if (anchor) {
return anchor.href;
}
}
return null;
};
/**
* Indicates whether or not the search widget has been activated.
* @return {boolean} Whether or not the search widget is active.
*/
cvox.SearchUtil.isSearchWidgetActive = function() {
var SEARCH_WIDGET_SELECT = '#cvox-search';
return document.querySelector(SEARCH_WIDGET_SELECT) !== null;
};
/**
* Adds one to and index with wrapping.
* @param {number} index Index to add to.
* @param {number} length Length to wrap at.
* @return {number} The new index++, wrapped if exceeding length.
*/
cvox.SearchUtil.addOneWrap = function(index, length) {
return (index + 1) % length;
};
/**
* Subtracts one to and index with wrapping.
* @param {number} index Index to subtract from.
* @param {number} length Length to wrap at.
* @return {number} The new index--, wrapped if below 0.
*/
cvox.SearchUtil.subOneWrap = function(index, length) {
return (index - 1 + length) % length;
};
/**
* Returns the id of a node's active descendant
* @param {Node} targetNode The node.
* @return {?string} The id of the active descendant.
* @private
*/
var getActiveDescendantId = function(targetNode) {
if (!targetNode.getAttribute) {
return null;
}
var activeId = targetNode.getAttribute('aria-activedescendant');
if (!activeId) {
return null;
}
return activeId;
};
/**
* If the node is an object with an active descendant, returns the
* descendant node.
*
* This function will fully resolve an active descendant chain. If a circular
* chain is detected, it will return null.
*
* @param {Node} targetNode The node to get descendant information for.
* @return {Node} The descendant node or null if no node exists.
*/
var getActiveDescendant = function(targetNode) {
var seenIds = {};
var node = targetNode;
while (node) {
var activeId = getActiveDescendantId(node);
if (!activeId) {
break;
}
if (activeId in seenIds) {
// A circlar activeDescendant is an error, so return null.
return null;
}
seenIds[activeId] = true;
node = document.getElementById(activeId);
}
if (node == targetNode) {
return null;
}
return node;
};
/**
* 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.
* @param {boolean=} callOnClickDirectly Specifies whether or not to directly
* invoke the onclick method if there is one.
* @param {boolean=} opt_double True to issue a double click.
*/
cvox.SearchUtil.clickElem = function(
targetNode, shiftKey, callOnClickDirectly, opt_double) {
// If there is an activeDescendant of the targetNode, then that is where the
// click should actually be targeted.
var activeDescendant = getActiveDescendant(targetNode);
if (activeDescendant) {
targetNode = activeDescendant;
}
if (callOnClickDirectly) {
var onClickFunction = null;
if (targetNode.onclick) {
onClickFunction = targetNode.onclick;
}
if (!onClickFunction && (targetNode.nodeType != 1) &&
targetNode.parentNode && targetNode.parentNode.onclick) {
onClickFunction = targetNode.parentNode.onclick;
}
var keepGoing = true;
if (onClickFunction) {
try {
keepGoing = onClickFunction();
} catch (exception) {
// Something went very wrong with the onclick method; we'll ignore it
// and just dispatch a click event normally.
}
}
if (!keepGoing) {
// The onclick method ran successfully and returned false, meaning the
// event should not bubble up, so we will return here.
return;
}
}
// Send a mousedown (or simply a double click if requested).
var evt = document.createEvent('MouseEvents');
var evtType = opt_double ? 'dblclick' : 'mousedown';
evt.initMouseEvent(
evtType, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false,
shiftKey, false, 0, null);
// Mark any events we generate so we don't try to process our own events.
evt.fromCvox = true;
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);
// Mark any events we generate so we don't try to process our own events.
evt.fromCvox = true;
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);
// Mark any events we generate so we don't try to process our own events.
evt.fromCvox = true;
try {
targetNode.dispatchEvent(evt);
} catch (e) {
}
};