/**
* Base library for MediaWiki.
*
- * Exposed as globally as `mediaWiki` with `mw` as shortcut.
+ * Exposed globally as `mediaWiki` with `mw` as shortcut.
*
* @class mw
* @alternateClassName mediaWiki
}
}
+ // String format helper. Replaces $1, $2 .. $N placeholders with positional
+ // args. Used by Message.prototype.parser() and exported as mw.format().
+ function format( formatString ) {
+ var parameters = slice.call( arguments, 1 );
+ return formatString.replace( /\$(\d+)/g, function ( str, match ) {
+ var index = parseInt( match, 10 ) - 1;
+ return parameters[index] !== undefined ? parameters[index] : '$' + match;
+ } );
+ }
+
/* Object constructors */
/**
* @class mw.Map
*
* @constructor
- * @param {Object|boolean} [values] Value-bearing object to map, or boolean
- * true to map over the global object. Defaults to an empty object.
+ * @param {Object|boolean} [values] Value-bearing object to map, defaults to an empty object.
+ * For backwards-compatibility with mw.config, this can also be `true` in which case values
+ * will be copied to the Window object as global variables (T72470). Values are copied in one
+ * direction only. Changes to globals are not reflected in the map.
*/
function Map( values ) {
- this.values = values === true ? window : ( values || {} );
- return this;
+ if ( values === true ) {
+ this.values = {};
+
+ // Override #set to also set the global variable
+ this.set = function ( selection, value ) {
+ var s;
+
+ if ( $.isPlainObject( selection ) ) {
+ for ( s in selection ) {
+ setGlobalMapValue( this, s, selection[s] );
+ }
+ return true;
+ }
+ if ( typeof selection === 'string' && arguments.length ) {
+ setGlobalMapValue( this, selection, value );
+ return true;
+ }
+ return false;
+ };
+
+ return;
+ }
+
+ this.values = values || {};
+ }
+
+ /**
+ * Alias property to the global object.
+ *
+ * @private
+ * @static
+ * @param {mw.Map} map
+ * @param {string} key
+ * @param {Mixed} value
+ */
+ function setGlobalMapValue( map, key, value ) {
+ map.values[key] = value;
+ mw.log.deprecate(
+ window,
+ key,
+ value,
+ // Deprecation notice for mw.config globals (T58550, T72470)
+ map === mw.config && 'Use mw.config instead.'
+ );
}
Map.prototype = {
/**
- * Get the value of one or multiple a keys.
+ * Get the value of one or multiple keys.
*
* If called with no arguments, all values will be returned.
*
* If selection was an array, returns an object of key/values (value is null if not found),
* If selection was not passed or invalid, will return the 'values' object member (be careful as
* objects are always passed by reference in JavaScript!).
- * @return {string|Object|null} Values as a string or object, null if invalid/inexistant.
+ * @return {string|Object|null} Values as a string or object, null if invalid/inexistent.
*/
get: function ( selection, fallback ) {
var results, i;
*
* @param {string|Object} selection String key to set value for, or object mapping keys to values.
* @param {Mixed} [value] Value to set (optional, only in use when key is a string)
- * @return {Boolean} This returns true on success, false on failure.
+ * @return {boolean} This returns true on success, false on failure.
*/
set: function ( selection, value ) {
var s;
}
return true;
}
- if ( typeof selection === 'string' && arguments.length > 1 ) {
+ if ( typeof selection === 'string' && arguments.length ) {
this.values[selection] = value;
return true;
}
* This function will not be called for nonexistent messages.
*/
parser: function () {
- var parameters = this.parameters;
- return this.map.get( this.key ).replace( /\$(\d+)/g, function ( str, match ) {
- var index = parseInt( match, 10 ) - 1;
- return parameters[index] !== undefined ? parameters[index] : '$' + match;
- } );
+ return format.apply( null, [ this.map.get( this.key ) ].concat( this.parameters ) );
},
/**
function () { return +new Date(); };
}() ),
+ /**
+ * Format a string. Replace $1, $2 ... $N with positional arguments.
+ *
+ * @method
+ * @since 1.25
+ * @param {string} fmt Format string
+ * @param {Mixed...} parameters Substitutions for $N placeholders.
+ * @return {string} Formatted string
+ */
+ format: format,
+
/**
* Track an analytic event.
*
*
* @property {mw.Map} config
*/
- // Dummy placeholder. Re-assigned in ResourceLoaderStartupModule to an instance of `mw.Map`.
+ // Dummy placeholder. Re-assigned in ResourceLoaderStartUpModule to an instance of `mw.Map`.
config: null,
/**
} );
} catch ( err ) {
// IE8 can throw on Object.defineProperty
+ // Create a copy of the value to the object.
obj[key] = val;
}
};
if ( 'documentMode' in document && document.documentMode <= 9 ) {
$style = getMarker().prev();
- // Verify that the the element before Marker actually is a
+ // Verify that the element before Marker actually is a
// <style> tag and one that came from ResourceLoader
// (not some other style tag or even a `<meta>` or `<script>`).
if ( $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) {
function sortDependencies( module, resolved, unresolved ) {
var n, deps, len, skip;
- if ( registry[module] === undefined ) {
+ if ( !hasOwn.call( registry, module ) ) {
throw new Error( 'Unknown dependency: ' + module );
}
// Build a list of modules which are in one of the specified states
for ( s = 0; s < states.length; s += 1 ) {
for ( m = 0; m < modules.length; m += 1 ) {
- if ( registry[modules[m]] === undefined ) {
+ if ( !hasOwn.call( registry, modules[m] ) ) {
// Module does not exist
if ( states[s] === 'unregistered' ) {
// OK, undefined
var key, value, media, i, urls, cssHandle, checkCssHandles,
cssHandlesRegistered = false;
- if ( registry[module] === undefined ) {
+ if ( !hasOwn.call( registry, module ) ) {
throw new Error( 'Module has not been registered yet: ' + module );
} else if ( registry[module].state === 'registered' ) {
throw new Error( 'Module has not been requested from the server yet: ' + module );
var check = checkCssHandles;
pending++;
return function () {
- if (check) {
+ if ( check ) {
pending--;
check();
check = undefined; // Revoke
// Appends a list of modules from the queue to the batch
for ( q = 0; q < queue.length; q += 1 ) {
// Only request modules which are registered
- if ( registry[queue[q]] !== undefined && registry[queue[q]].state === 'registered' ) {
+ if ( hasOwn.call( registry, queue[q] ) && registry[queue[q]].state === 'registered' ) {
// Prevent duplicate entries
if ( $.inArray( queue[q], batch ) === -1 ) {
batch[batch.length] = queue[q];
for ( b = 0; b < batch.length; b += 1 ) {
bSource = registry[batch[b]].source;
bGroup = registry[batch[b]].group;
- if ( splits[bSource] === undefined ) {
+ if ( !hasOwn.call( splits, bSource ) ) {
splits[bSource] = {};
}
- if ( splits[bSource][bGroup] === undefined ) {
+ if ( !hasOwn.call( splits[bSource], bGroup ) ) {
splits[bSource][bGroup] = [];
}
bSourceGroup = splits[bSource][bGroup];
prefix = modules[i].substr( 0, lastDotIndex );
suffix = modules[i].slice( lastDotIndex + 1 );
- bytesAdded = moduleMap[prefix] !== undefined
+ bytesAdded = hasOwn.call( moduleMap, prefix )
? suffix.length + 3 // '%2C'.length == 3
: modules[i].length + 3; // '%7C'.length == 3
async = true;
l = currReqBaseLength + 9;
}
- if ( moduleMap[prefix] === undefined ) {
+ if ( !hasOwn.call( moduleMap, prefix ) ) {
moduleMap[prefix] = [];
}
moduleMap[prefix].push( suffix );
return true;
}
- if ( sources[id] !== undefined ) {
+ if ( hasOwn.call( sources, id ) ) {
throw new Error( 'source already registered: ' + id );
}
if ( typeof module !== 'string' ) {
throw new Error( 'module must be a string, not a ' + typeof module );
}
- if ( registry[module] !== undefined ) {
+ if ( hasOwn.call( registry, module ) ) {
throw new Error( 'module already registered: ' + module );
}
// List the module as registered
* @param {string} module Name of module
* @param {Function|Array} script Function with module code or Array of URLs to
* be used as the src attribute of a new `<script>` tag.
- * @param {Object} style Should follow one of the following patterns:
+ * @param {Object} [style] Should follow one of the following patterns:
*
* { "css": [css, ..] }
* { "url": { <media>: [url, ..] } }
* The reason css strings are not concatenated anymore is bug 31676. We now check
* whether it's safe to extend the stylesheet (see #canExpandStylesheetWith).
*
- * @param {Object} msgs List of key/value pairs to be added to mw#messages.
+ * @param {Object} [msgs] List of key/value pairs to be added to mw#messages.
* @param {Object} [templates] List of key/value pairs to be added to mw#templates.
*/
implement: function ( module, script, style, msgs, templates ) {
// Validate input
if ( typeof module !== 'string' ) {
- throw new Error( 'module must be a string, not a ' + typeof module );
+ throw new Error( 'module must be of type string, not ' + typeof module );
}
if ( !$.isFunction( script ) && !$.isArray( script ) ) {
- throw new Error( 'script must be a function or an array, not a ' + typeof script );
+ throw new Error( 'script must be of type function or array, not ' + typeof script );
}
- if ( !$.isPlainObject( style ) ) {
- throw new Error( 'style must be an object, not a ' + typeof style );
+ if ( style && !$.isPlainObject( style ) ) {
+ throw new Error( 'style must be of type object, not ' + typeof style );
}
- if ( !$.isPlainObject( msgs ) ) {
- throw new Error( 'msgs must be an object, not a ' + typeof msgs );
+ if ( msgs && !$.isPlainObject( msgs ) ) {
+ throw new Error( 'msgs must be of type object, not a ' + typeof msgs );
}
- if ( templates !== undefined && !$.isPlainObject( templates ) ) {
- throw new Error( 'templates must be an object, not a ' + typeof templates );
+ if ( templates && !$.isPlainObject( templates ) ) {
+ throw new Error( 'templates must be of type object, not a ' + typeof templates );
}
// Automatically register module
- if ( registry[module] === undefined ) {
+ if ( !hasOwn.call( registry, module ) ) {
mw.loader.register( module );
}
// Check for duplicate implementation
- if ( registry[module] !== undefined && registry[module].script !== undefined ) {
+ if ( hasOwn.call( registry, module ) && registry[module].script !== undefined ) {
throw new Error( 'module already implemented: ' + module );
}
// Attach components
registry[module].script = script;
- registry[module].style = style;
- registry[module].messages = msgs;
+ registry[module].style = style || {};
+ registry[module].messages = msgs || {};
// Templates are optional (for back-compat)
registry[module].templates = templates || {};
// The module may already have been marked as erroneous
// an array of unrelated modules, whereas the modules passed to
// using() are related and must all be loaded.
for ( filtered = [], m = 0; m < modules.length; m += 1 ) {
- module = registry[modules[m]];
- if ( module !== undefined ) {
+ if ( hasOwn.call( registry, modules[m] ) ) {
+ module = registry[modules[m]];
if ( $.inArray( module.state, ['error', 'missing'] ) === -1 ) {
filtered[filtered.length] = modules[m];
}
}
return;
}
- if ( registry[module] === undefined ) {
+ if ( !hasOwn.call( registry, module ) ) {
mw.loader.register( module );
}
if ( $.inArray( state, ['ready', 'error', 'missing'] ) !== -1
* in the registry.
*/
getVersion: function ( module ) {
- if ( !registry[module] || registry[module].version === undefined ) {
+ if ( !hasOwn.call( registry, module ) || registry[module].version === undefined ) {
return null;
}
return formatVersionNumber( registry[module].version );
* Get the state of a module.
*
* @param {string} module Name of module
- * @return {string|null} The state, or null if the module (or its version) is not
+ * @return {string|null} The state, or null if the module (or its state) is not
* in the registry.
*/
getState: function ( module ) {
- if ( !registry[module] || registry[module].state === undefined ) {
+ if ( !hasOwn.call( registry, module ) || registry[module].state === undefined ) {
return null;
}
return registry[module].state;
* @return {string|null} Module key or null if module does not exist
*/
getModuleKey: function ( module ) {
- return typeof registry[module] === 'object' ?
+ return hasOwn.call( registry, module ) ?
( module + '@' + registry[module].version ) : null;
},