Home | History | Annotate | Download | only in searchvox
      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 
     10 goog.provide('cvox.SearchUtil');
     11 
     12 /** Utility functions. */
     13 cvox.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  */
     21 cvox.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  */
     38 cvox.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  */
     49 cvox.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  */
     59 cvox.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  */
     69 var 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  */
     91 var 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  */
    123 cvox.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