resourceloader: move track() from mediawiki.js to the base module
authorAaron Schulz <aschulz@wikimedia.org>
Thu, 7 Jun 2018 06:23:07 +0000 (23:23 -0700)
committerKrinkle <krinklemail@gmail.com>
Thu, 14 Jun 2018 19:55:31 +0000 (19:55 +0000)
Handlers will not run until the base module is loaded. The track()
method in the loader is upgraded to fire the handlers once the base
module loads.

Refactor the exception logging bits to call logError more directly
instead of subscribing a logging handler to the event queue.

Bug: T192623
Change-Id: I2372bbf70a8775c7ce4c2c78c5edccf1195a9875

resources/src/mediawiki/mediawiki.base.js
resources/src/mediawiki/mediawiki.js

index 43ee202..89b3902 100644 (file)
 ( 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 );
 }() );
index 9d34bf1..ad020b5 100644 (file)
@@ -14,8 +14,6 @@
        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 ) );