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 @@