# lib/goog/events/events.js

    1// Copyright 2005 The Closure Library Authors. All Rights Reserved.2//3// Licensed under the Apache License, Version 2.0 (the "License");4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an "AS-IS" BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.1415/**16 * @fileoverview An event manager for both native browser event17 * targets and custom JavaScript event targets18 * ({@code goog.events.Listenable}). This provides an abstraction19 * over browsers' event systems.20 *21 * It also provides a simulation of W3C event model's capture phase in22 * Internet Explorer (IE 8 and below). Caveat: the simulation does not23 * interact well with listeners registered directly on the elements24 * (bypassing goog.events) or even with listeners registered via25 * goog.events in a separate JS binary. In these cases, we provide26 * no ordering guarantees.27 *28 * The listeners will receive a "patched" event object. Such event object29 * contains normalized values for certain event properties that differs in30 * different browsers.31 *32 * Example usage:33 * <pre>34 * goog.events.listen(myNode, 'click', function(e) { alert('woo') });35 * goog.events.listen(myNode, 'mouseover', mouseHandler, true);36 * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);37 * goog.events.removeAll(myNode);38 * </pre>39 *40 *                                            in IE and event object patching]41 *42 * @see ../demos/events.html43 * @see ../demos/event-propagation.html44 * @see ../demos/stopevent.html45 */4647// IMPLEMENTATION NOTES:48// goog.events stores an auxiliary data structure on each EventTarget49// source being listened on. This allows us to take advantage of GC,50// having the data structure GC'd when the EventTarget is GC'd. This51// GC behavior is equivalent to using W3C DOM Events directly.5253goog.provide('goog.events');54goog.provide('goog.events.CaptureSimulationMode');55goog.provide('goog.events.Key');56goog.provide('goog.events.ListenableType');5758goog.require('goog.asserts');59goog.require('goog.debug.entryPointRegistry');60goog.require('goog.events.BrowserEvent');61goog.require('goog.events.BrowserFeature');62goog.require('goog.events.Listenable');63goog.require('goog.events.ListenerMap');6465goog.forwardDeclare('goog.debug.ErrorHandler');66goog.forwardDeclare('goog.events.EventWrapper');676869/**70 * @typedef {number|goog.events.ListenableKey}71 */72goog.events.Key;737475/**76 * @typedef {EventTarget|goog.events.Listenable}77 */78goog.events.ListenableType;798081/**82 * Property name on a native event target for the listener map83 * associated with the event target.84 * @private @const {string}85 */86goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);878889/**90 * String used to prepend to IE event types.91 * @const92 * @private93 */94goog.events.onString_ = 'on';959697/**98 * Map of computed "on<eventname>" strings for IE event types. Caching99 * this removes an extra object allocation in goog.events.listen which100 * improves IE6 performance.101 * @const102 * @dict103 * @private104 */105goog.events.onStringMap_ = {};106107108/**109 * @enum {number} Different capture simulation mode for IE8-.110 */111goog.events.CaptureSimulationMode = {112  /**113   * Does not perform capture simulation. Will asserts in IE8- when you114   * add capture listeners.115   */116  OFF_AND_FAIL: 0,117118  /**119   * Does not perform capture simulation, silently ignore capture120   * listeners.121   */122  OFF_AND_SILENT: 1,123124  /**125   * Performs capture simulation.126   */127  ON: 2128};129130131/**132 * @define {number} The capture simulation mode for IE8-. By default,133 *     this is ON.134 */135goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);136137138/**139 * Estimated count of total native listeners.140 * @private {number}141 */142goog.events.listenerCountEstimate_ = 0;143144145/**146 * Adds an event listener for a specific event on a native event147 * target (such as a DOM element) or an object that has implemented148 * {@link goog.events.Listenable}. A listener can only be added once149 * to an object and if it is added again the key for the listener is150 * returned. Note that if the existing listener is a one-off listener151 * (registered via listenOnce), it will no longer be a one-off152 * listener after a call to listen().153 *154 * @param {EventTarget|goog.events.Listenable} src The node to listen155 *     to events on.156 * @param {string|Array.<string>|157 *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}158 *     type Event type or array of event types.159 * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}160 *     listener Callback method, or an object with a handleEvent function.161 *     WARNING: passing an Object is now softly deprecated.162 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to163 *     false).164 * @param {T=} opt_handler Element in whose scope to call the listener.165 * @return {goog.events.Key} Unique key for the listener.166 * @template T,EVENTOBJ167 */168goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {169  if (goog.isArray(type)) {170    for (var i = 0; i < type.length; i++) {171      goog.events.listen(src, type[i], listener, opt_capt, opt_handler);172    }173    return null;174  }175176  listener = goog.events.wrapListener(listener);177  if (goog.events.Listenable.isImplementedBy(src)) {178    return src.listen(179        /** @type {string|!goog.events.EventId} */ (type),180        listener, opt_capt, opt_handler);181  } else {182    return goog.events.listen_(183        /** @type {EventTarget} */ (src),184        /** @type {string|!goog.events.EventId} */ (type),185        listener, /* callOnce */ false, opt_capt, opt_handler);186  }187};188189190/**191 * Adds an event listener for a specific event on a native event192 * target. A listener can only be added once to an object and if it193 * is added again the key for the listener is returned.194 *195 * Note that a one-off listener will not change an existing listener,196 * if any. On the other hand a normal listener will change existing197 * one-off listener to become a normal listener.198 *199 * @param {EventTarget} src The node to listen to events on.200 * @param {string|!goog.events.EventId} type Event type.201 * @param {!Function} listener Callback function.202 * @param {boolean} callOnce Whether the listener is a one-off203 *     listener or otherwise.204 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to205 *     false).206 * @param {Object=} opt_handler Element in whose scope to call the listener.207 * @return {goog.events.ListenableKey} Unique key for the listener.208 * @private209 */210goog.events.listen_ = function(211    src, type, listener, callOnce, opt_capt, opt_handler) {212  if (!type) {213    throw Error('Invalid event type');214  }215216  var capture = !!opt_capt;217  if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {218    if (goog.events.CAPTURE_SIMULATION_MODE ==219        goog.events.CaptureSimulationMode.OFF_AND_FAIL) {220      goog.asserts.fail('Can not register capture listener in IE8-.');221      return null;222    } else if (goog.events.CAPTURE_SIMULATION_MODE ==223        goog.events.CaptureSimulationMode.OFF_AND_SILENT) {224      return null;225    }226  }227228  var listenerMap = goog.events.getListenerMap_(src);229  if (!listenerMap) {230    src[goog.events.LISTENER_MAP_PROP_] = listenerMap =231        new goog.events.ListenerMap(src);232  }233234  var listenerObj = listenerMap.add(235      type, listener, callOnce, opt_capt, opt_handler);236237  // If the listenerObj already has a proxy, it has been set up238  // previously. We simply return.239  if (listenerObj.proxy) {240    return listenerObj;241  }242243  var proxy = goog.events.getProxy();244  listenerObj.proxy = proxy;245246  proxy.src = src;247  proxy.listener = listenerObj;248249  // Attach the proxy through the browser's API250  if (src.addEventListener) {251    src.addEventListener(type.toString(), proxy, capture);252  } else {253    // The else above used to be else if (src.attachEvent) and then there was254    // another else statement that threw an exception warning the developer255    // they made a mistake. This resulted in an extra object allocation in IE6256    // due to a wrapper object that had to be implemented around the element257    // and so was removed.258    src.attachEvent(goog.events.getOnString_(type.toString()), proxy);259  }260261  goog.events.listenerCountEstimate_++;262  return listenerObj;263};264265266/**267 * Helper function for returning a proxy function.268 * @return {!Function} A new or reused function object.269 */270goog.events.getProxy = function() {271  var proxyCallbackFunction = goog.events.handleBrowserEvent_;272  // Use a local var f to prevent one allocation.273  var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?274      function(eventObject) {275        return proxyCallbackFunction.call(f.src, f.listener, eventObject);276      } :277      function(eventObject) {278        var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);279        // NOTE(user): In IE, we hack in a capture phase. However, if280        // there is inline event handler which tries to prevent default (for281        // example <a href="..." onclick="return false">...</a>) in a282        // descendant element, the prevent default will be overridden283        // by this listener if this listener were to return true. Hence, we284        // return undefined.285        if (!v) return v;286      };287  return f;288};289290291/**292 * Adds an event listener for a specific event on a native event293 * target (such as a DOM element) or an object that has implemented294 * {@link goog.events.Listenable}. After the event has fired the event295 * listener is removed from the target.296 *297 * If an existing listener already exists, listenOnce will do298 * nothing. In particular, if the listener was previously registered299 * via listen(), listenOnce() will not turn the listener into a300 * one-off listener. Similarly, if there is already an existing301 * one-off listener, listenOnce does not modify the listeners (it is302 * still a once listener).303 *304 * @param {EventTarget|goog.events.Listenable} src The node to listen305 *     to events on.306 * @param {string|Array.<string>|307 *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}308 *     type Event type or array of event types.309 * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}310 *     listener Callback method.311 * @param {boolean=} opt_capt Fire in capture phase?.312 * @param {T=} opt_handler Element in whose scope to call the listener.313 * @return {goog.events.Key} Unique key for the listener.314 * @template T,EVENTOBJ315 */316goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {317  if (goog.isArray(type)) {318    for (var i = 0; i < type.length; i++) {319      goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);320    }321    return null;322  }323324  listener = goog.events.wrapListener(listener);325  if (goog.events.Listenable.isImplementedBy(src)) {326    return src.listenOnce(327        /** @type {string|!goog.events.EventId} */ (type),328        listener, opt_capt, opt_handler);329  } else {330    return goog.events.listen_(331        /** @type {EventTarget} */ (src),332        /** @type {string|!goog.events.EventId} */ (type),333        listener, /* callOnce */ true, opt_capt, opt_handler);334  }335};336337338/**339 * Adds an event listener with a specific event wrapper on a DOM Node or an340 * object that has implemented {@link goog.events.Listenable}. A listener can341 * only be added once to an object.342 *343 * @param {EventTarget|goog.events.Listenable} src The target to344 *     listen to events on.345 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.346 * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener347 *     Callback method, or an object with a handleEvent function.348 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to349 *     false).350 * @param {T=} opt_handler Element in whose scope to call the listener.351 * @template T352 */353goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,354    opt_handler) {355  wrapper.listen(src, listener, opt_capt, opt_handler);356};357358359/**360 * Removes an event listener which was added with listen().361 *362 * @param {EventTarget|goog.events.Listenable} src The target to stop363 *     listening to events on.364 * @param {string|Array.<string>|365 *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}366 *     type Event type or array of event types to unlisten to.367 * @param {function(?):?|{handleEvent:function(?):?}|null} listener The368 *     listener function to remove.369 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines370 *     whether the listener is fired during the capture or bubble phase of the371 *     event.372 * @param {Object=} opt_handler Element in whose scope to call the listener.373 * @return {?boolean} indicating whether the listener was there to remove.374 * @template EVENTOBJ375 */376goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {377  if (goog.isArray(type)) {378    for (var i = 0; i < type.length; i++) {379      goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);380    }381    return null;382  }383384  listener = goog.events.wrapListener(listener);385  if (goog.events.Listenable.isImplementedBy(src)) {386    return src.unlisten(387        /** @type {string|!goog.events.EventId} */ (type),388        listener, opt_capt, opt_handler);389  }390391  if (!src) {392    // TODO(user): We should tighten the API to only accept393    // non-null objects, or add an assertion here.394    return false;395  }396397  var capture = !!opt_capt;398  var listenerMap = goog.events.getListenerMap_(399      /** @type {EventTarget} */ (src));400  if (listenerMap) {401    var listenerObj = listenerMap.getListener(402        /** @type {string|!goog.events.EventId} */ (type),403        listener, capture, opt_handler);404    if (listenerObj) {405      return goog.events.unlistenByKey(listenerObj);406    }407  }408409  return false;410};411412413/**414 * Removes an event listener which was added with listen() by the key415 * returned by listen().416 *417 * @param {goog.events.Key} key The key returned by listen() for this418 *     event listener.419 * @return {boolean} indicating whether the listener was there to remove.420 */421goog.events.unlistenByKey = function(key) {422  // TODO(user): Remove this check when tests that rely on this423  // are fixed.424  if (goog.isNumber(key)) {425    return false;426  }427428  var listener = /** @type {goog.events.ListenableKey} */ (key);429  if (!listener || listener.removed) {430    return false;431  }432433  var src = listener.src;434  if (goog.events.Listenable.isImplementedBy(src)) {435    return src.unlistenByKey(listener);436  }437438  var type = listener.type;439  var proxy = listener.proxy;440  if (src.removeEventListener) {441    src.removeEventListener(type, proxy, listener.capture);442  } else if (src.detachEvent) {443    src.detachEvent(goog.events.getOnString_(type), proxy);444  }445  goog.events.listenerCountEstimate_--;446447  var listenerMap = goog.events.getListenerMap_(448      /** @type {EventTarget} */ (src));449  // TODO(user): Try to remove this conditional and execute the450  // first branch always. This should be safe.451  if (listenerMap) {452    listenerMap.removeByKey(listener);453    if (listenerMap.getTypeCount() == 0) {454      // Null the src, just because this is simple to do (and useful455      // for IE <= 7).456      listenerMap.src = null;457      // We don't use delete here because IE does not allow delete458      // on a window object.459      src[goog.events.LISTENER_MAP_PROP_] = null;460    }461  } else {462    listener.markAsRemoved();463  }464465  return true;466};467468469/**470 * Removes an event listener which was added with listenWithWrapper().471 *472 * @param {EventTarget|goog.events.Listenable} src The target to stop473 *     listening to events on.474 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.475 * @param {function(?):?|{handleEvent:function(?):?}|null} listener The476 *     listener function to remove.477 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines478 *     whether the listener is fired during the capture or bubble phase of the479 *     event.480 * @param {Object=} opt_handler Element in whose scope to call the listener.481 */482goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,483    opt_handler) {484  wrapper.unlisten(src, listener, opt_capt, opt_handler);485};486487488/**489 * Removes all listeners from an object. You can also optionally490 * remove listeners of a particular type.491 *492 * @param {Object|undefined} obj Object to remove listeners from. Must be an493 *     EventTarget or a goog.events.Listenable.494 * @param {string|!goog.events.EventId=} opt_type Type of event to remove.495 *     Default is all types.496 * @return {number} Number of listeners removed.497 */498goog.events.removeAll = function(obj, opt_type) {499  // TODO(user): Change the type of obj to500  // (!EventTarget|!goog.events.Listenable).501502  if (!obj) {503    return 0;504  }505506  if (goog.events.Listenable.isImplementedBy(obj)) {507    return obj.removeAllListeners(opt_type);508  }509510  var listenerMap = goog.events.getListenerMap_(511      /** @type {EventTarget} */ (obj));512  if (!listenerMap) {513    return 0;514  }515516  var count = 0;517  var typeStr = opt_type && opt_type.toString();518  for (var type in listenerMap.listeners) {519    if (!typeStr || type == typeStr) {520      // Clone so that we don't need to worry about unlistenByKey521      // changing the content of the ListenerMap.522      var listeners = listenerMap.listeners[type].concat();523      for (var i = 0; i < listeners.length; ++i) {524        if (goog.events.unlistenByKey(listeners[i])) {525          ++count;526        }527      }528    }529  }530  return count;531};532533534/**535 * Removes all native listeners registered via goog.events. Native536 * listeners are listeners on native browser objects (such as DOM537 * elements). In particular, goog.events.Listenable and538 * goog.events.EventTarget listeners will NOT be removed.539 * @return {number} Number of listeners removed.540 * @deprecated This doesn't do anything, now that Closure no longer541 * stores a central listener registry.542 */543goog.events.removeAllNativeListeners = function() {544  goog.events.listenerCountEstimate_ = 0;545  return 0;546};547548549/**550 * Gets the listeners for a given object, type and capture phase.551 *552 * @param {Object} obj Object to get listeners for.553 * @param {string|!goog.events.EventId} type Event type.554 * @param {boolean} capture Capture phase?.555 * @return {Array.<goog.events.Listener>} Array of listener objects.556 */557goog.events.getListeners = function(obj, type, capture) {558  if (goog.events.Listenable.isImplementedBy(obj)) {559    return obj.getListeners(type, capture);560  } else {561    if (!obj) {562      // TODO(user): We should tighten the API to accept563      // !EventTarget|goog.events.Listenable, and add an assertion here.564      return [];565    }566567    var listenerMap = goog.events.getListenerMap_(568        /** @type {EventTarget} */ (obj));569    return listenerMap ? listenerMap.getListeners(type, capture) : [];570  }571};572573574/**575 * Gets the goog.events.Listener for the event or null if no such listener is576 * in use.577 *578 * @param {EventTarget|goog.events.Listenable} src The target from579 *     which to get listeners.580 * @param {?string|!goog.events.EventId.<EVENTOBJ>} type The type of the event.581 * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The582 *     listener function to get.583 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines584 *                            whether the listener is fired during the585 *                            capture or bubble phase of the event.586 * @param {Object=} opt_handler Element in whose scope to call the listener.587 * @return {goog.events.ListenableKey} the found listener or null if not found.588 * @template EVENTOBJ589 */590goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {591  // TODO(user): Change type from ?string to string, or add assertion.592  type = /** @type {string} */ (type);593  listener = goog.events.wrapListener(listener);594  var capture = !!opt_capt;595  if (goog.events.Listenable.isImplementedBy(src)) {596    return src.getListener(type, listener, capture, opt_handler);597  }598599  if (!src) {600    // TODO(user): We should tighten the API to only accept601    // non-null objects, or add an assertion here.602    return null;603  }604605  var listenerMap = goog.events.getListenerMap_(606      /** @type {EventTarget} */ (src));607  if (listenerMap) {608    return listenerMap.getListener(type, listener, capture, opt_handler);609  }610  return null;611};612613614/**615 * Returns whether an event target has any active listeners matching the616 * specified signature. If either the type or capture parameters are617 * unspecified, the function will match on the remaining criteria.618 *619 * @param {EventTarget|goog.events.Listenable} obj Target to get620 *     listeners for.621 * @param {string|!goog.events.EventId=} opt_type Event type.622 * @param {boolean=} opt_capture Whether to check for capture or bubble-phase623 *     listeners.624 * @return {boolean} Whether an event target has one or more listeners matching625 *     the requested type and/or capture phase.626 */627goog.events.hasListener = function(obj, opt_type, opt_capture) {628  if (goog.events.Listenable.isImplementedBy(obj)) {629    return obj.hasListener(opt_type, opt_capture);630  }631632  var listenerMap = goog.events.getListenerMap_(633      /** @type {EventTarget} */ (obj));634  return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);635};636637638/**639 * Provides a nice string showing the normalized event objects public members640 * @param {Object} e Event Object.641 * @return {string} String of the public members of the normalized event object.642 */643goog.events.expose = function(e) {644  var str = [];645  for (var key in e) {646    if (e[key] && e[key].id) {647      str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');648    } else {649      str.push(key + ' = ' + e[key]);650    }651  }652  return str.join('\n');653};654655656/**657 * Returns a string with on prepended to the specified type. This is used for IE658 * which expects "on" to be prepended. This function caches the string in order659 * to avoid extra allocations in steady state.660 * @param {string} type Event type.661 * @return {string} The type string with 'on' prepended.662 * @private663 */664goog.events.getOnString_ = function(type) {665  if (type in goog.events.onStringMap_) {666    return goog.events.onStringMap_[type];667  }668  return goog.events.onStringMap_[type] = goog.events.onString_ + type;669};670671672/**673 * Fires an object's listeners of a particular type and phase674 *675 * @param {Object} obj Object whose listeners to call.676 * @param {string|!goog.events.EventId} type Event type.677 * @param {boolean} capture Which event phase.678 * @param {Object} eventObject Event object to be passed to listener.679 * @return {boolean} True if all listeners returned true else false.680 */681goog.events.fireListeners = function(obj, type, capture, eventObject) {682  if (goog.events.Listenable.isImplementedBy(obj)) {683    return obj.fireListeners(type, capture, eventObject);684  }685686  return goog.events.fireListeners_(obj, type, capture, eventObject);687};688689690/**691 * Fires an object's listeners of a particular type and phase.692 * @param {Object} obj Object whose listeners to call.693 * @param {string|!goog.events.EventId} type Event type.694 * @param {boolean} capture Which event phase.695 * @param {Object} eventObject Event object to be passed to listener.696 * @return {boolean} True if all listeners returned true else false.697 * @private698 */699goog.events.fireListeners_ = function(obj, type, capture, eventObject) {700  var retval = 1;701702  var listenerMap = goog.events.getListenerMap_(703      /** @type {EventTarget} */ (obj));704  if (listenerMap) {705    // TODO(user): Original code avoids array creation when there706    // is no listener, so we do the same. If this optimization turns707    // out to be not required, we can replace this with708    // listenerMap.getListeners(type, capture) instead, which is simpler.709    var listenerArray = listenerMap.listeners[type.toString()];710    if (listenerArray) {711      listenerArray = listenerArray.concat();712      for (var i = 0; i < listenerArray.length; i++) {713        var listener = listenerArray[i];714        // We might not have a listener if the listener was removed.715        if (listener && listener.capture == capture && !listener.removed) {716          retval &=717              goog.events.fireListener(listener, eventObject) !== false;718        }719      }720    }721  }722  return Boolean(retval);723};724725726/**727 * Fires a listener with a set of arguments728 *729 * @param {goog.events.Listener} listener The listener object to call.730 * @param {Object} eventObject The event object to pass to the listener.731 * @return {boolean} Result of listener.732 */733goog.events.fireListener = function(listener, eventObject) {734  var listenerFn = listener.listener;735  var listenerHandler = listener.handler || listener.src;736737  if (listener.callOnce) {738    goog.events.unlistenByKey(listener);739  }740  return listenerFn.call(listenerHandler, eventObject);741};742743744/**745 * Gets the total number of listeners currently in the system.746 * @return {number} Number of listeners.747 * @deprecated This returns estimated count, now that Closure no longer748 * stores a central listener registry. We still return an estimation749 * to keep existing listener-related tests passing. In the near future,750 * this function will be removed.751 */752goog.events.getTotalListenerCount = function() {753  return goog.events.listenerCountEstimate_;754};755756757/**758 * Dispatches an event (or event like object) and calls all listeners759 * listening for events of this type. The type of the event is decided by the760 * type property on the event object.761 *762 * If any of the listeners returns false OR calls preventDefault then this763 * function will return false.  If one of the capture listeners calls764 * stopPropagation, then the bubble listeners won't fire.765 *766 * @param {goog.events.Listenable} src The event target.767 * @param {goog.events.EventLike} e Event object.768 * @return {boolean} If anyone called preventDefault on the event object (or769 *     if any of the handlers returns false) this will also return false.770 *     If there are no handlers, or if all handlers return true, this returns771 *     true.772 */773goog.events.dispatchEvent = function(src, e) {774  goog.asserts.assert(775      goog.events.Listenable.isImplementedBy(src),776      'Can not use goog.events.dispatchEvent with ' +777      'non-goog.events.Listenable instance.');778  return src.dispatchEvent(e);779};780781782/**783 * Installs exception protection for the browser event entry point using the784 * given error handler.785 *786 * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to787 *     protect the entry point.788 */789goog.events.protectBrowserEventEntryPoint = function(errorHandler) {790  goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(791      goog.events.handleBrowserEvent_);792};793794795/**796 * Handles an event and dispatches it to the correct listeners. This797 * function is a proxy for the real listener the user specified.798 *799 * @param {goog.events.Listener} listener The listener object.800 * @param {Event=} opt_evt Optional event object that gets passed in via the801 *     native event handlers.802 * @return {boolean} Result of the event handler.803 * @this {EventTarget} The object or Element that fired the event.804 * @private805 */806goog.events.handleBrowserEvent_ = function(listener, opt_evt) {807  if (listener.removed) {808    return true;809  }810811  // Synthesize event propagation if the browser does not support W3C812  // event model.813  if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {814    var ieEvent = opt_evt ||815        /** @type {Event} */ (goog.getObjectByName('window.event'));816    var evt = new goog.events.BrowserEvent(ieEvent, this);817    var retval = true;818819    if (goog.events.CAPTURE_SIMULATION_MODE ==820            goog.events.CaptureSimulationMode.ON) {821      // If we have not marked this event yet, we should perform capture822      // simulation.823      if (!goog.events.isMarkedIeEvent_(ieEvent)) {824        goog.events.markIeEvent_(ieEvent);825826        var ancestors = [];827        for (var parent = evt.currentTarget; parent;828             parent = parent.parentNode) {829          ancestors.push(parent);830        }831832        // Fire capture listeners.833        var type = listener.type;834        for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;835             i--) {836          evt.currentTarget = ancestors[i];837          retval &= goog.events.fireListeners_(ancestors[i], type, true, evt);838        }839840        // Fire bubble listeners.841        //842        // We can technically rely on IE to perform bubble event843        // propagation. However, it turns out that IE fires events in844        // opposite order of attachEvent registration, which broke845        // some code and tests that rely on the order. (While W3C DOM846        // Level 2 Events TR leaves the event ordering unspecified,847        // modern browsers and W3C DOM Level 3 Events Working Draft848        // actually specify the order as the registration order.)849        for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {850          evt.currentTarget = ancestors[i];851          retval &= goog.events.fireListeners_(ancestors[i], type, false, evt);852        }853      }854    } else {855      retval = goog.events.fireListener(listener, evt);856    }857    return retval;858  }859860  // Otherwise, simply fire the listener.861  return goog.events.fireListener(862      listener, new goog.events.BrowserEvent(opt_evt, this));863};864865866/**867 * This is used to mark the IE event object so we do not do the Closure pass868 * twice for a bubbling event.869 * @param {Event} e The IE browser event.870 * @private871 */872goog.events.markIeEvent_ = function(e) {873  // Only the keyCode and the returnValue can be changed. We use keyCode for874  // non keyboard events.875  // event.returnValue is a bit more tricky. It is undefined by default. A876  // boolean false prevents the default action. In a window.onbeforeunload and877  // the returnValue is non undefined it will be alerted. However, we will only878  // modify the returnValue for keyboard events. We can get a problem if non879  // closure events sets the keyCode or the returnValue880881  var useReturnValue = false;882883  if (e.keyCode == 0) {884    // We cannot change the keyCode in case that srcElement is input[type=file].885    // We could test that that is the case but that would allocate 3 objects.886    // If we use try/catch we will only allocate extra objects in the case of a887    // failure.888    /** @preserveTry */889    try {890      e.keyCode = -1;891      return;892    } catch (ex) {893      useReturnValue = true;894    }895  }896897  if (useReturnValue ||898      /** @type {boolean|undefined} */ (e.returnValue) == undefined) {899    e.returnValue = true;900  }901};902903904/**905 * This is used to check if an IE event has already been handled by the Closure906 * system so we do not do the Closure pass twice for a bubbling event.907 * @param {Event} e  The IE browser event.908 * @return {boolean} True if the event object has been marked.909 * @private910 */911goog.events.isMarkedIeEvent_ = function(e) {912  return e.keyCode < 0 || e.returnValue != undefined;913};914915916/**917 * Counter to create unique event ids.918 * @private {number}919 */920goog.events.uniqueIdCounter_ = 0;921922923/**924 * Creates a unique event id.925 *926 * @param {string} identifier The identifier.927 * @return {string} A unique identifier.928 * @idGenerator929 */930goog.events.getUniqueId = function(identifier) {931  return identifier + '_' + goog.events.uniqueIdCounter_++;932};933934935/**936 * @param {EventTarget} src The source object.937 * @return {goog.events.ListenerMap} A listener map for the given938 *     source object, or null if none exists.939 * @private940 */941goog.events.getListenerMap_ = function(src) {942  var listenerMap = src[goog.events.LISTENER_MAP_PROP_];943  // IE serializes the property as well (e.g. when serializing outer944  // HTML). So we must check that the value is of the correct type.945  return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;946};947948949/**950 * Expando property for listener function wrapper for Object with951 * handleEvent.952 * @private @const {string}953 */954goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' +955    ((Math.random() * 1e9) >>> 0);956957958/**959 * @param {Object|Function} listener The listener function or an960 *     object that contains handleEvent method.961 * @return {!Function} Either the original function or a function that962 *     calls obj.handleEvent. If the same listener is passed to this963 *     function more than once, the same function is guaranteed to be964 *     returned.965 */966goog.events.wrapListener = function(listener) {967  goog.asserts.assert(listener, 'Listener can not be null.');968969  if (goog.isFunction(listener)) {970    return listener;971  }972973  goog.asserts.assert(974      listener.handleEvent, 'An object listener must have handleEvent method.');975  if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {976    listener[goog.events.LISTENER_WRAPPER_PROP_] =977        function(e) { return listener.handleEvent(e); };978  }979  return listener[goog.events.LISTENER_WRAPPER_PROP_];980};981982983// Register the browser event handler as an entry point, so that984// it can be monitored for exception handling, etc.985goog.debug.entryPointRegistry.register(986    /**987     * @param {function(!Function): !Function} transformer The transforming988     *     function.989     */990    function(transformer) {991      goog.events.handleBrowserEvent_ = transformer(992          goog.events.handleBrowserEvent_);993    });