* @alternateClassName mediaWiki
* @singleton
*/
+/* global $VARS, $CODE */
( function () {
'use strict';
*/
jobs = [],
+ /**
+ * @private
+ * @property {Array} baseModules
+ */
+ baseModules = $VARS.baseModules,
+
/**
* For #addEmbeddedCSS() and #addLink()
*
* @return {string} Hash of concatenated version hashes.
*/
function getCombinedVersion( modules ) {
- var hashes = modules.map( function ( module ) {
- return registry[ module ].version;
- } );
- return fnv132( hashes.join( '' ) );
+ var hashes = modules.reduce( function ( result, module ) {
+ return result + registry[ module ].version;
+ }, '' );
+ return fnv132( hashes );
}
/**
return true;
}
+ /**
+ * Determine whether all direct and base dependencies are in state 'ready'
+ *
+ * @private
+ * @param {string} module Name of the module to be checked
+ * @return {boolean} True if all direct/base dependencies are in state 'ready'; false otherwise
+ */
+ function allWithImplicitReady( module ) {
+ return allReady( registry[ module ].dependencies ) &&
+ ( baseModules.indexOf( module ) !== -1 || allReady( baseModules ) );
+ }
+
/**
* Determine whether all dependencies are in state 'ready', which means we may
* execute the module or job now.
* @param {string} module Name of module that entered one of the states 'ready', 'error', or 'missing'.
*/
function handlePending( module ) {
- var j, job, hasErrors, m, stateChange;
+ var j, job, hasErrors, m, stateChange, fromBaseModule;
if ( registry[ module ].state === 'error' || registry[ module ].state === 'missing' ) {
+ fromBaseModule = baseModules.indexOf( module ) !== -1;
// If the current module failed, mark all dependent modules also as failed.
// Iterate until steady-state to propagate the error state upwards in the
// dependency tree.
stateChange = false;
for ( m in registry ) {
if ( registry[ m ].state !== 'error' && registry[ m ].state !== 'missing' ) {
- if ( anyFailed( registry[ m ].dependencies ) ) {
+ // Always propagate errors from base modules to regular modules (implicit dependency).
+ // Between base modules or regular modules, consider direct dependencies only.
+ if (
+ ( fromBaseModule && baseModules.indexOf( m ) === -1 ) ||
+ anyFailed( registry[ m ].dependencies )
+ ) {
registry[ m ].state = 'error';
stateChange = true;
}
}
}
+ // The current module became 'ready'.
if ( registry[ module ].state === 'ready' ) {
- // The current module became 'ready'. Set it in the module store, and recursively execute all
- // dependent modules that are loaded and now have all dependencies satisfied.
+ // Save it to the module store.
mw.loader.store.set( module, registry[ module ] );
+ // Recursively execute all dependent modules that were already loaded
+ // (waiting for execution) and no longer have unsatisfied dependencies.
for ( m in registry ) {
- if ( registry[ m ].state === 'loaded' && allReady( registry[ m ].dependencies ) ) {
+ // Base modules may have dependencies amongst eachother to ensure correct
+ // execution order. Regular modules wait for all base modules.
+ if ( registry[ m ].state === 'loaded' && allWithImplicitReady( m ) ) {
// eslint-disable-next-line no-use-before-define
execute( m );
}
if ( !unresolved ) {
unresolved = new StringSet();
}
+
+ // Add base modules
+ if ( baseModules.indexOf( module ) === -1 ) {
+ baseModules.forEach( function ( baseModule ) {
+ if ( resolved.indexOf( baseModule ) === -1 ) {
+ resolved.push( baseModule );
+ }
+ } );
+ }
+
// Tracks down dependencies
deps = registry[ module ].dependencies;
+ unresolved.add( module );
for ( i = 0; i < deps.length; i++ ) {
if ( resolved.indexOf( deps[ i ] ) === -1 ) {
if ( unresolved.has( deps[ i ] ) ) {
);
}
- unresolved.add( module );
sortDependencies( deps[ i ], resolved, unresolved );
}
}
/**
* Queue the loading and execution of a script for a particular module.
*
+ * This does for debug mode what runScript() does for production.
+ *
* @private
* @param {string} src URL of the script
* @param {string} moduleName Name of currently executing module
*/
function queueModuleScript( src, moduleName, callback ) {
pendingRequests.push( function () {
- if ( hasOwn.call( registry, moduleName ) ) {
- // Emulate runScript() part of execute()
+ // Keep in sync with execute()/runScript().
+ if ( moduleName !== 'jquery' && hasOwn.call( registry, moduleName ) ) {
window.require = mw.loader.require;
window.module = registry[ moduleName ].module;
}
*/
function domEval( code ) {
var script = document.createElement( 'script' );
+ if ( mw.config.get( 'wgCSPNonce' ) !== false ) {
+ script.nonce = mw.config.get( 'wgCSPNonce' );
+ }
script.text = code;
document.head.appendChild( script );
script.parentNode.removeChild( script );
}
registry[ module ].state = 'executing';
+ $CODE.profileExecuteStart();
runScript = function () {
var script, markModuleReady, nestedAddScript;
+ $CODE.profileScriptStart();
script = registry[ module ].script;
markModuleReady = function () {
+ $CODE.profileScriptEnd();
registry[ module ].state = 'ready';
handlePending( module );
};
if ( Array.isArray( script ) ) {
nestedAddScript( script, markModuleReady, 0 );
} else if ( typeof script === 'function' ) {
- // Pass jQuery twice so that the signature of the closure which wraps
- // the script can bind both '$' and 'jQuery'.
- script( window.$, window.$, mw.loader.require, registry[ module ].module );
+ // Keep in sync with queueModuleScript() for debug mode
+ if ( module === 'jquery' ) {
+ // This is a special case for when 'jquery' itself is being loaded.
+ // - The standard jquery.js distribution does not set `window.jQuery`
+ // in CommonJS-compatible environments (Node.js, AMD, RequireJS, etc.).
+ // - MediaWiki's 'jquery' module also bundles jquery.migrate.js, which
+ // in a CommonJS-compatible environment, will use require('jquery'),
+ // but that can't work when we're still inside that module.
+ script();
+ } else {
+ // Pass jQuery twice so that the signature of the closure which wraps
+ // the script can bind both '$' and 'jQuery'.
+ script( window.$, window.$, mw.loader.require, registry[ module ].module );
+ }
markModuleReady();
} else if ( typeof script === 'string' ) {
// Use mw.track instead of mw.log because these errors are common in production mode
// (e.g. undefined variable), and mw.log is only enabled in debug mode.
registry[ module ].state = 'error';
+ $CODE.profileScriptEnd();
mw.trackError( 'resourceloader.exception', {
exception: e, module:
module, source: 'module-execute'
}
}
+ // End profiling of execute()-self before we call checkCssHandles(),
+ // which (sometimes asynchronously) calls runScript(), which we want
+ // to measure separately without overlap.
+ $CODE.profileExecuteEnd();
+
// Kick off.
cssHandlesRegistered = true;
checkCssHandles();
* @param {string[]} batch
*/
function batchRequest( batch ) {
- var reqBase, splits, maxQueryLength, b, bSource, bGroup, bSourceGroup,
+ var reqBase, splits, maxQueryLength, b, bSource, bGroup,
source, group, i, modules, sourceLoadScript,
currReqBase, currReqBaseLength, moduleMap, currReqModules, l,
lastDotIndex, prefix, suffix, bytesAdded;
maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', 2000 );
// Split module list by source and by group.
- splits = {};
+ splits = Object.create( null );
for ( b = 0; b < batch.length; b++ ) {
bSource = registry[ batch[ b ] ].source;
bGroup = registry[ batch[ b ] ].group;
- if ( !hasOwn.call( splits, bSource ) ) {
- splits[ bSource ] = {};
+ if ( !splits[ bSource ] ) {
+ splits[ bSource ] = Object.create( null );
}
- if ( !hasOwn.call( splits[ bSource ], bGroup ) ) {
+ if ( !splits[ bSource ][ bGroup ] ) {
splits[ bSource ][ bGroup ] = [];
}
- bSourceGroup = splits[ bSource ][ bGroup ];
- bSourceGroup.push( batch[ b ] );
+ splits[ bSource ][ bGroup ].push( batch[ b ] );
}
for ( source in splits ) {
-
sourceLoadScript = sources[ source ];
for ( group in splits[ source ] ) {
// We may need to split up the request to honor the query string length limit,
// so build it piece by piece.
l = currReqBaseLength;
- moduleMap = {}; // { prefix: [ suffixes ] }
+ moduleMap = Object.create( null ); // { prefix: [ suffixes ] }
currReqModules = [];
for ( i = 0; i < modules.length; i++ ) {
// If lastDotIndex is -1, substr() returns an empty string
prefix = modules[ i ].substr( 0, lastDotIndex );
suffix = modules[ i ].slice( lastDotIndex + 1 );
- bytesAdded = hasOwn.call( moduleMap, prefix ) ?
+ bytesAdded = moduleMap[ prefix ] ?
suffix.length + 3 : // '%2C'.length == 3
modules[ i ].length + 3; // '%7C'.length == 3
doRequest();
// .. and start again.
l = currReqBaseLength;
- moduleMap = {};
+ moduleMap = Object.create( null );
currReqModules = [];
mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } );
}
- if ( !hasOwn.call( moduleMap, prefix ) ) {
+ if ( !moduleMap[ prefix ] ) {
moduleMap[ prefix ] = [];
}
l += bytesAdded;
module: {
exports: {}
},
- version: version !== undefined ? String( version ) : '',
+ version: String( version || '' ),
dependencies: deps || [],
group: typeof group === 'string' ? group : null,
source: typeof source === 'string' ? source : 'local',
// The module may already have been marked as erroneous
if ( registry[ name ].state !== 'error' && registry[ name ].state !== 'missing' ) {
registry[ name ].state = 'loaded';
- if ( allReady( registry[ name ].dependencies ) ) {
+ if ( allWithImplicitReady( name ) ) {
execute( name );
}
}
/**
* Change the state of one or more modules.
*
- * @param {string|Object} module Module name or object of module name/state pairs
- * @param {string} state State name
+ * @param {Object} modules Object of module name/state pairs
*/
- state: function ( module, state ) {
- var m;
-
- if ( typeof module === 'object' ) {
- for ( m in module ) {
- mw.loader.state( m, module[ m ] );
+ state: function ( modules ) {
+ var module, state;
+ for ( module in modules ) {
+ state = modules[ module ];
+ if ( !hasOwn.call( registry, module ) ) {
+ mw.loader.register( module );
+ }
+ registry[ module ].state = state;
+ if ( state === 'ready' || state === 'error' || state === 'missing' ) {
+ // Make sure pending modules depending on this one get executed if their
+ // dependencies are now fulfilled!
+ handlePending( module );
}
- return;
- }
- if ( !hasOwn.call( registry, module ) ) {
- mw.loader.register( module );
- }
- registry[ module ].state = state;
- if ( state === 'ready' || state === 'error' || state === 'missing' ) {
- // Make sure pending modules depending on this one get executed if their
- // dependencies are now fulfilled!
- handlePending( module );
}
},