Skip to content

Commit bcd09a0

Browse files
committed
fix: stack rewriting now works with source maps
In Chrome for the source maps to work the object must be an instance of native `Error`. This means that we can’t return a subclass of `Error` or error handling in the dev console will not work properly. In addition the stack frames must have a certain format or the source mapping is disabled. For this reason we have changed the long stack format to conform to that shape.
1 parent 0e19304 commit bcd09a0

File tree

12 files changed

+153
-396
lines changed

12 files changed

+153
-396
lines changed

lib/browser/browser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const set = 'set';
1818
const clear = 'clear';
1919
const blockingMethods = ['alert', 'prompt', 'confirm'];
2020
const _global: any =
21-
typeof window === 'object' && window || typeof self === 'object' && self || global;
21+
typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global;
2222

2323
patchTimer(_global, set, clear, 'Timeout');
2424
patchTimer(_global, set, clear, 'Interval');

lib/common/timers.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam
2323

2424
function scheduleTask(task: Task) {
2525
const data = <TimerOptions>task.data;
26-
data.args[0] = function() {
26+
function timer() {
2727
try {
2828
task.invoke.apply(this, arguments);
2929
} finally {
3030
delete tasksByHandleId[data.handleId];
3131
}
3232
};
33+
data.args[0] = timer;
3334
data.handleId = setNative.apply(window, data.args);
3435
tasksByHandleId[data.handleId] = task;
3536
return task;

lib/common/utils.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
declare const WorkerGlobalScope: any;
1616

1717
export const zoneSymbol: (name: string) => string = (n) => `__zone_symbol__${n}`;
18-
const _global: any = typeof window === 'object' && window || typeof self === 'object' && self || global;
18+
const _global: any =
19+
typeof window === 'object' && window || typeof self === 'object' && self || global;
1920

2021
export function bindArguments(args: any[], source: string): any[] {
2122
for (let i = args.length - 1; i >= 0; i--) {

lib/zone-spec/long-stack-trace.ts

+24-39
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@
1111
*/
1212

1313
const NEWLINE = '\n';
14-
const SEP = ' ------------- ';
15-
const IGNORE_FRAMES: string[] = [];
14+
const IGNORE_FRAMES: {[k: string]: true} = {};
1615
const creationTrace = '__creationTrace__';
16+
const ERROR_TAG = 'STACKTRACE TRACKING';
17+
const SEP_TAG = '__SEP_TAG__';
18+
let sepTemplate = '';
1719

1820
class LongStackTrace {
1921
error: Error = getStacktrace();
2022
timestamp: Date = new Date();
2123
}
2224

2325
function getStacktraceWithUncaughtError(): Error {
24-
return new Error('STACKTRACE TRACKING');
26+
return new Error(ERROR_TAG);
2527
}
2628

2729
function getStacktraceWithCaughtError(): Error {
@@ -49,22 +51,24 @@ function addErrorStack(lines: string[], error: Error): void {
4951
for (let i = 0; i < trace.length; i++) {
5052
const frame = trace[i];
5153
// Filter out the Frames which are part of stack capturing.
52-
if (!(i < IGNORE_FRAMES.length && IGNORE_FRAMES[i] === frame)) {
54+
if (!IGNORE_FRAMES.hasOwnProperty(frame)) {
5355
lines.push(trace[i]);
5456
}
5557
}
5658
}
5759

5860
function renderLongStackTrace(frames: LongStackTrace[], stack: string): string {
59-
const longTrace: string[] = [stack];
61+
const longTrace: string[] = [stack.trim()];
6062

6163
if (frames) {
6264
let timestamp = new Date().getTime();
6365
for (let i = 0; i < frames.length; i++) {
6466
const traceFrames: LongStackTrace = frames[i];
6567
const lastTime = traceFrames.timestamp;
66-
longTrace.push(
67-
`${SEP} Elapsed: ${timestamp - lastTime.getTime()} ms; At: ${lastTime} ${SEP}`);
68+
let separator =
69+
`____________________Elapsed ${timestamp - lastTime.getTime()} ms; At: ${lastTime}`;
70+
separator = separator.replace(/[^\w\d]/g, '_');
71+
longTrace.push(sepTemplate.replace(SEP_TAG, separator));
6872
addErrorStack(longTrace, traceFrames.error);
6973

7074
timestamp = lastTime.getTime();
@@ -105,42 +109,15 @@ function renderLongStackTrace(frames: LongStackTrace[], stack: string): string {
105109
},
106110

107111
onHandleError: function(
108-
parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): any {
112+
parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): boolean {
109113
const parentTask = Zone.currentTask || error.task;
110114
if (error instanceof Error && parentTask) {
111-
let stackSetSucceeded: string|boolean = null;
115+
const longStack =
116+
renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack);
112117
try {
113-
let descriptor = Object.getOwnPropertyDescriptor(error, 'stack');
114-
if (descriptor && descriptor.configurable) {
115-
const delegateGet = descriptor.get;
116-
const value = descriptor.value;
117-
descriptor = {
118-
get: function() {
119-
return renderLongStackTrace(
120-
parentTask.data && parentTask.data[creationTrace],
121-
delegateGet ? delegateGet.apply(this) : value);
122-
}
123-
};
124-
Object.defineProperty(error, 'stack', descriptor);
125-
stackSetSucceeded = true;
126-
}
118+
error.stack = (error as any).longStack = longStack;
127119
} catch (err) {
128120
}
129-
const longStack: string = stackSetSucceeded ?
130-
null :
131-
renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack);
132-
if (!stackSetSucceeded) {
133-
try {
134-
stackSetSucceeded = error.stack = longStack;
135-
} catch (err) {
136-
}
137-
}
138-
if (!stackSetSucceeded) {
139-
try {
140-
stackSetSucceeded = (error as any).longStack = longStack;
141-
} catch (err) {
142-
}
143-
}
144121
}
145122
return parentZoneDelegate.handleError(targetZone, error);
146123
}
@@ -161,11 +138,19 @@ function computeIgnoreFrames() {
161138
for (let i = 0; i < frames1.length; i++) {
162139
const frame1 = frames1[i];
163140
const frame2 = frames2[i];
141+
if (!sepTemplate && frame1.indexOf(ERROR_TAG) == -1) {
142+
sepTemplate = frame1.replace(/^(\s*(at)?\s*)([\w\/\<]+)/, '$1' + SEP_TAG);
143+
}
164144
if (frame1 === frame2) {
165-
IGNORE_FRAMES.push(frame1);
145+
IGNORE_FRAMES[frame1] = true;
166146
} else {
167147
break;
168148
}
149+
console.log('>>>>>>', sepTemplate, frame1);
150+
}
151+
if (!sepTemplate) {
152+
// If we could not find it default to this text.
153+
sepTemplate = SEP_TAG + '@[native code]';
169154
}
170155
}
171156
computeIgnoreFrames();

0 commit comments

Comments
 (0)