}
}
- 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();
*
* @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 ) {
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.
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";
+ }
+ }
}
* Abstraction for resource loader modules, with name registration and maxage functionality.
*/
abstract class ResourceLoaderModule {
+
/* Protected Members */
protected $name = null;
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' ) ),
// 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";
}
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 ) {