*/
var mw = ( function ( $, undefined ) {
- "use strict";
+ 'use strict';
/* Private Members */
* Gets a message object, similar to wfMessage()
*
* @param key string Key of message to get
- * @param parameter_1 mixed First argument in a list of variadic arguments,
+ * @param parameter1 mixed First argument in a list of variadic arguments,
* each a parameter for $N replacement in messages.
* @return Message
*/
- message: function ( key, parameter_1 /* [, parameter_2] */ ) {
+ message: function ( key, parameter1 ) {
var parameters;
// Support variadic arguments
- if ( parameter_1 !== undefined ) {
+ if ( parameter1 !== undefined ) {
parameters = slice.call( arguments );
parameters.shift();
} else {
* Create a new style tag and add it to the DOM.
*
* @param text String: CSS text
- * @param $nextnode mixed: [optional] An Element or jQuery object for an element where
+ * @param nextnode mixed: [optional] An Element or jQuery object for an element where
* the style tag should be inserted before. Otherwise appended to the <head>.
* @return HTMLStyleElement
*/
- function addStyleTag( text, $nextnode ) {
+ function addStyleTag( text, nextnode ) {
var s = document.createElement( 'style' );
- s.type = 'text/css';
- s.rel = 'stylesheet';
// Insert into document before setting cssText (bug 33305)
- if ( $nextnode ) {
- // If a raw element, create a jQuery object, otherwise use directly
- if ( $nextnode.nodeType ) {
- $nextnode = $( $nextnode );
+ if ( nextnode ) {
+ // Must be inserted with native insertBefore, not $.fn.before.
+ // When using jQuery to insert it, like $nextnode.before( s ),
+ // then IE6 will throw "Access is denied" when trying to append
+ // to .cssText later. Some kind of weird security measure.
+ // http://stackoverflow.com/q/12586482/319266
+ // Works: jsfiddle.net/zJzMy/1
+ // Fails: jsfiddle.net/uJTQz
+ // Works again: http://jsfiddle.net/Azr4w/ (diff: the next 3 lines)
+ if ( nextnode.jquery ) {
+ nextnode = nextnode.get( 0 );
}
- $nextnode.before( s );
+ nextnode.parentNode.insertBefore( s, nextnode );
} else {
- document.getElementsByTagName('head')[0].appendChild( s );
+ document.getElementsByTagName( 'head' )[0].appendChild( s );
}
if ( s.styleSheet ) {
- s.styleSheet.cssText = text; // IE
+ // IE
+ s.styleSheet.cssText = text;
} else {
- // Safari sometimes borks on null
+ // Other browsers.
+ // (Safari sometimes borks on non-string values,
+ // play safe by casting to a string, just in case.)
s.appendChild( document.createTextNode( String( text ) ) );
}
return s;
}
- function addInlineCSS( css ) {
- var $style, style, $newStyle;
+ /**
+ * Checks if certain cssText is safe to append to
+ * a stylesheet.
+ *
+ * Right now it only makes sure that cssText containing @import
+ * rules will end up in a new stylesheet (as those only work when
+ * placed at the start of a stylesheet; bug 35562).
+ * This could later be extended to take care of other bugs, such as
+ * the IE cssRules limit - not the same as the IE styleSheets limit).
+ */
+ function canExpandStylesheetWith( $style, cssText ) {
+ return cssText.indexOf( '@import' ) === -1;
+ }
+
+ function addEmbeddedCSS( cssText ) {
+ var $style, styleEl;
$style = getMarker().prev();
- // Disable <style> tag recycling/concatenation because of bug 34669
- if ( false && $style.is( 'style' ) && $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) {
- // There's already a dynamic <style> tag present, append to it. This recycling of
- // <style> tags is for bug 31676 (can't have more than 32 <style> tags in IE)
- style = $style.get( 0 );
- if ( style.styleSheet ) {
- style.styleSheet.cssText += css; // IE
+ // Re-use <style> tags if possible, this to try to stay
+ // under the IE stylesheet limit (bug 31676).
+ // Also verify that the the element before Marker actually is one
+ // that came from ResourceLoader, and not a style tag that some
+ // other script inserted before our marker, or, more importantly,
+ // it may not be a style tag at all (could be <meta> or <script>).
+ if (
+ $style.data( 'ResourceLoaderDynamicStyleTag' ) === true &&
+ canExpandStylesheetWith( $style, cssText )
+ ) {
+ // There's already a dynamic <style> tag present and
+ // canExpandStylesheetWith() gave a green light to append more to it.
+ styleEl = $style.get( 0 );
+ if ( styleEl.styleSheet ) {
+ try {
+ styleEl.styleSheet.cssText += cssText; // IE
+ } catch ( e ) {
+ log( 'addEmbeddedCSS fail\ne.message: ' + e.message, e );
+ }
} else {
- style.appendChild( document.createTextNode( String( css ) ) );
+ styleEl.appendChild( document.createTextNode( String( cssText ) ) );
}
} else {
- $newStyle = $( addStyleTag( css, getMarker() ) )
+ $( addStyleTag( cssText, getMarker() ) )
.data( 'ResourceLoaderDynamicStyleTag', true );
}
}
- function compare( a, b ) {
- var i;
- if ( a.length !== b.length ) {
- return false;
- }
- for ( i = 0; i < b.length; i += 1 ) {
- if ( $.isArray( a[i] ) ) {
- if ( !compare( a[i], b[i] ) ) {
- return false;
- }
- }
- if ( a[i] !== b[i] ) {
- return false;
- }
- }
- return true;
- }
-
/**
* Generates an ISO8601 "basic" string from a UNIX timestamp
*/
function formatVersionNumber( timestamp ) {
- var pad = function ( a, b, c ) {
- return [a < 10 ? '0' + a : a, b < 10 ? '0' + b : b, c < 10 ? '0' + c : c].join( '' );
- },
- d = new Date();
+ var d = new Date();
+ function pad( a, b, c ) {
+ return [a < 10 ? '0' + a : a, b < 10 ? '0' + b : b, c < 10 ? '0' + c : c].join( '' );
+ }
d.setTime( timestamp * 1000 );
return [
pad( d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate() ), 'T',
j -= 1;
try {
if ( hasErrors ) {
- throw new Error ("Module " + module + " failed.");
+ throw new Error( 'Module ' + module + ' failed.');
} else {
if ( $.isFunction( job.ready ) ) {
job.ready();
// Using isReady directly instead of storing it locally from
// a $.fn.ready callback (bug 31895).
if ( $.isReady || async ) {
- // jQuery's getScript method is NOT better than doing this the old-fashioned way
- // because jQuery will eval the script's code, and errors will not have sane
- // line numbers.
+ // Can't use jQuery.getScript because that only uses <script> for cross-domain,
+ // it uses XHR and eval for same-domain scripts, which we don't want because it
+ // messes up line numbers.
+ // The below is based on jQuery ([jquery@1.8.2]/src/ajax/script.js)
+
+ // IE-safe way of getting the <head>. document.head isn't supported
+ // in old IE, and doesn't work when in the <head>.
+ head = document.getElementsByTagName( 'head' )[0] || document.body;
+
script = document.createElement( 'script' );
- script.setAttribute( 'src', src );
- script.setAttribute( 'type', 'text/javascript' );
+ script.async = true;
+ script.src = src;
if ( $.isFunction( callback ) ) {
- // Attach handlers for all browsers (based on jQuery.ajax)
script.onload = script.onreadystatechange = function () {
-
if (
!done
&& (
|| /loaded|complete/.test( script.readyState )
)
) {
-
done = true;
- callback();
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
- // Handle memory leak in IE. This seems to fail in
- // IE7 sometimes (Permission Denied error when
- // accessing script.parentNode) so wrap it in
- // a try catch.
- try {
- script.onload = script.onreadystatechange = null;
- if ( script.parentNode ) {
- script.parentNode.removeChild( script );
- }
-
- // Dereference the script
- script = undefined;
- } catch ( e ) { }
+ // Remove the script
+ if ( script.parentNode ) {
+ script.parentNode.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ callback();
}
};
}
if ( window.opera ) {
// Appending to the <head> blocks rendering completely in Opera,
// so append to the <body> after document ready. This means the
- // scripts only start loading after the document has been rendered,
+ // scripts only start loading after the document has been rendered,
// but so be it. Opera users don't deserve faster web pages if their
- // browser makes it impossible
- $( function () { document.body.appendChild( script ); } );
+ // browser makes it impossible.
+ $( function () {
+ document.body.appendChild( script );
+ } );
} else {
- // IE-safe way of getting the <head> . document.documentElement.head doesn't
- // work in scripts that run in the <head>
- head = document.getElementsByTagName( 'head' )[0];
- ( document.body || head ).appendChild( script );
+ head.appendChild( script );
}
} else {
- document.write( mw.html.element(
- 'script', { 'type': 'text/javascript', 'src': src }, ''
- ) );
+ document.write( mw.html.element( 'script', { 'src': src }, '' ) );
if ( $.isFunction( callback ) ) {
// Document.write is synchronous, so this is called when it's done
// FIXME: that's a lie. doc.write isn't actually synchronous
* @param module string module name to execute
*/
function execute( module ) {
- var style, media, i, script, markModuleReady, nestedAddScript;
+ var key, value, media, i, urls, script, markModuleReady, nestedAddScript;
if ( registry[module] === undefined ) {
throw new Error( 'Module has not been registered yet: ' + module );
throw new Error( 'Module has already been loaded: ' + module );
}
- // Add styles
+ /**
+ * Define loop-function here for efficiency
+ * and to avoid re-using badly scoped variables.
+ */
+ function addLink( media, url ) {
+ var el = document.createElement( 'link' );
+ getMarker().before( el ); // IE: Insert in dom before setting href
+ el.rel = 'stylesheet';
+ if ( media && media !== 'all' ) {
+ el.media = media;
+ }
+ el.href = url;
+ }
+
+ // Process styles (see also mw.loader.implement)
+ // * back-compat: { <media>: css }
+ // * back-compat: { <media>: [url, ..] }
+ // * { "css": [css, ..] }
+ // * { "url": { <media>: [url, ..] } }
if ( $.isPlainObject( registry[module].style ) ) {
- // 'media' type ignored, see documentation of mw.loader.implement
- for ( media in registry[module].style ) {
- style = registry[module].style[media];
- if ( $.isArray( style ) ) {
- for ( i = 0; i < style.length; i += 1 ) {
- getMarker().before( mw.html.element( 'link', {
- 'type': 'text/css',
- 'rel': 'stylesheet',
- 'href': style[i]
- } ) );
+ for ( key in registry[module].style ) {
+ value = registry[module].style[key];
+ media = undefined;
+
+ if ( key !== 'url' && key !== 'css' ) {
+ // Backwards compatibility, key is a media-type
+ if ( typeof value === 'string' ) {
+ // back-compat: { <media>: css }
+ // Ignore 'media' because it isn't supported (nor was it used).
+ // Strings are pre-wrapped in "@media". The media-type was just ""
+ // (because it had to be set to something).
+ // This is one of the reasons why this format is no longer used.
+ addEmbeddedCSS( value );
+ } else {
+ // back-compat: { <media>: [url, ..] }
+ media = key;
+ key = 'bc-url';
+ }
+ }
+
+ // Array of css strings in key 'css',
+ // or back-compat array of urls from media-type
+ if ( $.isArray( value ) ) {
+ for ( i = 0; i < value.length; i += 1 ) {
+ if ( key === 'bc-url' ) {
+ // back-compat: { <media>: [url, ..] }
+ addLink( media, value[i] );
+ } else if ( key === 'css' ) {
+ // { "css": [css, ..] }
+ addEmbeddedCSS( value[i] );
+ }
+ }
+ // Not an array, but a regular object
+ // Array of urls inside media-type key
+ } else if ( typeof value === 'object' ) {
+ // { "url": { <media>: [url, ..] } }
+ for ( media in value ) {
+ urls = value[media];
+ for ( i = 0; i < urls.length; i += 1 ) {
+ addLink( media, urls[i] );
+ }
}
- } else if ( typeof style === 'string' ) {
- addInlineCSS( style );
}
}
}
+
// Add localizations to message system
if ( $.isPlainObject( registry[module].messages ) ) {
mw.messages.set( registry[module].messages );
}
+
// Execute script
try {
script = registry[module].script;
} 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
- log('mw.loader::execute> Exception thrown by ' + module + ': ' + e.message, e);
+ log( 'Exception thrown by ' + module + ': ' + e.message, e );
registry[module].state = 'error';
handlePending( module );
}
* document ready has not yet occurred
*/
function request( dependencies, ready, error, async ) {
- var regItemDeps, regItemDepLen, n;
+ var n;
// Allow calling by single module name
if ( typeof dependencies === 'string' ) {
*/
function doRequest( moduleMap, currReqBase, sourceLoadScript, async ) {
var request = $.extend(
- { 'modules': buildModulesString( moduleMap ) },
+ { modules: buildModulesString( moduleMap ) },
currReqBase
);
request = sortQuery( request );
}
}
- currReqBase = $.extend( { 'version': formatVersionNumber( maxVersion ) }, reqBase );
+ currReqBase = $.extend( { version: formatVersionNumber( maxVersion ) }, reqBase );
// For user modules append a user name to the request.
- if ( group === "user" && mw.config.get( 'wgUserName' ) !== null ) {
+ if ( group === 'user' && mw.config.get( 'wgUserName' ) !== null ) {
currReqBase.user = mw.config.get( 'wgUserName' );
}
currReqBaseLength = $.param( currReqBase ).length;
}
// List the module as registered
registry[module] = {
- 'version': version !== undefined ? parseInt( version, 10 ) : 0,
- 'dependencies': [],
- 'group': typeof group === 'string' ? group : null,
- 'source': typeof source === 'string' ? source: 'local',
- 'state': 'registered'
+ version: version !== undefined ? parseInt( version, 10 ) : 0,
+ dependencies: [],
+ group: typeof group === 'string' ? group : null,
+ source: typeof source === 'string' ? source: 'local',
+ state: 'registered'
};
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
*
* All arguments are required.
*
- * @param module String: Name of module
- * @param script Mixed: Function of module code or String of URL to be used as the src
- * attribute when adding a script element to the body
- * @param style Object: Object of CSS strings keyed by media-type or Object of lists of URLs
- * keyed by media-type. Media-type should be "all" or "", actual types are not supported
- * right now due to the way execute() processes the stylesheets (they are concatenated
- * into a single <style> tag). In the past these weren't concatenated together (which is
- * these are keyed by media-type), but bug 31676 forces us to. In practice this is not a
- * problem because ResourceLoader only generates stylesheets for media-type all (e.g. print
- * stylesheets are wrapped in @media print {} and concatenated with the others).
- * @param msgs Object: List of key/value pairs to be passed through mw.messages.set
+ * @param {String} module Name of module
+ * @param {Function|Array} script Function with module code or Array of URLs to
+ * be used as the src attribute of a new <script> tag.
+ * @param {Object} style Should follow one of the following patterns:
+ * { "css": [css, ..] }
+ * { "url": { <media>: [url, ..] } }
+ * And for backwards compatibility (needs to be supported forever due to caching):
+ * { <media>: css }
+ * { <media>: [url, ..] }
+ *
+ * The reason css strings are not concatenated anymore is bug 31676. We now check
+ * whether it's safe to extend the stylesheet (see canExpandStylesheetWith).
+ *
+ * @param {Object} msgs List of key/value pairs to be passed through mw.messages.set
*/
implement: function ( module, script, style, msgs ) {
// Validate input
}
// Allow calling with a single dependency as a string
if ( tod === 'string' ) {
- dependencies = [dependencies];
+ dependencies = [ dependencies ];
}
// Resolve entire dependency map
dependencies = resolve( dependencies );
* be assumed if loading a URL, and false will be assumed otherwise.
*/
load: function ( modules, type, async ) {
- var filtered, m, module;
+ var filtered, m, module, l;
// Validate input
if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
async = true;
}
if ( type === 'text/css' ) {
- $( 'head' ).append( $( '<link>', {
- rel: 'stylesheet',
- type: 'text/css',
- href: modules
- } ) );
+ // IE7-8 throws security warnings when inserting a <link> tag
+ // with a protocol-relative URL set though attributes (instead of
+ // properties) - when on HTTPS. See also bug #.
+ l = document.createElement( 'link' );
+ l.rel = 'stylesheet';
+ l.href = modules;
+ $( 'head' ).append( l );
return;
}
if ( type === 'text/javascript' || type === undefined ) {
throw new Error( 'invalid type for external url, must be text/css or text/javascript. not ' + type );
}
// Called with single module
- modules = [modules];
+ modules = [ modules ];
}
// Filter out undefined modules, otherwise resolve() will throw
if ( registry[module] === undefined ) {
mw.loader.register( module );
}
- if ( $.inArray(state, ['ready', 'error', 'missing']) !== -1
+ 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!
html: ( function () {
function escapeCallback( s ) {
switch ( s ) {
- case "'":
+ case '\'':
return ''';
case '"':
return '"';