Skip to content

Commit baa0db4

Browse files
committed
Make long stack traces work in all browsers.
Tested in IE10 and Firefox 20, in addition to the usual Chrome. Tested to not break IE9 and below.
1 parent 89a549b commit baa0db4

File tree

1 file changed

+63
-34
lines changed

1 file changed

+63
-34
lines changed

q.js

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@
6363
})(function () {
6464
"use strict";
6565

66+
var hasStacks = false;
67+
try {
68+
throw new Error();
69+
} catch (e) {
70+
hasStacks = !!e.stack;
71+
}
72+
6673
// All code after this point will be filtered from stack traces reported
6774
// by Q.
6875
var qStartingLine = captureLine();
@@ -282,10 +289,10 @@ Q.longStackJumpLimit = 1;
282289
var STACK_JUMP_SEPARATOR = "From previous event:";
283290

284291
function makeStackTraceLong(error, promise) {
285-
// If possible (that is, if in V8), transform the error stack
286-
// trace by removing Node and Q cruft, then concatenating with
287-
// the stack trace of the promise we are ``done``ing. See #57.
288-
if (promise.stack &&
292+
// If possible, transform the error stack trace by removing Node and Q
293+
// cruft, then concatenating with the stack trace of `promise`. See #57.
294+
if (hasStacks &&
295+
promise.stack &&
289296
typeof error === "object" &&
290297
error !== null &&
291298
error.stack &&
@@ -303,7 +310,7 @@ function filterStackString(stackString) {
303310
for (var i = 0; i < lines.length; ++i) {
304311
var line = lines[i];
305312

306-
if (!isInternalFrame(line) && !isNodeFrame(line)) {
313+
if (!isInternalFrame(line) && !isNodeFrame(line) && line) {
307314
desiredLines.push(line);
308315
}
309316
}
@@ -315,15 +322,36 @@ function isNodeFrame(stackLine) {
315322
stackLine.indexOf("(node.js:") !== -1;
316323
}
317324

325+
function getFileNameAndLineNumber(stackLine) {
326+
// Named functions: "at functionName (filename:lineNumber:columnNumber)"
327+
// In IE10 function name can have spaces ("Anonymous function") O_o
328+
var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine);
329+
if (attempt1) {
330+
return [attempt1[1], Number(attempt1[2])];
331+
}
332+
333+
// Anonymous functions: "at filename:lineNumber:columnNumber"
334+
var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine);
335+
if (attempt2) {
336+
return [attempt2[1], Number(attempt2[2])];
337+
}
338+
339+
// Firefox style: "function@filename:lineNumber or @filename:lineNumber"
340+
var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine);
341+
if (attempt3) {
342+
return [attempt3[1], Number(attempt3[2])];
343+
}
344+
}
345+
318346
function isInternalFrame(stackLine) {
319-
var pieces = /at .+ \((.*):(\d+):\d+\)/.exec(stackLine);
347+
var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);
320348

321-
if (!pieces) {
349+
if (!fileNameAndLineNumber) {
322350
return false;
323351
}
324352

325-
var fileName = pieces[1];
326-
var lineNumber = pieces[2];
353+
var fileName = fileNameAndLineNumber[0];
354+
var lineNumber = fileNameAndLineNumber[1];
327355

328356
return fileName === qFileName &&
329357
lineNumber >= qStartingLine &&
@@ -333,24 +361,22 @@ function isInternalFrame(stackLine) {
333361
// discover own file name and line number range for filtering stack
334362
// traces
335363
function captureLine() {
336-
if (Error.captureStackTrace) {
337-
var fileName, lineNumber;
338-
339-
var oldPrepareStackTrace = Error.prepareStackTrace;
340-
341-
Error.prepareStackTrace = function (error, frames) {
342-
fileName = frames[1].getFileName();
343-
lineNumber = frames[1].getLineNumber();
344-
};
364+
if (!hasStacks) {
365+
return;
366+
}
345367

346-
// teases call of temporary prepareStackTrace
347-
// JSHint and Closure Compiler generate known warnings here
348-
/*jshint expr: true */
349-
new Error().stack;
368+
try {
369+
throw new Error();
370+
} catch (e) {
371+
var lines = e.stack.split("\n");
372+
var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2];
373+
var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);
374+
if (!fileNameAndLineNumber) {
375+
return;
376+
}
350377

351-
Error.prepareStackTrace = oldPrepareStackTrace;
352-
qFileName = fileName;
353-
return lineNumber;
378+
qFileName = fileNameAndLineNumber[0];
379+
return fileNameAndLineNumber[1];
354380
}
355381
}
356382

@@ -419,15 +445,18 @@ function defer() {
419445
return nearer;
420446
};
421447

422-
if (Error.captureStackTrace && Q.longStackJumpLimit > 0) {
423-
Error.captureStackTrace(promise, defer);
424-
425-
// Reify the stack into a string by using the accessor; this prevents
426-
// memory leaks as per GH-111. At the same time, cut off the first line;
427-
// it's always just "[object Promise]\n", as per the `toString`.
428-
promise.stack = promise.stack.substring(
429-
promise.stack.indexOf("\n") + 1
430-
);
448+
if (Q.longStackJumpLimit > 0 && hasStacks) {
449+
try {
450+
throw new Error();
451+
} catch (e) {
452+
// NOTE: don't try to use `Error.captureStackTrace` or transfer the
453+
// accessor around; that causes memory leaks as per GH-111. Just
454+
// reify the stack trace as a string ASAP.
455+
//
456+
// At the same time, cut off the first line; it's always just
457+
// "[object Promise]\n", as per the `toString`.
458+
promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
459+
}
431460
}
432461

433462
// NOTE: we do the checks for `resolvedPromise` in each method, instead of

0 commit comments

Comments
 (0)