diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index 8a8615ab5..3eca7f0cf 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -15,10 +15,15 @@ import {patchTimer} from '../common/timers'; import {patchClass, patchMacroTask, patchMethod, patchOnProperties, patchPrototype, zoneSymbol} from '../common/utils'; import {propertyPatch} from './define-property'; -import {eventTargetPatch} from './event-target'; +import {eventTargetPatch, patchEvent} from './event-target'; import {propertyDescriptorPatch} from './property-descriptor'; import {registerElementPatch} from './register-element'; +Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { + api.patchOnProperties = patchOnProperties; + api.patchMethod = patchMethod; +}); + Zone.__load_patch('timers', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const set = 'set'; const clear = 'clear'; @@ -46,6 +51,7 @@ Zone.__load_patch('blocking', (global: any, Zone: ZoneType, api: _ZonePrivate) = }); Zone.__load_patch('EventTarget', (global: any, Zone: ZoneType, api: _ZonePrivate) => { + patchEvent(global, api); eventTargetPatch(global, api); // patch XMLHttpRequestEventTarget's addEventListener/removeEventListener const XMLHttpRequestEventTarget = (global as any)['XMLHttpRequestEventTarget']; @@ -240,8 +246,3 @@ Zone.__load_patch('PromiseRejectionEvent', (global: any, Zone: ZoneType, api: _Z findPromiseRejectionHandler('rejectionhandled'); } }); - -Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { - api.patchOnProperties = patchOnProperties; - api.patchMethod = patchMethod; -}); diff --git a/lib/browser/event-target.ts b/lib/browser/event-target.ts index 438e815d2..2972cfbd6 100644 --- a/lib/browser/event-target.ts +++ b/lib/browser/event-target.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {FALSE_STR, globalSources, patchEventTarget, TRUE_STR, ZONE_SYMBOL_PREFIX, zoneSymbolEventNames} from '../common/events'; +import {FALSE_STR, globalSources, patchEventPrototype, patchEventTarget, TRUE_STR, ZONE_SYMBOL_PREFIX, zoneSymbolEventNames} from '../common/events'; import {attachOriginToPatched, isIEOrEdge, zoneSymbol} from '../common/utils'; import {eventNames} from './property-descriptor'; @@ -106,3 +106,7 @@ export function eventTargetPatch(_global: any, api: _ZonePrivate) { return true; } + +export function patchEvent(global: any, api: _ZonePrivate) { + patchEventPrototype(global, api); +} diff --git a/lib/common/events.ts b/lib/common/events.ts index e8d1d8408..94232a869 100644 --- a/lib/common/events.ts +++ b/lib/common/events.ts @@ -33,6 +33,8 @@ export const ZONE_SYMBOL_PREFIX = '__zone_symbol__'; const EVENT_NAME_SYMBOL_REGX = /^__zone_symbol__(\w+)(true|false)$/; +const IMMEDIATE_PROPAGATION_SYMBOL = ('__zone_symbol__propagationStopped'); + export interface PatchEventTargetOptions { validateHandler?: (nativeDelegate: any, delegate: any, target: any, args: any) => boolean; addEventListenerFnName?: string; @@ -105,6 +107,9 @@ export function patchEventTarget( // the callback will remove itself or other listener const copyTasks = tasks.slice(); for (let i = 0; i < copyTasks.length; i++) { + if (event && (event as any)[IMMEDIATE_PROPAGATION_SYMBOL] === true) { + break; + } invokeTask(copyTasks[i], target, event); } } @@ -127,6 +132,9 @@ export function patchEventTarget( // the callback will remove itself or other listener const copyTasks = tasks.slice(); for (let i = 0; i < copyTasks.length; i++) { + if (event && (event as any)[IMMEDIATE_PROPAGATION_SYMBOL] === true) { + break; + } invokeTask(copyTasks[i], target, event); } } @@ -564,3 +572,14 @@ export function findEventTasks(target: any, eventName: string): Task[] { } return foundTasks; } + +export function patchEventPrototype(global: any, api: _ZonePrivate) { + const Event = global['Event']; + if (Event && Event.prototype) { + api.patchMethod( + Event.prototype, 'stopImmediatePropagation', + (delegate: Function) => function(self: any, args: any[]) { + self[IMMEDIATE_PROPAGATION_SYMBOL] = true; + }); + } +} diff --git a/test/browser/browser.spec.ts b/test/browser/browser.spec.ts index ba3d013bc..fc8e1cb47 100644 --- a/test/browser/browser.spec.ts +++ b/test/browser/browser.spec.ts @@ -870,6 +870,42 @@ describe('Zone', function() { button.removeEventListener('click', listener); })); + it('should support Event.stopImmediatePropagation', + ifEnvSupports(supportEventListenerOptions, function() { + const hookSpy = jasmine.createSpy('hook'); + const logs: string[] = []; + const zone = rootZone.fork({ + name: 'spy', + onScheduleTask: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + task: Task): any => { + hookSpy(); + return parentZoneDelegate.scheduleTask(targetZone, task); + } + }); + + const listener1 = (e: Event) => { + logs.push('listener1'); + e.stopImmediatePropagation(); + }; + + const listener2 = (e: Event) => { + logs.push('listener2'); + }; + + zone.run(function() { + (button as any).addEventListener('click', listener1); + (button as any).addEventListener('click', listener2); + }); + + button.dispatchEvent(clickEvent); + + expect(hookSpy).toHaveBeenCalled(); + expect(logs).toEqual(['listener1']); + + button.removeEventListener('click', listener1); + button.removeEventListener('click', listener2); + })); + it('should support remove event listener by call zone.cancelTask directly', function() { let logs: string[] = []; let eventTask: Task;