* Moved registration generation to startup module.
authorTrevor Parscal <tparscal@users.mediawiki.org>
Fri, 24 Sep 2010 18:49:19 +0000 (18:49 +0000)
committerTrevor Parscal <tparscal@users.mediawiki.org>
Fri, 24 Sep 2010 18:49:19 +0000 (18:49 +0000)
* Moved some javascript code generation to reusable functions (more to do).
* Reduced the code output by using mutliple calling method for mediaWiki.loader.state.
* Moved CSS minification to the end (should be a bit faster than running it for each module).

includes/ResourceLoader.php
includes/ResourceLoaderContext.php
includes/ResourceLoaderModule.php
resources/mediawiki/mediawiki.js

index 17134d6..6da7ffa 100644 (file)
@@ -53,7 +53,16 @@ class ResourceLoader {
                }
        }
        
-       protected static function preloadModuleInfo( $modules, ResourceLoaderContext $context ) {
+       /*
+        * Loads information stored in the database about modules
+        * 
+        * This is not inside the module code because it's so much more performant to request all of the information at once
+        * than it is to have each module requests it's own information.
+        * 
+        * @param $modules array list of modules to preload information for
+        * @param $context ResourceLoaderContext context to load the information within
+        */
+       protected static function preloadModuleInfo( array $modules, ResourceLoaderContext $context ) {
                $dbr = wfGetDb( DB_SLAVE );
                $skin = $context->getSkin();
                $lang = $context->getLanguage();
@@ -107,8 +116,7 @@ class ResourceLoader {
         *
         * @param $filter String: name of filter to run
         * @param $data String: text to filter, such as JavaScript or CSS text
-        * @param $file String: path to file being filtered, (optional: only required 
-        *     for CSS to resolve paths)
+        * @param $file String: path to file being filtered, (optional: only required for CSS to resolve paths)
         * @return String: filtered data
         */
        protected static function filter( $filter, $data ) {
@@ -226,54 +234,6 @@ class ResourceLoader {
                return isset( self::$modules[$name] ) ? self::$modules[$name] : null;
        }
 
-       /**
-        * Gets registration code for all modules
-        *
-        * @param $context ResourceLoaderContext object
-        * @return String: JavaScript code for registering all modules with the client loader
-        */
-       public static function getModuleRegistrations( ResourceLoaderContext $context ) {
-               wfProfileIn( __METHOD__ );
-               self::initialize();
-               
-               $scripts = '';
-               $registrations = array();
-
-               foreach ( self::$modules as $name => $module ) {
-                       // Support module loader scripts
-                       if ( ( $loader = $module->getLoaderScript() ) !== false ) {
-                               $deps = FormatJson::encode( $module->getDependencies() );
-                               $group = FormatJson::encode( $module->getGroup() );
-                               $version = wfTimestamp( TS_ISO_8601, round( $module->getModifiedTime( $context ), -2 ) );
-                               $scripts .= "( function( name, version, dependencies ) { $loader } )\n" . 
-                                       "( '$name', '$version', $deps, $group );\n";
-                       }
-                       // Automatically register module
-                       else {
-                               // Modules without dependencies or a group pass two arguments (name, timestamp) to 
-                               // mediaWiki.loader.register()
-                               if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
-                                       $registrations[] = array( $name, $module->getModifiedTime( $context ) );
-                               }
-                               // Modules with dependencies but no group pass three arguments (name, timestamp, dependencies) 
-                               // to mediaWiki.loader.register()
-                               else if ( $module->getGroup() === null ) {
-                                       $registrations[] = array(
-                                               $name, $module->getModifiedTime( $context ),  $module->getDependencies() );
-                               }
-                               // Modules with dependencies pass four arguments (name, timestamp, dependencies, group) 
-                               // to mediaWiki.loader.register()
-                               else {
-                                       $registrations[] = array(
-                                               $name, $module->getModifiedTime( $context ),  $module->getDependencies(), $module->getGroup() );
-                               }
-                       }
-               }
-               $out = $scripts . "mediaWiki.loader.register( " . FormatJson::encode( $registrations ) . " );\n";
-               wfProfileOut( __METHOD__ );
-               return $out;
-       }
-
        /**
         * Get the highest modification time of all modules, based on a given 
         * combination of language code, skin name and debug mode flag.
@@ -356,99 +316,125 @@ class ResourceLoader {
                        return;
                }
 
-               // Use output buffering
-               ob_start();
-
                // Pre-fetch blobs
                $blobs = $context->shouldIncludeMessages() ?
                        MessageBlobStore::get( $modules, $context->getLanguage() ) : array();
 
                // Generate output
+               $out = '';
                foreach ( $modules as $name ) {
                        wfProfileIn( __METHOD__ . '-' . $name );
+
                        // Scripts
                        $scripts = '';
-
                        if ( $context->shouldIncludeScripts() ) {
                                $scripts .= self::$modules[$name]->getScript( $context ) . "\n";
                        }
 
                        // Styles
                        $styles = array();
-
                        if (
-                               $context->shouldIncludeStyles() 
-                               && ( count( $styles = self::$modules[$name]->getStyles( $context ) ) )
+                               $context->shouldIncludeStyles() &&
+                               ( count( $styles = self::$modules[$name]->getStyles( $context ) ) )
                        ) {
-                               foreach ( $styles as $media => $style ) {
-                                       if ( self::$modules[$name]->getFlip( $context ) ) {
+                               // Flip CSS on a per-module basis
+                               if ( self::$modules[$name]->getFlip( $context ) ) {
+                                       foreach ( $styles as $media => $style ) {
                                                $styles[$media] = self::filter( 'flip-css', $style );
                                        }
-                                       if ( !$context->getDebug() ) {
-                                               $styles[$media] = self::filter( 'minify-css', $style );
-                                       }
                                }
                        }
 
                        // Messages
                        $messages = isset( $blobs[$name] ) ? $blobs[$name] : '{}';
 
-                       // Output
-                       if ( $context->getOnly() === 'styles' ) {
-                               if ( $context->getDebug() ) {
-                                       echo "/* $name */\n";
-                                       foreach ( $styles as $media => $style ) {
-                                               echo "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "\n}\n";
-                                       }
-                               } else {
-                                       foreach ( $styles as $media => $style ) {
-                                               if ( strlen( $style ) ) {
-                                                       echo "@media $media{" . $style . "}";
+                       // Append output
+                       switch ( $context->getOnly() ) {
+                               case 'scripts':
+                                       $out .= $scripts;
+                                       break;
+                               case 'styles':
+                                       $out .= self::makeCombinedStyles( $styles );
+                                       break;
+                               case 'messages':
+                                       $out .= self::makeMessageSetScript( $messages );
+                                       break;
+                               default:
+                                       // Minify CSS, unless in debug mode, before embedding in implment script
+                                       if ( !$context->getDebug() ) {
+                                               foreach ( $styles as $media => $style ) {
+                                                       $styles[$media] = self::filter( 'minify-css', $style );
                                                }
                                        }
-                               }
-                       } else if ( $context->getOnly() === 'scripts' ) {
-                               echo $scripts;
-                       } else if ( $context->getOnly() === 'messages' ) {
-                               echo "mediaWiki.msg.set( $messages );\n";
-                       } else {
-                               if ( count( $styles ) ) {
-                                       $styles = FormatJson::encode( $styles );
-                               } else {
-                                       $styles = 'null';
-                               }
-                               echo "mediaWiki.loader.implement( '$name', function() {{$scripts}},\n$styles,\n$messages );\n";
+                                       $out .= self::makeLoaderImplementScript( $name, $scripts, $styles, $messages );
+                                       break;
                        }
+                       
                        wfProfileOut( __METHOD__ . '-' . $name );
                }
 
-               // Update the status of script-only modules
-               if ( $context->getOnly() === 'scripts' && !in_array( 'startup', $modules ) ) {
-                       $statuses = array();
-
-                       foreach ( $modules as $name ) {
-                               $statuses[$name] = 'ready';
-                       }
-
-                       $statuses = FormatJson::encode( $statuses );
-                       echo "mediaWiki.loader.state( $statuses );\n";
-               }
-
-               // Register missing modules
+               // Update module states
                if ( $context->shouldIncludeScripts() ) {
-                       foreach ( $missing as $name ) {
-                               echo "mediaWiki.loader.register( '$name', null, 'missing' );\n";
+                       // Set the state of modules loaded as only scripts to ready
+                       if ( count( $modules ) && $context->getOnly() === 'scripts' && !in_array( 'startup', $modules ) ) {
+                               $out .= self::makeLoaderStateScript( array_fill_keys( $modules, 'ready' ) );
+                       }
+                       // Set the state of modules which were requested but unavailable as missing
+                       if ( count( $missing ) ) {
+                               $out .= self::makeLoaderStateScript( array_fill_keys( $missing, 'missing' ) );
                        }
                }
 
-               // Output the appropriate header
-               if ( $context->getOnly() !== 'styles' ) {
-                       if ( $context->getDebug() ) {
-                               ob_end_flush();
+               // Send output
+               if ( $context->getDebug() ) {
+                       echo $out;
+               } else {
+                       if ( $context->getOnly() === 'styles' ) {
+                               echo self::filter( 'minify-css', $out );
                        } else {
-                               echo self::filter( 'minify-js', ob_get_clean() );
+                               echo self::filter( 'minify-js', $out );
                        }
                }
+
                wfProfileOut( __METHOD__ );
        }
+
+       public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
+               if ( is_array( $scripts ) ) {
+                       $scripts = implode( $scripts, "\n" );
+               }
+               if ( is_array( $styles ) ) {
+                       $styles = count( $styles ) ? FormatJson::encode( $styles ) : 'null';
+               }
+               if ( is_array( $messages ) ) {
+                       $messages = count( $messages ) ? FormatJson::encode( $messages ) : 'null';
+               }
+               return "mediaWiki.loader.implement( '$name', function() {{$scripts}},\n$styles,\n$messages );\n";
+       }
+
+       public static function makeMessageSetScript( $messages ) {
+               if ( is_array( $messages ) ) {
+                       $messages = count( $messages ) ? FormatJson::encode( $messages ) : 'null';
+               }
+               return "mediaWiki.msg.set( $messages );\n";
+       }
+
+       public static function makeCombinedStyles( array $styles ) {
+               $out = '';
+               foreach ( $styles as $media => $style ) {
+                       $out .= "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "\n}\n";
+               }
+               return $out;
+       }
+
+       public static function makeLoaderStateScript( $name, $state = null ) {
+               if ( is_array( $name ) ) {
+                       $statuses = FormatJson::encode( $name );
+                       return "mediaWiki.loader.state( $statuses );\n";
+               } else {
+                       $name = Xml::escapeJsString( $name );
+                       $name = Xml::escapeJsString( $state );
+                       return "mediaWiki.loader.state( '$name', '$state' );\n";
+               }
+       }
 }
index 0de6554..3e4bac8 100644 (file)
@@ -27,6 +27,7 @@ defined( 'MEDIAWIKI' ) || die( 1 );
  * of a specific loader request
  */
 class ResourceLoaderContext {
+       
        /* Protected Members */
 
        protected $request;
index 5a47b97..a27502b 100644 (file)
@@ -26,6 +26,7 @@ defined( 'MEDIAWIKI' ) || die( 1 );
  * Abstraction for resource loader modules, with name registration and maxage functionality.
  */
 abstract class ResourceLoaderModule {
+       
        /* Protected Members */
 
        protected $name = null;
@@ -1038,25 +1039,66 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                return $vars;
        }
        
+       /**
+        * Gets registration code for all modules
+        *
+        * @param $context ResourceLoaderContext object
+        * @return String: JavaScript code for registering all modules with the client loader
+        */
+       public static function getModuleRegistrations( ResourceLoaderContext $context ) {
+               wfProfileIn( __METHOD__ );
+               
+               $scripts = '';
+               $registrations = array();
+               foreach ( ResourceLoader::getModules() as $name => $module ) {
+                       // Support module loader scripts
+                       if ( ( $loader = $module->getLoaderScript() ) !== false ) {
+                               $deps = FormatJson::encode( $module->getDependencies() );
+                               $group = FormatJson::encode( $module->getGroup() );
+                               $version = wfTimestamp( TS_ISO_8601, round( $module->getModifiedTime( $context ), -2 ) );
+                               $scripts .= "( function( name, version, dependencies ) { $loader } )\n" . 
+                                       "( '$name', '$version', $deps, $group );\n";
+                       }
+                       // Automatically register module
+                       else {
+                               // Modules without dependencies or a group pass two arguments (name, timestamp) to 
+                               // mediaWiki.loader.register()
+                               if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
+                                       $registrations[] = array( $name, $module->getModifiedTime( $context ) );
+                               }
+                               // Modules with dependencies but no group pass three arguments (name, timestamp, dependencies) 
+                               // to mediaWiki.loader.register()
+                               else if ( $module->getGroup() === null ) {
+                                       $registrations[] = array(
+                                               $name, $module->getModifiedTime( $context ),  $module->getDependencies() );
+                               }
+                               // Modules with dependencies pass four arguments (name, timestamp, dependencies, group) 
+                               // to mediaWiki.loader.register()
+                               else {
+                                       $registrations[] = array(
+                                               $name, $module->getModifiedTime( $context ),  $module->getDependencies(), $module->getGroup() );
+                               }
+                       }
+               }
+               $out = $scripts . "mediaWiki.loader.register( " . FormatJson::encode( $registrations ) . " );";
+               
+               wfProfileOut( __METHOD__ );
+               return $out;
+       }
+
        /* Methods */
 
        public function getScript( ResourceLoaderContext $context ) {
                global $IP, $wgLoadScript;
 
                $scripts = file_get_contents( "$IP/resources/startup.js" );
-
                if ( $context->getOnly() === 'scripts' ) {
                        // Get all module registrations
-                       $registration = ResourceLoader::getModuleRegistrations( $context );
+                       $registration = self::getModuleRegistrations( $context );
                        // Build configuration
                        $config = FormatJson::encode( $this->getConfig( $context ) );
                        // Add a well-known start-up function
-                       $scripts .= <<<JAVASCRIPT
-window.startUp = function() {
-       $registration
-       mediaWiki.config.set( $config ); 
-};
-JAVASCRIPT;
+                       $scripts .= "window.startUp = function() {\n\t$registration\n\tmediaWiki.config.set( $config );\n};\n";
                        // Build load query for jquery and mediawiki modules
                        $query = array(
                                'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
@@ -1074,7 +1116,7 @@ JAVASCRIPT;
                        // Build HTML code for loading jquery and mediawiki modules
                        $loadScript = Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) );
                        // Add code to add jquery and mediawiki loading code; only if the current client is compatible
-                       $scripts .= "if ( isCompatible() ) { document.write( " . FormatJson::encode( $loadScript ) . "); }\n";
+                       $scripts .= "if ( isCompatible() ) {\n\tdocument.write( " . FormatJson::encode( $loadScript ) . ");\n}\n";
                        // Delete the compatible function - it's not needed anymore
                        $scripts .= "delete window['isCompatible'];\n";
                }
@@ -1090,10 +1132,10 @@ JAVASCRIPT;
                        return $this->modifiedTime[$hash];
                }
                $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
+               
                // ATTENTION!: Because of the line above, this is not going to cause infinite recursion - think carefully
                // before making changes to this code!
-               $this->modifiedTime[$hash] = ResourceLoader::getHighestModifiedTime( $context );
-               return $this->modifiedTime[$hash];
+               return $this->modifiedTime[$hash] = ResourceLoader::getHighestModifiedTime( $context );
        }
 
        public function getFlip( $context ) {
index 3c7d4df..1848917 100644 (file)
@@ -735,9 +735,10 @@ window.mediaWiki = new ( function( $ ) {
                                }
                                return;
                        }
-                       if ( module in registry ) {
-                               registry[module].state = state;
+                       if ( !( module in registry ) ) {
+                               that.register( module );
                        }
+                       registry[module].state = state;
                };
                
                /**