Merge "resourceloader: Add $modules parameter to makeVersionQuery()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sat, 28 Sep 2019 01:26:59 +0000 (01:26 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sat, 28 Sep 2019 01:26:59 +0000 (01:26 +0000)
1  2 
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderClientHtml.php

@@@ -689,24 -689,29 +689,29 @@@ class ResourceLoader implements LoggerA
         *
         * @since 1.28
         * @param ResourceLoaderContext $context
+        * @param string[]|null $modules
         * @return string Hash
         */
-       public function makeVersionQuery( ResourceLoaderContext $context ) {
+       public function makeVersionQuery( ResourceLoaderContext $context, array $modules = null ) {
+               if ( $modules === null ) {
+                       wfDeprecated( __METHOD__ . ' without $modules', '1.34' );
+                       $modules = $context->getModules();
+               }
                // As of MediaWiki 1.28, the server and client use the same algorithm for combining
                // version hashes. There is no technical reason for this to be same, and for years the
                // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
                // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
                // query parameter), then this method must continue to match the JS one.
-               $moduleNames = [];
-               foreach ( $context->getModules() as $name ) {
+               $filtered = [];
+               foreach ( $modules as $name ) {
                        if ( !$this->getModule( $name ) ) {
                                // If a versioned request contains a missing module, the version is a mismatch
                                // as the client considered a module (and version) we don't have.
                                return '';
                        }
-                       $moduleNames[] = $name;
+                       $filtered[] = $name;
                }
-               return $this->getCombinedVersion( $context, $moduleNames );
+               return $this->getCombinedVersion( $context, $filtered );
        }
  
        /**
                        $errorResponse = self::makeComment( $errorText );
                        if ( $context->shouldIncludeScripts() ) {
                                $errorResponse .= 'if (window.console && console.error) { console.error('
 -                                      . self::encodeJsonForScript( $errorText )
 +                                      . $context->encodeJson( $errorText )
                                        . "); }\n";
                        }
  
                // - Version mismatch (T117587, T47877)
                if ( is_null( $context->getVersion() )
                        || $errors
-                       || $context->getVersion() !== $this->makeVersionQuery( $context )
+                       || $context->getVersion() !== $this->makeVersionQuery( $context, $context->getModules() )
                ) {
                        $maxage = $rlMaxage['unversioned']['client'];
                        $smaxage = $rlMaxage['unversioned']['server'];
@@@ -1093,14 -1098,7 +1098,14 @@@ MESSAGE
                                                        $strContent = $scripts;
                                                } elseif ( is_array( $scripts ) ) {
                                                        // ...except when $scripts is an array of URLs or an associative array
 -                                                      $strContent = self::makeLoaderImplementScript( $implementKey, $scripts, [], [], [] );
 +                                                      $strContent = self::makeLoaderImplementScript(
 +                                                              $context,
 +                                                              $implementKey,
 +                                                              $scripts,
 +                                                              [],
 +                                                              [],
 +                                                              []
 +                                                      );
                                                }
                                                break;
                                        case 'styles':
                                                        }
                                                }
                                                $strContent = self::makeLoaderImplementScript(
 +                                                      $context,
                                                        $implementKey,
                                                        $scripts,
                                                        $content['styles'] ?? [],
  
                        // Set the state of modules we didn't respond to with mw.loader.implement
                        if ( $states ) {
 -                              $stateScript = self::makeLoaderStateScript( $states );
 +                              $stateScript = self::makeLoaderStateScript( $context, $states );
                                if ( !$context->getDebug() ) {
                                        $stateScript = self::filter( 'minify-js', $stateScript );
                                }
                        }
                } elseif ( $states ) {
                        $this->errors[] = 'Problematic modules: '
 -                              . self::encodeJsonForScript( $states );
 +                              . $context->encodeJson( $states );
                }
  
                return $out;
        /**
         * Return JS code that calls mw.loader.implement with given module properties.
         *
 +       * @param ResourceLoaderContext $context
         * @param string $name Module name or implement key (format "`[name]@[version]`")
         * @param XmlJsCode|array|string $scripts Code as XmlJsCode (to be wrapped in a closure),
         *  list of URLs to JavaScript files, string of JavaScript for `$.globalEval`, or array with
         * @throws MWException
         * @return string JavaScript code
         */
 -      protected static function makeLoaderImplementScript(
 -              $name, $scripts, $styles, $messages, $templates
 +      private static function makeLoaderImplementScript(
 +              ResourceLoaderContext $context, $name, $scripts, $styles, $messages, $templates
        ) {
                if ( $scripts instanceof XmlJsCode ) {
                        if ( $scripts->value === '' ) {
                                $scripts = null;
 -                      } elseif ( self::inDebugMode() ) {
 +                      } elseif ( $context->getDebug() ) {
                                $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
                        } else {
                                $scripts = new XmlJsCode( 'function($,jQuery,require,module){' . $scripts->value . '}' );
                                // All of these essentially do $file = $file['content'];, some just have wrapping around it
                                if ( $file['type'] === 'script' ) {
                                        // Multi-file modules only get two parameters ($ and jQuery are being phased out)
 -                                      if ( self::inDebugMode() ) {
 +                                      if ( $context->getDebug() ) {
                                                $file = new XmlJsCode( "function ( require, module ) {\n{$file['content']}\n}" );
                                        } else {
                                                $file = new XmlJsCode( 'function(require,module){' . $file['content'] . '}' );
                        }
                        $scripts = XmlJsCode::encodeObject( [
                                'main' => $scripts['main'],
 -                              'files' => XmlJsCode::encodeObject( $files, self::inDebugMode() )
 -                      ], self::inDebugMode() );
 +                              'files' => XmlJsCode::encodeObject( $files, $context->getDebug() )
 +                      ], $context->getDebug() );
                } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
                        throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
                }
                ];
                self::trimArray( $module );
  
 -              return Xml::encodeJsCall( 'mw.loader.implement', $module, self::inDebugMode() );
 +              return Xml::encodeJsCall( 'mw.loader.implement', $module, $context->getDebug() );
        }
  
        /**
         * Returns a JS call to mw.loader.state, which sets the state of one
         * ore more modules to a given value. Has two calling conventions:
         *
 -       *    - ResourceLoader::makeLoaderStateScript( $name, $state ):
 +       *    - ResourceLoader::makeLoaderStateScript( $context, $name, $state ):
         *         Set the state of a single module called $name to $state
         *
 -       *    - ResourceLoader::makeLoaderStateScript( [ $name => $state, ... ] ):
 +       *    - ResourceLoader::makeLoaderStateScript( $context, [ $name => $state, ... ] ):
         *         Set the state of modules with the given names to the given states
         *
 +       * @internal
 +       * @param ResourceLoaderContext $context
         * @param array|string $states
         * @param string|null $state
         * @return string JavaScript code
         */
 -      public static function makeLoaderStateScript( $states, $state = null ) {
 +      public static function makeLoaderStateScript(
 +              ResourceLoaderContext $context, $states, $state = null
 +      ) {
                if ( !is_array( $states ) ) {
                        $states = [ $states => $state ];
                }
                return 'mw.loader.state('
 -                      . self::encodeJsonForScript( $states )
 +                      . $context->encodeJson( $states )
                        . ');';
        }
  
         * @par Example
         * @code
         *
 -       *     ResourceLoader::makeLoaderRegisterScript( [
 +       *     ResourceLoader::makeLoaderRegisterScript( $context, [
         *        [ $name1, $version1, $dependencies1, $group1, $source1, $skip1 ],
         *        [ $name2, $version2, $dependencies1, $group2, $source2, $skip2 ],
         *        ...
         *     ] ):
         * @endcode
         *
 -       * @internal
 -       * @since 1.32
 +       * @internal For use by ResourceLoaderStartUpModule only
 +       * @param ResourceLoaderContext $context
         * @param array $modules Array of module registration arrays, each containing
         *  - string: module name
         *  - string: module version
         *  - string|null: Script body of a skip function (optional)
         * @return string JavaScript code
         */
 -      public static function makeLoaderRegisterScript( array $modules ) {
 +      public static function makeLoaderRegisterScript(
 +              ResourceLoaderContext $context, array $modules
 +      ) {
                // Optimisation: Transform dependency names into indexes when possible
                // to produce smaller output. They are expanded by mw.loader.register on
                // the other end using resolveIndexedDependencies().
                array_walk( $modules, [ self::class, 'trimArray' ] );
  
                return 'mw.loader.register('
 -                      . self::encodeJsonForScript( $modules )
 +                      . $context->encodeJson( $modules )
                        . ');';
        }
  
         * Returns JS code which calls mw.loader.addSource() with the given
         * parameters. Has two calling conventions:
         *
 -       *   - ResourceLoader::makeLoaderSourcesScript( $id, $properties ):
 +       *   - ResourceLoader::makeLoaderSourcesScript( $context, $id, $properties ):
         *       Register a single source
         *
 -       *   - ResourceLoader::makeLoaderSourcesScript( [ $id1 => $loadUrl, $id2 => $loadUrl, ... ] );
 +       *   - ResourceLoader::makeLoaderSourcesScript( $context,
 +       *         [ $id1 => $loadUrl, $id2 => $loadUrl, ... ]
 +       *     );
         *       Register sources with the given IDs and properties.
         *
 +       * @internal For use by ResourceLoaderStartUpModule only
 +       * @param ResourceLoaderContext $context
         * @param string|array $sources Source ID
         * @param string|null $loadUrl load.php url
         * @return string JavaScript code
         */
 -      public static function makeLoaderSourcesScript( $sources, $loadUrl = null ) {
 +      public static function makeLoaderSourcesScript(
 +              ResourceLoaderContext $context, $sources, $loadUrl = null
 +      ) {
                if ( !is_array( $sources ) ) {
                        $sources = [ $sources => $loadUrl ];
                }
                return 'mw.loader.addSource('
 -                      . self::encodeJsonForScript( $sources )
 +                      . $context->encodeJson( $sources )
                        . ');';
        }
  
@@@ -213,7 -213,7 +213,7 @@@ class ResourceLoaderClientHtml 
                                // Load from load.php?only=styles via <link rel=stylesheet>
                                $data['styles'][] = $name;
                        }
 -                      $deprecation = $module->getDeprecationInformation();
 +                      $deprecation = $module->getDeprecationInformation( $context );
                        if ( $deprecation ) {
                                $data['styleDeprecations'][] = $deprecation;
                        }
                // See also startup/startup.js.
                $nojsClass = $nojsClass ?? $this->getDocumentAttributes()['class'];
                $jsClass = preg_replace( '/(^|\s)client-nojs(\s|$)/', '$1client-js$2', $nojsClass );
 -              $jsClassJson = ResourceLoader::encodeJsonForScript( $jsClass );
 +              $jsClassJson = $this->context->encodeJson( $jsClass );
                $script = <<<JAVASCRIPT
  document.documentElement.className = {$jsClassJson};
  JAVASCRIPT;
  
                // Inline script: Declare mw.config variables for this page.
                if ( $this->config ) {
 -                      $confJson = ResourceLoader::encodeJsonForScript( $this->config );
 +                      $confJson = $this->context->encodeJson( $this->config );
                        $script .= <<<JAVASCRIPT
  RLCONF = {$confJson};
  JAVASCRIPT;
                // Inline script: Declare initial module states for this page.
                $states = array_merge( $this->exemptStates, $data['states'] );
                if ( $states ) {
 -                      $stateJson = ResourceLoader::encodeJsonForScript( $states );
 +                      $stateJson = $this->context->encodeJson( $states );
                        $script .= <<<JAVASCRIPT
  RLSTATE = {$stateJson};
  JAVASCRIPT;
  
                // Inline script: Declare general modules to load on this page.
                if ( $data['general'] ) {
 -                      $pageModulesJson = ResourceLoader::encodeJsonForScript( $data['general'] );
 +                      $pageModulesJson = $this->context->encodeJson( $data['general'] );
                        $script .= <<<JAVASCRIPT
  RLPAGEMODULES = {$pageModulesJson};
  JAVASCRIPT;
  
                                // Link/embed each set
                                foreach ( $moduleSets as list( $embed, $moduleSet ) ) {
-                                       $context->setModules( array_keys( $moduleSet ) );
+                                       $moduleSetNames = array_keys( $moduleSet );
+                                       $context->setModules( $moduleSetNames );
                                        if ( $embed ) {
                                                // Decide whether to use style or script element
                                                if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
                                                // This should NOT be done for the site group (T29564) because anons get that too
                                                // and we shouldn't be putting timestamps in CDN-cached HTML
                                                if ( $group === 'user' ) {
-                                                       // Must setModules() before makeVersionQuery()
-                                                       $context->setVersion( $rl->makeVersionQuery( $context ) );
+                                                       $context->setVersion( $rl->makeVersionQuery( $context, $moduleSetNames ) );
                                                }
  
+                                               // Must setModules() before createLoaderURL()
                                                $url = $rl->createLoaderURL( $source, $context, $extraQuery );
  
                                                // Decide whether to use 'style' or 'script' element
                                                        ] );
                                                } else {
                                                        $chunk = ResourceLoader::makeInlineScript(
 -                                                              'mw.loader.load(' . ResourceLoader::encodeJsonForScript( $url ) . ');',
 +                                                              'mw.loader.load(' . $mainContext->encodeJson( $url ) . ');',
                                                                $nonce
                                                        );
                                                }