X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Fsrc%2Fmediawiki%2Fmediawiki.js;h=fdd21e525e5473df6bf84f7226572f9f0ecfc836;hb=09f4a785b33c346f668067b526594ccbc73b8bf0;hp=2621a97dbaf050fab3d13878e43e6cfadf50693e;hpb=7ee3a118ebfd72862c60155b64b0bb39ab27d066;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index 2621a97dba..fdd21e525e 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -7,6 +7,7 @@ * @alternateClassName mediaWiki * @singleton */ +/*jshint latedef:false */ /*global sha1 */ ( function ( $ ) { 'use strict'; @@ -68,7 +69,7 @@ if ( $.isPlainObject( selection ) ) { for ( s in selection ) { - setGlobalMapValue( this, s, selection[s] ); + setGlobalMapValue( this, s, selection[ s ] ); } return true; } @@ -95,13 +96,13 @@ * @param {Mixed} value */ function setGlobalMapValue( map, key, value ) { - map.values[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.' + window, + key, + value, + // Deprecation notice for mw.config globals (T58550, T72470) + map === mw.config && 'Use mw.config instead.' ); } @@ -128,7 +129,7 @@ selection = slice.call( selection ); results = {}; for ( i = 0; i < selection.length; i++ ) { - results[selection[i]] = this.get( selection[i], fallback ); + results[ selection[ i ] ] = this.get( selection[ i ], fallback ); } return results; } @@ -137,7 +138,7 @@ if ( !hasOwn.call( this.values, selection ) ) { return fallback; } - return this.values[selection]; + return this.values[ selection ]; } if ( selection === undefined ) { @@ -160,12 +161,12 @@ if ( $.isPlainObject( selection ) ) { for ( s in selection ) { - this.values[s] = selection[s]; + this.values[ s ] = selection[ s ]; } return true; } if ( typeof selection === 'string' && arguments.length > 1 ) { - this.values[selection] = value; + this.values[ selection ] = value; return true; } return false; @@ -182,7 +183,7 @@ if ( $.isArray( selection ) ) { for ( s = 0; s < selection.length; s++ ) { - if ( typeof selection[s] !== 'string' || !hasOwn.call( this.values, selection[s] ) ) { + if ( typeof selection[ s ] !== 'string' || !hasOwn.call( this.values, selection[ s ] ) ) { return false; } } @@ -284,7 +285,7 @@ params: function ( parameters ) { var i; for ( i = 0; i < parameters.length; i += 1 ) { - this.parameters.push( parameters[i] ); + this.parameters.push( parameters[ i ] ); } return this; }, @@ -422,7 +423,7 @@ 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; + return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match; } ); }, @@ -485,8 +486,8 @@ */ trackUnsubscribe: function ( callback ) { trackHandlers = $.grep( trackHandlers, function ( fns ) { - if ( fns[1] === callback ) { - trackCallbacks.remove( fns[0] ); + if ( fns[ 1 ] === callback ) { + trackCallbacks.remove( fns[ 0 ] ); // Ensure the tuple is removed to avoid holding on to closures return false; } @@ -581,6 +582,7 @@ /** * Dummy placeholder for {@link mw.log} + * * @method */ log: ( function () { @@ -632,7 +634,7 @@ * @param {string} [msg] Optional text to include in the deprecation message */ log.deprecate = !Object.defineProperty ? function ( obj, key, val ) { - obj[key] = val; + obj[ key ] = val; } : function ( obj, key, val, msg ) { msg = 'Use of "' + key + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' ); // Support: IE8 @@ -654,7 +656,7 @@ } ); } catch ( err ) { // Fallback to creating a copy of the value to the object. - obj[key] = val; + obj[ key ] = val; } }; @@ -717,7 +719,7 @@ * * { * 'moduleName': { - * // From mw.loader.register() in startup module + * // From mw.loader.register() * 'version': '########' (hash) * 'dependencies': ['required.foo', 'bar.also', ...], (or) function () {} * 'group': 'somegroup', (or) null @@ -735,6 +737,30 @@ * } * } * + * State machine: + * + * - `registered`: + * The module is known to the system but not yet requested. + * Meta data is registered via mw.loader#register. Calls to that method are + * generated server-side by the startup module. + * - `loading`: + * The module is requested through mw.loader (either directly or as dependency of + * another module). The client will be fetching module contents from the server. + * The contents are then stashed in the registry via mw.loader#implement. + * - `loaded`: + * The module has been requested from the server and stashed via mw.loader#implement. + * If the module has no more dependencies in-fight, the module will be executed + * right away. Otherwise execution is deferred, controlled via #handlePending. + * - `executing`: + * The module is being executed. + * - `ready`: + * The module has been successfully executed. + * - `error`: + * The module (or one of its dependencies) produced an error during execution. + * - `missing`: + * The module was registered client-side and requested, but the server denied knowledge + * of the module's existence. + * * @property * @private */ @@ -795,7 +821,7 @@ if ( nextnode ) { $( nextnode ).before( s ); } else { - document.getElementsByTagName( 'head' )[0].appendChild( s ); + document.getElementsByTagName( 'head' )[ 0 ].appendChild( s ); } if ( s.styleSheet ) { // Support: IE6-10 @@ -911,7 +937,7 @@ */ function getCombinedVersion( modules ) { var hashes = $.map( modules, function ( module ) { - return registry[module].version; + return registry[ module ].version; } ); // Trim for consistency with server-side ResourceLoader::makeHash. It also helps // save precious space in the limited query string. Otherwise modules are more @@ -919,93 +945,6 @@ return sha1( hashes.join( '' ) ).slice( 0, 12 ); } - /** - * Resolve dependencies and detect circular references. - * - * @private - * @param {string} module Name of the top-level module whose dependencies shall be - * resolved and sorted. - * @param {Array} resolved Returns a topological sort of the given module and its - * dependencies, such that later modules depend on earlier modules. The array - * contains the module names. If the array contains already some module names, - * this function appends its result to the pre-existing array. - * @param {Object} [unresolved] Hash used to track the current dependency - * chain; used to report loops in the dependency graph. - * @throws {Error} If any unregistered module or a dependency loop is encountered - */ - function sortDependencies( module, resolved, unresolved ) { - var n, deps, len, skip; - - if ( !hasOwn.call( registry, module ) ) { - throw new Error( 'Unknown dependency: ' + module ); - } - - if ( registry[module].skip !== null ) { - /*jshint evil:true */ - skip = new Function( registry[module].skip ); - registry[module].skip = null; - if ( skip() ) { - registry[module].skipped = true; - registry[module].dependencies = []; - registry[module].state = 'ready'; - handlePending( module ); - return; - } - } - - // Resolves dynamic loader function and replaces it with its own results - if ( $.isFunction( registry[module].dependencies ) ) { - registry[module].dependencies = registry[module].dependencies(); - // Ensures the module's dependencies are always in an array - if ( typeof registry[module].dependencies !== 'object' ) { - registry[module].dependencies = [registry[module].dependencies]; - } - } - if ( $.inArray( module, resolved ) !== -1 ) { - // Module already resolved; nothing to do - return; - } - // Create unresolved if not passed in - if ( !unresolved ) { - unresolved = {}; - } - // Tracks down dependencies - deps = registry[module].dependencies; - len = deps.length; - for ( n = 0; n < len; n += 1 ) { - if ( $.inArray( deps[n], resolved ) === -1 ) { - if ( unresolved[deps[n]] ) { - throw new Error( - 'Circular reference detected: ' + module + - ' -> ' + deps[n] - ); - } - - // Add to unresolved - unresolved[module] = true; - sortDependencies( deps[n], resolved, unresolved ); - delete unresolved[module]; - } - } - resolved[resolved.length] = module; - } - - /** - * Get a list of module names that a module depends on in their proper dependency - * order. - * - * @private - * @param {string[]} module Array of string module names - * @return {Array} List of dependencies, including 'module'. - */ - function resolve( modules ) { - var resolved = []; - $.each( modules, function ( idx, module ) { - sortDependencies( module, resolved ); - } ); - return resolved; - } - /** * Determine whether all dependencies are in state 'ready', which means we may * execute the module or job now. @@ -1017,7 +956,7 @@ function allReady( modules ) { var i; for ( i = 0; i < modules.length; i++ ) { - if ( mw.loader.getState( modules[i] ) !== 'ready' ) { + if ( mw.loader.getState( modules[ i ] ) !== 'ready' ) { return false; } } @@ -1035,7 +974,7 @@ function anyFailed( modules ) { var i, state; for ( i = 0; i < modules.length; i++ ) { - state = mw.loader.getState( modules[i] ); + state = mw.loader.getState( modules[ i ] ); if ( state === 'error' || state === 'missing' ) { return true; } @@ -1057,16 +996,16 @@ function handlePending( module ) { var j, job, hasErrors, m, stateChange; - if ( registry[module].state === 'error' || registry[module].state === 'missing' ) { + if ( registry[ module ].state === 'error' || registry[ module ].state === 'missing' ) { // 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. do { stateChange = false; for ( m in registry ) { - if ( registry[m].state !== 'error' && registry[m].state !== 'missing' ) { - if ( anyFailed( registry[m].dependencies ) ) { - registry[m].state = 'error'; + if ( registry[ m ].state !== 'error' && registry[ m ].state !== 'missing' ) { + if ( anyFailed( registry[ m ].dependencies ) ) { + registry[ m ].state = 'error'; stateChange = true; } } @@ -1076,16 +1015,16 @@ // Execute all jobs whose dependencies are either all satisfied or contain at least one failed module. for ( j = 0; j < jobs.length; j += 1 ) { - hasErrors = anyFailed( jobs[j].dependencies ); - if ( hasErrors || allReady( jobs[j].dependencies ) ) { + hasErrors = anyFailed( jobs[ j ].dependencies ); + if ( hasErrors || allReady( jobs[ j ].dependencies ) ) { // All dependencies satisfied, or some have errors - job = jobs[j]; + job = jobs[ j ]; jobs.splice( j, 1 ); j -= 1; try { if ( hasErrors ) { if ( $.isFunction( job.error ) ) { - job.error( new Error( 'Module ' + module + ' has failed dependencies' ), [module] ); + job.error( new Error( 'Module ' + module + ' has failed dependencies' ), [ module ] ); } } else { if ( $.isFunction( job.ready ) ) { @@ -1100,27 +1039,114 @@ } } - if ( registry[module].state === '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. - mw.loader.store.set( module, registry[module] ); + mw.loader.store.set( module, registry[ module ] ); for ( m in registry ) { - if ( registry[m].state === 'loaded' && allReady( registry[m].dependencies ) ) { + if ( registry[ m ].state === 'loaded' && allReady( registry[ m ].dependencies ) ) { execute( m ); } } } } + /** + * Resolve dependencies and detect circular references. + * + * @private + * @param {string} module Name of the top-level module whose dependencies shall be + * resolved and sorted. + * @param {Array} resolved Returns a topological sort of the given module and its + * dependencies, such that later modules depend on earlier modules. The array + * contains the module names. If the array contains already some module names, + * this function appends its result to the pre-existing array. + * @param {Object} [unresolved] Hash used to track the current dependency + * chain; used to report loops in the dependency graph. + * @throws {Error} If any unregistered module or a dependency loop is encountered + */ + function sortDependencies( module, resolved, unresolved ) { + var n, deps, len, skip; + + if ( !hasOwn.call( registry, module ) ) { + throw new Error( 'Unknown dependency: ' + module ); + } + + if ( registry[ module ].skip !== null ) { + /*jshint evil:true */ + skip = new Function( registry[ module ].skip ); + registry[ module ].skip = null; + if ( skip() ) { + registry[ module ].skipped = true; + registry[ module ].dependencies = []; + registry[ module ].state = 'ready'; + handlePending( module ); + return; + } + } + + // Resolves dynamic loader function and replaces it with its own results + if ( $.isFunction( registry[ module ].dependencies ) ) { + registry[ module ].dependencies = registry[ module ].dependencies(); + // Ensures the module's dependencies are always in an array + if ( typeof registry[ module ].dependencies !== 'object' ) { + registry[ module ].dependencies = [ registry[ module ].dependencies ]; + } + } + if ( $.inArray( module, resolved ) !== -1 ) { + // Module already resolved; nothing to do + return; + } + // Create unresolved if not passed in + if ( !unresolved ) { + unresolved = {}; + } + // Tracks down dependencies + deps = registry[ module ].dependencies; + len = deps.length; + for ( n = 0; n < len; n += 1 ) { + if ( $.inArray( deps[ n ], resolved ) === -1 ) { + if ( unresolved[ deps[ n ] ] ) { + throw new Error( + 'Circular reference detected: ' + module + + ' -> ' + deps[ n ] + ); + } + + // Add to unresolved + unresolved[ module ] = true; + sortDependencies( deps[ n ], resolved, unresolved ); + delete unresolved[ module ]; + } + } + resolved[ resolved.length ] = module; + } + + /** + * Get a list of module names that a module depends on in their proper dependency + * order. + * + * @private + * @param {string[]} module Array of string module names + * @return {Array} List of dependencies, including 'module'. + */ + function resolve( modules ) { + var resolved = []; + $.each( modules, function ( idx, module ) { + sortDependencies( module, resolved ); + } ); + return resolved; + } + /** * Load and execute a script with callback. * * @private * @param {string} src URL to script, will be used as the src attribute in the script tag - * @param {Function} [callback] Callback which will be run when the script is done + * @return {jQuery.Promise} */ - function addScript( src, callback ) { - $.ajax( { + function addScript( src ) { + return $.ajax( { url: src, dataType: 'script', // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use @@ -1130,7 +1156,26 @@ // text, so we'd need to $.globalEval, which then messes up line numbers. crossDomain: true, cache: true - } ).always( callback ); + } ); + } + + /** + * Utility function for execute() + * + * @ignore + */ + function addLink( media, url ) { + var el = document.createElement( 'link' ); + // Support: IE + // Insert in document *before* setting href + getMarker().before( el ); + el.rel = 'stylesheet'; + if ( media && media !== 'all' ) { + el.media = media; + } + // If you end up here from an IE exception "SCRIPT: Invalid property value.", + // see #addEmbeddedCSS, bug 31676, and bug 47277 for details. + el.href = url; } /** @@ -1140,50 +1185,27 @@ * @param {string} module Module name to execute */ function execute( module ) { - var key, value, media, i, urls, cssHandle, checkCssHandles, + var key, value, media, i, urls, cssHandle, checkCssHandles, runScript, cssHandlesRegistered = false; if ( !hasOwn.call( registry, module ) ) { throw new Error( 'Module has not been registered yet: ' + module ); } - if ( registry[module].state === 'registered' ) { - throw new Error( 'Module has not been requested from the server yet: ' + module ); - } - if ( registry[module].state === 'loading' ) { - throw new Error( 'Module has not completed loading yet: ' + module ); - } - if ( registry[module].state === 'ready' ) { - throw new Error( 'Module has already been executed: ' + module ); + if ( registry[ module ].state !== 'loaded' ) { + throw new Error( 'Module in state "' + registry[ module ].state + '" may not be executed: ' + module ); } - /** - * Define loop-function here for efficiency - * and to avoid re-using badly scoped variables. - * @ignore - */ - function addLink( media, url ) { - var el = document.createElement( 'link' ); - // Support: IE - // Insert in document *before* setting href - getMarker().before( el ); - el.rel = 'stylesheet'; - if ( media && media !== 'all' ) { - el.media = media; - } - // If you end up here from an IE exception "SCRIPT: Invalid property value.", - // see #addEmbeddedCSS, bug 31676, and bug 47277 for details. - el.href = url; - } + registry[ module ].state = 'executing'; - function runScript() { + runScript = function () { var script, markModuleReady, nestedAddScript, legacyWait, // Expand to include dependencies since we have to exclude both legacy modules // and their dependencies from the legacyWait (to prevent a circular dependency). legacyModules = resolve( mw.config.get( 'wgResourceLoaderLegacyModules', [] ) ); try { - script = registry[module].script; + script = registry[ module ].script; markModuleReady = function () { - registry[module].state = 'ready'; + registry[ module ].state = 'ready'; handlePending( module ); }; nestedAddScript = function ( arr, callback, i ) { @@ -1195,7 +1217,7 @@ return; } - addScript( arr[i], function () { + addScript( arr[ i ] ).always( function () { nestedAddScript( arr, callback, i + 1 ); } ); }; @@ -1232,26 +1254,20 @@ } catch ( e ) { // This needs to NOT use mw.log because these errors are common in production mode // and not in debug mode, such as when a symbol that should be global isn't exported - registry[module].state = 'error'; + registry[ module ].state = 'error'; mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } ); handlePending( module ); } - } - - // This used to be inside runScript, but since that is now fired asychronously - // (after CSS is loaded) we need to set it here right away. It is crucial that - // when execute() is called this is set synchronously, otherwise modules will get - // executed multiple times as the registry will state that it isn't loading yet. - registry[module].state = 'loading'; + }; // Add localizations to message system - if ( registry[module].messages ) { - mw.messages.set( registry[module].messages ); + if ( registry[ module ].messages ) { + mw.messages.set( registry[ module ].messages ); } // Initialise templates - if ( registry[module].templates ) { - mw.templates.set( module, registry[module].templates ); + if ( registry[ module ].templates ) { + mw.templates.set( module, registry[ module ].templates ); } // Make sure we don't run the scripts until all stylesheet insertions have completed. @@ -1283,9 +1299,9 @@ // * back-compat: { : [url, ..] } // * { "css": [css, ..] } // * { "url": { : [url, ..] } } - if ( registry[module].style ) { - for ( key in registry[module].style ) { - value = registry[module].style[key]; + if ( registry[ module ].style ) { + for ( key in registry[ module ].style ) { + value = registry[ module ].style[ key ]; media = undefined; if ( key !== 'url' && key !== 'css' ) { @@ -1310,10 +1326,10 @@ for ( i = 0; i < value.length; i += 1 ) { if ( key === 'bc-url' ) { // back-compat: { : [url, ..] } - addLink( media, value[i] ); + addLink( media, value[ i ] ); } else if ( key === 'css' ) { // { "css": [css, ..] } - addEmbeddedCSS( value[i], cssHandle() ); + addEmbeddedCSS( value[ i ], cssHandle() ); } } // Not an array, but a regular object @@ -1321,9 +1337,9 @@ } else if ( typeof value === 'object' ) { // { "url": { : [url, ..] } } for ( media in value ) { - urls = value[media]; + urls = value[ media ]; for ( i = 0; i < urls.length; i += 1 ) { - addLink( media, urls[i] ); + addLink( media, urls[ i ] ); } } } @@ -1347,12 +1363,12 @@ function request( dependencies, ready, error ) { // Allow calling by single module name if ( typeof dependencies === 'string' ) { - dependencies = [dependencies]; + dependencies = [ dependencies ]; } // Add ready and error callbacks if they were given if ( ready !== undefined || error !== undefined ) { - jobs[jobs.length] = { + jobs[ jobs.length ] = { dependencies: $.grep( dependencies, function ( module ) { var state = mw.loader.getState( module ); return state === 'registered' || state === 'loaded' || state === 'loading'; @@ -1369,8 +1385,8 @@ if ( state === 'registered' && $.inArray( module, queue ) === -1 ) { // Private modules must be embedded in the page. Don't bother queuing // these as the server will deny them anyway (T101806). - if ( registry[module].group === 'private' ) { - registry[module].state = 'error'; + if ( registry[ module ].group === 'private' ) { + registry[ module ].state = 'error'; handlePending( module ); return; } @@ -1393,7 +1409,7 @@ } a.sort(); for ( key = 0; key < a.length; key += 1 ) { - sorted[a[key]] = o[a[key]]; + sorted[ a[ key ] ] = o[ a[ key ] ]; } return sorted; } @@ -1401,6 +1417,7 @@ /** * Converts a module map of the form { foo: [ 'bar', 'baz' ], bar: [ 'baz, 'quux' ] } * to a query string of the form foo.bar,baz|bar.baz,quux + * * @private */ function buildModulesString( moduleMap ) { @@ -1409,13 +1426,14 @@ for ( prefix in moduleMap ) { p = prefix === '' ? '' : prefix + '.'; - arr.push( p + moduleMap[prefix].join( ',' ) ); + arr.push( p + moduleMap[ prefix ].join( ',' ) ); } return arr.join( '|' ); } /** * Load modules from load.php + * * @private * @param {Object} moduleMap Module map, see #buildModulesString * @param {Object} currReqBase Object with other parameters (other than 'modules') to use in the request @@ -1443,9 +1461,9 @@ */ function resolveIndexedDependencies( modules ) { $.each( modules, function ( idx, module ) { - if ( module[2] ) { - module[2] = $.map( module[2], function ( dep ) { - return typeof dep === 'number' ? modules[dep][0] : dep; + if ( module[ 2 ] ) { + module[ 2 ] = $.map( module[ 2 ], function ( dep ) { + return typeof dep === 'number' ? modules[ dep ][ 0 ] : dep; } ); } } ); @@ -1491,12 +1509,12 @@ // 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 ( hasOwn.call( registry, queue[q] ) && 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]; + if ( $.inArray( queue[ q ], batch ) === -1 ) { + batch[ batch.length ] = queue[ q ]; // Mark registered modules as loading - registry[queue[q]].state = 'loading'; + registry[ queue[ q ] ].state = 'loading'; } } } @@ -1532,7 +1550,7 @@ // the error) instead of all of them. mw.track( 'resourceloader.exception', { exception: err, source: 'store-eval' } ); origBatch = $.grep( origBatch, function ( module ) { - return registry[module].state === 'loading'; + return registry[ module ].state === 'loading'; } ); batch = batch.concat( origBatch ); } @@ -1552,16 +1570,16 @@ // Split batch by source and by group. for ( b = 0; b < batch.length; b += 1 ) { - bSource = registry[batch[b]].source; - bGroup = registry[batch[b]].group; + bSource = registry[ batch[ b ] ].source; + bGroup = registry[ batch[ b ] ].group; if ( !hasOwn.call( splits, bSource ) ) { - splits[bSource] = {}; + splits[ bSource ] = {}; } - if ( !hasOwn.call( splits[bSource], bGroup ) ) { - splits[bSource][bGroup] = []; + if ( !hasOwn.call( splits[ bSource ], bGroup ) ) { + splits[ bSource ][ bGroup ] = []; } - bSourceGroup = splits[bSource][bGroup]; - bSourceGroup[bSourceGroup.length] = batch[b]; + bSourceGroup = splits[ bSource ][ bGroup ]; + bSourceGroup[ bSourceGroup.length ] = batch[ b ]; } // Clear the batch - this MUST happen before we append any @@ -1573,13 +1591,13 @@ for ( source in splits ) { - sourceLoadScript = sources[source]; + sourceLoadScript = sources[ source ]; - for ( group in splits[source] ) { + for ( group in splits[ source ] ) { // Cache access to currently selected list of // modules for this group from this source. - modules = splits[source][group]; + modules = splits[ source ][ group ]; currReqBase = $.extend( { version: getCombinedVersion( modules ) @@ -1597,15 +1615,15 @@ for ( i = 0; i < modules.length; i += 1 ) { // Determine how many bytes this module would add to the query string - lastDotIndex = modules[i].lastIndexOf( '.' ); + lastDotIndex = modules[ i ].lastIndexOf( '.' ); // If lastDotIndex is -1, substr() returns an empty string - prefix = modules[i].substr( 0, lastDotIndex ); - suffix = modules[i].slice( lastDotIndex + 1 ); + prefix = modules[ i ].substr( 0, lastDotIndex ); + suffix = modules[ i ].slice( lastDotIndex + 1 ); bytesAdded = hasOwn.call( moduleMap, prefix ) ? suffix.length + 3 // '%2C'.length == 3 - : modules[i].length + 3; // '%7C'.length == 3 + : modules[ i ].length + 3; // '%7C'.length == 3 // If the request would become too long, create a new one, // but don't create empty requests @@ -1618,9 +1636,9 @@ mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } ); } if ( !hasOwn.call( moduleMap, prefix ) ) { - moduleMap[prefix] = []; + moduleMap[ prefix ] = []; } - moduleMap[prefix].push( suffix ); + moduleMap[ prefix ].push( suffix ); l += bytesAdded; } // If there's anything left in moduleMap, request that too @@ -1648,7 +1666,7 @@ // Allow multiple additions if ( typeof id === 'object' ) { for ( source in id ) { - mw.loader.addSource( source, id[source] ); + mw.loader.addSource( source, id[ source ] ); } return true; } @@ -1661,7 +1679,7 @@ loadUrl = loadUrl.loadScript; } - sources[id] = loadUrl; + sources[ id ] = loadUrl; return true; }, @@ -1692,11 +1710,11 @@ resolveIndexedDependencies( module ); for ( i = 0, len = module.length; i < len; i++ ) { // module is an array of module names - if ( typeof module[i] === 'string' ) { - mw.loader.register( module[i] ); + if ( typeof module[ i ] === 'string' ) { + mw.loader.register( module[ i ] ); // module is an array of arrays - } else if ( typeof module[i] === 'object' ) { - mw.loader.register.apply( mw.loader, module[i] ); + } else if ( typeof module[ i ] === 'object' ) { + mw.loader.register.apply( mw.loader, module[ i ] ); } } return; @@ -1709,7 +1727,7 @@ throw new Error( 'module already registered: ' + module ); } // List the module as registered - registry[module] = { + registry[ module ] = { version: version !== undefined ? String( version ) : '', dependencies: [], group: typeof group === 'string' ? group : null, @@ -1719,11 +1737,11 @@ }; if ( typeof dependencies === 'string' ) { // Allow dependencies to be given as a single module name - registry[module].dependencies = [ dependencies ]; + registry[ module ].dependencies = [ dependencies ]; } else if ( typeof dependencies === 'object' || $.isFunction( dependencies ) ) { // Allow dependencies to be given as an array of module names // or a function which returns an array - registry[module].dependencies = dependencies; + registry[ module ].dependencies = dependencies; } }, @@ -1776,18 +1794,18 @@ mw.loader.register( module ); } // Check for duplicate implementation - if ( hasOwn.call( registry, module ) && 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 = messages || {}; - registry[module].templates = templates || {}; + registry[ module ].script = script || []; + registry[ module ].style = style || {}; + registry[ module ].messages = messages || {}; + registry[ module ].templates = templates || {}; // 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 ) ) { + if ( $.inArray( registry[ module ].state, [ 'error', 'missing' ] ) === -1 ) { + registry[ module ].state = 'loaded'; + if ( allReady( registry[ module ].dependencies ) ) { execute( module ); } } @@ -1921,21 +1939,21 @@ if ( typeof module === 'object' ) { for ( m in module ) { - mw.loader.state( m, module[m] ); + mw.loader.state( m, module[ m ] ); } return; } if ( !hasOwn.call( registry, module ) ) { mw.loader.register( module ); } - if ( $.inArray( state, ['ready', 'error', 'missing'] ) !== -1 - && registry[module].state !== state ) { + if ( $.inArray( state, [ 'ready', 'error', 'missing' ] ) !== -1 + && registry[ module ].state !== state ) { // Make sure pending modules depending on this one get executed if their // dependencies are now fulfilled! - registry[module].state = state; + registry[ module ].state = state; handlePending( module ); } else { - registry[module].state = state; + registry[ module ].state = state; } }, @@ -1947,10 +1965,10 @@ * in the registry. */ getVersion: function ( module ) { - if ( !hasOwn.call( registry, module ) || registry[module].version === undefined ) { + if ( !hasOwn.call( registry, module ) || registry[ module ].version === undefined ) { return null; } - return registry[module].version; + return registry[ module ].version; }, /** @@ -1961,10 +1979,10 @@ * in the registry. */ getState: function ( module ) { - if ( !hasOwn.call( registry, module ) || registry[module].state === undefined ) { + if ( !hasOwn.call( registry, module ) || registry[ module ].state === undefined ) { return null; } - return registry[module].state; + return registry[ module ].state; }, /** @@ -2016,6 +2034,7 @@ /** * Construct a JSON-serializable object representing the content of the store. + * * @return {Object} Module store contents. */ toJSON: function () { @@ -2034,6 +2053,7 @@ /** * Get a key on which to vary the module cache. + * * @return {string} String of concatenated vary conditions. */ getVary: function () { @@ -2052,7 +2072,7 @@ */ getModuleKey: function ( module ) { return hasOwn.call( registry, module ) ? - ( module + '@' + registry[module].version ) : null; + ( module + '@' + registry[ module ].version ) : null; }, /** @@ -2124,7 +2144,7 @@ key = mw.loader.store.getModuleKey( module ); if ( key in mw.loader.store.items ) { mw.loader.store.stats.hits++; - return mw.loader.store.items[key]; + return mw.loader.store.items[ key ]; } mw.loader.store.stats.misses++; return false; @@ -2172,8 +2192,8 @@ ]; // Attempted workaround for a possible Opera bug (bug T59567). // This regex should never match under sane conditions. - if ( /^\s*\(/.test( args[1] ) ) { - args[1] = 'function' + args[1]; + if ( /^\s*\(/.test( args[ 1 ] ) ) { + args[ 1 ] = 'function' + args[ 1 ]; mw.track( 'resourceloader.assert', { source: 'bug-T59567' } ); } } catch ( e ) { @@ -2185,7 +2205,7 @@ if ( src.length > mw.loader.store.MODULE_SIZE_MAX ) { return false; } - mw.loader.store.items[key] = src; + mw.loader.store.items[ key ] = src; mw.loader.store.update(); }, @@ -2204,10 +2224,10 @@ module = key.slice( 0, key.indexOf( '@' ) ); if ( mw.loader.store.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 ) { + delete mw.loader.store.items[ key ]; + } else if ( mw.loader.store.items[ key ].length > mw.loader.store.MODULE_SIZE_MAX ) { // This value predates the enforcement of a size limit on cached modules. - delete mw.loader.store.items[key]; + delete mw.loader.store.items[ key ]; } } }, @@ -2336,7 +2356,7 @@ var v, attrName, s = '<' + name; for ( attrName in attrs ) { - v = attrs[attrName]; + v = attrs[ attrName ]; // Convert name=true, to name=name if ( v === true ) { v = attrName; @@ -2383,6 +2403,7 @@ /** * Wrapper object for raw HTML passed to mw.html.element(). + * * @class mw.html.Raw */ Raw: function ( value ) { @@ -2391,6 +2412,7 @@ /** * Wrapper object for CDATA element contents passed to mw.html.element() + * * @class mw.html.Cdata */ Cdata: function ( value ) { @@ -2457,12 +2479,13 @@ */ return function ( name ) { var list = hasOwn.call( lists, name ) ? - lists[name] : - lists[name] = $.Callbacks( 'memory' ); + lists[ name ] : + lists[ name ] = $.Callbacks( 'memory' ); return { /** * Register a hook handler + * * @param {Function...} handler Function to bind. * @chainable */ @@ -2470,6 +2493,7 @@ /** * Unregister a hook handler + * * @param {Function...} handler Function to unbind. * @chainable */ @@ -2477,6 +2501,7 @@ /** * Run a hook. + * * @param {Mixed...} data * @chainable */ @@ -2531,10 +2556,33 @@ } } - // subscribe to error streams + // Subscribe to error streams mw.trackSubscribe( 'resourceloader.exception', log ); mw.trackSubscribe( 'resourceloader.assert', log ); + /** + * Fired when all modules associated with the page have finished loading. + * + * @event resourceloader_loadEnd + * @member mw.hook + */ + $( function () { + var loading = $.grep( mw.loader.getModuleNames(), function ( module ) { + return mw.loader.getState( module ) === 'loading'; + } ); + // In order to use jQuery.when (which stops early if one of the promises got rejected) + // cast any loading failures into successes. We only need a callback, not the module. + loading = $.map( loading, function ( module ) { + return mw.loader.using( module ).then( null, function () { + return $.Deferred().resolve(); + } ); + } ); + $.when.apply( $, loading ).then( function () { + performance.mark( 'mwLoadEnd' ); + mw.hook( 'resourceloader.loadEnd' ).fire(); + } ); + } ); + // Attach to window and globally alias window.mw = window.mediaWiki = mw; }( jQuery ) );