Report uncaught errors via mw.track

Adds a global error handler that sends errors to mw.track as
an errorLogging.windowOnerror event.

Bug: T88874
Change-Id: Ic091c9f93c59bda47bda2cfd609c64cd1d014b39
This commit is contained in:
Gergő Tisza 2015-01-29 01:21:01 +00:00 committed by Ori.livneh
parent 7ab9e6ed0c
commit 23c1cebca0
7 changed files with 98 additions and 2 deletions

View file

@ -22,6 +22,7 @@
"mediaWiki": true,
"JSON": true,
"jQuery": false,
"QUnit": false
"QUnit": false,
"sinon": false
}
}

View file

@ -14,7 +14,8 @@
"mw.html.Cdata",
"mw.html.Raw",
"mw.hook",
"mw.template"
"mw.template",
"mw.errorLogger"
]
},
{

View file

@ -41,6 +41,7 @@
</script>
<script src="modules/lib/jquery/jquery.js"></script>
<script src="modules/src/mediawiki/mediawiki.js"></script>
<script src="modules/src/mediawiki/mediawiki.errorLogger.js"></script>
<script src="modules/src/mediawiki/mediawiki.startUp.js"></script>
<style>
.mw-jsduck-log {

View file

@ -775,6 +775,7 @@ return array(
// Keep maintenance/jsduck/eg-iframe.html in sync
'scripts' => array(
'resources/src/mediawiki/mediawiki.js',
'resources/src/mediawiki/mediawiki.errorLogger.js',
'resources/src/mediawiki/mediawiki.startUp.js',
),
'debugScripts' => 'resources/src/mediawiki/mediawiki.log.js',

View file

@ -0,0 +1,49 @@
/**
* Try to catch errors in modules which don't do their own error handling.
* @class mw.errorLogger
* @singleton
*/
( function ( mw ) {
'use strict';
mw.errorLogger = {
/**
* Fired via mw.track when an error is not handled by local code and is caught by the
* window.onerror handler.
*
* @event global_error
* @param {string} errorMessage Error errorMessage.
* @param {string} url URL where error was raised.
* @param {number} lineNumber Line number where error was raised.
* @param {number} [columnNumber] Line number where error was raised. Not all browsers
* support this.
* @param {Error|Mixed} [errorObject] The error object. Typically an instance of Error, but anything
* (even a primitive value) passed to a throw clause will end up here.
*/
/**
* Install a window.onerror handler that will report via mw.track, while preserving
* any previous handler.
* @param {Object} window
*/
installGlobalHandler: function ( window ) {
// We will preserve the return value of the previous handler. window.onerror works the
// opposite way than normal event handlers (returning true will prevent the default
// action, returning false will let the browser handle the error normally, by e.g.
// logging to the console), so our fallback old handler needs to return false.
var oldHandler = window.onerror || function () { return false; };
/**
* Dumb window.onerror handler which forwards the errors via mw.track.
* @fires global_error
*/
window.onerror = function ( errorMessage, url, lineNumber, columnNumber, errorObject ) {
mw.track( 'global.error', { errorMessage: errorMessage, url: url,
lineNumber: lineNumber, columnNumber: columnNumber, errorObject: errorObject } );
return oldHandler.apply( this, arguments );
};
}
};
mw.errorLogger.installGlobalHandler( window );
}( mediaWiki ) );

View file

@ -62,6 +62,7 @@ return array(
'tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js',
'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js',
'tests/qunit/data/mediawiki.jqueryMsg.data.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',

View file

@ -0,0 +1,42 @@
( function ( $, mw ) {
QUnit.module( 'mediawiki.errorLogger', QUnit.newMwEnvironment() );
QUnit.test( 'installGlobalHandler', 7, function ( assert ) {
var w = {},
errorMessage = 'Foo',
errorUrl = 'http://example.com',
errorLine = '123',
errorColumn = '45',
errorObject = new Error( 'Foo'),
oldHandler = this.sandbox.stub();
this.sandbox.stub( mw, 'track' );
mw.errorLogger.installGlobalHandler( w );
assert.ok( w.onerror, 'Global handler has been installed' );
assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), false,
'Global handler returns false when there is no previous handler' );
sinon.assert.calledWithExactly( mw.track, 'global.error',
sinon.match( { errorMessage: errorMessage, url: errorUrl, lineNumber: errorLine } ) );
mw.track.reset();
w.onerror( errorMessage, errorUrl, errorLine, errorColumn, errorObject );
sinon.assert.calledWithExactly( mw.track, 'global.error',
sinon.match( { errorMessage: errorMessage, url: errorUrl, lineNumber: errorLine,
columnNumber: errorColumn, errorObject: errorObject } ) );
w = { onerror: oldHandler };
mw.errorLogger.installGlobalHandler( w );
w.onerror( errorMessage, errorUrl, errorLine );
sinon.assert.calledWithExactly( oldHandler, errorMessage, errorUrl, errorLine );
oldHandler.returns( false );
assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), false,
'Global handler preserves false return from previous handler' );
oldHandler.returns( true );
assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), true,
'Global handler preserves true return from previous handler' );
} );
}( jQuery, mediaWiki ) );