logged.add( trace );
return true;
}
- Object.defineProperty( obj, key, {
- configurable: true,
- enumerable: true,
- get: function () {
- if ( uniqueTrace() ) {
- mw.track( 'mw.deprecate', key );
- mw.log.warn( msg );
+ // Support: Safari 5.0
+ // Throws "not supported on DOM Objects" for Node or Element objects (incl. document)
+ // Safari 4.0 doesn't have this method, and it was fixed in Safari 5.1.
+ try {
+ Object.defineProperty( obj, key, {
+ configurable: true,
+ enumerable: true,
+ get: function () {
+ if ( uniqueTrace() ) {
+ mw.track( 'mw.deprecate', key );
+ mw.log.warn( msg );
+ }
+ return val;
+ },
+ set: function ( newVal ) {
+ if ( uniqueTrace() ) {
+ mw.track( 'mw.deprecate', key );
+ mw.log.warn( msg );
+ }
+ val = newVal;
}
- return val;
- },
- set: function ( newVal ) {
- if ( uniqueTrace() ) {
- mw.track( 'mw.deprecate', key );
- mw.log.warn( msg );
- }
- val = newVal;
- }
- } );
-
+ } );
+ } catch ( err ) {
+ obj[ key ] = val;
+ }
};
return log;
pendingRequests.push( function () {
if ( moduleName && hasOwn.call( registry, moduleName ) ) {
+ // Emulate runScript() part of execute()
window.require = mw.loader.require;
window.module = registry[ moduleName ].module;
}
addScript( src ).always( function () {
- // Clear environment
- delete window.require;
+ // 'module.exports' should not persist after the file is executed to
+ // avoid leakage to unrelated code. 'require' should be kept, however,
+ // as asynchronous access to 'require' is allowed and expected. (T144879)
delete window.module;
r.resolve();
}
}
+ /**
+ * Make a versioned key for a specific module.
+ *
+ * @private
+ * @param {string} module Module name
+ * @return {string|null} Module key in format '`[name]@[version]`',
+ * or null if the module does not exist
+ */
+ function getModuleKey( module ) {
+ return hasOwn.call( registry, module ) ?
+ ( module + '@' + registry[ module ].version ) : null;
+ }
+
+ /**
+ * @private
+ * @param {string} key Module name or '`[name]@[version]`'
+ * @return {Object}
+ */
+ function splitModuleKey( key ) {
+ var index = key.indexOf( '@' );
+ if ( index === -1 ) {
+ return { name: key };
+ }
+ return {
+ name: key.slice( 0, index ),
+ version: key.slice( index )
+ };
+ }
+
/* Public Members */
return {
/**
* When #load() or #using() requests one or more modules, the server
* response contain calls to this function.
*
- * @param {string} module Name of module
+ * @param {string} module Name of module and current module version. Formatted
+ * as '`[name]@[version]`". This version should match the requested version
+ * (from #batchRequest and #registry). This avoids race conditions (T117587).
+ * For back-compat with MediaWiki 1.27 and earlier, the version may be omitted.
* @param {Function|Array|string} [script] Function with module code, list of URLs
* to load via `<script src>`, or string of module code for `$.globalEval()`.
* @param {Object} [style] Should follow one of the following patterns:
* @param {Object} [templates] List of key/value pairs to be added to mw#templates.
*/
implement: function ( module, script, style, messages, templates ) {
+ var split = splitModuleKey( module ),
+ name = split.name,
+ version = split.version;
// Automatically register module
- if ( !hasOwn.call( registry, module ) ) {
- mw.loader.register( module );
+ if ( !hasOwn.call( registry, name ) ) {
+ mw.loader.register( name );
}
// Check for duplicate implementation
- if ( hasOwn.call( registry, module ) && registry[ module ].script !== undefined ) {
- throw new Error( 'module already implemented: ' + module );
+ if ( hasOwn.call( registry, name ) && registry[ name ].script !== undefined ) {
+ throw new Error( 'module already implemented: ' + name );
+ }
+ if ( version ) {
+ // Without this reset, if there is a version mismatch between the
+ // requested and received module version, then mw.loader.store would
+ // cache the response under the requested key. Thus poisoning the cache
+ // indefinitely with a stale value. (T117587)
+ registry[ name ].version = version;
}
// Attach components
- registry[ module ].script = script || null;
- registry[ module ].style = style || null;
- registry[ module ].messages = messages || null;
- registry[ module ].templates = templates || null;
+ registry[ name ].script = script || null;
+ registry[ name ].style = style || null;
+ registry[ name ].messages = messages || null;
+ registry[ name ].templates = templates || null;
// The module may already have been marked as erroneous
- if ( $.inArray( registry[ module ].state, [ 'error', 'missing' ] ) === -1 ) {
- registry[ module ].state = 'loaded';
- if ( allReady( registry[ module ].dependencies ) ) {
- execute( module );
+ if ( $.inArray( registry[ name ].state, [ 'error', 'missing' ] ) === -1 ) {
+ registry[ name ].state = 'loaded';
+ if ( allReady( registry[ name ].dependencies ) ) {
+ execute( name );
}
}
},
MODULE_SIZE_MAX: 100 * 1000,
- // The contents of the store, mapping '[module name]@[version]' keys
+ // The contents of the store, mapping '[name]@[version]' keys
// to module implementations.
items: {},
].join( ':' );
},
- /**
- * Get a key for a specific module. The key format is '[name]@[version]'.
- *
- * @param {string} module Module name
- * @return {string|null} Module key or null if module does not exist
- */
- getModuleKey: function ( module ) {
- return hasOwn.call( registry, module ) ?
- ( module + '@' + registry[ module ].version ) : null;
- },
-
/**
* Initialize the store.
*
return false;
}
- key = mw.loader.store.getModuleKey( module );
+ key = getModuleKey( module );
if ( key in mw.loader.store.items ) {
mw.loader.store.stats.hits++;
return mw.loader.store.items[ key ];
return false;
}
- key = mw.loader.store.getModuleKey( module );
+ key = getModuleKey( module );
if (
// Already stored a copy of this exact version
try {
args = [
- JSON.stringify( module ),
+ JSON.stringify( key ),
typeof descriptor.script === 'function' ?
String( descriptor.script ) :
JSON.stringify( descriptor.script ),
for ( key in mw.loader.store.items ) {
module = key.slice( 0, key.indexOf( '@' ) );
- if ( mw.loader.store.getModuleKey( module ) !== key ) {
+ if ( getModuleKey( module ) !== key ) {
mw.loader.store.stats.expired++;
delete mw.loader.store.items[ key ];
} else if ( mw.loader.store.items[ key ].length > mw.loader.store.MODULE_SIZE_MAX ) {