wiki.techinc.nl/tests/phpunit/ResourceLoaderTestCase.php
Timo Tijhof b99458ceab resourceloader: Fix debug mode for RL-to-RL cross-wiki module loads
The native "foreign module source" feature, as used by the GlobalCssJs
extension, did not work correctly in debug mode as the urls returned
by the remote wiki were formatted as "/w/load.php...", which would
be interpreted by the browser relative to the host document, instead
of relative to the parent script.

For example:

1. Page view on en.wikipedia.org.

2. Script call to
   meta.wikimedia.org/w/load.php?debug=true&modules=ext.globalCssJs.user&user

   This URL is formatted by getScriptURLsForDebug on en.wikipedia.org,
   when building the article HTML. It knows the modules is on Meta, and
   formats it as such.

   So far so good.

3. meta.wikimedia.org responds with an array of urls for sub resources.
   That array contained URLs like "/w/load.php...only=scripts".

   These were formatted by getScriptURLsForDebug running on Meta,
   no longer with a reason to make it a Meta-Wiki URL as it isn't
   perceived as cross-wiki. It is indistinguishable from debugging
   a Meta-Wiki page view from its perspective.

This patch affects scenario 3 by always expanding it relative to the
current-request's wgServer. We still only do this in debug mode. There
is not yet a need to do this in non-debug mode, and if there was we'd
likely want to find a way to avoid it in the common case to keep
embedded URLs short.

The ResourceLoader::expandUrl() method is similar to the one in
Wikimedia\Minify\CSSMin.

Test Plan:

* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=site
  For Module base class.
  Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/w/load.php?debug=1&modules=jquery
  For FileModule.
  Before, the array entries were relative. After, they are full.
* view-source:http://mw.localhost:8080/wiki/Main_Page?debug=true
  Unchanged.
* view-source:http://mw.localhost:8080/wiki/Main_Page
  Unchanged.

Bug: T255367
Change-Id: I83919744b2677c7fb52b84089ecc60b89957d32a
2021-08-25 20:05:38 +00:00

238 lines
6.3 KiB
PHP

<?php
use MediaWiki\MediaWikiServices;
use Psr\Log\LoggerInterface;
abstract class ResourceLoaderTestCase extends MediaWikiIntegrationTestCase {
// Version hash for a blank file module.
// Result of ResourceLoader::makeHash(), ResourceLoaderTestModule
// and ResourceLoaderFileModule::getDefinitionSummary().
public const BLANK_VERSION = '9p30q';
// Result of ResoureLoader::makeVersionQuery() for a blank file module.
// In other words, result of ResourceLoader::makeHash( BLANK_VERSION );
public const BLANK_COMBI = 'rbml8';
/**
* @param array|string $options Language code or options array
* - string 'lang' Language code
* - string 'dir' Language direction (ltr or rtl)
* - string 'modules' Pipe-separated list of module names
* - string|null 'only' "scripts" (unwrapped script), "styles" (stylesheet), or null
* (mw.loader.implement).
* @param ResourceLoader|null $rl
* @return ResourceLoaderContext
*/
protected function getResourceLoaderContext( $options = [], ResourceLoader $rl = null ) {
if ( is_string( $options ) ) {
// Back-compat for extension tests
$options = [ 'lang' => $options ];
}
$options += [
'debug' => 'true',
'lang' => 'en',
'dir' => 'ltr',
'skin' => 'fallback',
'modules' => 'startup',
'only' => 'scripts',
'safemode' => null,
];
$resourceLoader = $rl ?: new ResourceLoader( MediaWikiServices::getInstance()->getMainConfig() );
$request = new FauxRequest( [
'debug' => $options['debug'],
'lang' => $options['lang'],
'modules' => $options['modules'],
'only' => $options['only'],
'safemode' => $options['safemode'],
'skin' => $options['skin'],
'target' => 'phpunit',
] );
$ctx = $this->getMockBuilder( ResourceLoaderContext::class )
->setConstructorArgs( [ $resourceLoader, $request ] )
->onlyMethods( [ 'getDirection' ] )
->getMock();
$ctx->method( 'getDirection' )->willReturn( $options['dir'] );
return $ctx;
}
public static function getSettings() {
return [
// For ResourceLoader::inDebugMode since it doesn't have context
'ResourceLoaderDebug' => true,
// For ResourceLoaderModule
'ResourceLoaderValidateJS' => false,
// For ResourceLoaderWikiModule
'MaxRedirects' => 1,
// For ResourceLoaderSkinModule
'Logos' => false,
'Logo' => '/logo.png',
'ResourceBasePath' => '/w',
'ParserEnableLegacyMediaDOM' => true,
// For ResourceLoader::expandUrl()
'Server' => 'https://example.org',
// For ResourceLoaderStartUpModule and ResourceLoader::__construct()
'ScriptPath' => '/w',
'Script' => '/w/index.php',
'LoadScript' => '/w/load.php',
'EnableJavaScriptTest' => false,
'ResourceLoaderEnableJSProfiler' => false,
// For ResourceLoader::respond() - TODO: Inject somehow T32956
'UseFileCache' => false,
];
}
public static function getMinimalConfig() {
return new HashConfig( self::getSettings() );
}
/**
* The annotation causes this to be called immediately before setUp()
* @before
*/
final protected function mediaWikiResourceLoaderSetUp(): void {
ResourceLoader::clearCache();
$globals = [];
foreach ( self::getSettings() as $key => $value ) {
$globals['wg' . $key] = $value;
}
$this->setMwGlobals( $globals );
}
}
/* Stubs */
class ResourceLoaderTestModule extends ResourceLoaderModule {
protected $messages = [];
protected $dependencies = [];
protected $group = null;
protected $source = 'local';
protected $script = '';
protected $styles = '';
protected $skipFunction = null;
protected $es6 = false;
protected $isRaw = false;
protected $isKnownEmpty = false;
protected $type = ResourceLoaderModule::LOAD_GENERAL;
protected $targets = [ 'phpunit' ];
protected $shouldEmbed = null;
protected $mayValidateScript = false;
public function __construct( $options = [] ) {
foreach ( $options as $key => $value ) {
$this->$key = $value;
}
}
public function getScript( ResourceLoaderContext $context ) {
if ( $this->mayValidateScript ) {
// This enables the validation check that replaces invalid
// scripts with a warning message.
// Based on $wgResourceLoaderValidateJS
return $this->validateScriptFile( 'input', $this->script );
} else {
return $this->script;
}
}
public function getStyles( ResourceLoaderContext $context ) {
return [ '' => $this->styles ];
}
public function getMessages() {
return $this->messages;
}
public function getDependencies( ResourceLoaderContext $context = null ) {
return $this->dependencies;
}
public function getGroup() {
return $this->group;
}
public function getSource() {
return $this->source;
}
public function getType() {
return $this->type;
}
public function getSkipFunction() {
return $this->skipFunction;
}
public function requiresES6() {
return $this->es6;
}
public function isRaw() {
return $this->isRaw;
}
public function isKnownEmpty( ResourceLoaderContext $context ) {
return $this->isKnownEmpty;
}
public function shouldEmbedModule( ResourceLoaderContext $context ) {
return $this->shouldEmbed ?? parent::shouldEmbedModule( $context );
}
public function enableModuleContentVersion() {
return true;
}
}
/**
* A more constrained and testable variant of ResourceLoaderFileModule.
*
* - Implements getLessVars() support.
* - Disables database persistance of discovered file dependencies.
*/
class ResourceLoaderFileTestModule extends ResourceLoaderFileModule {
protected $lessVars = [];
public function __construct( $options = [] ) {
if ( isset( $options['lessVars'] ) ) {
$this->lessVars = $options['lessVars'];
unset( $options['lessVars'] );
}
parent::__construct( $options );
}
public function getLessVars( ResourceLoaderContext $context ) {
return $this->lessVars;
}
/**
* @param ResourceLoaderContext $context
* @return array
*/
protected function getFileDependencies( ResourceLoaderContext $context ) {
// No-op
return [];
}
protected function saveFileDependencies( ResourceLoaderContext $context, $refs ) {
// No-op
}
}
class ResourceLoaderFileModuleTestingSubclass extends ResourceLoaderFileModule {
}
class EmptyResourceLoader extends ResourceLoader {
public function __construct( Config $config = null, LoggerInterface $logger = null ) {
parent::__construct( $config ?: ResourceLoaderTestCase::getMinimalConfig(), $logger );
}
public function getErrors() {
return $this->errors;
}
}