Phuedx has uploaded a new change for review. https://gerrit.wikimedia.org/r/316542
Change subject: [Hygiene] [QA] Extract responsive image hooks ...................................................................... [Hygiene] [QA] Extract responsive image hooks Make MobileFrontendHooks less complex by removing two related static hook handlers from it and make those handlers more easily testable by migrating them using the guidelines in docs/injection.txt [0]. This follows on from I343e5da5. Changes: * Move the cautionary note about about stripping responsive image variants into the README. * Move MobileFrontendHooks::onPageRenderingHash and MobileFrontend\\ResponsiveImages\\Hooks#doPageRenderingHash and * Move MobileFrontendHooks::onThumbnailBeforeProduceHTML to MobileFrontend\\ResponsiveImages\\Hooks#doThumbnailBeforeProduceHTML and cover the method with unit tests. [0]: https://github.com/wikimedia/mediawiki/blob/d7410db0fdf99dab9dcd4244e608383016e2dfb1/docs/injection.txt#L212-L224 Change-Id: I04e927e3a3c903839d62ac9f8156c53f89cc644e --- M README.md M extension.json M includes/MobileFrontend.hooks.php A includes/ResponsiveImages/Hooks.php M tests/phpunit/MobileFrontend.hooksTest.php A tests/phpunit/ResponsiveImages/HooksTest.php 6 files changed, 269 insertions(+), 93 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MobileFrontend refs/changes/42/316542/1 diff --git a/README.md b/README.md index 6439c3b..f315434 100644 --- a/README.md +++ b/README.md @@ -702,9 +702,13 @@ #### $wgMFResponsiveImageWhitelist -Whitelist of source file mime types to retain srcset attributes on when using -$wgMFStripResponsiveImages. Defaults to allow rasterized SVGs since they -usually are diagrams that compress well and benefit from the higher resolution. +Whitelist of source file mime types to retain srcset attributes on when +`$wgMFStripResponsiveImages` is truthy. Defaults to allow rasterized SVGs since +they usually are diagrams that compress well and benefit from the higher +resolution. + +In future, the `srcset` attribute may include small-screen-friendly image +variants as well as density variants, so this should be used with caution. * Type: `Array` * Default: diff --git a/extension.json b/extension.json index efa101f..3a6913f 100644 --- a/extension.json +++ b/extension.json @@ -91,7 +91,8 @@ "MobileFrontend\\Devices\\AMFDeviceDetector": "includes/devices/AMFDeviceDetector.php", "MobileFrontend\\Devices\\CustomHeaderDeviceDetector": "includes/devices/CustomHeaderDeviceDetector.php", "MobileFrontend\\Devices\\UADeviceDetector": "includes/devices/UADeviceDetector.php", - "MobileFrontend\\Devices\\DeviceDetectorService": "includes/devices/DeviceDetectorService.php" + "MobileFrontend\\Devices\\DeviceDetectorService": "includes/devices/DeviceDetectorService.php", + "MobileFrontend\\ResponsiveImages\\Hooks": "includes/ResponsiveImages/Hooks.php" }, "ResourceModules": { "skins.minerva.base.reset": { @@ -1919,10 +1920,10 @@ "MobileFrontendHooks::onResourceLoaderGetLessVars" ], "ThumbnailBeforeProduceHTML": [ - "MobileFrontendHooks::onThumbnailBeforeProduceHTML" + "MobileFrontend\\ResponsiveImages\\Hooks::onThumbnailBeforeProduceHTML" ], "PageRenderingHash": [ - "MobileFrontendHooks::onPageRenderingHash" + "MobileFrontend\\ResponsiveImages\\Hooks::onPageRenderingHash" ], "AfterBuildFeedLinks": [ "MobileFrontendHooks::onAfterBuildFeedLinks" diff --git a/includes/MobileFrontend.hooks.php b/includes/MobileFrontend.hooks.php index 6622b17..f372cc5 100644 --- a/includes/MobileFrontend.hooks.php +++ b/includes/MobileFrontend.hooks.php @@ -366,25 +366,6 @@ } /** - * PageRenderingHash hook handler - * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageRenderingHash - * - * @param string &$confstr Reference to a hash key string which can be modified - * @param User $user User object that is requesting the page - * @param array &$forOptions Array of options used to generate the $confstr hash key - */ - public static function onPageRenderingHash( &$confstr, User $user, &$forOptions ) { - $context = MobileContext::singleton(); - - if ( - $context->shouldDisplayMobileView() - && $context->shouldStripResponsiveImages() - ) { - $confstr .= '!responsiveimages=0'; - } - } - - /** * ResourceLoaderGetConfigVars hook handler * This should be used for variables which: * - vary with the html @@ -1289,34 +1270,6 @@ */ public static function onHTMLFileCache_useFileCache() { return !MobileContext::singleton()->shouldDisplayMobileView(); - } - - /** - * Omit srcset attributes from thumbnail image tags, to conserve bandwidth. - * - * @param ThumbnailImage $thumbnail - * @param array &$attribs - * @param array &$linkAttribs - */ - public static function onThumbnailBeforeProduceHTML( $thumbnail, &$attribs, &$linkAttribs ) { - $context = MobileContext::singleton(); - $config = $context->getMFConfig(); - if ( - $context->shouldDisplayMobileView() && - $context->shouldStripResponsiveImages() - ) { - $file = $thumbnail->getFile(); - if ( !$file || !in_array( $file->getMimeType(), - $config->get( 'MFResponsiveImageWhitelist' ) ) ) { - // Remove all responsive image 'srcset' attributes, except - // from SVG->PNG renderings which usually aren't too huge, - // or other whitelisted types. - // Note that in future, srcset may be used for specifying - // small-screen-friendly image variants as well as density - // variants, so this should be used with caution. - unset( $attribs['srcset'] ); - } - } } /** diff --git a/includes/ResponsiveImages/Hooks.php b/includes/ResponsiveImages/Hooks.php new file mode 100644 index 0000000..0977d34 --- /dev/null +++ b/includes/ResponsiveImages/Hooks.php @@ -0,0 +1,126 @@ +<?php + +namespace MobileFrontend\ResponsiveImages; + +use MobileContext; +use User; +use ThumbnailImage; + +class Hooks { + + /** + * @see Hooks::doPageRenderingHash + */ + public static function onPageRenderingHash( + &$confstr, + User $user, + &$forOptions + ) { + self::newFromGlobalState() + ->doPageRenderingHash( $confstr, $user, $forOptions ); + } + + /** + * @see Hooks::doThumbnailBeforeProduceHTML + */ + public static function onThumbnailBeforeProduceHTML( + ThumbnailImage $thumbnail, + &$attribs, + &$linkAttribs + ) { + self::newFromGlobalState() + ->doThumbnailBeforeProduceHTML( $thumbnail, $attribs, $linkAttribs ); + } + + /** + * Creates an instance of the `Hooks` class with the singleton instance of + * `MobileContext` so that the hook handlers can be dispatched statically. + * + * @return Hooks + */ + private static function newFromGlobalState() { + $context = MobileContext::singleton(); + $whitelist = $context->getMFConfig() + ->get( 'MFResponsiveImageWhitelist' ); + + return new self( $context, $whitelist ); + } + + /** + * @var MobileContext $context + */ + private $context; + + /** + * A list of MIME types of images that shouldn't have their variants removed. + * + * @var string[] $whitelist + */ + private $whitelist; + + /** + * @TODO Only the smallest slice of `MobileContext` is required here: + * `MobileContext#shouldStripResponsiveImages`. Create an interface that + * reflects this to increase flexibility and ease testing. + */ + public function __construct( MobileContext $context, array $whitelist ) { + $this->context = $context; + $this->whitelist = $whitelist; + } + + /** + * Varies the parser cache if responsive images should have their variants + * stripped from the parser output, since the transformation happens during + * the parse. + * + * See `$wgMFStripResponsiveImages` and `$wgMFResponsiveImageWhitelist` for + * more detail about the stripping of responsive images. + * + * See https://www.mediawiki.org/wiki/Manual:Hooks/PageRenderingHash for more + * detail about the `PageRenderingHash` hook. + * + * @param string &$confstr The parser cache key + * @param User $user User + * @param array &$forOptions + */ + public function doPageRenderingHash( &$confstr, User $user, &$forOptions ) { + if ( $this->shouldStripResponsiveImages() ) { + $confstr .= '!responsiveimages=0'; + } + } + + /** + * Removes the responsive image's variants from the parser output if + * configured to do so and the thumbnail's MIME type isn't whitelisted. + * + * See https://www.mediawiki.org/wiki/Manual:Hooks/ThumbnailBeforeProduceHTML + * for more detail about the `ThumbnailBeforeProduceHTML` hook. + * + * @param ThumbnailImage $thumbnail + * @param array &$attribs + * @param array &$linkAttribs + */ + public function doThumbnailBeforeProduceHTML( + ThumbnailImage $thumbnail, + &$attribs, + &$linkAttribs + ) { + if ( $this->shouldStripResponsiveImages() ) { + $file = $thumbnail->getFile(); + + if ( !$file || !in_array( $file->getMimeType(), $this->whitelist ) ) { + unset( $attribs['srcset'] ); + } + } + } + + /** + * Gets whether responsive images should be stripped from the parser output. + * + * @return bool + */ + private function shouldStripResponsiveImages() { + return $this->context->shouldDisplayMobileView() + && $this->context->shouldStripResponsiveImages(); + } +} diff --git a/tests/phpunit/MobileFrontend.hooksTest.php b/tests/phpunit/MobileFrontend.hooksTest.php index 361164d..e9ac88f 100644 --- a/tests/phpunit/MobileFrontend.hooksTest.php +++ b/tests/phpunit/MobileFrontend.hooksTest.php @@ -200,44 +200,4 @@ $this->assertArrayEquals( $expected, $urls ); } - - /** - * @dataProvider provideOnPageRenderingHash - * @covers MobileFrontendHooks::onPageRenderingHash - * - * @param bool $shouldConfstrChange Whether $confstr parameter should have - * changed - * @param bool $forceMobileView - * @param bool $stripResponsiveImages - */ - public function testOnPageRenderingHash( - $shouldConfstrChange, - $forceMobileView, - $stripResponsiveImages - ) { - $context = MobileContext::singleton(); - $context->setForceMobileView( $forceMobileView ); - $context->setStripResponsiveImages( $stripResponsiveImages ); - - $expectedConfstr = $confstr = ''; - - if ( $shouldConfstrChange ) { - $expectedConfstr = '!responsiveimages=0'; - } - - $user = new User(); - $forOptions = []; - - MobileFrontendHooks::onPageRenderingHash( $confstr, $user, $forOptions ); - - $this->assertEquals( $expectedConfstr, $confstr ); - } - - public static function provideOnPageRenderingHash() { - return [ - [ true, true, true ], - [ false, true, false ], - [ false, false, true ], - ]; - } } diff --git a/tests/phpunit/ResponsiveImages/HooksTest.php b/tests/phpunit/ResponsiveImages/HooksTest.php new file mode 100644 index 0000000..6d3dbba --- /dev/null +++ b/tests/phpunit/ResponsiveImages/HooksTest.php @@ -0,0 +1,132 @@ +<?php + +namespace Tests\MobileFrontend\ResponsiveImages; + +use MediaWikiTestCase; +use User; +use MobileContext; +use MobileFrontend\ResponsiveImages\Hooks; +use ThumbnailImage; + +class HooksTest extends MediaWikiTestCase { + + protected function setUp() { + parent::setUp(); + + MobileContext::resetInstanceForTesting(); + $this->context = MobileContext::singleton(); + + $whitelist = [ 'image/svg+xml' ]; + + $this->hooks = new Hooks( $this->context, $whitelist ); + } + + /** + * @dataProvider provideDoPageRenderingHash + * @covers MobileFrontend\\ResponsiveImages\\Hooks::doPageRenderingHash + * + * @param bool $shouldConfstrChange Whether the $confstr parameter should have + * changed + */ + public function testDoPageRenderingHash( + $shouldConfstrChange, + $forceMobileView, + $stripResponsiveImages + ) { + $expectedConfstr = $confstr = ''; + + if ( $shouldConfstrChange ) { + $expectedConfstr = '!responsiveimages=0'; + } + + $user = new User(); + $forOptions = []; + + $this->context->setForceMobileView( $forceMobileView ); + $this->context->setStripResponsiveImages( $stripResponsiveImages ); + + $this->hooks->doPageRenderingHash( $confstr, $user, $forOptions ); + + $this->assertEquals( $expectedConfstr, $confstr ); + } + + public static function provideDoPageRenderingHash() { + return [ + [ true, true, true ], + [ false, true, false ], + [ false, false, true ], + ]; + } + + /** + * @dataProvider provideDoThumbnailBeforeProduceHTML + * @covers MobileFrontend\\ResponsiveImages\\Hooks::doPageRenderingHash + */ + public function testDoThumbnailBeforeProduceHTML( + $expected, + $mimeType, + $stripResponsiveImages = true + ) { + $this->context->setForceMobileView( true ); + $this->context->setStripResponsiveImages( $stripResponsiveImages ); + + $file = $mimeType ? $this->factoryFile( $mimeType ) : null; + $thumbnail = new ThumbnailImage( + $file, + + // The following is stub data that stops `ThumbnailImage#__construct` + // triggering a warning. + '/foo.svg', + false, + [ + 'width' => 375, + 'height' => 667 + ] + ); + + // We're only asserting that the `srcset` attribute is unset. + $attribs = [ 'srcset' => 'bar' ]; + + $linkAttribs = []; + + $this->hooks->doThumbnailBeforeProduceHTML( + $thumbnail, + $attribs, + $linkAttribs + ); + + $this->assertEquals( $expected, array_key_exists( 'srcset', $attribs ) ); + } + + public static function provideDoThumbnailBeforeProduceHTML() { + return [ + [ false, 'image/jpg' ], + + // `ThumbnailImage#getFile` can return `null`. + [ false, null ], + + // It handles an image with a whitelisted MIME type. + [ true, 'image/svg+xml' ], + + // It handles the stripping of responsive images from the parser output + // being disabled. + [ true, 'image/jpg', false ], + ]; + } + + /** + * Creates an instance of `File` which has the given MIME type. + * + * @return File + */ + private function factoryFile( $mimeType ) { + $file = $this->getMockBuilder( 'File' ) + ->disableOriginalConstructor() + ->getMock(); + + $file->method( 'getMimeType' ) + ->willReturn( $mimeType ); + + return $file; + } +} -- To view, visit https://gerrit.wikimedia.org/r/316542 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I04e927e3a3c903839d62ac9f8156c53f89cc644e Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/MobileFrontend Gerrit-Branch: master Gerrit-Owner: Phuedx <samsm...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits