Implement extension registration from an extension.json file
[mediawiki.git] / resources / src / mediawiki / mediawiki.js
blob6bf93f49acdb80343bf4eccd71a1f840530dd26b
1 /**
2  * Base library for MediaWiki.
3  *
4  * Exposed globally as `mediaWiki` with `mw` as shortcut.
5  *
6  * @class mw
7  * @alternateClassName mediaWiki
8  * @singleton
9  */
10 ( function ( $ ) {
11         'use strict';
13         /* Private Members */
15         var mw,
16                 hasOwn = Object.prototype.hasOwnProperty,
17                 slice = Array.prototype.slice,
18                 trackCallbacks = $.Callbacks( 'memory' ),
19                 trackQueue = [];
21         /**
22          * Log a message to window.console, if possible. Useful to force logging of some
23          * errors that are otherwise hard to detect (I.e., this logs also in production mode).
24          * Gets console references in each invocation, so that delayed debugging tools work
25          * fine. No need for optimization here, which would only result in losing logs.
26          *
27          * @private
28          * @method log_
29          * @param {string} msg text for the log entry.
30          * @param {Error} [e]
31          */
32         function log( msg, e ) {
33                 var console = window.console;
34                 if ( console && console.log ) {
35                         console.log( msg );
36                         // If we have an exception object, log it through .error() to trigger
37                         // proper stacktraces in browsers that support it. There are no (known)
38                         // browsers that don't support .error(), that do support .log() and
39                         // have useful exception handling through .log().
40                         if ( e && console.error ) {
41                                 console.error( String( e ), e );
42                         }
43                 }
44         }
46         /* Object constructors */
48         /**
49          * Creates an object that can be read from or written to from prototype functions
50          * that allow both single and multiple variables at once.
51          *
52          *     @example
53          *
54          *     var addies, wanted, results;
55          *
56          *     // Create your address book
57          *     addies = new mw.Map();
58          *
59          *     // This data could be coming from an external source (eg. API/AJAX)
60          *     addies.set( {
61          *         'John Doe' : '10 Wall Street, New York, USA',
62          *         'Jane Jackson' : '21 Oxford St, London, UK',
63          *         'Dominique van Halen' : 'Kalverstraat 7, Amsterdam, NL'
64          *     } );
65          *
66          *     wanted = ['Dominique van Halen', 'George Johnson', 'Jane Jackson'];
67          *
68          *     // You can detect missing keys first
69          *     if ( !addies.exists( wanted ) ) {
70          *         // One or more are missing (in this case: "George Johnson")
71          *         mw.log( 'One or more names were not found in your address book' );
72          *     }
73          *
74          *     // Or just let it give you what it can
75          *     results = addies.get( wanted, 'Middle of Nowhere, Alaska, US' );
76          *     mw.log( results['Jane Jackson'] ); // "21 Oxford St, London, UK"
77          *     mw.log( results['George Johnson'] ); // "Middle of Nowhere, Alaska, US"
78          *
79          * @class mw.Map
80          *
81          * @constructor
82          * @param {Object|boolean} [values] Value-bearing object to map, defaults to an empty object.
83          *  For backwards-compatibility with mw.config, this can also be `true` in which case values
84          *  will be copied to the Window object as global variables (T72470). Values are copied in one
85          *  direction only. Changes to globals are not reflected in the map.
86          */
87         function Map( values ) {
88                 if ( values === true ) {
89                         this.values = {};
91                         // Override #set to also set the global variable
92                         this.set = function ( selection, value ) {
93                                 var s;
95                                 if ( $.isPlainObject( selection ) ) {
96                                         for ( s in selection ) {
97                                                 setGlobalMapValue( this, s, selection[s] );
98                                         }
99                                         return true;
100                                 }
101                                 if ( typeof selection === 'string' && arguments.length ) {
102                                         setGlobalMapValue( this, selection, value );
103                                         return true;
104                                 }
105                                 return false;
106                         };
108                         return;
109                 }
111                 this.values = values || {};
112         }
114         /**
115          * Alias property to the global object.
116          *
117          * @private
118          * @static
119          * @param {mw.Map} map
120          * @param {string} key
121          * @param {Mixed} value
122          */
123         function setGlobalMapValue( map, key, value ) {
124                 map.values[key] = value;
125                 mw.log.deprecate(
126                         window,
127                         key,
128                         value,
129                         // Deprecation notice for mw.config globals (T58550, T72470)
130                         map === mw.config && 'Use mw.config instead.'
131                 );
132         }
134         Map.prototype = {
135                 /**
136                  * Get the value of one or multiple keys.
137                  *
138                  * If called with no arguments, all values will be returned.
139                  *
140                  * @param {string|Array} selection String key or array of keys to get values for.
141                  * @param {Mixed} [fallback] Value to use in case key(s) do not exist.
142                  * @return mixed If selection was a string returns the value or null,
143                  *  If selection was an array, returns an object of key/values (value is null if not found),
144                  *  If selection was not passed or invalid, will return the 'values' object member (be careful as
145                  *  objects are always passed by reference in JavaScript!).
146                  * @return {string|Object|null} Values as a string or object, null if invalid/inexistent.
147                  */
148                 get: function ( selection, fallback ) {
149                         var results, i;
150                         // If we only do this in the `return` block, it'll fail for the
151                         // call to get() from the mutli-selection block.
152                         fallback = arguments.length > 1 ? fallback : null;
154                         if ( $.isArray( selection ) ) {
155                                 selection = slice.call( selection );
156                                 results = {};
157                                 for ( i = 0; i < selection.length; i++ ) {
158                                         results[selection[i]] = this.get( selection[i], fallback );
159                                 }
160                                 return results;
161                         }
163                         if ( typeof selection === 'string' ) {
164                                 if ( !hasOwn.call( this.values, selection ) ) {
165                                         return fallback;
166                                 }
167                                 return this.values[selection];
168                         }
170                         if ( selection === undefined ) {
171                                 return this.values;
172                         }
174                         // invalid selection key
175                         return null;
176                 },
178                 /**
179                  * Sets one or multiple key/value pairs.
180                  *
181                  * @param {string|Object} selection String key to set value for, or object mapping keys to values.
182                  * @param {Mixed} [value] Value to set (optional, only in use when key is a string)
183                  * @return {boolean} This returns true on success, false on failure.
184                  */
185                 set: function ( selection, value ) {
186                         var s;
188                         if ( $.isPlainObject( selection ) ) {
189                                 for ( s in selection ) {
190                                         this.values[s] = selection[s];
191                                 }
192                                 return true;
193                         }
194                         if ( typeof selection === 'string' && arguments.length ) {
195                                 this.values[selection] = value;
196                                 return true;
197                         }
198                         return false;
199                 },
201                 /**
202                  * Checks if one or multiple keys exist.
203                  *
204                  * @param {Mixed} selection String key or array of keys to check
205                  * @return {boolean} Existence of key(s)
206                  */
207                 exists: function ( selection ) {
208                         var s;
210                         if ( $.isArray( selection ) ) {
211                                 for ( s = 0; s < selection.length; s++ ) {
212                                         if ( typeof selection[s] !== 'string' || !hasOwn.call( this.values, selection[s] ) ) {
213                                                 return false;
214                                         }
215                                 }
216                                 return true;
217                         }
218                         return typeof selection === 'string' && hasOwn.call( this.values, selection );
219                 }
220         };
222         /**
223          * Object constructor for messages.
224          *
225          * Similar to the Message class in MediaWiki PHP.
226          *
227          * Format defaults to 'text'.
228          *
229          *     @example
230          *
231          *     var obj, str;
232          *     mw.messages.set( {
233          *         'hello': 'Hello world',
234          *         'hello-user': 'Hello, $1!',
235          *         'welcome-user': 'Welcome back to $2, $1! Last visit by $1: $3'
236          *     } );
237          *
238          *     obj = new mw.Message( mw.messages, 'hello' );
239          *     mw.log( obj.text() );
240          *     // Hello world
241          *
242          *     obj = new mw.Message( mw.messages, 'hello-user', [ 'John Doe' ] );
243          *     mw.log( obj.text() );
244          *     // Hello, John Doe!
245          *
246          *     obj = new mw.Message( mw.messages, 'welcome-user', [ 'John Doe', 'Wikipedia', '2 hours ago' ] );
247          *     mw.log( obj.text() );
248          *     // Welcome back to Wikipedia, John Doe! Last visit by John Doe: 2 hours ago
249          *
250          *     // Using mw.message shortcut
251          *     obj = mw.message( 'hello-user', 'John Doe' );
252          *     mw.log( obj.text() );
253          *     // Hello, John Doe!
254          *
255          *     // Using mw.msg shortcut
256          *     str = mw.msg( 'hello-user', 'John Doe' );
257          *     mw.log( str );
258          *     // Hello, John Doe!
259          *
260          *     // Different formats
261          *     obj = new mw.Message( mw.messages, 'hello-user', [ 'John "Wiki" <3 Doe' ] );
262          *
263          *     obj.format = 'text';
264          *     str = obj.toString();
265          *     // Same as:
266          *     str = obj.text();
267          *
268          *     mw.log( str );
269          *     // Hello, John "Wiki" <3 Doe!
270          *
271          *     mw.log( obj.escaped() );
272          *     // Hello, John &quot;Wiki&quot; &lt;3 Doe!
273          *
274          * @class mw.Message
275          *
276          * @constructor
277          * @param {mw.Map} map Message storage
278          * @param {string} key
279          * @param {Array} [parameters]
280          */
281         function Message( map, key, parameters ) {
282                 this.format = 'text';
283                 this.map = map;
284                 this.key = key;
285                 this.parameters = parameters === undefined ? [] : slice.call( parameters );
286                 return this;
287         }
289         Message.prototype = {
290                 /**
291                  * Simple message parser, does $N replacement and nothing else.
292                  *
293                  * This may be overridden to provide a more complex message parser.
294                  *
295                  * The primary override is in mediawiki.jqueryMsg.
296                  *
297                  * This function will not be called for nonexistent messages.
298                  */
299                 parser: function () {
300                         var parameters = this.parameters;
301                         return this.map.get( this.key ).replace( /\$(\d+)/g, function ( str, match ) {
302                                 var index = parseInt( match, 10 ) - 1;
303                                 return parameters[index] !== undefined ? parameters[index] : '$' + match;
304                         } );
305                 },
307                 /**
308                  * Appends (does not replace) parameters for replacement to the .parameters property.
309                  *
310                  * @param {Array} parameters
311                  * @chainable
312                  */
313                 params: function ( parameters ) {
314                         var i;
315                         for ( i = 0; i < parameters.length; i += 1 ) {
316                                 this.parameters.push( parameters[i] );
317                         }
318                         return this;
319                 },
321                 /**
322                  * Converts message object to its string form based on the state of format.
323                  *
324                  * @return {string} Message as a string in the current form or `<key>` if key does not exist.
325                  */
326                 toString: function () {
327                         var text;
329                         if ( !this.exists() ) {
330                                 // Use <key> as text if key does not exist
331                                 if ( this.format === 'escaped' || this.format === 'parse' ) {
332                                         // format 'escaped' and 'parse' need to have the brackets and key html escaped
333                                         return mw.html.escape( '<' + this.key + '>' );
334                                 }
335                                 return '<' + this.key + '>';
336                         }
338                         if ( this.format === 'plain' || this.format === 'text' || this.format === 'parse' ) {
339                                 text = this.parser();
340                         }
342                         if ( this.format === 'escaped' ) {
343                                 text = this.parser();
344                                 text = mw.html.escape( text );
345                         }
347                         return text;
348                 },
350                 /**
351                  * Changes format to 'parse' and converts message to string
352                  *
353                  * If jqueryMsg is loaded, this parses the message text from wikitext
354                  * (where supported) to HTML
355                  *
356                  * Otherwise, it is equivalent to plain.
357                  *
358                  * @return {string} String form of parsed message
359                  */
360                 parse: function () {
361                         this.format = 'parse';
362                         return this.toString();
363                 },
365                 /**
366                  * Changes format to 'plain' and converts message to string
367                  *
368                  * This substitutes parameters, but otherwise does not change the
369                  * message text.
370                  *
371                  * @return {string} String form of plain message
372                  */
373                 plain: function () {
374                         this.format = 'plain';
375                         return this.toString();
376                 },
378                 /**
379                  * Changes format to 'text' and converts message to string
380                  *
381                  * If jqueryMsg is loaded, {{-transformation is done where supported
382                  * (such as {{plural:}}, {{gender:}}, {{int:}}).
383                  *
384                  * Otherwise, it is equivalent to plain.
385                  */
386                 text: function () {
387                         this.format = 'text';
388                         return this.toString();
389                 },
391                 /**
392                  * Changes the format to 'escaped' and converts message to string
393                  *
394                  * This is equivalent to using the 'text' format (see text method), then
395                  * HTML-escaping the output.
396                  *
397                  * @return {string} String form of html escaped message
398                  */
399                 escaped: function () {
400                         this.format = 'escaped';
401                         return this.toString();
402                 },
404                 /**
405                  * Checks if message exists
406                  *
407                  * @see mw.Map#exists
408                  * @return {boolean}
409                  */
410                 exists: function () {
411                         return this.map.exists( this.key );
412                 }
413         };
415         /**
416          * @class mw
417          */
418         mw = {
419                 /* Public Members */
421                 /**
422                  * Get the current time, measured in milliseconds since January 1, 1970 (UTC).
423                  *
424                  * On browsers that implement the Navigation Timing API, this function will produce floating-point
425                  * values with microsecond precision that are guaranteed to be monotonic. On all other browsers,
426                  * it will fall back to using `Date`.
427                  *
428                  * @return {number} Current time
429                  */
430                 now: ( function () {
431                         var perf = window.performance,
432                                 navStart = perf && perf.timing && perf.timing.navigationStart;
433                         return navStart && typeof perf.now === 'function' ?
434                                 function () { return navStart + perf.now(); } :
435                                 function () { return +new Date(); };
436                 }() ),
438                 /**
439                  * Track an analytic event.
440                  *
441                  * This method provides a generic means for MediaWiki JavaScript code to capture state
442                  * information for analysis. Each logged event specifies a string topic name that describes
443                  * the kind of event that it is. Topic names consist of dot-separated path components,
444                  * arranged from most general to most specific. Each path component should have a clear and
445                  * well-defined purpose.
446                  *
447                  * Data handlers are registered via `mw.trackSubscribe`, and receive the full set of
448                  * events that match their subcription, including those that fired before the handler was
449                  * bound.
450                  *
451                  * @param {string} topic Topic name
452                  * @param {Object} [data] Data describing the event, encoded as an object
453                  */
454                 track: function ( topic, data ) {
455                         trackQueue.push( { topic: topic, timeStamp: mw.now(), data: data } );
456                         trackCallbacks.fire( trackQueue );
457                 },
459                 /**
460                  * Register a handler for subset of analytic events, specified by topic
461                  *
462                  * Handlers will be called once for each tracked event, including any events that fired before the
463                  * handler was registered; 'this' is set to a plain object with a 'timeStamp' property indicating
464                  * the exact time at which the event fired, a string 'topic' property naming the event, and a
465                  * 'data' property which is an object of event-specific data. The event topic and event data are
466                  * also passed to the callback as the first and second arguments, respectively.
467                  *
468                  * @param {string} topic Handle events whose name starts with this string prefix
469                  * @param {Function} callback Handler to call for each matching tracked event
470                  */
471                 trackSubscribe: function ( topic, callback ) {
472                         var seen = 0;
474                         trackCallbacks.add( function ( trackQueue ) {
475                                 var event;
476                                 for ( ; seen < trackQueue.length; seen++ ) {
477                                         event = trackQueue[ seen ];
478                                         if ( event.topic.indexOf( topic ) === 0 ) {
479                                                 callback.call( event, event.topic, event.data );
480                                         }
481                                 }
482                         } );
483                 },
485                 // Make the Map constructor publicly available.
486                 Map: Map,
488                 // Make the Message constructor publicly available.
489                 Message: Message,
491                 /**
492                  * Map of configuration values
493                  *
494                  * Check out [the complete list of configuration values](https://www.mediawiki.org/wiki/Manual:Interface/JavaScript#mw.config)
495                  * on mediawiki.org.
496                  *
497                  * If `$wgLegacyJavaScriptGlobals` is true, this Map will add its values to the
498                  * global `window` object.
499                  *
500                  * @property {mw.Map} config
501                  */
502                 // Dummy placeholder. Re-assigned in ResourceLoaderStartupModule to an instance of `mw.Map`.
503                 config: null,
505                 /**
506                  * Empty object that plugins can be installed in.
507                  * @property
508                  */
509                 libs: {},
511                 /**
512                  * Access container for deprecated functionality that can be moved from
513                  * from their legacy location and attached to this object (e.g. a global
514                  * function that is deprecated and as stop-gap can be exposed through here).
515                  *
516                  * This was reserved for future use but never ended up being used.
517                  *
518                  * @deprecated since 1.22 Let deprecated identifiers keep their original name
519                  *  and use mw.log#deprecate to create an access container for tracking.
520                  * @property
521                  */
522                 legacy: {},
524                 /**
525                  * Localization system
526                  * @property {mw.Map}
527                  */
528                 messages: new Map(),
530                 /**
531                  * Templates associated with a module
532                  * @property {mw.Map}
533                  */
534                 templates: new Map(),
536                 /* Public Methods */
538                 /**
539                  * Get a message object.
540                  *
541                  * Shorcut for `new mw.Message( mw.messages, key, parameters )`.
542                  *
543                  * @see mw.Message
544                  * @param {string} key Key of message to get
545                  * @param {Mixed...} parameters Parameters for the $N replacements in messages.
546                  * @return {mw.Message}
547                  */
548                 message: function ( key ) {
549                         // Variadic arguments
550                         var parameters = slice.call( arguments, 1 );
551                         return new Message( mw.messages, key, parameters );
552                 },
554                 /**
555                  * Get a message string using the (default) 'text' format.
556                  *
557                  * Shortcut for `mw.message( key, parameters... ).text()`.
558                  *
559                  * @see mw.Message
560                  * @param {string} key Key of message to get
561                  * @param {Mixed...} parameters Parameters for the $N replacements in messages.
562                  * @return {string}
563                  */
564                 msg: function () {
565                         return mw.message.apply( mw.message, arguments ).toString();
566                 },
568                 /**
569                  * Dummy placeholder for {@link mw.log}
570                  * @method
571                  */
572                 log: ( function () {
573                         // Also update the restoration of methods in mediawiki.log.js
574                         // when adding or removing methods here.
575                         var log = function () {};
577                         /**
578                          * @class mw.log
579                          * @singleton
580                          */
582                         /**
583                          * Write a message the console's warning channel.
584                          * Also logs a stacktrace for easier debugging.
585                          * Each action is silently ignored if the browser doesn't support it.
586                          *
587                          * @param {string...} msg Messages to output to console
588                          */
589                         log.warn = function () {
590                                 var console = window.console;
591                                 if ( console && console.warn && console.warn.apply ) {
592                                         console.warn.apply( console, arguments );
593                                         if ( console.trace ) {
594                                                 console.trace();
595                                         }
596                                 }
597                         };
599                         /**
600                          * Create a property in a host object that, when accessed, will produce
601                          * a deprecation warning in the console with backtrace.
602                          *
603                          * @param {Object} obj Host object of deprecated property
604                          * @param {string} key Name of property to create in `obj`
605                          * @param {Mixed} val The value this property should return when accessed
606                          * @param {string} [msg] Optional text to include in the deprecation message.
607                          */
608                         log.deprecate = !Object.defineProperty ? function ( obj, key, val ) {
609                                 obj[key] = val;
610                         } : function ( obj, key, val, msg ) {
611                                 msg = 'Use of "' + key + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' );
612                                 try {
613                                         Object.defineProperty( obj, key, {
614                                                 configurable: true,
615                                                 enumerable: true,
616                                                 get: function () {
617                                                         mw.track( 'mw.deprecate', key );
618                                                         mw.log.warn( msg );
619                                                         return val;
620                                                 },
621                                                 set: function ( newVal ) {
622                                                         mw.track( 'mw.deprecate', key );
623                                                         mw.log.warn( msg );
624                                                         val = newVal;
625                                                 }
626                                         } );
627                                 } catch ( err ) {
628                                         // IE8 can throw on Object.defineProperty
629                                         // Create a copy of the value to the object.
630                                         obj[key] = val;
631                                 }
632                         };
634                         return log;
635                 }() ),
637                 /**
638                  * Client-side module loader which integrates with the MediaWiki ResourceLoader
639                  * @class mw.loader
640                  * @singleton
641                  */
642                 loader: ( function () {
644                         /* Private Members */
646                         /**
647                          * Mapping of registered modules
648                          *
649                          * The jquery module is pre-registered, because it must have already
650                          * been provided for this object to have been built, and in debug mode
651                          * jquery would have been provided through a unique loader request,
652                          * making it impossible to hold back registration of jquery until after
653                          * mediawiki.
654                          *
655                          * For exact details on support for script, style and messages, look at
656                          * mw.loader.implement.
657                          *
658                          * Format:
659                          *     {
660                          *         'moduleName': {
661                          *             // At registry
662                          *             'version': ############## (unix timestamp),
663                          *             'dependencies': ['required.foo', 'bar.also', ...], (or) function () {}
664                          *             'group': 'somegroup', (or) null,
665                          *             'source': 'local', 'someforeignwiki', (or) null
666                          *             'state': 'registered', 'loaded', 'loading', 'ready', 'error' or 'missing'
667                          *             'skip': 'return !!window.Example', (or) null
668                          *
669                          *             // Added during implementation
670                          *             'skipped': true,
671                          *             'script': ...,
672                          *             'style': ...,
673                          *             'messages': { 'key': 'value' },
674                          *         }
675                          *     }
676                          *
677                          * @property
678                          * @private
679                          */
680                         var registry = {},
681                                 //
682                                 // Mapping of sources, keyed by source-id, values are strings.
683                                 // Format:
684                                 //      {
685                                 //              'sourceId': 'http://foo.bar/w/load.php'
686                                 //      }
687                                 //
688                                 sources = {},
689                                 // List of modules which will be loaded as when ready
690                                 batch = [],
691                                 // List of modules to be loaded
692                                 queue = [],
693                                 // List of callback functions waiting for modules to be ready to be called
694                                 jobs = [],
695                                 // Selector cache for the marker element. Use getMarker() to get/use the marker!
696                                 $marker = null,
697                                 // Buffer for addEmbeddedCSS.
698                                 cssBuffer = '',
699                                 // Callbacks for addEmbeddedCSS.
700                                 cssCallbacks = $.Callbacks();
702                         /* Private methods */
704                         function getMarker() {
705                                 // Cached
706                                 if ( !$marker ) {
707                                         $marker = $( 'meta[name="ResourceLoaderDynamicStyles"]' );
708                                         if ( !$marker.length ) {
709                                                 mw.log( 'No <meta name="ResourceLoaderDynamicStyles"> found, inserting dynamically' );
710                                                 $marker = $( '<meta>' ).attr( 'name', 'ResourceLoaderDynamicStyles' ).appendTo( 'head' );
711                                         }
712                                 }
713                                 return $marker;
714                         }
716                         /**
717                          * Create a new style tag and add it to the DOM.
718                          *
719                          * @private
720                          * @param {string} text CSS text
721                          * @param {HTMLElement|jQuery} [nextnode=document.head] The element where the style tag should be
722                          *  inserted before. Otherwise it will be appended to `<head>`.
723                          * @return {HTMLElement} Reference to the created `<style>` element.
724                          */
725                         function newStyleTag( text, nextnode ) {
726                                 var s = document.createElement( 'style' );
727                                 // Insert into document before setting cssText (bug 33305)
728                                 if ( nextnode ) {
729                                         // Must be inserted with native insertBefore, not $.fn.before.
730                                         // When using jQuery to insert it, like $nextnode.before( s ),
731                                         // then IE6 will throw "Access is denied" when trying to append
732                                         // to .cssText later. Some kind of weird security measure.
733                                         // http://stackoverflow.com/q/12586482/319266
734                                         // Works: jsfiddle.net/zJzMy/1
735                                         // Fails: jsfiddle.net/uJTQz
736                                         // Works again: http://jsfiddle.net/Azr4w/ (diff: the next 3 lines)
737                                         if ( nextnode.jquery ) {
738                                                 nextnode = nextnode.get( 0 );
739                                         }
740                                         nextnode.parentNode.insertBefore( s, nextnode );
741                                 } else {
742                                         document.getElementsByTagName( 'head' )[0].appendChild( s );
743                                 }
744                                 if ( s.styleSheet ) {
745                                         // IE
746                                         s.styleSheet.cssText = text;
747                                 } else {
748                                         // Other browsers.
749                                         // (Safari sometimes borks on non-string values,
750                                         // play safe by casting to a string, just in case.)
751                                         s.appendChild( document.createTextNode( String( text ) ) );
752                                 }
753                                 return s;
754                         }
756                         /**
757                          * Checks whether it is safe to add this css to a stylesheet.
758                          *
759                          * @private
760                          * @param {string} cssText
761                          * @return {boolean} False if a new one must be created.
762                          */
763                         function canExpandStylesheetWith( cssText ) {
764                                 // Makes sure that cssText containing `@import`
765                                 // rules will end up in a new stylesheet (as those only work when
766                                 // placed at the start of a stylesheet; bug 35562).
767                                 return cssText.indexOf( '@import' ) === -1;
768                         }
770                         /**
771                          * Add a bit of CSS text to the current browser page.
772                          *
773                          * The CSS will be appended to an existing ResourceLoader-created `<style>` tag
774                          * or create a new one based on whether the given `cssText` is safe for extension.
775                          *
776                          * @param {string} [cssText=cssBuffer] If called without cssText,
777                          *  the internal buffer will be inserted instead.
778                          * @param {Function} [callback]
779                          */
780                         function addEmbeddedCSS( cssText, callback ) {
781                                 var $style, styleEl;
783                                 if ( callback ) {
784                                         cssCallbacks.add( callback );
785                                 }
787                                 // Yield once before inserting the <style> tag. There are likely
788                                 // more calls coming up which we can combine this way.
789                                 // Appending a stylesheet and waiting for the browser to repaint
790                                 // is fairly expensive, this reduces it (bug 45810)
791                                 if ( cssText ) {
792                                         // Be careful not to extend the buffer with css that needs a new stylesheet
793                                         if ( !cssBuffer || canExpandStylesheetWith( cssText ) ) {
794                                                 // Linebreak for somewhat distinguishable sections
795                                                 // (the rl-cachekey comment separating each)
796                                                 cssBuffer += '\n' + cssText;
797                                                 // TODO: Use requestAnimationFrame in the future which will
798                                                 // perform even better by not injecting styles while the browser
799                                                 // is paiting.
800                                                 setTimeout( function () {
801                                                         // Can't pass addEmbeddedCSS to setTimeout directly because Firefox
802                                                         // (below version 13) has the non-standard behaviour of passing a
803                                                         // numerical "lateness" value as first argument to this callback
804                                                         // http://benalman.com/news/2009/07/the-mysterious-firefox-settime/
805                                                         addEmbeddedCSS();
806                                                 } );
807                                                 return;
808                                         }
810                                 // This is a delayed call and we got a buffer still
811                                 } else if ( cssBuffer ) {
812                                         cssText = cssBuffer;
813                                         cssBuffer = '';
814                                 } else {
815                                         // This is a delayed call, but buffer is already cleared by
816                                         // another delayed call.
817                                         return;
818                                 }
820                                 // By default, always create a new <style>. Appending text to a <style>
821                                 // tag is bad as it means the contents have to be re-parsed (bug 45810).
822                                 //
823                                 // Except, of course, in IE 9 and below. In there we default to re-using and
824                                 // appending to a <style> tag due to the IE stylesheet limit (bug 31676).
825                                 if ( 'documentMode' in document && document.documentMode <= 9 ) {
827                                         $style = getMarker().prev();
828                                         // Verify that the element before Marker actually is a
829                                         // <style> tag and one that came from ResourceLoader
830                                         // (not some other style tag or even a `<meta>` or `<script>`).
831                                         if ( $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) {
832                                                 // There's already a dynamic <style> tag present and
833                                                 // canExpandStylesheetWith() gave a green light to append more to it.
834                                                 styleEl = $style.get( 0 );
835                                                 if ( styleEl.styleSheet ) {
836                                                         try {
837                                                                 styleEl.styleSheet.cssText += cssText; // IE
838                                                         } catch ( e ) {
839                                                                 log( 'Stylesheet error', e );
840                                                         }
841                                                 } else {
842                                                         styleEl.appendChild( document.createTextNode( String( cssText ) ) );
843                                                 }
844                                                 cssCallbacks.fire().empty();
845                                                 return;
846                                         }
847                                 }
849                                 $( newStyleTag( cssText, getMarker() ) ).data( 'ResourceLoaderDynamicStyleTag', true );
851                                 cssCallbacks.fire().empty();
852                         }
854                         /**
855                          * Convert UNIX timestamp to ISO8601 format
856                          * @param {number} timestamp UNIX timestamp
857                          * @private
858                          */
859                         function formatVersionNumber( timestamp ) {
860                                 var     d = new Date();
861                                 function pad( a, b, c ) {
862                                         return [a < 10 ? '0' + a : a, b < 10 ? '0' + b : b, c < 10 ? '0' + c : c].join( '' );
863                                 }
864                                 d.setTime( timestamp * 1000 );
865                                 return [
866                                         pad( d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate() ), 'T',
867                                         pad( d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ), 'Z'
868                                 ].join( '' );
869                         }
871                         /**
872                          * Resolves dependencies and detects circular references.
873                          *
874                          * @private
875                          * @param {string} module Name of the top-level module whose dependencies shall be
876                          *   resolved and sorted.
877                          * @param {Array} resolved Returns a topological sort of the given module and its
878                          *   dependencies, such that later modules depend on earlier modules. The array
879                          *   contains the module names. If the array contains already some module names,
880                          *   this function appends its result to the pre-existing array.
881                          * @param {Object} [unresolved] Hash used to track the current dependency
882                          *   chain; used to report loops in the dependency graph.
883                          * @throws {Error} If any unregistered module or a dependency loop is encountered
884                          */
885                         function sortDependencies( module, resolved, unresolved ) {
886                                 var n, deps, len, skip;
888                                 if ( !hasOwn.call( registry, module ) ) {
889                                         throw new Error( 'Unknown dependency: ' + module );
890                                 }
892                                 if ( registry[module].skip !== null ) {
893                                         /*jshint evil:true */
894                                         skip = new Function( registry[module].skip );
895                                         registry[module].skip = null;
896                                         if ( skip() ) {
897                                                 registry[module].skipped = true;
898                                                 registry[module].dependencies = [];
899                                                 registry[module].state = 'ready';
900                                                 handlePending( module );
901                                                 return;
902                                         }
903                                 }
905                                 // Resolves dynamic loader function and replaces it with its own results
906                                 if ( $.isFunction( registry[module].dependencies ) ) {
907                                         registry[module].dependencies = registry[module].dependencies();
908                                         // Ensures the module's dependencies are always in an array
909                                         if ( typeof registry[module].dependencies !== 'object' ) {
910                                                 registry[module].dependencies = [registry[module].dependencies];
911                                         }
912                                 }
913                                 if ( $.inArray( module, resolved ) !== -1 ) {
914                                         // Module already resolved; nothing to do.
915                                         return;
916                                 }
917                                 // unresolved is optional, supply it if not passed in
918                                 if ( !unresolved ) {
919                                         unresolved = {};
920                                 }
921                                 // Tracks down dependencies
922                                 deps = registry[module].dependencies;
923                                 len = deps.length;
924                                 for ( n = 0; n < len; n += 1 ) {
925                                         if ( $.inArray( deps[n], resolved ) === -1 ) {
926                                                 if ( unresolved[deps[n]] ) {
927                                                         throw new Error(
928                                                                 'Circular reference detected: ' + module +
929                                                                 ' -> ' + deps[n]
930                                                         );
931                                                 }
933                                                 // Add to unresolved
934                                                 unresolved[module] = true;
935                                                 sortDependencies( deps[n], resolved, unresolved );
936                                                 delete unresolved[module];
937                                         }
938                                 }
939                                 resolved[resolved.length] = module;
940                         }
942                         /**
943                          * Gets a list of module names that a module depends on in their proper dependency
944                          * order.
945                          *
946                          * @private
947                          * @param {string} module Module name or array of string module names
948                          * @return {Array} list of dependencies, including 'module'.
949                          * @throws {Error} If circular reference is detected
950                          */
951                         function resolve( module ) {
952                                 var m, resolved;
954                                 // Allow calling with an array of module names
955                                 if ( $.isArray( module ) ) {
956                                         resolved = [];
957                                         for ( m = 0; m < module.length; m += 1 ) {
958                                                 sortDependencies( module[m], resolved );
959                                         }
960                                         return resolved;
961                                 }
963                                 if ( typeof module === 'string' ) {
964                                         resolved = [];
965                                         sortDependencies( module, resolved );
966                                         return resolved;
967                                 }
969                                 throw new Error( 'Invalid module argument: ' + module );
970                         }
972                         /**
973                          * Narrows a list of module names down to those matching a specific
974                          * state (see comment on top of this scope for a list of valid states).
975                          * One can also filter for 'unregistered', which will return the
976                          * modules names that don't have a registry entry.
977                          *
978                          * @private
979                          * @param {string|string[]} states Module states to filter by
980                          * @param {Array} [modules] List of module names to filter (optional, by default the entire
981                          * registry is used)
982                          * @return {Array} List of filtered module names
983                          */
984                         function filter( states, modules ) {
985                                 var list, module, s, m;
987                                 // Allow states to be given as a string
988                                 if ( typeof states === 'string' ) {
989                                         states = [states];
990                                 }
991                                 // If called without a list of modules, build and use a list of all modules
992                                 list = [];
993                                 if ( modules === undefined ) {
994                                         modules = [];
995                                         for ( module in registry ) {
996                                                 modules[modules.length] = module;
997                                         }
998                                 }
999                                 // Build a list of modules which are in one of the specified states
1000                                 for ( s = 0; s < states.length; s += 1 ) {
1001                                         for ( m = 0; m < modules.length; m += 1 ) {
1002                                                 if ( !hasOwn.call( registry, modules[m] ) ) {
1003                                                         // Module does not exist
1004                                                         if ( states[s] === 'unregistered' ) {
1005                                                                 // OK, undefined
1006                                                                 list[list.length] = modules[m];
1007                                                         }
1008                                                 } else {
1009                                                         // Module exists, check state
1010                                                         if ( registry[modules[m]].state === states[s] ) {
1011                                                                 // OK, correct state
1012                                                                 list[list.length] = modules[m];
1013                                                         }
1014                                                 }
1015                                         }
1016                                 }
1017                                 return list;
1018                         }
1020                         /**
1021                          * Determine whether all dependencies are in state 'ready', which means we may
1022                          * execute the module or job now.
1023                          *
1024                          * @private
1025                          * @param {Array} dependencies Dependencies (module names) to be checked.
1026                          * @return {boolean} True if all dependencies are in state 'ready', false otherwise
1027                          */
1028                         function allReady( dependencies ) {
1029                                 return filter( 'ready', dependencies ).length === dependencies.length;
1030                         }
1032                         /**
1033                          * A module has entered state 'ready', 'error', or 'missing'. Automatically update pending jobs
1034                          * and modules that depend upon this module. if the given module failed, propagate the 'error'
1035                          * state up the dependency tree; otherwise, execute all jobs/modules that now have all their
1036                          * dependencies satisfied. On jobs depending on a failed module, run the error callback, if any.
1037                          *
1038                          * @private
1039                          * @param {string} module Name of module that entered one of the states 'ready', 'error', or 'missing'.
1040                          */
1041                         function handlePending( module ) {
1042                                 var j, job, hasErrors, m, stateChange;
1044                                 // Modules.
1045                                 if ( $.inArray( registry[module].state, ['error', 'missing'] ) !== -1 ) {
1046                                         // If the current module failed, mark all dependent modules also as failed.
1047                                         // Iterate until steady-state to propagate the error state upwards in the
1048                                         // dependency tree.
1049                                         do {
1050                                                 stateChange = false;
1051                                                 for ( m in registry ) {
1052                                                         if ( $.inArray( registry[m].state, ['error', 'missing'] ) === -1 ) {
1053                                                                 if ( filter( ['error', 'missing'], registry[m].dependencies ).length > 0 ) {
1054                                                                         registry[m].state = 'error';
1055                                                                         stateChange = true;
1056                                                                 }
1057                                                         }
1058                                                 }
1059                                         } while ( stateChange );
1060                                 }
1062                                 // Execute all jobs whose dependencies are either all satisfied or contain at least one failed module.
1063                                 for ( j = 0; j < jobs.length; j += 1 ) {
1064                                         hasErrors = filter( ['error', 'missing'], jobs[j].dependencies ).length > 0;
1065                                         if ( hasErrors || allReady( jobs[j].dependencies ) ) {
1066                                                 // All dependencies satisfied, or some have errors
1067                                                 job = jobs[j];
1068                                                 jobs.splice( j, 1 );
1069                                                 j -= 1;
1070                                                 try {
1071                                                         if ( hasErrors ) {
1072                                                                 if ( $.isFunction( job.error ) ) {
1073                                                                         job.error( new Error( 'Module ' + module + ' has failed dependencies' ), [module] );
1074                                                                 }
1075                                                         } else {
1076                                                                 if ( $.isFunction( job.ready ) ) {
1077                                                                         job.ready();
1078                                                                 }
1079                                                         }
1080                                                 } catch ( e ) {
1081                                                         // A user-defined callback raised an exception.
1082                                                         // Swallow it to protect our state machine!
1083                                                         log( 'Exception thrown by user callback', e );
1084                                                 }
1085                                         }
1086                                 }
1088                                 if ( registry[module].state === 'ready' ) {
1089                                         // The current module became 'ready'. Set it in the module store, and recursively execute all
1090                                         // dependent modules that are loaded and now have all dependencies satisfied.
1091                                         mw.loader.store.set( module, registry[module] );
1092                                         for ( m in registry ) {
1093                                                 if ( registry[m].state === 'loaded' && allReady( registry[m].dependencies ) ) {
1094                                                         execute( m );
1095                                                 }
1096                                         }
1097                                 }
1098                         }
1100                         /**
1101                          * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
1102                          * depending on whether document-ready has occurred yet and whether we are in async mode.
1103                          *
1104                          * @private
1105                          * @param {string} src URL to script, will be used as the src attribute in the script tag
1106                          * @param {Function} [callback] Callback which will be run when the script is done
1107                          * @param {boolean} [async=false] Whether to load modules asynchronously.
1108                          *  Ignored (and defaulted to `true`) if the document-ready event has already occurred.
1109                          */
1110                         function addScript( src, callback, async ) {
1111                                 // Using isReady directly instead of storing it locally from a $().ready callback (bug 31895)
1112                                 if ( $.isReady || async ) {
1113                                         $.ajax( {
1114                                                 url: src,
1115                                                 dataType: 'script',
1116                                                 // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use
1117                                                 // XHR for a same domain request instead of <script>, which changes the request
1118                                                 // headers (potentially missing a cache hit), and reduces caching in general
1119                                                 // since browsers cache XHR much less (if at all). And XHR means we retreive
1120                                                 // text, so we'd need to $.globalEval, which then messes up line numbers.
1121                                                 crossDomain: true,
1122                                                 cache: true,
1123                                                 async: true
1124                                         } ).always( callback );
1125                                 } else {
1126                                         /*jshint evil:true */
1127                                         document.write( mw.html.element( 'script', { 'src': src }, '' ) );
1128                                         if ( callback ) {
1129                                                 // Document.write is synchronous, so this is called when it's done.
1130                                                 // FIXME: That's a lie. doc.write isn't actually synchronous.
1131                                                 callback();
1132                                         }
1133                                 }
1134                         }
1136                         /**
1137                          * Executes a loaded module, making it ready to use
1138                          *
1139                          * @private
1140                          * @param {string} module Module name to execute
1141                          */
1142                         function execute( module ) {
1143                                 var key, value, media, i, urls, cssHandle, checkCssHandles,
1144                                         cssHandlesRegistered = false;
1146                                 if ( !hasOwn.call( registry, module ) ) {
1147                                         throw new Error( 'Module has not been registered yet: ' + module );
1148                                 } else if ( registry[module].state === 'registered' ) {
1149                                         throw new Error( 'Module has not been requested from the server yet: ' + module );
1150                                 } else if ( registry[module].state === 'loading' ) {
1151                                         throw new Error( 'Module has not completed loading yet: ' + module );
1152                                 } else if ( registry[module].state === 'ready' ) {
1153                                         throw new Error( 'Module has already been executed: ' + module );
1154                                 }
1156                                 /**
1157                                  * Define loop-function here for efficiency
1158                                  * and to avoid re-using badly scoped variables.
1159                                  * @ignore
1160                                  */
1161                                 function addLink( media, url ) {
1162                                         var el = document.createElement( 'link' );
1163                                         // For IE: Insert in document *before* setting href
1164                                         getMarker().before( el );
1165                                         el.rel = 'stylesheet';
1166                                         if ( media && media !== 'all' ) {
1167                                                 el.media = media;
1168                                         }
1169                                         // If you end up here from an IE exception "SCRIPT: Invalid property value.",
1170                                         // see #addEmbeddedCSS, bug 31676, and bug 47277 for details.
1171                                         el.href = url;
1172                                 }
1174                                 function runScript() {
1175                                         var script, markModuleReady, nestedAddScript;
1176                                         try {
1177                                                 script = registry[module].script;
1178                                                 markModuleReady = function () {
1179                                                         registry[module].state = 'ready';
1180                                                         handlePending( module );
1181                                                 };
1182                                                 nestedAddScript = function ( arr, callback, async, i ) {
1183                                                         // Recursively call addScript() in its own callback
1184                                                         // for each element of arr.
1185                                                         if ( i >= arr.length ) {
1186                                                                 // We're at the end of the array
1187                                                                 callback();
1188                                                                 return;
1189                                                         }
1191                                                         addScript( arr[i], function () {
1192                                                                 nestedAddScript( arr, callback, async, i + 1 );
1193                                                         }, async );
1194                                                 };
1196                                                 if ( $.isArray( script ) ) {
1197                                                         nestedAddScript( script, markModuleReady, registry[module].async, 0 );
1198                                                 } else if ( $.isFunction( script ) ) {
1199                                                         registry[module].state = 'ready';
1200                                                         // Pass jQuery twice so that the signature of the closure which wraps
1201                                                         // the script can bind both '$' and 'jQuery'.
1202                                                         script( $, $ );
1203                                                         handlePending( module );
1204                                                 }
1205                                         } catch ( e ) {
1206                                                 // This needs to NOT use mw.log because these errors are common in production mode
1207                                                 // and not in debug mode, such as when a symbol that should be global isn't exported
1208                                                 log( 'Exception thrown by ' + module, e );
1209                                                 registry[module].state = 'error';
1210                                                 handlePending( module );
1211                                         }
1212                                 }
1214                                 // This used to be inside runScript, but since that is now fired asychronously
1215                                 // (after CSS is loaded) we need to set it here right away. It is crucial that
1216                                 // when execute() is called this is set synchronously, otherwise modules will get
1217                                 // executed multiple times as the registry will state that it isn't loading yet.
1218                                 registry[module].state = 'loading';
1220                                 // Add localizations to message system
1221                                 if ( $.isPlainObject( registry[module].messages ) ) {
1222                                         mw.messages.set( registry[module].messages );
1223                                 }
1225                                 // Initialise templates
1226                                 if ( registry[module].templates ) {
1227                                         mw.templates.set( module, registry[module].templates );
1228                                 }
1230                                 if ( $.isReady || registry[module].async ) {
1231                                         // Make sure we don't run the scripts until all (potentially asynchronous)
1232                                         // stylesheet insertions have completed.
1233                                         ( function () {
1234                                                 var pending = 0;
1235                                                 checkCssHandles = function () {
1236                                                         // cssHandlesRegistered ensures we don't take off too soon, e.g. when
1237                                                         // one of the cssHandles is fired while we're still creating more handles.
1238                                                         if ( cssHandlesRegistered && pending === 0 && runScript ) {
1239                                                                 runScript();
1240                                                                 runScript = undefined; // Revoke
1241                                                         }
1242                                                 };
1243                                                 cssHandle = function () {
1244                                                         var check = checkCssHandles;
1245                                                         pending++;
1246                                                         return function () {
1247                                                                 if ( check ) {
1248                                                                         pending--;
1249                                                                         check();
1250                                                                         check = undefined; // Revoke
1251                                                                 }
1252                                                         };
1253                                                 };
1254                                         }() );
1255                                 } else {
1256                                         // We are in blocking mode, and so we can't afford to wait for CSS
1257                                         cssHandle = function () {};
1258                                         // Run immediately
1259                                         checkCssHandles = runScript;
1260                                 }
1262                                 // Process styles (see also mw.loader.implement)
1263                                 // * back-compat: { <media>: css }
1264                                 // * back-compat: { <media>: [url, ..] }
1265                                 // * { "css": [css, ..] }
1266                                 // * { "url": { <media>: [url, ..] } }
1267                                 if ( $.isPlainObject( registry[module].style ) ) {
1268                                         for ( key in registry[module].style ) {
1269                                                 value = registry[module].style[key];
1270                                                 media = undefined;
1272                                                 if ( key !== 'url' && key !== 'css' ) {
1273                                                         // Backwards compatibility, key is a media-type
1274                                                         if ( typeof value === 'string' ) {
1275                                                                 // back-compat: { <media>: css }
1276                                                                 // Ignore 'media' because it isn't supported (nor was it used).
1277                                                                 // Strings are pre-wrapped in "@media". The media-type was just ""
1278                                                                 // (because it had to be set to something).
1279                                                                 // This is one of the reasons why this format is no longer used.
1280                                                                 addEmbeddedCSS( value, cssHandle() );
1281                                                         } else {
1282                                                                 // back-compat: { <media>: [url, ..] }
1283                                                                 media = key;
1284                                                                 key = 'bc-url';
1285                                                         }
1286                                                 }
1288                                                 // Array of css strings in key 'css',
1289                                                 // or back-compat array of urls from media-type
1290                                                 if ( $.isArray( value ) ) {
1291                                                         for ( i = 0; i < value.length; i += 1 ) {
1292                                                                 if ( key === 'bc-url' ) {
1293                                                                         // back-compat: { <media>: [url, ..] }
1294                                                                         addLink( media, value[i] );
1295                                                                 } else if ( key === 'css' ) {
1296                                                                         // { "css": [css, ..] }
1297                                                                         addEmbeddedCSS( value[i], cssHandle() );
1298                                                                 }
1299                                                         }
1300                                                 // Not an array, but a regular object
1301                                                 // Array of urls inside media-type key
1302                                                 } else if ( typeof value === 'object' ) {
1303                                                         // { "url": { <media>: [url, ..] } }
1304                                                         for ( media in value ) {
1305                                                                 urls = value[media];
1306                                                                 for ( i = 0; i < urls.length; i += 1 ) {
1307                                                                         addLink( media, urls[i] );
1308                                                                 }
1309                                                         }
1310                                                 }
1311                                         }
1312                                 }
1314                                 // Kick off.
1315                                 cssHandlesRegistered = true;
1316                                 checkCssHandles();
1317                         }
1319                         /**
1320                          * Adds a dependencies to the queue with optional callbacks to be run
1321                          * when the dependencies are ready or fail
1322                          *
1323                          * @private
1324                          * @param {string|string[]} dependencies Module name or array of string module names
1325                          * @param {Function} [ready] Callback to execute when all dependencies are ready
1326                          * @param {Function} [error] Callback to execute when any dependency fails
1327                          * @param {boolean} [async=false] Whether to load modules asynchronously.
1328                          *  Ignored (and defaulted to `true`) if the document-ready event has already occurred.
1329                          */
1330                         function request( dependencies, ready, error, async ) {
1331                                 var n;
1333                                 // Allow calling by single module name
1334                                 if ( typeof dependencies === 'string' ) {
1335                                         dependencies = [dependencies];
1336                                 }
1338                                 // Add ready and error callbacks if they were given
1339                                 if ( ready !== undefined || error !== undefined ) {
1340                                         jobs[jobs.length] = {
1341                                                 'dependencies': filter(
1342                                                         ['registered', 'loading', 'loaded'],
1343                                                         dependencies
1344                                                 ),
1345                                                 'ready': ready,
1346                                                 'error': error
1347                                         };
1348                                 }
1350                                 // Queue up any dependencies that are registered
1351                                 dependencies = filter( ['registered'], dependencies );
1352                                 for ( n = 0; n < dependencies.length; n += 1 ) {
1353                                         if ( $.inArray( dependencies[n], queue ) === -1 ) {
1354                                                 queue[queue.length] = dependencies[n];
1355                                                 if ( async ) {
1356                                                         // Mark this module as async in the registry
1357                                                         registry[dependencies[n]].async = true;
1358                                                 }
1359                                         }
1360                                 }
1362                                 // Work the queue
1363                                 mw.loader.work();
1364                         }
1366                         function sortQuery( o ) {
1367                                 var sorted = {}, key, a = [];
1368                                 for ( key in o ) {
1369                                         if ( hasOwn.call( o, key ) ) {
1370                                                 a.push( key );
1371                                         }
1372                                 }
1373                                 a.sort();
1374                                 for ( key = 0; key < a.length; key += 1 ) {
1375                                         sorted[a[key]] = o[a[key]];
1376                                 }
1377                                 return sorted;
1378                         }
1380                         /**
1381                          * Converts a module map of the form { foo: [ 'bar', 'baz' ], bar: [ 'baz, 'quux' ] }
1382                          * to a query string of the form foo.bar,baz|bar.baz,quux
1383                          * @private
1384                          */
1385                         function buildModulesString( moduleMap ) {
1386                                 var arr = [], p, prefix;
1387                                 for ( prefix in moduleMap ) {
1388                                         p = prefix === '' ? '' : prefix + '.';
1389                                         arr.push( p + moduleMap[prefix].join( ',' ) );
1390                                 }
1391                                 return arr.join( '|' );
1392                         }
1394                         /**
1395                          * Asynchronously append a script tag to the end of the body
1396                          * that invokes load.php
1397                          * @private
1398                          * @param {Object} moduleMap Module map, see #buildModulesString
1399                          * @param {Object} currReqBase Object with other parameters (other than 'modules') to use in the request
1400                          * @param {string} sourceLoadScript URL of load.php
1401                          * @param {boolean} async Whether to load modules asynchronously.
1402                          *  Ignored (and defaulted to `true`) if the document-ready event has already occurred.
1403                          */
1404                         function doRequest( moduleMap, currReqBase, sourceLoadScript, async ) {
1405                                 var request = $.extend(
1406                                         { modules: buildModulesString( moduleMap ) },
1407                                         currReqBase
1408                                 );
1409                                 request = sortQuery( request );
1410                                 // Append &* to avoid triggering the IE6 extension check
1411                                 addScript( sourceLoadScript + '?' + $.param( request ) + '&*', null, async );
1412                         }
1414                         /**
1415                          * Resolve indexed dependencies.
1416                          *
1417                          * ResourceLoader uses an optimization to save space which replaces module names in
1418                          * dependency lists with the index of that module within the array of module
1419                          * registration data if it exists. The benefit is a significant reduction in the data
1420                          * size of the startup module. This function changes those dependency lists back to
1421                          * arrays of strings.
1422                          *
1423                          * @param {Array} modules Modules array
1424                          */
1425                         function resolveIndexedDependencies( modules ) {
1426                                 var i, iLen, j, jLen, module, dependency;
1428                                 // Expand indexed dependency names
1429                                 for ( i = 0, iLen = modules.length; i < iLen; i++ ) {
1430                                         module = modules[i];
1431                                         if ( module[2] ) {
1432                                                 for ( j = 0, jLen = module[2].length; j < jLen; j++ ) {
1433                                                         dependency = module[2][j];
1434                                                         if ( typeof dependency === 'number' ) {
1435                                                                 module[2][j] = modules[dependency][0];
1436                                                         }
1437                                                 }
1438                                         }
1439                                 }
1440                         }
1442                         /* Public Members */
1443                         return {
1444                                 /**
1445                                  * The module registry is exposed as an aid for debugging and inspecting page
1446                                  * state; it is not a public interface for modifying the registry.
1447                                  *
1448                                  * @see #registry
1449                                  * @property
1450                                  * @private
1451                                  */
1452                                 moduleRegistry: registry,
1454                                 /**
1455                                  * @inheritdoc #newStyleTag
1456                                  * @method
1457                                  */
1458                                 addStyleTag: newStyleTag,
1460                                 /**
1461                                  * Batch-request queued dependencies from the server.
1462                                  */
1463                                 work: function () {
1464                                         var     reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
1465                                                 source, concatSource, origBatch, group, g, i, modules, maxVersion, sourceLoadScript,
1466                                                 currReqBase, currReqBaseLength, moduleMap, l,
1467                                                 lastDotIndex, prefix, suffix, bytesAdded, async;
1469                                         // Build a list of request parameters common to all requests.
1470                                         reqBase = {
1471                                                 skin: mw.config.get( 'skin' ),
1472                                                 lang: mw.config.get( 'wgUserLanguage' ),
1473                                                 debug: mw.config.get( 'debug' )
1474                                         };
1475                                         // Split module batch by source and by group.
1476                                         splits = {};
1477                                         maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', -1 );
1479                                         // Appends a list of modules from the queue to the batch
1480                                         for ( q = 0; q < queue.length; q += 1 ) {
1481                                                 // Only request modules which are registered
1482                                                 if ( hasOwn.call( registry, queue[q] ) && registry[queue[q]].state === 'registered' ) {
1483                                                         // Prevent duplicate entries
1484                                                         if ( $.inArray( queue[q], batch ) === -1 ) {
1485                                                                 batch[batch.length] = queue[q];
1486                                                                 // Mark registered modules as loading
1487                                                                 registry[queue[q]].state = 'loading';
1488                                                         }
1489                                                 }
1490                                         }
1492                                         mw.loader.store.init();
1493                                         if ( mw.loader.store.enabled ) {
1494                                                 concatSource = [];
1495                                                 origBatch = batch;
1496                                                 batch = $.grep( batch, function ( module ) {
1497                                                         var source = mw.loader.store.get( module );
1498                                                         if ( source ) {
1499                                                                 concatSource.push( source );
1500                                                                 return false;
1501                                                         }
1502                                                         return true;
1503                                                 } );
1504                                                 try {
1505                                                         $.globalEval( concatSource.join( ';' ) );
1506                                                 } catch ( err ) {
1507                                                         // Not good, the cached mw.loader.implement calls failed! This should
1508                                                         // never happen, barring ResourceLoader bugs, browser bugs and PEBKACs.
1509                                                         // Depending on how corrupt the string is, it is likely that some
1510                                                         // modules' implement() succeeded while the ones after the error will
1511                                                         // never run and leave their modules in the 'loading' state forever.
1513                                                         // Since this is an error not caused by an individual module but by
1514                                                         // something that infected the implement call itself, don't take any
1515                                                         // risks and clear everything in this cache.
1516                                                         mw.loader.store.clear();
1517                                                         // Re-add the ones still pending back to the batch and let the server
1518                                                         // repopulate these modules to the cache.
1519                                                         // This means that at most one module will be useless (the one that had
1520                                                         // the error) instead of all of them.
1521                                                         log( 'Error while evaluating data from mw.loader.store', err );
1522                                                         origBatch = $.grep( origBatch, function ( module ) {
1523                                                                 return registry[module].state === 'loading';
1524                                                         } );
1525                                                         batch = batch.concat( origBatch );
1526                                                 }
1527                                         }
1529                                         // Early exit if there's nothing to load...
1530                                         if ( !batch.length ) {
1531                                                 return;
1532                                         }
1534                                         // The queue has been processed into the batch, clear up the queue.
1535                                         queue = [];
1537                                         // Always order modules alphabetically to help reduce cache
1538                                         // misses for otherwise identical content.
1539                                         batch.sort();
1541                                         // Split batch by source and by group.
1542                                         for ( b = 0; b < batch.length; b += 1 ) {
1543                                                 bSource = registry[batch[b]].source;
1544                                                 bGroup = registry[batch[b]].group;
1545                                                 if ( !hasOwn.call( splits, bSource ) ) {
1546                                                         splits[bSource] = {};
1547                                                 }
1548                                                 if ( !hasOwn.call( splits[bSource], bGroup ) ) {
1549                                                         splits[bSource][bGroup] = [];
1550                                                 }
1551                                                 bSourceGroup = splits[bSource][bGroup];
1552                                                 bSourceGroup[bSourceGroup.length] = batch[b];
1553                                         }
1555                                         // Clear the batch - this MUST happen before we append any
1556                                         // script elements to the body or it's possible that a script
1557                                         // will be locally cached, instantly load, and work the batch
1558                                         // again, all before we've cleared it causing each request to
1559                                         // include modules which are already loaded.
1560                                         batch = [];
1562                                         for ( source in splits ) {
1564                                                 sourceLoadScript = sources[source];
1566                                                 for ( group in splits[source] ) {
1568                                                         // Cache access to currently selected list of
1569                                                         // modules for this group from this source.
1570                                                         modules = splits[source][group];
1572                                                         // Calculate the highest timestamp
1573                                                         maxVersion = 0;
1574                                                         for ( g = 0; g < modules.length; g += 1 ) {
1575                                                                 if ( registry[modules[g]].version > maxVersion ) {
1576                                                                         maxVersion = registry[modules[g]].version;
1577                                                                 }
1578                                                         }
1580                                                         currReqBase = $.extend( { version: formatVersionNumber( maxVersion ) }, reqBase );
1581                                                         // For user modules append a user name to the request.
1582                                                         if ( group === 'user' && mw.config.get( 'wgUserName' ) !== null ) {
1583                                                                 currReqBase.user = mw.config.get( 'wgUserName' );
1584                                                         }
1585                                                         currReqBaseLength = $.param( currReqBase ).length;
1586                                                         async = true;
1587                                                         // We may need to split up the request to honor the query string length limit,
1588                                                         // so build it piece by piece.
1589                                                         l = currReqBaseLength + 9; // '&modules='.length == 9
1591                                                         moduleMap = {}; // { prefix: [ suffixes ] }
1593                                                         for ( i = 0; i < modules.length; i += 1 ) {
1594                                                                 // Determine how many bytes this module would add to the query string
1595                                                                 lastDotIndex = modules[i].lastIndexOf( '.' );
1597                                                                 // If lastDotIndex is -1, substr() returns an empty string
1598                                                                 prefix = modules[i].substr( 0, lastDotIndex );
1599                                                                 suffix = modules[i].slice( lastDotIndex + 1 );
1601                                                                 bytesAdded = hasOwn.call( moduleMap, prefix )
1602                                                                         ? suffix.length + 3 // '%2C'.length == 3
1603                                                                         : modules[i].length + 3; // '%7C'.length == 3
1605                                                                 // If the request would become too long, create a new one,
1606                                                                 // but don't create empty requests
1607                                                                 if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) {
1608                                                                         // This request would become too long, create a new one
1609                                                                         // and fire off the old one
1610                                                                         doRequest( moduleMap, currReqBase, sourceLoadScript, async );
1611                                                                         moduleMap = {};
1612                                                                         async = true;
1613                                                                         l = currReqBaseLength + 9;
1614                                                                 }
1615                                                                 if ( !hasOwn.call( moduleMap, prefix ) ) {
1616                                                                         moduleMap[prefix] = [];
1617                                                                 }
1618                                                                 moduleMap[prefix].push( suffix );
1619                                                                 if ( !registry[modules[i]].async ) {
1620                                                                         // If this module is blocking, make the entire request blocking
1621                                                                         // This is slightly suboptimal, but in practice mixing of blocking
1622                                                                         // and async modules will only occur in debug mode.
1623                                                                         async = false;
1624                                                                 }
1625                                                                 l += bytesAdded;
1626                                                         }
1627                                                         // If there's anything left in moduleMap, request that too
1628                                                         if ( !$.isEmptyObject( moduleMap ) ) {
1629                                                                 doRequest( moduleMap, currReqBase, sourceLoadScript, async );
1630                                                         }
1631                                                 }
1632                                         }
1633                                 },
1635                                 /**
1636                                  * Register a source.
1637                                  *
1638                                  * The #work method will use this information to split up requests by source.
1639                                  *
1640                                  *     mw.loader.addSource( 'mediawikiwiki', '//www.mediawiki.org/w/load.php' );
1641                                  *
1642                                  * @param {string} id Short string representing a source wiki, used internally for
1643                                  *  registered modules to indicate where they should be loaded from (usually lowercase a-z).
1644                                  * @param {Object|string} loadUrl load.php url, may be an object for backwards-compatibility
1645                                  * @return {boolean}
1646                                  */
1647                                 addSource: function ( id, loadUrl ) {
1648                                         var source;
1649                                         // Allow multiple additions
1650                                         if ( typeof id === 'object' ) {
1651                                                 for ( source in id ) {
1652                                                         mw.loader.addSource( source, id[source] );
1653                                                 }
1654                                                 return true;
1655                                         }
1657                                         if ( hasOwn.call( sources, id ) ) {
1658                                                 throw new Error( 'source already registered: ' + id );
1659                                         }
1661                                         if ( typeof loadUrl === 'object' ) {
1662                                                 loadUrl = loadUrl.loadScript;
1663                                         }
1665                                         sources[id] = loadUrl;
1667                                         return true;
1668                                 },
1670                                 /**
1671                                  * Register a module, letting the system know about it and its
1672                                  * properties. Startup modules contain calls to this function.
1673                                  *
1674                                  * When using multiple module registration by passing an array, dependencies that
1675                                  * are specified as references to modules within the array will be resolved before
1676                                  * the modules are registered.
1677                                  *
1678                                  * @param {string|Array} module Module name or array of arrays, each containing
1679                                  *   a list of arguments compatible with this method
1680                                  * @param {number} version Module version number as a timestamp (falls backs to 0)
1681                                  * @param {string|Array|Function} dependencies One string or array of strings of module
1682                                  *  names on which this module depends, or a function that returns that array.
1683                                  * @param {string} [group=null] Group which the module is in
1684                                  * @param {string} [source='local'] Name of the source
1685                                  * @param {string} [skip=null] Script body of the skip function
1686                                  */
1687                                 register: function ( module, version, dependencies, group, source, skip ) {
1688                                         var i, len;
1689                                         // Allow multiple registration
1690                                         if ( typeof module === 'object' ) {
1691                                                 resolveIndexedDependencies( module );
1692                                                 for ( i = 0, len = module.length; i < len; i++ ) {
1693                                                         // module is an array of module names
1694                                                         if ( typeof module[i] === 'string' ) {
1695                                                                 mw.loader.register( module[i] );
1696                                                         // module is an array of arrays
1697                                                         } else if ( typeof module[i] === 'object' ) {
1698                                                                 mw.loader.register.apply( mw.loader, module[i] );
1699                                                         }
1700                                                 }
1701                                                 return;
1702                                         }
1703                                         // Validate input
1704                                         if ( typeof module !== 'string' ) {
1705                                                 throw new Error( 'module must be a string, not a ' + typeof module );
1706                                         }
1707                                         if ( hasOwn.call( registry, module ) ) {
1708                                                 throw new Error( 'module already registered: ' + module );
1709                                         }
1710                                         // List the module as registered
1711                                         registry[module] = {
1712                                                 version: version !== undefined ? parseInt( version, 10 ) : 0,
1713                                                 dependencies: [],
1714                                                 group: typeof group === 'string' ? group : null,
1715                                                 source: typeof source === 'string' ? source : 'local',
1716                                                 state: 'registered',
1717                                                 skip: typeof skip === 'string' ? skip : null
1718                                         };
1719                                         if ( typeof dependencies === 'string' ) {
1720                                                 // Allow dependencies to be given as a single module name
1721                                                 registry[module].dependencies = [ dependencies ];
1722                                         } else if ( typeof dependencies === 'object' || $.isFunction( dependencies ) ) {
1723                                                 // Allow dependencies to be given as an array of module names
1724                                                 // or a function which returns an array
1725                                                 registry[module].dependencies = dependencies;
1726                                         }
1727                                 },
1729                                 /**
1730                                  * Implement a module given the components that make up the module.
1731                                  *
1732                                  * When #load or #using requests one or more modules, the server
1733                                  * response contain calls to this function.
1734                                  *
1735                                  * All arguments are required.
1736                                  *
1737                                  * @param {string} module Name of module
1738                                  * @param {Function|Array} script Function with module code or Array of URLs to
1739                                  *  be used as the src attribute of a new `<script>` tag.
1740                                  * @param {Object} [style] Should follow one of the following patterns:
1741                                  *
1742                                  *     { "css": [css, ..] }
1743                                  *     { "url": { <media>: [url, ..] } }
1744                                  *
1745                                  * And for backwards compatibility (needs to be supported forever due to caching):
1746                                  *
1747                                  *     { <media>: css }
1748                                  *     { <media>: [url, ..] }
1749                                  *
1750                                  * The reason css strings are not concatenated anymore is bug 31676. We now check
1751                                  * whether it's safe to extend the stylesheet (see #canExpandStylesheetWith).
1752                                  *
1753                                  * @param {Object} [msgs] List of key/value pairs to be added to mw#messages.
1754                                  * @param {Object} [templates] List of key/value pairs to be added to mw#templates.
1755                                  */
1756                                 implement: function ( module, script, style, msgs, templates ) {
1757                                         // Validate input
1758                                         if ( typeof module !== 'string' ) {
1759                                                 throw new Error( 'module must be of type string, not ' + typeof module );
1760                                         }
1761                                         if ( !$.isFunction( script ) && !$.isArray( script ) ) {
1762                                                 throw new Error( 'script must be of type function or array, not ' + typeof script );
1763                                         }
1764                                         if ( style && !$.isPlainObject( style ) ) {
1765                                                 throw new Error( 'style must be of type object, not ' + typeof style );
1766                                         }
1767                                         if ( msgs && !$.isPlainObject( msgs ) ) {
1768                                                 throw new Error( 'msgs must be of type object, not a ' + typeof msgs );
1769                                         }
1770                                         if ( templates && !$.isPlainObject( templates ) ) {
1771                                                 throw new Error( 'templates must be of type object, not a ' + typeof templates );
1772                                         }
1773                                         // Automatically register module
1774                                         if ( !hasOwn.call( registry, module ) ) {
1775                                                 mw.loader.register( module );
1776                                         }
1777                                         // Check for duplicate implementation
1778                                         if ( hasOwn.call( registry, module ) && registry[module].script !== undefined ) {
1779                                                 throw new Error( 'module already implemented: ' + module );
1780                                         }
1781                                         // Attach components
1782                                         registry[module].script = script;
1783                                         registry[module].style = style || {};
1784                                         registry[module].messages = msgs || {};
1785                                         // Templates are optional (for back-compat)
1786                                         registry[module].templates = templates || {};
1787                                         // The module may already have been marked as erroneous
1788                                         if ( $.inArray( registry[module].state, ['error', 'missing'] ) === -1 ) {
1789                                                 registry[module].state = 'loaded';
1790                                                 if ( allReady( registry[module].dependencies ) ) {
1791                                                         execute( module );
1792                                                 }
1793                                         }
1794                                 },
1796                                 /**
1797                                  * Execute a function as soon as one or more required modules are ready.
1798                                  *
1799                                  * Example of inline dependency on OOjs:
1800                                  *
1801                                  *     mw.loader.using( 'oojs', function () {
1802                                  *         OO.compare( [ 1 ], [ 1 ] );
1803                                  *     } );
1804                                  *
1805                                  * @param {string|Array} dependencies Module name or array of modules names the callback
1806                                  *  dependends on to be ready before executing
1807                                  * @param {Function} [ready] Callback to execute when all dependencies are ready
1808                                  * @param {Function} [error] Callback to execute if one or more dependencies failed
1809                                  * @return {jQuery.Promise}
1810                                  * @since 1.23 this returns a promise
1811                                  */
1812                                 using: function ( dependencies, ready, error ) {
1813                                         var deferred = $.Deferred();
1815                                         // Allow calling with a single dependency as a string
1816                                         if ( typeof dependencies === 'string' ) {
1817                                                 dependencies = [ dependencies ];
1818                                         } else if ( !$.isArray( dependencies ) ) {
1819                                                 // Invalid input
1820                                                 throw new Error( 'Dependencies must be a string or an array' );
1821                                         }
1823                                         if ( ready ) {
1824                                                 deferred.done( ready );
1825                                         }
1826                                         if ( error ) {
1827                                                 deferred.fail( error );
1828                                         }
1830                                         // Resolve entire dependency map
1831                                         dependencies = resolve( dependencies );
1832                                         if ( allReady( dependencies ) ) {
1833                                                 // Run ready immediately
1834                                                 deferred.resolve();
1835                                         } else if ( filter( ['error', 'missing'], dependencies ).length ) {
1836                                                 // Execute error immediately if any dependencies have errors
1837                                                 deferred.reject(
1838                                                         new Error( 'One or more dependencies failed to load' ),
1839                                                         dependencies
1840                                                 );
1841                                         } else {
1842                                                 // Not all dependencies are ready: queue up a request
1843                                                 request( dependencies, deferred.resolve, deferred.reject );
1844                                         }
1846                                         return deferred.promise();
1847                                 },
1849                                 /**
1850                                  * Load an external script or one or more modules.
1851                                  *
1852                                  * @param {string|Array} modules Either the name of a module, array of modules,
1853                                  *  or a URL of an external script or style
1854                                  * @param {string} [type='text/javascript'] MIME type to use if calling with a URL of an
1855                                  *  external script or style; acceptable values are "text/css" and
1856                                  *  "text/javascript"; if no type is provided, text/javascript is assumed.
1857                                  * @param {boolean} [async] Whether to load modules asynchronously.
1858                                  *  Ignored (and defaulted to `true`) if the document-ready event has already occurred.
1859                                  *  Defaults to `true` if loading a URL, `false` otherwise.
1860                                  */
1861                                 load: function ( modules, type, async ) {
1862                                         var filtered, m, module, l;
1864                                         // Validate input
1865                                         if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
1866                                                 throw new Error( 'modules must be a string or an array, not a ' + typeof modules );
1867                                         }
1868                                         // Allow calling with an external url or single dependency as a string
1869                                         if ( typeof modules === 'string' ) {
1870                                                 // Support adding arbitrary external scripts
1871                                                 if ( /^(https?:)?\/\//.test( modules ) ) {
1872                                                         if ( async === undefined ) {
1873                                                                 // Assume async for bug 34542
1874                                                                 async = true;
1875                                                         }
1876                                                         if ( type === 'text/css' ) {
1877                                                                 // IE7-8 throws security warnings when inserting a <link> tag
1878                                                                 // with a protocol-relative URL set though attributes (instead of
1879                                                                 // properties) - when on HTTPS. See also bug 41331.
1880                                                                 l = document.createElement( 'link' );
1881                                                                 l.rel = 'stylesheet';
1882                                                                 l.href = modules;
1883                                                                 $( 'head' ).append( l );
1884                                                                 return;
1885                                                         }
1886                                                         if ( type === 'text/javascript' || type === undefined ) {
1887                                                                 addScript( modules, null, async );
1888                                                                 return;
1889                                                         }
1890                                                         // Unknown type
1891                                                         throw new Error( 'invalid type for external url, must be text/css or text/javascript. not ' + type );
1892                                                 }
1893                                                 // Called with single module
1894                                                 modules = [ modules ];
1895                                         }
1897                                         // Filter out undefined modules, otherwise resolve() will throw
1898                                         // an exception for trying to load an undefined module.
1899                                         // Undefined modules are acceptable here in load(), because load() takes
1900                                         // an array of unrelated modules, whereas the modules passed to
1901                                         // using() are related and must all be loaded.
1902                                         for ( filtered = [], m = 0; m < modules.length; m += 1 ) {
1903                                                 if ( hasOwn.call( registry, modules[m] ) ) {
1904                                                         module = registry[modules[m]];
1905                                                         if ( $.inArray( module.state, ['error', 'missing'] ) === -1 ) {
1906                                                                 filtered[filtered.length] = modules[m];
1907                                                         }
1908                                                 }
1909                                         }
1911                                         if ( filtered.length === 0 ) {
1912                                                 return;
1913                                         }
1914                                         // Resolve entire dependency map
1915                                         filtered = resolve( filtered );
1916                                         // If all modules are ready, nothing to be done
1917                                         if ( allReady( filtered ) ) {
1918                                                 return;
1919                                         }
1920                                         // If any modules have errors: also quit.
1921                                         if ( filter( ['error', 'missing'], filtered ).length ) {
1922                                                 return;
1923                                         }
1924                                         // Since some modules are not yet ready, queue up a request.
1925                                         request( filtered, undefined, undefined, async );
1926                                 },
1928                                 /**
1929                                  * Change the state of one or more modules.
1930                                  *
1931                                  * @param {string|Object} module Module name or object of module name/state pairs
1932                                  * @param {string} state State name
1933                                  */
1934                                 state: function ( module, state ) {
1935                                         var m;
1937                                         if ( typeof module === 'object' ) {
1938                                                 for ( m in module ) {
1939                                                         mw.loader.state( m, module[m] );
1940                                                 }
1941                                                 return;
1942                                         }
1943                                         if ( !hasOwn.call( registry, module ) ) {
1944                                                 mw.loader.register( module );
1945                                         }
1946                                         if ( $.inArray( state, ['ready', 'error', 'missing'] ) !== -1
1947                                                 && registry[module].state !== state ) {
1948                                                 // Make sure pending modules depending on this one get executed if their
1949                                                 // dependencies are now fulfilled!
1950                                                 registry[module].state = state;
1951                                                 handlePending( module );
1952                                         } else {
1953                                                 registry[module].state = state;
1954                                         }
1955                                 },
1957                                 /**
1958                                  * Get the version of a module.
1959                                  *
1960                                  * @param {string} module Name of module
1961                                  * @return {string|null} The version, or null if the module (or its version) is not
1962                                  *  in the registry.
1963                                  */
1964                                 getVersion: function ( module ) {
1965                                         if ( !hasOwn.call( registry, module ) || registry[module].version === undefined ) {
1966                                                 return null;
1967                                         }
1968                                         return formatVersionNumber( registry[module].version );
1969                                 },
1971                                 /**
1972                                  * Get the state of a module.
1973                                  *
1974                                  * @param {string} module Name of module
1975                                  * @return {string|null} The state, or null if the module (or its state) is not
1976                                  *  in the registry.
1977                                  */
1978                                 getState: function ( module ) {
1979                                         if ( !hasOwn.call( registry, module ) || registry[module].state === undefined ) {
1980                                                 return null;
1981                                         }
1982                                         return registry[module].state;
1983                                 },
1985                                 /**
1986                                  * Get the names of all registered modules.
1987                                  *
1988                                  * @return {Array}
1989                                  */
1990                                 getModuleNames: function () {
1991                                         return $.map( registry, function ( i, key ) {
1992                                                 return key;
1993                                         } );
1994                                 },
1996                                 /**
1997                                  * @inheritdoc mw.inspect#runReports
1998                                  * @method
1999                                  */
2000                                 inspect: function () {
2001                                         var args = slice.call( arguments );
2002                                         mw.loader.using( 'mediawiki.inspect', function () {
2003                                                 mw.inspect.runReports.apply( mw.inspect, args );
2004                                         } );
2005                                 },
2007                                 /**
2008                                  * On browsers that implement the localStorage API, the module store serves as a
2009                                  * smart complement to the browser cache. Unlike the browser cache, the module store
2010                                  * can slice a concatenated response from ResourceLoader into its constituent
2011                                  * modules and cache each of them separately, using each module's versioning scheme
2012                                  * to determine when the cache should be invalidated.
2013                                  *
2014                                  * @singleton
2015                                  * @class mw.loader.store
2016                                  */
2017                                 store: {
2018                                         // Whether the store is in use on this page.
2019                                         enabled: null,
2021                                         // The contents of the store, mapping '[module name]@[version]' keys
2022                                         // to module implementations.
2023                                         items: {},
2025                                         // Cache hit stats
2026                                         stats: { hits: 0, misses: 0, expired: 0 },
2028                                         /**
2029                                          * Construct a JSON-serializable object representing the content of the store.
2030                                          * @return {Object} Module store contents.
2031                                          */
2032                                         toJSON: function () {
2033                                                 return { items: mw.loader.store.items, vary: mw.loader.store.getVary() };
2034                                         },
2036                                         /**
2037                                          * Get the localStorage key for the entire module store. The key references
2038                                          * $wgDBname to prevent clashes between wikis which share a common host.
2039                                          *
2040                                          * @return {string} localStorage item key
2041                                          */
2042                                         getStoreKey: function () {
2043                                                 return 'MediaWikiModuleStore:' + mw.config.get( 'wgDBname' );
2044                                         },
2046                                         /**
2047                                          * Get a string key on which to vary the module cache.
2048                                          * @return {string} String of concatenated vary conditions.
2049                                          */
2050                                         getVary: function () {
2051                                                 return [
2052                                                         mw.config.get( 'skin' ),
2053                                                         mw.config.get( 'wgResourceLoaderStorageVersion' ),
2054                                                         mw.config.get( 'wgUserLanguage' )
2055                                                 ].join( ':' );
2056                                         },
2058                                         /**
2059                                          * Get a string key for a specific module. The key format is '[name]@[version]'.
2060                                          *
2061                                          * @param {string} module Module name
2062                                          * @return {string|null} Module key or null if module does not exist
2063                                          */
2064                                         getModuleKey: function ( module ) {
2065                                                 return hasOwn.call( registry, module ) ?
2066                                                         ( module + '@' + registry[module].version ) : null;
2067                                         },
2069                                         /**
2070                                          * Initialize the store.
2071                                          *
2072                                          * Retrieves store from localStorage and (if successfully retrieved) decoding
2073                                          * the stored JSON value to a plain object.
2074                                          *
2075                                          * The try / catch block is used for JSON & localStorage feature detection.
2076                                          * See the in-line documentation for Modernizr's localStorage feature detection
2077                                          * code for a full account of why we need a try / catch:
2078                                          * <https://github.com/Modernizr/Modernizr/blob/v2.7.1/modernizr.js#L771-L796>.
2079                                          */
2080                                         init: function () {
2081                                                 var raw, data;
2083                                                 if ( mw.loader.store.enabled !== null ) {
2084                                                         // Init already ran
2085                                                         return;
2086                                                 }
2088                                                 if ( !mw.config.get( 'wgResourceLoaderStorageEnabled' ) ) {
2089                                                         // Disabled by configuration.
2090                                                         // Clear any previous store to free up space. (T66721)
2091                                                         mw.loader.store.clear();
2092                                                         mw.loader.store.enabled = false;
2093                                                         return;
2094                                                 }
2095                                                 if ( mw.config.get( 'debug' ) ) {
2096                                                         // Disable module store in debug mode
2097                                                         mw.loader.store.enabled = false;
2098                                                         return;
2099                                                 }
2101                                                 try {
2102                                                         raw = localStorage.getItem( mw.loader.store.getStoreKey() );
2103                                                         // If we get here, localStorage is available; mark enabled
2104                                                         mw.loader.store.enabled = true;
2105                                                         data = JSON.parse( raw );
2106                                                         if ( data && typeof data.items === 'object' && data.vary === mw.loader.store.getVary() ) {
2107                                                                 mw.loader.store.items = data.items;
2108                                                                 return;
2109                                                         }
2110                                                 } catch ( e ) {
2111                                                         log( 'Storage error', e );
2112                                                 }
2114                                                 if ( raw === undefined ) {
2115                                                         // localStorage failed; disable store
2116                                                         mw.loader.store.enabled = false;
2117                                                 } else {
2118                                                         mw.loader.store.update();
2119                                                 }
2120                                         },
2122                                         /**
2123                                          * Retrieve a module from the store and update cache hit stats.
2124                                          *
2125                                          * @param {string} module Module name
2126                                          * @return {string|boolean} Module implementation or false if unavailable
2127                                          */
2128                                         get: function ( module ) {
2129                                                 var key;
2131                                                 if ( !mw.loader.store.enabled ) {
2132                                                         return false;
2133                                                 }
2135                                                 key = mw.loader.store.getModuleKey( module );
2136                                                 if ( key in mw.loader.store.items ) {
2137                                                         mw.loader.store.stats.hits++;
2138                                                         return mw.loader.store.items[key];
2139                                                 }
2140                                                 mw.loader.store.stats.misses++;
2141                                                 return false;
2142                                         },
2144                                         /**
2145                                          * Stringify a module and queue it for storage.
2146                                          *
2147                                          * @param {string} module Module name
2148                                          * @param {Object} descriptor The module's descriptor as set in the registry
2149                                          */
2150                                         set: function ( module, descriptor ) {
2151                                                 var args, key;
2153                                                 if ( !mw.loader.store.enabled ) {
2154                                                         return false;
2155                                                 }
2157                                                 key = mw.loader.store.getModuleKey( module );
2159                                                 if (
2160                                                         // Already stored a copy of this exact version
2161                                                         key in mw.loader.store.items ||
2162                                                         // Module failed to load
2163                                                         descriptor.state !== 'ready' ||
2164                                                         // Unversioned, private, or site-/user-specific
2165                                                         ( !descriptor.version || $.inArray( descriptor.group, [ 'private', 'user', 'site' ] ) !== -1 ) ||
2166                                                         // Partial descriptor
2167                                                         $.inArray( undefined, [ descriptor.script, descriptor.style,
2168                                                                         descriptor.messages, descriptor.templates ] ) !== -1
2169                                                 ) {
2170                                                         // Decline to store
2171                                                         return false;
2172                                                 }
2174                                                 try {
2175                                                         args = [
2176                                                                 JSON.stringify( module ),
2177                                                                 typeof descriptor.script === 'function' ?
2178                                                                         String( descriptor.script ) :
2179                                                                         JSON.stringify( descriptor.script ),
2180                                                                 JSON.stringify( descriptor.style ),
2181                                                                 JSON.stringify( descriptor.messages ),
2182                                                                 JSON.stringify( descriptor.templates )
2183                                                         ];
2184                                                         // Attempted workaround for a possible Opera bug (bug 57567).
2185                                                         // This regex should never match under sane conditions.
2186                                                         if ( /^\s*\(/.test( args[1] ) ) {
2187                                                                 args[1] = 'function' + args[1];
2188                                                                 log( 'Detected malformed function stringification (bug 57567)' );
2189                                                         }
2190                                                 } catch ( e ) {
2191                                                         log( 'Storage error', e );
2192                                                         return;
2193                                                 }
2195                                                 mw.loader.store.items[key] = 'mw.loader.implement(' + args.join( ',' ) + ');';
2196                                                 mw.loader.store.update();
2197                                         },
2199                                         /**
2200                                          * Iterate through the module store, removing any item that does not correspond
2201                                          * (in name and version) to an item in the module registry.
2202                                          */
2203                                         prune: function () {
2204                                                 var key, module;
2206                                                 if ( !mw.loader.store.enabled ) {
2207                                                         return false;
2208                                                 }
2210                                                 for ( key in mw.loader.store.items ) {
2211                                                         module = key.slice( 0, key.indexOf( '@' ) );
2212                                                         if ( mw.loader.store.getModuleKey( module ) !== key ) {
2213                                                                 mw.loader.store.stats.expired++;
2214                                                                 delete mw.loader.store.items[key];
2215                                                         }
2216                                                 }
2217                                         },
2219                                         /**
2220                                          * Clear the entire module store right now.
2221                                          */
2222                                         clear: function () {
2223                                                 mw.loader.store.items = {};
2224                                                 localStorage.removeItem( mw.loader.store.getStoreKey() );
2225                                         },
2227                                         /**
2228                                          * Sync modules to localStorage.
2229                                          *
2230                                          * This function debounces localStorage updates. When called multiple times in
2231                                          * quick succession, the calls are coalesced into a single update operation.
2232                                          * This allows us to call #update without having to consider the module load
2233                                          * queue; the call to localStorage.setItem will be naturally deferred until the
2234                                          * page is quiescent.
2235                                          *
2236                                          * Because localStorage is shared by all pages with the same origin, if multiple
2237                                          * pages are loaded with different module sets, the possibility exists that
2238                                          * modules saved by one page will be clobbered by another. But the impact would
2239                                          * be minor and the problem would be corrected by subsequent page views.
2240                                          *
2241                                          * @method
2242                                          */
2243                                         update: ( function () {
2244                                                 var timer;
2246                                                 function flush() {
2247                                                         var data,
2248                                                                 key = mw.loader.store.getStoreKey();
2250                                                         if ( !mw.loader.store.enabled ) {
2251                                                                 return false;
2252                                                         }
2253                                                         mw.loader.store.prune();
2254                                                         try {
2255                                                                 // Replacing the content of the module store might fail if the new
2256                                                                 // contents would exceed the browser's localStorage size limit. To
2257                                                                 // avoid clogging the browser with stale data, always remove the old
2258                                                                 // value before attempting to set the new one.
2259                                                                 localStorage.removeItem( key );
2260                                                                 data = JSON.stringify( mw.loader.store );
2261                                                                 localStorage.setItem( key, data );
2262                                                         } catch ( e ) {
2263                                                                 log( 'Storage error', e );
2264                                                         }
2265                                                 }
2267                                                 return function () {
2268                                                         clearTimeout( timer );
2269                                                         timer = setTimeout( flush, 2000 );
2270                                                 };
2271                                         }() )
2272                                 }
2273                         };
2274                 }() ),
2276                 /**
2277                  * HTML construction helper functions
2278                  *
2279                  *     @example
2280                  *
2281                  *     var Html, output;
2282                  *
2283                  *     Html = mw.html;
2284                  *     output = Html.element( 'div', {}, new Html.Raw(
2285                  *         Html.element( 'img', { src: '<' } )
2286                  *     ) );
2287                  *     mw.log( output ); // <div><img src="&lt;"/></div>
2288                  *
2289                  * @class mw.html
2290                  * @singleton
2291                  */
2292                 html: ( function () {
2293                         function escapeCallback( s ) {
2294                                 switch ( s ) {
2295                                         case '\'':
2296                                                 return '&#039;';
2297                                         case '"':
2298                                                 return '&quot;';
2299                                         case '<':
2300                                                 return '&lt;';
2301                                         case '>':
2302                                                 return '&gt;';
2303                                         case '&':
2304                                                 return '&amp;';
2305                                 }
2306                         }
2308                         return {
2309                                 /**
2310                                  * Escape a string for HTML.
2311                                  *
2312                                  * Converts special characters to HTML entities.
2313                                  *
2314                                  *     mw.html.escape( '< > \' & "' );
2315                                  *     // Returns &lt; &gt; &#039; &amp; &quot;
2316                                  *
2317                                  * @param {string} s The string to escape
2318                                  * @return {string} HTML
2319                                  */
2320                                 escape: function ( s ) {
2321                                         return s.replace( /['"<>&]/g, escapeCallback );
2322                                 },
2324                                 /**
2325                                  * Create an HTML element string, with safe escaping.
2326                                  *
2327                                  * @param {string} name The tag name.
2328                                  * @param {Object} attrs An object with members mapping element names to values
2329                                  * @param {Mixed} contents The contents of the element. May be either:
2330                                  *
2331                                  *  - string: The string is escaped.
2332                                  *  - null or undefined: The short closing form is used, e.g. `<br/>`.
2333                                  *  - this.Raw: The value attribute is included without escaping.
2334                                  *  - this.Cdata: The value attribute is included, and an exception is
2335                                  *   thrown if it contains an illegal ETAGO delimiter.
2336                                  *   See <http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.3.2>.
2337                                  * @return {string} HTML
2338                                  */
2339                                 element: function ( name, attrs, contents ) {
2340                                         var v, attrName, s = '<' + name;
2342                                         for ( attrName in attrs ) {
2343                                                 v = attrs[attrName];
2344                                                 // Convert name=true, to name=name
2345                                                 if ( v === true ) {
2346                                                         v = attrName;
2347                                                 // Skip name=false
2348                                                 } else if ( v === false ) {
2349                                                         continue;
2350                                                 }
2351                                                 s += ' ' + attrName + '="' + this.escape( String( v ) ) + '"';
2352                                         }
2353                                         if ( contents === undefined || contents === null ) {
2354                                                 // Self close tag
2355                                                 s += '/>';
2356                                                 return s;
2357                                         }
2358                                         // Regular open tag
2359                                         s += '>';
2360                                         switch ( typeof contents ) {
2361                                                 case 'string':
2362                                                         // Escaped
2363                                                         s += this.escape( contents );
2364                                                         break;
2365                                                 case 'number':
2366                                                 case 'boolean':
2367                                                         // Convert to string
2368                                                         s += String( contents );
2369                                                         break;
2370                                                 default:
2371                                                         if ( contents instanceof this.Raw ) {
2372                                                                 // Raw HTML inclusion
2373                                                                 s += contents.value;
2374                                                         } else if ( contents instanceof this.Cdata ) {
2375                                                                 // CDATA
2376                                                                 if ( /<\/[a-zA-z]/.test( contents.value ) ) {
2377                                                                         throw new Error( 'mw.html.element: Illegal end tag found in CDATA' );
2378                                                                 }
2379                                                                 s += contents.value;
2380                                                         } else {
2381                                                                 throw new Error( 'mw.html.element: Invalid type of contents' );
2382                                                         }
2383                                         }
2384                                         s += '</' + name + '>';
2385                                         return s;
2386                                 },
2388                                 /**
2389                                  * Wrapper object for raw HTML passed to mw.html.element().
2390                                  * @class mw.html.Raw
2391                                  */
2392                                 Raw: function ( value ) {
2393                                         this.value = value;
2394                                 },
2396                                 /**
2397                                  * Wrapper object for CDATA element contents passed to mw.html.element()
2398                                  * @class mw.html.Cdata
2399                                  */
2400                                 Cdata: function ( value ) {
2401                                         this.value = value;
2402                                 }
2403                         };
2404                 }() ),
2406                 // Skeleton user object. mediawiki.user.js extends this
2407                 user: {
2408                         options: new Map(),
2409                         tokens: new Map()
2410                 },
2412                 /**
2413                  * Registry and firing of events.
2414                  *
2415                  * MediaWiki has various interface components that are extended, enhanced
2416                  * or manipulated in some other way by extensions, gadgets and even
2417                  * in core itself.
2418                  *
2419                  * This framework helps streamlining the timing of when these other
2420                  * code paths fire their plugins (instead of using document-ready,
2421                  * which can and should be limited to firing only once).
2422                  *
2423                  * Features like navigating to other wiki pages, previewing an edit
2424                  * and editing itself – without a refresh – can then retrigger these
2425                  * hooks accordingly to ensure everything still works as expected.
2426                  *
2427                  * Example usage:
2428                  *
2429                  *     mw.hook( 'wikipage.content' ).add( fn ).remove( fn );
2430                  *     mw.hook( 'wikipage.content' ).fire( $content );
2431                  *
2432                  * Handlers can be added and fired for arbitrary event names at any time. The same
2433                  * event can be fired multiple times. The last run of an event is memorized
2434                  * (similar to `$(document).ready` and `$.Deferred().done`).
2435                  * This means if an event is fired, and a handler added afterwards, the added
2436                  * function will be fired right away with the last given event data.
2437                  *
2438                  * Like Deferreds and Promises, the mw.hook object is both detachable and chainable.
2439                  * Thus allowing flexible use and optimal maintainability and authority control.
2440                  * You can pass around the `add` and/or `fire` method to another piece of code
2441                  * without it having to know the event name (or `mw.hook` for that matter).
2442                  *
2443                  *     var h = mw.hook( 'bar.ready' );
2444                  *     new mw.Foo( .. ).fetch( { callback: h.fire } );
2445                  *
2446                  * Note: Events are documented with an underscore instead of a dot in the event
2447                  * name due to jsduck not supporting dots in that position.
2448                  *
2449                  * @class mw.hook
2450                  */
2451                 hook: ( function () {
2452                         var lists = {};
2454                         /**
2455                          * Create an instance of mw.hook.
2456                          *
2457                          * @method hook
2458                          * @member mw
2459                          * @param {string} name Name of hook.
2460                          * @return {mw.hook}
2461                          */
2462                         return function ( name ) {
2463                                 var list = hasOwn.call( lists, name ) ?
2464                                         lists[name] :
2465                                         lists[name] = $.Callbacks( 'memory' );
2467                                 return {
2468                                         /**
2469                                          * Register a hook handler
2470                                          * @param {Function...} handler Function to bind.
2471                                          * @chainable
2472                                          */
2473                                         add: list.add,
2475                                         /**
2476                                          * Unregister a hook handler
2477                                          * @param {Function...} handler Function to unbind.
2478                                          * @chainable
2479                                          */
2480                                         remove: list.remove,
2482                                         /**
2483                                          * Run a hook.
2484                                          * @param {Mixed...} data
2485                                          * @chainable
2486                                          */
2487                                         fire: function () {
2488                                                 return list.fireWith.call( this, null, slice.call( arguments ) );
2489                                         }
2490                                 };
2491                         };
2492                 }() )
2493         };
2495         // Alias $j to jQuery for backwards compatibility
2496         // @deprecated since 1.23 Use $ or jQuery instead
2497         mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' );
2499         // Attach to window and globally alias
2500         window.mw = window.mediaWiki = mw;
2502         // Auto-register from pre-loaded startup scripts
2503         if ( $.isFunction( window.startUp ) ) {
2504                 window.startUp();
2505                 window.startUp = undefined;
2506         }
2508 }( jQuery ) );