// 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 An event manager for both native browser event * targets and custom JavaScript event targets * ({@code goog.events.Listenable}). This provides an abstraction * over browsers' event systems. * * It also provides a simulation of W3C event model's capture phase in * Internet Explorer (IE 8 and below). Caveat: the simulation does not * interact well with listeners registered directly on the elements * (bypassing goog.events) or even with listeners registered via * goog.events in a separate JS binary. In these cases, we provide * no ordering guarantees. * * The listeners will also automagically have their event objects patched, so * your handlers don't need to worry about the browser. * * Example usage: *
 * 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);
 * 
* * 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 */ // IMPLEMENTATION NOTES: // This uses an indirect lookup of listener functions to avoid // circular references between DOM (in IE) or XPCOM (in Mozilla) // objects which leak memory. Unfortunately, this design is now // problematic in modern browsers as it requires a global lookup table // in JavaScript. This lookup table needs to be cleaned up manually // (by calling #unlisten/#unlistenByKey), otherwise it will cause // memory leaks. (This does not apply to goog.events.EventTarget, which // no longer uses the global lookup table.) // // This uses 3 lookup tables/trees for native event targets. // 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.provide('goog.events.Key'); goog.provide('goog.events.ListenableType'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.debug.entryPointRegistry'); goog.require('goog.events.BrowserEvent'); goog.require('goog.events.BrowserFeature'); goog.require('goog.events.Listenable'); goog.require('goog.events.Listener'); goog.require('goog.object'); /** * @typedef {number|goog.events.ListenableKey} */ goog.events.Key; /** * @typedef {EventTarget|goog.events.Listenable} */ goog.events.ListenableType; /** * Container for storing event listeners and their proxies * @private {!Object.} */ 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 native event * target (such as a DOM element) or an object that has implemented * {@link goog.events.Listenable}. A listener can only be added once * to an object and if it is added again the key for the listener is * returned. Note that if the existing listener is a one-off listener * (registered via listenOnce), it will no longer be a one-off * listener after a call to listen(). * * @param {EventTarget|goog.events.Listenable} src The node to listen * to events on. * @param {string|Array.} type Event type or array of event types. * @param {Function|Object} listener Callback method, or an object * with a handleEvent function. WARNING: passing an Object is now * softly deprecated. * @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 {goog.events.Key} Unique key for the listener. */ goog.events.listen = function(src, type, listener, opt_capt, opt_handler) { 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; } listener = goog.events.wrapListener_(listener); if (goog.events.Listenable.isImplementedBy(src)) { return src.listen( /** @type {string} */ (type), listener, opt_capt, opt_handler); } else { return goog.events.listen_( /** @type {EventTarget} */ (src), type, listener, /* callOnce */ false, opt_capt, opt_handler); } }; /** * Adds an event listener for a specific event on a native event * target. A listener can only be added once to an object and if it * is added again the key for the listener is returned. * * Note that a one-off listener will not change an existing listener, * if any. On the other hand a normal listener will change existing * one-off listener to become a normal listener. * * @param {EventTarget} src The node to listen to events on. * @param {?string} type Event type or array of event types. * @param {!Function} listener Callback function. * @param {boolean} callOnce Whether the listener is a one-off * listener or otherwise. * @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 {goog.events.ListenableKey} Unique key for the listener. * @private */ goog.events.listen_ = function( src, type, listener, callOnce, opt_capt, opt_handler) { if (!type) { throw Error('Invalid event type'); } var capture = !!opt_capt; var map = goog.events.listenerTree_; if (!(type in map)) { map[type] = {count_: 0}; } map = map[type]; if (!(capture in map)) { map[capture] = {count_: 0}; map.count_++; } map = map[capture]; var srcUid = goog.getUid(src); var listenerArray, listenerObj; // 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; } if (!callOnce) { // Ensure that, if there is an existing callOnce listener, it is no // longer a callOnce listener. listenerArray[i].callOnce = false; } // We already have this listener. Return its key. return listenerArray[i]; } } } var proxy = goog.events.getProxy(); listenerObj = new goog.events.Listener( listener, proxy, src, type, capture, opt_handler); listenerObj.callOnce = callOnce; proxy.src = src; proxy.listener = listenerObj; listenerArray.push(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) { 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); } var key = listenerObj.key; goog.events.listeners_[key] = listenerObj; return listenerObj; }; /** * 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.listener, eventObject); } : function(eventObject) { var v = proxyCallbackFunction.call(f.src, f.listener, 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 ...) 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 native event * target (such as a DOM element) or an object that has implemented * {@link goog.events.Listenable}. After the event has fired the event * listener is removed from the target. * * If an existing listener already exists, listenOnce will do * nothing. In particular, if the listener was previously registered * via listen(), listenOnce() will not turn the listener into a * one-off listener. Similarly, if there is already an existing * one-off listener, listenOnce does not modify the listeners (it is * still a once listener). * * @param {EventTarget|goog.events.Listenable} src The node to listen * to events on. * @param {string|Array.} 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 {goog.events.Key} 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 listenableKey; listener = goog.events.wrapListener_(listener); if (goog.events.Listenable.isImplementedBy(src)) { listenableKey = src.listenOnce( /** @type {string} */ (type), listener, opt_capt, opt_handler); } else { listenableKey = goog.events.listen_( /** @type {EventTarget} */ (src), type, listener, /* callOnce */ true, opt_capt, opt_handler); } return listenableKey; }; /** * Adds an event listener with a specific event wrapper on a DOM Node or an * object that has implemented {@link goog.events.Listenable}. A listener can * only be added once to an object. * * @param {EventTarget|goog.events.Listenable} src The target 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.Listenable} src The target to stop * listening to events on. * @param {string|Array.} 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; } listener = goog.events.wrapListener_(listener); if (goog.events.Listenable.isImplementedBy(src)) { return src.unlisten( /** @type {string} */ (type), listener, opt_capt, opt_handler); } 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]); } } return false; }; /** * Removes an event listener which was added with listen() by the key * returned by listen(). * * @param {goog.events.Key} 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) { // TODO(user): Remove this check when tests that rely on this // are fixed. if (goog.isNumber(key)) { return false; } var listener = /** @type {goog.events.ListenableKey} */ (key); if (!listener) { return false; } if (listener.removed) { return false; } var src = listener.src; if (goog.events.Listenable.isImplementedBy(src)) { return src.unlistenByKey(listener); } var type = listener.type; var proxy = listener.proxy; var capture = listener.capture; if (src.removeEventListener) { src.removeEventListener(type, proxy, capture); } else if (src.detachEvent) { src.detachEvent(goog.events.getOnString_(type), proxy); } var srcUid = goog.getUid(src); // 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.markAsRemoved(); // 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) { goog.array.remove(listenerArray, listener); if (listenerArray.length == 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]; } } delete goog.events.listeners_[listener.key]; return true; }; /** * Removes an event listener which was added with listenWithWrapper(). * * @param {EventTarget|goog.events.Listenable} 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); }; /** * Removes all listeners from an object. You can also optionally * remove listeners of a particular type. * * @param {Object=} opt_obj Object to remove listeners from. Not * specifying opt_obj is now DEPRECATED (it used to remove all * registered listeners). * @param {string=} opt_type Type of event to, default is all types. * @return {number} Number of listeners removed. */ goog.events.removeAll = function(opt_obj, opt_type) { var count = 0; var noObj = opt_obj == null; var noType = opt_type == null; if (!noObj) { if (opt_obj && goog.events.Listenable.isImplementedBy(opt_obj)) { return opt_obj.removeAllListeners(opt_type); } 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) { goog.events.unlistenByKey(listener); count++; } } } } else { goog.object.forEach(goog.events.listeners_, function(listener) { goog.events.unlistenByKey(listener); count++; }); } return count; }; /** * Removes all native listeners registered via goog.events. Native * listeners are listeners on native browser objects (such as DOM * elements). In particular, goog.events.Listenable and * goog.events.EventTarget listeners will NOT be removed. * @return {number} Number of listeners removed. */ goog.events.removeAllNativeListeners = function() { var count = 0; // All listeners in goog.events.listeners_ are native listeners, // custom listenable is no longer inserted to this table. goog.object.forEach(goog.events.listeners_, function(listener) { goog.events.unlistenByKey(listener); 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.} Array of listener objects. */ goog.events.getListeners = function(obj, type, capture) { if (goog.events.Listenable.isImplementedBy(obj)) { return obj.getListeners(type, capture); } else { 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.?} 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.Listenable} src The target 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.ListenableKey} the found listener or null if not found. */ goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { var capture = !!opt_capt; listener = goog.events.wrapListener_(listener); if (goog.events.Listenable.isImplementedBy(src)) { return src.getListener( /** @type {string} */ (type), listener, capture, opt_handler); } 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.Listenable} 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) { if (goog.events.Listenable.isImplementedBy(obj)) { return obj.hasListener(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 with 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. * @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) { if (goog.events.Listenable.isImplementedBy(obj)) { return obj.fireListeners(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]) { // 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 listenerArray = goog.array.clone(map[objUid]); for (var i = 0; i < listenerArray.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; } } } 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) { var listenerFn = listener.listener; var listenerHandler = listener.handler || listener.src; if (listener.callOnce) { goog.events.unlistenByKey(listener); } return listenerFn.call(listenerHandler, 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.Listenable} src The event target. * @param {goog.events.EventLike} 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) { goog.asserts.assert( goog.events.Listenable.isImplementedBy(src), 'Can not use goog.events.dispatchEvent with ' + 'non-goog.events.Listenable instance.'); return src.dispatchEvent(e); }; /** * 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 {goog.events.Listener} listener The listener object. * @param {Event=} opt_evt Optional event object that gets passed in via the * native event handlers. * @return {boolean} Result of the event handler. * @this {EventTarget} The object or Element that fired the event. * @private */ goog.events.handleBrowserEvent_ = function(listener, opt_evt) { if (listener.removed) { return true; } 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, this); retval = true; try { if (hasCapture) { var ancestors = []; for (var parent = evt.currentTarget; parent; parent = parent.parentNode) { ancestors.push(parent); } targetsMap = map[true]; // Call capture listeners for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0; i--) { evt.currentTarget = ancestors[i]; retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type, true, evt); } if (hasBubble) { targetsMap = map[false]; // Call bubble listeners for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; 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 {EventTarget} */ (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. * @idGenerator */ goog.events.getUniqueId = function(identifier) { return identifier + '_' + goog.events.uniqueIdCounter_++; }; /** * Expando property for listener function wrapper for Object with * handleEvent. * @type {string} * @private */ goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' + ((Math.random() * 1e9) >>> 0); /** * @param {Object|Function} listener The listener function or an * object that contains handleEvent method. * @return {!Function} Either the original function or a function that * calls obj.handleEvent. If the same listener is passed to this * function more than once, the same function is guaranteed to be * returned. * @private */ goog.events.wrapListener_ = function(listener) { goog.asserts.assert(listener, 'Listener can not be null.'); if (goog.isFunction(listener)) { return listener; } goog.asserts.assert( listener.handleEvent, 'An object listener must have handleEvent method.'); return listener[goog.events.LISTENER_WRAPPER_PROP_] || (listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) { return listener.handleEvent(e); }); }; // 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_); });