X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Fsrc%2Fmediawiki%2Fmediawiki.js;h=f878e4258081ec84c289496ba251f8cbc1c00946;hb=acf2e7603c4de9ebe42563292f9587b5f8808cf1;hp=04807f4e4fece44d6275d9ac5f7f459d38bb0c00;hpb=056f1f532d5e60df920bf4c2d69f80c4fc593b65;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index 04807f4e4f..f878e42580 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -11,7 +11,7 @@ ( function ( $ ) { 'use strict'; - var mw, + var mw, StringSet, log, hasOwn = Object.prototype.hasOwnProperty, slice = Array.prototype.slice, trackCallbacks = $.Callbacks( 'memory' ), @@ -48,6 +48,23 @@ return hash; } + StringSet = window.Set || ( function () { + /** + * @private + * @class + */ + function StringSet() { + this.set = {}; + } + StringSet.prototype.add = function ( value ) { + this.set[ value ] = true; + }; + StringSet.prototype.has = function ( value ) { + return this.set.hasOwnProperty( value ); + }; + return StringSet; + }() ); + /** * Create an object that can be read from or written to from methods that allow * interaction both with single and multiple properties at once. @@ -416,6 +433,92 @@ } }; + log = ( function () { + // Also update the restoration of methods in mediawiki.log.js + // when adding or removing methods here. + var log = function () {}, + console = window.console; + + /** + * @class mw.log + * @singleton + */ + + /** + * Write a message to the console's warning channel. + * Actions not supported by the browser console are silently ignored. + * + * @param {...string} msg Messages to output to console + */ + log.warn = console && console.warn && Function.prototype.bind ? + Function.prototype.bind.call( console.warn, console ) : + $.noop; + + /** + * Write a message to the console's error channel. + * + * Most browsers provide a stacktrace by default if the argument + * is a caught Error object. + * + * @since 1.26 + * @param {Error|...string} msg Messages to output to console + */ + log.error = console && console.error && Function.prototype.bind ? + Function.prototype.bind.call( console.error, console ) : + $.noop; + + /** + * Create a property in a host object that, when accessed, will produce + * a deprecation warning in the console. + * + * @param {Object} obj Host object of deprecated property + * @param {string} key Name of property to create in `obj` + * @param {Mixed} val The value this property should return when accessed + * @param {string} [msg] Optional text to include in the deprecation message + */ + log.deprecate = !Object.defineProperty ? function ( obj, key, val ) { + obj[ key ] = val; + } : function ( obj, key, val, msg ) { + msg = 'Use of "' + key + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' ); + var logged = new StringSet(); + function uniqueTrace() { + var trace = new Error().stack; + if ( logged.has( trace ) ) { + return false; + } + logged.add( trace ); + return true; + } + // 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; + } + } ); + } catch ( err ) { + obj[ key ] = val; + } + }; + + return log; + }() ); + /** * @class mw */ @@ -610,105 +713,11 @@ }, /** - * Dummy placeholder for {@link mw.log} + * No-op dummy placeholder for {@link mw.log} in debug mode. * * @method */ - log: ( function () { - // Also update the restoration of methods in mediawiki.log.js - // when adding or removing methods here. - var log = function () {}, - console = window.console; - - /** - * @class mw.log - * @singleton - */ - - /** - * Write a message to the console's warning channel. - * Actions not supported by the browser console are silently ignored. - * - * @param {...string} msg Messages to output to console - */ - log.warn = console && console.warn && Function.prototype.bind ? - Function.prototype.bind.call( console.warn, console ) : - $.noop; - - /** - * Write a message to the console's error channel. - * - * Most browsers provide a stacktrace by default if the argument - * is a caught Error object. - * - * @since 1.26 - * @param {Error|...string} msg Messages to output to console - */ - log.error = console && console.error && Function.prototype.bind ? - Function.prototype.bind.call( console.error, console ) : - $.noop; - - /** - * Create a property in a host object that, when accessed, will produce - * a deprecation warning in the console with backtrace. - * - * @param {Object} obj Host object of deprecated property - * @param {string} key Name of property to create in `obj` - * @param {Mixed} val The value this property should return when accessed - * @param {string} [msg] Optional text to include in the deprecation message - */ - log.deprecate = !Object.defineProperty ? function ( obj, key, val ) { - obj[ key ] = val; - } : function ( obj, key, val, msg ) { - /*globals Set */ - msg = 'Use of "' + key + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' ); - var logged, loggedIsSet, uniqueTrace; - if ( window.Set ) { - logged = new Set(); - loggedIsSet = true; - } else { - logged = {}; - loggedIsSet = false; - } - uniqueTrace = function () { - var trace = new Error().stack; - if ( loggedIsSet ) { - if ( logged.has( trace ) ) { - return false; - } - logged.add( trace ); - return true; - } else { - if ( logged.hasOwnProperty( trace ) ) { - return false; - } - logged[ trace ] = 1; - return true; - } - }; - 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 log; - }() ), + log: log, /** * Client for ResourceLoader server end point. @@ -1106,8 +1115,8 @@ * 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. + * @param {StringSet} [unresolved] Used to track the current dependency + * chain, and to report loops in the dependency graph. * @throws {Error} If any unregistered module or a dependency loop is encountered */ function sortDependencies( module, resolved, unresolved ) { @@ -1144,13 +1153,13 @@ } // Create unresolved if not passed in if ( !unresolved ) { - unresolved = {}; + unresolved = new StringSet(); } // Tracks down dependencies deps = registry[ module ].dependencies; for ( i = 0; i < deps.length; i++ ) { if ( $.inArray( deps[ i ], resolved ) === -1 ) { - if ( unresolved[ deps[ i ] ] ) { + if ( unresolved.has( deps[ i ] ) ) { throw new Error( mw.format( 'Circular reference detected: $1 -> $2', module, @@ -1158,8 +1167,7 @@ ) ); } - // Add to unresolved - unresolved[ module ] = true; + unresolved.add( module ); sortDependencies( deps[ i ], resolved, unresolved ); } } @@ -1215,12 +1223,14 @@ 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(); @@ -1278,35 +1288,46 @@ registry[ module ].state = 'executing'; runScript = function () { - var script, markModuleReady, nestedAddScript, legacyWait, + var script, markModuleReady, nestedAddScript, legacyWait, implicitDependencies, // 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; - markModuleReady = function () { - registry[ module ].state = 'ready'; - handlePending( module ); - }; - nestedAddScript = function ( arr, callback, i ) { - // Recursively call queueModuleScript() in its own callback - // for each element of arr. - if ( i >= arr.length ) { - // We're at the end of the array - callback(); - return; - } - queueModuleScript( arr[ i ], module ).always( function () { - nestedAddScript( arr, callback, i + 1 ); - } ); - }; + script = registry[ module ].script; + markModuleReady = function () { + registry[ module ].state = 'ready'; + handlePending( module ); + }; + nestedAddScript = function ( arr, callback, i ) { + // Recursively call queueModuleScript() in its own callback + // for each element of arr. + if ( i >= arr.length ) { + // We're at the end of the array + callback(); + return; + } - legacyWait = ( $.inArray( module, legacyModules ) !== -1 ) - ? $.Deferred().resolve() - : mw.loader.using( legacyModules ); + queueModuleScript( arr[ i ], module ).always( function () { + nestedAddScript( arr, callback, i + 1 ); + } ); + }; + + implicitDependencies = ( $.inArray( module, legacyModules ) !== -1 ) + ? [] + : legacyModules; + + if ( module === 'user' ) { + // Implicit dependency on the site module. Not real dependency because + // it should run after 'site' regardless of whether it succeeds or fails. + implicitDependencies.push( 'site' ); + } - legacyWait.always( function () { + legacyWait = implicitDependencies.length + ? mw.loader.using( implicitDependencies ) + : $.Deferred().resolve(); + + legacyWait.always( function () { + try { if ( $.isArray( script ) ) { nestedAddScript( script, markModuleReady, 0 ); } else if ( typeof script === 'function' ) { @@ -1319,29 +1340,21 @@ // Site and user modules are legacy scripts that run in the global scope. // This is transported as a string instead of a function to avoid needing // to use string manipulation to undo the function wrapper. - if ( module === 'user' ) { - // Implicit dependency on the site module. Not real dependency because - // it should run after 'site' regardless of whether it succeeds or fails. - mw.loader.using( 'site' ).always( function () { - $.globalEval( script ); - markModuleReady(); - } ); - } else { - $.globalEval( script ); - markModuleReady(); - } + $.globalEval( script ); + markModuleReady(); + } else { // Module without script markModuleReady(); } - } ); - } 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'; - mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } ); - handlePending( module ); - } + } catch ( e ) { + // 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'; + mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } ); + handlePending( module ); + } + } ); }; // Add localizations to message system @@ -1668,31 +1681,32 @@ } /** - * Evaluate a batch of load.php responses retrieved from mw.loader.store. + * Make a versioned key for a specific module. * * @private - * @param {string[]} implementations Array containing pieces of JavaScript code in the - * form of calls to mw.loader#implement(). - * @param {Function} cb Callback in case of failure - * @param {Error} cb.err + * @param {string} module Module name + * @return {string|null} Module key in format '`[name]@[version]`', + * or null if the module does not exist */ - function batchEval( implementations, cb ) { - if ( !implementations.length ) { - return; + 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 }; } - mw.requestIdleCallback( function iterate( deadline ) { - while ( implementations[ 0 ] && deadline.timeRemaining() > 5 ) { - try { - $.globalEval( implementations.shift() ); - } catch ( err ) { - cb( err ); - return; - } - } - if ( implementations[ 0 ] ) { - mw.requestIdleCallback( iterate ); - } - } ); + return { + name: key.slice( 0, index ), + version: key.slice( index ) + }; } /* Public Members */ @@ -1719,7 +1733,7 @@ * @protected */ work: function () { - var q, batch, implementations, sourceModules; + var q, batch, concatSource, origBatch; batch = []; @@ -1749,35 +1763,39 @@ mw.loader.store.init(); if ( mw.loader.store.enabled ) { - implementations = []; - sourceModules = []; + concatSource = []; + origBatch = batch; batch = $.grep( batch, function ( module ) { - var implementation = mw.loader.store.get( module ); - if ( implementation ) { - implementations.push( implementation ); - sourceModules.push( module ); + var source = mw.loader.store.get( module ); + if ( source ) { + concatSource.push( source ); return false; } return true; } ); - batchEval( implementations, function ( err ) { + try { + $.globalEval( concatSource.join( ';' ) ); + } catch ( err ) { // Not good, the cached mw.loader.implement calls failed! This should // never happen, barring ResourceLoader bugs, browser bugs and PEBKACs. // Depending on how corrupt the string is, it is likely that some // modules' implement() succeeded while the ones after the error will // never run and leave their modules in the 'loading' state forever. + // Since this is an error not caused by an individual module but by // something that infected the implement call itself, don't take any // risks and clear everything in this cache. mw.loader.store.clear(); + // Re-add the ones still pending back to the batch and let the server + // repopulate these modules to the cache. + // This means that at most one module will be useless (the one that had + // the error) instead of all of them. mw.track( 'resourceloader.exception', { exception: err, source: 'store-eval' } ); - - // Re-add the failed ones that are still pending back to the batch - var failed = $.grep( sourceModules, function ( module ) { + origBatch = $.grep( origBatch, function ( module ) { return registry[ module ].state === 'loading'; } ); - batchRequest( failed ); - } ); + batch = batch.concat( origBatch ); + } } batchRequest( batch ); @@ -1878,7 +1896,10 @@ * 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 `