diff --git a/closure/goog/events/events.js b/closure/goog/events/events.js
index 13317ac5..da313dc7 100644
--- a/closure/goog/events/events.js
+++ b/closure/goog/events/events.js
@@ -245,16 +245,14 @@ goog.events.listen_ = function(
}
var proxy = goog.events.getProxy();
- proxy.src = src;
listenerObj = new goog.events.Listener();
listenerObj.init(listener, proxy, src, type, capture, opt_handler);
listenerObj.callOnce = callOnce;
- var key = listenerObj.key;
- proxy.key = key;
+ proxy.src = src;
+ proxy.listener = listenerObj;
listenerArray.push(listenerObj);
- goog.events.listeners_[key] = listenerObj;
if (!goog.events.sources_[srcUid]) {
goog.events.sources_[srcUid] = [];
@@ -275,6 +273,8 @@ goog.events.listen_ = function(
src.attachEvent(goog.events.getOnString_(type), proxy);
}
+ var key = listenerObj.key;
+ goog.events.listeners_[key] = listenerObj;
return key;
};
@@ -288,10 +288,10 @@ goog.events.getProxy = function() {
// Use a local var f to prevent one allocation.
var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
function(eventObject) {
- return proxyCallbackFunction.call(f.src, f.key, eventObject);
+ return proxyCallbackFunction.call(f.src, f.listener, eventObject);
} :
function(eventObject) {
- var v = proxyCallbackFunction.call(f.src, f.key, 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
@@ -491,6 +491,22 @@ goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
};
+/**
+ * Cleans up goog.events internal data structure. This should be
+ * called by all implementations of goog.events.Listenable when
+ * removing listeners.
+ *
+ * TODO(user): Once we remove numeric key support from
+ * goog.events.listen and friend, we will be able to remove this
+ * requirement.
+ *
+ * @param {goog.events.ListenableKey} listenableKey The key to clean up.
+ */
+goog.events.cleanUp = function(listenableKey) {
+ delete goog.events.listeners_[listenableKey.key];
+};
+
+
/**
* Cleans up the listener array as well as the listener tree
* @param {string} type The type of the event.
@@ -953,7 +969,7 @@ goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
* Handles an event and dispatches it to the correct listeners. This
* function is a proxy for the real listener the user specified.
*
- * @param {number} key Unique key for the listener.
+ * @param {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.
@@ -961,15 +977,11 @@ goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
* fired the event.
* @private
*/
-goog.events.handleBrowserEvent_ = function(key, opt_evt) {
- // If the listener isn't there it was probably removed when processing
- // another listener on the same event (e.g. the later listener is
- // not managed by closure so that they are both fired under IE)
- if (!goog.events.listeners_[key]) {
+goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
+ if (listener.removed) {
return true;
}
- var listener = goog.events.listeners_[key];
var type = listener.type;
var map = goog.events.listenerTree_;
diff --git a/closure/goog/events/eventtarget.js b/closure/goog/events/eventtarget.js
index 0e7d993b..d4ee101d 100644
--- a/closure/goog/events/eventtarget.js
+++ b/closure/goog/events/eventtarget.js
@@ -312,9 +312,7 @@ goog.events.EventTarget.prototype.unlisten = function(
listenerArray, listener, opt_useCapture, opt_listenerScope);
if (index > -1) {
var listenerObj = listenerArray[index];
- // We still need to mark this as removed as unlisten may be called
- // when a listener fired (the listener may already be queued to be
- // fired in the same dispatch sequence).
+ goog.events.cleanUp(listenerObj);
listenerObj.removed = true;
return goog.array.removeAt(listenerArray, index);
}
@@ -331,7 +329,12 @@ goog.events.EventTarget.prototype.unlistenByKey = function(key) {
return false;
}
- return goog.array.remove(this.eventTargetListeners_[type], key);
+ var removed = goog.array.remove(this.eventTargetListeners_[type], key);
+ if (removed) {
+ goog.events.cleanUp(key);
+ key.removed = true;
+ }
+ return removed;
};
@@ -342,7 +345,11 @@ goog.events.EventTarget.prototype.removeAllListeners = function(
for (var type in this.eventTargetListeners_) {
if (!opt_type || type == opt_type) {
var listenerArray = this.eventTargetListeners_[type];
- count += listenerArray.length;
+ for (var i = 0; i < listenerArray.length; i++) {
+ ++count;
+ goog.events.cleanUp(listenerArray[i]);
+ listenerArray[i].removed = true;
+ }
listenerArray.length = 0;
}
}
diff --git a/closure/goog/events/eventtarget_test.html b/closure/goog/events/eventtarget_test.html
index 3f2421c4..e32ba495 100644
--- a/closure/goog/events/eventtarget_test.html
+++ b/closure/goog/events/eventtarget_test.html
@@ -32,6 +32,9 @@
var unlistenFn = function(src, type, listener, opt_capt, opt_handler) {
return src.unlisten(type, listener, opt_capt, opt_handler);
};
+ var unlistenByKeyFn = function(src, key) {
+ return src.unlistenByKey(key);
+ };
var listenOnceFn = function(src, type, listener, opt_capt, opt_handler) {
return src.listenOnce(type, listener, opt_capt, opt_handler);
};
@@ -46,7 +49,7 @@
};
goog.events.eventTargetTester.setUp(
- listenFn, unlistenFn,
+ listenFn, unlistenFn, unlistenByKeyFn,
listenOnceFn, dispatchEventFn,
removeAllFn, getListenersFn,
goog.events.eventTargetTester.KeyType.NUMBER,
diff --git a/closure/goog/events/eventtarget_via_googevents_test.html b/closure/goog/events/eventtarget_via_googevents_test.html
index 525ae0c5..ef3dbd54 100644
--- a/closure/goog/events/eventtarget_via_googevents_test.html
+++ b/closure/goog/events/eventtarget_via_googevents_test.html
@@ -22,8 +22,11 @@