license fileoverview suppress import { ADD_EVENT_LISTENER_STR , attachOriginToPatched , FALSE_STR , isNode , ObjectGetPrototypeOf , REMOVE_EVENT_LISTENER_STR , TRUE_STR , ZONE_SYMBOL_PREFIX , zoneSymbol } from './utils' ; internal interface EventTaskData extends TaskData { readonly useG ? : boolean ; } let passiveSupported = false ; if ( typeof window !== 'undefined' ) { try { const options = Object . defineProperty ( { } , 'passive' , { get : function ( ) { passiveSupported = true ; } } ) ; window . addEventListener ( 'test' , options , options ) ; window . removeEventListener ( 'test' , options , options ) ; } catch ( err ) { passiveSupported = false ; } } const OPTIMIZED_ZONE_EVENT_TASK_DATA = { useG : true } ; export const zoneSymbolEventNames = { } ; export const globalSources = { } ; const EVENT_NAME_SYMBOL_REGX = new RegExp ( '^' + ZONE_SYMBOL_PREFIX + '(\\w+)(true|false)$' ) ; const IMMEDIATE_PROPAGATION_SYMBOL = zoneSymbol ( 'propagationStopped' ) ; export interface PatchEventTargetOptions { vh ? : ( nativeDelegate : any , delegate : any , target : any , args : any ) => boolean ; add ? : string ; rm ? : string ; prepend ? : string ; listeners ? : string ; rmAll ? : string ; useG ? : boolean ; chkDup ? : boolean ; rt ? : boolean ; diff ? : ( task : any , delegate : any ) => boolean ; supportPassive ? : boolean ; eventNameToString ? : ( eventName : any ) => string ; } export function patchEventTarget ( _global , apis , patchOptions ? ) { const ADD_EVENT_LISTENER = ( patchOptions && patchOptions . add ) || ADD_EVENT_LISTENER_STR ; const REMOVE_EVENT_LISTENER = ( patchOptions && patchOptions . rm ) || REMOVE_EVENT_LISTENER_STR ; const LISTENERS_EVENT_LISTENER = ( patchOptions && patchOptions . listeners ) || 'eventListeners' ; const REMOVE_ALL_LISTENERS_EVENT_LISTENER = ( patchOptions && patchOptions . rmAll ) || 'removeAllListeners' ; const zoneSymbolAddEventListener = zoneSymbol ( ADD_EVENT_LISTENER ) ; const ADD_EVENT_LISTENER_SOURCE = '.' + ADD_EVENT_LISTENER + ':' ; const PREPEND_EVENT_LISTENER = 'prependListener' ; const PREPEND_EVENT_LISTENER_SOURCE = '.' + PREPEND_EVENT_LISTENER + ':' ; const invokeTask = function ( task , target , event ) { if ( task . isRemoved ) { return ; } const delegate = task . callback ; if ( typeof delegate === 'object' && delegate . handleEvent ) { task . callback = ( event ) => delegate . handleEvent ( event ) ; task . originalDelegate = delegate ; } task . invoke ( task , target , [ event ] ) ; const options = task . options ; if ( options && typeof options === 'object' && options . once ) { const delegate = task . originalDelegate ? task . originalDelegate : task . callback ; target [ REMOVE_EVENT_LISTENER ] . call ( target , event . type , delegate , options ) ; } } ; const globalZoneAwareCallback = function ( event ) { event = event || _global . event ; if ( ! event ) { return ; } const target = this || event . target || _global ; const tasks = target [ zoneSymbolEventNames [ event . type ] [ FALSE_STR ] ] ; if ( tasks ) { if ( tasks . length === 1 ) { invokeTask ( tasks [ 0 ] , target , event ) ; } else { 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 ) ; } } } } ; const globalZoneAwareCaptureCallback = function ( event ) { event = event || _global . event ; if ( ! event ) { return ; } const target = this || event . target || _global ; const tasks = target [ zoneSymbolEventNames [ event . type ] [ TRUE_STR ] ] ; if ( tasks ) { if ( tasks . length === 1 ) { invokeTask ( tasks [ 0 ] , target , event ) ; } else { 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 ) ; } } } } ; function patchEventTargetMethods ( obj , patchOptions ? ) { if ( ! obj ) { return false ; } let useGlobalCallback = true ; if ( patchOptions && patchOptions . useG !== undefined ) { useGlobalCallback = patchOptions . useG ; } const validateHandler = patchOptions && patchOptions . vh ; let checkDuplicate = true ; if ( patchOptions && patchOptions . chkDup !== undefined ) { checkDuplicate = patchOptions . chkDup ; } let returnTarget = false ; if ( patchOptions && patchOptions . rt !== undefined ) { returnTarget = patchOptions . rt ; } let proto = obj ; while ( proto && ! proto . hasOwnProperty ( ADD_EVENT_LISTENER ) ) { proto = ObjectGetPrototypeOf ( proto ) ; } if ( ! proto && obj [ ADD_EVENT_LISTENER ] ) { proto = obj ; } if ( ! proto ) { return false ; } if ( proto [ zoneSymbolAddEventListener ] ) { return false ; } const eventNameToString = patchOptions && patchOptions . eventNameToString ; const taskData = { } ; const nativeAddEventListener = proto [ zoneSymbolAddEventListener ] = proto [ ADD_EVENT_LISTENER ] ; const nativeRemoveEventListener = proto [ zoneSymbol ( REMOVE_EVENT_LISTENER ) ] = proto [ REMOVE_EVENT_LISTENER ] ; const nativeListeners = proto [ zoneSymbol ( LISTENERS_EVENT_LISTENER ) ] = proto [ LISTENERS_EVENT_LISTENER ] ; const nativeRemoveAllListeners = proto [ zoneSymbol ( REMOVE_ALL_LISTENERS_EVENT_LISTENER ) ] = proto [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] ; let nativePrependEventListener ; if ( patchOptions && patchOptions . prepend ) { nativePrependEventListener = proto [ zoneSymbol ( patchOptions . prepend ) ] = proto [ patchOptions . prepend ] ; } function checkIsPassive ( task ) { if ( ! passiveSupported && typeof taskData . options !== 'boolean' && typeof taskData . options !== 'undefined' && taskData . options !== null ) { ( task as any ) . options = ! ! taskData . options . capture ; taskData . options = ( task as any ) . options ; } } const customScheduleGlobal = function ( task ) { if ( taskData . isExisting ) { return ; } checkIsPassive ( task ) ; return nativeAddEventListener . call ( taskData . target , taskData . eventName , taskData . capture ? globalZoneAwareCaptureCallback : globalZoneAwareCallback , taskData . options ) ; } ; const customCancelGlobal = function ( task ) { if ( ! task . isRemoved ) { const symbolEventNames = zoneSymbolEventNames [ task . eventName ] ; let symbolEventName ; if ( symbolEventNames ) { symbolEventName = symbolEventNames [ task . capture ? TRUE_STR : FALSE_STR ] ; } const existingTasks = symbolEventName && task . target [ symbolEventName ] ; if ( existingTasks ) { for ( let i = 0 ; i < existingTasks . length ; i ++ ) { const existingTask = existingTasks [ i ] ; if ( existingTask === task ) { existingTasks . splice ( i , 1 ) ; task . isRemoved = true ; if ( existingTasks . length === 0 ) { task . allRemoved = true ; task . target [ symbolEventName ] = null ; } break ; } } } } if ( ! task . allRemoved ) { return ; } return nativeRemoveEventListener . call ( task . target , task . eventName , task . capture ? globalZoneAwareCaptureCallback : globalZoneAwareCallback , task . options ) ; } ; const customScheduleNonGlobal = function ( task ) { checkIsPassive ( task ) ; return nativeAddEventListener . call ( taskData . target , taskData . eventName , task . invoke , taskData . options ) ; } ; const customSchedulePrepend = function ( task ) { return nativePrependEventListener . call ( taskData . target , taskData . eventName , task . invoke , taskData . options ) ; } ; const customCancelNonGlobal = function ( task ) { return nativeRemoveEventListener . call ( task . target , task . eventName , task . invoke , task . options ) ; } ; const customSchedule = useGlobalCallback ? customScheduleGlobal : customScheduleNonGlobal ; const customCancel = useGlobalCallback ? customCancelGlobal : customCancelNonGlobal ; const compareTaskCallbackVsDelegate = function ( task , delegate ) { const typeOfDelegate = typeof delegate ; return ( typeOfDelegate === 'function' && task . callback === delegate ) || ( typeOfDelegate === 'object' && task . originalDelegate === delegate ) ; } ; const compare = ( patchOptions && patchOptions . diff ) ? patchOptions . diff : compareTaskCallbackVsDelegate ; const blackListedEvents = ( Zone as any ) [ zoneSymbol ( 'BLACK_LISTED_EVENTS' ) ] ; const makeAddListener = function ( nativeListener , addSource , customScheduleFn , customCancelFn , returnTarget = false , prepend = false ) { return function ( ) { const target = this || _global ; const eventName = arguments [ 0 ] ; let delegate = arguments [ 1 ] ; if ( ! delegate ) { return nativeListener . apply ( this , arguments ) ; } if ( isNode && eventName === 'uncaughtException' ) { return nativeListener . apply ( this , arguments ) ; } let isHandleEvent = false ; if ( typeof delegate !== 'function' ) { if ( ! delegate . handleEvent ) { return nativeListener . apply ( this , arguments ) ; } isHandleEvent = true ; } if ( validateHandler && ! validateHandler ( nativeListener , delegate , target , arguments ) ) { return ; } const options = arguments [ 2 ] ; if ( blackListedEvents ) { for ( let i = 0 ; i < blackListedEvents . length ; i ++ ) { if ( eventName === blackListedEvents [ i ] ) { return nativeListener . apply ( this , arguments ) ; } } } let capture ; let once = false ; if ( options === undefined ) { capture = false ; } else if ( options === true ) { capture = true ; } else if ( options === false ) { capture = false ; } else { capture = options ? ! ! options . capture : false ; once = options ? ! ! options . once : false ; } const zone = Zone . current ; const symbolEventNames = zoneSymbolEventNames [ eventName ] ; let symbolEventName ; if ( ! symbolEventNames ) { const falseEventName = ( eventNameToString ? eventNameToString ( eventName ) : eventName ) + FALSE_STR ; const trueEventName = ( eventNameToString ? eventNameToString ( eventName ) : eventName ) + TRUE_STR ; const symbol = ZONE_SYMBOL_PREFIX + falseEventName ; const symbolCapture = ZONE_SYMBOL_PREFIX + trueEventName ; zoneSymbolEventNames [ eventName ] = { } ; zoneSymbolEventNames [ eventName ] [ FALSE_STR ] = symbol ; zoneSymbolEventNames [ eventName ] [ TRUE_STR ] = symbolCapture ; symbolEventName = capture ? symbolCapture : symbol ; } else { symbolEventName = symbolEventNames [ capture ? TRUE_STR : FALSE_STR ] ; } let existingTasks = target [ symbolEventName ] ; let isExisting = false ; if ( existingTasks ) { isExisting = true ; if ( checkDuplicate ) { for ( let i = 0 ; i < existingTasks . length ; i ++ ) { if ( compare ( existingTasks [ i ] , delegate ) ) { return ; } } } } else { existingTasks = target [ symbolEventName ] = [ ] ; } let source ; const constructorName = target . constructor [ 'name' ] ; const targetSource = globalSources [ constructorName ] ; if ( targetSource ) { source = targetSource [ eventName ] ; } if ( ! source ) { source = constructorName + addSource + ( eventNameToString ? eventNameToString ( eventName ) : eventName ) ; } taskData . options = options ; if ( once ) { taskData . options . once = false ; } taskData . target = target ; taskData . capture = capture ; taskData . eventName = eventName ; taskData . isExisting = isExisting ; const data = useGlobalCallback ? OPTIMIZED_ZONE_EVENT_TASK_DATA : undefined ; if ( data ) { ( data as any ) . taskData = taskData ; } const task = zone . scheduleEventTask ( source , delegate , data , customScheduleFn , customCancelFn ) ; taskData . target = null ; if ( data ) { ( data as any ) . taskData = null ; } if ( once ) { options . once = true ; } if ( ! ( ! passiveSupported && typeof task . options === 'boolean' ) ) { task . options = options ; } task . target = target ; task . capture = capture ; task . eventName = eventName ; if ( isHandleEvent ) { ( task as any ) . originalDelegate = delegate ; } if ( ! prepend ) { existingTasks . push ( task ) ; } else { existingTasks . unshift ( task ) ; } if ( returnTarget ) { return target ; } } ; } ; proto [ ADD_EVENT_LISTENER ] = makeAddListener ( nativeAddEventListener , ADD_EVENT_LISTENER_SOURCE , customSchedule , customCancel , returnTarget ) ; if ( nativePrependEventListener ) { proto [ PREPEND_EVENT_LISTENER ] = makeAddListener ( nativePrependEventListener , PREPEND_EVENT_LISTENER_SOURCE , customSchedulePrepend , customCancel , returnTarget , true ) ; } proto [ REMOVE_EVENT_LISTENER ] = function ( ) { const target = this || _global ; const eventName = arguments [ 0 ] ; const options = arguments [ 2 ] ; let capture ; if ( options === undefined ) { capture = false ; } else if ( options === true ) { capture = true ; } else if ( options === false ) { capture = false ; } else { capture = options ? ! ! options . capture : false ; } const delegate = arguments [ 1 ] ; if ( ! delegate ) { return nativeRemoveEventListener . apply ( this , arguments ) ; } if ( validateHandler && ! validateHandler ( nativeRemoveEventListener , delegate , target , arguments ) ) { return ; } const symbolEventNames = zoneSymbolEventNames [ eventName ] ; let symbolEventName ; if ( symbolEventNames ) { symbolEventName = symbolEventNames [ capture ? TRUE_STR : FALSE_STR ] ; } const existingTasks = symbolEventName && target [ symbolEventName ] ; if ( existingTasks ) { for ( let i = 0 ; i < existingTasks . length ; i ++ ) { const existingTask = existingTasks [ i ] ; if ( compare ( existingTask , delegate ) ) { existingTasks . splice ( i , 1 ) ; ( existingTask as any ) . isRemoved = true ; if ( existingTasks . length === 0 ) { ( existingTask as any ) . allRemoved = true ; target [ symbolEventName ] = null ; } existingTask . zone . cancelTask ( existingTask ) ; if ( returnTarget ) { return target ; } return ; } } } return nativeRemoveEventListener . apply ( this , arguments ) ; } ; proto [ LISTENERS_EVENT_LISTENER ] = function ( ) { const target = this || _global ; const eventName = arguments [ 0 ] ; const listeners = [ ] ; const tasks = findEventTasks ( target , eventNameToString ? eventNameToString ( eventName ) : eventName ) ; for ( let i = 0 ; i < tasks . length ; i ++ ) { const task = tasks [ i ] ; let delegate = task . originalDelegate ? task . originalDelegate : task . callback ; listeners . push ( delegate ) ; } return listeners ; } ; proto [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] = function ( ) { const target = this || _global ; const eventName = arguments [ 0 ] ; if ( ! eventName ) { const keys = Object . keys ( target ) ; for ( let i = 0 ; i < keys . length ; i ++ ) { const prop = keys [ i ] ; const match = EVENT_NAME_SYMBOL_REGX . exec ( prop ) ; let evtName = match && match [ 1 ] ; if ( evtName && evtName !== 'removeListener' ) { this [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] . call ( this , evtName ) ; } } this [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] . call ( this , 'removeListener' ) ; } else { const symbolEventNames = zoneSymbolEventNames [ eventName ] ; if ( symbolEventNames ) { const symbolEventName = symbolEventNames [ FALSE_STR ] ; const symbolCaptureEventName = symbolEventNames [ TRUE_STR ] ; const tasks = target [ symbolEventName ] ; const captureTasks = target [ symbolCaptureEventName ] ; if ( tasks ) { const removeTasks = tasks . slice ( ) ; for ( let i = 0 ; i < removeTasks . length ; i ++ ) { const task = removeTasks [ i ] ; let delegate = task . originalDelegate ? task . originalDelegate : task . callback ; this [ REMOVE_EVENT_LISTENER ] . call ( this , eventName , delegate , task . options ) ; } } if ( captureTasks ) { const removeTasks = captureTasks . slice ( ) ; for ( let i = 0 ; i < removeTasks . length ; i ++ ) { const task = removeTasks [ i ] ; let delegate = task . originalDelegate ? task . originalDelegate : task . callback ; this [ REMOVE_EVENT_LISTENER ] . call ( this , eventName , delegate , task . options ) ; } } } } if ( returnTarget ) { return this ; } } ; attachOriginToPatched ( proto [ ADD_EVENT_LISTENER ] , nativeAddEventListener ) ; attachOriginToPatched ( proto [ REMOVE_EVENT_LISTENER ] , nativeRemoveEventListener ) ; if ( nativeRemoveAllListeners ) { attachOriginToPatched ( proto [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] , nativeRemoveAllListeners ) ; } if ( nativeListeners ) { attachOriginToPatched ( proto [ LISTENERS_EVENT_LISTENER ] , nativeListeners ) ; } return true ; } let results = [ ] ; for ( let i = 0 ; i < apis . length ; i ++ ) { results [ i ] = patchEventTargetMethods ( apis [ i ] , patchOptions ) ; } return results ; } export function findEventTasks ( target , eventName ) { const foundTasks = [ ] ; for ( let prop in target ) { const match = EVENT_NAME_SYMBOL_REGX . exec ( prop ) ; let evtName = match && match [ 1 ] ; if ( evtName && ( ! eventName || evtName === eventName ) ) { const tasks = target [ prop ] ; if ( tasks ) { for ( let i = 0 ; i < tasks . length ; i ++ ) { foundTasks . push ( tasks [ i ] ) ; } } } } return foundTasks ; } export function patchEventPrototype ( global , api ) { const Event = global [ 'Event' ] ; if ( Event && Event . prototype ) { api . patchMethod ( Event . prototype , 'stopImmediatePropagation' , ( delegate ) => function ( self , args ) { self [ IMMEDIATE_PROPAGATION_SYMBOL ] = true ; delegate && delegate . apply ( self , args ) ; } ) ; } }