From 126fb8d1573a8dad4a6fb4365854ffab20d14a17 Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Fri, 27 Jun 2014 19:57:40 -0700 Subject: [PATCH] OutputPage: Support foreign module sources in makeResourceLoaderLink To do so, created ResourceLoader::createLoaderURL(), which takes a ResourceLoaderContext object. ResourceLoader::makeLoaderURL() was deprecated. While reviewing usage of the old function, many of the callers only differed by one or two parameters from their respective ResourceLoaderContext object. To simplify that use case, I created DerivativeResourceLoaderContext, based of off DerivativeContext for IContextSource. Change-Id: I961c641ab953153057be3e3b8cf6c07435e9a0b0 --- includes/AutoLoader.php | 2 + includes/OutputPage.php | 220 +++++++++--------- .../DerivativeResourceLoaderContext.php | 202 ++++++++++++++++ includes/resourceloader/ResourceLoader.php | 61 +++++ .../resourceloader/ResourceLoaderContext.php | 10 +- .../resourceloader/ResourceLoaderModule.php | 40 ++-- .../resourceloader/ResourceLoaderTest.php | 33 +++ 7 files changed, 428 insertions(+), 140 deletions(-) create mode 100644 includes/resourceloader/DerivativeResourceLoaderContext.php diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 32cc5b7c72..127f2cd4d3 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -853,6 +853,8 @@ $wgAutoloadLocalClasses = array( 'MachineReadableRCFeedFormatter' => 'includes/rcfeed/MachineReadableRCFeedFormatter.php', # includes/resourceloader + 'DerivativeResourceLoaderContext' => + 'includes/resourceloader/DerivativeResourceLoaderContext.php', 'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php', 'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php', 'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php', diff --git a/includes/OutputPage.php b/includes/OutputPage.php index fdf8e7f1be..0dca0da640 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2714,8 +2714,8 @@ $templates $extraQuery['target'] = $this->mTarget; } - // Create keyed-by-group list of module objects from modules list - $groups = array(); + // Create keyed-by-source and then keyed-by-group list of module objects from modules list + $sortedModules = array(); $resourceLoader = $this->getResourceLoader(); foreach ( $modules as $name ) { $module = $resourceLoader->getModule( $name ); @@ -2730,136 +2730,126 @@ $templates continue; } - $group = $module->getGroup(); - if ( !isset( $groups[$group] ) ) { - $groups[$group] = array(); - } - $groups[$group][$name] = $module; + $sortedModules[$module->getSource()][$module->getGroup()][$name] = $module; } - foreach ( $groups as $group => $grpModules ) { - // Special handling for user-specific groups - $user = null; - if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) { - $user = $this->getUser()->getName(); - } + foreach ( $sortedModules as $source => $groups ) { + foreach ( $groups as $group => $grpModules ) { + // Special handling for user-specific groups + $user = null; + if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) { + $user = $this->getUser()->getName(); + } - // Create a fake request based on the one we are about to make so modules return - // correct timestamp and emptiness data - $query = ResourceLoader::makeLoaderQuery( - array(), // modules; not determined yet - $this->getLanguage()->getCode(), - $this->getSkin()->getSkinName(), - $user, - null, // version; not determined yet - ResourceLoader::inDebugMode(), - $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, - $this->isPrintable(), - $this->getRequest()->getBool( 'handheld' ), - $extraQuery - ); - $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); - - // Extract modules that know they're empty - foreach ( $grpModules as $key => $module ) { - // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857) - // If we're only getting the styles, we don't need to do anything for empty modules. - if ( $module->isKnownEmpty( $context ) ) { - unset( $grpModules[$key] ); - if ( $only !== ResourceLoaderModule::TYPE_STYLES ) { - $links['states'][$key] = 'ready'; + // Create a fake request based on the one we are about to make so modules return + // correct timestamp and emptiness data + $query = ResourceLoader::makeLoaderQuery( + array(), // modules; not determined yet + $this->getLanguage()->getCode(), + $this->getSkin()->getSkinName(), + $user, + null, // version; not determined yet + ResourceLoader::inDebugMode(), + $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, + $this->isPrintable(), + $this->getRequest()->getBool( 'handheld' ), + $extraQuery + ); + $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); + + // Extract modules that know they're empty + foreach ( $grpModules as $key => $module ) { + // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857) + // If we're only getting the styles, we don't need to do anything for empty modules. + if ( $module->isKnownEmpty( $context ) ) { + unset( $grpModules[$key] ); + if ( $only !== ResourceLoaderModule::TYPE_STYLES ) { + $links['states'][$key] = 'ready'; + } } } - } - // If there are no non-empty modules, skip this group - if ( count( $grpModules ) === 0 ) { - continue; - } + // If there are no non-empty modules, skip this group + if ( count( $grpModules ) === 0 ) { + continue; + } - // Inline private modules. These can't be loaded through load.php for security - // reasons, see bug 34907. Note that these modules should be loaded from - // getHeadScripts() before the first loader call. Otherwise other modules can't - // properly use them as dependencies (bug 30914) - if ( $group === 'private' ) { - if ( $only == ResourceLoaderModule::TYPE_STYLES ) { - $links['html'] .= Html::inlineStyle( - $resourceLoader->makeModuleResponse( $context, $grpModules ) - ); - } else { - $links['html'] .= Html::inlineScript( - ResourceLoader::makeLoaderConditionalScript( + // Inline private modules. These can't be loaded through load.php for security + // reasons, see bug 34907. Note that these modules should be loaded from + // getHeadScripts() before the first loader call. Otherwise other modules can't + // properly use them as dependencies (bug 30914) + if ( $group === 'private' ) { + if ( $only == ResourceLoaderModule::TYPE_STYLES ) { + $links['html'] .= Html::inlineStyle( $resourceLoader->makeModuleResponse( $context, $grpModules ) - ) - ); + ); + } else { + $links['html'] .= Html::inlineScript( + ResourceLoader::makeLoaderConditionalScript( + $resourceLoader->makeModuleResponse( $context, $grpModules ) + ) + ); + } + $links['html'] .= "\n"; + continue; } - $links['html'] .= "\n"; - continue; - } - // 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 - // This should NOT be done for the site group (bug 27564) because anons get that too - // and we shouldn't be putting timestamps in Squid-cached HTML - $version = null; - if ( $group === 'user' ) { - // Get the maximum timestamp - $timestamp = 1; - foreach ( $grpModules as $module ) { - $timestamp = max( $timestamp, $module->getModifiedTime( $context ) ); + // 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 + // This should NOT be done for the site group (bug 27564) because anons get that too + // and we shouldn't be putting timestamps in Squid-cached HTML + $version = null; + if ( $group === 'user' ) { + // Get the maximum timestamp + $timestamp = 1; + foreach ( $grpModules as $module ) { + $timestamp = max( $timestamp, $module->getModifiedTime( $context ) ); + } + // Add a version parameter so cache will break when things change + $query['version'] = wfTimestamp( TS_ISO_8601_BASIC, $timestamp ); } - // Add a version parameter so cache will break when things change - $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp ); - } - $url = ResourceLoader::makeLoaderURL( - array_keys( $grpModules ), - $this->getLanguage()->getCode(), - $this->getSkin()->getSkinName(), - $user, - $version, - ResourceLoader::inDebugMode(), - $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, - $this->isPrintable(), - $this->getRequest()->getBool( 'handheld' ), - $extraQuery - ); - if ( $useESI && $wgResourceLoaderUseESI ) { - $esi = Xml::element( 'esi:include', array( 'src' => $url ) ); - if ( $only == ResourceLoaderModule::TYPE_STYLES ) { - $link = Html::inlineStyle( $esi ); - } else { - $link = Html::inlineScript( $esi ); - } - } else { - // Automatically select style/script elements - if ( $only === ResourceLoaderModule::TYPE_STYLES ) { - $link = Html::linkedStyle( $url ); - } elseif ( $loadCall ) { - $link = Html::inlineScript( - ResourceLoader::makeLoaderConditionalScript( - Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) ) - ) - ); + $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $grpModules ) ); + $moduleContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); + $url = $resourceLoader->createLoaderURL( $source, $moduleContext, $extraQuery ); + + if ( $useESI && $wgResourceLoaderUseESI ) { + $esi = Xml::element( 'esi:include', array( 'src' => $url ) ); + if ( $only == ResourceLoaderModule::TYPE_STYLES ) { + $link = Html::inlineStyle( $esi ); + } else { + $link = Html::inlineScript( $esi ); + } } else { - $link = Html::linkedScript( $url ); - - // For modules requested directly in the html via or