ResourceLoader: Follow-up to adding ResourceLoaderClientPreferences
Minor clean up:
* Fix broken `@see` in MainConfigSchema.
* Add missing `@since`.
* Doc experimental nature, as per the Ic3b6eec1995393 msg.
* Doc anonymous scope, and mention general strategy for elsewhere.
* Dependency inject, to separate concerns and keep ClientHtml
decoupled from MediaWiki settings like wgCookiePrefix, which
otherwise break testing this class with only RL-specific config.
* Apply JS conventions. Made easier by using "JS" as the heredoc
identifier, which IDEs recognise as for highlighting.
* Move code together with the other documentElement.className
statement. This helps both with understanding the PHP side in terms
of related logic and how it interacts, as well as the frontend as
it literally brings <html script>, client-js, and clientpref all
next to each other.
HTML weight of default Main_Page on localhost, logged-out:
* vector : 23.21 kB / 140.48 kB
* vector-2022 : 24.17 kB / 146.43 kB +6.0 kB
* vector-2022 ClientPref=true before : 24.28 kB / 146.70 kB +6.3 kB
* vector-2022 ClientPref=true after : 24.27 kB / 146.68 kB +6.2 kB
Given:
* $wgResourceLoaderClientPreferences = true;
Test plan:
1. View /wiki/Main_Page?useskin=vector-2022 while logged-out,
confirm the inline script is there, and
"vector-feature-limited-width-content" is set on <html class>,
layout width appears fixed.
2. Run mw.cookie.set('mwclientprefs', 'vector-feature-limited-width-content');
3. Refresh and confirm the class is changed at runtime and layout
width is fluid.
Use mw.cookie.set('mwclientprefs', null) to undo.
Bug: T321498
Change-Id: I07f471b815ffadfca9eb4f7bd228cb72dfd1ec9b
This commit is contained in:
parent
d4d6156c3b
commit
190598d10f
4 changed files with 75 additions and 49 deletions
|
|
@ -3205,8 +3205,13 @@ config-schema:
|
|||
ResourceLoaderClientPreferences:
|
||||
default: false
|
||||
description: |-
|
||||
Whether skins support client side (anonymous) preferences.
|
||||
@see RL/ClientHtml
|
||||
Enable client-side preferences for unregistered users.
|
||||
This is only supported for unregistered users. For registered users, skins
|
||||
and extensions must use user preferences (e.g. hidden or API-only options)
|
||||
and swap class names server-side through the Skin interface.
|
||||
@warning EXPERIMENTAL!
|
||||
@since 1.40
|
||||
@see \MediaWiki\ResourceLoader\ClientHtml
|
||||
DisableOutputCompression:
|
||||
default: false
|
||||
description: 'Disable output compression (enabled by default if zlib is available)'
|
||||
|
|
|
|||
|
|
@ -5153,9 +5153,15 @@ class MainConfigSchema {
|
|||
];
|
||||
|
||||
/**
|
||||
* Whether skins support client side (anonymous) preferences.
|
||||
* Enable client-side preferences for unregistered users.
|
||||
*
|
||||
* @see RL/ClientHtml
|
||||
* This is only supported for unregistered users. For registered users, skins
|
||||
* and extensions must use user preferences (e.g. hidden or API-only options)
|
||||
* and swap class names server-side through the Skin interface.
|
||||
*
|
||||
* @warning EXPERIMENTAL!
|
||||
* @since 1.40
|
||||
* @see \MediaWiki\ResourceLoader\ClientHtml
|
||||
*/
|
||||
public const ResourceLoaderClientPreferences = [
|
||||
'default' => false,
|
||||
|
|
|
|||
|
|
@ -3319,6 +3319,13 @@ class OutputPage extends ContextSource {
|
|||
);
|
||||
$this->rlExemptStyleModules = $exemptGroups;
|
||||
|
||||
$config = $this->getConfig();
|
||||
$clientPrefEnabled = (
|
||||
$config->get( MainConfigNames::ResourceLoaderClientPreferences ) &&
|
||||
!$this->getUser()->isRegistered()
|
||||
);
|
||||
$clientPrefCookiePrefix = $config->get( MainConfigNames::CookiePrefix );
|
||||
|
||||
$rlClient = new RL\ClientHtml( $context, [
|
||||
'target' => $this->getTarget(),
|
||||
'nonce' => $this->CSP->getNonce(),
|
||||
|
|
@ -3332,6 +3339,8 @@ class OutputPage extends ContextSource {
|
|||
'safemode' => ( $this->getAllowedModules( RL\Module::TYPE_COMBINED )
|
||||
<= RL\Module::ORIGIN_CORE_INDIVIDUAL
|
||||
) ? '1' : null,
|
||||
'clientPrefEnabled' => $clientPrefEnabled,
|
||||
'clientPrefCookiePrefix' => $clientPrefCookiePrefix,
|
||||
] );
|
||||
$rlClient->setConfig( $this->getJSVars( self::JS_VAR_EARLY ) );
|
||||
$rlClient->setModules( $this->getModules( /*filter*/ true ) );
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
namespace MediaWiki\ResourceLoader;
|
||||
|
||||
use Html;
|
||||
use MediaWiki\MainConfigNames;
|
||||
use Wikimedia\WrappedString;
|
||||
use Wikimedia\WrappedStringList;
|
||||
|
||||
|
|
@ -62,6 +61,8 @@ class ClientHtml {
|
|||
* - 'target': Parameter for modules=startup request, see StartUpModule.
|
||||
* - 'safemode': Parameter for modules=startup request, see StartUpModule.
|
||||
* - 'nonce': From OutputPage->getCSP->getNonce().
|
||||
* - 'clientPrefEnabled': See $wgResourceLoaderClientPreferences.
|
||||
* - 'clientPrefCookiePrefix': See $wgResourceLoaderClientPreferences.
|
||||
*/
|
||||
public function __construct( Context $context, array $options = [] ) {
|
||||
$this->context = $context;
|
||||
|
|
@ -70,6 +71,8 @@ class ClientHtml {
|
|||
'target' => null,
|
||||
'safemode' => null,
|
||||
'nonce' => null,
|
||||
'clientPrefEnabled' => false,
|
||||
'clientPrefCookiePrefix' => '',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +238,52 @@ class ClientHtml {
|
|||
return [ 'class' => 'client-nojs' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set relevant classes on document.documentElement
|
||||
*
|
||||
* @param string|null $nojsClass Class name that Skin will set on HTML document
|
||||
* @return string
|
||||
*/
|
||||
private function getDocumentClassNameScript( $nojsClass ) {
|
||||
// Change "client-nojs" to "client-js".
|
||||
// This enables server rendering of UI components, even for those that should be hidden
|
||||
// in Grade C where JavaScript is unsupported, whilst avoiding a flash of wrong content.
|
||||
//
|
||||
// See also Skin:getHtmlElementAttributes() and startup/startup.js.
|
||||
//
|
||||
// Optimisation: Produce shorter and faster JS by only writing to DOM. Avoid reading
|
||||
// HTMLElement.className and executing JS regexes by doing the string replace in PHP.
|
||||
// This is possible because Skin informs RL about the final value of <html class>, and
|
||||
// because RL already controls the first element in HTML <head> for performance reasons.
|
||||
$nojsClass ??= $this->getDocumentAttributes()['class'];
|
||||
$jsClass = preg_replace( '/(^|\s)client-nojs(\s|$)/', '$1client-js$2', $nojsClass );
|
||||
$jsClassJson = $this->context->encodeJson( $jsClass );
|
||||
$script = "
|
||||
document.documentElement.className = {$jsClassJson};
|
||||
";
|
||||
|
||||
if ( $this->options['clientPrefEnabled'] ) {
|
||||
$cookiePrefix = $this->options['clientPrefCookiePrefix'];
|
||||
$script .= <<<JS
|
||||
( function () {
|
||||
var cookie = document.cookie.match( /(?:^|; )${cookiePrefix}mwclientprefs=([^;]+)/ );
|
||||
// For now, only support disabling a feature
|
||||
// Only supports a single feature (modifying a single class) at this stage.
|
||||
// In future this may be expanded to multiple once this has been proven as viable.
|
||||
if ( cookie ) {
|
||||
var featureName = cookie[1];
|
||||
document.documentElement.className = document.documentElement.className.replace(
|
||||
featureName + '-enabled',
|
||||
featureName + '-disabled'
|
||||
);
|
||||
}
|
||||
} () );
|
||||
JS;
|
||||
}
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* The order of elements in the head is as follows:
|
||||
* - Inline scripts.
|
||||
|
|
@ -255,15 +304,7 @@ class ClientHtml {
|
|||
$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 startup/startup.js.
|
||||
$nojsClass ??= $this->getDocumentAttributes()['class'];
|
||||
$jsClass = preg_replace( '/(^|\s)client-nojs(\s|$)/', '$1client-js$2', $nojsClass );
|
||||
$jsClassJson = $this->context->encodeJson( $jsClass );
|
||||
$script = "
|
||||
document.documentElement.className = {$jsClassJson};
|
||||
";
|
||||
$script = $this->getDocumentClassNameScript( $nojsClass );
|
||||
|
||||
// Inline script: Declare mw.config variables for this page.
|
||||
if ( $this->config ) {
|
||||
|
|
@ -290,15 +331,6 @@ RLPAGEMODULES = {$pageModulesJson};
|
|||
";
|
||||
}
|
||||
|
||||
$config = $this->resourceLoader->getConfig();
|
||||
$user = $this->context->getUserIdentity();
|
||||
$isAnon = !$user || !$user->isRegistered();
|
||||
// This code is only loaded for anonymous users. Logged in users should use preferences.
|
||||
if ( $config->get( MainConfigNames::ResourceLoaderClientPreferences ) && $isAnon ) {
|
||||
$script .= $this->getClientSidePreferencesScript(
|
||||
$config->get( MainConfigNames::CookiePrefix )
|
||||
);
|
||||
}
|
||||
if ( !$this->context->getDebug() ) {
|
||||
$script = ResourceLoader::filter( 'minify-js', $script, [ 'cache' => false ] );
|
||||
}
|
||||
|
|
@ -393,32 +425,6 @@ RLPAGEMODULES = {$pageModulesJson};
|
|||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds ability for anonymous users to change classes on document.documentElement
|
||||
*
|
||||
* @param string $cookiePrefix
|
||||
* @return string
|
||||
*/
|
||||
private function getClientSidePreferencesScript( string $cookiePrefix ) {
|
||||
return <<<END
|
||||
(function () {
|
||||
// Client side preferences
|
||||
var doc = document.documentElement;
|
||||
var clientPrefCookie = document.cookie.match(/(?:^|; )${cookiePrefix}mwclientprefs=([^;]+)/);
|
||||
// For now, only support disabling a feature
|
||||
// Only supports a single feature (modifying a single class) at this stage.
|
||||
// In future this may be expanded to multiple once this has been proven as viable.
|
||||
if ( clientPrefCookie ) {
|
||||
var featureName = clientPrefCookie[1];
|
||||
doc.className = doc.className.replace(
|
||||
featureName + '-enabled',
|
||||
featureName + '-disabled'
|
||||
);
|
||||
}
|
||||
} () );
|
||||
END;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly load or embed modules on a page.
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in a new issue