Moves feature classes from BODY element to HTML element

Move feature classes to HTML element - this is significant
for anonymous as placing the classes on the body tag breaks
the browser's ability to parse the article concurrently
with the stylesheet download, because inline scripts
are spec'ed to be able to see document.styleSheets.

Changes:
* Feature classes are moved from BODY tag to HTML tag
* For now disable localStorage storage until we've worked out
the storage mechanism in core.

Bug: T321498
Change-Id: Id5afe2c60dc0067e7c74433eda5cd7858f54b0d7
This commit is contained in:
Jon Robson 2022-10-21 11:37:26 -07:00 committed by bwang
parent 05c6f9ecd4
commit 55bb37f2ab
10 changed files with 60 additions and 47 deletions

View file

@ -5,7 +5,7 @@
}, },
{ {
"resourceModule": "skins.vector.styles", "resourceModule": "skins.vector.styles",
"maxSize": "11.1 kB" "maxSize": "11.2 kB"
}, },
{ {
"resourceModule": "skins.vector.legacy.js", "resourceModule": "skins.vector.legacy.js",

View file

@ -6,7 +6,6 @@ use Config;
use IContextSource; use IContextSource;
use MediaWiki\Auth\Hook\LocalUserCreatedHook; use MediaWiki\Auth\Hook\LocalUserCreatedHook;
use MediaWiki\Hook\MakeGlobalVariablesScriptHook; use MediaWiki\Hook\MakeGlobalVariablesScriptHook;
use MediaWiki\Hook\OutputPageBodyAttributesHook;
use MediaWiki\Hook\RequestContextCreateSkinHook; use MediaWiki\Hook\RequestContextCreateSkinHook;
use MediaWiki\MediaWikiServices; use MediaWiki\MediaWikiServices;
use MediaWiki\Preferences\Hook\GetPreferencesHook; use MediaWiki\Preferences\Hook\GetPreferencesHook;
@ -32,7 +31,6 @@ class Hooks implements
GetPreferencesHook, GetPreferencesHook,
LocalUserCreatedHook, LocalUserCreatedHook,
MakeGlobalVariablesScriptHook, MakeGlobalVariablesScriptHook,
OutputPageBodyAttributesHook,
ResourceLoaderSiteModulePagesHook, ResourceLoaderSiteModulePagesHook,
ResourceLoaderSiteStylesModulePagesHook, ResourceLoaderSiteStylesModulePagesHook,
RequestContextCreateSkinHook, RequestContextCreateSkinHook,
@ -650,26 +648,6 @@ class Hooks implements
} }
} }
/**
* Called when OutputPage::headElement is creating the body tag to allow skins
* and extensions to add attributes they might need to the body of the page.
*
* @param OutputPage $out
* @param Skin $sk
* @param string[] &$bodyAttrs
*/
public function onOutputPageBodyAttributes( $out, $sk, &$bodyAttrs ): void {
$skinName = $out->getSkin()->getSkinName();
if ( !self::isVectorSkin( $skinName ) ) {
return;
}
$config = $sk->getConfig();
$featureManager = VectorServices::getFeatureManager();
$bodyAttrs['class'] .= ' ' . implode( ' ', $featureManager->getFeatureBodyClass() );
$bodyAttrs['class'] = trim( $bodyAttrs['class'] );
}
/** /**
* Temporary RequestContextCreateSkin hook handler. * Temporary RequestContextCreateSkin hook handler.
* Switches to new Vector on certain pages. * Switches to new Vector on certain pages.

View file

@ -185,6 +185,8 @@ class SkinVector22 extends SkinMustache {
*/ */
public function getHtmlElementAttributes() { public function getHtmlElementAttributes() {
$original = parent::getHtmlElementAttributes(); $original = parent::getHtmlElementAttributes();
$featureManager = VectorServices::getFeatureManager();
$original['class'] .= ' ' . implode( ' ', $featureManager->getFeatureBodyClass() );
if ( VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER ) ) { if ( VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER ) ) {
// T290518: Add scroll padding to root element when the sticky header is // T290518: Add scroll padding to root element when the sticky header is
@ -200,6 +202,7 @@ class SkinVector22 extends SkinMustache {
// possibly others). It must instead be applied to the html tag. // possibly others). It must instead be applied to the html tag.
$original['class'] = implode( ' ', [ $original['class'] ?? '', self::STICKY_HEADER_ENABLED_CLASS ] ); $original['class'] = implode( ' ', [ $original['class'] ?? '', self::STICKY_HEADER_ENABLED_CLASS ] );
} }
$original['class'] = trim( $original['class'] );
return $original; return $original;
} }

View file

@ -4,25 +4,12 @@ let /** @type {MwApi} */ api;
const debounce = require( /** @type {string} */ ( 'mediawiki.util' ) ).debounce; const debounce = require( /** @type {string} */ ( 'mediawiki.util' ) ).debounce;
/** /**
* Saves preference to user preferences and/or localStorage. * Saves preference to user preferences if user is logged in
* *
* @param {string} feature * @param {string} feature
* @param {boolean} enabled * @param {boolean} enabled
*/ */
function save( feature, enabled ) { function save( feature, enabled ) {
let featuresJSON;
// @ts-ignore
const features = mw.storage.get( 'VectorFeatures' ) || '{}';
try {
featuresJSON = JSON.parse( features );
} catch ( e ) {
featuresJSON = {};
}
featuresJSON[ feature ] = enabled;
// @ts-ignore
mw.storage.set( 'VectorFeatures', JSON.stringify( featuresJSON ) );
if ( !mw.user.isAnon() ) { if ( !mw.user.isAnon() ) {
debounce( function () { debounce( function () {
api = api || new mw.Api(); api = api || new mw.Api();
@ -57,12 +44,40 @@ function toggleBodyClasses( name, override ) {
} }
} }
/**
*
* @param {string} name feature name
* @param {boolean} [override] option to force enabled or disabled state.
*
* @return {boolean} The new feature state (false=disabled, true=enabled).
* @throws {Error} if unknown feature toggled.
*/
function toggleDocClasses( name, override ) {
const featureClassEnabled = `vector-feature-${name}-enabled`,
classList = document.documentElement.classList,
featureClassDisabled = `vector-feature-${name}-disabled`;
if ( classList.contains( featureClassDisabled ) || override === true ) {
classList.remove( featureClassDisabled );
classList.add( featureClassEnabled );
return true;
} else if ( classList.contains( featureClassEnabled ) || override === false ) {
classList.add( featureClassDisabled );
classList.remove( featureClassEnabled );
return false;
} else {
// Perhaps we are dealing with cached HTML ?
// FIXME: Can be removed and replaced with throw new Error when cache is clear.
return toggleBodyClasses( name, override );
}
}
/** /**
* @param {string} name * @param {string} name
* @throws {Error} if unknown feature toggled. * @throws {Error} if unknown feature toggled.
*/ */
function toggle( name ) { function toggle( name ) {
const featureState = toggleBodyClasses( name ); const featureState = toggleDocClasses( name );
save( name, featureState ); save( name, featureState );
} }
@ -73,9 +88,10 @@ function toggle( name ) {
* @return {boolean} * @return {boolean}
*/ */
function isEnabled( name ) { function isEnabled( name ) {
return document.body.classList.contains( const className = `vector-feature-${name}-enabled`;
'vector-feature-' + name + '-enabled' return document.documentElement.classList.contains( className ) ||
); // FIXME: For cached HTML. Remove after one train cycle.
document.body.classList.contains( className );
} }
module.exports = { isEnabled, toggle, toggleBodyClasses }; module.exports = { isEnabled, toggle, toggleDocClasses };

View file

@ -21,11 +21,11 @@
} }
//NOTE: enabled/disabled class on body. //NOTE: enabled/disabled class on body.
&.vector-feature-limited-width-disabled .vector-limited-width-toggle:before { .vector-feature-limited-width-disabled .vector-limited-width-toggle:before {
background-image: url( images/fullscreen-close.svg ); background-image: url( images/fullscreen-close.svg );
} }
&.vector-feature-limited-width-enabled .vector-limited-width-toggle:before { .vector-feature-limited-width-enabled .vector-limited-width-toggle:before {
background-image: url( images/fullscreen.svg ); background-image: url( images/fullscreen.svg );
} }
} }

View file

@ -31,14 +31,14 @@ function disablePinningAtBreakpoint( header, e ) {
// FIXME: Class toggling should be centralized instead of being // FIXME: Class toggling should be centralized instead of being
// handled here, in features.js and togglePinnableClasses(). // handled here, in features.js and togglePinnableClasses().
if ( e.matches && savedPinnedState === true ) { if ( e.matches && savedPinnedState === true ) {
features.toggleBodyClasses( featureName, false ); features.toggleDocClasses( featureName, false );
header.classList.remove( PINNED_HEADER_CLASS ); header.classList.remove( PINNED_HEADER_CLASS );
header.classList.add( UNPINNED_HEADER_CLASS ); header.classList.add( UNPINNED_HEADER_CLASS );
movePinnableElement( pinnableElementId, unpinnedContainerId ); movePinnableElement( pinnableElementId, unpinnedContainerId );
} }
if ( !e.matches && savedPinnedState === true ) { if ( !e.matches && savedPinnedState === true ) {
features.toggleBodyClasses( featureName, true ); features.toggleDocClasses( featureName, true );
header.classList.add( PINNED_HEADER_CLASS ); header.classList.add( PINNED_HEADER_CLASS );
header.classList.remove( UNPINNED_HEADER_CLASS ); header.classList.remove( UNPINNED_HEADER_CLASS );
movePinnableElement( pinnableElementId, pinnedContainerId ); movePinnableElement( pinnableElementId, pinnedContainerId );

View file

@ -32,11 +32,15 @@
@media ( min-width: @min-width-desktop-wide ) { @media ( min-width: @min-width-desktop-wide ) {
// Ensure search box is aligned with content when search thumbnails or JS is off // Ensure search box is aligned with content when search thumbnails or JS is off
// FIXME: Remove html:not( .client-js ) when cache has cleared.
.vector-feature-page-tools-disabled & .vector-search-box:not( .vector-search-box-auto-expand-width ), .vector-feature-page-tools-disabled & .vector-search-box:not( .vector-search-box-auto-expand-width ),
.vector-feature-page-tools-disabled:not( .client-js ) & .vector-search-box,
html:not( .client-js ) .vector-feature-page-tools-disabled & .vector-search-box { html:not( .client-js ) .vector-feature-page-tools-disabled & .vector-search-box {
padding-left: @size-search-expand; padding-left: @size-search-expand;
} }
// FIXME: Remove .client-js .vector-feature-page-tools-enabled when cache has cleared.
.client-js.vector-feature-page-tools-enabled & .vector-search-box.vector-search-box-auto-expand-width,
.client-js .vector-feature-page-tools-enabled & .vector-search-box.vector-search-box-auto-expand-width { .client-js .vector-feature-page-tools-enabled & .vector-search-box.vector-search-box-auto-expand-width {
// Ensure search box is aligned with content when it autoexpands (i.e. search thumbnails) // Ensure search box is aligned with content when it autoexpands (i.e. search thumbnails)
margin-left: -@size-search-expand; margin-left: -@size-search-expand;

View file

@ -18,11 +18,15 @@
contain: paint; contain: paint;
} }
// FIXME: `.vector-feature-page-tools-disabled.vector-toc-pinned` can be removed when cached HTML no longer an issue.
.vector-feature-page-tools-disabled .vector-toc-pinned &,
.vector-feature-page-tools-disabled.vector-toc-pinned & { .vector-feature-page-tools-disabled.vector-toc-pinned & {
// Align the left edge of the TOC text with the main menu button icon. // Align the left edge of the TOC text with the main menu button icon.
margin-left: -27px; margin-left: -27px;
} }
// FIXME: `.vector-feature-page-tools-enabled.vector-toc-pinned` can be removed when cached HTML no longer an issue.
.vector-feature-page-tools-enabled .vector-toc-pinned &,
.vector-feature-page-tools-enabled.vector-toc-pinned & { .vector-feature-page-tools-enabled.vector-toc-pinned & {
// Align the left edge of the TOC text with the page container // Align the left edge of the TOC text with the page container
margin-left: -@spacing-subsection-toggle; margin-left: -@spacing-subsection-toggle;
@ -40,11 +44,15 @@
padding-top: 1.5em; padding-top: 1.5em;
} }
// FIXME: .vector-feature-page-tools-disabled.vector-toc-pinned can be removed when cached HTML no longer an issue.
.vector-feature-page-tools-disabled .vector-toc-pinned @{selector-main-menu-closed} ~ .mw-table-of-contents-container &,
.vector-feature-page-tools-disabled.vector-toc-pinned @{selector-main-menu-closed} ~ .mw-table-of-contents-container & { .vector-feature-page-tools-disabled.vector-toc-pinned @{selector-main-menu-closed} ~ .mw-table-of-contents-container & {
// Needed to align TOC with bottom of title, 1.5em padding + 1.5em margin = 3em // Needed to align TOC with bottom of title, 1.5em padding + 1.5em margin = 3em
margin-top: 1.5em; margin-top: 1.5em;
} }
// FIXME: .vector-feature-page-tools-enabled.vector-toc-pinned can be removed when cached HTML no longer an issue.
.vector-feature-page-tools-enabled.vector-feature-main-menu-pinned-disabled .vector-toc-pinned &,
.vector-feature-page-tools-enabled.vector-toc-pinned.vector-feature-main-menu-pinned-disabled & { .vector-feature-page-tools-enabled.vector-toc-pinned.vector-feature-main-menu-pinned-disabled & {
// Align TOC with bottom of title when main menu is not pinned but the TOC is // Align TOC with bottom of title when main menu is not pinned but the TOC is
margin-top: @margin-top-pinned-toc; margin-top: @margin-top-pinned-toc;

View file

@ -108,7 +108,10 @@
// Horizontally center content when column start is empty (i.e. no pinned ToC or pinned main menu) // Horizontally center content when column start is empty (i.e. no pinned ToC or pinned main menu)
.vector-feature-page-tools-disabled { .vector-feature-page-tools-disabled {
// FIXME: &.vector-toc-unpinned can be removed when cache is clear.
@{selector-sidebar-no-toc-sidebar-closed}, @{selector-sidebar-no-toc-sidebar-closed},
.vector-toc-unpinned @{selector-main-menu-closed},
&.vector-toc-unpinned @{selector-main-menu-closed} { &.vector-toc-unpinned @{selector-main-menu-closed} {
& ~ .mw-content-container { & ~ .mw-content-container {
grid-column: mainMenu / pageContent; grid-column: mainMenu / pageContent;
@ -121,7 +124,9 @@
// Horizontally center content when column start is empty (i.e. no pinned ToC or pinned main menu) // Horizontally center content when column start is empty (i.e. no pinned ToC or pinned main menu)
.vector-feature-page-tools-enabled { .vector-feature-page-tools-enabled {
// FIXME: &.vector-toc-unpinned can be removed when cache is clear.
&.vector-feature-main-menu-pinned-disabled .vector-sidebar-container-no-toc ~ .mw-content-container, &.vector-feature-main-menu-pinned-disabled .vector-sidebar-container-no-toc ~ .mw-content-container,
&.vector-feature-main-menu-pinned-disabled .vector-toc-unpinned .mw-content-container,
&.vector-toc-unpinned.vector-feature-main-menu-pinned-disabled .mw-content-container { &.vector-toc-unpinned.vector-feature-main-menu-pinned-disabled .mw-content-container {
grid-column: mainMenu / pageContent; grid-column: mainMenu / pageContent;
margin-left: auto; margin-left: auto;

View file

@ -175,7 +175,6 @@
"GetPreferences": "VectorHooks", "GetPreferences": "VectorHooks",
"LocalUserCreated": "VectorHooks", "LocalUserCreated": "VectorHooks",
"MakeGlobalVariablesScript": "VectorHooks", "MakeGlobalVariablesScript": "VectorHooks",
"OutputPageBodyAttributes": "VectorHooks",
"ResourceLoaderSiteModulePages": "VectorHooks", "ResourceLoaderSiteModulePages": "VectorHooks",
"ResourceLoaderSiteStylesModulePages": "VectorHooks", "ResourceLoaderSiteStylesModulePages": "VectorHooks",
"SkinPageReadyConfig": "VectorHooks" "SkinPageReadyConfig": "VectorHooks"