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

Commit 0db2680

Browse files
committed
fix(error): add more handling for IE
1 parent 858b482 commit 0db2680

File tree

2 files changed

+153
-35
lines changed

2 files changed

+153
-35
lines changed

Diff for: lib/zone.ts

+130-31
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,8 @@ const Zone: ZoneType = (function(global: any) {
16571657
// in NativeError
16581658
createProperty(props, 'originalStack');
16591659
createProperty(props, 'zoneAwareStack');
1660+
// in IE11, stack is not in prototype
1661+
createProperty(props, 'stack');
16601662

16611663
// define toString, toSource as method property
16621664
createMethodProperty(props, 'toString');
@@ -1700,32 +1702,17 @@ const Zone: ZoneType = (function(global: any) {
17001702
'long-stack-trace'
17011703
];
17021704

1703-
/**
1704-
* This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as
1705-
* adds zone information to it.
1706-
*/
1707-
function ZoneAwareError() {
1708-
// make sure we have a valid this
1709-
// if this is undefined(call Error without new) or this is global
1710-
// or this is some other objects, we should force to create a
1711-
// valid ZoneAwareError by call Object.create()
1712-
if (!(this instanceof ZoneAwareError)) {
1713-
return ZoneAwareError.apply(Object.create(ZoneAwareError.prototype), arguments);
1714-
}
1715-
// Create an Error.
1716-
let error: Error = NativeError.apply(this, arguments);
1717-
this[__symbol__('error')] = error;
1718-
1705+
function attachZoneAndRemoveInternalZoneFrames(error: any) {
17191706
// Save original stack trace
17201707
error.originalStack = error.stack;
1721-
17221708
// Process the stack trace and rewrite the frames.
17231709
if ((ZoneAwareError as any)[stackRewrite] && error.originalStack) {
17241710
let frames: string[] = error.originalStack.split('\n');
17251711
let zoneFrame = _currentZoneFrame;
17261712
let i = 0;
17271713
// Find the first frame
1728-
while (i < frames.length && zoneAwareFrame.filter(zf => zf === frames[i]).length === 0) {
1714+
while (i < frames.length &&
1715+
zoneAwareFrame.filter(zf => zf.trim() === frames[i].trim()).length === 0) {
17291716
i++;
17301717
}
17311718
for (; i < frames.length && zoneFrame; i++) {
@@ -1758,9 +1745,41 @@ const Zone: ZoneType = (function(global: any) {
17581745
}
17591746
error.stack = error.zoneAwareStack = frames.join('\n');
17601747
}
1748+
}
1749+
1750+
/**
1751+
* This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as
1752+
* adds zone information to it.
1753+
*/
1754+
function ZoneAwareError() {
1755+
// make sure we have a valid this
1756+
// if this is undefined(call Error without new) or this is global
1757+
// or this is some other objects, we should force to create a
1758+
// valid ZoneAwareError by call Object.create()
1759+
if (!(this instanceof ZoneAwareError)) {
1760+
return ZoneAwareError.apply(Object.create(ZoneAwareError.prototype), arguments);
1761+
}
1762+
// Create an Error.
1763+
let error: Error = NativeError.apply(this, arguments);
1764+
if (!error.stack) {
1765+
// in IE, the error.stack will be undefined
1766+
// when error was constructed, it will only
1767+
// be available when throw
1768+
try {
1769+
throw error;
1770+
} catch (err) {
1771+
error = err;
1772+
}
1773+
}
1774+
this[__symbol__('error')] = error;
1775+
// 1. attach zone information to stack frame
1776+
// 2. remove zone internal stack frames
1777+
attachZoneAndRemoveInternalZoneFrames(error);
1778+
17611779
// use defineProperties here instead of copy property value
17621780
// because of issue #595 which will break angular2.
1763-
Object.defineProperties(this, getErrorPropertiesForPrototype(Object.getPrototypeOf(this)));
1781+
const props = getErrorPropertiesForPrototype(Object.getPrototypeOf(this));
1782+
Object.defineProperties(this, props);
17641783
return this;
17651784
}
17661785

@@ -1896,17 +1915,44 @@ const Zone: ZoneType = (function(global: any) {
18961915
}) as Zone;
18971916
// carefully constructor a stack frame which contains all of the frames of interest which
18981917
// need to be detected and blacklisted.
1918+
1919+
// use this method to handle
1920+
// 1. IE issue, the error.stack can only be not undefined after throw
1921+
// 2. handle Error(...) without new options
1922+
const throwError = (message: string, withNew: boolean = true) => {
1923+
let error;
1924+
try {
1925+
if (withNew) {
1926+
throw new Error(message);
1927+
} else {
1928+
throw Error(message);
1929+
}
1930+
} catch (err) {
1931+
error = err;
1932+
}
1933+
return error;
1934+
};
1935+
1936+
const nativeStackTraceLimit = NativeError.stackTraceLimit;
1937+
// in some system/browser, some additional stack frames
1938+
// will be generated (such as inline function)
1939+
// so the the stack frame to check ZoneAwareError Start
1940+
// maybe ignored because the frame's number will exceed
1941+
// stackTraceLimit, so we just set stackTraceLimit to 100
1942+
// and reset after all detect work is done.
1943+
NativeError.stackTraceLimit = 100;
18991944
let detectRunFn = () => {
19001945
detectZone.run(() => {
19011946
detectZone.runGuarded(() => {
1902-
throw new Error('blacklistStackFrames');
1947+
throw throwError('blacklistStackFrames');
19031948
});
19041949
});
19051950
};
1951+
19061952
let detectRunWithoutNewFn = () => {
19071953
detectZone.run(() => {
19081954
detectZone.runGuarded(() => {
1909-
throw Error('blacklistStackFrames');
1955+
throw throwError('blacklistStackFrames', false);
19101956
});
19111957
});
19121958
};
@@ -1948,33 +1994,40 @@ const Zone: ZoneType = (function(global: any) {
19481994
const detectZoneWithCallbacks = Zone.root.fork({
19491995
name: 'detectCallbackZone',
19501996
onFork: (parentDelegate, currentZone, targetZone, zoneSpec) => {
1951-
handleDetectError(Error('onFork'));
1997+
handleDetectError(throwError('onFork'));
1998+
handleDetectError(throwError('onFork', false));
19521999
return parentDelegate.fork(targetZone, zoneSpec);
19532000
},
19542001
onIntercept: (parentDelegate, currentZone, targetZone, delegate, source) => {
1955-
handleDetectError(Error('onIntercept'));
2002+
handleDetectError(throwError('onIntercept'));
2003+
handleDetectError(throwError('onIntercept', false));
19562004
return parentDelegate.intercept(targetZone, delegate, source);
19572005
},
19582006
onInvoke:
19592007
(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) => {
1960-
handleDetectError(Error('onInvoke'));
2008+
handleDetectError(throwError('onInvoke'));
2009+
handleDetectError(throwError('onInvoke', false));
19612010
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
19622011
},
19632012
onScheduleTask: (parentZoneDelegate, currentZone, targetZone, task) => {
1964-
handleDetectError(Error('onScheduleTask'));
2013+
handleDetectError(throwError('onScheduleTask'));
2014+
handleDetectError(throwError('onScheduleTask', false));
19652015
return parentZoneDelegate.scheduleTask(targetZone, task);
19662016
},
19672017
onInvokeTask: (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) => {
1968-
handleDetectError(Error('onInvokeTask'));
2018+
handleDetectError(throwError('onInvokeTask'));
2019+
handleDetectError(throwError('onInvokeTask', false));
19692020
return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
19702021
},
19712022
onCancelTask: (parentZoneDelegate, currentZone, targetZone, task) => {
1972-
handleDetectError(Error('onCancelTask'));
2023+
handleDetectError(throwError('onCancelTask'));
2024+
handleDetectError(throwError('onCancelTask', false));
19732025
return parentZoneDelegate.cancelTask(targetZone, task);
19742026
},
19752027

19762028
onHasTask: (delegate, current, target, hasTaskState) => {
1977-
handleDetectError(Error('onHasTask'));
2029+
handleDetectError(throwError('onHasTask'));
2030+
handleDetectError(throwError('onHasTask', false));
19782031
return delegate.hasTask(target, hasTaskState);
19792032
},
19802033

@@ -1986,18 +2039,37 @@ const Zone: ZoneType = (function(global: any) {
19862039
});
19872040

19882041
let detectFn = () => {
1989-
throw Error('zoneAwareFrames');
2042+
throw throwError('zoneAwareFrames');
2043+
};
2044+
2045+
let detectWithoutNewFn = () => {
2046+
throw throwError('zoneAwareFrames', false);
19902047
};
19912048

19922049
let detectPromiseFn = () => {
19932050
new Promise((resolve, reject) => {
1994-
reject(Error('zoneAwareFrames'));
2051+
reject(throwError('zoneAwareFrames'));
2052+
});
2053+
};
2054+
2055+
let detectPromiseWithoutNewFn = () => {
2056+
new Promise((resolve, reject) => {
2057+
reject(throwError('zoneAwareFrames', false));
19952058
});
19962059
};
19972060

19982061
let detectPromiseCaughtFn = () => {
19992062
const p = new Promise((resolve, reject) => {
2000-
reject(Error('zoneAwareFrames'));
2063+
reject(throwError('zoneAwareFrames'));
2064+
});
2065+
p.catch(err => {
2066+
throw err;
2067+
});
2068+
};
2069+
2070+
let detectPromiseCaughtWithoutNewFn = () => {
2071+
const p = new Promise((resolve, reject) => {
2072+
reject(throwError('zoneAwareFrames', false));
20012073
});
20022074
p.catch(err => {
20032075
throw err;
@@ -2024,11 +2096,38 @@ const Zone: ZoneType = (function(global: any) {
20242096
detectEmptyZone.run(detectFn);
20252097
});
20262098

2099+
detectEmptyZone.runTask(
2100+
detectEmptyZone.scheduleEventTask('detect', detectWithoutNewFn, null, () => null, null));
2101+
detectZoneWithCallbacks.runTask(detectZoneWithCallbacks.scheduleEventTask(
2102+
'detect', detectWithoutNewFn, null, () => null, null));
2103+
detectEmptyZone.runTask(
2104+
detectEmptyZone.scheduleMacroTask('detect', detectWithoutNewFn, null, () => null, null));
2105+
detectZoneWithCallbacks.runTask(detectZoneWithCallbacks.scheduleMacroTask(
2106+
'detect', detectWithoutNewFn, null, () => null, null));
2107+
detectEmptyZone.runTask(
2108+
detectEmptyZone.scheduleMicroTask('detect', detectWithoutNewFn, null, () => null));
2109+
detectZoneWithCallbacks.runTask(
2110+
detectZoneWithCallbacks.scheduleMicroTask('detect', detectWithoutNewFn, null, () => null));
2111+
2112+
detectEmptyZone.runGuarded(() => {
2113+
detectEmptyZone.run(detectWithoutNewFn);
2114+
});
2115+
detectZoneWithCallbacks.runGuarded(() => {
2116+
detectEmptyZone.run(detectWithoutNewFn);
2117+
});
2118+
20272119
detectEmptyZone.runGuarded(detectPromiseFn);
20282120
detectZoneWithCallbacks.runGuarded(detectPromiseFn);
20292121

2122+
detectEmptyZone.runGuarded(detectPromiseWithoutNewFn);
2123+
detectZoneWithCallbacks.runGuarded(detectPromiseWithoutNewFn);
2124+
20302125
detectEmptyZone.runGuarded(detectPromiseCaughtFn);
20312126
detectZoneWithCallbacks.runGuarded(detectPromiseCaughtFn);
20322127

2128+
detectEmptyZone.runGuarded(detectPromiseCaughtWithoutNewFn);
2129+
detectZoneWithCallbacks.runGuarded(detectPromiseCaughtWithoutNewFn);
2130+
NativeError.stackTraceLimit = nativeStackTraceLimit;
2131+
20332132
return global['Zone'] = Zone;
20342133
})(typeof window === 'object' && window || typeof self === 'object' && self || global);

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

+23-4
Original file line numberDiff line numberDiff line change
@@ -220,18 +220,39 @@ describe('ZoneAwareError', () => {
220220
if (/new Error/.test(outsideFrames[0])) {
221221
outsideFrames.shift();
222222
}
223+
223224
if (/Outside/.test(outsideWithoutNewFrames[0])) {
224225
outsideWithoutNewFrames.shift();
225226
}
227+
if (/new Error/.test(outsideWithoutNewFrames[0])) {
228+
outsideWithoutNewFrames.shift();
229+
}
230+
if (/Error.ZoneAwareError/.test(outsideWithoutNewFrames[0])) {
231+
outsideWithoutNewFrames.shift();
232+
}
233+
if (/ZoneAwareError/.test(outsideWithoutNewFrames[0])) {
234+
outsideWithoutNewFrames.shift();
235+
}
236+
226237
if (/Inside/.test(insideFrames[0])) {
227238
insideFrames.shift();
228239
}
229240
if (/new Error/.test(insideFrames[0])) {
230241
insideFrames.shift();
231242
}
243+
232244
if (/Inside/.test(insideWithoutNewFrames[0])) {
233245
insideWithoutNewFrames.shift();
234246
}
247+
if (/new Error/.test(insideWithoutNewFrames[0])) {
248+
insideWithoutNewFrames.shift();
249+
}
250+
if (/Error.ZoneAwareError/.test(insideWithoutNewFrames[0])) {
251+
insideWithoutNewFrames.shift();
252+
}
253+
if (/ZoneAwareError/.test(insideWithoutNewFrames[0])) {
254+
insideWithoutNewFrames.shift();
255+
}
235256

236257
expect(outsideFrames[0]).toMatch(/testFn.*[<root>]/);
237258

@@ -249,7 +270,7 @@ describe('ZoneAwareError', () => {
249270
const zoneAwareFrames = [
250271
'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask',
251272
'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask', 'ZoneDelegate.invokeTask',
252-
'ZoneTask.invoke', 'zoneAwareAddListener', 'drainMicroTaskQueue', 'LongStackTrace',
273+
'ZoneTask.invoke', 'zoneAwareAddListener', 'drainMicroTaskQueue', 'new LongStackTrace',
253274
'getStacktraceWithUncaughtError'
254275
];
255276

@@ -283,11 +304,10 @@ const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) {
283304
};
284305

285306
describe('Error stack', () => {
286-
(Zone as any)['debug'] = true;
287307
it('new Error which occurs in setTimeout callback should not have zone frames visible',
288308
assertStackDoesNotContainZoneFramesTest(() => {
289309
setTimeout(() => {
290-
throw new Error('test error');
310+
throw new Error('timeout test error');
291311
}, 10);
292312
}));
293313

@@ -333,7 +353,6 @@ describe('Error stack', () => {
333353

334354
it('stack frames of the callback in user customized zoneSpec should be kept',
335355
assertStackDoesNotContainZoneFramesTest(() => {
336-
(Zone as any)['debug'] = false;
337356
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
338357
.fork({
339358
name: 'customZone',

0 commit comments

Comments
 (0)