X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Fsrc%2Fstartup%2Fmediawiki.js;h=06eb80e04640a150e0b9b24d68b9ea02088530a5;hb=2fca6bc47d220a218923835f455f6a775b9e3394;hp=8d42c0f0249a34d48b71ce0babfee8719fdb6ea5;hpb=2ff855cd494b1274ece1c316ce2d830e659ee964;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/src/startup/mediawiki.js b/resources/src/startup/mediawiki.js index 8d42c0f024..06eb80e046 100644 --- a/resources/src/startup/mediawiki.js +++ b/resources/src/startup/mediawiki.js @@ -7,6 +7,7 @@ * @alternateClassName mediaWiki * @singleton */ +/* global $VARS, $CODE */ ( function () { 'use strict'; @@ -618,6 +619,12 @@ */ jobs = [], + /** + * @private + * @property {Array} baseModules + */ + baseModules = $VARS.baseModules, + /** * For #addEmbeddedCSS() and #addLink() * @@ -720,10 +727,10 @@ * @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 ); } /** @@ -744,6 +751,18 @@ 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. @@ -775,9 +794,10 @@ * @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. @@ -785,7 +805,12 @@ 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; } @@ -824,12 +849,16 @@ } } + // 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 ); } @@ -879,8 +908,19 @@ 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 ] ) ) { @@ -889,7 +929,6 @@ ); } - unresolved.add( module ); sortDependencies( deps[ i ], resolved, unresolved ); } } @@ -965,6 +1004,8 @@ /** * 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 @@ -972,8 +1013,8 @@ */ 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; } @@ -1028,6 +1069,9 @@ */ 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 ); @@ -1122,12 +1166,15 @@ } 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 ); }; @@ -1149,9 +1196,20 @@ 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' ) { @@ -1169,6 +1227,7 @@ // 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' @@ -1280,6 +1339,11 @@ } } + // 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(); @@ -1386,7 +1450,7 @@ * @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; @@ -1428,22 +1492,20 @@ 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 ] ) { @@ -1469,7 +1531,7 @@ // 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++ ) { @@ -1478,7 +1540,7 @@ // 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 @@ -1488,12 +1550,12 @@ 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; @@ -1738,7 +1800,7 @@ module: { exports: {} }, - version: version !== undefined ? String( version ) : '', + version: String( version || '' ), dependencies: deps || [], group: typeof group === 'string' ? group : null, source: typeof source === 'string' ? source : 'local', @@ -1803,7 +1865,7 @@ // 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 ); } } @@ -1867,26 +1929,21 @@ /** * 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 ); } },