* @alternateClassName mediaWiki
* @singleton
*/
+/*global sha1 */
( function ( $ ) {
'use strict';
hasOwn = Object.prototype.hasOwnProperty,
slice = Array.prototype.slice,
trackCallbacks = $.Callbacks( 'memory' ),
+ trackHandlers = [],
trackQueue = [];
/**
*/
trackSubscribe: function ( topic, callback ) {
var seen = 0;
-
- trackCallbacks.add( function ( trackQueue ) {
+ function handler( trackQueue ) {
var event;
for ( ; seen < trackQueue.length; seen++ ) {
event = trackQueue[ seen ];
callback.call( event, event.topic, event.data );
}
}
+ }
+
+ trackHandlers.push( [ handler, callback ] );
+
+ trackCallbacks.add( handler );
+ },
+
+ /**
+ * Stop handling events for a particular handler
+ *
+ * @param {Function} callback
+ */
+ trackUnsubscribe: function ( callback ) {
+ trackHandlers = $.grep( trackHandlers, function ( fns ) {
+ if ( fns[1] === callback ) {
+ trackCallbacks.remove( fns[0] );
+ // Ensure the tuple is removed to avoid holding on to closures
+ return false;
+ }
+ return true;
} );
},
}
};
+ /**
+ * Write a message 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 = function () {
+ var console = window.console;
+ if ( console && console.error && console.error.apply ) {
+ console.error.apply( console, arguments );
+ }
+ };
+
/**
* Create a property in a host object that, when accessed, will produce
* a deprecation warning in the console with backtrace.
* {
* 'moduleName': {
* // From startup mdoule
- * 'version': ############## (unix timestamp)
+ * 'version': '################' (Hash)
* 'dependencies': ['required.foo', 'bar.also', ...], (or) function () {}
* 'group': 'somegroup', (or) null
* 'source': 'local', (or) 'anotherwiki'
}
/**
- * Zero-pad three numbers.
- *
- * @private
- * @param {number} a
- * @param {number} b
- * @param {number} c
- * @return {string}
- */
- function pad( a, b, c ) {
- return (
- ( a < 10 ? '0' : '' ) + a +
- ( b < 10 ? '0' : '' ) + b +
- ( c < 10 ? '0' : '' ) + c
- );
- }
-
- /**
- * Convert UNIX timestamp to ISO8601 format.
- *
- * @private
- * @param {number} timestamp UNIX timestamp
+ * @since 1.26
+ * @param {Object[]} modules List of module registry objects
+ * @return {string} Hash of concatenated version hashes.
*/
- function formatVersionNumber( timestamp ) {
- var d = new Date();
- d.setTime( timestamp * 1000 );
- return [
- pad( d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate() ),
- 'T',
- pad( d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ),
- 'Z'
- ].join( '' );
+ function getCombinedVersion( modules ) {
+ var hashes = $.map( modules, function ( module ) {
+ return 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
+ // likely to require multiple HTTP requests.
+ return sha1( hashes.join( '' ) ).slice( 0, 12 );
}
/**
*/
work: function () {
var reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
- source, concatSource, origBatch, group, g, i, modules, maxVersion, sourceLoadScript,
+ source, concatSource, origBatch, group, i, modules, sourceLoadScript,
currReqBase, currReqBaseLength, moduleMap, l,
lastDotIndex, prefix, suffix, bytesAdded, async;
// modules for this group from this source.
modules = splits[source][group];
- // Calculate the highest timestamp
- maxVersion = 0;
- for ( g = 0; g < modules.length; g += 1 ) {
- if ( registry[modules[g]].version > maxVersion ) {
- maxVersion = registry[modules[g]].version;
- }
- }
-
- currReqBase = $.extend( { version: formatVersionNumber( maxVersion ) }, reqBase );
+ currReqBase = $.extend( {
+ version: getCombinedVersion( modules )
+ }, reqBase );
// For user modules append a user name to the request.
if ( group === 'user' && mw.config.get( 'wgUserName' ) !== null ) {
currReqBase.user = mw.config.get( 'wgUserName' );
},
/**
- * Register a module, letting the system know about it and its
- * properties. Startup modules contain calls to this function.
+ * Register a module, letting the system know about it and its properties.
+ *
+ * The startup modules contain calls to this method.
*
* When using multiple module registration by passing an array, dependencies that
* are specified as references to modules within the array will be resolved before
*
* @param {string|Array} module Module name or array of arrays, each containing
* a list of arguments compatible with this method
- * @param {number} version Module version number as a timestamp (falls backs to 0)
+ * @param {string|number} version Module version hash (falls backs to empty string)
+ * Can also be a number (timestamp) for compatibility with MediaWiki 1.25 and earlier.
* @param {string|Array|Function} dependencies One string or array of strings of module
* names on which this module depends, or a function that returns that array.
* @param {string} [group=null] Group which the module is in
}
// List the module as registered
registry[module] = {
- version: version !== undefined ? parseInt( version, 10 ) : 0,
+ version: version !== undefined ? String( version ) : '',
dependencies: [],
group: typeof group === 'string' ? group : null,
source: typeof source === 'string' ? source : 'local',
if ( !hasOwn.call( registry, module ) || registry[module].version === undefined ) {
return null;
}
- return formatVersionNumber( registry[module].version );
+ return registry[module].version;
},
/**