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