blueslip: Replace jQuery wrappers with error event listener.

Not all our errors actually happen in the contexts we were
wrapping (e.g. `setTimeout` and `_.throttle`).  Also this fixes the
neat Firefox inspector feature that shows you where your event
handlers for a given DOM element actually live.

Using this "semi-modern" browser event means that Safari 9 and older
and IE10 and older may not have our browser error reporting active;
that seems fine giving the vanishing market share of those browsers.

https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
This commit is contained in:
Anders Kaseorg 2019-07-08 19:09:53 -07:00 committed by Tim Abbott
parent 0c80fc6aba
commit e0a18d3394
3 changed files with 7 additions and 151 deletions

View File

@ -85,12 +85,4 @@ run_test('basics', () => {
throw_a_warning();
assert.equal(blueslip.get_test_logs('warn').length, 1);
blueslip.clear_test_data();
// Finally, let's check the wrap_function feature which allows logging
// of errors in function calls. We can use it as follows:
const original_function = () => {
return 'hello';
};
const wrapped_function = blueslip.wrap_function(original_function);
assert.equal(original_function(), wrapped_function());
});

View File

@ -81,12 +81,6 @@ exports.make_zblueslip = function (opts) {
return ex.message;
};
lib.wrap_function = (f) => {
return (...args) => {
return f.apply(this, args);
};
};
return lib;
};

View File

@ -217,144 +217,14 @@ exports.exception_msg = function blueslip_exception_msg(ex) {
return message;
};
exports.wrap_function = function blueslip_wrap_function(func) {
if (func.blueslip_wrapper !== undefined) {
func.blueslip_wrapper_refcnt++;
return func.blueslip_wrapper;
$(window).on('error', function (event) {
var ex = event.originalEvent.error;
if (ex instanceof BlueslipError) {
return;
}
var new_func = function blueslip_wrapper() {
try {
return func.apply(this, arguments);
} catch (ex) {
// Treat exceptions like a call to fatal() if they
// weren't generated from fatal()
if (ex instanceof BlueslipError) {
throw ex;
}
var message = exports.exception_msg(ex);
report_error(message, ex.stack);
throw ex;
}
};
func.blueslip_wrapper = new_func;
func.blueslip_wrapper_refcnt = 1;
return new_func;
};
// Catch all exceptions from jQuery event handlers, $(document).ready
// functions, and ajax success/failure continuations and funnel them
// through blueslip.
(function () {
// This reference counting scheme can't break all the circular
// references we create because users can remove jQuery event
// handlers without referencing the particular handler they want
// to remove. We just hope this memory leak won't be too bad.
function dec_wrapper_refcnt(func) {
if (func.blueslip_wrapper_refcnt !== undefined) {
func.blueslip_wrapper_refcnt--;
if (func.blueslip_wrapper_refcnt === 0) {
delete func.blueslip_wrapper;
delete func.blueslip_wrapper_refcnt;
}
}
}
$.ajaxPrefilter(function (options) {
_.each(['success', 'error', 'complete'], function (cb_name) {
if (options[cb_name] !== undefined) {
options[cb_name] = exports.wrap_function(options[cb_name]);
}
});
});
if (document.addEventListener) {
var orig_on = $.fn.on;
var orig_off = $.fn.off;
var orig_ready = $.fn.ready;
$.fn.on = function blueslip_jquery_on_wrapper(types, selector, data, fn, one) {
if (typeof types === 'object') {
// ( types-Object, selector, data)
// We'll get called again from the recursive call in the original
// $.fn.on
return orig_on.call(this, types, selector, data, fn, one);
}
// Only one handler, but we have to figure out which
// argument it is. The argument munging is taken from
// jQuery itself, so we tell jslint to ignore the style
// issues that the jQuery code would raise. It sucks
// that we have to replicate the code :(
/*jslint eqeq: true */
if ( data == null && fn == null ) {
// ( types, fn )
fn = selector;
data = selector = undefined;
} else if ( fn == null ) {
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
if ( fn === false ) {
fn = function () { return false; };
} else if ( !fn ) {
return this;
}
/*jslint eqeq: false */
return orig_on.call(this, types, selector, data, exports.wrap_function(fn), one);
};
$.fn.off = function (types, selector, fn) {
if (types && types.preventDefault && types.handleObj) {
// (event)
// We'll get called again through the recursive call in the original
// $.fn.off
return orig_off.call(this, types, selector, fn);
}
if (typeof types === "object" ) {
// ( types-object [, selector] )
// We'll get called again through the recursive call in the original
// $.fn.off
return orig_off.call(this, types, selector, fn);
}
// Only one handler, but we have to figure out which
// argument it is. The argument munging is taken from
// jQuery, itself.
if ( selector === false || typeof selector === "function" ) {
// ( types [, fn] )
fn = selector;
selector = undefined;
}
if ( fn === false ) {
fn = function () { return false; };
}
if (fn) {
var wrapper = fn.blueslip_wrapper;
if (wrapper !== undefined) {
dec_wrapper_refcnt(fn);
fn = wrapper;
}
}
return orig_off.call(this, types, selector, fn);
};
$.fn.ready = function blueslip_jquery_ready_wrapper(func) {
return orig_ready.call(this, exports.wrap_function(func));
};
}
}());
var message = exports.exception_msg(ex);
report_error(message, ex.stack);
});
function build_arg_list(msg, more_info) {
var args = [msg];