Skip to content

Commit 1068a0c

Browse files
committed
Merge pull request #449 from getsentry/uninstall-builtins
Raven.uninstall also restores builtin methods
2 parents aef0df8 + 276782f commit 1068a0c

File tree

4 files changed

+132
-23
lines changed

4 files changed

+132
-23
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"grunt-release": "^0.13.0",
3131
"grunt-s3": "0.2.0-alpha.3",
3232
"grunt-sri": "mattrobenolt/grunt-sri#pretty",
33+
"jquery": "^2.1.4",
3334
"lodash": "^3.10.1",
3435
"mocha": "^1.21.5",
3536
"proxyquireify": "^3.0.1",

src/raven.js

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function Raven() {
5555
this._originalConsoleMethods = {};
5656
this._plugins = [];
5757
this._startTime = now();
58+
this._wrappedBuiltIns = [];
5859

5960
for (var method in this._originalConsole) { // eslint-disable-line guard-for-in
6061
this._originalConsoleMethods[method] = this._originalConsole[method];
@@ -267,6 +268,9 @@ Raven.prototype = {
267268
*/
268269
uninstall: function() {
269270
TraceKit.report.uninstall();
271+
272+
this._restoreBuiltIns();
273+
270274
this._isRavenInstalled = false;
271275

272276
return this;
@@ -547,9 +551,12 @@ Raven.prototype = {
547551
_wrapBuiltIns: function() {
548552
var self = this;
549553

550-
function fill(obj, name, replacement) {
554+
function fill(obj, name, replacement, noUndo) {
551555
var orig = obj[name];
552556
obj[name] = replacement(orig);
557+
if (!noUndo) {
558+
self._wrappedBuiltIns.push([obj, name, orig]);
559+
}
553560
}
554561

555562
function wrapTimeFn(orig) {
@@ -611,26 +618,42 @@ Raven.prototype = {
611618
var origOpen;
612619
if ('XMLHttpRequest' in window) {
613620
origOpen = XMLHttpRequest.prototype.open;
614-
XMLHttpRequest.prototype.open = function (data) { // preserve arity
615-
var xhr = this;
616-
'onreadystatechange onload onerror onprogress'.replace(/\w+/g, function (prop) {
617-
if (prop in xhr && Object.prototype.toString.call(xhr[prop]) === '[object Function]') {
618-
fill(xhr, prop, function (orig) {
619-
return self.wrap(orig);
620-
});
621-
}
622-
});
623-
origOpen.apply(this, arguments);
624-
};
621+
fill(XMLHttpRequest.prototype, 'open', function(origOpen) {
622+
return function (data) { // preserve arity
623+
var xhr = this;
624+
'onreadystatechange onload onerror onprogress'.replace(/\w+/g, function (prop) {
625+
if (prop in xhr && Object.prototype.toString.call(xhr[prop]) === '[object Function]') {
626+
fill(xhr, prop, function (orig) {
627+
return self.wrap(orig);
628+
}, true /* noUndo */); // don't track filled methods on XHR instances
629+
}
630+
});
631+
origOpen.apply(this, arguments);
632+
};
633+
});
625634
}
626635

627636
var $ = window.jQuery || window.$;
628-
var origReady;
629637
if ($ && $.fn && $.fn.ready) {
630-
origReady = $.fn.ready;
631-
$.fn.ready = function ravenjQueryReadyWrapper(fn) {
632-
return origReady.call(this, self.wrap(fn));
633-
};
638+
fill($.fn, 'ready', function (orig) {
639+
return function (fn) {
640+
orig.call(this, self.wrap(fn));
641+
};
642+
});
643+
}
644+
},
645+
646+
_restoreBuiltIns: function () {
647+
// restore any wrapped builtins
648+
var builtin;
649+
while (this._wrappedBuiltIns.length) {
650+
builtin = this._wrappedBuiltIns.shift();
651+
652+
var obj = builtin[0],
653+
name = builtin[1],
654+
orig = builtin[2];
655+
656+
obj[name] = orig;
634657
}
635658
},
636659

test/integration/frame.html

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,25 @@
3636
};
3737
}());
3838
</script>
39+
<script src="../../node_modules/jquery/dist/jquery.js"></script>
3940
<script src="../../build/raven.js"></script>
4041
<script>
4142
// stub _makeRequest so we don't actually transmit any data
4243
Raven._makeRequest = function () {};
4344

44-
// get a reference to the original, unwrapped setTimeout (used for
45-
// making "clean" stack traces that don't originate from mocha)
46-
window.origSetTimeout = setTimeout;
45+
// store references to original, unwrapped built-ins in order to:
46+
// - get a clean, unwrapped setTimeout (so stack traces don't include
47+
// frames from mocha)
48+
// - make assertions re: wrapped functions
49+
50+
window.originalBuiltIns = {
51+
setTimeout: setTimeout,
52+
setInterval: setInterval,
53+
requestAnimationFrame: requestAnimationFrame,
54+
xhrProtoOpen: XMLHttpRequest.prototype.open,
55+
headAddEventListener: document.head.addEventListener, // use <head> 'cause body isn't closed yet
56+
headRemoveEventListener: document.head.removeEventListener
57+
};
4758

4859
window.ravenData = [];
4960
Raven.config('https://[email protected]/1', {
@@ -62,3 +73,5 @@
6273
</script>
6374
</head>
6475
<body>
76+
</body>
77+
</html>

test/integration/test.js

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ function iframeExecute(iframe, done, execute, assertCallback) {
77
} catch (e) {
88
done(e);
99
}
10-
}
10+
};
1111
// use setTimeout so stack trace doesn't go all the way back to mocha test runner
12-
iframe.contentWindow.eval('origSetTimeout(' + execute.toString() + ');');
12+
iframe.contentWindow.eval('window.originalBuiltIns.setTimeout.call(window, ' + execute.toString() + ');');
1313
}
1414

1515
function createIframe(done) {
@@ -248,7 +248,7 @@ describe('integration', function () {
248248
var xhr = new XMLHttpRequest();
249249
xhr.onreadystatechange = function () {
250250
foo();
251-
}
251+
};
252252
xhr.open('GET', 'example.json');
253253
xhr.send();
254254
},
@@ -259,5 +259,77 @@ describe('integration', function () {
259259
}
260260
);
261261
});
262+
263+
it('should capture exceptions from $.fn.ready (jQuery)', function (done) {
264+
var iframe = this.iframe;
265+
266+
iframeExecute(iframe, done,
267+
function () {
268+
setTimeout(done);
269+
270+
$(function () {
271+
foo();
272+
});
273+
},
274+
function () {
275+
var ravenData = iframe.contentWindow.ravenData[0];
276+
// # of frames alter significantly between chrome/firefox & safari
277+
assert.isAbove(ravenData.exception.values[0].stacktrace.frames.length, 2);
278+
}
279+
);
280+
});
281+
});
282+
283+
describe('uninstall', function () {
284+
it('should restore original built-ins', function (done) {
285+
var iframe = this.iframe;
286+
287+
iframeExecute(iframe, done,
288+
function () {
289+
setTimeout(done);
290+
Raven.uninstall();
291+
292+
window.isRestored = {
293+
setTimeout: originalBuiltIns.setTimeout === setTimeout,
294+
setInterval: originalBuiltIns.setInterval === setInterval,
295+
requestAnimationFrame: originalBuiltIns.requestAnimationFrame === requestAnimationFrame,
296+
xhrProtoOpen: originalBuiltIns.xhrProtoOpen === XMLHttpRequest.prototype.open,
297+
headAddEventListener: originalBuiltIns.headAddEventListener === document.body.addEventListener,
298+
headRemoveEventListener: originalBuiltIns.headRemoveEventListener === document.body.removeEventListener
299+
};
300+
},
301+
function () {
302+
var isRestored = iframe.contentWindow.isRestored;
303+
assert.isTrue(isRestored.setTimeout);
304+
assert.isTrue(isRestored.setInterval);
305+
assert.isTrue(isRestored.requestAnimationFrame);
306+
assert.isTrue(isRestored.xhrProtoOpen);
307+
assert.isTrue(isRestored.headAddEventListener);
308+
assert.isTrue(isRestored.headRemoveEventListener);
309+
}
310+
);
311+
});
312+
313+
it('should not restore XMLHttpRequest instance methods', function (done) {
314+
var iframe = this.iframe;
315+
316+
iframeExecute(iframe, done,
317+
function () {
318+
setTimeout(done);
319+
320+
var xhr = new XMLHttpRequest();
321+
var origOnReadyStateChange = xhr.onreadystatechange = function () {};
322+
xhr.open('GET', '/foo/');
323+
xhr.abort();
324+
325+
Raven.uninstall();
326+
327+
window.isOnReadyStateChangeRestored = xhr.onready === origOnReadyStateChange;
328+
},
329+
function () {
330+
assert.isFalse(iframe.contentWindow.isOnReadyStateChangeRestored);
331+
}
332+
);
333+
});
262334
});
263335
});

0 commit comments

Comments
 (0)