// Build list of variables
$skin = $context->getSkin();
$vars = [
- 'wgLoadScript' => $conf->get( 'LoadScript' ),
'debug' => $context->getDebug(),
'skin' => $skin,
'stylepath' => $conf->get( 'StylePath' ),
'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
'wgIllegalFileChars' => Title::convertByteClassToUnicodeClass( $illegalFileChars ),
- 'wgResourceLoaderStorageVersion' => $conf->get( 'ResourceLoaderStorageVersion' ),
'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
'wgForeignUploadTargets' => $conf->get( 'ForeignUploadTargets' ),
'wgEnableUploads' => $conf->get( 'EnableUploads' ),
*
* @param array $registryData
* @param string $moduleName
+ * @param string[] $handled Internal parameter for recursion. (Optional)
* @return array
+ * @throws ResourceLoaderCircularDependencyError
*/
- protected static function getImplicitDependencies( array $registryData, $moduleName ) {
+ protected static function getImplicitDependencies(
+ array $registryData,
+ $moduleName,
+ array $handled = []
+ ) {
static $dependencyCache = [];
- // The list of implicit dependencies won't be altered, so we can
- // cache them without having to worry.
+ // No modules will be added or changed server-side after this point,
+ // so we can safely cache parts of the tree for re-use.
if ( !isset( $dependencyCache[$moduleName] ) ) {
if ( !isset( $registryData[$moduleName] ) ) {
- // Dependencies may not exist
- $dependencyCache[$moduleName] = [];
+ // Unknown module names are allowed here, this is only an optimisation.
+ // Checks for illegal and unknown dependencies happen as PHPUnit structure tests,
+ // and also client-side at run-time.
+ $flat = [];
} else {
$data = $registryData[$moduleName];
- $dependencyCache[$moduleName] = $data['dependencies'];
+ $flat = $data['dependencies'];
+ // Prevent recursion
+ $handled[] = $moduleName;
foreach ( $data['dependencies'] as $dependency ) {
- // Recursively get the dependencies of the dependencies
- $dependencyCache[$moduleName] = array_merge(
- $dependencyCache[$moduleName],
- self::getImplicitDependencies( $registryData, $dependency )
- );
+ if ( in_array( $dependency, $handled, true ) ) {
+ // If we encounter a circular dependency, then stop the optimiser and leave the
+ // original dependencies array unmodified. Circular dependencies are not
+ // supported in ResourceLoader. Awareness of them exists here so that we can
+ // optimise the registry when it isn't broken, and otherwise transport the
+ // registry unchanged. The client will handle this further.
+ throw new ResourceLoaderCircularDependencyError();
+ } else {
+ // Recursively add the dependencies of the dependencies
+ $flat = array_merge(
+ $flat,
+ self::getImplicitDependencies( $registryData, $dependency, $handled )
+ );
+ }
}
}
+
+ $dependencyCache[$moduleName] = $flat;
}
return $dependencyCache[$moduleName];
public static function compileUnresolvedDependencies( array &$registryData ) {
foreach ( $registryData as $name => &$data ) {
$dependencies = $data['dependencies'];
- foreach ( $data['dependencies'] as $dependency ) {
- $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency );
- $dependencies = array_diff( $dependencies, $implicitDependencies );
+ try {
+ foreach ( $data['dependencies'] as $dependency ) {
+ $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency );
+ $dependencies = array_diff( $dependencies, $implicitDependencies );
+ }
+ } catch ( ResourceLoaderCircularDependencyError $err ) {
+ // Leave unchanged
+ $dependencies = $data['dependencies'];
}
+
// Rebuild keys
$data['dependencies'] = array_values( $dependencies );
}
continue;
}
- if ( $module->isRaw() ) {
- // Don't register "raw" modules (like 'startup') client-side because depending on them
- // is illegal anyway and would only lead to them being loaded a second time,
- // causing any state to be lost.
+ if ( $module instanceof ResourceLoaderStartUpModule ) {
+ // Don't register 'startup' to the client because loading it lazily or depending
+ // on it doesn't make sense, because the startup module *is* the client.
+ // Registering would be a waste of bandwidth and memory and risks somehow causing
+ // it to load a second time.
// ATTENTION: Because of the line below, this is not going to cause infinite recursion.
// Think carefully before making changes to this code!
return $out;
}
- /**
- * @return bool
- */
- public function isRaw() {
- return true;
- }
-
/**
* @private For internal use by SpecialJavaScriptTest
* @since 1.32
return $baseModules;
}
+ /**
+ * Get the localStorage key for the entire module store. The key references
+ * $wgDBname to prevent clashes between wikis under the same web domain.
+ *
+ * @return string localStorage item key for JavaScript
+ */
+ private function getStoreKey() {
+ return 'MediaWikiModuleStore:' . $this->getConfig()->get( 'DBname' );
+ }
+
+ /**
+ * Get the key on which the JavaScript module cache (mw.loader.store) will vary.
+ *
+ * @param ResourceLoaderContext $context
+ * @return string String of concatenated vary conditions
+ */
+ private function getStoreVary( ResourceLoaderContext $context ) {
+ return implode( ':', [
+ $context->getSkin(),
+ $this->getConfig()->get( 'ResourceLoaderStorageVersion' ),
+ $context->getLanguage(),
+ ] );
+ }
+
/**
* @param ResourceLoaderContext $context
* @return string JavaScript code
// Perform replacements for mediawiki.js
$mwLoaderPairs = [
+ '$VARS.reqBase' => ResourceLoader::encodeJsonForScript( $context->getReqBase() ),
'$VARS.baseModules' => ResourceLoader::encodeJsonForScript( $this->getBaseModules() ),
'$VARS.maxQueryLength' => ResourceLoader::encodeJsonForScript(
$conf->get( 'ResourceLoaderMaxQueryLength' )
),
+ '$VARS.storeKey' => ResourceLoader::encodeJsonForScript( $this->getStoreKey() ),
+ '$VARS.storeVary' => ResourceLoader::encodeJsonForScript( $this->getStoreVary( $context ) ),
];
$profilerStubs = [
'$CODE.profileExecuteStart();' => 'mw.loader.profiler.onExecuteStart( module );',
// and hash it to determine the version (as used by E-Tag HTTP response header).
return true;
}
-
- /**
- * @return string
- */
- public function getGroup() {
- return 'startup';
- }
}