Merge "Ensure variables in database classes are defined, used and correctly cased"
[lhc/web/wiklou.git] / resources / mediawiki / mediawiki.js
index 840a071..5623575 100644 (file)
@@ -12,7 +12,9 @@ var mw = ( function ( $, undefined ) {
        /* Private Members */
 
        var hasOwn = Object.prototype.hasOwnProperty,
-               slice = Array.prototype.slice;
+               slice = Array.prototype.slice,
+               trackCallbacks = $.Callbacks( 'memory' ),
+               trackQueue = [];
 
        /**
         * Log a message to window.console, if possible. Useful to force logging of some
@@ -325,6 +327,70 @@ var mw = ( function ( $, undefined ) {
        return {
                /* Public Members */
 
+               /**
+                * Get the current time, measured in milliseconds since January 1, 1970 (UTC).
+                *
+                * On browsers that implement the Navigation Timing API, this function will produce floating-point
+                * values with microsecond precision that are guaranteed to be monotonic. On all other browsers,
+                * it will fall back to using `Date`.
+                *
+                * @returns {number} Current time
+                */
+               now: ( function () {
+                       var perf = window.performance,
+                               navStart = perf && perf.timing && perf.timing.navigationStart;
+                       return navStart && typeof perf.now === 'function' ?
+                               function () { return navStart + perf.now(); } :
+                               function () { return +new Date(); };
+               }() ),
+
+               /**
+                * 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
+                */
+               track: function ( topic, data ) {
+                       trackQueue.push( { topic: topic, timeStamp: mw.now(), data: data } );
+                       trackCallbacks.fire( 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
+                */
+               trackSubscribe: function ( topic, callback ) {
+                       var seen = 0;
+
+                       trackCallbacks.add( function ( trackQueue ) {
+                               var event;
+                               for ( ; seen < trackQueue.length; seen++ ) {
+                                       event = trackQueue[ seen ];
+                                       if ( event.topic.indexOf( topic ) === 0 ) {
+                                               callback.call( event, event.topic, event.data );
+                                       }
+                               }
+                       } );
+               },
+
                /**
                 * Dummy placeholder for {@link mw.log}
                 * @method
@@ -1269,12 +1335,7 @@ var mw = ( function ( $, undefined ) {
                                                        }
                                                        return true;
                                                } );
-                                               if ( mw.loader.store.useFunction ) {
-                                                       /* jshint -W054 */
-                                                       new Function( concatSource.join( ';' ) )();
-                                               } else {
-                                                       $.globalEval( concatSource.join( ';' ) );
-                                               }
+                                               $.globalEval( concatSource.join( ';' ) );
                                        }
 
                                        // Early exit if there's nothing to load...
@@ -1640,8 +1701,8 @@ var mw = ( function ( $, undefined ) {
                                /**
                                 * Change the state of one or more modules.
                                 *
-                                * @param {string|Object} module module name or object of module name/state pairs
-                                * @param {string} state state name
+                                * @param {string|Object} module Module name or object of module name/state pairs
+                                * @param {string} state State name
                                 */
                                state: function ( module, state ) {
                                        var m;
@@ -1689,7 +1750,7 @@ var mw = ( function ( $, undefined ) {
                                /**
                                 * Get the state of a module.
                                 *
-                                * @param {string} module name of module to get state for
+                                * @param {string} module Name of module to get state for
                                 */
                                getState: function ( module ) {
                                        if ( registry[module] !== undefined && registry[module].state !== undefined ) {
@@ -1754,6 +1815,38 @@ var mw = ( function ( $, undefined ) {
                                        // Cache hit stats
                                        stats: { hits: 0, misses: 0, expired: 0 },
 
+                                       // Experiment data
+                                       experiment: ( function () {
+                                               var start = ( new Date() ).getTime(), id = 0, seed = 0;
+
+                                               try {
+                                                       id = JSON.parse( localStorage.getItem( 'moduleStorageExperiment2' ) );
+                                                       if ( typeof id !== 'number' ) {
+                                                               id = Math.floor( Math.random() * Math.random() * 1e16 );
+                                                               localStorage.setItem( 'moduleStorageExperiment2', id );
+                                                       }
+                                                       seed = id % 2000;
+                                               } catch ( e ) {}
+
+                                               return {
+                                                       // Unique identifier for this browser. This allows us to group all
+                                                       // datapoints generated by a particular browser, which in turn allows us
+                                                       // to see how the initial load compares to subsequent page loads.
+                                                       id: id,
+
+                                                       // Group assignment may be 0 (not in experiment), 1 (control group), or 2
+                                                       // (experimental group). Browsers that don't implement all the prerequisite APIs
+                                                       // (JSON and Web Storage) are ineligible. Eligible browsers have a 0.1% chance
+                                                       // of being included in the experiment, in which case they are equally likely to
+                                                       // be assigned to either the experimental or control group.
+                                                       group: seed === 1 ? 1 : ( seed === 2 ? 2 : 0 ),
+
+                                                       // Assess module storage performance by measuring the time between this
+                                                       // reference point and the window load event.
+                                                       start: start
+                                               };
+                                       }() ),
+
                                        /**
                                         * Construct a JSON-serializable object representing the content of the store.
                                         * @return {Object} Module store contents.
@@ -1796,32 +1889,35 @@ var mw = ( function ( $, undefined ) {
                                        },
 
                                        /**
-                                        * Initialize the store by retrieving it from localStorage and (if successfully
-                                        * retrieved) decoding the stored JSON value to a plain object.
+                                        * Initialize the store.
+                                        *
+                                        * Retrieves store from localStorage and (if successfully retrieved) decoding
+                                        * the stored JSON value to a plain object.
                                         *
                                         * The try / catch block is used for JSON & localStorage feature detection.
                                         * See the in-line documentation for Modernizr's localStorage feature detection
-                                        * code for a full account of why we need a try / catch: <http://git.io/4NEwKg>.
+                                        * code for a full account of why we need a try / catch:
+                                        * https://github.com/Modernizr/Modernizr/blob/v2.7.1/modernizr.js#L771-L796
                                         */
                                        init: function () {
                                                var raw, data;
 
                                                if ( mw.loader.store.enabled !== null ) {
-                                                       // #init already ran.
+                                                       // Init already ran
                                                        return;
                                                }
 
-                                               if ( !mw.config.get( 'wgResourceLoaderStorageEnabled' ) || mw.config.get( 'debug' ) ) {
-                                                       // Disabled by configuration, or because debug mode is set.
+                                               if ( ( !mw.config.get( 'wgResourceLoaderStorageEnabled' ) && mw.loader.store.experiment.group !== 2 )
+                                                       || mw.config.get( 'debug' ) ) {
+                                                       // Disabled by configuration, or because debug mode is set
                                                        mw.loader.store.enabled = false;
                                                        return;
                                                }
 
                                                try {
                                                        raw = localStorage.getItem( mw.loader.store.getStoreKey() );
-                                                       // If we get here, localStorage is available; mark enabled.
+                                                       // If we get here, localStorage is available; mark enabled
                                                        mw.loader.store.enabled = true;
-                                                       mw.loader.store.useFunction = !!Math.floor( Math.random() * 2 );
                                                        data = JSON.parse( raw );
                                                        if ( data && typeof data.items === 'object' && data.vary === mw.loader.store.getVary() ) {
                                                                mw.loader.store.items = data.items;
@@ -1830,7 +1926,8 @@ var mw = ( function ( $, undefined ) {
                                                } catch (e) {}
 
                                                if ( raw === undefined ) {
-                                                       mw.loader.store.enabled = false;  // localStorage failed; disable store.
+                                                       // localStorage failed; disable store
+                                                       mw.loader.store.enabled = false;
                                                } else {
                                                        mw.loader.store.update();
                                                }
@@ -1845,7 +1942,7 @@ var mw = ( function ( $, undefined ) {
                                        get: function ( module ) {
                                                var key;
 
-                                               if ( mw.loader.store.enabled !== true ) {
+                                               if ( !mw.loader.store.enabled ) {
                                                        return false;
                                                }
 
@@ -1867,29 +1964,23 @@ var mw = ( function ( $, undefined ) {
                                        set: function ( module, descriptor ) {
                                                var args, key;
 
-                                               if ( mw.loader.store.enabled !== true ) {
+                                               if ( !mw.loader.store.enabled ) {
                                                        return false;
                                                }
 
                                                key = mw.loader.store.getModuleKey( module );
 
-                                               if ( key in mw.loader.store.items ) {
-                                                       // Already set; decline to store.
-                                                       return false;
-                                               }
-
-                                               if ( descriptor.state !== 'ready' ) {
-                                                       // Module failed to load; decline to store.
-                                                       return false;
-                                               }
-
-                                               if ( !descriptor.version || $.inArray( descriptor.group, [ 'private', 'user', 'site' ] ) !== -1 ) {
-                                                       // Unversioned, private, or site-/user-specific; decline to store.
-                                                       return false;
-                                               }
-
-                                               if ( $.inArray( undefined, [ descriptor.script, descriptor.style, descriptor.messages ] ) !== -1 ) {
-                                                       // Partial descriptor; decline to store.
+                                               if (
+                                                       // Already stored a copy of this exact version
+                                                       key in mw.loader.store.items ||
+                                                       // Module failed to load
+                                                       descriptor.state !== 'ready' ||
+                                                       // Unversioned, private, or site-/user-specific
+                                                       ( !descriptor.version || $.inArray( descriptor.group, [ 'private', 'user', 'site' ] ) !== -1 ) ||
+                                                       // Partial descriptor
+                                                       $.inArray( undefined, [ descriptor.script, descriptor.style, descriptor.messages ] ) !== -1
+                                               ) {
+                                                       // Decline to store
                                                        return false;
                                                }
 
@@ -1897,13 +1988,21 @@ var mw = ( function ( $, undefined ) {
                                                        args = [
                                                                JSON.stringify( module ),
                                                                typeof descriptor.script === 'function' ?
-                                                                       String( descriptor.script ) : JSON.stringify( descriptor.script ),
+                                                                       String( descriptor.script ) :
+                                                                       JSON.stringify( descriptor.script ),
                                                                JSON.stringify( descriptor.style ),
                                                                JSON.stringify( descriptor.messages )
                                                        ];
-                                               } catch (e) {
+                                                       // Attempted workaround for a possible Opera bug (bug 57567).
+                                                       // 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)' );
+                                                       }
+                                               } catch ( e ) {
                                                        return;
                                                }
+
                                                mw.loader.store.items[key] = 'mw.loader.implement(' + args.join(',') + ');';
                                                mw.loader.store.update();
                                        },
@@ -1915,7 +2014,7 @@ var mw = ( function ( $, undefined ) {
                                        prune: function () {
                                                var key, module;
 
-                                               if ( mw.loader.store.enabled !== true ) {
+                                               if ( !mw.loader.store.enabled ) {
                                                        return false;
                                                }
 
@@ -1946,8 +2045,10 @@ var mw = ( function ( $, undefined ) {
                                                var timer;
 
                                                function flush() {
-                                                       var data, key = mw.loader.store.getStoreKey();
-                                                       if ( mw.loader.store.enabled !== true ) {
+                                                       var data,
+                                                               key = mw.loader.store.getStoreKey();
+
+                                                       if ( !mw.loader.store.enabled ) {
                                                                return false;
                                                        }
                                                        mw.loader.store.prune();
@@ -1959,7 +2060,7 @@ var mw = ( function ( $, undefined ) {
                                                                localStorage.removeItem( key );
                                                                data = JSON.stringify( mw.loader.store );
                                                                localStorage.setItem( key, data );
-                                                       } catch (e) {}
+                                                       } catch ( e ) {}
                                                }
 
                                                return function () {