Skip to content

Commit

Permalink
Raven.uninstall also restores builtin methods
Browse files Browse the repository at this point in the history
  • Loading branch information
benvinegar committed Jan 5, 2016
1 parent 03e8fc1 commit 944323c
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 23 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"grunt-release": "~0.6.0",
"grunt-s3": "~0.2.0-alpha.3",
"grunt-sri": "mattrobenolt/grunt-sri#pretty",
"jquery": "^2.1.4",
"lodash": "~2.4.0",
"proxyquireify": "^3.0.0",
"sinon": "~1.7.3",
Expand Down
57 changes: 40 additions & 17 deletions src/raven.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function Raven() {
this._originalConsoleMethods = {};
this._plugins = [];
this._startTime = now();
this._wrappedBuiltIns = [];

for (var method in this._originalConsole) {
this._originalConsoleMethods[method] = this._originalConsole[method];
Expand Down Expand Up @@ -267,6 +268,9 @@ Raven.prototype = {
*/
uninstall: function() {
TraceKit.report.uninstall();

this._restoreBuiltIns();

this._isRavenInstalled = false;

return this;
Expand Down Expand Up @@ -545,9 +549,12 @@ Raven.prototype = {
_wrapBuiltIns: function() {
var self = this;

function fill(obj, name, replacement) {
function fill(obj, name, replacement, noUndo) {
var orig = obj[name];
obj[name] = replacement(orig);
if (!noUndo) {
self._wrappedBuiltIns.push([obj, name, orig]);
}
}

function wrapTimeFn(orig) {
Expand Down Expand Up @@ -607,26 +614,42 @@ Raven.prototype = {
var origOpen;
if ('XMLHttpRequest' in window) {
origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (data) { // preserve arity
var xhr = this;
'onreadystatechange onload onerror onprogress'.replace(/\w+/g, function (prop) {
if (prop in xhr && Object.prototype.toString.call(xhr[prop]) === '[object Function]') {
fill(xhr, prop, function (orig) {
return self.wrap(orig);
});
}
});
origOpen.apply(this, arguments);
};
fill(XMLHttpRequest.prototype, 'open', function(origOpen) {
return function (data) { // preserve arity
var xhr = this;
'onreadystatechange onload onerror onprogress'.replace(/\w+/g, function (prop) {
if (prop in xhr && Object.prototype.toString.call(xhr[prop]) === '[object Function]') {
fill(xhr, prop, function (orig) {
return self.wrap(orig);
}, true /* noUndo */); // don't track filled methods on XHR instances
}
});
origOpen.apply(this, arguments);
};
});
}

var $ = window.jQuery || window.$;
var origReady;
if ($ && $.fn && $.fn.ready) {
origReady = $.fn.ready;
$.fn.ready = function ravenjQueryReadyWrapper(fn) {
return origReady.call(this, self.wrap(fn));
};
fill($.fn, 'ready', function (orig) {
return function (fn) {
orig.call(this, self.wrap(fn));
};
});
}
},

_restoreBuiltIns: function () {
// restore any wrapped builtins
var builtin;
while (this._wrappedBuiltIns.length) {
builtin = this._wrappedBuiltIns.shift();

var obj = builtin[0],
name = builtin[1],
orig = builtin[2];

obj[name] = orig;
}
},

Expand Down
19 changes: 16 additions & 3 deletions test/integration/frame.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,25 @@
};
}());
</script>
<script src="../../node_modules/jquery/dist/jquery.js"></script>
<script src="../../build/raven.js"></script>
<script>
// stub _makeRequest so we don't actually transmit any data
Raven._makeRequest = function () {};

// get a reference to the original, unwrapped setTimeout (used for
// making "clean" stack traces that don't originate from mocha)
window.origSetTimeout = setTimeout;
// store references to original, unwrapped built-ins in order to:
// - get a clean, unwrapped setTimeout (so stack traces don't include
// frames from mocha)
// - make assertions re: wrapped functions

window.originalBuiltIns = {
setTimeout: setTimeout,
setInterval: setInterval,
requestAnimationFrame: requestAnimationFrame,
xhrProtoOpen: XMLHttpRequest.prototype.open,
headAddEventListener: document.head.addEventListener, // use <head> 'cause body isn't closed yet
headRemoveEventListener: document.head.removeEventListener
};

window.ravenData = [];
Raven.config('https://[email protected]/1', {
Expand All @@ -62,3 +73,5 @@
</script>
</head>
<body>
</body>
</html>
78 changes: 75 additions & 3 deletions test/integration/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ function iframeExecute(iframe, done, execute, assertCallback) {
} catch (e) {
done(e);
}
}
};
// use setTimeout so stack trace doesn't go all the way back to mocha test runner
iframe.contentWindow.eval('origSetTimeout(' + execute.toString() + ');');
iframe.contentWindow.eval('window.originalBuiltIns.setTimeout.call(window, ' + execute.toString() + ');');
}

function createIframe(done) {
Expand Down Expand Up @@ -248,7 +248,7 @@ describe('integration', function () {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
foo();
}
};
xhr.open('GET', 'example.json');
xhr.send();
},
Expand All @@ -259,5 +259,77 @@ describe('integration', function () {
}
);
});

it('should capture exceptions from $.fn.ready (jQuery)', function (done) {
var iframe = this.iframe;

iframeExecute(iframe, done,
function () {
setTimeout(done);

$(function () {
foo();
});
},
function () {
var ravenData = iframe.contentWindow.ravenData[0];
// # of frames alter significantly between chrome/firefox & safari
assert.isAbove(ravenData.exception.values[0].stacktrace.frames.length, 2);
}
);
});
});

describe('uninstall', function () {
it('should restore original built-ins', function (done) {
var iframe = this.iframe;

iframeExecute(iframe, done,
function () {
setTimeout(done);
Raven.uninstall();

window.isRestored = {
setTimeout: originalBuiltIns.setTimeout === setTimeout,
setInterval: originalBuiltIns.setInterval === setInterval,
requestAnimationFrame: originalBuiltIns.requestAnimationFrame === requestAnimationFrame,
xhrProtoOpen: originalBuiltIns.xhrProtoOpen === XMLHttpRequest.prototype.open,
headAddEventListener: originalBuiltIns.headAddEventListener === document.body.addEventListener,
headRemoveEventListener: originalBuiltIns.headRemoveEventListener === document.body.removeEventListener
};
},
function () {
var isRestored = iframe.contentWindow.isRestored;
assert.isTrue(isRestored.setTimeout);
assert.isTrue(isRestored.setInterval);
assert.isTrue(isRestored.requestAnimationFrame);
assert.isTrue(isRestored.xhrProtoOpen);
assert.isTrue(isRestored.headAddEventListener);
assert.isTrue(isRestored.headRemoveEventListener);
}
);
});

it('should not restore XMLHttpRequest instance methods', function (done) {
var iframe = this.iframe;

iframeExecute(iframe, done,
function () {
setTimeout(done);

var xhr = new XMLHttpRequest();
var origOnReadyStateChange = xhr.onreadystatechange = function () {};
xhr.open('GET', '/foo/');
xhr.abort();

Raven.uninstall();

window.isOnReadyStateChangeRestored = xhr.onready === origOnReadyStateChange;
},
function () {
assert.isFalse(iframe.contentWindow.isOnReadyStateChangeRestored);
}
);
});
});
});

0 comments on commit 944323c

Please sign in to comment.