Skip to content

Handle JavaScript loaded in a blob #1322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions test/vendor/tracekit-parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@

var TraceKit = require('../../vendor/TraceKit/tracekit');
var CapturedExceptions = require('./fixtures/captured-errors');
var sinon = require('sinon');
var xhr;

describe('TraceKit', function() {
beforeEach(function() {
xhr = sinon.useFakeXMLHttpRequest();
});

afterEach(function() {
xhr.restore();
});

describe('Parser', function() {
it('should parse Safari 6 error', function() {
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.SAFARI_6);
Expand Down
47 changes: 47 additions & 0 deletions test/vendor/tracekit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
var TraceKit = require('../../vendor/TraceKit/tracekit');
var utils = require('../../src/utils');
var supportsErrorEvent = utils.supportsErrorEvent;
var sinon = require('sinon');

describe('TraceKit', function() {
describe('stacktrace info', function() {
Expand Down Expand Up @@ -81,6 +82,52 @@ describe('TraceKit', function() {
assert.equal(trace.stack[2].line, 26);
assert.equal(trace.stack[2].column, 5);
});

it('should update url based on sourcemap suffix in blob: based frames if full url available', function() {
var server = sinon.createFakeServer();
server.respondImmediately = true;
server.respondWith('GET', 'blob:http://localhost:8080/some-blob', [
200,
{'Content-Type': 'application/javascript'},
'just a random stream of bytes, as we care only about the sourcemaps suffix there\n' +
'oh, here it comes! //# sourceMappingURL=http://awesome.com/file.js.map'
]);

var stack_str =
'Error: test\n' +
' at Error (native)\n' +
' at s (blob:http://localhost:8080/some-blob:31:29146)';

var mock_err = {stack: stack_str};
var trace = TraceKit.computeStackTrace.computeStackTraceFromStackProp(mock_err);

assert.equal(trace.stack[1].url, 'http://awesome.com/file.js');

server.restore();
});

it('should update url based on sourcemap suffix in blob: based frames if relative url available, by adding location.origin to it', function() {
var server = sinon.createFakeServer();
server.respondImmediately = true;
server.respondWith('GET', 'blob:http://localhost:8080/some-blob', [
200,
{'Content-Type': 'application/javascript'},
'just a random stream of bytes, as we care only about the sourcemaps suffix there\n' +
'oh, here it comes! //# sourceMappingURL=~/awesome.com/file.js.map'
]);

var stack_str =
'Error: test\n' +
' at Error (native)\n' +
' at s (blob:http://localhost:8080/some-blob:31:29146)';

var mock_err = {stack: stack_str};
var trace = TraceKit.computeStackTrace.computeStackTraceFromStackProp(mock_err);

assert.equal(trace.stack[1].url, 'http://localhost:9876/awesome.com/file.js');

server.restore();
});
});

describe('.computeStackTrace', function() {
Expand Down
44 changes: 43 additions & 1 deletion vendor/TraceKit/tracekit.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Ran

function getLocationHref() {
if (typeof document === 'undefined' || document.location == null) return '';

return document.location.href;
}

function getLocationOrigin() {
if (typeof document === 'undefined' || document.location == null) return '';
return document.location.origin;
}

/**
* TraceKit.report: cross-browser processing of unhandled exceptions
*
Expand Down Expand Up @@ -441,6 +445,44 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
element.func = UNKNOWN_FUNCTION;
}

if (element.url && element.url.substr(0, 5) === 'blob:') {
// Special case for handling JavaScript loaded into a blob.
// We use a synchronous AJAX request here as a blob is already in
// memory - it's not making a network request. This will generate a warning
// in the browser console, but there has already been an error so that's not
// that much of an issue.
var xhr = new XMLHttpRequest();
xhr.open('GET', element.url, false);
xhr.send(null);

// If we failed to download the source, skip this patch
if (xhr.status === 200) {
var source = xhr.responseText || '';

// We trim the source down to the last 300 characters as sourceMappingURL is always at the end of the file.
// Why 300? To be in line with: https://github.com/getsentry/sentry/blob/4af29e8f2350e20c28a6933354e4f42437b4ba42/src/sentry/lang/javascript/processor.py#L164-L175
source = source.slice(-300);

// Now we dig out the source map URL
var sourceMaps = source.match(/\/\/# sourceMappingURL=(.*)$/);

// If we don't find a source map comment or we find more than one, continue on to the next element.
if (sourceMaps) {
var sourceMapAddress = sourceMaps[1];

// Now we check to see if it's a relative URL.
// If it is, convert it to an absolute one.
if (sourceMapAddress.charAt(0) === '~') {
sourceMapAddress = getLocationOrigin() + sourceMapAddress.slice(1);
}

// Now we strip the '.map' off of the end of the URL and update the
// element so that Sentry can match the map to the blob.
element.url = sourceMapAddress.slice(0, -4);
}
}
}

stack.push(element);
}

Expand Down