Features: Make max width a feature

Making this a feature part of the feature management system is integral
to making this a toggle and will allow us to explore making this
persistent in future.

Bug: T319447
Bug: T319449
Change-Id: I80c7b892a6891094854b4154db90917b67986102
This commit is contained in:
Jon Robson 2022-10-21 10:39:16 -07:00
parent 5da5190a9d
commit 614da1dc5e
11 changed files with 350 additions and 110 deletions

View file

@ -240,11 +240,31 @@ final class Constants {
*/
public const FEATURE_ARTICLE_TOOLS = 'ArticleTools';
/**
* @var string
*/
public const FEATURE_LIMITED_WIDTH = 'LimitedWidth';
/**
* @var string
*/
public const REQUIREMENT_LIMITED_WIDTH = 'LimitedWidth';
/**
* @var string
*/
public const PREF_KEY_LIMITED_WIDTH = 'vector-limited-width';
/**
* @var string
*/
public const FEATURE_LIMITED_WIDTH_CONTENT = 'LimitedWidthContent';
/**
* @var string
*/
public const REQUIREMENT_LIMITED_WIDTH_CONTENT = 'LimitedWidthContent';
/**
* @var bool
*/

View file

@ -0,0 +1,159 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Skins\Vector\FeatureManagement\Requirements;
use Config;
use MediaWiki\Skins\Vector\Constants;
use MediaWiki\Skins\Vector\FeatureManagement\Requirement;
use Title;
use WebRequest;
/**
* The `MaxWidthRequirement` for content.
* @package MediaWiki\Skins\Vector\FeatureManagement\Requirements
*/
final class LimitedWidthContentRequirement implements Requirement {
/**
* @var Config
*/
private $config;
/**
* @var WebRequest
*/
private $request;
/**
* @var Title
*/
private $title;
/**
* This constructor accepts all dependencies needed to determine whether
* the overridable config is enabled for the current user and request.
*
* @param Config $config
* @param WebRequest $request
* @param Title|null $title can be null in testing environment
*/
public function __construct(
Config $config,
WebRequest $request,
$title = null
) {
$this->config = $config;
$this->title = $title;
$this->request = $request;
}
/**
* @inheritDoc
*/
public function getName(): string {
return Constants::REQUIREMENT_LIMITED_WIDTH_CONTENT;
}
/**
* Per the $options configuration (for use with $wgVectorMaxWidthOptions)
* determine whether max-width should be disabled on the page.
* For the main page: Check the value of $options['exclude']['mainpage']
* For all other pages, the following will happen:
* - the array $options['include'] of canonical page names will be checked
* against the current page. If a page has been listed there, function will return false
* (max-width will not be disabled)
* Max width is disabled if:
* 1) The current namespace is listed in array $options['exclude']['namespaces']
* OR
* 2) A query string parameter matches one of the regex patterns in $exclusions['querystring'].
*
* @internal only for use inside tests.
* @param array $options
* @param Title $title
* @param WebRequest $request
* @return bool
*/
private static function shouldDisableMaxWidth( array $options, Title $title, WebRequest $request ) {
$canonicalTitle = $title->getRootTitle();
$inclusions = $options['include'] ?? [];
$exclusions = $options['exclude'] ?? [];
if ( $title->isMainPage() ) {
// only one check to make
return $exclusions['mainpage'] ?? false;
} elseif ( $canonicalTitle->isSpecialPage() ) {
$canonicalTitle->fixSpecialName();
}
//
// Check the inclusions based on the canonical title
// The inclusions are checked first as these trump any exclusions.
//
// Now we have the canonical title and the inclusions link we look for any matches.
foreach ( $inclusions as $titleText ) {
$includedTitle = Title::newFromText( $titleText );
if ( $canonicalTitle->equals( $includedTitle ) ) {
return false;
}
}
//
// Check the exclusions
// If nothing matches the exclusions to determine what should happen
//
$excludeNamespaces = $exclusions['namespaces'] ?? [];
// Max width is disabled on certain namespaces
if ( $title->inNamespaces( $excludeNamespaces ) ) {
return true;
}
$excludeQueryString = $exclusions['querystring'] ?? [];
foreach ( $excludeQueryString as $param => $excludedParamPattern ) {
$paramValue = $request->getRawVal( $param );
if ( $paramValue !== null ) {
if ( $excludedParamPattern === '*' ) {
// Backwards compatibility for the '*' wildcard.
$excludedParamPattern = '.+';
}
return (bool)preg_match( "/$excludedParamPattern/", $paramValue );
}
}
return false;
}
/**
* Check query parameter to override config or not.
* Then check for AB test value.
* Fallback to config value.
*
* @inheritDoc
*/
public function isMet(): bool {
return $this->title && !self::shouldDisableMaxWidth(
$this->config->get( 'VectorMaxWidthOptions' ),
$this->title,
$this->request
);
}
}

View file

@ -0,0 +1,105 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Skins\Vector\FeatureManagement\Requirements;
use MediaWiki\Skins\Vector\Constants;
use MediaWiki\Skins\Vector\FeatureManagement\Requirement;
use MediaWiki\User\UserOptionsLookup;
use Title;
use User;
/**
* The `MaxWidthRequirement` for skin
* @package MediaWiki\Skins\Vector\FeatureManagement\Requirements
*/
final class LimitedWidthRequirement implements Requirement {
/**
* @var Title
*/
private $title;
/**
* @var User
*/
private $user;
/**
* @var UserOptionsLookup
*/
private $userOptionsLookup;
/**
* This constructor accepts all dependencies needed to determine whether
* the overridable config is enabled for the current user and request.
*
* @param User $user
* @param UserOptionsLookup $userOptionsLookup
* @param Title|null $title
*/
public function __construct(
User $user,
UserOptionsLookup $userOptionsLookup,
$title = null
) {
$this->user = $user;
$this->userOptionsLookup = $userOptionsLookup;
$this->title = $title;
}
/**
* @inheritDoc
*/
public function getName(): string {
return Constants::REQUIREMENT_LIMITED_WIDTH;
}
/**
* Indicates if this skin should be shown with max-width.
* @internal
*
* @return bool
*/
public function hasUserLimitedWidthEnabled() {
$user = $this->user;
$userOptionsLookup = $this->userOptionsLookup;
$isLimitedWidth = $userOptionsLookup->getOption(
$user,
Constants::PREF_KEY_LIMITED_WIDTH
);
$isLimitedWidth = $isLimitedWidth === null ? true : $userOptionsLookup->getBoolOption(
$user,
Constants::PREF_KEY_LIMITED_WIDTH
);
return $this->title && $isLimitedWidth;
}
/**
* Check query parameter to override config or not.
* Then check for AB test value.
* Fallback to config value.
*
* @inheritDoc
*/
public function isMet(): bool {
return $this->hasUserLimitedWidthEnabled();
}
}

View file

@ -18,9 +18,7 @@ use OutputPage;
use RuntimeException;
use Skin;
use SkinTemplate;
use Title;
use User;
use WebRequest;
/**
* Presentation hook handlers for Vector skin.
@ -723,18 +721,6 @@ class Hooks implements
$bodyAttrs['class'] .= ' ' . implode( ' ', $tocClasses );
}
$shouldDisableMaxWidth = !self::isSkinVersionLegacy( $skinName ) &&
$sk->getTitle() &&
self::shouldDisableMaxWidth(
$config->get( 'VectorMaxWidthOptions' ),
$sk->getTitle(),
$out->getRequest()
);
// Should we disable the max-width styling?
if ( $sk instanceof SkinVector22 && ( !$sk->hasUserLimitedWidthEnabled() || $shouldDisableMaxWidth ) ) {
$bodyAttrs['class'] .= ' skin-vector-disable-max-width';
}
$featureManager = VectorServices::getFeatureManager();
$bodyAttrs['class'] .= ' ' . implode( ' ', $featureManager->getFeatureBodyClass() );
$bodyAttrs['class'] = trim( $bodyAttrs['class'] );
@ -765,76 +751,6 @@ class Hooks implements
}
}
/**
* Per the $options configuration (for use with $wgVectorMaxWidthOptions)
* determine whether max-width should be disabled on the page.
* For the main page: Check the value of $options['exclude']['mainpage']
* For all other pages, the following will happen:
* - the array $options['include'] of canonical page names will be checked
* against the current page. If a page has been listed there, function will return false
* (max-width will not be disabled)
* Max width is disabled if:
* 1) The current namespace is listed in array $options['exclude']['namespaces']
* OR
* 2) A query string parameter matches one of the regex patterns in $exclusions['querystring'].
*
* @internal only for use inside tests.
* @param array $options
* @param Title $title
* @param WebRequest $request
* @return bool
*/
public static function shouldDisableMaxWidth( array $options, Title $title, WebRequest $request ) {
$canonicalTitle = $title->getRootTitle();
$inclusions = $options['include'] ?? [];
$exclusions = $options['exclude'] ?? [];
if ( $title->isMainPage() ) {
// only one check to make
return $exclusions['mainpage'] ?? false;
} elseif ( $canonicalTitle->isSpecialPage() ) {
$canonicalTitle->fixSpecialName();
}
//
// Check the inclusions based on the canonical title
// The inclusions are checked first as these trump any exclusions.
//
// Now we have the canonical title and the inclusions link we look for any matches.
foreach ( $inclusions as $titleText ) {
$includedTitle = Title::newFromText( $titleText );
if ( $canonicalTitle->equals( $includedTitle ) ) {
return false;
}
}
//
// Check the exclusions
// If nothing matches the exclusions to determine what should happen
//
$excludeNamespaces = $exclusions['namespaces'] ?? [];
// Max width is disabled on certain namespaces
if ( $title->inNamespaces( $excludeNamespaces ) ) {
return true;
}
$excludeQueryString = $exclusions['querystring'] ?? [];
foreach ( $excludeQueryString as $param => $excludedParamPattern ) {
$paramValue = $request->getRawVal( $param );
if ( $paramValue !== null ) {
if ( $excludedParamPattern === '*' ) {
// Backwards compatibility for the '*' wildcard.
$excludedParamPattern = '.+';
}
return (bool)preg_match( "/$excludedParamPattern/", $paramValue );
}
}
return false;
}
/**
* NOTE: Please use ResourceLoaderGetConfigVars hook instead if possible
* for adding config to the page.

View file

@ -26,6 +26,8 @@ use MediaWiki\MediaWikiServices;
use MediaWiki\Skins\Vector\Constants;
use MediaWiki\Skins\Vector\FeatureManagement\FeatureManager;
use MediaWiki\Skins\Vector\FeatureManagement\Requirements\DynamicConfigRequirement;
use MediaWiki\Skins\Vector\FeatureManagement\Requirements\LimitedWidthContentRequirement;
use MediaWiki\Skins\Vector\FeatureManagement\Requirements\LimitedWidthRequirement;
use MediaWiki\Skins\Vector\FeatureManagement\Requirements\OverridableConfigRequirement;
use MediaWiki\Skins\Vector\FeatureManagement\Requirements\TableOfContentsTreatmentRequirement;
@ -241,6 +243,40 @@ return [
]
);
// Feature: Max Width (skin)
// ================================
$featureManager->registerRequirement(
new LimitedWidthRequirement(
$context->getUser(),
$services->getUserOptionsLookup(),
$context->getTitle()
)
);
$featureManager->registerFeature(
Constants::FEATURE_LIMITED_WIDTH,
[
Constants::REQUIREMENT_FULLY_INITIALISED,
Constants::REQUIREMENT_LIMITED_WIDTH,
]
);
// Feature: Max Width (content)
// ================================
$featureManager->registerRequirement(
new LimitedWidthContentRequirement(
$services->getMainConfig(),
$context->getRequest(),
$context->getTitle()
)
);
$featureManager->registerFeature(
Constants::FEATURE_LIMITED_WIDTH_CONTENT,
[
Constants::REQUIREMENT_FULLY_INITIALISED,
Constants::REQUIREMENT_LIMITED_WIDTH_CONTENT,
]
);
return $featureManager;
}
];

View file

@ -2,8 +2,6 @@
namespace MediaWiki\Skins\Vector;
use MediaWiki\MediaWikiServices;
/**
* @ingroup Skins
* @package Vector
@ -196,25 +194,4 @@ class SkinVector22 extends SkinVector {
) : false,
] );
}
/**
* Indicates if this skin should be shown with max-width.
* @internal
* @since 1.40
*
* @return bool
*/
public function hasUserLimitedWidthEnabled() {
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
$isSkinModern = $this->getSkin()->getSkinName() === Constants::SKIN_NAME_MODERN;
$isLimitedWidth = $userOptionsLookup->getOption(
$this->getSkin()->getUser(),
Constants::PREF_KEY_LIMITED_WIDTH
);
$isLimitedWidth = $isLimitedWidth === null ? true : $userOptionsLookup->getBoolOption(
$this->getUser(),
Constants::PREF_KEY_LIMITED_WIDTH
);
return $isSkinModern && $this->getSkin()->getTitle() && $isLimitedWidth;
}
}

View file

@ -22,6 +22,10 @@
justify-content: space-between;
box-sizing: border-box;
.vector-feature-limited-width-disabled & {
max-width: none;
}
// T317573 Account for icon padding, align with header icons
padding: 6px (@padding-horizontal-page-container-desktop - @icon-padding-md-em);

View file

@ -11,12 +11,19 @@
width: 100%;
box-sizing: border-box;
max-width: @max-width-content-container;
.vector-feature-limited-width-disabled & {
max-width: none;
}
}
.mw-content-container {
max-width: @max-width-content-container;
// For container logic specific to special pages and history pages.
// FIXME: Remove `.skin-vector-disable-max-width` when caching no longer an issue.
.vector-feature-limited-width-disabled &,
.vector-feature-limited-width-content-disabled &,
.skin-vector-disable-max-width & {
// Allow the max-width of content on history/special pages to be wider than
// the max-width of content on article pages.

View file

@ -126,6 +126,12 @@ body {
background-color: @background-color-page-container;
box-sizing: border-box;
// FIXME: Remove `.skin-vector-disable-max-width` when caching no longer an issue.
.vector-feature-limited-width-disabled &,
.skin-vector-disable-max-width & {
max-width: none;
}
@media ( min-width: @min-width-desktop ) {
padding-left: @padding-horizontal-page-container-desktop;
padding-right: @padding-horizontal-page-container-desktop;

View file

@ -7,6 +7,8 @@
}
// Even if the editing form is max-width, the preview should be constrained.
// FIXME: Remove `.skin-vector-disable-max-width` when caching no longer an issue.
.vector-feature-limited-width-disabled #wikiPreview,
.skin-vector-disable-max-width #wikiPreview {
max-width: @max-width-content-container;
margin: auto;

View file

@ -9,6 +9,7 @@ namespace MediaWiki\Skins\Vector\Tests\Integration;
use FauxRequest;
use HashConfig;
use MediaWiki\Skins\Vector\Constants;
use MediaWiki\Skins\Vector\FeatureManagement\Requirements\LimitedWidthContentRequirement;
use MediaWiki\Skins\Vector\Hooks;
use MediaWiki\Skins\Vector\SkinVector22;
use MediaWiki\Skins\Vector\SkinVectorLegacy;
@ -317,7 +318,9 @@ class VectorHooksTest extends MediaWikiIntegrationTestCase {
}
/**
* @covers ::shouldDisableMaxWidth
* @todo move into MediaWiki\Skins\Vector\FeatureManagement\Requirements\LimitedWidthContentRequirement
* test in future.
* @covers MediaWiki\Skins\Vector\FeatureManagement\Requirements\LimitedWidthContentRequirement::isMet
* @dataProvider providerShouldDisableMaxWidth
*/
public function testShouldDisableMaxWidth(
@ -327,9 +330,14 @@ class VectorHooksTest extends MediaWikiIntegrationTestCase {
$requestValues,
$shouldDisableMaxWidth
) {
$requirement = new LimitedWidthContentRequirement(
new HashConfig( [ 'VectorMaxWidthOptions' => $options ] ),
new FauxRequest( $requestValues ),
$title
);
$this->assertSame(
$shouldDisableMaxWidth,
Hooks::shouldDisableMaxWidth( $options, $title, new FauxRequest( $requestValues ) ),
!$shouldDisableMaxWidth,
$requirement->isMet(),
$msg
);
}