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