Skip to content

Commit 36b7ec5

Browse files
Merge pull request prototypejs#101 from kir/master
Fix memory leak when Event#stopObserving is called for a non-existing event.
2 parents 4485572 + 4eeef9d commit 36b7ec5

File tree

2 files changed

+47
-17
lines changed

2 files changed

+47
-17
lines changed

src/prototype/dom/event.js

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@
532532

533533
// These two functions take an optional UID as a second argument so that we
534534
// can skip lookup if we've already got the element's UID.
535-
function getRegistryForElement(element, uid) {
535+
function getOrCreateRegistryFor(element, uid) {
536536
var CACHE = GLOBAL.Event.cache;
537537
if (Object.isUndefined(uid))
538538
uid = getUniqueElementID(element);
@@ -552,7 +552,7 @@
552552

553553
// Add an event to the element's event registry.
554554
function register(element, eventName, handler) {
555-
var registry = getRegistryForElement(element);
555+
var registry = getOrCreateRegistryFor(element);
556556
if (!registry[eventName]) registry[eventName] = [];
557557
var entries = registry[eventName];
558558

@@ -574,10 +574,10 @@
574574

575575
// Remove an event from the element's event registry.
576576
function unregister(element, eventName, handler) {
577-
var registry = getRegistryForElement(element);
578-
var entries = registry[eventName];
579-
if (!entries) return;
577+
var registry = getOrCreateRegistryFor(element);
578+
var entries = registry[eventName] || [];
580579

580+
// Looking for entry:
581581
var i = entries.length, entry;
582582
while (i--) {
583583
if (entries[i].handler === handler) {
@@ -586,14 +586,13 @@
586586
}
587587
}
588588

589-
// This handler wasn't in the collection, so it doesn't need to be
590-
// unregistered.
591-
if (!entry) return;
592-
593-
// Remove the entry from the collection;
594-
var index = entries.indexOf(entry);
595-
entries.splice(index, 1);
589+
if (entry) {
590+
// Remove the entry from the collection;
591+
var index = entries.indexOf(entry);
592+
entries.splice(index, 1);
593+
}
596594

595+
// Last entry for given event was deleted?
597596
if (entries.length === 0) {
598597
// We can destroy the registry's entry for this event name...
599598
delete registry[eventName];
@@ -929,14 +928,25 @@
929928

930929
// Stop observing all listeners of a certain event name on an element.
931930
function stopObservingEventName(element, eventName) {
932-
var registry = getRegistryForElement(element);
931+
var registry = getOrCreateRegistryFor(element);
933932
var entries = registry[eventName];
934-
if (!entries) return;
935-
delete registry[eventName];
933+
if (entries) {
934+
delete registry[eventName];
935+
}
936+
937+
entries = entries || [];
936938

937939
var i = entries.length;
938940
while (i--)
939941
removeEvent(element, eventName, entries[i].responder);
942+
943+
for (var name in registry) {
944+
if (name === 'element') continue;
945+
return; // There is another registered event
946+
}
947+
948+
// No other events for the element, destroy the registry:
949+
destroyRegistryForElement(element);
940950
}
941951

942952

@@ -1349,7 +1359,8 @@
13491359

13501360
function createResponderForCustomEvent(uid, eventName, handler) {
13511361
return function(event) {
1352-
var element = Event.cache[uid].element;
1362+
var cache = Event.cache[uid];
1363+
var element = cache && cache.element;
13531364

13541365
if (Object.isUndefined(event.eventName))
13551366
return false;

test/unit/tests/event.test.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ suite('Event', function () {
185185
});
186186

187187
test('last #stopObserving clears cache', function () {
188-
var span = $("span"), observer = Prototype.emptyFunction, eventID;
188+
var span = $("span"), observer = Prototype.emptyFunction;
189189
delete Event.cache[uidForElement(span)];
190190

191191
span.observe("test:somethingHappened", observer);
@@ -202,6 +202,25 @@ suite('Event', function () {
202202
assert(!registry, 'registry');
203203
});
204204

205+
test('double #stopObserving - cache should be kept empty', function () {
206+
var span = $("span"), observer = Prototype.emptyFunction;
207+
delete Event.cache[uidForElement(span)];
208+
209+
span.observe("test:somethingHappened", observer);
210+
span.stopObserving("test:somethingHappened", observer);
211+
span.stopObserving("test:somethingHappened", observer);
212+
213+
assert(!Event.cache[uidForElement(span)], 'registry should be clear after 2 stopObserving');
214+
215+
span.stopObserving("test:somethingHappened");
216+
217+
assert(!Event.cache[uidForElement(span)], 'registry should be clear after stopObserving with no handler');
218+
219+
span.stopObserving();
220+
221+
assert(!Event.cache[uidForElement(span)], 'registry should be clear after stopObserving with no eventName');
222+
});
223+
205224
test('#observe and #stopObserving are chainable', function () {
206225
var span = $("span"), observer = Prototype.emptyFunction;
207226

0 commit comments

Comments
 (0)