diff --git a/packages/browser/src/tracekit.ts b/packages/browser/src/tracekit.ts index cf3445c09468..4750cbc5cad2 100644 --- a/packages/browser/src/tracekit.ts +++ b/packages/browser/src/tracekit.ts @@ -38,6 +38,7 @@ interface ComputeStackTrace { * @param {(string|number)=} depth */ (ex: Error, depth?: string | number): StackTrace; + _computeStackTraceFromStackProp(ex: any): StackTrace; } /** @@ -469,10 +470,11 @@ TraceKit._computeStackTrace = (function _computeStackTraceWrapper() { * @memberof TraceKit._computeStackTrace */ function _computeStackTraceFromStackProp(ex: any) { - if (!ex.stack) { + if (!ex || !ex.stack) { return null; } + // Chromium based browsers: Chrome, Brave, new Opera, new Edge var chrome = /^\s*at (?:(.*?) ?\()?((?:file|https?|blob|chrome-extension|native|eval|webpack||[a-z]:|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, // gecko regex: `(?:bundle|\d+\.js)`: `bundle` is for react native, `\d+\.js` also but specifically for ram bundles because it // generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js @@ -492,19 +494,17 @@ TraceKit._computeStackTrace = (function _computeStackTraceWrapper() { for (var i = 0, j = lines.length; i < j; ++i) { if ((parts = chrome.exec(lines[i]))) { - var isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line if (isEval && (submatch = chromeEval.exec(parts[2]))) { // throw out eval line/column and use top-most line/column number parts[2] = submatch[1]; // url - // NOTE: It's messing out our integration tests in Karma, let's see if we can live with it – Kamil - // parts[3] = submatch[2]; // line - // parts[4] = submatch[3]; // column + parts[3] = submatch[2]; // line + parts[4] = submatch[3]; // column } element = { - url: !isNative ? parts[2] : null, + url: parts[2], func: parts[1] || UNKNOWN_FUNCTION, - args: isNative ? [parts[2]] : [], + args: [], line: parts[3] ? +parts[3] : null, column: parts[4] ? +parts[4] : null, }; @@ -521,9 +521,8 @@ TraceKit._computeStackTrace = (function _computeStackTraceWrapper() { if (isEval && (submatch = geckoEval.exec(parts[3]))) { // throw out eval line/column and use top-most line number parts[3] = submatch[1]; - // NOTE: It's messing out our integration tests in Karma, let's see if we can live with it – Kamil - // parts[4] = submatch[2]; - // parts[5] = null; // no column when eval + parts[4] = submatch[2]; + parts[5] = ''; // no column when eval } else if (i === 0 && !parts[5] && ex.columnNumber !== void 0) { // FireFox uses this awesome columnNumber property for its top frame // Also note, Firefox's column number is 0-based and everything else expects 1-based, diff --git a/packages/browser/test/tracekit.test.ts b/packages/browser/test/tracekit.test.ts new file mode 100644 index 000000000000..970e8f8464eb --- /dev/null +++ b/packages/browser/test/tracekit.test.ts @@ -0,0 +1,194 @@ +import { expect } from 'chai'; + +import { _computeStackTrace } from '../src/tracekit'; + +const CHROME73_NATIVE_CODE_EXCEPTION = { + stack: `Error: test + at fooIterator (http://192.168.20.143:5000/test:20:17) + at Array.map () + at foo (http://192.168.20.143:5000/test:19:19) + at http://192.168.20.143:5000/test:24:7`, +}; + +const FIREFOX66_NATIVE_CODE_EXCEPTION = { + stack: `fooIterator@http://192.168.20.143:5000/test:20:17 + foo@http://192.168.20.143:5000/test:19:19 + @http://192.168.20.143:5000/test:24:7`, +}; + +const SAFARI12_NATIVE_CODE_EXCEPTION = { + stack: `fooIterator@http://192.168.20.143:5000/test:20:26 + map@[native code] + foo@http://192.168.20.143:5000/test:19:22 + global code@http://192.168.20.143:5000/test:24:10`, +}; + +const EDGE44_NATIVE_CODE_EXCEPTION = { + stack: `Error: test + at fooIterator (http://192.168.20.143:5000/test:20:11) + at Array.prototype.map (native code) + at foo (http://192.168.20.143:5000/test:19:9) + at Global code (http://192.168.20.143:5000/test:24:7)`, +}; + +describe('Tracekit', () => { + describe('computeStackTrace', () => { + it('no exception', () => { + const stacktrace = _computeStackTrace._computeStackTraceFromStackProp(undefined); + expect(stacktrace).equal(null); + }); + + it('no stack', () => { + const stacktrace = _computeStackTrace._computeStackTraceFromStackProp({}); + expect(stacktrace).equal(null); + }); + + it('chrome73', () => { + const stacktrace = _computeStackTrace._computeStackTraceFromStackProp(CHROME73_NATIVE_CODE_EXCEPTION); + + expect(stacktrace.stack).deep.equal([ + { + args: [], + column: 17, + context: null, + func: 'fooIterator', + line: 20, + url: 'http://192.168.20.143:5000/test', + }, + { + args: [], + column: null, + context: null, + func: 'Array.map', + line: null, + url: '', + }, + { + args: [], + column: 19, + context: null, + func: 'foo', + line: 19, + url: 'http://192.168.20.143:5000/test', + }, + { + args: [], + column: 7, + context: null, + func: '?', + line: 24, + url: 'http://192.168.20.143:5000/test', + }, + ]); + }); + + it('firefox66', () => { + const stacktrace = _computeStackTrace._computeStackTraceFromStackProp(FIREFOX66_NATIVE_CODE_EXCEPTION); + + expect(stacktrace.stack).deep.equal([ + { + args: [], + column: 17, + context: null, + func: 'fooIterator', + line: 20, + url: 'http://192.168.20.143:5000/test', + }, + { + args: [], + column: 19, + context: null, + func: 'foo', + line: 19, + url: 'http://192.168.20.143:5000/test', + }, + { + args: [], + column: 7, + context: null, + func: '?', + line: 24, + url: 'http://192.168.20.143:5000/test', + }, + ]); + }); + + it('safari12', () => { + const stacktrace = _computeStackTrace._computeStackTraceFromStackProp(SAFARI12_NATIVE_CODE_EXCEPTION); + + expect(stacktrace.stack).deep.equal([ + { + args: [], + column: 26, + context: null, + func: 'fooIterator', + line: 20, + url: 'http://192.168.20.143:5000/test', + }, + { + args: [], + column: null, + context: null, + func: 'map', + line: null, + url: '[native code]', + }, + { + args: [], + column: 22, + context: null, + func: 'foo', + line: 19, + url: 'http://192.168.20.143:5000/test', + }, + { + args: [], + column: 10, + context: null, + func: 'global code', + line: 24, + url: 'http://192.168.20.143:5000/test', + }, + ]); + }); + + it('edge44', () => { + const stacktrace = _computeStackTrace._computeStackTraceFromStackProp(EDGE44_NATIVE_CODE_EXCEPTION); + + expect(stacktrace.stack).deep.equal([ + { + args: [], + column: 11, + context: null, + func: 'fooIterator', + line: 20, + url: 'http://192.168.20.143:5000/test', + }, + { + args: [], + column: null, + context: null, + func: 'Array.prototype.map', + line: null, + url: 'native code', + }, + { + args: [], + column: 9, + context: null, + func: 'foo', + line: 19, + url: 'http://192.168.20.143:5000/test', + }, + { + args: [], + column: 7, + context: null, + func: 'Global code', + line: 24, + url: 'http://192.168.20.143:5000/test', + }, + ]); + }); + }); +});