diff --git a/lib/common/events.ts b/lib/common/events.ts index 94232a869..935e2e9eb 100644 --- a/lib/common/events.ts +++ b/lib/common/events.ts @@ -93,8 +93,12 @@ export function patchEventTarget( // global shared zoneAwareCallback to handle all event callback with capture = false const globalZoneAwareCallback = function(event: Event) { - const target = this || _global; - + if (!event) { + return; + } + // event.target is needed for Samusung TV and SourceBuffer + // || global is needed https://github.com/angular/zone.js/issues/190 + const target: any = this || event.target || _global; const tasks = target[zoneSymbolEventNames[event.type][FALSE_STR]]; if (tasks) { // invoke all tasks which attached to current target with given event.type and capture = false @@ -118,8 +122,12 @@ export function patchEventTarget( // global shared zoneAwareCallback to handle all event callback with capture = true const globalZoneAwareCaptureCallback = function(event: Event) { - const target = this || _global; - + if (!event) { + return; + } + // event.target is needed for Samusung TV and SourceBuffer + // || global is needed https://github.com/angular/zone.js/issues/190 + const target: any = this || event.target || _global; const tasks = target[zoneSymbolEventNames[event.type][TRUE_STR]]; if (tasks) { // invoke all tasks which attached to current target with given event.type and capture = false diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 8bf55f79f..0477cfcba 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -89,12 +89,14 @@ export const isMix: boolean = typeof _global.process !== 'undefined' && const ON_PROPERTY_HANDLER_SYMBOL = zoneSymbol('onPropertyHandler'); const zoneSymbolEventNames: {[eventName: string]: string} = {}; + const wrapFn = function(event: Event) { let eventNameSymbol = zoneSymbolEventNames[event.type]; if (!eventNameSymbol) { eventNameSymbol = zoneSymbolEventNames[event.type] = zoneSymbol('ON_PROPERTY' + event.type); } - const listener = this[eventNameSymbol]; + const target = this || event && event.target || _global; + const listener = target[eventNameSymbol]; let result = listener && listener.apply(this, arguments); if (result != undefined && !result) { diff --git a/test/browser/browser.spec.ts b/test/browser/browser.spec.ts index fc8e1cb47..bb8346db8 100644 --- a/test/browser/browser.spec.ts +++ b/test/browser/browser.spec.ts @@ -7,8 +7,9 @@ */ import {patchFilteredProperties} from '../../lib/browser/property-descriptor'; +import {patchEventTarget} from '../../lib/common/events'; import {isBrowser, isIEOrEdge, isMix, zoneSymbol} from '../../lib/common/utils'; -import {ifEnvSupports, ifEnvSupportsWithDone} from '../test-util'; +import {getIEVersion, ifEnvSupports, ifEnvSupportsWithDone} from '../test-util'; import Spy = jasmine.Spy; declare const global: any; @@ -243,6 +244,74 @@ describe('Zone', function() { document.removeEventListener('mousedown', eventListenerSpy); })); + it('event handler with null context should use event.target', + ifEnvSupports(canPatchOnProperty(Document.prototype, 'onmousedown'), function() { + const ieVer = getIEVersion(); + if (ieVer && ieVer === 9) { + // in ie9, this is window object even we call func.apply(undefined) + return; + } + const logs: string[] = []; + const EventTarget = (window as any)['EventTarget']; + let oriAddEventListener = EventTarget && EventTarget.prototype ? + (EventTarget.prototype as any)['__zone_symbol__addEventListener'] : + (HTMLSpanElement.prototype as any)['__zone_symbol__addEventListener']; + + if (!oriAddEventListener) { + // no patched addEventListener found + return; + } + let handler1: Function; + let handler2: Function; + + const listener = function() { + logs.push('listener1'); + }; + + const listener1 = function() { + logs.push('listener2'); + }; + + HTMLSpanElement.prototype.addEventListener = function( + eventName: string, callback: any) { + if (eventName === 'click') { + handler1 = callback; + } else if (eventName === 'mousedown') { + handler2 = callback; + } + return oriAddEventListener.apply(this, arguments); + }; + + (HTMLSpanElement.prototype as any)['__zone_symbol__addEventListener'] = null; + + patchEventTarget(window, [HTMLSpanElement.prototype]); + + const span = document.createElement('span'); + document.body.appendChild(span); + + zone.run(function() { + span.addEventListener('click', listener); + span.onmousedown = listener1; + }); + + expect(handler1).toBe(handler2); + + handler1.apply(undefined, [{type: 'click', target: span}]); + + handler2.apply(undefined, [{type: 'mousedown', target: span}]); + + expect(hookSpy).toHaveBeenCalled(); + expect(logs).toEqual(['listener1', 'listener2']); + document.body.removeChild(span); + if (EventTarget) { + (EventTarget.prototype as any)['__zone_symbol__addEventListener'] = + oriAddEventListener; + } else { + (HTMLSpanElement.prototype as any)['__zone_symbol__addEventListener'] = + oriAddEventListener; + } + })); + it('SVGElement onclick should be in zone', ifEnvSupports( canPatchOnProperty(SVGElement && SVGElement.prototype, 'onmousedown'), function() { diff --git a/test/test-util.ts b/test/test-util.ts index 6f6e2cb38..7d313a189 100644 --- a/test/test-util.ts +++ b/test/test-util.ts @@ -95,4 +95,12 @@ export function asyncTest(testFn: Function, zone: Zone = Zone.current) { }, 'asyncTest')); asyncTestZone.run(testFn, this, [done]); }; -} \ No newline at end of file +} + +export function getIEVersion() { + const userAgent = navigator.userAgent.toLowerCase(); + if (userAgent.indexOf('msie') != -1) { + return parseInt(userAgent.split('msie')[1]); + } + return null; +}