r88053 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r88052‎ | r88053 | r88054 >
Date:12:15, 14 May 2011
Author:tparscal
Status:resolved (Comments)
Tags:
Comment:
Added direct file loading functionality to debug mode for both scripts and styles, with callbacks for module state changes (changing to ready) and executing of jobs and modules awaiting dependency resolutions. These changes also provide a way to used mw.loader.implement with arrays of URLs for the scripts and styles arguments, which will make it possible to implement modules using user scripts. This probably solves bug #27023 - tests to verify that will be coming soon.
Modified paths:
  • /trunk/phase3/includes/resourceloader/ResourceLoader.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderFileModule.php (modified) (history)
  • /trunk/phase3/resources/mediawiki/mediawiki.js (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/resourceloader/ResourceLoader.php
@@ -471,14 +471,15 @@
472472 foreach ( $modules as $name => $module ) {
473473 wfProfileIn( __METHOD__ . '-' . $name );
474474 try {
475 - // Scripts
476475 $scripts = '';
477476 if ( $context->shouldIncludeScripts() ) {
478 - // bug 27054: Append semicolon to prevent weird bugs
479 - // caused by files not terminating their statements right
480 - $scripts .= $module->getScript( $context ) . ";\n";
 477+ $scripts = $module->getScript( $context );
 478+ if ( is_string( $scripts ) ) {
 479+ // bug 27054: Append semicolon to prevent weird bugs
 480+ // caused by files not terminating their statements right
 481+ $scripts .= ";\n";
 482+ }
481483 }
482 -
483484 // Styles
484485 $styles = array();
485486 if ( $context->shouldIncludeStyles() ) {
@@ -491,7 +492,11 @@
492493 // Append output
493494 switch ( $context->getOnly() ) {
494495 case 'scripts':
495 - $out .= $scripts;
 496+ if ( is_string( $scripts ) ) {
 497+ $out .= $scripts;
 498+ } else if ( is_array( $scripts ) ) {
 499+ $out .= self::makeLoaderImplementScript( $name, $scripts, array(), array() );
 500+ }
496501 break;
497502 case 'styles':
498503 $out .= self::makeCombinedStyles( $styles );
@@ -504,7 +509,9 @@
505510 // (unless in debug mode)
506511 if ( !$context->getDebug() ) {
507512 foreach ( $styles as $media => $style ) {
508 - $styles[$media] = $this->filter( 'minify-css', $style );
 513+ if ( is_string( $style ) ) {
 514+ $styles[$media] = $this->filter( 'minify-css', $style );
 515+ }
509516 }
510517 }
511518 $out .= self::makeLoaderImplementScript( $name, $scripts, $styles,
@@ -556,22 +563,24 @@
557564 * given properties.
558565 *
559566 * @param $name Module name
560 - * @param $scripts Array: List of JavaScript code snippets to be executed after the
561 - * module is loaded
562 - * @param $styles Array: List of CSS strings keyed by media type
 567+ * @param $scripts Mixed: List of URLs to JavaScript files or String of JavaScript code
 568+ * @param $styles Mixed: List of CSS strings keyed by media type, or list of lists of URLs to
 569+ * CSS files keyed by media type
563570 * @param $messages Mixed: List of messages associated with this module. May either be an
564571 * associative array mapping message key to value, or a JSON-encoded message blob containing
565572 * the same data, wrapped in an XmlJsCode object.
566573 */
567574 public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
568 - if ( is_array( $scripts ) ) {
569 - $scripts = implode( $scripts, "\n" );
 575+ if ( is_string( $scripts ) ) {
 576+ $scripts = new XmlJsCode( "function( $ ) {{$scripts}}" );
 577+ } else if ( !is_array( $scripts ) ) {
 578+ throw MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
570579 }
571580 return Xml::encodeJsCall(
572581 'mw.loader.implement',
573582 array(
574583 $name,
575 - new XmlJsCode( "function( $ ) {{$scripts}}" ),
 584+ $scripts,
576585 (object)$styles,
577586 (object)$messages
578587 ) );
Index: trunk/phase3/includes/resourceloader/ResourceLoaderFileModule.php
@@ -215,21 +215,13 @@
216216 * @return String: JavaScript code for $context
217217 */
218218 public function getScript( ResourceLoaderContext $context ) {
219 - $files = array_merge(
220 - $this->scripts,
221 - self::tryForKey( $this->languageScripts, $context->getLanguage() ),
222 - self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
223 - );
224 - if ( $context->getDebug() ) {
225 - $files = array_merge( $files, $this->debugScripts );
226 - if ( $this->debugRaw ) {
227 - $script = '';
228 - foreach ( $files as $file ) {
229 - $path = $this->getRemotePath( $file );
230 - $script .= "\n\t" . Xml::encodeJsCall( 'mw.loader.load', array( $path ) );
231 - }
232 - return $script;
 219+ $files = $this->getScriptFiles( $context );
 220+ if ( $context->getDebug() && $this->debugRaw ) {
 221+ $urls = array();
 222+ foreach ( $this->getScriptFiles( $context ) as $file ) {
 223+ $urls[] = $this->getRemotePath( $file );
233224 }
 225+ return $urls;
234226 }
235227 return $this->readScriptFiles( $files );
236228 }
@@ -253,19 +245,19 @@
254246 * @return String: CSS code for $context
255247 */
256248 public function getStyles( ResourceLoaderContext $context ) {
257 - // Merge general styles and skin specific styles, retaining media type collation
258 - $styles = $this->readStyleFiles( $this->styles, $this->getFlip( $context ) );
259 - $skinStyles = $this->readStyleFiles(
260 - self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
 249+ $styles = $this->readStyleFiles(
 250+ $this->getStyleFiles( $context ),
261251 $this->getFlip( $context )
262252 );
263 -
264 - foreach ( $skinStyles as $media => $style ) {
265 - if ( isset( $styles[$media] ) ) {
266 - $styles[$media] .= $style;
267 - } else {
268 - $styles[$media] = $style;
 253+ if ( !$context->getOnly() && $context->getDebug() && $this->debugRaw ) {
 254+ $urls = array();
 255+ foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) {
 256+ $urls[$mediaType] = array();
 257+ foreach ( $list as $file ) {
 258+ $urls[$mediaType][] = $this->getRemotePath( $file );
 259+ }
269260 }
 261+ return $urls;
270262 }
271263 // Collect referenced files
272264 $this->localFileRefs = array_unique( $this->localFileRefs );
@@ -381,7 +373,7 @@
382374 return $this->modifiedTime[$context->getHash()];
383375 }
384376
385 - /* Protected Members */
 377+ /* Protected Methods */
386378
387379 protected function getLocalPath( $path ) {
388380 return "{$this->localBasePath}/$path";
@@ -443,6 +435,39 @@
444436 }
445437
446438 /**
 439+ * Gets a list of file paths for all scripts in this module, in order of propper execution.
 440+ *
 441+ * @param $context ResourceLoaderContext: Context
 442+ * @return Array: List of file paths
 443+ */
 444+ protected function getScriptFiles( ResourceLoaderContext $context ) {
 445+ $files = array_merge(
 446+ $this->scripts,
 447+ self::tryForKey( $this->languageScripts, $context->getLanguage() ),
 448+ self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
 449+ );
 450+ if ( $context->getDebug() ) {
 451+ $files = array_merge( $files, $this->debugScripts );
 452+ }
 453+ return $files;
 454+ }
 455+
 456+ /**
 457+ * Gets a list of file paths for all styles in this module, in order of propper inclusion.
 458+ *
 459+ * @param $context ResourceLoaderContext: Context
 460+ * @return Array: List of file paths
 461+ */
 462+ protected function getStyleFiles( ResourceLoaderContext $context ) {
 463+ return array_merge_recursive(
 464+ self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
 465+ self::collateFilePathListByOption(
 466+ self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), 'media', 'all'
 467+ )
 468+ );
 469+ }
 470+
 471+ /**
447472 * Gets the contents of a list of JavaScript files.
448473 *
449474 * @param $scripts Array: List of file paths to scripts to read, remap and concetenate
@@ -467,7 +492,8 @@
468493 /**
469494 * Gets the contents of a list of CSS files.
470495 *
471 - * @param $styles Array: List of file paths to styles to read, remap and concetenate
 496+ * @param $styles Array: List of media type/list of file paths pairs, to read, remap and
 497+ * concetenate
472498 * @return Array: List of concatenated and remapped CSS data from $styles,
473499 * keyed by media type
474500 */
@@ -475,7 +501,6 @@
476502 if ( empty( $styles ) ) {
477503 return array();
478504 }
479 - $styles = self::collateFilePathListByOption( $styles, 'media', 'all' );
480505 foreach ( $styles as $media => $files ) {
481506 $uniqueFiles = array_unique( $files );
482507 $styles[$media] = implode(
Index: trunk/phase3/resources/mediawiki/mediawiki.js
@@ -733,7 +733,7 @@
734734 *
735735 * @param module string module name to execute
736736 */
737 - function execute( module ) {
 737+ function execute( module, callback ) {
738738 var _fn = 'mw.loader::execute> ';
739739 if ( typeof registry[module] === 'undefined' ) {
740740 throw new Error( 'Module has not been registered yet: ' + module );
@@ -744,30 +744,74 @@
745745 } else if ( registry[module].state === 'ready' ) {
746746 throw new Error( 'Module has already been loaded: ' + module );
747747 }
748 - // Add style sheet to document
749 - if ( typeof registry[module].style === 'string' && registry[module].style.length ) {
750 - $marker.before( mw.html.element( 'style',
751 - { type: 'text/css' },
752 - new mw.html.Cdata( registry[module].style )
753 - ) );
754 - } else if ( typeof registry[module].style === 'object'
755 - && !( $.isArray( registry[module].style ) ) )
756 - {
 748+ // Add styles
 749+ if ( $.isPlainObject( registry[module].style ) ) {
757750 for ( var media in registry[module].style ) {
758 - $marker.before( mw.html.element( 'style',
759 - { type: 'text/css', media: media },
760 - new mw.html.Cdata( registry[module].style[media] )
761 - ) );
 751+ var style = registry[module].style[media];
 752+ if ( $.isArray( style ) ) {
 753+ for ( var i = 0; i < style.length; i++ ) {
 754+ $marker.before( mw.html.element( 'link', {
 755+ 'type': 'text/css',
 756+ 'rel': 'stylesheet',
 757+ 'href': style[i]
 758+ } ) );
 759+ }
 760+ } else if ( typeof style === 'string' ) {
 761+ $marker.before( mw.html.element(
 762+ 'style',
 763+ { 'type': 'text/css', 'media': media },
 764+ new mw.html.Cdata( style )
 765+ ) );
 766+ }
762767 }
763768 }
764769 // Add localizations to message system
765 - if ( typeof registry[module].messages === 'object' ) {
 770+ if ( $.isPlainObject( registry[module].messages ) ) {
766771 mw.messages.set( registry[module].messages );
767772 }
768773 // Execute script
769774 try {
770 - registry[module].script( jQuery );
771 - registry[module].state = 'ready';
 775+ var script = registry[module].script;
 776+ if ( $.isArray( script ) ) {
 777+ var done = 0;
 778+ for ( var i = 0; i < script.length; i++ ) {
 779+ registry[module].state = 'loading';
 780+ addScript( script[i], function() {
 781+ if ( ++done == script.length ) {
 782+ registry[module].state = 'ready';
 783+ handlePending();
 784+ if ( $.isFunction( callback ) ) {
 785+ callback();
 786+ }
 787+ }
 788+ } );
 789+ }
 790+ } else if ( $.isFunction( script ) ) {
 791+ script( jQuery );
 792+ registry[module].state = 'ready';
 793+ handlePending();
 794+ if ( $.isFunction( callback ) ) {
 795+ callback();
 796+ }
 797+ }
 798+ } catch ( e ) {
 799+ // This needs to NOT use mw.log because these errors are common in production mode
 800+ // and not in debug mode, such as when a symbol that should be global isn't exported
 801+ if ( window.console && typeof window.console.log === 'function' ) {
 802+ console.log( _fn + 'Exception thrown by ' + module + ': ' + e.message );
 803+ console.log( e );
 804+ }
 805+ registry[module].state = 'error';
 806+ }
 807+ }
 808+
 809+ /**
 810+ * Automatically executes jobs and modules which are pending with satistifed dependencies.
 811+ *
 812+ * This is used when dependencies are satisfied, such as when a module is executed.
 813+ */
 814+ function handlePending() {
 815+ try {
772816 // Run jobs who's dependencies have just been met
773817 for ( var j = 0; j < jobs.length; j++ ) {
774818 if ( compare(
@@ -793,13 +837,6 @@
794838 }
795839 }
796840 } catch ( e ) {
797 - // This needs to NOT use mw.log because these errors are common in production mode
798 - // and not in debug mode, such as when a symbol that should be global isn't exported
799 - if ( window.console && typeof window.console.log === 'function' ) {
800 - console.log( _fn + 'Exception thrown by ' + module + ': ' + e.message );
801 - console.log( e );
802 - }
803 - registry[module].state = 'error';
804841 // Run error callbacks of jobs affected by this condition
805842 for ( var j = 0; j < jobs.length; j++ ) {
806843 if ( $.inArray( module, jobs[j].dependencies ) !== -1 ) {
@@ -883,8 +920,11 @@
884921 /**
885922 * Adds a script tag to the body, either using document.write or low-level DOM manipulation,
886923 * depending on whether document-ready has occured yet.
 924+ *
 925+ * @param src String: URL to script, will be used as the src attribute in the script tag
 926+ * @param callback Function: Optional callback which will be run when the script is done
887927 */
888 - function addScript( src ) {
 928+ function addScript( src, callback ) {
889929 if ( ready ) {
890930 // jQuery's getScript method is NOT better than doing this the old-fassioned way
891931 // because jQuery will eval the script's code, and errors will not have sane
@@ -892,11 +932,18 @@
893933 var script = document.createElement( 'script' );
894934 script.setAttribute( 'src', src );
895935 script.setAttribute( 'type', 'text/javascript' );
 936+ if ( $.isFunction( callback ) ) {
 937+ script.onload = script.onreadystatechange = callback;
 938+ }
896939 document.body.appendChild( script );
897940 } else {
898941 document.write( mw.html.element(
899942 'script', { 'type': 'text/javascript', 'src': src }, ''
900943 ) );
 944+ if ( $.isFunction( callback ) ) {
 945+ // Document.write is synchronous, so this is called when it's done
 946+ callback();
 947+ }
901948 }
902949 }
903950
@@ -1050,27 +1097,36 @@
10511098 * Implements a module, giving the system a course of action to take
10521099 * upon loading. Results of a request for one or more modules contain
10531100 * calls to this function.
 1101+ *
 1102+ * All arguments are required.
 1103+ *
 1104+ * @param module String: Name of module
 1105+ * @param script Mixed: Function of module code or String of URL to be used as the src
 1106+ * attribute when adding a script element to the body
 1107+ * @param style Object: Object of CSS strings keyed by media-type or Object of lists of URLs
 1108+ * keyed by media-type
 1109+ * as the href attribute when adding a link element to the head
 1110+ * @param msgs Object: List of key/value pairs to be passed through mw.messages.set
10541111 */
1055 - this.implement = function( module, script, style, localization ) {
 1112+ this.implement = function( module, script, style, msgs ) {
 1113+ // Validate input
 1114+ if ( typeof module !== 'string' ) {
 1115+ throw new Error( 'module must be a string, not a ' + typeof module );
 1116+ }
 1117+ if ( !$.isFunction( script ) && !$.isArray( script ) ) {
 1118+ throw new Error( 'script must be a function or an array, not a ' + typeof script );
 1119+ }
 1120+ if ( !$.isPlainObject( style ) ) {
 1121+ throw new Error( 'style must be a object or a string, not a ' + typeof style );
 1122+ }
 1123+ if ( !$.isPlainObject( msgs ) ) {
 1124+ throw new Error( 'msgs must be an object, not a ' + typeof msgs );
 1125+ }
10561126 // Automatically register module
10571127 if ( typeof registry[module] === 'undefined' ) {
10581128 mw.loader.register( module );
10591129 }
1060 - // Validate input
1061 - if ( !$.isFunction( script ) ) {
1062 - throw new Error( 'script must be a function, not a ' + typeof script );
1063 - }
1064 - if ( typeof style !== 'undefined'
1065 - && typeof style !== 'string'
1066 - && typeof style !== 'object' )
1067 - {
1068 - throw new Error( 'style must be a string or object, not a ' + typeof style );
1069 - }
1070 - if ( typeof localization !== 'undefined'
1071 - && typeof localization !== 'object' )
1072 - {
1073 - throw new Error( 'localization must be an object, not a ' + typeof localization );
1074 - }
 1130+ // Check for duplicate implementation
10751131 if ( typeof registry[module] !== 'undefined'
10761132 && typeof registry[module].script !== 'undefined' )
10771133 {
@@ -1080,14 +1136,8 @@
10811137 registry[module].state = 'loaded';
10821138 // Attach components
10831139 registry[module].script = script;
1084 - if ( typeof style === 'string'
1085 - || typeof style === 'object' && !( style instanceof Array ) )
1086 - {
1087 - registry[module].style = style;
1088 - }
1089 - if ( typeof localization === 'object' ) {
1090 - registry[module].messages = localization;
1091 - }
 1140+ registry[module].style = style;
 1141+ registry[module].messages = msgs;
10921142 // Execute or queue callback
10931143 if ( compare(
10941144 filter( ['ready'], registry[module].dependencies ),

Follow-up revisions

RevisionCommit summaryAuthorDate
r88061Adding QUnit tests for mw.loader (Follow-up r88053)krinkle12:48, 14 May 2011
r88073Improves on r88053 - added code basically lifted from jQuery.getScript which ...tparscal13:14, 14 May 2011
r88074Fixed language in comments and error messages - see comments in r88053.tparscal13:21, 14 May 2011
r88166Follow up r88053. This was trying to throw the return value of a function cal...platonides12:36, 15 May 2011
r88329Passing module to handlePending (bug 28998; Follow-up r88053)krinkle17:20, 17 May 2011
r96964Address one of the fixmes on r88053: because addScript() is asynchronous, cal...catrope13:55, 13 September 2011
r96978Fix the fixme on r88053: dependency handling was broken in debug mode in cert...catrope17:13, 13 September 2011
r97362[RL] pass 'media' property to <link> tags as well (in already did it for the ...krinkle02:32, 17 September 2011

Comments

#Comment by Catrope (talk | contribs)   12:40, 14 May 2011
+		 * @param style Object: Object of CSS strings keyed by media-type or Object of lists of URLs
+		 * keyed by media-type
+		 * as the href attribute when adding a link element to the head

The last line looks out of place.

+			if ( !$.isPlainObject( style ) ) {
+				throw new Error( 'style must be a object or a string, not a ' + typeof style );
+			}

"A object" isn't correct English, and strings don't seem to be allowed here.

#Comment by Krinkle (talk | contribs)   07:39, 15 May 2011

Trevor fixed this in r88074.

#Comment by Krinkle (talk | contribs)   21:11, 15 May 2011

+		function handlePending() {

 					if ( $.inArray( module, jobs[j].dependencies ) !== -1 ) {

module is not defined here. Causes ReferenceError: Can't find variable: module when (for example) tablesorter is loaded after dom-ready (try on a page with a <table class="sortable">).

I guess passing module should do it although I'm not quite sure, this function is a little magic to me.

Doesn't err in production mode (either not happening or silently ignored, I guess the latter)

#Comment by Krinkle (talk | contribs)   17:22, 17 May 2011

Fixed in r88329.

#Comment by Catrope (talk | contribs)   16:25, 25 May 2011
+				if ( $.isFunction( callback ) ) {
+					// Document.write is synchronous, so this is called when it's done
+					callback();
+				}

Lies! document.write() is not synchronous, at least not in Firefox 4.0.1, but I understand why it would appear to be. The document.write() is deferred: it's queued and the script tag is only written to the DOM after this script's thread finishes, i.e. some time after the callback has run to completion. However, in your use case (debug mode where all modules are file-based) the only thing in the callback was more calls to document.write() (indirectly, via addScript()). This means that if B depends on A, the document.write() for B is executed before A is loaded. That's not a problem, though, because the document.write() calls are serialized properly, so the script tags are written to the DOM in the right order, and executed sequentially.

This house of cards collapses when you're mixing file-based and wiki page-based modules in debug mode, though, or generally when mixing modules of the form mw.loader.implement(["some url"], {}, {}); (which is how file modules work in debug mode) with modules of the form mw.loader.implement(function($) { code; }, {}, {}); (which is how wiki page modules work in both debug and production mode). If a wiki page module depends on a file module, the document.write() call for the file module will be executed before the wiki page module is executed, but because the document.write() is deferred the wiki page module ends up being executed before its dependency. This is happening in the WikiLove extension with ext.wikiLove.local (which is a wiki page module and depends on ext.wikiLove.startup, which is a file-based module). I investigated Jan Paul's complaints and found this.

I'm not really sure how this should be fixed. At the bottom of the body, document ready hasn't happened yet but the document is ready enough to do async script loading as done in the if ( ready ) { branch (at least in FF4). At the top of the body, this doesn't work, but this can be detected with if ( document.body ) { (at least in FF4). However, doing that (I tried it locally) introduces another set of problems. Because load() is no longer synchronous before document ready, modules like mw.util and mw.user can't just be assumed to be present. This means dependencies for mw.util have to be flagged explicitly and the inlined mw.user.options and mw.user.tokens modules have to be wrapped in mw.loader.using( 'mw.user', function() { ... } ); . Worse still, it means site and user scripts will typically execute before mw.util and mw.user or any of the other stuff that's loaded at the bottom of the body.

I've tried wrapping the callback(); invocation in setTimeout( ..., 0 ); but that fails catastrophically because code run through setTimeout() executes in a different thread where, apparently, jQuery isn't available and IFFEs fail.

Other solutions that could be explored are:

  • return the code as a string instead of a closure, and inject that into the DOM as a script tag using document.write() . We basically do the same thing for CSS already. A major disadvantage is that we won't get proper line numbers and that tracking down the origin of a piece of code is non-trivial. These are bad things in debug mode
  • URLify non-file modules too, using only=scripts. This seems like the most promising way to go forward to me
+					for ( var i = 0; i < script.length; i++ ) {
+						registry[module].state = 'loading';
+						addScript( script[i], function() {
+							if ( ++done == script.length ) {
+								registry[module].state = 'ready';
+								handlePending();
+								if ( $.isFunction( callback ) ) {
+									callback();
+								}
+							}
+						} );
+					}

This code also has a bug, which I found when experimenting with loading things asynchronously. When loading a module that consists of multiple scripts (in my case, this was ext.wikiLove.startup), this code issues a separate addScript() for each file, then increment a shared variable done so the callback is only called when all files are loaded. However, this potentially breaks loading order guarantees for files within the module: when doing addScript(A); addScript(B); , A and B are requested in parallel and executed when they arrive, which means it's perfectly possible for B to execute before A. This violates assumptions that module writers make about multi-file modules always being loaded in the specified order (this happens in production mode), and broke the WikiLove module I mentioned because the first file defined $.wikiLove = { ... }; and the second file extended it using $.wikiLove.foo = ...; .

#Comment by Catrope (talk | contribs)   13:58, 13 September 2011

The bug I mentioned at the very end of the comment (about breaking loading order within the module), is fixed in r96964.

#Comment by MarkAHershberger (talk | contribs)   15:28, 30 June 2011

When this is fixed, could you check that Bug #29606 is also fixed?

#Comment by Krinkle (talk | contribs)   17:17, 30 June 2011

Hm.. I'm not sure I see the link here ?

#Comment by MarkAHershberger (talk | contribs)   18:49, 30 June 2011

Sorry, I meant Bug #29608

#Comment by He7d3r (talk | contribs)   22:35, 1 July 2011

bug 22094 ;-)

#Comment by Krinkle (talk | contribs)   10:39, 16 July 2011
#Comment by Krinkle (talk | contribs)   05:22, 28 July 2011

Roan wrote:

URLify non-file modules too, using only=scripts. This seems like the most promising way to go forward to me

Seems like the logical choise to me, that avoids the work-around crap all together.

btw, all of the above only applies to debug mode. Production mode is OK.

#Comment by Catrope (talk | contribs)   17:15, 13 September 2011

I implemented this in r96978. Setting to resolved, because all of my concerns with this revision have been addressed.

#Comment by Krinkle (talk | contribs)   02:30, 17 September 2011

When loading a module that has stylesheets for non-screen media (such as 'skins.modern' which has a 'media' => 'print' stylesheet), this revision loads the print stylesheet and the screen regardless and the browsers also gets to execute them.

This usually only happens in debug mode, caused by this revision:

 				for ( var media in registry[module].style ) {
-					$marker.before( mw.html.element( 'style',
-						{ type: 'text/css', media: media },
-						new mw.html.Cdata( registry[module].style[media] )
-					) );
+					var style = registry[module].style[media];
+					if ( $.isArray( style ) ) {
+						for ( var i = 0; i < style.length; i++ ) {
+							$marker.before( mw.html.element( 'link', {
+								'type': 'text/css',
+								'rel': 'stylesheet',
+								'href': style[i]
+							} ) );
+						}
+					} else if ( typeof style === 'string' ) {
+						$marker.before( mw.html.element(
+							'style',
+							{ 'type': 'text/css', 'media': media },
+							new mw.html.Cdata( style )
+						) );
+					}
 				}

Previously it used the media property. In the new code it only uses it for ‎<style> but not in the ‎<link>.

Status & tagging log