MediaWiki  master
ResourceLoaderSkinModule.php
Go to the documentation of this file.
1 <?php
20 use Wikimedia\Minify\CSSMin;
21 
32  public $targets = [ 'desktop', 'mobile' ];
33 
116  private const FEATURE_FILES = [
117  'normalize' => [
118  'all' => [ 'resources/src/mediawiki.skinning/normalize.less' ],
119  ],
120  'logo' => [
121  // Applies the logo and ensures it downloads prior to printing.
122  'all' => [ 'resources/src/mediawiki.skinning/logo.less' ],
123  // Reserves whitespace for the logo in a pseudo element.
124  'print' => [ 'resources/src/mediawiki.skinning/logo-print.less' ],
125  ],
126  'content-media' => [
127  'all' => [ 'resources/src/mediawiki.skinning/content.thumbnails-common.less' ],
128  'screen' => [ 'resources/src/mediawiki.skinning/content.thumbnails-screen.less' ],
129  'print' => [ 'resources/src/mediawiki.skinning/content.thumbnails-print.less' ],
130  ],
131  'content-links' => [
132  'screen' => [ 'resources/src/mediawiki.skinning/content.links.less' ]
133  ],
134  'content-links-external' => [
135  'screen' => [ 'resources/src/mediawiki.skinning/content.externallinks.less' ]
136  ],
137  'content-body' => [
138  'screen' => [ 'resources/src/mediawiki.skinning/content.body.less' ],
139  'print' => [ 'resources/src/mediawiki.skinning/content.body-print.less' ],
140  ],
141  'content-tables' => [
142  'screen' => [ 'resources/src/mediawiki.skinning/content.tables.less' ],
143  'print' => [ 'resources/src/mediawiki.skinning/content.tables-print.less' ]
144  ],
145  'interface' => [
146  'screen' => [ 'resources/src/mediawiki.skinning/interface.less' ],
147  'print' => [ 'resources/src/mediawiki.skinning/interface-print.less' ],
148  ],
149  'interface-category' => [
150  'screen' => [ 'resources/src/mediawiki.skinning/interface.category.less' ],
151  'print' => [ 'resources/src/mediawiki.skinning/interface.category-print.less' ],
152  ],
153  'interface-message-box' => [
154  'all' => [ 'resources/src/mediawiki.skinning/messageBoxes.less' ],
155  ],
156  'elements' => [
157  'screen' => [ 'resources/src/mediawiki.skinning/elements.less' ],
158  'print' => [ 'resources/src/mediawiki.skinning/elements-print.less' ],
159  ],
160  'legacy' => [
161  'all' => [ 'resources/src/mediawiki.skinning/messageBoxes.less' ],
162  'print' => [ 'resources/src/mediawiki.skinning/commonPrint.less' ],
163  'screen' => [ 'resources/src/mediawiki.skinning/legacy.less' ],
164  ],
165  'i18n-ordered-lists' => [
166  'screen' => [ 'resources/src/mediawiki.skinning/i18n-ordered-lists.less' ],
167  ],
168  'i18n-all-lists-margins' => [
169  'screen' => [ 'resources/src/mediawiki.skinning/i18n-all-lists-margins.less' ],
170  ],
171  'i18n-headings' => [
172  'screen' => [ 'resources/src/mediawiki.skinning/i18n-headings.less' ],
173  ],
174  'toc' => [
175  'all' => [ 'resources/src/mediawiki.skinning/toc/common.css' ],
176  'screen' => [ 'resources/src/mediawiki.skinning/toc/screen.less' ],
177  'print' => [ 'resources/src/mediawiki.skinning/toc/print.css' ],
178  ],
179  ];
180 
182  private $features;
183 
191  private const DEFAULT_FEATURES_SPECIFIED = [
192  'content-body' => true,
193  'toc' => true,
194  ];
195 
204  private const DEFAULT_FEATURES_ABSENT = [
205  'logo',
206  'legacy',
207  ];
208 
209  private const LESS_MESSAGES = [
210  // `toc` feature, used in screen.less
211  'hidetoc',
212  'showtoc',
213  ];
214 
235  public function __construct(
236  array $options = [],
237  $localBasePath = null,
238  $remoteBasePath = null
239  ) {
240  $features = $options['features'] ?? self::DEFAULT_FEATURES_ABSENT;
241  $listMode = array_keys( $features ) === range( 0, count( $features ) - 1 );
242 
243  $messages = '';
244  // NOTE: Compatibility is only applied when features are provided
245  // in map-form. The list-form does not currently get these.
246  $features = $listMode ? self::applyFeaturesCompatibility( array_fill_keys( $features, true ), $messages ) :
248 
249  foreach ( $features as $key => $enabled ) {
250  if ( !isset( self::FEATURE_FILES[$key] ) ) {
251  throw new InvalidArgumentException( "Feature '$key' is not recognised" );
252  }
253  }
254 
255  $this->features = $listMode
256  ? array_keys( array_filter( $features ) )
257  : array_keys( array_filter( $features + self::DEFAULT_FEATURES_SPECIFIED ) );
258 
259  // Only the `toc` feature makes use of interface messages.
260  // For skins not using the `toc` feature, make sure LocalisationCache
261  // remains untouched (T270027).
262  if ( in_array( 'toc', $this->features ) ) {
263  $options['lessMessages'] = array_merge(
264  $options['lessMessages'] ?? [],
265  self::LESS_MESSAGES
266  );
267  }
268 
269  if ( $messages !== '' ) {
270  $messages .= 'More information can be found at [[mw:Manual:ResourceLoaderSkinModule]]. ';
271  $options['deprecated'] = $messages;
272  }
273  parent::__construct( $options, $localBasePath, $remoteBasePath );
274  }
275 
282  protected static function applyFeaturesCompatibility( array $features, &$messages = '' ): array {
283  // The `content` feature is mapped to `content-media`.
284  if ( isset( $features[ 'content' ] ) ) {
285  $features[ 'content-media' ] = $features[ 'content' ];
286  unset( $features[ 'content' ] );
287  $messages .= '[1.37] The use of the `content` feature with ResourceLoaderSkinModule'
288  . ' is deprecated. Use `content-media` instead. ';
289  }
290 
291  // The `content-thumbnails` feature is mapped to `content-media`.
292  if ( isset( $features[ 'content-thumbnails' ] ) ) {
293  $features[ 'content-media' ] = $features[ 'content-thumbnails' ];
294  $messages .= '[1.37] The use of the `content-thumbnails` feature with ResourceLoaderSkinModule'
295  . ' is deprecated. Use `content-media` instead. ';
296  unset( $features[ 'content-thumbnails' ] );
297  }
298 
299  // If `content-links` feature is set but no preference for `content-links-external` is set
300  if ( isset( $features[ 'content-links' ] ) && !isset( $features[ 'content-links-external' ] ) ) {
301  // Assume the same true/false preference for both.
302  $features[ 'content-links-external' ] = $features[ 'content-links' ];
303  }
304 
305  // The legacy feature is deprecated (T89981).
306  if ( isset( $features['legacy'] ) && $features['legacy'] ) {
307  $messages .= '[1.37] The use of the `legacy` feature with ResourceLoaderSkinModule is deprecated'
308  . '(T89981). ';
309  }
310 
311  // The `content-links` feature was split out from `elements`.
312  // Make sure skins asking for `elements` also get these by default.
313  if ( isset( $features[ 'element' ] ) && !isset( $features[ 'content-links' ] ) ) {
314  $features[ 'content-links' ] = $features[ 'element' ];
315  }
316 
317  // `content-parser-output` was renamed to `content-body`.
318  // No need to go through deprecation process here since content-parser-output added and removed in 1.36.
319  // Remove this check when no matches for
320  // https://codesearch.wmcloud.org/search/?q=content-parser-output&i=nope&files=&excludeFiles=&repos=
321  if ( isset( $features[ 'content-parser-output' ] ) ) {
322  $features[ 'content-body' ] = $features[ 'content-parser-output' ];
323  unset( $features[ 'content-parser-output' ] );
324  }
325 
326  return $features;
327  }
328 
335  public function getStyleFiles( ResourceLoaderContext $context ) {
336  $styles = parent::getStyleFiles( $context );
337 
338  // Bypass the current module paths so that these files are served from core,
339  // instead of the individual skin's module directory.
340  list( $defaultLocalBasePath, $defaultRemoteBasePath ) =
342  [],
343  null,
344  $this->getConfig()->get( 'ResourceBasePath' )
345  );
346 
347  $featureFilePaths = [];
348 
349  foreach ( self::FEATURE_FILES as $feature => $featureFiles ) {
350  if ( in_array( $feature, $this->features ) ) {
351  foreach ( $featureFiles as $mediaType => $files ) {
352  foreach ( $files as $filepath ) {
353  $featureFilePaths[$mediaType][] = new ResourceLoaderFilePath(
354  $filepath,
355  $defaultLocalBasePath,
356  $defaultRemoteBasePath
357  );
358  }
359  }
360  if ( $feature === 'content-media' && (
361  !$this->getConfig()->get( 'ParserEnableLegacyMediaDOM' ) ||
362  $this->getConfig()->get( 'UseContentMediaStyles' )
363  ) ) {
364  $featureFilePaths['all'][] = new ResourceLoaderFilePath(
365  'resources/src/mediawiki.skinning/content.media-common.less',
366  $defaultLocalBasePath,
367  $defaultRemoteBasePath
368  );
369  $featureFilePaths['screen'][] = new ResourceLoaderFilePath(
370  'resources/src/mediawiki.skinning/content.media-screen.less',
371  $defaultLocalBasePath,
372  $defaultRemoteBasePath
373  );
374  $featureFilePaths['print'][] = new ResourceLoaderFilePath(
375  'resources/src/mediawiki.skinning/content.media-print.less',
376  $defaultLocalBasePath,
377  $defaultRemoteBasePath
378  );
379  }
380  }
381  }
382 
383  // Styles defines in options are added to the $featureFilePaths to ensure
384  // that $featureFilePaths styles precede module defined ones.
385  // This is particularly important given the `normalize` styles need to be the first
386  // outputted (see T269618).
387  foreach ( $styles as $mediaType => $paths ) {
388  $featureFilePaths[$mediaType] = array_merge( $featureFilePaths[$mediaType] ?? [], $paths );
389  }
390 
391  return $featureFilePaths;
392  }
393 
398  public function getStyles( ResourceLoaderContext $context ) {
399  $logo = $this->getLogoData( $this->getConfig() );
400  $styles = parent::getStyles( $context );
401  $this->normalizeStyles( $styles );
402 
403  $isLogoFeatureEnabled = in_array( 'logo', $this->features );
404  if ( $isLogoFeatureEnabled ) {
405  $default = !is_array( $logo ) ? $logo : $logo['1x'];
406  $styles['all'][] = '.mw-wiki-logo { background-image: ' .
407  CSSMin::buildUrlValue( $default ) .
408  '; }';
409 
410  if ( is_array( $logo ) ) {
411  if ( isset( $logo['svg'] ) ) {
412  $styles['all'][] = '.mw-wiki-logo { ' .
413  'background-image: -webkit-linear-gradient(transparent, transparent), ' .
414  CSSMin::buildUrlValue( $logo['svg'] ) . '; ' .
415  'background-image: linear-gradient(transparent, transparent), ' .
416  CSSMin::buildUrlValue( $logo['svg'] ) . ';' .
417  'background-size: 135px auto; }';
418  } else {
419  if ( isset( $logo['1.5x'] ) ) {
420  $styles[
421  '(-webkit-min-device-pixel-ratio: 1.5), ' .
422  '(min-resolution: 1.5dppx), ' .
423  '(min-resolution: 144dpi)'
424  ][] = '.mw-wiki-logo { background-image: ' .
425  CSSMin::buildUrlValue( $logo['1.5x'] ) . ';' .
426  'background-size: 135px auto; }';
427  }
428  if ( isset( $logo['2x'] ) ) {
429  $styles[
430  '(-webkit-min-device-pixel-ratio: 2), ' .
431  '(min-resolution: 2dppx), ' .
432  '(min-resolution: 192dpi)'
433  ][] = '.mw-wiki-logo { background-image: ' .
434  CSSMin::buildUrlValue( $logo['2x'] ) . ';' .
435  'background-size: 135px auto; }';
436  }
437  }
438  }
439  }
440 
441  return $styles;
442  }
443 
448  public function getPreloadLinks( ResourceLoaderContext $context ) {
449  return $this->getLogoPreloadlinks();
450  }
451 
456  private function getLogoPreloadlinks(): array {
457  if ( !in_array( 'logo', $this->features ) ) {
458  return [];
459  }
460 
461  $logo = $this->getLogoData( $this->getConfig() );
462 
463  if ( !is_array( $logo ) ) {
464  // No media queries required if we only have one variant
465  return [ $logo => [ 'as' => 'image' ] ];
466  }
467 
468  if ( isset( $logo['svg'] ) ) {
469  // No media queries required if we only have a 1x and svg variant
470  // because all preload-capable browsers support SVGs
471  return [ $logo['svg'] => [ 'as' => 'image' ] ];
472  }
473 
474  $logosPerDppx = [];
475  foreach ( $logo as $dppx => $src ) {
476  // Keys are in this format: "1.5x"
477  $dppx = substr( $dppx, 0, -1 );
478  $logosPerDppx[$dppx] = $src;
479  }
480 
481  // Because PHP can't have floats as array keys
482  uksort( $logosPerDppx, static function ( $a, $b ) {
483  $a = floatval( $a );
484  $b = floatval( $b );
485  // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
486  return $a <=> $b;
487  } );
488 
489  $logos = [];
490  foreach ( $logosPerDppx as $dppx => $src ) {
491  $logos[] = [
492  'dppx' => $dppx,
493  'src' => $src
494  ];
495  }
496 
497  $logosCount = count( $logos );
498  $preloadLinks = [];
499  // Logic must match ResourceLoaderSkinModule:
500  // - 1x applies to resolution < 1.5dppx
501  // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
502  // - 2x applies to resolution >= 2dppx
503  // Note that min-resolution and max-resolution are both inclusive.
504  for ( $i = 0; $i < $logosCount; $i++ ) {
505  if ( $i === 0 ) {
506  // Smallest dppx
507  // min-resolution is ">=" (larger than or equal to)
508  // "not min-resolution" is essentially "<"
509  $media_query = 'not all and (min-resolution: ' . $logos[1]['dppx'] . 'dppx)';
510  } elseif ( $i !== $logosCount - 1 ) {
511  // In between
512  // Media query expressions can only apply "not" to the entire expression
513  // (e.g. can't express ">= 1.5 and not >= 2).
514  // Workaround: Use <= 1.9999 in place of < 2.
515  $upper_bound = floatval( $logos[$i + 1]['dppx'] ) - 0.000001;
516  $media_query = '(min-resolution: ' . $logos[$i]['dppx'] .
517  'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
518  } else {
519  // Largest dppx
520  $media_query = '(min-resolution: ' . $logos[$i]['dppx'] . 'dppx)';
521  }
522 
523  $preloadLinks[$logos[$i]['src']] = [
524  'as' => 'image',
525  'media' => $media_query
526  ];
527  }
528 
529  return $preloadLinks;
530  }
531 
540  private function normalizeStyles( array &$styles ): void {
541  foreach ( $styles as $key => $val ) {
542  if ( !is_array( $val ) ) {
543  $styles[$key] = [ $val ];
544  }
545  }
546  }
547 
554  private static function getRelativeSizedLogo( array $logoElement ) {
555  $width = $logoElement['width'];
556  $height = $logoElement['height'];
557  $widthRelative = $width / 16;
558  $heightRelative = $height / 16;
559  // Allow skins to scale the wordmark with browser font size (T207789)
560  $logoElement['style'] = 'width: ' . $widthRelative . 'em; height: ' . $heightRelative . 'em;';
561  return $logoElement;
562  }
563 
578  public static function getAvailableLogos( $conf ): array {
579  $logos = $conf->get( 'Logos' );
580  if ( $logos === false ) {
581  // no logos were defined... this will either
582  // 1. Load from wgLogo and wgLogoHD
583  // 2. Trigger runtime exception if those are not defined.
584  $logos = [];
585  }
586 
587  // If logos['1x'] is not defined, see if we can use wgLogo
588  if ( !isset( $logos[ '1x' ] ) ) {
589  $logo = $conf->get( 'Logo' );
590  if ( $logo ) {
591  $logos['1x'] = $logo;
592  }
593  }
594 
595  try {
596  $logoHD = $conf->get( 'LogoHD' );
597  // make sure not false
598  if ( $logoHD ) {
599  // wfDeprecated( __METHOD__ . ' with $wgLogoHD set instead of $wgLogos', '1.35', false, 1 );
600  $logos += $logoHD;
601  }
602  } catch ( ConfigException $e ) {
603  // no backwards compatibility changes needed.
604  }
605 
606  // check the configuration is valid
607  if ( !isset( $logos['1x'] ) ) {
608  throw new RuntimeException( "The key `1x` is required for wgLogos or wgLogo must be defined." );
609  }
610 
611  // @todo: Note the beta cluster and other wikis may be using
612  // unsupported configuration where these values are set to false.
613  // The boolean check can be removed when this has been addressed.
614  if ( isset( $logos['wordmark'] ) && $logos['wordmark'] ) {
615  // Allow skins to scale the wordmark with browser font size (T207789)
616  $logos['wordmark'] = self::getRelativeSizedLogo( $logos['wordmark'] );
617  }
618 
619  // @todo: Note the beta cluster and other wikis may be using
620  // unsupported configuration where these values are set to false.
621  // The boolean check can be removed when this has been addressed.
622  if ( isset( $logos['tagline'] ) && $logos['tagline'] ) {
623  $logos['tagline'] = self::getRelativeSizedLogo( $logos['tagline'] );
624  }
625  // return the modified logos!
626  return $logos;
627  }
628 
637  protected function getLogoData( Config $conf ) {
638  $logoHD = self::getAvailableLogos( $conf );
639  $logo = $logoHD['1x'];
640 
641  $logo1Url = OutputPage::transformResourcePath( $conf, $logo );
642 
643  $logoUrls = [
644  '1x' => $logo1Url,
645  ];
646 
647  if ( isset( $logoHD['svg'] ) ) {
648  $logoUrls['svg'] = OutputPage::transformResourcePath(
649  $conf,
650  $logoHD['svg']
651  );
652  } elseif ( isset( $logoHD['1.5x'] ) || isset( $logoHD['2x'] ) ) {
653  // Only 1.5x and 2x are supported
654  if ( isset( $logoHD['1.5x'] ) ) {
655  $logoUrls['1.5x'] = OutputPage::transformResourcePath(
656  $conf,
657  $logoHD['1.5x']
658  );
659  }
660  if ( isset( $logoHD['2x'] ) ) {
661  $logoUrls['2x'] = OutputPage::transformResourcePath(
662  $conf,
663  $logoHD['2x']
664  );
665  }
666  } else {
667  // Return a string rather than a one-element array, getLogoPreloadlinks depends on this
668  return $logo1Url;
669  }
670 
671  return $logoUrls;
672  }
673 
678  public function isKnownEmpty( ResourceLoaderContext $context ) {
679  // Regardless of whether the files are specified, we always
680  // provide mw-wiki-logo styles.
681  return false;
682  }
683 
690  protected function getLessVars( ResourceLoaderContext $context ) {
691  $lessVars = parent::getLessVars( $context );
692  $logos = self::getAvailableLogos( $this->getConfig() );
693 
694  if ( isset( $logos['wordmark'] ) ) {
695  $logo = $logos['wordmark'];
696  $lessVars[ 'logo-enabled' ] = true;
697  $lessVars[ 'logo-wordmark-url' ] = CSSMin::buildUrlValue( $logo['src'] );
698  $lessVars[ 'logo-wordmark-width' ] = intval( $logo['width'] );
699  $lessVars[ 'logo-wordmark-height' ] = intval( $logo['height'] );
700  } else {
701  $lessVars[ 'logo-enabled' ] = false;
702  }
703  return $lessVars;
704  }
705 
706  public function getDefinitionSummary( ResourceLoaderContext $context ) {
707  $summary = parent::getDefinitionSummary( $context );
708  $summary[] = [
709  'logos' => self::getAvailableLogos( $this->getConfig() ),
710  ];
711  return $summary;
712  }
713 }
ResourceLoaderLessVarFileModule
Module augmented with context-specific LESS variables.
Definition: ResourceLoaderLessVarFileModule.php:31
ResourceLoaderSkinModule\getPreloadLinks
getPreloadLinks(ResourceLoaderContext $context)
Definition: ResourceLoaderSkinModule.php:448
ResourceLoaderSkinModule\LESS_MESSAGES
const LESS_MESSAGES
Definition: ResourceLoaderSkinModule.php:209
ResourceLoaderContext
Context object that contains information about the state of a specific ResourceLoader web request.
Definition: ResourceLoaderContext.php:37
ResourceLoaderFileModule\$styles
array $styles
List of paths to CSS files to always include.
Definition: ResourceLoaderFileModule.php:93
ResourceLoaderSkinModule\__construct
__construct(array $options=[], $localBasePath=null, $remoteBasePath=null)
Definition: ResourceLoaderSkinModule.php:235
true
return true
Definition: router.php:90
ResourceLoaderSkinModule\normalizeStyles
normalizeStyles(array &$styles)
Ensure all media keys use array values.
Definition: ResourceLoaderSkinModule.php:540
ResourceLoaderFilePath
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Definition: ResourceLoaderFilePath.php:28
ResourceLoaderSkinModule\getDefinitionSummary
getDefinitionSummary(ResourceLoaderContext $context)
Get the definition summary for this module.
Definition: ResourceLoaderSkinModule.php:706
ResourceLoaderSkinModule\$features
string[] $features
Definition: ResourceLoaderSkinModule.php:182
Config
Interface for configuration instances.
Definition: Config.php:30
ResourceLoaderSkinModule\getLessVars
getLessVars(ResourceLoaderContext $context)
Get language-specific LESS variables for this module.
Definition: ResourceLoaderSkinModule.php:690
ConfigException
Exceptions for config failures.
Definition: ConfigException.php:29
ResourceLoaderSkinModule\getRelativeSizedLogo
static getRelativeSizedLogo(array $logoElement)
Modifies configured logo width/height to ensure they are present and scalable with different font-siz...
Definition: ResourceLoaderSkinModule.php:554
ResourceLoaderSkinModule\getLogoData
getLogoData(Config $conf)
Definition: ResourceLoaderSkinModule.php:637
ResourceLoaderFileModule\$messages
array $messages
List of message keys used by this module.
Definition: ResourceLoaderFileModule.php:146
ResourceLoaderSkinModule\getAvailableLogos
static getAvailableLogos( $conf)
Return an array of all available logos that a skin may use.
Definition: ResourceLoaderSkinModule.php:578
ResourceLoaderSkinModule\applyFeaturesCompatibility
static applyFeaturesCompatibility(array $features, &$messages='')
Definition: ResourceLoaderSkinModule.php:282
ResourceLoaderFileModule\extractBasePaths
static extractBasePaths(array $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
Definition: ResourceLoaderFileModule.php:306
ResourceLoaderSkinModule\FEATURE_FILES
const FEATURE_FILES
Every skin should define which features it would like to reuse for core inside a ResourceLoader modul...
Definition: ResourceLoaderSkinModule.php:116
ResourceLoaderSkinModule\getLogoPreloadlinks
getLogoPreloadlinks()
Helper method for getPreloadLinks()
Definition: ResourceLoaderSkinModule.php:456
ResourceLoaderSkinModule\getStyles
getStyles(ResourceLoaderContext $context)
Definition: ResourceLoaderSkinModule.php:398
ResourceLoaderFileModule\$localBasePath
string $localBasePath
Local base path, see __construct()
Definition: ResourceLoaderFileModule.php:42
ResourceLoaderSkinModule\isKnownEmpty
isKnownEmpty(ResourceLoaderContext $context)
Definition: ResourceLoaderSkinModule.php:678
ResourceLoaderSkinModule\getStyleFiles
getStyleFiles(ResourceLoaderContext $context)
Get styles defined in the module definition, plus any enabled feature styles.
Definition: ResourceLoaderSkinModule.php:335
ResourceLoaderFileModule\$remoteBasePath
string $remoteBasePath
Remote base path, see __construct()
Definition: ResourceLoaderFileModule.php:45
ResourceLoaderSkinModule
Module for skin stylesheets.
Definition: ResourceLoaderSkinModule.php:28
ResourceLoaderModule\getConfig
getConfig()
Definition: ResourceLoaderModule.php:243
ResourceLoaderSkinModule\$targets
$targets
All skins are assumed to be compatible with mobile.
Definition: ResourceLoaderSkinModule.php:32
OutputPage\transformResourcePath
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
Definition: OutputPage.php:4042