},
/**
- * Add (does not replace) parameters for `N$` placeholder values.
+ * Add (does not replace) parameters for `$N` placeholder values.
*
* @param {Array} parameters
* @chainable
*/
jobs = [],
- // Selector cache for the marker element. Use getMarker() to get/use the marker!
- $marker = null,
+ // For getMarker()
+ marker = null,
- // For #addEmbeddedCSS
+ // For addEmbeddedCSS()
cssBuffer = '',
cssBufferTimer = null,
- cssCallbacks = $.Callbacks();
+ cssCallbacks = $.Callbacks(),
+ isIE9 = document.documentMode === 9;
function getMarker() {
- if ( !$marker ) {
+ if ( !marker ) {
// Cache
- $marker = $( 'meta[name="ResourceLoaderDynamicStyles"]' );
- if ( !$marker.length ) {
- mw.log( 'No <meta name="ResourceLoaderDynamicStyles"> found, inserting dynamically' );
- $marker = $( '<meta>' ).attr( 'name', 'ResourceLoaderDynamicStyles' ).appendTo( 'head' );
+ marker = document.querySelector( 'meta[name="ResourceLoaderDynamicStyles"]' );
+ if ( !marker ) {
+ mw.log( 'Create <meta name="ResourceLoaderDynamicStyles"> dynamically' );
+ marker = $( '<meta>' ).attr( 'name', 'ResourceLoaderDynamicStyles' ).appendTo( 'head' )[ 0 ];
}
}
- return $marker;
+ return marker;
}
/**
*
* @private
* @param {string} text CSS text
- * @param {HTMLElement|jQuery} [nextnode=document.head] The element where the style tag
+ * @param {Node} [nextNode] The element where the style tag
* should be inserted before
* @return {HTMLElement} Reference to the created style element
*/
- function newStyleTag( text, nextnode ) {
+ function newStyleTag( text, nextNode ) {
var s = document.createElement( 'style' );
- // Support: IE
- // Must attach to document before setting cssText (bug 33305)
- if ( nextnode ) {
- $( nextnode ).before( s );
+
+ s.appendChild( document.createTextNode( text ) );
+ if ( nextNode && nextNode.parentNode ) {
+ nextNode.parentNode.insertBefore( s, nextNode );
} else {
document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
}
- if ( s.styleSheet ) {
- // Support: IE6-10
- // Old IE ignores appended text nodes, access stylesheet directly.
- s.styleSheet.cssText = text;
- } else {
- // Standard behaviour
- s.appendChild( document.createTextNode( text ) );
- }
+
return s;
}
* @param {Function} [callback]
*/
function addEmbeddedCSS( cssText, callback ) {
- var $style, styleEl, newCssText;
+ var $style, styleEl;
function fireCallbacks() {
var oldCallbacks = cssCallbacks;
cssBuffer = '';
}
- // By default, always create a new <style>. Appending text to a <style>
- // tag is bad as it means the contents have to be re-parsed (bug 45810).
+ // By default, always create a new <style>. Appending text to a <style> tag is
+ // is a performance anti-pattern as it requires CSS to be reparsed (T47810).
//
- // Except, of course, in IE 9 and below. In there we default to re-using and
- // appending to a <style> tag due to the IE stylesheet limit (bug 31676).
- if ( 'documentMode' in document && document.documentMode <= 9 ) {
-
- $style = getMarker().prev();
- // Verify that the element before the marker actually is a
- // <style> tag and one that came from ResourceLoader
- // (not some other style tag or even a `<meta>` or `<script>`).
+ // Support: IE 6-9
+ // Try to re-use existing <style> tags due to the IE stylesheet limit (T33676).
+ if ( isIE9 ) {
+ $style = $( getMarker() ).prev();
+ // Verify that the element before the marker actually is a <style> tag created
+ // by mw.loader (not some other style tag, or e.g. a <meta> tag).
if ( $style.data( 'ResourceLoaderDynamicStyleTag' ) ) {
- // There's already a dynamic <style> tag present and
- // we are able to append more to it.
- styleEl = $style.get( 0 );
- // Support: IE6-10
- if ( styleEl.styleSheet ) {
- try {
- // Support: IE9
- // We can't do styleSheet.cssText += cssText, since IE9 mangles this property on
- // write, dropping @media queries from the CSS text. If we read it and used its
- // value, we would accidentally apply @media-specific styles to all media. (T108727)
- if ( document.documentMode === 9 ) {
- newCssText = $style.data( 'ResourceLoaderDynamicStyleTag' ) + cssText;
- styleEl.styleSheet.cssText = newCssText;
- $style.data( 'ResourceLoaderDynamicStyleTag', newCssText );
- } else {
- styleEl.styleSheet.cssText += cssText;
- }
- } catch ( e ) {
- mw.track( 'resourceloader.exception', { exception: e, source: 'stylesheet' } );
- }
- } else {
- styleEl.appendChild( document.createTextNode( cssText ) );
- }
+ styleEl = $style[ 0 ];
+ styleEl.appendChild( document.createTextNode( cssText ) );
fireCallbacks();
return;
}
+ // Else: No existing tag to reuse. Continue below and create the first one.
}
$style = $( newStyleTag( cssText, getMarker() ) );
- if ( document.documentMode === 9 ) {
- // Support: IE9
- // Preserve original CSS text because IE9 mangles it on write
- $style.data( 'ResourceLoaderDynamicStyleTag', cssText );
- } else {
+ if ( isIE9 ) {
$style.data( 'ResourceLoaderDynamicStyleTag', true );
}
* @param {string} [moduleName] Name of currently executing module
* @return {jQuery.Promise}
*/
- function queueModuleScript( src ) {
+ function queueModuleScript( src, moduleName ) {
var r = $.Deferred();
pendingRequests.push( function () {
+ if ( moduleName && hasOwn.call( registry, moduleName ) ) {
+ window.require = mw.loader.require;
+ window.module = registry[ moduleName ].module;
+ }
addScript( src ).always( function () {
+ // Clear environment
+ delete window.require;
+ delete window.module;
r.resolve();
// Start the next one (if any)
*/
function addLink( media, url ) {
var el = document.createElement( 'link' );
- // Support: IE
- // Insert in document *before* setting href
- getMarker().before( el );
+
el.rel = 'stylesheet';
if ( media && media !== 'all' ) {
el.media = media;
// If you end up here from an IE exception "SCRIPT: Invalid property value.",
// see #addEmbeddedCSS, bug 31676, and bug 47277 for details.
el.href = url;
+
+ $( getMarker() ).before( el );
}
/**
}
return;
}
- // Validate input
- if ( typeof module !== 'string' ) {
- throw new Error( 'module must be a string, not a ' + typeof module );
- }
if ( hasOwn.call( registry, module ) ) {
throw new Error( 'module already registered: ' + module );
}
* @param {Object} [templates] List of key/value pairs to be added to mw#templates.
*/
implement: function ( module, script, style, messages, templates ) {
- // Validate input
- if ( typeof module !== 'string' ) {
- throw new Error( 'module must be of type string, not ' + typeof module );
- }
- if ( script && !$.isFunction( script ) && !$.isArray( script ) && typeof script !== 'string' ) {
- throw new Error( 'script must be of type function, array, or script; not ' + typeof script );
- }
- if ( style && !$.isPlainObject( style ) ) {
- throw new Error( 'style must be of type object, not ' + typeof style );
- }
- if ( messages && !$.isPlainObject( messages ) ) {
- throw new Error( 'messages must be of type object, not a ' + typeof messages );
- }
- if ( templates && !$.isPlainObject( templates ) ) {
- throw new Error( 'templates must be of type object, not a ' + typeof templates );
- }
// Automatically register module
if ( !hasOwn.call( registry, module ) ) {
mw.loader.register( module );
// Allow calling with a single dependency as a string
if ( typeof dependencies === 'string' ) {
dependencies = [ dependencies ];
- } else if ( !$.isArray( dependencies ) ) {
- // Invalid input
- throw new Error( 'Dependencies must be a string or an array' );
}
if ( ready ) {
load: function ( modules, type ) {
var filtered, l;
- // Validate input
- if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
- throw new Error( 'modules must be a string or an array, not a ' + typeof modules );
- }
// Allow calling with a url or single dependency as a string
if ( typeof modules === 'string' ) {
// "https://example.org/x.js", "http://example.org/x.js", "//example.org/x.js", "/x.js"