@@ -519,6 +519,21 @@ interface EventTask extends Task {
519
519
/* TS v1.8 => type: 'eventTask'; */
520
520
}
521
521
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
+
522
537
/** @internal */
523
538
type AmbientZone = Zone ;
524
539
/** @internal */
@@ -545,7 +560,7 @@ const Zone: ZoneType = (function(global: any) {
545
560
546
561
547
562
static get current ( ) : AmbientZone {
548
- return _currentZone ;
563
+ return _currentZoneFrame . zone ;
549
564
} ;
550
565
static get currentTask ( ) : Task {
551
566
return _currentTask ;
@@ -606,19 +621,17 @@ const Zone: ZoneType = (function(global: any) {
606
621
607
622
public run (
608
623
callback : Function , applyThis : any = null , applyArgs : any [ ] = null , source : string = null ) {
609
- const oldZone = _currentZone ;
610
- _currentZone = this ;
624
+ _currentZoneFrame = new ZoneFrame ( _currentZoneFrame , this ) ;
611
625
try {
612
626
return this . _zoneDelegate . invoke ( this , callback , applyThis , applyArgs , source ) ;
613
627
} finally {
614
- _currentZone = oldZone ;
628
+ _currentZoneFrame = _currentZoneFrame . parent ;
615
629
}
616
630
}
617
631
618
632
public runGuarded (
619
633
callback : Function , applyThis : any = null , applyArgs : any [ ] = null , source : string = null ) {
620
- const oldZone = _currentZone ;
621
- _currentZone = this ;
634
+ _currentZoneFrame = new ZoneFrame ( _currentZoneFrame , this ) ;
622
635
try {
623
636
try {
624
637
return this . _zoneDelegate . invoke ( this , callback , applyThis , applyArgs , source ) ;
@@ -628,7 +641,7 @@ const Zone: ZoneType = (function(global: any) {
628
641
}
629
642
}
630
643
} finally {
631
- _currentZone = oldZone ;
644
+ _currentZoneFrame = _currentZoneFrame . parent ;
632
645
}
633
646
}
634
647
@@ -641,8 +654,7 @@ const Zone: ZoneType = (function(global: any) {
641
654
'; Execution: ' + this . name + ')' ) ;
642
655
const previousTask = _currentTask ;
643
656
_currentTask = task ;
644
- const oldZone = _currentZone ;
645
- _currentZone = this ;
657
+ _currentZoneFrame = new ZoneFrame ( _currentZoneFrame , this ) ;
646
658
try {
647
659
if ( task . type == 'macroTask' && task . data && ! task . data . isPeriodic ) {
648
660
task . cancelFn = null ;
@@ -655,7 +667,7 @@ const Zone: ZoneType = (function(global: any) {
655
667
}
656
668
}
657
669
} finally {
658
- _currentZone = oldZone ;
670
+ _currentZoneFrame = _currentZoneFrame . parent ;
659
671
_currentTask = previousTask ;
660
672
}
661
673
}
@@ -924,14 +936,23 @@ const Zone: ZoneType = (function(global: any) {
924
936
rejection : any ;
925
937
}
926
938
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
+
927
948
function __symbol__ ( name : string ) {
928
949
return '__zone_symbol__' + name ;
929
950
} ;
930
951
const symbolSetTimeout = __symbol__ ( 'setTimeout' ) ;
931
952
const symbolPromise = __symbol__ ( 'Promise' ) ;
932
953
const symbolThen = __symbol__ ( 'then' ) ;
933
954
934
- let _currentZone : Zone = new Zone ( null , null ) ;
955
+ let _currentZoneFrame = new ZoneFrame ( null , new Zone ( null , null ) ) ;
935
956
let _currentTask : Task = null ;
936
957
let _microTaskQueue : Task [ ] = [ ] ;
937
958
let _isDrainingMicrotaskQueue : boolean = false ;
@@ -1232,5 +1253,153 @@ const Zone: ZoneType = (function(global: any) {
1232
1253
1233
1254
// This is not part of public API, but it is usefull for tests, so we expose it.
1234
1255
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
+
1235
1404
return global . Zone = Zone ;
1236
1405
} ) ( typeof window === 'object' && window || typeof self === 'object' && self || global ) ;
0 commit comments