( function () {
'use strict';
- var slice = Array.prototype.slice;
+ var slice = Array.prototype.slice,
+ mwLoaderTrack = mw.track,
+ trackCallbacks = $.Callbacks( 'memory' ),
+ trackHandlers = [];
/**
* Object constructor for messages.
mw.msg = function () {
return mw.message.apply( mw.message, arguments ).toString();
};
+
+ /**
+ * Track an analytic event.
+ *
+ * This method provides a generic means for MediaWiki JavaScript code to capture state
+ * information for analysis. Each logged event specifies a string topic name that describes
+ * the kind of event that it is. Topic names consist of dot-separated path components,
+ * arranged from most general to most specific. Each path component should have a clear and
+ * well-defined purpose.
+ *
+ * Data handlers are registered via `mw.trackSubscribe`, and receive the full set of
+ * events that match their subcription, including those that fired before the handler was
+ * bound.
+ *
+ * @param {string} topic Topic name
+ * @param {Object} [data] Data describing the event, encoded as an object
+ */
+ mw.track = function ( topic, data ) {
+ mwLoaderTrack( topic, data );
+ trackCallbacks.fire( mw.trackQueue );
+ };
+
+ /**
+ * Register a handler for subset of analytic events, specified by topic.
+ *
+ * Handlers will be called once for each tracked event, including any events that fired before the
+ * handler was registered; 'this' is set to a plain object with a 'timeStamp' property indicating
+ * the exact time at which the event fired, a string 'topic' property naming the event, and a
+ * 'data' property which is an object of event-specific data. The event topic and event data are
+ * also passed to the callback as the first and second arguments, respectively.
+ *
+ * @param {string} topic Handle events whose name starts with this string prefix
+ * @param {Function} callback Handler to call for each matching tracked event
+ * @param {string} callback.topic
+ * @param {Object} [callback.data]
+ */
+ mw.trackSubscribe = function ( topic, callback ) {
+ var seen = 0;
+ function handler( trackQueue ) {
+ var event;
+ for ( ; seen < trackQueue.length; seen++ ) {
+ event = trackQueue[ seen ];
+ if ( event.topic.indexOf( topic ) === 0 ) {
+ callback.call( event, event.topic, event.data );
+ }
+ }
+ }
+
+ trackHandlers.push( [ handler, callback ] );
+
+ trackCallbacks.add( handler );
+ };
+
+ /**
+ * Stop handling events for a particular handler
+ *
+ * @param {Function} callback
+ */
+ mw.trackUnsubscribe = function ( callback ) {
+ trackHandlers = trackHandlers.filter( function ( fns ) {
+ if ( fns[ 1 ] === callback ) {
+ trackCallbacks.remove( fns[ 0 ] );
+ // Ensure the tuple is removed to avoid holding on to closures
+ return false;
+ }
+ return true;
+ } );
+ };
+
+ // Fire events from before track() triggred fire()
+ trackCallbacks.fire( mw.trackQueue );
}() );
var mw, StringSet, log,
hasOwn = Object.prototype.hasOwnProperty,
slice = Array.prototype.slice,
- trackCallbacks = $.Callbacks( 'memory' ),
- trackHandlers = [],
trackQueue = [];
/**
);
}
+ /**
+ * Log a message to window.console, if possible.
+ *
+ * Useful to force logging of some errors that are otherwise hard to detect (i.e., this logs
+ * also in production mode). Gets console references in each invocation instead of caching the
+ * reference, so that debugging tools loaded later are supported (e.g. Firebug Lite in IE).
+ *
+ * @private
+ * @param {string} topic Stream name passed by mw.track
+ * @param {Object} data Data passed by mw.track
+ * @param {Error} [data.exception]
+ * @param {string} data.source Error source
+ * @param {string} [data.module] Name of module which caused the error
+ */
+ function logError( topic, data ) {
+ /* eslint-disable no-console */
+ var msg,
+ e = data.exception,
+ source = data.source,
+ module = data.module,
+ console = window.console;
+
+ if ( console && console.log ) {
+ msg = ( e ? 'Exception' : 'Error' ) + ' in ' + source;
+ if ( module ) {
+ msg += ' in module ' + module;
+ }
+ msg += ( e ? ':' : '.' );
+ console.log( msg );
+
+ // If we have an exception object, log it to the warning channel to trigger
+ // proper stacktraces in browsers that support it.
+ if ( e && console.warn ) {
+ console.warn( e );
+ }
+ }
+ /* eslint-enable no-console */
+ }
+
/**
* Create an object that can be read from or written to via methods that allow
* interaction both with single and multiple properties at once.
}() ),
/**
- * Track an analytic event.
+ * List of all analytic events emitted so far.
*
- * This method provides a generic means for MediaWiki JavaScript code to capture state
- * information for analysis. Each logged event specifies a string topic name that describes
- * the kind of event that it is. Topic names consist of dot-separated path components,
- * arranged from most general to most specific. Each path component should have a clear and
- * well-defined purpose.
- *
- * Data handlers are registered via `mw.trackSubscribe`, and receive the full set of
- * events that match their subcription, including those that fired before the handler was
- * bound.
- *
- * @param {string} topic Topic name
- * @param {Object} [data] Data describing the event, encoded as an object
+ * @private
+ * @property {Array}
*/
+ trackQueue: trackQueue,
+
track: function ( topic, data ) {
trackQueue.push( { topic: topic, timeStamp: mw.now(), data: data } );
- trackCallbacks.fire( trackQueue );
+ // The base module extends this method to fire events here
},
/**
- * Register a handler for subset of analytic events, specified by topic.
+ * Track an early error event via mw.track and send it to the window console.
*
- * Handlers will be called once for each tracked event, including any events that fired before the
- * handler was registered; 'this' is set to a plain object with a 'timeStamp' property indicating
- * the exact time at which the event fired, a string 'topic' property naming the event, and a
- * 'data' property which is an object of event-specific data. The event topic and event data are
- * also passed to the callback as the first and second arguments, respectively.
- *
- * @param {string} topic Handle events whose name starts with this string prefix
- * @param {Function} callback Handler to call for each matching tracked event
- * @param {string} callback.topic
- * @param {Object} [callback.data]
- */
- trackSubscribe: function ( topic, callback ) {
- var seen = 0;
- function handler( trackQueue ) {
- var event;
- for ( ; seen < trackQueue.length; seen++ ) {
- event = trackQueue[ seen ];
- if ( event.topic.indexOf( topic ) === 0 ) {
- callback.call( event, event.topic, event.data );
- }
- }
- }
-
- trackHandlers.push( [ handler, callback ] );
-
- trackCallbacks.add( handler );
- },
-
- /**
- * Stop handling events for a particular handler
- *
- * @param {Function} callback
+ * @private
+ * @param {string} topic Topic name
+ * @param {Object} data Data describing the event, encoded as an object; see mw#logError
*/
- trackUnsubscribe: function ( callback ) {
- trackHandlers = trackHandlers.filter( function ( fns ) {
- if ( fns[ 1 ] === callback ) {
- trackCallbacks.remove( fns[ 0 ] );
- // Ensure the tuple is removed to avoid holding on to closures
- return false;
- }
- return true;
- } );
+ trackError: function ( topic, data ) {
+ mw.track( topic, data );
+ logError( topic, data );
},
// Expose Map constructor
} catch ( e ) {
// A user-defined callback raised an exception.
// Swallow it to protect our state machine!
- mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'load-callback' } );
+ mw.trackError( 'resourceloader.exception', {
+ exception: e,
+ module: module,
+ source: 'load-callback'
+ } );
}
}
}
// This module is unknown or has unknown dependencies.
// Undo any incomplete resolutions made and keep going.
resolved = saved;
- mw.track( 'resourceloader.exception', {
+ mw.trackError( 'resourceloader.exception', {
exception: err,
source: 'resolve'
} );
// Use mw.track instead of mw.log because these errors are common in production mode
// (e.g. undefined variable), and mw.log is only enabled in debug mode.
registry[ module ].state = 'error';
- mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } );
+ mw.trackError( 'resourceloader.exception', {
+ exception: e, module:
+ module, source: 'module-execute'
+ } );
handlePending( module );
}
};
// risks and clear everything in this cache.
mw.loader.store.clear();
- mw.track( 'resourceloader.exception', { exception: err, source: 'store-eval' } );
+ mw.trackError( 'resourceloader.exception', {
+ exception: err,
+ source: 'store-eval'
+ } );
// Re-add the failed ones that are still pending back to the batch
failed = sourceModules.filter( function ( module ) {
return registry[ module ].state === 'loading';
return;
}
} catch ( e ) {
- mw.track( 'resourceloader.exception', { exception: e, source: 'store-localstorage-init' } );
+ mw.trackError( 'resourceloader.exception', {
+ exception: e,
+ source: 'store-localstorage-init'
+ } );
}
if ( raw === undefined ) {
// This regex should never match under sane conditions.
if ( /^\s*\(/.test( args[ 1 ] ) ) {
args[ 1 ] = 'function' + args[ 1 ];
- mw.track( 'resourceloader.assert', { source: 'bug-T59567' } );
+ mw.trackError( 'resourceloader.assert', { source: 'bug-T59567' } );
}
} catch ( e ) {
- mw.track( 'resourceloader.exception', { exception: e, source: 'store-localstorage-json' } );
+ mw.trackError( 'resourceloader.exception', {
+ exception: e,
+ source: 'store-localstorage-json'
+ } );
return false;
}
data = JSON.stringify( mw.loader.store );
localStorage.setItem( key, data );
} catch ( e ) {
- mw.track( 'resourceloader.exception', { exception: e, source: 'store-localstorage-update' } );
+ mw.trackError( 'resourceloader.exception', {
+ exception: e,
+ source: 'store-localstorage-update'
+ } );
}
hasPendingWrite = false;
// @deprecated since 1.23 Use $ or jQuery instead
mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' );
- /**
- * Log a message to window.console, if possible.
- *
- * Useful to force logging of some errors that are otherwise hard to detect (i.e., this logs
- * also in production mode). Gets console references in each invocation instead of caching the
- * reference, so that debugging tools loaded later are supported (e.g. Firebug Lite in IE).
- *
- * @private
- * @param {string} topic Stream name passed by mw.track
- * @param {Object} data Data passed by mw.track
- * @param {Error} [data.exception]
- * @param {string} data.source Error source
- * @param {string} [data.module] Name of module which caused the error
- */
- function logError( topic, data ) {
- /* eslint-disable no-console */
- var msg,
- e = data.exception,
- source = data.source,
- module = data.module,
- console = window.console;
-
- if ( console && console.log ) {
- msg = ( e ? 'Exception' : 'Error' ) + ' in ' + source;
- if ( module ) {
- msg += ' in module ' + module;
- }
- msg += ( e ? ':' : '.' );
- console.log( msg );
-
- // If we have an exception object, log it to the warning channel to trigger
- // proper stacktraces in browsers that support it.
- if ( e && console.warn ) {
- console.warn( e );
- }
- }
- /* eslint-enable no-console */
- }
-
- // Subscribe to error streams
- mw.trackSubscribe( 'resourceloader.exception', logError );
- mw.trackSubscribe( 'resourceloader.assert', logError );
-
// Attach to window and globally alias
window.mw = window.mediaWiki = mw;
}( jQuery ) );