text: // Copyright 2005 The Closure Library Authors. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS-IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  /**
   * @fileoverview Event Manager.
   *
   * Provides an abstracted interface to the browsers' event
   * systems. This uses an indirect lookup of listener functions to avoid circular
   * references between DOM (in IE) or XPCOM (in Mozilla) objects which leak
   * memory. This makes it easier to write OO Javascript/DOM code.
   *
   * It simulates capture & bubble in Internet Explorer.
   *
   * The listeners will also automagically have their event objects patched, so
   * your handlers don't need to worry about the browser.
   *
   * Example usage:
   * <pre>
   * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
   * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
   * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
   * goog.events.removeAll(myNode);
   * goog.events.removeAll();
   * </pre>
   *
   *                                            in IE and event object patching]
   *
   * @supported IE6+, FF1.5+, WebKit, Opera.
   * @see ../demos/events.html
   * @see ../demos/event-propagation.html
   * @see ../demos/stopevent.html
   */
  
  
  // This uses 3 lookup tables/trees.
  // listenerTree_ is a tree of type -> capture -> src uid -> [Listener]
  // listeners_ is a map of key -> [Listener]
  //
  // The key is a field of the Listener. The Listener class also has the type,
  // capture and the src so one can always trace back in the tree
  //
  // sources_: src uid -> [Listener]
  
  
  goog.provide('goog.events');
  
  goog.require('goog.array');
  goog.require('goog.debug.entryPointRegistry');
  goog.require('goog.debug.errorHandlerWeakDep');
  goog.require('goog.events.BrowserEvent');
  goog.require('goog.events.BrowserFeature');
  goog.require('goog.events.Event');
  goog.require('goog.events.EventWrapper');
  goog.require('goog.events.Listener');
  goog.require('goog.object');
  goog.require('goog.userAgent');
  
  
  /**
   * Container for storing event listeners and their proxies
   * @private
   * @type {Object.<goog.events.Listener>}
   */
  goog.events.listeners_ = {};
  
  
  /**
   * The root of the listener tree
   * @private
   * @type {Object}
   */
  goog.events.listenerTree_ = {};
  
  
  /**
   * Lookup for mapping source UIDs to listeners.
   * @private
   * @type {Object}
   */
  goog.events.sources_ = {};
  
  
  /**
   * String used to prepend to IE event types.  Not a constant so that it is not
   * inlined.
   * @type {string}
   * @private
   */
  goog.events.onString_ = 'on';
  
  
  /**
   * Map of computed on strings for IE event types. Caching this removes an extra
   * object allocation in goog.events.listen which improves IE6 performance.
   * @type {Object}
   * @private
   */
  goog.events.onStringMap_ = {};
  
  
  /**
   * Separator used to split up the various parts of an event key, to help avoid
   * the possibilities of collisions.
   * @type {string}
   * @private
   */
  goog.events.keySeparator_ = '_';
  
  
  /**
   * Adds an event listener for a specific event on a DOM Node or an object that
   * has implemented {@link goog.events.EventTarget}. A listener can only be
   * added once to an object and if it is added again the key for the listener
   * is returned.
   *
   * @param {EventTarget|goog.events.EventTarget} src The node to listen to
   *     events on.
   * @param {string|Array.<string>} type Event type or array of event types.
   * @param {Function|Object} listener Callback method, or an object with a
   *     handleEvent function.
   * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
   *     false).
   * @param {Object=} opt_handler Element in whose scope to call the listener.
   * @return {?number} Unique key for the listener.
   */
  goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
    if (!type) {
      throw Error('Invalid event type');
    } else if (goog.isArray(type)) {
      for (var i = 0; i < type.length; i++) {
        goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
      }
      return null;
    } else {
      var capture = !!opt_capt;
      var map = goog.events.listenerTree_;
  
      if (!(type in map)) {
        map[type] = {count_: 0, remaining_: 0};
      }
      map = map[type];
  
      if (!(capture in map)) {
        map[capture] = {count_: 0, remaining_: 0};
        map.count_++;
      }
      map = map[capture];
  
      var srcUid = goog.getUid(src);
      var listenerArray, listenerObj;
  
      // The remaining_ property is used to be able to short circuit the iteration
      // of the event listeners.
      //
      // Increment the remaining event listeners to call even if this event might
      // already have been fired. At this point we do not know if the event has
      // been fired and it is too expensive to find out. By incrementing it we are
      // guaranteed that we will not skip any event listeners.
      map.remaining_++;
  
      // Do not use srcUid in map here since that will cast the number to a
      // string which will allocate one string object.
      if (!map[srcUid]) {
        listenerArray = map[srcUid] = [];
        map.count_++;
      } else {
        listenerArray = map[srcUid];
        // Ensure that the listeners do not already contain the current listener
        for (var i = 0; i < listenerArray.length; i++) {
          listenerObj = listenerArray[i];
          if (listenerObj.listener == listener &&
              listenerObj.handler == opt_handler) {
  
            // If this listener has been removed we should not return its key. It
            // is OK that we create new listenerObj below since the removed one
            // will be cleaned up later.
            if (listenerObj.removed) {
              break;
            }
  
            // We already have this listener. Return its key.
            return listenerArray[i].key;
          }
        }
      }
  
      var proxy = goog.events.getProxy();
      proxy.src = src;
      listenerObj = new goog.events.Listener();
      listenerObj.init(listener, proxy, src, type, capture, opt_handler);
      var key = listenerObj.key;
      proxy.key = key;
  
      listenerArray.push(listenerObj);
      goog.events.listeners_[key] = listenerObj;
  
      if (!goog.events.sources_[srcUid]) {
        goog.events.sources_[srcUid] = [];
      }
      goog.events.sources_[srcUid].push(listenerObj);
  
  
      // Attach the proxy through the browser's API
      if (src.addEventListener) {
        if (src == goog.global || !src.customEvent_) {
          src.addEventListener(type, proxy, capture);
        }
      } else {
        // The else above used to be else if (src.attachEvent) and then there was
        // another else statement that threw an exception warning the developer
        // they made a mistake. This resulted in an extra object allocation in IE6
        // due to a wrapper object that had to be implemented around the element
        // and so was removed.
        src.attachEvent(goog.events.getOnString_(type), proxy);
      }
  
      return key;
    }
  };
  
  
  /**
   * Helper function for returning a proxy function.
   * @return {Function} A new or reused function object.
   */
  goog.events.getProxy = function() {
    var proxyCallbackFunction = goog.events.handleBrowserEvent_;
    // Use a local var f to prevent one allocation.
    var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
        function(eventObject) {
          return proxyCallbackFunction.call(f.src, f.key, eventObject);
        } :
        function(eventObject) {
          var v = proxyCallbackFunction.call(f.src, f.key, eventObject);
          // NOTE(user): In IE, we hack in a capture phase. However, if
          // there is inline event handler which tries to prevent default (for
          // example <a href="..." onclick="return false">...</a>) in a
          // descendant element, the prevent default will be overridden
          // by this listener if this listener were to return true. Hence, we
          // return undefined.
          if (!v) return v;
        };
    return f;
  };
  
  
  /**
   * Adds an event listener for a specific event on a DomNode or an object that
   * has implemented {@link goog.events.EventTarget}. After the event has fired
   * the event listener is removed from the target.
   *
   * @param {EventTarget|goog.events.EventTarget} src The node to listen to
   *     events on.
   * @param {string|Array.<string>} type Event type or array of event types.
   * @param {Function|Object} listener Callback method.
   * @param {boolean=} opt_capt Fire in capture phase?.
   * @param {Object=} opt_handler Element in whose scope to call the listener.
   * @return {?number} Unique key for the listener.
   */
  goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
    if (goog.isArray(type)) {
      for (var i = 0; i < type.length; i++) {
        goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
      }
      return null;
    }
  
    var key = goog.events.listen(src, type, listener, opt_capt, opt_handler);
    var listenerObj = goog.events.listeners_[key];
    listenerObj.callOnce = true;
    return key;
  };
  
  
  /**
   * Adds an event listener with a specific event wrapper on a DOM Node or an
   * object that has implemented {@link goog.events.EventTarget}. A listener can
   * only be added once to an object.
   *
   * @param {EventTarget|goog.events.EventTarget} src The node to listen to
   *     events on.
   * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
   * @param {Function|Object} listener Callback method, or an object with a
   *     handleEvent function.
   * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
   *     false).
   * @param {Object=} opt_handler Element in whose scope to call the listener.
   */
  goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
      opt_handler) {
    wrapper.listen(src, listener, opt_capt, opt_handler);
  };
  
  
  /**
   * Removes an event listener which was added with listen().
   *
   * @param {EventTarget|goog.events.EventTarget} src The target to stop
   *     listening to events on.
   * @param {string|Array.<string>} type The name of the event without the 'on'
   *     prefix.
   * @param {Function|Object} listener The listener function to remove.
   * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
   *     whether the listener is fired during the capture or bubble phase of the
   *     event.
   * @param {Object=} opt_handler Element in whose scope to call the listener.
   * @return {?boolean} indicating whether the listener was there to remove.
   */
  goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
    if (goog.isArray(type)) {
      for (var i = 0; i < type.length; i++) {
        goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
      }
      return null;
    }
  
    var capture = !!opt_capt;
  
    var listenerArray = goog.events.getListeners_(src, type, capture);
    if (!listenerArray) {
      return false;
    }
  
    for (var i = 0; i < listenerArray.length; i++) {
      if (listenerArray[i].listener == listener &&
          listenerArray[i].capture == capture &&
          listenerArray[i].handler == opt_handler) {
        return goog.events.unlistenByKey(listenerArray[i].key);
      }
    }
  
    return false;
  };
  
  
  /**
   * Removes an event listener which was added with listen() by the key
   * returned by listen().
   *
   * @param {?number} key The key returned by listen() for this event listener.
   * @return {boolean} indicating whether the listener was there to remove.
   */
  goog.events.unlistenByKey = function(key) {
    // Do not use key in listeners here since that will cast the number to a
    // string which will allocate one string object.
    if (!goog.events.listeners_[key]) {
      return false;
    }
    var listener = goog.events.listeners_[key];
  
    if (listener.removed) {
      return false;
    }
  
    var src = listener.src;
    var type = listener.type;
    var proxy = listener.proxy;
    var capture = listener.capture;
  
    if (src.removeEventListener) {
      // EventTarget calls unlisten so we need to ensure that the source is not
      // an event target to prevent re-entry.
      // TODO(arv): What is this goog.global for? Why would anyone listen to
      // events on the [[Global]] object? Is it supposed to be window? Why would
      // we not want to allow removing event listeners on the window?
      if (src == goog.global || !src.customEvent_) {
        src.removeEventListener(type, proxy, capture);
      }
    } else if (src.detachEvent) {
      src.detachEvent(goog.events.getOnString_(type), proxy);
    }
  
    var srcUid = goog.getUid(src);
  
    // In a perfect implementation we would decrement the remaining_ field here
    // but then we would need to know if the listener has already been fired or
    // not. We therefore skip doing this and in this uncommon case the entire
    // ancestor chain will need to be traversed as before.
  
    // Remove from sources_
    if (goog.events.sources_[srcUid]) {
      var sourcesArray = goog.events.sources_[srcUid];
      goog.array.remove(sourcesArray, listener);
      if (sourcesArray.length == 0) {
        delete goog.events.sources_[srcUid];
      }
    }
  
    listener.removed = true;
  
    // There are some esoteric situations where the hash code of an object
    // can change, and we won't be able to find the listenerArray anymore.
    // For example, if you're listening on a window, and the user navigates to
    // a different window, the UID will disappear.
    //
    // It should be impossible to ever find the original listenerArray, so it
    // doesn't really matter if we can't clean it up in this case.
    var listenerArray = goog.events.listenerTree_[type][capture][srcUid];
    if (listenerArray) {
      listenerArray.needsCleanup_ = true;
      goog.events.cleanUp_(type, capture, srcUid, listenerArray);
    }
  
    delete goog.events.listeners_[key];
  
    return true;
  };
  
  
  /**
   * Removes an event listener which was added with listenWithWrapper().
   *
   * @param {EventTarget|goog.events.EventTarget} src The target to stop
   *     listening to events on.
   * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
   * @param {Function|Object} listener The listener function to remove.
   * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
   *     whether the listener is fired during the capture or bubble phase of the
   *     event.
   * @param {Object=} opt_handler Element in whose scope to call the listener.
   */
  goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
      opt_handler) {
    wrapper.unlisten(src, listener, opt_capt, opt_handler);
  };
  
  
  /**
   * Cleans up the listener array as well as the listener tree
   * @param {string} type  The type of the event.
   * @param {boolean} capture Whether to clean up capture phase listeners instead
   *     bubble phase listeners.
   * @param {number} srcUid  The unique ID of the source.
   * @param {Array.<goog.events.Listener>} listenerArray The array being cleaned.
   * @private
   */
  goog.events.cleanUp_ = function(type, capture, srcUid, listenerArray) {
    // The listener array gets locked during the dispatch phase so that removals
    // of listeners during this phase does not screw up the indeces. This method
    // is called after we have removed a listener as well as after the dispatch
    // phase in case any listeners were removed.
    if (!listenerArray.locked_) { // catches both 0 and not set
      if (listenerArray.needsCleanup_) {
        // Loop over the listener array and remove listeners that have removed set
        // to true. This could have been done with filter or something similar but
        // we want to change the array in place and we want to minimize
        // allocations. Adding a listener during this phase adds to the end of the
        // array so that works fine as long as the length is rechecked every in
        // iteration.
        for (var oldIndex = 0, newIndex = 0;
             oldIndex < listenerArray.length;
             oldIndex++) {
          if (listenerArray[oldIndex].removed) {
            var proxy = listenerArray[oldIndex].proxy;
            proxy.src = null;
            continue;
          }
          if (oldIndex != newIndex) {
            listenerArray[newIndex] = listenerArray[oldIndex];
          }
          newIndex++;
        }
        listenerArray.length = newIndex;
  
        listenerArray.needsCleanup_ = false;
  
        // In case the length is now zero we release the object.
        if (newIndex == 0) {
          delete goog.events.listenerTree_[type][capture][srcUid];
          goog.events.listenerTree_[type][capture].count_--;
  
          if (goog.events.listenerTree_[type][capture].count_ == 0) {
            delete goog.events.listenerTree_[type][capture];
            goog.events.listenerTree_[type].count_--;
          }
  
          if (goog.events.listenerTree_[type].count_ == 0) {
            delete goog.events.listenerTree_[type];
          }
        }
  
      }
    }
  };
  
  
  /**
   * Removes all listeners from an object, if no object is specified it will
   * remove all listeners that have been registered.  You can also optionally
   * remove listeners of a particular type or capture phase.
   *
   * @param {Object=} opt_obj Object to remove listeners from.
   * @param {string=} opt_type Type of event to, default is all types.
   * @param {boolean=} opt_capt Whether to remove the listeners from the capture
   *     or bubble phase.  If unspecified, will remove both.
   * @return {number} Number of listeners removed.
   */
  goog.events.removeAll = function(opt_obj, opt_type, opt_capt) {
    var count = 0;
  
    var noObj = opt_obj == null;
    var noType = opt_type == null;
    var noCapt = opt_capt == null;
    opt_capt = !!opt_capt;
  
    if (!noObj) {
      var srcUid = goog.getUid(/** @type {Object} */ (opt_obj));
      if (goog.events.sources_[srcUid]) {
        var sourcesArray = goog.events.sources_[srcUid];
        for (var i = sourcesArray.length - 1; i >= 0; i--) {
          var listener = sourcesArray[i];
          if ((noType || opt_type == listener.type) &&
              (noCapt || opt_capt == listener.capture)) {
            goog.events.unlistenByKey(listener.key);
            count++;
          }
        }
      }
    } else {
      // Loop over the sources_ map instead of over the listeners_ since it is
      // smaller which results in fewer allocations.
      goog.object.forEach(goog.events.sources_, function(listeners) {
        for (var i = listeners.length - 1; i >= 0; i--) {
          var listener = listeners[i];
          if ((noType || opt_type == listener.type) &&
              (noCapt || opt_capt == listener.capture)) {
            goog.events.unlistenByKey(listener.key);
            count++;
          }
        }
      });
    }
  
    return count;
  };
  
  
  /**
   * Gets the listeners for a given object, type and capture phase.
   *
   * @param {Object} obj Object to get listeners for.
   * @param {string} type Event type.
   * @param {boolean} capture Capture phase?.
   * @return {Array.<goog.events.Listener>} Array of listener objects.
   */
  goog.events.getListeners = function(obj, type, capture) {
    return goog.events.getListeners_(obj, type, capture) || [];
  };
  
  
  /**
   * Gets the listeners for a given object, type and capture phase.
   *
   * @param {Object} obj Object to get listeners for.
   * @param {?string} type Event type.
   * @param {boolean} capture Capture phase?.
   * @return {Array.<goog.events.Listener>?} Array of listener objects.
   *     Returns null if object has no listeners of that type.
   * @private
   */
  goog.events.getListeners_ = function(obj, type, capture) {
    var map = goog.events.listenerTree_;
    if (type in map) {
      map = map[type];
      if (capture in map) {
        map = map[capture];
        var objUid = goog.getUid(obj);
        if (map[objUid]) {
          return map[objUid];
        }
      }
    }
  
    return null;
  };
  
  
  /**
   * Gets the goog.events.Listener for the event or null if no such listener is
   * in use.
   *
   * @param {EventTarget|goog.events.EventTarget} src The node from which to get
   *     listeners.
   * @param {?string} type The name of the event without the 'on' prefix.
   * @param {Function|Object} listener The listener function to get.
   * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
   *                            whether the listener is fired during the
   *                            capture or bubble phase of the event.
   * @param {Object=} opt_handler Element in whose scope to call the listener.
   * @return {goog.events.Listener?} the found listener or null if not found.
   */
  goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
    var capture = !!opt_capt;
    var listenerArray = goog.events.getListeners_(src, type, capture);
    if (listenerArray) {
      for (var i = 0; i < listenerArray.length; i++) {
        // If goog.events.unlistenByKey is called during an event dispatch
        // then the listener array won't get cleaned up and there might be
        // 'removed' listeners in the list. Ignore those.
        if (!listenerArray[i].removed &&
            listenerArray[i].listener == listener &&
            listenerArray[i].capture == capture &&
            listenerArray[i].handler == opt_handler) {
          // We already have this listener. Return its key.
          return listenerArray[i];
        }
      }
    }
    return null;
  };
  
  
  /**
   * Returns whether an event target has any active listeners matching the
   * specified signature. If either the type or capture parameters are
   * unspecified, the function will match on the remaining criteria.
   *
   * @param {EventTarget|goog.events.EventTarget} obj Target to get listeners for.
   * @param {string=} opt_type Event type.
   * @param {boolean=} opt_capture Whether to check for capture or bubble-phase
   *     listeners.
   * @return {boolean} Whether an event target has one or more listeners matching
   *     the requested type and/or capture phase.
   */
  goog.events.hasListener = function(obj, opt_type, opt_capture) {
    var objUid = goog.getUid(obj);
    var listeners = goog.events.sources_[objUid];
  
    if (listeners) {
      var hasType = goog.isDef(opt_type);
      var hasCapture = goog.isDef(opt_capture);
  
      if (hasType && hasCapture) {
        // Lookup in the listener tree whether the specified listener exists.
        var map = goog.events.listenerTree_[opt_type];
        return !!map && !!map[opt_capture] && objUid in map[opt_capture];
  
      } else if (!(hasType || hasCapture)) {
        // Simple check for whether the event target has any listeners at all.
        return true;
  
      } else {
        // Iterate through the listeners for the event target to find a match.
        return goog.array.some(listeners, function(listener) {
          return (hasType && listener.type == opt_type) ||
                 (hasCapture && listener.capture == opt_capture);
        });
      }
    }
  
    return false;
  };
  
  
  /**
   * Provides a nice string showing the normalized event objects public members
   * @param {Object} e Event Object.
   * @return {string} String of the public members of the normalized event object.
   */
  goog.events.expose = function(e) {
    var str = [];
    for (var key in e) {
      if (e[key] && e[key].id) {
        str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
      } else {
        str.push(key + ' = ' + e[key]);
      }
    }
    return str.join('\n');
  };
  
  
  /**
   * Returns a string wth on prepended to the specified type. This is used for IE
   * which expects "on" to be prepended. This function caches the string in order
   * to avoid extra allocations in steady state.
   * @param {string} type Event type strng.
   * @return {string} The type string with 'on' prepended.
   * @private
   */
  goog.events.getOnString_ = function(type) {
    if (type in goog.events.onStringMap_) {
      return goog.events.onStringMap_[type];
    }
    return goog.events.onStringMap_[type] = goog.events.onString_ + type;
  };
  
  
  /**
   * Fires an object's listeners of a particular type and phase
   *
   * @param {Object} obj Object whose listeners to call.
   * @param {string} type Event type.
   * @param {boolean} capture Which event phase.
   * @param {Object} eventObject Event object to be passed to listener.
   * @return {boolean} True if all listeners returned true else false.
   */
  goog.events.fireListeners = function(obj, type, capture, eventObject) {
    var map = goog.events.listenerTree_;
    if (type in map) {
      map = map[type];
      if (capture in map) {
        return goog.events.fireListeners_(map[capture], obj, type,
                                          capture, eventObject);
      }
    }
    return true;
  };
  
  
  /**
   * Fires an object's listeners of a particular type and phase.
   *
   * @param {Object} map Object with listeners in it.
   * @param {Object} obj Object whose listeners to call.
   * @param {string} type Event type.
   * @param {boolean} capture Which event phase.
   * @param {Object} eventObject Event object to be passed to listener.
   * @return {boolean} True if all listeners returned true else false.
   * @private
   */
  goog.events.fireListeners_ = function(map, obj, type, capture, eventObject) {
    var retval = 1;
  
    var objUid = goog.getUid(obj);
    if (map[objUid]) {
      map.remaining_--;
      var listenerArray = map[objUid];
  
      // If locked_ is not set (and if already 0) initialize it to 1.
      if (!listenerArray.locked_) {
        listenerArray.locked_ = 1;
      } else {
        listenerArray.locked_++;
      }
  
      try {
        // Events added in the dispatch phase should not be dispatched in
        // the current dispatch phase. They will be included in the next
        // dispatch phase though.
        var length = listenerArray.length;
        for (var i = 0; i < length; i++) {
          var listener = listenerArray[i];
          // We might not have a listener if the listener was removed.
          if (listener && !listener.removed) {
            retval &=
                goog.events.fireListener(listener, eventObject) !== false;
          }
        }
      } finally {
        listenerArray.locked_--;
        goog.events.cleanUp_(type, capture, objUid, listenerArray);
      }
    }
  
    return Boolean(retval);
  };
  
  
  /**
   * Fires a listener with a set of arguments
   *
   * @param {goog.events.Listener} listener The listener object to call.
   * @param {Object} eventObject The event object to pass to the listener.
   * @return {boolean} Result of listener.
   */
  goog.events.fireListener = function(listener, eventObject) {
    if (listener.callOnce) {
      goog.events.unlistenByKey(listener.key);
    }
    return listener.handleEvent(eventObject);
  };
  
  
  /**
   * Gets the total number of listeners currently in the system.
   * @return {number} Number of listeners.
   */
  goog.events.getTotalListenerCount = function() {
    return goog.object.getCount(goog.events.listeners_);
  };
  
  
  /**
   * Dispatches an event (or event like object) and calls all listeners
   * listening for events of this type. The type of the event is decided by the
   * type property on the event object.
   *
   * If any of the listeners returns false OR calls preventDefault then this
   * function will return false.  If one of the capture listeners calls
   * stopPropagation, then the bubble listeners won't fire.
   *
   * @param {goog.events.EventTarget} src  The event target.
   * @param {string|Object|goog.events.Event} e Event object.
   * @return {boolean} If anyone called preventDefault on the event object (or
   *     if any of the handlers returns false) this will also return false.
   *     If there are no handlers, or if all handlers return true, this returns
   *     true.
   */
  goog.events.dispatchEvent = function(src, e) {
    var type = e.type || e;
    var map = goog.events.listenerTree_;
    if (!(type in map)) {
      return true;
    }
  
    // If accepting a string or object, create a custom event object so that
    // preventDefault and stopPropagation work with the event.
    if (goog.isString(e)) {
      e = new goog.events.Event(e, src);
    } else if (!(e instanceof goog.events.Event)) {
      var oldEvent = e;
      e = new goog.events.Event(type, src);
      goog.object.extend(e, oldEvent);
    } else {
      e.target = e.target || src;
    }
  
    var rv = 1, ancestors;
  
    map = map[type];
    var hasCapture = true in map;
    var targetsMap;
  
    if (hasCapture) {
      // Build ancestors now
      ancestors = [];
      for (var parent = src; parent; parent = parent.getParentEventTarget()) {
        ancestors.push(parent);
      }
  
      targetsMap = map[true];
      targetsMap.remaining_ = targetsMap.count_;
  
      // Call capture listeners
      for (var i = ancestors.length - 1;
           !e.propagationStopped_ && i >= 0 && targetsMap.remaining_;
           i--) {
        e.currentTarget = ancestors[i];
        rv &= goog.events.fireListeners_(targetsMap, ancestors[i], e.type,
                                         true, e) &&
              e.returnValue_ != false;
      }
    }
  
    var hasBubble = false in map;
    if (hasBubble) {
      targetsMap = map[false];
      targetsMap.remaining_ = targetsMap.count_;
  
      if (hasCapture) { // We have the ancestors.
  
        // Call bubble listeners
        for (var i = 0; !e.propagationStopped_ && i < ancestors.length &&
             targetsMap.remaining_;
             i++) {
          e.currentTarget = ancestors[i];
          rv &= goog.events.fireListeners_(targetsMap, ancestors[i], e.type,
                                           false, e) &&
                e.returnValue_ != false;
        }
      } else {
        // In case we don't have capture we don't have to build up the
        // ancestors array.
  
        for (var current = src;
             !e.propagationStopped_ && current && targetsMap.remaining_;
             current = current.getParentEventTarget()) {
          e.currentTarget = current;
          rv &= goog.events.fireListeners_(targetsMap, current, e.type,
                                           false, e) &&
                e.returnValue_ != false;
        }
      }
    }
  
    return Boolean(rv);
  };
  
  
  /**
   * Installs exception protection for the browser event entry point using the
   * given error handler.
   *
   * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
   *     protect the entry point.
   */
  goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
    goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
        goog.events.handleBrowserEvent_);
  };
  
  
  /**
   * Handles an event and dispatches it to the correct listeners. This
   * function is a proxy for the real listener the user specified.
   *
   * @param {number} key Unique key for the listener.
   * @param {Event=} opt_evt Optional event object that gets passed in via the
   *     native event handlers.
   * @return {boolean} Result of the event handler.
   * @this {goog.events.EventTarget|Object} The object or Element that
   *     fired the event.
   * @private
   */
  goog.events.handleBrowserEvent_ = function(key, opt_evt) {
    // If the listener isn't there it was probably removed when processing
    // another listener on the same event (e.g. the later listener is
    // not managed by closure so that they are both fired under IE)
    if (!goog.events.listeners_[key]) {
      return true;
    }
  
    var listener = goog.events.listeners_[key];
    var type = listener.type;
    var map = goog.events.listenerTree_;
  
    if (!(type in map)) {
      return true;
    }
    map = map[type];
    var retval, targetsMap;
    // Synthesize event propagation if the browser does not support W3C
    // event model.
    if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
      var ieEvent = opt_evt ||
          /** @type {Event} */ (goog.getObjectByName('window.event'));
  
      // Check if we have any capturing event listeners for this type.
      var hasCapture = true in map;
      var hasBubble = false in map;
  
      if (hasCapture) {
        if (goog.events.isMarkedIeEvent_(ieEvent)) {
          return true;
        }
  
        goog.events.markIeEvent_(ieEvent);
      }
  
      var evt = new goog.events.BrowserEvent();
      evt.init(ieEvent, /** @type {Node} */(this));
  
      retval = true;
      try {
        if (hasCapture) {
          var ancestors = [];
  
          for (var parent = evt.currentTarget;
               parent;
               parent = parent.parentNode) {
            ancestors.push(parent);
          }
  
          targetsMap = map[true];
          targetsMap.remaining_ = targetsMap.count_;
  
          // Call capture listeners
          for (var i = ancestors.length - 1;
               !evt.propagationStopped_ && i >= 0 && targetsMap.remaining_;
               i--) {
            evt.currentTarget = ancestors[i];
            retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type,
                                                 true, evt);
          }
  
          if (hasBubble) {
            targetsMap = map[false];
            targetsMap.remaining_ = targetsMap.count_;
  
            // Call bubble listeners
            for (var i = 0;
                 !evt.propagationStopped_ && i < ancestors.length &&
                 targetsMap.remaining_;
                 i++) {
              evt.currentTarget = ancestors[i];
              retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type,
                                                   false, evt);
            }
          }
  
        } else {
          // Bubbling, let IE handle the propagation.
          retval = goog.events.fireListener(listener, evt);
        }
  
      } finally {
        if (ancestors) {
          ancestors.length = 0;
        }
      }
      return retval;
    } // IE
  
    // Caught a non-IE DOM event. 1 additional argument which is the event object
    var be = new goog.events.BrowserEvent(opt_evt, /** @type {Node} */(this));
    retval = goog.events.fireListener(listener, be);
    return retval;
  };
  
  
  /**
   * This is used to mark the IE event object so we do not do the Closure pass
   * twice for a bubbling event.
   * @param {Event} e The IE browser event.
   * @private
   */
  goog.events.markIeEvent_ = function(e) {
    // Only the keyCode and the returnValue can be changed. We use keyCode for
    // non keyboard events.
    // event.returnValue is a bit more tricky. It is undefined by default. A
    // boolean false prevents the default action. In a window.onbeforeunload and
    // the returnValue is non undefined it will be alerted. However, we will only
    // modify the returnValue for keyboard events. We can get a problem if non
    // closure events sets the keyCode or the returnValue
  
    var useReturnValue = false;
  
    if (e.keyCode == 0) {
      // We cannot change the keyCode in case that srcElement is input[type=file].
      // We could test that that is the case but that would allocate 3 objects.
      // If we use try/catch we will only allocate extra objects in the case of a
      // failure.
      /** @preserveTry */
      try {
        e.keyCode = -1;
        return;
      } catch (ex) {
        useReturnValue = true;
      }
    }
  
    if (useReturnValue ||
        /** @type {boolean|undefined} */ (e.returnValue) == undefined) {
      e.returnValue = true;
    }
  };
  
  
  /**
   * This is used to check if an IE event has already been handled by the Closure
   * system so we do not do the Closure pass twice for a bubbling event.
   * @param {Event} e  The IE browser event.
   * @return {boolean} True if the event object has been marked.
   * @private
   */
  goog.events.isMarkedIeEvent_ = function(e) {
    return e.keyCode < 0 || e.returnValue != undefined;
  };
  
  
  /**
   * Counter to create unique event ids.
   * @type {number}
   * @private
   */
  goog.events.uniqueIdCounter_ = 0;
  
  
  /**
   * Creates a unique event id.
   *
   * @param {string} identifier The identifier.
   * @return {string} A unique identifier.
   */
  goog.events.getUniqueId = function(identifier) {
    return identifier + '_' + goog.events.uniqueIdCounter_++;
  };
  
  
  // Register the browser event handler as an entry point, so that
  // it can be monitored for exception handling, etc.
  goog.debug.entryPointRegistry.register(
      /**
       * @param {function(!Function): !Function} transformer The transforming
       *     function.
       */
      function(transformer) {
        goog.events.handleBrowserEvent_ = transformer(
            goog.events.handleBrowserEvent_);
      });
  