jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/215061 )

Change subject: Preload the logo using link rel="preload" http header
......................................................................


Preload the logo using link rel="preload" http header

This greatly increases the priority of loading
the logo on browsers that support rel="preload".

Bug: T100999
Change-Id: I0738fcc0a575153dab65016fa87faaa9b8b97a9d
---
M includes/OutputPage.php
M includes/resourceloader/ResourceLoaderSkinModule.php
M tests/phpunit/includes/OutputPageTest.php
3 files changed, 195 insertions(+), 2 deletions(-)

Approvals:
  Krinkle: Looks good to me, but someone else must approve
  jenkins-bot: Verified
  Gilles: Looks good to me, approved



diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 5380264..3f10c06 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -303,6 +303,11 @@
        private $limitReportJSData = [];
 
        /**
+        * Link: header contents
+        */
+       private $mLinkHeader = [];
+
+       /**
         * Constructor for OutputPage. This should not be called directly.
         * Instead a new RequestContext should be created and it will 
implicitly create
         * a OutputPage tied to that context.
@@ -2106,6 +2111,28 @@
        }
 
        /**
+        * Add an HTTP Link: header
+        *
+        * @param string $header Header value
+        */
+       public function addLinkHeader( $header ) {
+               $this->mLinkHeader[] = $header;
+       }
+
+       /**
+        * Return a Link: header. Based on the values of $mLinkHeader.
+        *
+        * @return string
+        */
+       public function getLinkHeader() {
+               if ( !$this->mLinkHeader ) {
+                       return false;
+               }
+
+               return 'Link: ' . implode( ',', $this->mLinkHeader );
+       }
+
+       /**
         * Get a complete Key header
         *
         * @return string
@@ -2360,6 +2387,12 @@
                // Avoid Internet Explorer "compatibility view" in IE 8-10, so 
that
                // jQuery etc. can work correctly.
                $response->header( 'X-UA-Compatible: IE=Edge' );
+
+               $this->addLogoPreloadLinkHeaders();
+               $linkHeader = $this->getLinkHeader();
+               if ( $linkHeader ) {
+                       $response->header( $linkHeader );
+               }
 
                // Prevent framing, if requested
                $frameOptions = $this->getFrameOptions();
@@ -3960,4 +3993,82 @@
                        'mediawiki.widgets.styles',
                ] );
        }
+
+       /**
+        * Add Link headers for preloading the wiki's logo.
+        *
+        * @since  1.26
+        */
+       protected function addLogoPreloadLinkHeaders() {
+               $logo = $this->getConfig()->get( 'Logo' ); // wgLogo
+               $logoHD = $this->getConfig()->get( 'LogoHD' ); // wgLogoHD
+
+               $tags = [];
+               $logosPerDppx = [];
+               $logos = [];
+
+               $logosPerDppx['1.0'] = $logo;
+
+               if ( !$logoHD ) {
+                       // No media queries required if we only have one variant
+                       $this->addLinkHeader( '<' . $logo . 
'>;rel=preload;as=image' );
+                       return;
+               }
+
+               foreach ( $logoHD as $dppx => $src ) {
+                       // Only 1.5x and 2x are supported
+                       // Note: Keep in sync with ResourceLoaderSkinModule
+                       if ( in_array( $dppx, [ '1.5x', '2x' ] ) ) {
+                               // LogoHD uses a string in this format: "1.5x"
+                               $dppx = substr( $dppx, 0, -1 );
+                               $logosPerDppx[$dppx] = $src;
+                       }
+               }
+
+               // Because PHP can't have floats as array keys
+               uksort( $logosPerDppx, function ( $a , $b ) {
+                       $a = floatval( $a );
+                       $b = floatval( $b );
+
+                       if ( $a == $b ) {
+                               return 0;
+                       }
+                       // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
+                       return ( $a < $b ) ? -1 : 1;
+               } );
+
+               foreach ( $logosPerDppx as $dppx => $src ) {
+                       $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
+               }
+
+               $logosCount = count( $logos );
+               // Logic must match ResourceLoaderSkinModule:
+               // - 1x applies to resolution < 1.5dppx
+               // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
+               // - 2x applies to resolution >= 2dppx
+               // Note that min-resolution and max-resolution are both 
inclusive.
+               for ( $i = 0; $i < $logosCount; $i++ ) {
+                       if ( $i === 0 ) {
+                               // Smallest dppx
+                               // min-resolution is ">=" (larger than or equal 
to)
+                               // "not min-resolution" is essentially "<"
+                               $media_query = 'not all and (min-resolution: ' 
. $logos[ 1 ]['dppx'] . 'dppx)';
+                       } elseif ( $i !== $logosCount - 1 ) {
+                               // In between
+                               // Media query expressions can only apply "not" 
to the entire expression
+                               // (e.g. can't express ">= 1.5 and not >= 2).
+                               // Workaround: Use <= 1.9999 in place of < 2.
+                               $upper_bound = floatval( $logos[ $i + 1 
]['dppx'] ) - 0.000001;
+                               $media_query = '(min-resolution: ' .  $logos[ 
$i ]['dppx'] .
+                                       'dppx) and (max-resolution: ' . 
$upper_bound . 'dppx)';
+                       } else {
+                               // Largest dppx
+                               $media_query = '(min-resolution: ' . $logos[ $i 
]['dppx'] . 'dppx)';
+                       }
+
+                       $this->addLinkHeader(
+                               '<' . $logos[$i]['src'] . 
'>;rel=preload;as=image;media=' . $media_query
+                       );
+               }
+       }
 }
diff --git a/includes/resourceloader/ResourceLoaderSkinModule.php 
b/includes/resourceloader/ResourceLoaderSkinModule.php
index d72b3afa..7d37944 100644
--- a/includes/resourceloader/ResourceLoaderSkinModule.php
+++ b/includes/resourceloader/ResourceLoaderSkinModule.php
@@ -40,6 +40,8 @@
                $styles['all'][] = '.mw-wiki-logo { background-image: ' .
                        CSSMin::buildUrlValue( $logo1 ) .
                        '; }';
+               // Only 1.5x and 2x are supported
+               // Note: Keep in sync with 
OutputPage::addLogoPreloadLinkHeaders()
                if ( $logoHD ) {
                        if ( isset( $logoHD['1.5x'] ) ) {
                                $styles[
diff --git a/tests/phpunit/includes/OutputPageTest.php 
b/tests/phpunit/includes/OutputPageTest.php
index 59441ce..7571e26 100644
--- a/tests/phpunit/includes/OutputPageTest.php
+++ b/tests/phpunit/includes/OutputPageTest.php
@@ -473,12 +473,92 @@
        }
 
        /**
+        * @dataProvider provideLinkHeaders
+        * @covers OutputPage::addLinkHeader
+        * @covers OutputPage::getLinkHeader
+        */
+       public function testLinkHeaders( $headers, $result ) {
+               $outputPage = $this->newInstance();
+
+               foreach ( $headers as $header ) {
+                       $outputPage->addLinkHeader( $header );
+               }
+
+               $this->assertEquals( $result, $outputPage->getLinkHeader() );
+       }
+
+       public function provideLinkHeaders() {
+               return [
+                       [
+                               [],
+                               false
+                       ],
+                       [
+                               [ '<https://foo/bar.jpg>;rel=preload;as=image' 
],
+                               'Link: 
<https://foo/bar.jpg>;rel=preload;as=image',
+                       ],
+                       [
+                               [ 
'<https://foo/bar.jpg>;rel=preload;as=image','<https://foo/baz.jpg>;rel=preload;as=image'
 ],
+                               'Link: 
<https://foo/bar.jpg>;rel=preload;as=image,<https://foo/baz.jpg>;rel=preload;as=image',
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider providePreloadLinkHeaders
+        * @covers OutputPage::addLogoPreloadLinkHeaders
+        */
+       public function testPreloadLinkHeaders( $config, $result ) {
+               $out = TestingAccessWrapper::newFromObject( $this->newInstance( 
$config ) );
+               $out->addLogoPreloadLinkHeaders();
+
+               $this->assertEquals( $result, $out->getLinkHeader() );
+       }
+
+       public function providePreloadLinkHeaders() {
+               return [
+                       [
+                               [
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => [
+                                               '1.5x' => 
'/img/one-point-five.png',
+                                               '2x' => '/img/two-x.png',
+                                       ],
+                               ],
+                               'Link: 
</img/default.png>;rel=preload;as=image;media=' .
+                               'not all and (min-resolution: 1.5dppx),' .
+                               
'</img/one-point-five.png>;rel=preload;as=image;media=' .
+                               '(min-resolution: 1.5dppx) and (max-resolution: 
1.999999dppx),' .
+                               
'</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
+                       ],
+                       [
+                               [
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => false,
+                               ],
+                               'Link: </img/default.png>;rel=preload;as=image'
+                       ],
+                       [
+                               [
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => [
+                                               '2x' => '/img/two-x.png',
+                                       ],
+                               ],
+                               'Link: 
</img/default.png>;rel=preload;as=image;media=' .
+                               'not all and (min-resolution: 2dppx),' .
+                               
'</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
+                       ],
+               ];
+       }
+
+       /**
         * @return OutputPage
         */
-       private function newInstance() {
+       private function newInstance( $config = [] ) {
                $context = new RequestContext();
 
-               $context->setConfig( new HashConfig( [
+               $context->setConfig( new HashConfig( $config + [
                        'AppleTouchIcon' => false,
                        'DisableLangConversion' => true,
                        'EnableAPI' => false,

-- 
To view, visit https://gerrit.wikimedia.org/r/215061
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I0738fcc0a575153dab65016fa87faaa9b8b97a9d
Gerrit-PatchSet: 16
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Gilles <[email protected]>
Gerrit-Reviewer: Bartosz DziewoƄski <[email protected]>
Gerrit-Reviewer: Daniel Friesen <[email protected]>
Gerrit-Reviewer: Gilles <[email protected]>
Gerrit-Reviewer: Hashar <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Ori.livneh <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to