Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit 8142c3b

Browse files
JiaLiPassionmhevery
authored andcommitted
feat(error): Remove all Zone frames from stack (#693)
* feat(error): an idea for simple stack * merge detectZone
1 parent f514c11 commit 8142c3b

2 files changed

Lines changed: 213 additions & 19 deletions

File tree

lib/zone.ts

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,11 +1671,12 @@ const Zone: ZoneType = (function(global: any) {
16711671
case FrameType.transition:
16721672
if (zoneFrame.parent) {
16731673
// This is the special frame where zone changed. Print and process it accordingly
1674-
frames[i] += ` [${zoneFrame.parent.zone.name} => ${zoneFrame.zone.name}]`;
16751674
zoneFrame = zoneFrame.parent;
16761675
} else {
16771676
zoneFrame = null;
16781677
}
1678+
frames.splice(i, 1);
1679+
i--;
16791680
break;
16801681
default:
16811682
frames[i] += ` [${zoneFrame.zone.name}]`;
@@ -1787,12 +1788,6 @@ const Zone: ZoneType = (function(global: any) {
17871788
// find the frames of interest.
17881789
let detectZone: Zone = Zone.current.fork({
17891790
name: 'detect',
1790-
onInvoke: function(
1791-
parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, delegate: Function,
1792-
applyThis: any, applyArgs: any[], source: string): any {
1793-
// Here only so that it will show up in the stack frame so that it can be black listed.
1794-
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
1795-
},
17961791
onHandleError: function(parentZD: ZoneDelegate, current: Zone, target: Zone, error: any):
17971792
boolean {
17981793
if (error.originalStack && Error === ZoneAwareError) {
@@ -1854,17 +1849,67 @@ const Zone: ZoneType = (function(global: any) {
18541849
// carefully constructor a stack frame which contains all of the frames of interest which
18551850
// need to be detected and blacklisted.
18561851

1857-
// carefully constructor a stack frame which contains all of the frames of interest which
1858-
// need to be detected and blacklisted.
1859-
let detectRunFn = () => {
1860-
detectZone.run(() => {
1861-
detectZone.runGuarded(() => {
1862-
throw new (ZoneAwareError as any)(ZoneAwareError, NativeError);
1863-
});
1852+
const childDetectZone = detectZone.fork({
1853+
name: 'child',
1854+
onScheduleTask: function(delegate, curr, target, task) {
1855+
return delegate.scheduleTask(target, task);
1856+
},
1857+
onInvokeTask: function(delegate, curr, target, task, applyThis, applyArgs) {
1858+
return delegate.invokeTask(target, task, applyThis, applyArgs);
1859+
},
1860+
onCancelTask: function(delegate, curr, target, task) {
1861+
return delegate.cancelTask(target, task);
1862+
},
1863+
onInvoke: function(delegate, curr, target, callback, applyThis, applyArgs, source) {
1864+
return delegate.invoke(target, callback, applyThis, applyArgs, source);
1865+
}
1866+
});
1867+
1868+
// we need to detect all zone related frames, it will
1869+
// exceed default stackTraceLimit, so we set it to
1870+
// larger number here, and restore it after detect finish.
1871+
const originalStackTraceLimit = Error.stackTraceLimit;
1872+
Error.stackTraceLimit = 100;
1873+
// we schedule event/micro/macro task, and invoke them
1874+
// when onSchedule, so we can get all stack traces for
1875+
// all kinds of tasks with one error thrown.
1876+
childDetectZone.run(() => {
1877+
childDetectZone.runGuarded(() => {
1878+
const fakeTransitionTo =
1879+
(toState: TaskState, fromState1: TaskState, fromState2: TaskState) => {};
1880+
childDetectZone.scheduleEventTask(
1881+
blacklistedStackFramesSymbol,
1882+
() => {
1883+
childDetectZone.scheduleMacroTask(
1884+
blacklistedStackFramesSymbol,
1885+
() => {
1886+
childDetectZone.scheduleMicroTask(
1887+
blacklistedStackFramesSymbol,
1888+
() => {
1889+
throw new (ZoneAwareError as any)(ZoneAwareError, NativeError);
1890+
},
1891+
null,
1892+
(t: Task) => {
1893+
(t as any)._transitionTo = fakeTransitionTo;
1894+
t.invoke();
1895+
});
1896+
},
1897+
null,
1898+
(t) => {
1899+
(t as any)._transitionTo = fakeTransitionTo;
1900+
t.invoke();
1901+
},
1902+
() => {});
1903+
},
1904+
null,
1905+
(t) => {
1906+
(t as any)._transitionTo = fakeTransitionTo;
1907+
t.invoke();
1908+
},
1909+
() => {});
18641910
});
1865-
};
1866-
// Cause the error to extract the stack frames.
1867-
detectZone.runTask(detectZone.scheduleMacroTask('detect', detectRunFn, null, () => null, null));
1911+
});
1912+
Error.stackTraceLimit = originalStackTraceLimit;
18681913

18691914
return global['Zone'] = Zone;
18701915
})(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global);

test/common/Error.spec.ts

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,161 @@ describe('ZoneAwareError', () => {
258258
expect(outsideFrames[0]).toMatch(/testFn.*[<root>]/);
259259

260260
expect(insideFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
261-
expect(insideFrames[2]).toMatch(/testFn.*[<root>]]/);
261+
expect(insideFrames[1]).toMatch(/testFn.*[<root>]]/);
262262

263263
expect(outsideWithoutNewFrames[0]).toMatch(/testFn.*[<root>]/);
264264

265265
expect(insideWithoutNewFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
266-
expect(insideWithoutNewFrames[2]).toMatch(/testFn.*[<root>]]/);
266+
expect(insideWithoutNewFrames[1]).toMatch(/testFn.*[<root>]]/);
267267
}
268268
});
269+
270+
const zoneAwareFrames = [
271+
'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask',
272+
'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask',
273+
'ZoneDelegate.invokeTask', 'zoneAwareAddListener'
274+
];
275+
276+
function assertStackDoesNotContainZoneFrames(err: Error) {
277+
const frames = err.stack.split('\n');
278+
for (let i = 0; i < frames.length; i++) {
279+
expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]);
280+
}
281+
};
282+
283+
const errorZoneSpec = {
284+
name: 'errorZone',
285+
done: <() => void>null,
286+
onHandleError:
287+
(parentDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: Error) => {
288+
assertStackDoesNotContainZoneFrames(error);
289+
setTimeout(() => {
290+
errorZoneSpec.done && errorZoneSpec.done();
291+
}, 0);
292+
return false;
293+
}
294+
};
295+
296+
const errorZone = Zone.root.fork(errorZoneSpec);
297+
298+
const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) {
299+
return function(done: () => void) {
300+
errorZoneSpec.done = done;
301+
errorZone.run(testFn);
302+
};
303+
};
304+
305+
describe('Error stack', () => {
306+
it('Error with new which occurs in setTimeout callback should not have zone frames visible',
307+
assertStackDoesNotContainZoneFramesTest(() => {
308+
setTimeout(() => {
309+
throw new Error('timeout test error');
310+
}, 10);
311+
}));
312+
313+
it('Error without new which occurs in setTimeout callback should not have zone frames visible',
314+
assertStackDoesNotContainZoneFramesTest(() => {
315+
setTimeout(() => {
316+
throw Error('test error');
317+
}, 10);
318+
}));
319+
320+
it('Error with new which cause by promise rejection should not have zone frames visible',
321+
(done) => {
322+
const p = new Promise((resolve, reject) => {
323+
reject(new Error('test error'));
324+
});
325+
p.catch(err => {
326+
assertStackDoesNotContainZoneFrames(err);
327+
done();
328+
});
329+
});
330+
331+
it('Error without new which cause by promise rejection should not have zone frames visible',
332+
(done) => {
333+
const p = new Promise((resolve, reject) => {
334+
reject(Error('test error'));
335+
});
336+
p.catch(err => {
337+
assertStackDoesNotContainZoneFrames(err);
338+
done();
339+
});
340+
});
341+
342+
it('Error with new which occurs in eventTask callback should not have zone frames visible',
343+
assertStackDoesNotContainZoneFramesTest(() => {
344+
const task = Zone.current.scheduleEventTask('errorEvent', () => {
345+
throw new Error('test error');
346+
}, null, () => null, null);
347+
task.invoke();
348+
}));
349+
350+
it('Error without new which occurs in eventTask callback should not have zone frames visible',
351+
assertStackDoesNotContainZoneFramesTest(() => {
352+
const task = Zone.current.scheduleEventTask('errorEvent', () => {
353+
throw Error('test error');
354+
}, null, () => null, null);
355+
task.invoke();
356+
}));
357+
358+
it('Error with new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
359+
assertStackDoesNotContainZoneFramesTest(() => {
360+
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
361+
.scheduleEventTask('errorEvent', () => {
362+
throw new Error('test error');
363+
}, null, () => null, null);
364+
task.invoke();
365+
}));
366+
367+
it('Error without new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
368+
assertStackDoesNotContainZoneFramesTest(() => {
369+
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
370+
.scheduleEventTask('errorEvent', () => {
371+
throw Error('test error');
372+
}, null, () => null, null);
373+
task.invoke();
374+
}));
375+
376+
it('stack frames of the callback in user customized zoneSpec should be kept',
377+
assertStackDoesNotContainZoneFramesTest(() => {
378+
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
379+
.fork({
380+
name: 'customZone',
381+
onScheduleTask: (parentDelegate, currentZone, targetZone, task) => {
382+
return parentDelegate.scheduleTask(targetZone, task);
383+
},
384+
onHandleError: (parentDelegate, currentZone, targetZone, error) => {
385+
parentDelegate.handleError(targetZone, error);
386+
const containsCustomZoneSpecStackTrace =
387+
error.stack.indexOf('onScheduleTask') !== -1;
388+
expect(containsCustomZoneSpecStackTrace).toBeTruthy();
389+
return false;
390+
}
391+
})
392+
.scheduleEventTask('errorEvent', () => {
393+
throw new Error('test error');
394+
}, null, () => null, null);
395+
task.invoke();
396+
}));
397+
398+
it('should be able to generate zone free stack even NativeError stack is readonly', function() {
399+
const _global: any =
400+
typeof window === 'object' && window || typeof self === 'object' && self || global;
401+
const NativeError = _global['__zone_symbol__Error'];
402+
const desc = Object.getOwnPropertyDescriptor(NativeError.prototype, 'stack');
403+
if (desc) {
404+
const originalSet: (value: any) => void = desc.set;
405+
// make stack readonly
406+
desc.set = null;
407+
408+
try {
409+
const error = new Error('test error');
410+
expect(error.stack).toBeTruthy();
411+
assertStackDoesNotContainZoneFrames(error);
412+
} finally {
413+
desc.set = originalSet;
414+
}
415+
}
416+
});
417+
});
269418
});

0 commit comments

Comments
 (0)