@@ -519,6 +519,21 @@ interface EventTask extends Task {
519519 /* TS v1.8 => type: 'eventTask'; */
520520}
521521
522+ /**
523+ * Extend the Error with additional fields for rewritten stack frames
524+ */
525+ interface Error {
526+ /**
527+ * Stack trace where extra frames have been removed and zone names added.
528+ */
529+ zoneAwareStack ?: string ;
530+
531+ /**
532+ * Original stack trace with no modiffications
533+ */
534+ originalStack ?: string ;
535+ }
536+
522537/** @internal */
523538type AmbientZone = Zone ;
524539/** @internal */
@@ -545,7 +560,7 @@ const Zone: ZoneType = (function(global: any) {
545560
546561
547562 static get current ( ) : AmbientZone {
548- return _currentZone ;
563+ return _currentZoneFrame . zone ;
549564 } ;
550565 static get currentTask ( ) : Task {
551566 return _currentTask ;
@@ -606,19 +621,17 @@ const Zone: ZoneType = (function(global: any) {
606621
607622 public run (
608623 callback : Function , applyThis : any = null , applyArgs : any [ ] = null , source : string = null ) {
609- const oldZone = _currentZone ;
610- _currentZone = this ;
624+ _currentZoneFrame = new ZoneFrame ( _currentZoneFrame , this ) ;
611625 try {
612626 return this . _zoneDelegate . invoke ( this , callback , applyThis , applyArgs , source ) ;
613627 } finally {
614- _currentZone = oldZone ;
628+ _currentZoneFrame = _currentZoneFrame . parent ;
615629 }
616630 }
617631
618632 public runGuarded (
619633 callback : Function , applyThis : any = null , applyArgs : any [ ] = null , source : string = null ) {
620- const oldZone = _currentZone ;
621- _currentZone = this ;
634+ _currentZoneFrame = new ZoneFrame ( _currentZoneFrame , this ) ;
622635 try {
623636 try {
624637 return this . _zoneDelegate . invoke ( this , callback , applyThis , applyArgs , source ) ;
@@ -628,7 +641,7 @@ const Zone: ZoneType = (function(global: any) {
628641 }
629642 }
630643 } finally {
631- _currentZone = oldZone ;
644+ _currentZoneFrame = _currentZoneFrame . parent ;
632645 }
633646 }
634647
@@ -641,8 +654,7 @@ const Zone: ZoneType = (function(global: any) {
641654 '; Execution: ' + this . name + ')' ) ;
642655 const previousTask = _currentTask ;
643656 _currentTask = task ;
644- const oldZone = _currentZone ;
645- _currentZone = this ;
657+ _currentZoneFrame = new ZoneFrame ( _currentZoneFrame , this ) ;
646658 try {
647659 if ( task . type == 'macroTask' && task . data && ! task . data . isPeriodic ) {
648660 task . cancelFn = null ;
@@ -655,7 +667,7 @@ const Zone: ZoneType = (function(global: any) {
655667 }
656668 }
657669 } finally {
658- _currentZone = oldZone ;
670+ _currentZoneFrame = _currentZoneFrame . parent ;
659671 _currentTask = previousTask ;
660672 }
661673 }
@@ -924,14 +936,23 @@ const Zone: ZoneType = (function(global: any) {
924936 rejection : any ;
925937 }
926938
939+ class ZoneFrame {
940+ public parent : ZoneFrame ;
941+ public zone : Zone ;
942+ constructor ( parent : ZoneFrame , zone : Zone ) {
943+ this . parent = parent ;
944+ this . zone = zone ;
945+ }
946+ }
947+
927948 function __symbol__ ( name : string ) {
928949 return '__zone_symbol__' + name ;
929950 } ;
930951 const symbolSetTimeout = __symbol__ ( 'setTimeout' ) ;
931952 const symbolPromise = __symbol__ ( 'Promise' ) ;
932953 const symbolThen = __symbol__ ( 'then' ) ;
933954
934- let _currentZone : Zone = new Zone ( null , null ) ;
955+ let _currentZoneFrame = new ZoneFrame ( null , new Zone ( null , null ) ) ;
935956 let _currentTask : Task = null ;
936957 let _microTaskQueue : Task [ ] = [ ] ;
937958 let _isDrainingMicrotaskQueue : boolean = false ;
@@ -1232,5 +1253,153 @@ const Zone: ZoneType = (function(global: any) {
12321253
12331254 // This is not part of public API, but it is usefull for tests, so we expose it.
12341255 Promise [ Zone . __symbol__ ( 'uncaughtPromiseErrors' ) ] = _uncaughtPromiseErrors ;
1256+
1257+ /*
1258+ * This code patches Error so that:
1259+ * - It ignores un-needed stack frames.
1260+ * - It Shows the associated Zone for reach frame.
1261+ */
1262+
1263+ enum FrameType {
1264+ /// Skip this frame when printing out stack
1265+ blackList ,
1266+ /// This frame marks zone transition
1267+ trasition
1268+ } ;
1269+ const NativeError = global [ __symbol__ ( 'Error' ) ] = global . Error ;
1270+ // Store the frames which should be removed from the stack frames
1271+ const blackListedStackFrames : { [ frame : string ] :FrameType } = { } ;
1272+ // We must find the frame where Error was created, otherwise we assume we don't understand stack
1273+ let zoneAwareFrame : string ;
1274+ global . Error = ZoneAwareError ;
1275+ // How should the stack frames be parsed.
1276+ let frameParserStrategy = null ;
1277+ const stackRewrite = 'stackRewrite' ;
1278+
1279+
1280+ /**
1281+ * This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as
1282+ * adds zone information to it.
1283+ */
1284+ function ZoneAwareError ( ) {
1285+ // Create an Error.
1286+ let error : Error = NativeError . apply ( this , arguments ) ;
1287+
1288+ // Save original stack trace
1289+ error . originalStack = error . stack ;
1290+
1291+ // Process the stack trace and rewrite the frames.
1292+ if ( ZoneAwareError [ stackRewrite ] && error . originalStack ) {
1293+ let frames : string [ ] = error . originalStack . split ( '\n' ) ;
1294+ let zoneFrame = _currentZoneFrame ;
1295+ let i = 0 ;
1296+ // Find the first frame
1297+ while ( frames [ i ] !== zoneAwareFrame && i < frames . length ) {
1298+ i ++ ;
1299+ }
1300+ for ( ; i < frames . length && zoneFrame ; i ++ ) {
1301+ let frame = frames [ i ] ;
1302+ if ( frame . trim ( ) ) {
1303+ let frameType = blackListedStackFrames . hasOwnProperty ( frame ) && blackListedStackFrames [ frame ] ;
1304+ if ( frameType === FrameType . blackList ) {
1305+ frames . splice ( i , 1 ) ;
1306+ i -- ;
1307+ } else if ( frameType === FrameType . trasition ) {
1308+ if ( zoneFrame . parent ) {
1309+ // This is the special frame where zone changed. Print and process it accordingly
1310+ frames [ i ] += ` [${ zoneFrame . parent . zone . name } => ${ zoneFrame . zone . name } ]` ;
1311+ zoneFrame = zoneFrame . parent ;
1312+ } else {
1313+ zoneFrame == null ;
1314+ }
1315+ } else {
1316+ frames [ i ] += ` [${ zoneFrame . zone . name } ]` ;
1317+ }
1318+ }
1319+ }
1320+ error . stack = error . zoneAwareStack = frames . join ( '\n' ) ;
1321+ }
1322+ return error ;
1323+ } ;
1324+ // Copy the prototype so that instanceof operator works as expected
1325+ ZoneAwareError . prototype = NativeError . prototype ;
1326+ ZoneAwareError [ Zone . __symbol__ ( 'blacklistedStackFrames' ) ] = blackListedStackFrames ;
1327+ ZoneAwareError [ stackRewrite ] = false ;
1328+
1329+ if ( NativeError . hasOwnProperty ( 'stackTraceLimit' ) ) {
1330+ // Extend default stack limit as we will be removing few frames.
1331+ NativeError . stackTraceLimit = Math . max ( NativeError . stackTraceLimit , 15 ) ;
1332+
1333+ // make sure that ZoneAwareError has the same property which forwards to NativeError.
1334+ Object . defineProperty ( ZoneAwareError , 'stackTraceLimit' , {
1335+ get : function ( ) { return NativeError . stackTraceLimit ; } ,
1336+ set : function ( value ) { return NativeError . stackTraceLimit = value ; }
1337+ } ) ;
1338+ }
1339+
1340+ // Now we need to populet the `blacklistedStackFrames` as well as find the
1341+ // run/runGuraded/runTask frames. This is done by creating a detect zone and then threading
1342+ // the execution through all of the above methods so that we can look at the stack trace and
1343+ // find the frames of interest.
1344+ let detectZone : Zone = Zone . current . fork ( {
1345+ name : 'detect' ,
1346+ onInvoke : function ( parentZoneDelegate : ZoneDelegate , currentZone : Zone , targetZone : Zone ,
1347+ delegate : Function , applyThis : any , applyArgs : any [ ] , source : string ) : any {
1348+ // Here only so that it will show up in the stack frame so that it can be black listed.
1349+ return parentZoneDelegate . invoke ( targetZone , delegate , applyThis , applyArgs , source ) ;
1350+ } ,
1351+ onHandleError : function ( parentZD : ZoneDelegate , current : Zone , target : Zone , error : any ) : boolean {
1352+ if ( error . originalStack && Error === ZoneAwareError ) {
1353+ let frames = error . originalStack . split ( / \n / ) ;
1354+ let runFrame = false , runGuardedFrame = false , runTaskFrame = false ;
1355+ while ( frames . length ) {
1356+ let frame = frames . shift ( ) ;
1357+ // On safari it is possible to have stack frame with no line number.
1358+ // This check makes sure that we don't filter frames on name only (must have linenumber)
1359+ if ( / : \d + : \d + / . test ( frame ) ) {
1360+ // Get rid of the path so that we don't accidintely find function name in path.
1361+ // In chrome the seperator is `(` and `@` in FF and safari
1362+ // Chrome: at Zone.run (zone.js:100)
1363+ // Chrome: at Zone.run (http://localhost:9876/base/build/lib/zone.js:100:24)
1364+ // FireFox: Zone.prototype.run@http ://localhost:9876/base/build/lib/zone.js:101:24
1365+ // Safari: run@http ://localhost:9876/base/build/lib/zone.js:101:24
1366+ let fnName : string = frame . split ( '(' ) [ 0 ] . split ( '@' ) [ 0 ] ;
1367+ let frameType = FrameType . trasition ;
1368+ if ( fnName . indexOf ( 'ZoneAwareError' ) !== - 1 ) {
1369+ zoneAwareFrame = frame ;
1370+ }
1371+ if ( fnName . indexOf ( 'runGuarded' ) !== - 1 ) {
1372+ runGuardedFrame = true ;
1373+ } else if ( fnName . indexOf ( 'runTask' ) !== - 1 ) {
1374+ runTaskFrame = true ;
1375+ } else if ( fnName . indexOf ( 'run' ) !== - 1 ) {
1376+ runFrame = true ;
1377+ } else {
1378+ frameType = FrameType . blackList ;
1379+ }
1380+ blackListedStackFrames [ frame ] = frameType ;
1381+ // Once we find all of the frames we can stop looking.
1382+ if ( runFrame && runGuardedFrame && runTaskFrame ) {
1383+ ZoneAwareError [ stackRewrite ] = true ;
1384+ break ;
1385+ }
1386+ }
1387+ }
1388+ }
1389+ return false ;
1390+ }
1391+ } ) as Zone ;
1392+ // carefully constructor a stack frame which contains all of the frames of interest which
1393+ // need to be detected and blacklisted.
1394+ let detectRunFn = ( ) => {
1395+ detectZone . run ( ( ) => {
1396+ detectZone . runGuarded ( ( ) => {
1397+ throw new Error ( 'blacklistStackFrames' ) ;
1398+ } ) ;
1399+ } ) ;
1400+ } ;
1401+ // Cause the error to extract the stack frames.
1402+ detectZone . runTask ( detectZone . scheduleMacroTask ( 'detect' , detectRunFn , null , ( ) => null , null ) ) ;
1403+
12351404 return global . Zone = Zone ;
12361405} ) ( typeof window === 'object' && window || typeof self === 'object' && self || global ) ;
0 commit comments