1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003 |
- // 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:
- * <pre>
- * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
- * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
- * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
- * goog.events.removeAll(myNode);
- * </pre>
- *
- * 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<eventname>" 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<string>|
- * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
- * 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<EVENTOBJ>} 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 <a href="..." onclick="return false">...</a>) in a
- // descendant element, the prevent default will be overridden
- // by this listener if this listener were to return true. Hence, we
- // return undefined.
- if (!v) return v;
- };
- return f;
- };
- /**
- * Adds an event listener for a specific event on a 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<string>|
- * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
- * 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<string>|
- * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
- * 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<!goog.events.Listener>} 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<EVENTOBJ>} 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_);
- });
|