/**
* Base library for MediaWiki.
*
- * Exposed globally as `mediaWiki` with `mw` as shortcut.
+ * Exposed globally as `mw`, with `mediaWiki` as alias.
*
* @class mw
* @alternateClassName mediaWiki
* @class
*/
function StringSet() {
- this.set = {};
+ this.set = Object.create( null );
}
StringSet.prototype.add = function ( value ) {
this.set[ value ] = true;
};
StringSet.prototype.has = function ( value ) {
- return hasOwn.call( this.set, value );
+ return value in this.set;
};
return StringSet;
}() );
* copied in one direction only. Changes to globals do not reflect in the map.
*/
function Map( global ) {
- this.values = {};
+ this.values = Object.create( null );
if ( global === true ) {
// Override #set to also set the global variable
this.set = function ( selection, value ) {
results = {};
for ( i = 0; i < selection.length; i++ ) {
if ( typeof selection[ i ] === 'string' ) {
- results[ selection[ i ] ] = hasOwn.call( this.values, selection[ i ] ) ?
+ results[ selection[ i ] ] = selection[ i ] in this.values ?
this.values[ selection[ i ] ] :
fallback;
}
}
if ( typeof selection === 'string' ) {
- return hasOwn.call( this.values, selection ) ?
+ return selection in this.values ?
this.values[ selection ] :
fallback;
}
var i;
if ( Array.isArray( selection ) ) {
for ( i = 0; i < selection.length; i++ ) {
- if ( typeof selection[ i ] !== 'string' || !hasOwn.call( this.values, selection[ i ] ) ) {
+ if ( typeof selection[ i ] !== 'string' || !( selection[ i ] in this.values ) ) {
return false;
}
}
return true;
}
- return typeof selection === 'string' && hasOwn.call( this.values, selection );
+ return typeof selection === 'string' && selection in this.values;
}
};
log.deprecate = !Object.defineProperty ? function ( obj, key, val ) {
obj[ key ] = val;
} : function ( obj, key, val, msg, logName ) {
- var logged = new StringSet();
- logName = logName || key;
- msg = 'Use of "' + logName + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' );
- function uniqueTrace() {
- var trace = new Error().stack;
- if ( logged.has( trace ) ) {
- return false;
+ var stacks;
+ function maybeLog() {
+ var name,
+ trace = new Error().stack;
+ if ( !stacks ) {
+ stacks = new StringSet();
+ }
+ if ( !stacks.has( trace ) ) {
+ stacks.add( trace );
+ name = logName || key;
+ mw.track( 'mw.deprecate', name );
+ mw.log.warn(
+ 'Use of "' + name + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' )
+ );
}
- logged.add( trace );
- return true;
}
// Support: Safari 5.0
// Throws "not supported on DOM Objects" for Node or Element objects (incl. document)
configurable: true,
enumerable: true,
get: function () {
- if ( uniqueTrace() ) {
- mw.track( 'mw.deprecate', logName );
- mw.log.warn( msg );
- }
+ maybeLog();
return val;
},
set: function ( newVal ) {
- if ( uniqueTrace() ) {
- mw.track( 'mw.deprecate', logName );
- mw.log.warn( msg );
- }
+ maybeLog();
val = newVal;
}
} );
* @param {string} module Module name to execute
*/
function execute( module ) {
- var key, value, media, i, urls, cssHandle, checkCssHandles, runScript,
- cssHandlesRegistered = false;
+ var key, value, media, i, urls, cssHandle, siteDeps, siteDepErr, runScript,
+ cssPending = 0;
if ( !hasOwn.call( registry, module ) ) {
throw new Error( 'Module has not been registered yet: ' + module );
mw.templates.set( module, registry[ module ].templates );
}
- // Make sure we don't run the scripts until all stylesheet insertions have completed.
- ( function () {
- var pending = 0;
- checkCssHandles = function () {
- var ex, dependencies;
- // cssHandlesRegistered ensures we don't take off too soon, e.g. when
- // one of the cssHandles is fired while we're still creating more handles.
- if ( cssHandlesRegistered && pending === 0 && runScript ) {
- 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.
- // Note: This is a simplified version of mw.loader.using(), inlined here
- // as using() depends on jQuery (T192623).
- try {
- dependencies = resolve( [ 'site' ] );
- } catch ( e ) {
- ex = e;
- runScript();
- }
- if ( ex === undefined ) {
- enqueue( dependencies, runScript, runScript );
- }
- } else {
- runScript();
- }
- runScript = undefined; // Revoke
+ // Adding of stylesheets is asynchronous via addEmbeddedCSS().
+ // The below function uses a counting semaphore to make sure we don't call
+ // runScript() until after this module's stylesheets have been inserted
+ // into the DOM.
+ cssHandle = function () {
+ // Increase semaphore, when creating a callback for addEmbeddedCSS.
+ cssPending++;
+ return function () {
+ var runScriptCopy;
+ // Decrease semaphore, when said callback is invoked.
+ cssPending--;
+ if ( cssPending === 0 ) {
+ // Paranoia:
+ // This callback is exposed to addEmbeddedCSS, which is outside the execute()
+ // function and is not concerned with state-machine integrity. In turn,
+ // addEmbeddedCSS() actually exposes stuff further into the browser (rAF).
+ // If increment and decrement callbacks happen in the wrong order, or start
+ // again afterwards, then this branch could be reached multiple times.
+ // To protect the integrity of the state-machine, prevent that from happening
+ // by making runScript() cannot be called more than once. We store a private
+ // reference when we first reach this branch, then deference the original, and
+ // call our reference to it.
+ runScriptCopy = runScript;
+ runScript = undefined;
+ runScriptCopy();
}
};
- cssHandle = function () {
- var check = checkCssHandles;
- pending++;
- return function () {
- if ( check ) {
- pending--;
- check();
- check = undefined; // Revoke
- }
- };
- };
- }() );
+ };
// Process styles (see also mw.loader.implement)
// * back-compat: { <media>: css }
}
}
- // End profiling of execute()-self before we call checkCssHandles(),
- // which (sometimes asynchronously) calls runScript(), which we want
- // to measure separately without overlap.
+ // End profiling of execute()-self before we call runScript(),
+ // which we want to measure separately without overlap.
$CODE.profileExecuteEnd();
- // Kick off.
- cssHandlesRegistered = true;
- checkCssHandles();
+ if ( module === 'user' ) {
+ // Implicit dependency on the site module. Not a real dependency because it should
+ // run after 'site' regardless of whether it succeeds or fails.
+ // Note: This is a simplified version of mw.loader.using(), inlined here because
+ // mw.loader.using() is part of mediawiki.base (depends on jQuery; T192623).
+ try {
+ siteDeps = resolve( [ 'site' ] );
+ } catch ( e ) {
+ siteDepErr = e;
+ runScript();
+ }
+ if ( siteDepErr === undefined ) {
+ enqueue( siteDeps, runScript, runScript );
+ }
+ } else if ( cssPending === 0 ) {
+ // Regular module without styles
+ runScript();
+ }
+ // else: runScript will get called via cssHandle()
}
function sortQuery( o ) {
// Allow multiple registration
if ( typeof module === 'object' ) {
resolveIndexedDependencies( module );
+ // module is an array of arrays
for ( i = 0; i < module.length; i++ ) {
// module is an array of module names
- 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 ] );
- }
+ mw.loader.register.apply( mw.loader, module[ i ] );
}
return;
}
mw.loader.register( name );
}
// Check for duplicate implementation
- if ( hasOwn.call( registry, name ) && registry[ name ].script !== undefined ) {
+ if ( registry[ name ].script !== undefined ) {
throw new Error( 'module already implemented: ' + name );
}
if ( version ) {
init: function () {
var raw, data;
- if ( mw.loader.store.enabled !== null ) {
+ if ( this.enabled !== null ) {
// Init already ran
return;
}
!mw.config.get( 'wgResourceLoaderStorageEnabled' )
) {
// Clear any previous store to free up space. (T66721)
- mw.loader.store.clear();
- mw.loader.store.enabled = false;
+ this.clear();
+ this.enabled = false;
return;
}
if ( mw.config.get( 'debug' ) ) {
// Disable module store in debug mode
- mw.loader.store.enabled = false;
+ this.enabled = false;
return;
}
try {
// This a string we stored, or `null` if the key does not (yet) exist.
- raw = localStorage.getItem( mw.loader.store.getStoreKey() );
+ raw = localStorage.getItem( this.getStoreKey() );
// If we get here, localStorage is available; mark enabled
- mw.loader.store.enabled = true;
+ this.enabled = true;
// If null, JSON.parse() will cast to string and re-parse, still null.
data = JSON.parse( raw );
- if ( data && typeof data.items === 'object' && data.vary === mw.loader.store.getVary() ) {
- mw.loader.store.items = data.items;
+ if ( data && typeof data.items === 'object' && data.vary === this.getVary() ) {
+ this.items = data.items;
return;
}
} catch ( e ) {
// We will disable the store below.
if ( raw === undefined ) {
// localStorage failed; disable store
- mw.loader.store.enabled = false;
+ this.enabled = false;
}
},
get: function ( module ) {
var key;
- if ( !mw.loader.store.enabled ) {
+ if ( !this.enabled ) {
return false;
}
key = getModuleKey( module );
- if ( key in mw.loader.store.items ) {
- mw.loader.store.stats.hits++;
- return mw.loader.store.items[ key ];
+ if ( key in this.items ) {
+ this.stats.hits++;
+ return this.items[ key ];
}
- mw.loader.store.stats.misses++;
+ this.stats.misses++;
return false;
},
set: function ( module, descriptor ) {
var args, key, src;
- if ( !mw.loader.store.enabled ) {
+ if ( !this.enabled ) {
return;
}
if (
// Already stored a copy of this exact version
- key in mw.loader.store.items ||
+ key in this.items ||
// Module failed to load
descriptor.state !== 'ready' ||
// Unversioned, private, or site-/user-specific
}
src = 'mw.loader.implement(' + args.join( ',' ) + ');';
- if ( src.length > mw.loader.store.MODULE_SIZE_MAX ) {
+ if ( src.length > this.MODULE_SIZE_MAX ) {
return;
}
- mw.loader.store.items[ key ] = src;
- mw.loader.store.update();
+ this.items[ key ] = src;
+ this.update();
},
/**
prune: function () {
var key, module;
- for ( key in mw.loader.store.items ) {
+ for ( key in this.items ) {
module = key.slice( 0, key.indexOf( '@' ) );
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 ) {
+ this.stats.expired++;
+ delete this.items[ key ];
+ } else if ( this.items[ key ].length > this.MODULE_SIZE_MAX ) {
// This value predates the enforcement of a size limit on cached modules.
- delete mw.loader.store.items[ key ];
+ delete this.items[ key ];
}
}
},
* Clear the entire module store right now.
*/
clear: function () {
- mw.loader.store.items = {};
+ this.items = {};
try {
- localStorage.removeItem( mw.loader.store.getStoreKey() );
+ localStorage.removeItem( this.getStoreKey() );
} catch ( e ) {}
},