From 200d92d0301ef4c584cb7d69366bde3395157e96 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sat, 4 Nov 2017 07:22:05 +0900 Subject: [PATCH] fix(core): should support event.stopImmediatePropagation (#19222) --- .../src/dom/events/dom_events.ts | 29 ++++++++++++++- .../test/dom/events/event_manager_spec.ts | 36 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/platform-browser/src/dom/events/dom_events.ts b/packages/platform-browser/src/dom/events/dom_events.ts index 3eec5178fc..30720ff2a1 100644 --- a/packages/platform-browser/src/dom/events/dom_events.ts +++ b/packages/platform-browser/src/dom/events/dom_events.ts @@ -34,6 +34,8 @@ const FALSE = 'FALSE'; const ANGULAR = 'ANGULAR'; const NATIVE_ADD_LISTENER = 'addEventListener'; const NATIVE_REMOVE_LISTENER = 'removeEventListener'; +// use the same symbol string which is used in zone.js +const stopSymbol = '__zone_symbol__propagationStopped'; const blackListedEvents: string[] = (typeof Zone !== 'undefined') && (Zone as any)[__symbol__('BLACK_LISTED_EVENTS')]; @@ -81,6 +83,9 @@ const globalListener = function(event: Event) { // itself or others const copiedTasks = taskDatas.slice(); for (let i = 0; i < copiedTasks.length; i++) { + if ((event as any)[stopSymbol] === true) { + break; + } const taskData = copiedTasks[i]; if (taskData.zone !== Zone.current) { // only use Zone.run when Zone.current not equals to stored zone @@ -94,7 +99,29 @@ const globalListener = function(event: Event) { @Injectable() export class DomEventsPlugin extends EventManagerPlugin { - constructor(@Inject(DOCUMENT) doc: any, private ngZone: NgZone) { super(doc); } + constructor(@Inject(DOCUMENT) doc: any, private ngZone: NgZone) { + super(doc); + + this.patchEvent(); + } + + private patchEvent() { + if (!Event || !Event.prototype) { + return; + } + const symbol = '__zone_symbol__stopImmediatePropagation'; + if ((Event.prototype as any)[symbol]) { + // already patched by zone.js + return; + } + (Event.prototype as any)[symbol] = Event.prototype.stopImmediatePropagation; + Event.prototype.stopImmediatePropagation = function() { + if (this) { + this[stopSymbol] = true; + } + }; + } + // This plugin should come last in the list of plugins, because it accepts all // events. diff --git a/packages/platform-browser/test/dom/events/event_manager_spec.ts b/packages/platform-browser/test/dom/events/event_manager_spec.ts index 480de63bd1..e1a405e9b9 100644 --- a/packages/platform-browser/test/dom/events/event_manager_spec.ts +++ b/packages/platform-browser/test/dom/events/event_manager_spec.ts @@ -152,6 +152,42 @@ export function main() { expect(receivedEvents).toEqual([]); }); + it('should support event.stopImmediatePropagation', () => { + const Zone = (window as any)['Zone']; + + const element = el('
'); + getDOM().appendChild(doc.body, element); + const dispatchedEvent = getDOM().createMouseEvent('click'); + let receivedEvents: any[] /** TODO #9100 */ = []; + let receivedZones: any[] = []; + const handler1 = (e: any /** TODO #9100 */) => { + receivedEvents.push(e); + receivedZones.push(Zone.current.name); + e.stopImmediatePropagation(); + }; + const handler2 = (e: any /** TODO #9100 */) => { + receivedEvents.push(e); + receivedZones.push(Zone.current.name); + }; + const manager = new EventManager([domEventPlugin], new FakeNgZone()); + + let remover1 = null; + let remover2 = null; + Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); }); + Zone.root.fork({name: 'test'}).run(() => { + remover2 = manager.addEventListener(element, 'click', handler2); + }); + getDOM().dispatchEvent(element, dispatchedEvent); + expect(receivedEvents).toEqual([dispatchedEvent]); + expect(receivedZones).toEqual([Zone.root.name]); + + receivedEvents = []; + remover1 && remover1(); + remover2 && remover2(); + getDOM().dispatchEvent(element, dispatchedEvent); + expect(receivedEvents).toEqual([]); + }); + it('should handle event correctly when one handler remove itself ', () => { const Zone = (window as any)['Zone'];