"mediaWiki": true,
"JSON": true,
"jQuery": false,
- "QUnit": false
+ "QUnit": false,
+ "sinon": false
}
}
"mw.html.Cdata",
"mw.html.Raw",
"mw.hook",
- "mw.template"
+ "mw.template",
+ "mw.errorLogger"
]
},
{
</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 {
// 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',
--- /dev/null
+/**
+ * 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 ) );
'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',
--- /dev/null
+( 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 ) );