Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): should support event.stopImmediatePropagation #20469

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 36 additions & 1 deletion packages/platform-browser/src/dom/events/dom_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ 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 stopMethodSymbol = '__zone_symbol__stopImmediatePropagation';

const blackListedEvents: string[] =
(typeof Zone !== 'undefined') && (Zone as any)[__symbol__('BLACK_LISTED_EVENTS')];
let blackListedMap: {[eventName: string]: string};
Expand Down Expand Up @@ -81,6 +85,11 @@ const globalListener = function(event: Event) {
// itself or others
const copiedTasks = taskDatas.slice();
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];
if (taskData.zone !== Zone.current) {
// only use Zone.run when Zone.current not equals to stored zone
Expand All @@ -94,7 +103,33 @@ 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;
}
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
// events.
Expand Down
36 changes: 36 additions & 0 deletions packages/platform-browser/test/dom/events/event_manager_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,42 @@ export function main() {
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 ', () => {
const Zone = (window as any)['Zone'];

Expand Down