Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit d71fb6f

Browse files
committed
fix(jqLite): remove native listener when all jqLite listeners were deregistered
This fixes an iOS issue where some events buble only when native listeners are present (see #9509), but more importantly previously we would pass wrong argument into the `removeEventListenerFn` which caused native listeners to be never deregistered. Oops! Closes #9509
1 parent addfff3 commit d71fb6f

File tree

2 files changed

+98
-6
lines changed

2 files changed

+98
-6
lines changed

src/jqLite.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -274,18 +274,22 @@ function jqLiteOff(element, type, fn, unsupported) {
274274
if (!type) {
275275
for (type in events) {
276276
if (type !== '$destroy') {
277-
removeEventListenerFn(element, type, events[type]);
277+
removeEventListenerFn(element, type, handle);
278278
}
279279
delete events[type];
280280
}
281281
} else {
282282
forEach(type.split(' '), function(type) {
283-
if (isUndefined(fn)) {
284-
removeEventListenerFn(element, type, events[type]);
285-
delete events[type];
286-
} else {
287-
arrayRemove(events[type] || [], fn);
283+
if (isDefined(fn)) {
284+
var listenerFns = events[type];
285+
arrayRemove(listenerFns || [], fn);
286+
if (listenerFns && listenerFns.length > 0) {
287+
return;
288+
}
288289
}
290+
291+
removeEventListenerFn(element, type, handle);
292+
delete events[type];
289293
});
290294
}
291295
}

test/jqLiteSpec.js

+88
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,94 @@ describe('jqLite', function() {
14001400
expect(extraSpy).toHaveBeenCalledOnce();
14011401
});
14021402

1403+
1404+
describe('native listener deregistration', function() {
1405+
1406+
it('should deregister the native listener when all jqLite listeners for given type are gone ' +
1407+
'after off("eventName", listener) call', function() {
1408+
var aElem = jqLite(a);
1409+
var addEventListenerSpy = spyOn(aElem[0], 'addEventListener').andCallThrough();
1410+
var removeEventListenerSpy = spyOn(aElem[0], 'removeEventListener').andCallThrough();
1411+
var nativeListenerFn;
1412+
1413+
var jqLiteListener = function() {};
1414+
aElem.on('click', jqLiteListener);
1415+
1416+
expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false);
1417+
nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1];
1418+
expect(removeEventListenerSpy).not.toHaveBeenCalled();
1419+
1420+
aElem.off('click', jqLiteListener);
1421+
expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn, false);
1422+
});
1423+
1424+
1425+
it('should deregister the native listener when all jqLite listeners for given type are gone ' +
1426+
'after off("eventName") call', function() {
1427+
var aElem = jqLite(a);
1428+
var addEventListenerSpy = spyOn(aElem[0], 'addEventListener').andCallThrough();
1429+
var removeEventListenerSpy = spyOn(aElem[0], 'removeEventListener').andCallThrough();
1430+
var nativeListenerFn;
1431+
1432+
aElem.on('click', function() {});
1433+
expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false);
1434+
nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1];
1435+
expect(removeEventListenerSpy).not.toHaveBeenCalled();
1436+
1437+
aElem.off('click');
1438+
expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn, false);
1439+
});
1440+
1441+
1442+
it('should deregister the native listener when all jqLite listeners for given type are gone ' +
1443+
'after off("eventName1 eventName2") call', function() {
1444+
var aElem = jqLite(a);
1445+
var addEventListenerSpy = spyOn(aElem[0], 'addEventListener').andCallThrough();
1446+
var removeEventListenerSpy = spyOn(aElem[0], 'removeEventListener').andCallThrough();
1447+
var nativeListenerFn;
1448+
1449+
aElem.on('click', function() {});
1450+
expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false);
1451+
nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1];
1452+
addEventListenerSpy.reset();
1453+
1454+
aElem.on('dblclick', function() {});
1455+
expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn, false);
1456+
1457+
expect(removeEventListenerSpy).not.toHaveBeenCalled();
1458+
1459+
aElem.off('click dblclick');
1460+
1461+
expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn, false);
1462+
expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn, false);
1463+
expect(removeEventListenerSpy.callCount).toBe(2);
1464+
});
1465+
1466+
1467+
it('should deregister the native listener when all jqLite listeners for given type are gone ' +
1468+
'after off() call', function() {
1469+
var aElem = jqLite(a);
1470+
var addEventListenerSpy = spyOn(aElem[0], 'addEventListener').andCallThrough();
1471+
var removeEventListenerSpy = spyOn(aElem[0], 'removeEventListener').andCallThrough();
1472+
var nativeListenerFn;
1473+
1474+
aElem.on('click', function() {});
1475+
expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false);
1476+
nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1];
1477+
addEventListenerSpy.reset();
1478+
1479+
aElem.on('dblclick', function() {});
1480+
expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn, false);
1481+
1482+
aElem.off();
1483+
1484+
expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn, false);
1485+
expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn, false);
1486+
expect(removeEventListenerSpy.callCount).toBe(2);
1487+
});
1488+
});
1489+
1490+
14031491
// Only run this test for jqLite and not normal jQuery
14041492
if ( _jqLiteMode ) {
14051493
it('should throw an error if a selector is passed', function () {

0 commit comments

Comments
 (0)