text: // 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 Keeps track of live regions on the page and speaks updates
   * when they change.
   *
   */
  
  goog.provide('cvox.LiveRegionsDeprecated');
  
  goog.require('cvox.AriaUtil');
  goog.require('cvox.ChromeVox');
  goog.require('cvox.DescriptionUtil');
  goog.require('cvox.DomUtil');
  goog.require('cvox.NavDescription');
  
  /**
   * @constructor
   */
  cvox.LiveRegionsDeprecated = function() {
  };
  
  /**
   * An array of all of the elements on the page that are live regions.
   * @type {Array.<Element>}
   */
  cvox.LiveRegionsDeprecated.trackedRegions = [];
  
  /**
   * A parallel array to trackedRegions that stores the previous value of
   * each live region, represented as an array of NavDescriptions.
   * @type {Array.<Array.<cvox.NavDescription> >}
   */
  cvox.LiveRegionsDeprecated.previousRegionValue = [];
  
  /**
   * @type {Date}
   */
  cvox.LiveRegionsDeprecated.pageLoadTime = null;
  
  /**
   * Time in milliseconds after initial page load to ignore live region
   * updates, to avoid announcing regions as they're initially created.
   * The exception is alerts, they're announced when a page is loaded.
   * @type {number}
   * @const
   */
  cvox.LiveRegionsDeprecated.INITIAL_SILENCE_MS = 2000;
  
  /**
   * @param {Date} pageLoadTime The time the page was loaded. Live region
   *     updates within the first INITIAL_SILENCE_MS milliseconds are ignored.
   * @param {number} queueMode Interrupt or flush.  Polite live region
   *   changes always queue.
   * @param {boolean} disableSpeak true if change announcement should be disabled.
   * @return {boolean} true if any regions announced.
   */
  cvox.LiveRegionsDeprecated.init = function(pageLoadTime, queueMode, disableSpeak) {
    if (queueMode == undefined) {
      queueMode = cvox.AbstractTts.QUEUE_MODE_FLUSH;
    }
  
    cvox.LiveRegionsDeprecated.pageLoadTime = pageLoadTime;
  
    var anyRegionsAnnounced = false;
    var regions = cvox.AriaUtil.getLiveRegions(document.body);
    for (var i = 0; i < regions.length; i++) {
      if (cvox.LiveRegionsDeprecated.updateLiveRegion(regions[i], queueMode,
                                            disableSpeak)) {
        anyRegionsAnnounced = true;
        queueMode = cvox.AbstractTts.QUEUE_MODE_QUEUE;
      }
    }
  
    return anyRegionsAnnounced;
  };
  
  /**
   * Speak relevant changes to a live region.
   *
   * @param {Node} region The live region node that changed.
   * @param {number} queueMode Interrupt or queue. Polite live region
   *   changes always queue.
   * @param {boolean} disableSpeak true if change announcement should be disabled.
   * @return {boolean} true if the region announced a change.
   */
  cvox.LiveRegionsDeprecated.updateLiveRegion = function(region, queueMode, disableSpeak) {
    if (cvox.AriaUtil.getAriaBusy(region)) {
      return false;
    }
  
    // Make sure it's visible.
    if (!cvox.DomUtil.isVisible(region)) {
      return false;
    }
  
    // Retrieve the previous value of this region if we've tracked it
    // before, otherwise start tracking it.
    var regionIndex = cvox.LiveRegionsDeprecated.trackedRegions.indexOf(region);
    var previousValue;
    if (regionIndex >= 0) {
      previousValue = cvox.LiveRegionsDeprecated.previousRegionValue[regionIndex];
    } else {
      regionIndex = cvox.LiveRegionsDeprecated.trackedRegions.length;
      previousValue = [];
      cvox.LiveRegionsDeprecated.trackedRegions.push(region);
      cvox.LiveRegionsDeprecated.previousRegionValue.push([]);
    }
  
    // Get the new value.
    var currentValue = cvox.LiveRegionsDeprecated.buildCurrentLiveRegionValue(region);
  
    // If the page just loaded and this is any region type other than 'alert',
    // keep track of the new value but don't announce anything. Alerts are
    // the exception, they're announced on page load.
    var deltaTime = new Date() - cvox.LiveRegionsDeprecated.pageLoadTime;
    if (cvox.AriaUtil.getRoleAttribute(region) != 'alert' &&
        deltaTime < cvox.LiveRegionsDeprecated.INITIAL_SILENCE_MS) {
      cvox.LiveRegionsDeprecated.previousRegionValue[regionIndex] = currentValue;
      return false;
    }
  
    // Don't announce alerts on page load if their text and values consist of
    // just whitespace.
    if (cvox.AriaUtil.getRoleAttribute(region) == 'alert' &&
        deltaTime < cvox.LiveRegionsDeprecated.INITIAL_SILENCE_MS) {
      var regionText = '';
      for (var i = 0; i < currentValue.length; i++) {
        regionText += currentValue[i].text;
        regionText += currentValue[i].userValue;
      }
      if (cvox.DomUtil.collapseWhitespace(regionText) == '') {
        cvox.LiveRegionsDeprecated.previousRegionValue[regionIndex] = currentValue;
        return false;
      }
    }
  
    // Create maps of values in the live region for fast hash lookup.
    var previousValueMap = {};
    for (var i = 0; i < previousValue.length; i++) {
      previousValueMap[previousValue[i].toString()] = true;
    }
    var currentValueMap = {};
    for (i = 0; i < currentValue.length; i++) {
      currentValueMap[currentValue[i].toString()] = true;
    }
  
    // Figure out the additions and removals.
    var additions = [];
    if (cvox.AriaUtil.getAriaRelevant(region, 'additions')) {
      for (i = 0; i < currentValue.length; i++) {
        if (!previousValueMap[currentValue[i].toString()]) {
          additions.push(currentValue[i]);
        }
      }
    }
    var removals = [];
    if (cvox.AriaUtil.getAriaRelevant(region, 'removals')) {
      for (i = 0; i < previousValue.length; i++) {
        if (!currentValueMap[previousValue[i].toString()]) {
          removals.push(previousValue[i]);
        }
      }
    }
  
    // Only speak removals if they're the only change. Otherwise, when one or
    // more removals and additions happen concurrently, treat it as a change
    // and just speak any additions (which includes changed nodes).
    var messages = [];
    if (additions.length == 0 && removals.length > 0) {
      messages = [new cvox.NavDescription({
        context: cvox.ChromeVox.msgs.getMsg('live_regions_removed'), text: ''
      })].concat(removals);
    } else {
      messages = additions;
    }
  
    // Store the new value of the live region.
    cvox.LiveRegionsDeprecated.previousRegionValue[regionIndex] = currentValue;
  
    // Return if speak is disabled or there's nothing to announce.
    if (disableSpeak || messages.length == 0) {
      return false;
    }
  
    // Announce the changes with the appropriate politeness level.
    var live = cvox.AriaUtil.getAriaLive(region);
    if (live == 'polite') {
      queueMode = cvox.AbstractTts.QUEUE_MODE_QUEUE;
    }
    for (i = 0; i < messages.length; i++) {
      messages[i].speak(queueMode);
      queueMode = cvox.AbstractTts.QUEUE_MODE_QUEUE;
    }
  
    return true;
  };
  
  /**
   * Recursively build up the value of a live region and return it as
   * an array of NavDescriptions. Each atomic portion of the region gets a
   * single string, otherwise each leaf node gets its own string. When a region
   * changes, the sets of strings before and after are searched to determine
   * which have changed.
   *
   * @param {Node} node The root node.
   * @return {Array.<cvox.NavDescription>} An array of NavDescriptions
   *     describing atomic nodes or leaf nodes in the subtree rooted
   *     at this node.
   */
  cvox.LiveRegionsDeprecated.buildCurrentLiveRegionValue = function(node) {
    if (cvox.AriaUtil.getAriaAtomic(node) ||
        cvox.DomUtil.isLeafNode(node)) {
      var description = cvox.DescriptionUtil.getDescriptionFromAncestors(
          [node], true, cvox.ChromeVox.verbosity);
      if (!description.isEmpty()) {
        return [description];
      } else {
        return [];
      }
    }
  
    var result = [];
  
    // Start with the description of this node.
    var description = cvox.DescriptionUtil.getDescriptionFromAncestors(
        [node], false, cvox.ChromeVox.verbosity);
    if (!description.isEmpty()) {
      result.push(description);
    }
  
    // Recursively add descriptions of child nodes.
    for (var i = 0; i < node.childNodes.length; i++) {
      var child = node.childNodes[i];
      if (cvox.DomUtil.isVisible(child, {checkAncestors: false}) &&
          !cvox.AriaUtil.isHidden(child)) {
        var recursiveArray = cvox.LiveRegionsDeprecated.buildCurrentLiveRegionValue(child);
        result = result.concat(recursiveArray);
      }
    }
    return result;
  };
  