From b3fea2070b1566658c439d511f7fe79ed99c8ef9 Mon Sep 17 00:00:00 2001 From: Trevor Parscal Date: Fri, 10 Sep 2010 00:21:36 +0000 Subject: [PATCH] Added media-type support for static and dynamic ResourceLoader requests. --- includes/OutputPage.php | 15 ++-- includes/ResourceLoader.php | 22 +++--- includes/ResourceLoaderContext.php | 9 ++- includes/ResourceLoaderModule.php | 108 ++++++++++++++++++++++------- includes/SkinTemplate.php | 4 +- resources/Resources.php | 13 ++-- resources/mediawiki/mediawiki.js | 10 ++- 7 files changed, 132 insertions(+), 49 deletions(-) diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 33d49316e5..4510b0976c 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2198,7 +2198,7 @@ class OutputPage { global $wgUser, $wgRequest, $wgLang; if ( $sk->commonPrintStylesheet() ) { - $this->addStyle( 'common/wikiprintable.css', 'print' ); + $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' ); } $sk->setupUserCss( $this ); @@ -2280,7 +2280,7 @@ class OutputPage { return $ret; } - static function makeResourceLoaderLink( $skin, $modules, $only ) { + static function makeResourceLoaderLink( $skin, $modules, $only, $media = 'all' ) { global $wgUser, $wgLang, $wgRequest; // TODO: Should this be a static function of ResourceLoader instead? $query = array( @@ -2292,7 +2292,8 @@ class OutputPage { ); // Automatically select style/script elements if ( $only === 'styles' ) { - return Html::linkedStyle( wfAppendQuery( wfScript( 'load' ), $query ) ); + $query['media'] = $media; + return Html::linkedStyle( wfAppendQuery( wfScript( 'load' ), $query ), $media ); } else { return Html::linkedScript( wfAppendQuery( wfScript( 'load' ), $query ) ); } @@ -2488,11 +2489,15 @@ class OutputPage { // Support individual script requests in debug mode if ( $wgRequest->getBool( 'debug' ) && $wgRequest->getVal( 'debug' ) !== 'false' ) { foreach ( $this->getModuleStyles() as $name ) { - $tags[] = self::makeResourceLoaderLink( $sk, $name, 'styles' ); + $tags[] = self::makeResourceLoaderLink( $sk, $name, 'styles', 'all' ); + $tags[] = self::makeResourceLoaderLink( $sk, $name, 'styles', 'screen' ); + $tags[] = self::makeResourceLoaderLink( $sk, $name, 'styles', 'print' ); } } else { if ( count( $this->getModuleStyles() ) ) { - $tags[] = self::makeResourceLoaderLink( $sk, $this->getModuleStyles(), 'styles' ); + $tags[] = self::makeResourceLoaderLink( $sk, $this->getModuleStyles(), 'styles', 'all' ); + $tags[] = self::makeResourceLoaderLink( $sk, $this->getModuleStyles(), 'styles', 'screen' ); + $tags[] = self::makeResourceLoaderLink( $sk, $this->getModuleStyles(), 'styles', 'print' ); } } diff --git a/includes/ResourceLoader.php b/includes/ResourceLoader.php index 4a165edc81..ad0d0bf239 100644 --- a/includes/ResourceLoader.php +++ b/includes/ResourceLoader.php @@ -287,16 +287,20 @@ class ResourceLoader { } // Styles - $styles = ''; + $styles = array(); if ( $context->shouldIncludeStyles() && - ( $styles .= self::$modules[$name]->getStyle( $context ) ) !== '' + ( count( $styles = self::$modules[$name]->getStyles( $context ) ) ) ) { - if ( self::$modules[$name]->getFlip( $context ) ) { - $styles = self::filter( 'flip-css', $styles ); + foreach ( $styles as $media => $style ) { + if ( self::$modules[$name]->getFlip( $context ) ) { + $styles[$media] = self::filter( 'flip-css', $style ); + } + if ( !$context->getDebug() ) { + $styles[$media] = self::filter( 'minify-css', $style ); + } } - $styles = $context->getDebug() ? $styles : self::filter( 'minify-css', $styles ); } // Messages @@ -304,14 +308,16 @@ class ResourceLoader { // Output if ( $context->getOnly() === 'styles' ) { - echo $styles; + if ( isset( $styles[$context->getMedia()] ) ) { + echo $styles[$context->getMedia()]; + } } else if ( $context->getOnly() === 'scripts' ) { echo $scripts; } else if ( $context->getOnly() === 'messages' ) { echo "mediaWiki.msg.set( $messages );\n"; } else { - $styles = Xml::escapeJsString( $styles ); - echo "mediaWiki.loader.implement( '$name', function() {{$scripts}},\n'$styles',\n$messages );\n"; + $styles = FormatJson::encode( $styles ); + echo "mediaWiki.loader.implement( '$name', function() {{$scripts}},\n$styles,\n$messages );\n"; } } diff --git a/includes/ResourceLoaderContext.php b/includes/ResourceLoaderContext.php index 264a834006..39e650ce7a 100644 --- a/includes/ResourceLoaderContext.php +++ b/includes/ResourceLoaderContext.php @@ -33,6 +33,7 @@ class ResourceLoaderContext { protected $skin; protected $debug; protected $only; + protected $media; protected $hash; /* Methods */ @@ -49,6 +50,7 @@ class ResourceLoaderContext { $this->skin = $request->getVal( 'skin' ); $this->debug = $request->getVal( 'debug' ) === 'true' || $request->getBool( 'debug' ); $this->only = $request->getVal( 'only' ); + $this->media = $request->getVal( 'media', 'all' ); // Fallback on system defaults if ( !$this->language ) { @@ -95,6 +97,10 @@ class ResourceLoaderContext { public function getOnly() { return $this->only; } + + public function getMedia() { + return $this->media; + } public function shouldIncludeScripts() { return is_null( $this->only ) || $this->only === 'scripts'; @@ -110,6 +116,7 @@ class ResourceLoaderContext { public function getHash() { return isset( $this->hash ) ? - $this->hash : $this->hash = implode( '|', array( $this->language, $this->skin, $this->debug ) ); + $this->hash : $this->hash = + implode( '|', array( $this->language, $this->skin, $this->debug, $this->only, $this->media ) ); } } diff --git a/includes/ResourceLoaderModule.php b/includes/ResourceLoaderModule.php index 4c84ff1b26..4f6277adea 100644 --- a/includes/ResourceLoaderModule.php +++ b/includes/ResourceLoaderModule.php @@ -96,9 +96,9 @@ abstract class ResourceLoaderModule { * Get all CSS for this module for a given skin. * * @param $context ResourceLoaderContext object - * @return String: CSS + * @return array: strings of CSS keyed by media type */ - public abstract function getStyle( ResourceLoaderContext $context ); + public abstract function getStyles( ResourceLoaderContext $context ); /** * Get the messages needed for this module. @@ -191,9 +191,11 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * // Non-raw module options * 'dependencies' => 'module' | array( 'module1', 'module2' ... ) * 'loaderScripts' => 'dir/loader.js' | array( 'dir/loader1.js', 'dir/loader2.js' ... ), - * 'styles' => 'dir/file.css' | array( 'dir/file1.css', 'dir/file2.css' ... ), + * 'styles' => 'dir/file.css' | array( 'dir/file1.css', 'dir/file2.css' ... ), | + * array( 'dir/file1.css' => array( 'media' => 'print' ) ), * 'skinStyles' => array( - * '[skin name]' => 'dir/skin.css' | '[skin name]' => array( 'dir/skin1.css', 'dir/skin2.css' ... ) + * '[skin name]' => 'dir/skin.css' | array( 'dir/skin1.css', 'dir/skin2.css' ... ) | + * array( 'dir/file1.css' => array( 'media' => 'print' ) * ... * ), * 'messages' => array( 'message1', 'message2' ... ), @@ -362,12 +364,28 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { return $retval; } - public function getStyle( ResourceLoaderContext $context ) { - $style = $this->getPrimaryStyle() . "\n" . $this->getSkinStyle( $context->getSkin() ); - - // Extract and store the list of referenced files - $files = CSSMin::getLocalFileReferences( $style ); - + public function getStyles( ResourceLoaderContext $context ) { + $styles = array(); + foreach ( $this->getPrimaryStyles() as $media => $style ) { + if ( !isset( $styles[$media] ) ) { + $styles[$media] = ''; + } + $styles[$media] .= $style; + } + foreach ( $this->getPrimaryStyles() as $media => $style ) { + if ( !isset( $styles[$media] ) ) { + $styles[$media] = ''; + } + $styles[$media] .= $this->getSkinStyles( $context->getSkin() ); + } + + // Collect referenced files + $files = array(); + foreach ( $styles as $media => $style ) { + // Extract and store the list of referenced files + $files = array_merge( $files, CSSMin::getLocalFileReferences( $style ) ); + } + // Only store if modified if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) { $encFiles = FormatJson::encode( $files ); @@ -379,15 +397,15 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { 'md_deps' => $encFiles, ) ); - + // Save into memcached global $wgMemc; - + $key = wfMemcKey( 'resourceloader', 'module_deps', $this->getName(), $context->getSkin() ); $wgMemc->set( $key, $encFiles ); } - - return $style; + + return $styles; } public function getMessages() { @@ -421,19 +439,31 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { if ( isset( $this->modifiedTime[$context->getHash()] ) ) { return $this->modifiedTime[$context->getHash()]; } - + + // Sort of nasty way we can get a flat list of files depended on by all styles + $styles = array(); + foreach ( self::organizeFilesByOption( $this->styles, 'media', 'all' ) as $media => $styleFiles ) { + $styles = array_merge( $styles, $styleFiles ); + } + $skinFiles = (array) self::getSkinFiles( + $context->getSkin(), self::organizeFilesByOption( $this->skinStyles, 'media', 'all' ) + ); + foreach ( $skinFiles as $media => $styleFiles ) { + $styles = array_merge( $styles, $styleFiles ); + } + + // Final merge, this should result in a master list of dependent files $files = array_merge( $this->scripts, - $this->styles, + $styles, $context->getDebug() ? $this->debugScripts : array(), isset( $this->languageScripts[$context->getLanguage()] ) ? (array) $this->languageScripts[$context->getLanguage()] : array(), (array) self::getSkinFiles( $context->getSkin(), $this->skinScripts ), - (array) self::getSkinFiles( $context->getSkin(), $this->skinStyles ), $this->loaders, $this->getFileDependencies( $context->getSkin() ) ); - + $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) ); // Get the mtime of the message blob @@ -468,7 +498,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * * @return String: JS */ - protected function getPrimaryStyle() { + protected function getPrimaryStyles() { return self::concatStyles( $this->styles ); } @@ -509,9 +539,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * Get the skin-specific CSS for a given skin. This is pulled from the * skin-specific CSS files added through addSkinStyles() * - * @return String: CSS + * @return Array: list of CSS strings keyed by media type */ - protected function getSkinStyle( $skin ) { + protected function getSkinStyles( $skin ) { return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) ); } @@ -582,15 +612,41 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { return implode( "\n", array_map( 'file_get_contents', array_map( array( __CLASS__, 'remapFilename' ), array_unique( (array) $files ) ) ) ); } + protected static function organizeFilesByOption( $files, $option, $default ) { + $organizedFiles = array(); + foreach ( (array) $files as $key => $value ) { + if ( is_int( $key ) ) { + // File name as the value + if ( !isset( $organizedFiles[$default] ) ) { + $organizedFiles[$default] = array(); + } + $organizedFiles[$default][] = $value; + } else if ( is_array( $value ) ) { + // File name as the key, options array as the value + $media = isset( $value[$option] ) ? $value[$option] : $default; + if ( !isset( $organizedFiles[$media] ) ) { + $organizedFiles[$media] = array(); + } + $organizedFiles[$media][] = $key; + } + } + return $organizedFiles; + } + /** * Get the contents of a set of CSS files, remap then and concatenate * them, with newlines in between. Each file is used only once. * * @param $files Array of file names - * @return String: concatenated and remapped contents of $files + * @return Array: list of concatenated and remapped contents of $files keyed by media type */ - protected static function concatStyles( $files ) { - return implode( "\n", array_map( array( __CLASS__, 'remapStyle' ), array_unique( (array) $files ) ) ); + protected static function concatStyles( $styles ) { + $styles = self::organizeFilesByOption( $styles, 'media', 'all' ); + foreach ( $styles as $media => $files ) { + $styles[$media] = + implode( "\n", array_map( array( __CLASS__, 'remapStyle' ), array_unique( (array) $files ) ) ); + } + return $styles; } /** @@ -662,7 +718,7 @@ class ResourceLoaderSiteModule extends ResourceLoaderModule { return $this->modifiedTime; } - public function getStyle( ResourceLoaderContext $context ) { return ''; } + public function getStyles( ResourceLoaderContext $context ) { return array(); } public function getMessages() { return array(); } public function getLoaderScript() { return ''; } public function getDependencies() { return array(); } @@ -738,7 +794,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { return 300; // 5 minutes } - public function getStyle( ResourceLoaderContext $context ) { return ''; } + public function getStyles( ResourceLoaderContext $context ) { return array(); } public function getFlip( $context ) { global $wgContLang; diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php index 02df836186..0285c89e7a 100644 --- a/includes/SkinTemplate.php +++ b/includes/SkinTemplate.php @@ -107,9 +107,7 @@ class SkinTemplate extends Skin { * @param $out OutputPage */ function setupSkinUserCss( OutputPage $out ){ - $out->addModuleStyles( 'mediawiki.legacy.shared' ); - // ResourceLoader does not support CSS media types yet, so we must include this on it's own the old way - $out->addStyle( 'common/commonPrint.css', 'print' ); + $out->addModuleStyles( array( 'mediawiki.legacy.shared', 'mediawiki.legacy.commonPrint' ) ); } /** diff --git a/resources/Resources.php b/resources/Resources.php index 199312eb49..a4c5aa8550 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -9,7 +9,9 @@ ResourceLoader::register( array( /* Skins */ - 'vector' => new ResourceLoaderFileModule( array( 'styles' => 'skins/vector/main-ltr.css' ) ), + 'vector' => new ResourceLoaderFileModule( + array( 'styles' => array( 'skins/vector/main-ltr.css' => array( 'media' => 'screen' ) ) ) + ), /* jQuery */ @@ -315,7 +317,7 @@ ResourceLoader::register( array( 'dependencies' => 'mediawiki.legacy.wikibits', ) ), 'mediawiki.legacy.commonPrint' => new ResourceLoaderFileModule( array( - 'styles' => 'skins/common/commonPrint.css', + 'styles' => array( 'skins/common/commonPrint.css' => array( 'media' => 'print' ) ), ) ), 'mediawiki.legacy.config' => new ResourceLoaderFileModule( array( 'scripts' => 'skins/common/config.js', @@ -381,10 +383,10 @@ ResourceLoader::register( array( 'dependencies' => 'mediawiki.legacy.wikibits', ) ), 'mediawiki.legacy.shared' => new ResourceLoaderFileModule( array( - 'styles' => 'skins/common/shared.css', + 'styles' => array( 'skins/common/shared.css' => array( 'media' => 'screen' ) ), ) ), 'mediawiki.legacy.oldshared' => new ResourceLoaderFileModule( array( - 'styles' => 'skins/common/oldshared.css', + 'styles' => array( 'skins/common/oldshared.css' => array( 'media' => 'screen' ) ), ) ), 'mediawiki.legacy.upload' => new ResourceLoaderFileModule( array( 'scripts' => 'skins/common/upload.js', @@ -394,4 +396,7 @@ ResourceLoader::register( array( 'scripts' => 'skins/common/wikibits.js', 'messages' => array( 'showtoc', 'hidetoc' ), ) ), + 'mediawiki.legacy.wikiprintable' => new ResourceLoaderFileModule( array( + 'styles' => array( 'skins/common/wikiprintable.css' => array( 'media' => 'print' ) ), + ) ), ) ); diff --git a/resources/mediawiki/mediawiki.js b/resources/mediawiki/mediawiki.js index 8c11455608..19f944c2ae 100644 --- a/resources/mediawiki/mediawiki.js +++ b/resources/mediawiki/mediawiki.js @@ -323,6 +323,12 @@ window.mediaWiki = new ( function( $ ) { // Add style sheet to document if ( typeof registry[module].style === 'string' && registry[module].style.length ) { $( 'head' ).append( '' ); + } else if ( typeof registry[module].style === 'object' ) { + for ( var media in registry[module].style ) { + $( 'head' ).append( + '' + ); + } } // Add localizations to message system if ( typeof registry[module].messages === 'object' ) { @@ -526,8 +532,8 @@ window.mediaWiki = new ( function( $ ) { if ( typeof script !== 'function' ) { throw new Error( 'script must be a function, not a ' + typeof script ); } - if ( typeof style !== 'undefined' && typeof style !== 'string' ) { - throw new Error( 'style must be a string, not a ' + typeof style ); + if ( typeof style !== 'undefined' && typeof style !== 'string' && typeof style !== 'object' ) { + throw new Error( 'style must be a string or object, not a ' + typeof style ); } if ( typeof localization !== 'undefined' && typeof localization !== 'object' ) { throw new Error( 'localization must be an object, not a ' + typeof localization ); -- 2.20.1