// 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 receive a "patched" event object. Such event object * contains normalized values for certain event properties that differs in * different browsers. * * 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] * @author arv@google.com (Erik Arvidsson) * * @see ../demos/events.html * @see ../demos/event-propagation.html * @see ../demos/stopevent.html */ // IMPLEMENTATION NOTES: // goog.events stores an auxiliary data structure on each EventTarget // source being listened on. This allows us to take advantage of GC, // having the data structure GC'd when the EventTarget is GC'd. This // GC behavior is equivalent to using W3C DOM Events directly. goog.provide('goog.events'); goog.provide('goog.events.CaptureSimulationMode'); goog.provide('goog.events.Key'); goog.provide('goog.events.ListenableType'); 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.ListenerMap'); goog.forwardDeclare('goog.debug.ErrorHandler'); goog.forwardDeclare('goog.events.EventWrapper'); /** * @typedef {number|goog.events.ListenableKey} */ goog.events.Key; /** * @typedef {EventTarget|goog.events.Listenable} */ goog.events.ListenableType; /** * Property name on a native event target for the listener map * associated with the event target. * @private @const {string} */ goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0); /** * String used to prepend to IE event types. * @const * @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. * @const * @dict * @private */ goog.events.onStringMap_ = {}; /** * @enum {number} Different capture simulation mode for IE8-. */ goog.events.CaptureSimulationMode = { /** * Does not perform capture simulation. Will asserts in IE8- when you * add capture listeners. */ OFF_AND_FAIL: 0, /** * Does not perform capture simulation, silently ignore capture * listeners. */ OFF_AND_SILENT: 1, /** * Performs capture simulation. */ ON: 2 }; /** * @define {number} The capture simulation mode for IE8-. By default, * this is ON. */ goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2); /** * Estimated count of total native listeners. * @private {number} */ goog.events.listenerCountEstimate_ = 0; /** * 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| * !goog.events.EventId|!Array>} * type Event type or array of event types. * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null} * listener Callback method, or an object with a handleEvent function. * WARNING: passing an Object is now softly deprecated. * @param {(boolean|!AddEventListenerOptions)=} opt_options * @param {T=} opt_handler Element in whose scope to call the listener. * @return {goog.events.Key} Unique key for the listener. * @template T,EVENTOBJ */ goog.events.listen = function(src, type, listener, opt_options, opt_handler) { if (opt_options && opt_options.once) { return goog.events.listenOnce( src, type, listener, opt_options, opt_handler); } if (goog.isArray(type)) { for (var i = 0; i < type.length; i++) { goog.events.listen(src, type[i], listener, opt_options, opt_handler); } return null; } listener = goog.events.wrapListener(listener); if (goog.events.Listenable.isImplementedBy(src)) { var capture = goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; return src.listen( /** @type {string|!goog.events.EventId} */ (type), listener, capture, opt_handler); } else { return goog.events.listen_( /** @type {!EventTarget} */ (src), type, listener, /* callOnce */ false, opt_options, 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|?goog.events.EventId} type Event type. * @param {!Function} listener Callback function. * @param {boolean} callOnce Whether the listener is a one-off * listener or otherwise. * @param {(boolean|!AddEventListenerOptions)=} opt_options * @param {Object=} opt_handler Element in whose scope to call the listener. * @return {goog.events.ListenableKey} Unique key for the listener. * @template EVENTOBJ * @private */ goog.events.listen_ = function( src, type, listener, callOnce, opt_options, opt_handler) { if (!type) { throw Error('Invalid event type'); } var capture = goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) { if (goog.events.CAPTURE_SIMULATION_MODE == goog.events.CaptureSimulationMode.OFF_AND_FAIL) { goog.asserts.fail('Can not register capture listener in IE8-.'); return null; } else if ( goog.events.CAPTURE_SIMULATION_MODE == goog.events.CaptureSimulationMode.OFF_AND_SILENT) { return null; } } var listenerMap = goog.events.getListenerMap_(src); if (!listenerMap) { src[goog.events.LISTENER_MAP_PROP_] = listenerMap = new goog.events.ListenerMap(src); } var listenerObj = /** @type {goog.events.Listener} */ ( listenerMap.add(type, listener, callOnce, capture, opt_handler)); // If the listenerObj already has a proxy, it has been set up // previously. We simply return. if (listenerObj.proxy) { return listenerObj; } var proxy = goog.events.getProxy(); listenerObj.proxy = proxy; proxy.src = src; proxy.listener = listenerObj; // Attach the proxy through the browser's API if (src.addEventListener) { // Don't pass an object as `capture` if the browser doesn't support that. if (!goog.events.BrowserFeature.PASSIVE_EVENTS) { opt_options = capture; } // Don't break tests that expect a boolean. if (opt_options === undefined) opt_options = false; src.addEventListener(type.toString(), proxy, opt_options); } else if (src.attachEvent) { // The else if above used to be an unconditional else. It would call // exception on IE11, spoiling the day of some callers. The previous // incarnation of this code, from 2007, indicates that it replaced an // earlier still version that caused excess allocations on IE6. src.attachEvent(goog.events.getOnString_(type.toString()), proxy); } else { throw Error('addEventListener and attachEvent are unavailable.'); } goog.events.listenerCountEstimate_++; 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(chrishenry): 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| * !goog.events.EventId|!Array>} * type Event type or array of event types. * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null} * listener Callback method. * @param {(boolean|!AddEventListenerOptions)=} opt_options * @param {T=} opt_handler Element in whose scope to call the listener. * @return {goog.events.Key} Unique key for the listener. * @template T,EVENTOBJ */ goog.events.listenOnce = function( src, type, listener, opt_options, opt_handler) { if (goog.isArray(type)) { for (var i = 0; i < type.length; i++) { goog.events.listenOnce(src, type[i], listener, opt_options, opt_handler); } return null; } listener = goog.events.wrapListener(listener); if (goog.events.Listenable.isImplementedBy(src)) { var capture = goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; return src.listenOnce( /** @type {string|!goog.events.EventId} */ (type), listener, capture, opt_handler); } else { return goog.events.listen_( /** @type {!EventTarget} */ (src), type, listener, /* callOnce */ true, opt_options, opt_handler); } }; /** * 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(this:T, ?):?|{handleEvent:function(?):?}|null} listener * Callback method, or an object with a handleEvent function. * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to * false). * @param {T=} opt_handler Element in whose scope to call the listener. * @template T */ 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| * !goog.events.EventId|!Array>} * type Event type or array of event types to unlisten to. * @param {function(?):?|{handleEvent:function(?):?}|null} listener The * listener function to remove. * @param {(boolean|!EventListenerOptions)=} opt_options * 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. * @template EVENTOBJ */ goog.events.unlisten = function(src, type, listener, opt_options, opt_handler) { if (goog.isArray(type)) { for (var i = 0; i < type.length; i++) { goog.events.unlisten(src, type[i], listener, opt_options, opt_handler); } return null; } var capture = goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; listener = goog.events.wrapListener(listener); if (goog.events.Listenable.isImplementedBy(src)) { return src.unlisten( /** @type {string|!goog.events.EventId} */ (type), listener, capture, opt_handler); } if (!src) { // TODO(chrishenry): We should tighten the API to only accept // non-null objects, or add an assertion here. return false; } var listenerMap = goog.events.getListenerMap_( /** @type {!EventTarget} */ (src)); if (listenerMap) { var listenerObj = listenerMap.getListener( /** @type {string|!goog.events.EventId} */ (type), listener, capture, opt_handler); if (listenerObj) { return goog.events.unlistenByKey(listenerObj); } } 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(chrishenry): Remove this check when tests that rely on this // are fixed. if (goog.isNumber(key)) { return false; } var listener = key; if (!listener || listener.removed) { return false; } var src = listener.src; if (goog.events.Listenable.isImplementedBy(src)) { return /** @type {!goog.events.Listenable} */ (src).unlistenByKey(listener); } var type = listener.type; var proxy = listener.proxy; if (src.removeEventListener) { src.removeEventListener(type, proxy, listener.capture); } else if (src.detachEvent) { src.detachEvent(goog.events.getOnString_(type), proxy); } goog.events.listenerCountEstimate_--; var listenerMap = goog.events.getListenerMap_( /** @type {!EventTarget} */ (src)); // TODO(chrishenry): Try to remove this conditional and execute the // first branch always. This should be safe. if (listenerMap) { listenerMap.removeByKey(listener); if (listenerMap.getTypeCount() == 0) { // Null the src, just because this is simple to do (and useful // for IE <= 7). listenerMap.src = null; // We don't use delete here because IE does not allow delete // on a window object. src[goog.events.LISTENER_MAP_PROP_] = null; } } else { /** @type {!goog.events.Listener} */ (listener).markAsRemoved(); } 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(?):?|{handleEvent:function(?):?}|null} 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|undefined} obj Object to remove listeners from. Must be an * EventTarget or a goog.events.Listenable. * @param {string|!goog.events.EventId=} opt_type Type of event to remove. * Default is all types. * @return {number} Number of listeners removed. */ goog.events.removeAll = function(obj, opt_type) { // TODO(chrishenry): Change the type of obj to // (!EventTarget|!goog.events.Listenable). if (!obj) { return 0; } if (goog.events.Listenable.isImplementedBy(obj)) { return /** @type {?} */ (obj).removeAllListeners(opt_type); } var listenerMap = goog.events.getListenerMap_( /** @type {!EventTarget} */ (obj)); if (!listenerMap) { return 0; } var count = 0; var typeStr = opt_type && opt_type.toString(); for (var type in listenerMap.listeners) { if (!typeStr || type == typeStr) { // Clone so that we don't need to worry about unlistenByKey // changing the content of the ListenerMap. var listeners = listenerMap.listeners[type].concat(); for (var i = 0; i < listeners.length; ++i) { if (goog.events.unlistenByKey(listeners[i])) { ++count; } } } } return count; }; /** * Gets the listeners for a given object, type and capture phase. * * @param {Object} obj Object to get listeners for. * @param {string|!goog.events.EventId} 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 /** @type {!goog.events.Listenable} */ (obj).getListeners( type, capture); } else { if (!obj) { // TODO(chrishenry): We should tighten the API to accept // !EventTarget|goog.events.Listenable, and add an assertion here. return []; } var listenerMap = goog.events.getListenerMap_( /** @type {!EventTarget} */ (obj)); return listenerMap ? listenerMap.getListeners(type, capture) : []; } }; /** * 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|!goog.events.EventId} type The type of the event. * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} 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. * @template EVENTOBJ */ goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { // TODO(chrishenry): Change type from ?string to string, or add assertion. type = /** @type {string} */ (type); listener = goog.events.wrapListener(listener); var capture = !!opt_capt; if (goog.events.Listenable.isImplementedBy(src)) { return src.getListener(type, listener, capture, opt_handler); } if (!src) { // TODO(chrishenry): We should tighten the API to only accept // non-null objects, or add an assertion here. return null; } var listenerMap = goog.events.getListenerMap_( /** @type {!EventTarget} */ (src)); if (listenerMap) { return listenerMap.getListener(type, listener, capture, opt_handler); } 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|!goog.events.EventId=} 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 listenerMap = goog.events.getListenerMap_( /** @type {!EventTarget} */ (obj)); return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture); }; /** * 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|!goog.events.EventId} 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 /** @type {!goog.events.Listenable} */ (obj).fireListeners( type, capture, eventObject); } return goog.events.fireListeners_(obj, type, capture, eventObject); }; /** * Fires an object's listeners of a particular type and phase. * @param {Object} obj Object whose listeners to call. * @param {string|!goog.events.EventId} 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(obj, type, capture, eventObject) { /** @type {boolean} */ var retval = true; var listenerMap = goog.events.getListenerMap_( /** @type {EventTarget} */ (obj)); if (listenerMap) { // TODO(chrishenry): Original code avoids array creation when there // is no listener, so we do the same. If this optimization turns // out to be not required, we can replace this with // listenerMap.getListeners(type, capture) instead, which is simpler. var listenerArray = listenerMap.listeners[type.toString()]; if (listenerArray) { listenerArray = listenerArray.concat(); 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.capture == capture && !listener.removed) { var result = goog.events.fireListener(listener, eventObject); retval = retval && (result !== false); } } } } return 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 {*} 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. * @deprecated This returns estimated count, now that Closure no longer * stores a central listener registry. We still return an estimation * to keep existing listener-related tests passing. In the near future, * this function will be removed. */ goog.events.getTotalListenerCount = function() { return goog.events.listenerCountEstimate_; }; /** * 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 {*} 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; } // 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')); var evt = new goog.events.BrowserEvent(ieEvent, this); /** @type {*} */ var retval = true; if (goog.events.CAPTURE_SIMULATION_MODE == goog.events.CaptureSimulationMode.ON) { // If we have not marked this event yet, we should perform capture // simulation. if (!goog.events.isMarkedIeEvent_(ieEvent)) { goog.events.markIeEvent_(ieEvent); var ancestors = []; for (var parent = evt.currentTarget; parent; parent = parent.parentNode) { ancestors.push(parent); } // Fire capture listeners. var type = listener.type; for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0; i--) { evt.currentTarget = ancestors[i]; var result = goog.events.fireListeners_(ancestors[i], type, true, evt); retval = retval && result; } // Fire bubble listeners. // // We can technically rely on IE to perform bubble event // propagation. However, it turns out that IE fires events in // opposite order of attachEvent registration, which broke // some code and tests that rely on the order. (While W3C DOM // Level 2 Events TR leaves the event ordering unspecified, // modern browsers and W3C DOM Level 3 Events Working Draft // actually specify the order as the registration order.) for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) { evt.currentTarget = ancestors[i]; var result = goog.events.fireListeners_(ancestors[i], type, false, evt); retval = retval && result; } } } else { retval = goog.events.fireListener(listener, evt); } return retval; } // Otherwise, simply fire the listener. return goog.events.fireListener( listener, new goog.events.BrowserEvent(opt_evt, this)); }; /** * 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. 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. * @private {number} */ goog.events.uniqueIdCounter_ = 0; /** * Creates a unique event id. * * @param {string} identifier The identifier. * @return {string} A unique identifier. * @idGenerator {unique} */ goog.events.getUniqueId = function(identifier) { return identifier + '_' + goog.events.uniqueIdCounter_++; }; /** * @param {EventTarget} src The source object. * @return {goog.events.ListenerMap} A listener map for the given * source object, or null if none exists. * @private */ goog.events.getListenerMap_ = function(src) { var listenerMap = src[goog.events.LISTENER_MAP_PROP_]; // IE serializes the property as well (e.g. when serializing outer // HTML). So we must check that the value is of the correct type. return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null; }; /** * Expando property for listener function wrapper for Object with * handleEvent. * @private @const {string} */ 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. */ 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.'); if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) { listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) { return /** @type {?} */ (listener).handleEvent(e); }; } return listener[goog.events.LISTENER_WRAPPER_PROP_]; }; // 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_); });