mw.loader: Replace log() calls with mw.track events
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.js
index bed5cb5..17b06ce 100644 (file)
                trackCallbacks = $.Callbacks( 'memory' ),
                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
-        * @method log_
-        * @param {string} msg Text for the log entry.
-        * @param {Error} [e]
-        */
-       function log( msg, e ) {
-               var console = window.console;
-               if ( console && console.log ) {
-                       console.log( msg );
-                       // If we have an exception object, log it to the error channel to trigger a
-                       // proper stacktraces in browsers that support it. No fallback as we have no browsers
-                       // that don't support error(), but do support log().
-                       if ( e && console.error ) {
-                               console.error( String( e ), e );
-                       }
-               }
-       }
-
        /**
         * Create an object that can be read from or written to from methods that allow
         * interaction both with single and multiple properties at once.
                config: null,
 
                /**
-                * Empty object that plugins can be installed in.
+                * Empty object for third-party libraries, for cases where you don't
+                * want to add a new global, or the global is bad and needs containment
+                * or wrapping.
                 *
                 * @property
                 */
                 */
                loader: ( function () {
 
+                       /**
+                        * Fired via mw.track on various resource loading errors.
+                        *
+                        * @event resourceloader_exception
+                        * @param {Error|Mixed} e The error that was thrown. Almost always an Error
+                        *   object, but in theory module code could manually throw something else, and that
+                        *   might also end up here.
+                        * @param {string} [module] Name of the module which caused the error. Omitted if the
+                        *   error is not module-related or the module cannot be easily identified due to
+                        *   batched handling.
+                        * @param {string} source Source of the error. Possible values:
+                        *
+                        *   - style: stylesheet error (only affects old IE where a special style loading method
+                        *     is used)
+                        *   - load-callback: exception thrown by user callback
+                        *   - module-execute: exception thrown by module code
+                        *   - store-eval: could not evaluate module code cached in localStorage
+                        *   - store-localstorage-init: localStorage or JSON parse error in mw.loader.store.init
+                        *   - store-localstorage-json: JSON conversion error in mw.loader.store.set
+                        *   - store-localstorage-update: localStorage or JSON conversion error in mw.loader.store.update
+                        */
+
+                       /**
+                        * Fired via mw.track on resource loading error conditions.
+                        *
+                        * @event resourceloader_assert
+                        * @param {string} source Source of the error. Possible values:
+                        *
+                        *   - bug-T59567: failed to cache script due to an Opera function -> string conversion
+                        *     bug; see <https://phabricator.wikimedia.org/T59567> for details
+                        */
+
                        /**
                         * Mapping of registered modules.
                         *
                                // Makes sure that cssText containing `@import`
                                // rules will end up in a new stylesheet (as those only work when
                                // placed at the start of a stylesheet; bug 35562).
-                               return cssText.indexOf( '@import' ) === -1;
+                               return cssText.slice( 0, '@import'.length ) !== '@import';
                        }
 
                        /**
                                                        try {
                                                                styleEl.styleSheet.cssText += cssText;
                                                        } catch ( e ) {
-                                                               log( 'Stylesheet error', e );
+                                                               mw.track( 'resourceloader.exception', { exception: e, source: 'stylesheet' } );
                                                        }
                                                } else {
                                                        styleEl.appendChild( document.createTextNode( cssText ) );
                                                } catch ( e ) {
                                                        // A user-defined callback raised an exception.
                                                        // Swallow it to protect our state machine!
-                                                       log( 'Exception thrown by user callback', e );
+                                                       mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'load-callback' } );
                                                }
                                        }
                                }
                                        } catch ( e ) {
                                                // This needs to NOT use mw.log because these errors are common in production mode
                                                // and not in debug mode, such as when a symbol that should be global isn't exported
-                                               log( 'Exception thrown by ' + module, e );
                                                registry[module].state = 'error';
+                                               mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } );
                                                handlePending( module );
                                        }
                                }
                        }
 
                        function sortQuery( o ) {
-                               var sorted = {}, key, a = [];
+                               var key,
+                                       sorted = {},
+                                       a = [];
+
                                for ( key in o ) {
                                        if ( hasOwn.call( o, key ) ) {
                                                a.push( key );
                         * @private
                         */
                        function buildModulesString( moduleMap ) {
-                               var arr = [], p, prefix;
+                               var p, prefix,
+                                       arr = [];
+
                                for ( prefix in moduleMap ) {
                                        p = prefix === '' ? '' : prefix + '.';
                                        arr.push( p + moduleMap[prefix].join( ',' ) );
                                                        // repopulate these modules to the cache.
                                                        // This means that at most one module will be useless (the one that had
                                                        // the error) instead of all of them.
-                                                       log( 'Error while evaluating data from mw.loader.store', err );
+                                                       mw.track( 'resourceloader.exception', { exception: err, source: 'store-eval' } );
                                                        origBatch = $.grep( origBatch, function ( module ) {
                                                                return registry[module].state === 'loading';
                                                        } );
                                                                return;
                                                        }
                                                } catch ( e ) {
-                                                       log( 'Storage error', e );
+                                                       mw.track( 'resourceloader.exception', { exception: e, source: 'store-localstorage-init' } );
                                                }
 
                                                if ( raw === undefined ) {
                                                                JSON.stringify( descriptor.messages ),
                                                                JSON.stringify( descriptor.templates )
                                                        ];
-                                                       // Attempted workaround for a possible Opera bug (bug 57567).
+                                                       // Attempted workaround for a possible Opera bug (bug T59567).
                                                        // This regex should never match under sane conditions.
                                                        if ( /^\s*\(/.test( args[1] ) ) {
                                                                args[1] = 'function' + args[1];
-                                                               log( 'Detected malformed function stringification (bug 57567)' );
+                                                               mw.track( 'resourceloader.assert', { source: 'bug-T59567' } );
                                                        }
                                                } catch ( e ) {
-                                                       log( 'Storage error', e );
+                                                       mw.track( 'resourceloader.exception', { exception: e, source: 'store-localstorage-json' } );
                                                        return;
                                                }
 
                                                                data = JSON.stringify( mw.loader.store );
                                                                localStorage.setItem( key, data );
                                                        } catch ( e ) {
-                                                               log( 'Storage error', e );
+                                                               mw.track( 'resourceloader.exception', { exception: e, source: 'store-localstorage-update' } );
                                                        }
                                                }
 
        // @deprecated since 1.23 Use $ or jQuery instead
        mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' );
 
-       // Attach to window and globally alias
-       window.mw = window.mediaWiki = mw;
+       /**
+        * 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
+        * @method log_
+        * @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 log( topic, data ) {
+               var msg,
+                       e = data.exception,
+                       source = data.source,
+                       module = data.module,
+                       console = window.console;
 
-       // Auto-register from pre-loaded startup scripts
-       if ( $.isFunction( window.startUp ) ) {
-               window.startUp();
-               window.startUp = undefined;
+               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 error channel to trigger a
+                       // proper stacktraces in browsers that support it. No fallback as we have no browsers
+                       // that don't support error(), but do support log().
+                       if ( e && console.error ) {
+                               console.error( String( e ), e );
+                       }
+               }
        }
 
+       // subscribe to error streams
+       mw.trackSubscribe( 'resourceloader.exception', log );
+       mw.trackSubscribe( 'resourceloader.assert', log );
+
+       // Attach to window and globally alias
+       window.mw = window.mediaWiki = mw;
 }( jQuery ) );