X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Fmediawiki%2Fmediawiki.js;h=562357529421a18684e1b2722ccd81539152bb26;hb=af7a22b77132b498daa78ed92177a26e52b2f571;hp=840a071514ec6fc781ae8d67c24b9eac291debde;hpb=1a06bdf6c7b3c54926d0bea67d22fe1b6dc25d56;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/mediawiki/mediawiki.js b/resources/mediawiki/mediawiki.js index 840a071514..5623575294 100644 --- a/resources/mediawiki/mediawiki.js +++ b/resources/mediawiki/mediawiki.js @@ -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: . + * 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 () {