$group = $module->getGroup();
$context = $this->getContext( $group, ResourceLoaderModule::TYPE_COMBINED );
- if ( $module->isKnownEmpty( $context ) ) {
- // Avoid needless request or embed for empty module
- $data['states'][$name] = 'ready';
- continue;
- }
+ $shouldEmbed = $module->shouldEmbedModule( $this->context );
- if ( $group === 'user' || $module->shouldEmbedModule( $this->context ) ) {
- // Call makeLoad() to decide how to load these, instead of
- // loading via mw.loader.load().
+ if ( ( $group === 'user' || $shouldEmbed ) && $module->isKnownEmpty( $context ) ) {
+ // This is a user-specific or embedded module, which means its output
+ // can be specific to the current page or user. As such, we can optimise
+ // the way we load it based on the current version of the module.
+ // Avoid needless embed for empty module, preset ready state.
+ $data['states'][$name] = 'ready';
+ } elseif ( $group === 'user' || $shouldEmbed ) {
// - For group=user: We need to provide a pre-generated load.php
// url to the client that has the 'user' and 'version' parameters
// filled in. Without this, the client would wrongly use the static
$group = $module->getGroup();
$context = $this->getContext( $group, ResourceLoaderModule::TYPE_STYLES );
- // Avoid needless request for empty module
- if ( !$module->isKnownEmpty( $context ) ) {
- if ( $module->shouldEmbedModule( $this->context ) ) {
- // Embed via style element
+ if ( $module->shouldEmbedModule( $this->context ) ) {
+ // Avoid needless embed for private embeds we know are empty.
+ // (Set "ready" state directly instead, which we do a few lines above.)
+ if ( !$module->isKnownEmpty( $context ) ) {
+ // Embed via <style> element
$data['embed']['styles'][] = $name;
- } else {
- // Load from load.php?only=styles via <link rel=stylesheet>
- $data['styles'][] = $name;
}
+ // For other style modules, always request them, regardless of whether they are
+ // currently known to be empty. Because:
+ // 1. Those modules are requested in batch, so there is no extra request overhead
+ // or extra HTML element to be avoided.
+ // 2. Checking isKnownEmpty for those can be expensive and slow down page view
+ // generation (T230260).
+ // 3. We don't want cached HTML to vary on the current state of a module.
+ // If the module becomes non-empty a few minutes later, it should start working
+ // on cached HTML without requiring a purge.
+ //
+ // But, user-specific modules:
+ // * ... are used on page views not publicly cached.
+ // * ... are in their own group and thus a require a request we can avoid
+ // * ... have known-empty status preloaded by ResourceLoader.
+ } elseif ( $group !== 'user' || !$module->isKnownEmpty( $context ) ) {
+ // Load from load.php?only=styles via <link rel=stylesheet>
+ $data['styles'][] = $name;
}
$deprecation = $module->getDeprecationInformation();
if ( $deprecation ) {
// Change "client-nojs" class to client-js. This allows easy toggling of UI components.
// This must happen synchronously on every page view to avoid flashes of wrong content.
// See also #getDocumentAttributes() and /resources/src/startup.js.
- $script = <<<JAVASCRIPT
+ $script = <<<'JAVASCRIPT'
document.documentElement.className = document.documentElement.className
.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );
JAVASCRIPT;
JAVASCRIPT;
}
- if ( $this->context->getDebug() ) {
- $chunks[] = Html::inlineScript( $script, $nonce );
- } else {
- $chunks[] = Html::inlineScript(
- ResourceLoader::filter( 'minify-js', $script, [ 'cache' => false ] ),
- $nonce
- );
+ if ( !$this->context->getDebug() ) {
+ $script = ResourceLoader::filter( 'minify-js', $script, [ 'cache' => false ] );
}
+ $chunks[] = Html::inlineScript( $script, $nonce );
+
// Inline RLQ: Embedded modules
if ( $data['embed']['general'] ) {
$chunks[] = $this->getLoad(
// Async scripts. Once the startup is loaded, inline RLQ scripts will run.
// Pass-through a custom 'target' from OutputPage (T143066).
- $startupQuery = [];
+ $startupQuery = [ 'raw' => '1' ];
foreach ( [ 'target', 'safemode' ] as $param ) {
if ( $this->options[$param] !== null ) {
$startupQuery[$param] = (string)$this->options[$param];
private static function makeContext( ResourceLoaderContext $mainContext, $group, $type,
array $extraQuery = []
) {
- // Create new ResourceLoaderContext so that $extraQuery may trigger isRaw().
+ // Create new ResourceLoaderContext so that $extraQuery is supported (eg. for 'sync=1').
$req = new FauxRequest( array_merge( $mainContext->getRequest()->getValues(), $extraQuery ) );
// Set 'only' if not combined
$req->setVal( 'only', $type === ResourceLoaderModule::TYPE_COMBINED ? null : $type );
);
}
} else {
- // See if we have one or more raw modules
- $isRaw = false;
- foreach ( $moduleSet as $key => $module ) {
- $isRaw |= $module->isRaw();
- }
-
// Special handling for the user group; because users might change their stuff
// on-wiki like user pages, or user preferences; we need to find the highest
// timestamp of these user-changeable modules so we can ensure cache misses on change
// Decide whether to use 'style' or 'script' element
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$chunk = Html::linkedStyle( $url );
- } elseif ( $context->getRaw() || $isRaw ) {
+ } elseif ( $context->getRaw() ) {
+ // This request is asking for the module to be delivered standalone,
+ // (aka "raw") without communicating to any mw.loader client.
+ // Use cases:
+ // - startup (naturally because this is what will define mw.loader)
+ // - html5shiv (loads synchronously in old IE before the async startup module arrives)
+ // - QUnit (needed in SpecialJavaScriptTest before async startup)
$chunk = Html::element( 'script', [
- // In SpecialJavaScriptTest, QUnit must load synchronous
+ // The 'sync' option is only supported in combination with 'raw'.
'async' => !isset( $extraQuery['sync'] ),
'src' => $url
] );