fix(core): should support event.stopImmediatePropagation (#20469)
PR Close #20469
This commit is contained in:
parent
1861e416a1
commit
997336b790
|
@ -35,6 +35,10 @@ const ANGULAR = 'ANGULAR';
|
||||||
const NATIVE_ADD_LISTENER = 'addEventListener';
|
const NATIVE_ADD_LISTENER = 'addEventListener';
|
||||||
const NATIVE_REMOVE_LISTENER = 'removeEventListener';
|
const NATIVE_REMOVE_LISTENER = 'removeEventListener';
|
||||||
|
|
||||||
|
// use the same symbol string which is used in zone.js
|
||||||
|
const stopSymbol = '__zone_symbol__propagationStopped';
|
||||||
|
const stopMethodSymbol = '__zone_symbol__stopImmediatePropagation';
|
||||||
|
|
||||||
const blackListedEvents: string[] =
|
const blackListedEvents: string[] =
|
||||||
(typeof Zone !== 'undefined') && (Zone as any)[__symbol__('BLACK_LISTED_EVENTS')];
|
(typeof Zone !== 'undefined') && (Zone as any)[__symbol__('BLACK_LISTED_EVENTS')];
|
||||||
let blackListedMap: {[eventName: string]: string};
|
let blackListedMap: {[eventName: string]: string};
|
||||||
|
@ -81,6 +85,11 @@ const globalListener = function(event: Event) {
|
||||||
// itself or others
|
// itself or others
|
||||||
const copiedTasks = taskDatas.slice();
|
const copiedTasks = taskDatas.slice();
|
||||||
for (let i = 0; i < copiedTasks.length; i++) {
|
for (let i = 0; i < copiedTasks.length; i++) {
|
||||||
|
// if other listener call event.stopImmediatePropagation
|
||||||
|
// just break
|
||||||
|
if ((event as any)[stopSymbol] === true) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
const taskData = copiedTasks[i];
|
const taskData = copiedTasks[i];
|
||||||
if (taskData.zone !== Zone.current) {
|
if (taskData.zone !== Zone.current) {
|
||||||
// only use Zone.run when Zone.current not equals to stored zone
|
// only use Zone.run when Zone.current not equals to stored zone
|
||||||
|
@ -94,7 +103,33 @@ const globalListener = function(event: Event) {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DomEventsPlugin extends EventManagerPlugin {
|
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;
|
||||||
|
}
|
||||||
|
if ((Event.prototype as any)[stopMethodSymbol]) {
|
||||||
|
// already patched by zone.js
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const delegate = (Event.prototype as any)[stopMethodSymbol] =
|
||||||
|
Event.prototype.stopImmediatePropagation;
|
||||||
|
Event.prototype.stopImmediatePropagation = function() {
|
||||||
|
if (this) {
|
||||||
|
this[stopSymbol] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// should call native delegate in case
|
||||||
|
// in some enviroment part of the application
|
||||||
|
// will not use the patched Event
|
||||||
|
delegate && delegate.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// This plugin should come last in the list of plugins, because it accepts all
|
// This plugin should come last in the list of plugins, because it accepts all
|
||||||
// events.
|
// events.
|
||||||
|
|
|
@ -152,6 +152,42 @@ export function main() {
|
||||||
expect(receivedEvents).toEqual([]);
|
expect(receivedEvents).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support event.stopImmediatePropagation', () => {
|
||||||
|
const Zone = (window as any)['Zone'];
|
||||||
|
|
||||||
|
const element = el('<div><div></div></div>');
|
||||||
|
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 ', () => {
|
it('should handle event correctly when one handler remove itself ', () => {
|
||||||
const Zone = (window as any)['Zone'];
|
const Zone = (window as any)['Zone'];
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue