| // 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) { |
| } |
| }; |