Merge "resourceloader: Compile documentElement.className server-side"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 3 Sep 2019 21:04:23 +0000 (21:04 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 3 Sep 2019 21:04:23 +0000 (21:04 +0000)
1  2 
includes/OutputPage.php
includes/resourceloader/ResourceLoaderClientHtml.php
tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php

diff --combined includes/OutputPage.php
@@@ -44,7 -44,7 +44,7 @@@ use Wikimedia\WrappedStringList
   * @todo document
   */
  class OutputPage extends ContextSource {
 -      /** @var array Should be private. Used with addMeta() which adds "<meta>" */
 +      /** @var string[][] Should be private. Used with addMeta() which adds "<meta>" */
        protected $mMetatags = [];
  
        /** @var array */
         * @param Title $t
         */
        public function setTitle( Title $t ) {
 +              // @phan-suppress-next-next-line PhanUndeclaredMethod
 +              // @fixme Not all implementations of IContextSource have this method!
                $this->getContext()->setTitle( $t );
        }
  
         * @param string $text Wikitext
         * @param Title $title
         * @param bool $linestart Is this the start of a line?
 -       * @param bool $tidy Whether to use tidy.
 -       *             Setting this to false (or omitting it) is deprecated
 -       *             since 1.32; all wikitext should be tidied.
         * @param bool $interface Whether it is an interface message
         *   (for example disables conversion)
         * @param string $wrapperClass if not empty, wraps the output in
         *   a `<div class="$wrapperClass">`
 -       * @private
         */
        private function addWikiTextTitleInternal(
                $text, Title $title, $linestart, $interface, $wrapperClass = null
                $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
  
                $pieces = [];
-               $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
+               $htmlAttribs = Sanitizer::mergeAttributes(
                        $this->getRlClient()->getDocumentAttributes(),
                        $sk->getHtmlElementAttributes()
-               ) );
+               );
+               $pieces[] = Html::htmlHeader( $htmlAttribs );
                $pieces[] = Html::openElement( 'head' );
  
                if ( $this->getHTMLTitle() == '' ) {
                }
  
                $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
-               $pieces[] = $this->getRlClient()->getHeadHtml();
+               $pieces[] = $this->getRlClient()->getHeadHtml( $htmlAttribs['class'] ?? null );
                $pieces[] = $this->buildExemptModules();
                $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
                $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
  
                $title = $this->getTitle();
                $ns = $title->getNamespace();
 -              $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
 +              $nsInfo = $services->getNamespaceInfo();
                $canonicalNamespace = $nsInfo->exists( $ns )
                        ? $nsInfo->getCanonicalName( $ns )
                        : $title->getNsText();
                        $vars['wgUserVariant'] = $contLang->getPreferredVariant();
                }
                // Same test as SkinTemplate
 -              $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
 -                      && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
 +              $vars['wgIsProbablyEditable'] = $this->userCanEditOrCreate( $user, $title );
  
 -              $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
 -                      && $relevantTitle->quickUserCan( 'edit', $user )
 -                      && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
 +              $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
 +                      $this->userCanEditOrCreate( $user, $relevantTitle );
  
                foreach ( $title->getRestrictionTypes() as $type ) {
                        // Following keys are set in $vars:
                return true;
        }
  
 +      /**
 +       * @param User $user
 +       * @param LinkTarget $title
 +       * @return bool
 +       */
 +      private function userCanEditOrCreate(
 +              User $user,
 +              LinkTarget $title
 +      ) {
 +              $pm = MediaWikiServices::getInstance()->getPermissionManager();
 +              return $pm->quickUserCan( 'edit', $user, $title )
 +              && ( $this->getTitle()->exists() ||
 +                       $pm->quickUserCan( 'create', $user, $title ) );
 +      }
 +
        /**
         * @return array Array in format "link name or number => 'link html'".
         */
  
                # Universal edit button
                if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
 -                      $user = $this->getUser();
 -                      if ( $this->getTitle()->quickUserCan( 'edit', $user )
 -                              && ( $this->getTitle()->exists() ||
 -                                      $this->getTitle()->quickUserCan( 'create', $user ) )
 -                      ) {
 +                      if ( $this->userCanEditOrCreate( $this->getUser(), $this->getTitle() ) ) {
                                // Original UniversalEditButton
                                $msg = $this->msg( 'edit' )->text();
                                $tags['universal-edit-button'] = Html::element( 'link', [
@@@ -240,19 -240,22 +240,22 @@@ class ResourceLoaderClientHtml 
         * - Inline scripts can't be asynchronous.
         * - For styles, earlier is better.
         *
+        * @param string|null $nojsClass Class name that caller uses on HTML document element
         * @return string|WrappedStringList HTML
         */
-       public function getHeadHtml() {
+       public function getHeadHtml( $nojsClass = null ) {
                $nonce = $this->options['nonce'];
                $data = $this->getData();
                $chunks = [];
  
                // 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'
- document.documentElement.className = document.documentElement.className
-       .replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );
+               // 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 );
+               $script = <<<JAVASCRIPT
+ document.documentElement.className = {$jsClassJson};
  JAVASCRIPT;
  
                // Inline script: Declare mw.config variables for this page.
@@@ -424,7 -427,6 +427,7 @@@ JAVASCRIPT
                                $idx = -1;
                                foreach ( $grpModules as $name => $module ) {
                                        $shouldEmbed = $module->shouldEmbedModule( $context );
 +                                      // @phan-suppress-next-line PhanTypeInvalidDimOffset
                                        if ( !$moduleSets || $moduleSets[$idx][0] !== $shouldEmbed ) {
                                                $moduleSets[++$idx] = [ $shouldEmbed, [] ];
                                        }
@@@ -108,7 -108,7 +108,7 @@@ Deprecation message.' 
  
                // phpcs:disable Generic.Files.LineLength
                $expected = '<script>'
-                       . 'document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");'
+                       . 'document.documentElement.className="client-js";'
                        . 'RLCONF={"key":"value"};'
                        . 'RLSTATE={"test.exempt":"ready","test.private":"loading","test.styles.pure":"ready","test.styles.private":"ready","test.styles.deprecated":"ready"};'
                        . 'RLPAGEMODULES=["test"];'
                );
  
                // phpcs:disable Generic.Files.LineLength
-               $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
+               $expected = '<script>document.documentElement.className="client-js";</script>' . "\n"
                        . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1&amp;target=example"></script>';
                // phpcs:enable
  
                );
  
                // phpcs:disable Generic.Files.LineLength
-               $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
+               $expected = '<script>document.documentElement.className="client-js";</script>' . "\n"
                        . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1&amp;safemode=1"></script>';
                // phpcs:enable
  
                );
  
                // phpcs:disable Generic.Files.LineLength
-               $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
+               $expected = '<script>document.documentElement.className="client-js";</script>' . "\n"
                        . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1"></script>';
                // phpcs:enable
  
                                'modules' => [ 'test.scripts.user' ],
                                'only' => ResourceLoaderModule::TYPE_SCRIPTS,
                                'extra' => [],
 -                              'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.scripts.user\u0026only=scripts\u0026user=Example\u0026version=0a56zyi");});</script>',
 +                              'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.scripts.user\u0026only=scripts\u0026user=Example\u0026version={blankCombi}");});</script>',
                        ],
                        [
                                'context' => [],
                                'modules' => [ 'test.user' ],
                                'only' => ResourceLoaderModule::TYPE_COMBINED,
                                'extra' => [],
 -                              'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.user\u0026user=Example\u0026version=0a56zyi");});</script>',
 +                              'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.user\u0026user=Example\u0026version={blankCombi}");});</script>',
                        ],
                        [
                                'context' => [ 'debug' => 'true' ],
                                'modules' => [ 'test.shouldembed' ],
                                'only' => ResourceLoaderModule::TYPE_COMBINED,
                                'extra' => [],
 -                              'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.shouldembed@09p30q0",null,{"css":[]});});</script>',
 +                              'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.shouldembed@{blankVer}",null,{"css":[]});});</script>',
                        ],
                        [
                                'context' => [],
                                'modules' => [ 'test', 'test.shouldembed' ],
                                'only' => ResourceLoaderModule::TYPE_COMBINED,
                                'extra' => [],
 -                              'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test");mw.loader.implement("test.shouldembed@09p30q0",null,{"css":[]});});</script>',
 +                              'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test");mw.loader.implement("test.shouldembed@{blankVer}",null,{"css":[]});});</script>',
                        ],
                        [
                                'context' => [],
  
        private static function expandVariables( $text ) {
                return strtr( $text, [
 +                      '{blankCombi}' => ResourceLoaderTestCase::BLANK_COMBI,
                        '{blankVer}' => ResourceLoaderTestCase::BLANK_VERSION
                ] );
        }