From 6845912bcf106824a69a553be4791b7c77b9ffae Mon Sep 17 00:00:00 2001 From: jdlrobson Date: Wed, 25 Sep 2019 10:44:05 -0700 Subject: [PATCH] ResourceLoaderSkinModule: Provide optional mediawiki.skinning styles All mediawiki.skinning modules are repurposed as variants of the ResourceLoaderSkinModule and those ResourceLoader modules are marked for deprecation. Skins will now be encouraged to use the ResourceLoaderSkinModule class themselves as part of their own style module rather than using a module defined in core. Bug: T118134 Bug: T114695 Change-Id: I4bc8b9b4da1c16eed34f3a517ec695019381e764 --- .../ResourceLoaderSkinModule.php | 153 ++++++++++++++---- resources/Resources.php | 51 ++---- .../ResourceLoaderSkinModuleTest.php | 1 - 3 files changed, 131 insertions(+), 74 deletions(-) diff --git a/includes/resourceloader/ResourceLoaderSkinModule.php b/includes/resourceloader/ResourceLoaderSkinModule.php index 991740b2b72..2b1fb83492c 100644 --- a/includes/resourceloader/ResourceLoaderSkinModule.php +++ b/includes/resourceloader/ResourceLoaderSkinModule.php @@ -30,6 +30,95 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule { */ public $targets = [ 'desktop', 'mobile' ]; + /** + * Every skin should define which features it would like to reuse for core inside a + * ResourceLoader module that has set the class to ResourceLoaderSkinModule. + * For a feature to be valid it must be listed here along with the associated resources + * + * The following features are available: + * + * "logo": + * Adds CSS to style an element with class `mw-wiki-logo` using the value of wgLogo. + * This is enabled by default if no features are added. + * + * "elements": + * The base level that only contains the most basic of common skin styles. + * Only styles for single elements are included, no styling for complex structures like the + * TOC is present. This level is for skins that want to implement the entire style of even + * content area structures like the TOC themselves. + * + * "content": + * The most commonly used level for skins implemented from scratch. This level includes all + * the single element styles from "elements" as well as styles for complex structures such + * as the TOC that are output in the content area by MediaWiki rather than the skin. + * Essentially this is the common level that lets skins leave the style of the content area + * as it is normally styled, while leaving the rest of the skin up to the skin + * implementation. + * + * "interface": + * The highest level, this stylesheet contains extra common styles for classes like + * .firstHeading, #contentSub, et cetera which are not outputted by MediaWiki but are common + * to skins like MonoBook, Vector, etc... Essentially this level is for styles that are + * common to MonoBook clones. + */ + private const FEATURE_FILES = [ + 'logo' => [], + 'content' => [ + 'screen' => [ 'resources/src/mediawiki.skinning/content.css' ], + ], + 'interface' => [ + 'screen' => [ 'resources/src/mediawiki.skinning/interface.css' ], + ], + 'elements' => [ + 'screen' => [ 'resources/src/mediawiki.skinning/elements.css' ], + ], + ]; + + /** @var string[] */ + private $features; + + public function __construct( + array $options = [], + $localBasePath = null, + $remoteBasePath = null + ) { + parent::__construct( $options, $localBasePath, $remoteBasePath ); + $this->features = $options['features'] ?? [ 'logo' ]; + } + + /** + * Get styles defined in the module definition, plus any enabled feature styles. + * + * @param ResourceLoaderContext $context + * @return array + */ + public function getStyleFiles( ResourceLoaderContext $context ) { + $styles = parent::getStyleFiles( $context ); + + list( $defaultLocalBasePath, $defaultRemoteBasePath ) = + ResourceLoaderFileModule::extractBasePaths(); + + foreach ( $this->features as $feature ) { + if ( !isset( self::FEATURE_FILES[$feature] ) ) { + throw new InvalidArgumentException( "Feature `$feature` is not recognised" ); + } + foreach ( self::FEATURE_FILES[$feature] as $mediaType => $files ) { + if ( !isset( $styles[$mediaType] ) ) { + $styles[$mediaType] = []; + } + foreach ( $files as $filepath ) { + $styles[$mediaType][] = new ResourceLoaderFilePath( + $filepath, + $defaultLocalBasePath, + $defaultRemoteBasePath + ); + } + } + } + + return $styles; + } + /** * @param ResourceLoaderContext $context * @return array @@ -39,39 +128,41 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule { $styles = parent::getStyles( $context ); $this->normalizeStyles( $styles ); - $default = !is_array( $logo ) ? $logo : $logo['1x']; - $styles['all'][] = '.mw-wiki-logo { background-image: ' . + $isLogoFeatureEnabled = in_array( 'logo', $this->features ); + if ( $isLogoFeatureEnabled ) { + $default = !is_array( $logo ) ? $logo : $logo['1x']; + $styles['all'][] = '.mw-wiki-logo { background-image: ' . CSSMin::buildUrlValue( $default ) . '; }'; - - if ( is_array( $logo ) ) { - if ( isset( $logo['svg'] ) ) { - $styles['all'][] = '.mw-wiki-logo { ' . - 'background-image: -webkit-linear-gradient(transparent, transparent), ' . - CSSMin::buildUrlValue( $logo['svg'] ) . '; ' . - 'background-image: linear-gradient(transparent, transparent), ' . - CSSMin::buildUrlValue( $logo['svg'] ) . ';' . - 'background-size: 135px auto; }'; - } else { - if ( isset( $logo['1.5x'] ) ) { - $styles[ - '(-webkit-min-device-pixel-ratio: 1.5), ' . - '(min--moz-device-pixel-ratio: 1.5), ' . - '(min-resolution: 1.5dppx), ' . - '(min-resolution: 144dpi)' - ][] = '.mw-wiki-logo { background-image: ' . - CSSMin::buildUrlValue( $logo['1.5x'] ) . ';' . - 'background-size: 135px auto; }'; - } - if ( isset( $logo['2x'] ) ) { - $styles[ - '(-webkit-min-device-pixel-ratio: 2), ' . - '(min--moz-device-pixel-ratio: 2), ' . - '(min-resolution: 2dppx), ' . - '(min-resolution: 192dpi)' - ][] = '.mw-wiki-logo { background-image: ' . - CSSMin::buildUrlValue( $logo['2x'] ) . ';' . - 'background-size: 135px auto; }'; + if ( is_array( $logo ) ) { + if ( isset( $logo['svg'] ) ) { + $styles['all'][] = '.mw-wiki-logo { ' . + 'background-image: -webkit-linear-gradient(transparent, transparent), ' . + CSSMin::buildUrlValue( $logo['svg'] ) . '; ' . + 'background-image: linear-gradient(transparent, transparent), ' . + CSSMin::buildUrlValue( $logo['svg'] ) . ';' . + 'background-size: 135px auto; }'; + } else { + if ( isset( $logo['1.5x'] ) ) { + $styles[ + '(-webkit-min-device-pixel-ratio: 1.5), ' . + '(min--moz-device-pixel-ratio: 1.5), ' . + '(min-resolution: 1.5dppx), ' . + '(min-resolution: 144dpi)' + ][] = '.mw-wiki-logo { background-image: ' . + CSSMin::buildUrlValue( $logo['1.5x'] ) . ';' . + 'background-size: 135px auto; }'; + } + if ( isset( $logo['2x'] ) ) { + $styles[ + '(-webkit-min-device-pixel-ratio: 2), ' . + '(min--moz-device-pixel-ratio: 2), ' . + '(min-resolution: 2dppx), ' . + '(min-resolution: 192dpi)' + ][] = '.mw-wiki-logo { background-image: ' . + CSSMin::buildUrlValue( $logo['2x'] ) . ';' . + 'background-size: 135px auto; }'; + } } } } diff --git a/resources/Resources.php b/resources/Resources.php index 5425e6ed207..33b6ae612ca 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -50,55 +50,22 @@ return [ 'user.options' => [ 'class' => ResourceLoaderUserOptionsModule::class ], 'user.tokens' => [ 'class' => ResourceLoaderUserTokensModule::class ], - /* MediaWiki base skinning modules */ - - /** - * Common skin styles, grouped into three graded levels. - * - * Level 1 "elements": - * The base level that only contains the most basic of common skin styles. - * Only styles for single elements are included, no styling for complex structures like the - * TOC is present. This level is for skins that want to implement the entire style of even - * content area structures like the TOC themselves. - * - * Level 2 "content": - * The most commonly used level for skins implemented from scratch. This level includes all - * the single element styles from "elements" as well as styles for complex structures such - * as the TOC that are output in the content area by MediaWiki rather than the skin. - * Essentially this is the common level that lets skins leave the style of the content area - * as it is normally styled, while leaving the rest of the skin up to the skin - * implementation. - * - * Level 3 "interface": - * The highest level, this stylesheet contains extra common styles for classes like - * .firstHeading, #contentSub, et cetera which are not outputted by MediaWiki but are common - * to skins like MonoBook, Vector, etc... Essentially this level is for styles that are - * common to MonoBook clones. - * - * These modules are typically loaded by addModuleStyles(), which has absolutely no concept of - * dependency management. As a result they contain duplicate stylesheet references instead of - * setting 'dependencies' to the lower level the module is based on. For this reason avoid - * including more than one of them into your skin as this will result in duplicate CSS. - */ 'mediawiki.skinning.elements' => [ - 'styles' => [ - 'resources/src/mediawiki.skinning/elements.css' => [ 'media' => 'screen' ], - ], + 'deprecated' => 'Your default skin ResourceLoader class should use ' + . 'ResourceLoaderSkinModule::class', + 'class' => ResourceLoaderSkinModule::class, + 'features' => [ 'elements' ], ], 'mediawiki.skinning.content' => [ - 'styles' => [ - 'resources/src/mediawiki.skinning/elements.css' => [ 'media' => 'screen' ], - 'resources/src/mediawiki.skinning/content.css' => [ 'media' => 'screen' ], - ], + 'deprecated' => 'Your default skin ResourceLoader class should use ' + . 'ResourceLoaderSkinModule::class', + 'class' => ResourceLoaderSkinModule::class, + 'features' => [ 'elements', 'content' ], ], // Used in the web installer. Test it after modifying this definition! 'mediawiki.skinning.interface' => [ 'class' => ResourceLoaderSkinModule::class, - 'styles' => [ - 'resources/src/mediawiki.skinning/elements.css' => [ 'media' => 'screen' ], - 'resources/src/mediawiki.skinning/content.css' => [ 'media' => 'screen' ], - 'resources/src/mediawiki.skinning/interface.css' => [ 'media' => 'screen' ], - ], + 'features' => [ 'elements', 'content', 'interface', 'logo' ], ], 'jquery.makeCollapsible.styles' => [ 'targets' => [ 'desktop', 'mobile' ], diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php index 23b0cb911b9..5e994c58b71 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php @@ -75,7 +75,6 @@ CSS */ public function testGetStyles( $parent, $logo, $expected ) { $module = $this->getMockBuilder( ResourceLoaderSkinModule::class ) - ->disableOriginalConstructor() ->setMethods( [ 'readStyleFiles', 'getConfig', 'getLogoData' ] ) ->getMock(); $module->expects( $this->once() )->method( 'readStyleFiles' )