diff --git a/lib/utils.js b/lib/utils.js index 21d0f78fb..77b972573 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -96,7 +96,8 @@ var boundFnsKey = keys.create('boundFns'); function patchEventTargetMethods(obj) { // This is required for the addEventListener hook on the root zone. obj[keys.common.addEventListener] = obj.addEventListener; - obj.addEventListener = function (eventName, handler) { + obj.addEventListener = function (eventName, handler, useCapturing) { + var eventType = eventName + (useCapturing ? '$capturing' : '$bubbling'); var fn; //Ignore special listeners of IE11 & Edge dev tools, see https://github.com/angular/zone.js/issues/150 if (handler.toString() !== "[object FunctionWrapper]") { @@ -114,19 +115,20 @@ function patchEventTargetMethods(obj) { handler[originalFnKey] = fn; handler[boundFnsKey] = handler[boundFnsKey] || {}; - arguments[1] = handler[boundFnsKey][eventName] = zone.bind(fn); + arguments[1] = handler[boundFnsKey][eventType] = zone.bind(fn); } return global.zone.addEventListener.apply(this, arguments); }; // This is required for the removeEventListener hook on the root zone. obj[keys.common.removeEventListener] = obj.removeEventListener; - obj.removeEventListener = function (eventName, handler) { - if(handler[boundFnsKey] && handler[boundFnsKey][eventName]) { + obj.removeEventListener = function (eventName, handler, useCapturing) { + var eventType = eventName + (useCapturing ? '$capturing' : '$bubbling'); + if (handler[boundFnsKey] && handler[boundFnsKey][eventType]) { var _bound = handler[boundFnsKey]; - arguments[1] = _bound[eventName]; - delete _bound[eventName]; + arguments[1] = _bound[eventType]; + delete _bound[eventType]; } var result = global.zone.removeEventListener.apply(this, arguments); global.zone.dequeueTask(handler[originalFnKey]); diff --git a/test/patch/element.spec.js b/test/patch/element.spec.js index 19becceb6..86b20ed3e 100644 --- a/test/patch/element.spec.js +++ b/test/patch/element.spec.js @@ -84,6 +84,56 @@ describe('element', function () { expect(eventListener.handleEvent).not.toHaveBeenCalled(); }); + it('should correctly handle capturing versus nonCapturing eventListeners', function () { + var log = []; + var clickEvent = document.createEvent('Event'); + + function capturingListener () { + log.push('capturingListener'); + } + + function bubblingListener () { + log.push('bubblingListener'); + } + + clickEvent.initEvent('click', true, true); + + document.body.addEventListener('click', capturingListener, true); + document.body.addEventListener('click', bubblingListener); + + button.dispatchEvent(clickEvent); + + expect(log).toEqual([ + 'capturingListener', + 'bubblingListener' + ]); + }); + + it('should correctly handle a listener that is both capturing and nonCapturing', function () { + var log = []; + var clickEvent = document.createEvent('Event'); + + function listener () { + log.push('listener'); + } + + clickEvent.initEvent('click', true, true); + + document.body.addEventListener('click', listener, true); + document.body.addEventListener('click', listener); + + button.dispatchEvent(clickEvent); + + document.body.removeEventListener('click', listener, true); + document.body.removeEventListener('click', listener); + + button.dispatchEvent(clickEvent); + + expect(log).toEqual([ + 'listener', + 'listener' + ]); + }); describe('onclick', function() {