Skip to content

Commit 276782f

Browse files
committed
Raven.uninstall also restores builtin methods
1 parent 5d0db95 commit 276782f

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
@@ -29,6 +29,7 @@
2929
"grunt-release": "^0.13.0",
3030
"grunt-s3": "0.2.0-alpha.3",
3131
"grunt-sri": "mattrobenolt/grunt-sri#pretty",
32+
"jquery": "^2.1.4",
3233
"lodash": "^3.10.1",
3334
"mocha": "^1.21.5",
3435
"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) {
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;
@@ -545,9 +549,12 @@ Raven.prototype = {
545549
_wrapBuiltIns: function() {
546550
var self = this;
547551

548-
function fill(obj, name, replacement) {
552+
function fill(obj, name, replacement, noUndo) {
549553
var orig = obj[name];
550554
obj[name] = replacement(orig);
555+
if (!noUndo) {
556+
self._wrappedBuiltIns.push([obj, name, orig]);
557+
}
551558
}
552559

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

623632
var $ = window.jQuery || window.$;
624-
var origReady;
625633
if ($ && $.fn && $.fn.ready) {
626-
origReady = $.fn.ready;
627-
$.fn.ready = function ravenjQueryReadyWrapper(fn) {
628-
return origReady.call(this, self.wrap(fn));
629-
};
634+
fill($.fn, 'ready', function (orig) {
635+
return function (fn) {
636+
orig.call(this, self.wrap(fn));
637+
};
638+
});
639+
}
640+
},
641+
642+
_restoreBuiltIns: function () {
643+
// restore any wrapped builtins
644+
var builtin;
645+
while (this._wrappedBuiltIns.length) {
646+
builtin = this._wrappedBuiltIns.shift();
647+
648+
var obj = builtin[0],
649+
name = builtin[1],
650+
orig = builtin[2];
651+
652+
obj[name] = orig;
630653
}
631654
},
632655

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)