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

Commit 681a017

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 06d1ac0 commit 681a017

File tree

2 files changed

+213
-19
lines changed

2 files changed

+213
-19
lines changed

Diff for: lib/zone.ts

+62-17
Original file line numberDiff line numberDiff line change
@@ -1654,11 +1654,12 @@ const Zone: ZoneType = (function(global: any) {
16541654
case FrameType.transition:
16551655
if (zoneFrame.parent) {
16561656
// This is the special frame where zone changed. Print and process it accordingly
1657-
frames[i] += ` [${zoneFrame.parent.zone.name} => ${zoneFrame.zone.name}]`;
16581657
zoneFrame = zoneFrame.parent;
16591658
} else {
16601659
zoneFrame = null;
16611660
}
1661+
frames.splice(i, 1);
1662+
i--;
16621663
break;
16631664
default:
16641665
frames[i] += ` [${zoneFrame.zone.name}]`;
@@ -1770,12 +1771,6 @@ const Zone: ZoneType = (function(global: any) {
17701771
// find the frames of interest.
17711772
let detectZone: Zone = Zone.current.fork({
17721773
name: 'detect',
1773-
onInvoke: function(
1774-
parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, delegate: Function,
1775-
applyThis: any, applyArgs: any[], source: string): any {
1776-
// Here only so that it will show up in the stack frame so that it can be black listed.
1777-
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
1778-
},
17791774
onHandleError: function(parentZD: ZoneDelegate, current: Zone, target: Zone, error: any):
17801775
boolean {
17811776
if (error.originalStack && Error === ZoneAwareError) {
@@ -1824,17 +1819,67 @@ const Zone: ZoneType = (function(global: any) {
18241819
// carefully constructor a stack frame which contains all of the frames of interest which
18251820
// need to be detected and blacklisted.
18261821

1827-
// carefully constructor a stack frame which contains all of the frames of interest which
1828-
// need to be detected and blacklisted.
1829-
let detectRunFn = () => {
1830-
detectZone.run(() => {
1831-
detectZone.runGuarded(() => {
1832-
throw new (ZoneAwareError as any)(ZoneAwareError, NativeError);
1833-
});
1822+
const childDetectZone = detectZone.fork({
1823+
name: 'child',
1824+
onScheduleTask: function(delegate, curr, target, task) {
1825+
return delegate.scheduleTask(target, task);
1826+
},
1827+
onInvokeTask: function(delegate, curr, target, task, applyThis, applyArgs) {
1828+
return delegate.invokeTask(target, task, applyThis, applyArgs);
1829+
},
1830+
onCancelTask: function(delegate, curr, target, task) {
1831+
return delegate.cancelTask(target, task);
1832+
},
1833+
onInvoke: function(delegate, curr, target, callback, applyThis, applyArgs, source) {
1834+
return delegate.invoke(target, callback, applyThis, applyArgs, source);
1835+
}
1836+
});
1837+
1838+
// we need to detect all zone related frames, it will
1839+
// exceed default stackTraceLimit, so we set it to
1840+
// larger number here, and restore it after detect finish.
1841+
const originalStackTraceLimit = Error.stackTraceLimit;
1842+
Error.stackTraceLimit = 100;
1843+
// we schedule event/micro/macro task, and invoke them
1844+
// when onSchedule, so we can get all stack traces for
1845+
// all kinds of tasks with one error thrown.
1846+
childDetectZone.run(() => {
1847+
childDetectZone.runGuarded(() => {
1848+
const fakeTransitionTo =
1849+
(toState: TaskState, fromState1: TaskState, fromState2: TaskState) => {};
1850+
childDetectZone.scheduleEventTask(
1851+
blacklistedStackFramesSymbol,
1852+
() => {
1853+
childDetectZone.scheduleMacroTask(
1854+
blacklistedStackFramesSymbol,
1855+
() => {
1856+
childDetectZone.scheduleMicroTask(
1857+
blacklistedStackFramesSymbol,
1858+
() => {
1859+
throw new (ZoneAwareError as any)(ZoneAwareError, NativeError);
1860+
},
1861+
null,
1862+
(t: Task) => {
1863+
(t as any)._transitionTo = fakeTransitionTo;
1864+
t.invoke();
1865+
});
1866+
},
1867+
null,
1868+
(t) => {
1869+
(t as any)._transitionTo = fakeTransitionTo;
1870+
t.invoke();
1871+
},
1872+
() => {});
1873+
},
1874+
null,
1875+
(t) => {
1876+
(t as any)._transitionTo = fakeTransitionTo;
1877+
t.invoke();
1878+
},
1879+
() => {});
18341880
});
1835-
};
1836-
// Cause the error to extract the stack frames.
1837-
detectZone.runTask(detectZone.scheduleMacroTask('detect', detectRunFn, null, () => null, null));
1881+
});
1882+
Error.stackTraceLimit = originalStackTraceLimit;
18381883

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

Diff for: test/common/Error.spec.ts

+151-2
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)