Ignore parameters to OutputPage::setupOOUI() and deduplicate calls

There are about 33 callers in Code Search, and almost all were calling
it with incorrect parameters.

It was incorrect to call it with no parameters, since this would
globally set the wrong skin and direction. Most callers were doing this.

It was incorrect to call it with the values from some local context.
We are setting a global singleton here so only the global RequestContext
has the correct values.

Usually, setupOOUI() is called to avoid the exception which is thrown
when HTML is generated without a singleton. But it was often called
multiple times in a request, because different parts of the stack need
OOUI HTML and they don't know whether OOUI has been initialised.

So, ignore the parameters passed to setupOOUI(). Ignore all calls after
the first one, so that we can stop throwing away the theme object. Reset
the cached data when RequestContext::setSkin or
RequestContext::setLanguage() are called.

Add RequestContext::getSkinName() so that the name of the skin can be
determined without actually creating the skin object. It would be
complicated to add the method to IContextSource since there is a
method of the same name in Skin, which implements IContextSource.

Change-Id: I7a1e8b613408d2ecc2e497e0672644af85ce0da3
This commit is contained in:
Tim Starling 2023-07-06 16:24:56 +10:00
parent dbb99b9dd3
commit a99e31533b
2 changed files with 108 additions and 44 deletions

View file

@ -68,6 +68,9 @@ class OutputPage extends ContextSource {
// Extensions use the 'LateJSConfigVarNames' attribute instead.
private const CORE_LATE_JS_CONFIG_VAR_NAMES = [];
/** @var bool Whether setupOOUI() has been called */
private static $oouiSetupDone = false;
/** @var string[][] Should be private. Used with addMeta() which adds "<meta>" */
protected $mMetatags = [];
@ -4632,16 +4635,34 @@ class OutputPage extends ContextSource {
* Helper function to setup the PHP implementation of OOUI to use in this request.
*
* @since 1.26
* @param string $skinName The Skin name to determine the correct OOUI theme
* @param string $dir Language direction
* @param string|null $skinName Ignored since 1.41
* @param string|null $dir Ignored since 1.41
*/
public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
$themes = RL\OOUIFileModule::getSkinThemeMap();
$theme = $themes[$skinName] ?? $themes['default'];
// For example, 'OOUI\WikimediaUITheme'.
$themeClass = "OOUI\\{$theme}Theme";
OOUI\Theme::setSingleton( new $themeClass() );
OOUI\Element::setDefaultDir( $dir );
public static function setupOOUI( $skinName = null, $dir = null ) {
if ( !self::$oouiSetupDone ) {
self::$oouiSetupDone = true;
$context = RequestContext::getMain();
$skinName = $context->getSkinName();
$dir = $context->getLanguage()->getDir();
$themes = RL\OOUIFileModule::getSkinThemeMap();
$theme = $themes[$skinName] ?? $themes['default'];
// For example, 'OOUI\WikimediaUITheme'.
$themeClass = "OOUI\\{$theme}Theme";
OOUI\Theme::setSingleton( new $themeClass() );
OOUI\Element::setDefaultDir( $dir );
}
}
/**
* Notify of a change in global skin or language which would necessitate
* reinitialization of OOUI global static data.
* @internal
*/
public static function resetOOUI() {
if ( self::$oouiSetupDone ) {
self::$oouiSetupDone = false;
self::setupOOUI();
}
}
/**
@ -4651,10 +4672,7 @@ class OutputPage extends ContextSource {
* @since 1.25
*/
public function enableOOUI() {
self::setupOOUI(
strtolower( $this->getSkin()->getSkinName() ),
$this->getLanguage()->getDir()
);
self::setupOOUI();
$this->addModuleStyles( [
'oojs-ui-core.styles',
'oojs-ui.styles.indicators',

View file

@ -113,6 +113,15 @@ class RequestContext implements IContextSource, MutableContext {
*/
private $languageRecursion = false;
/** @var Skin|string|null */
private $skinFromHook;
/** @var bool */
private $skinHookCalled = false;
/** @var string|null */
private $skinName;
/**
* @param Config $config
*/
@ -415,6 +424,7 @@ class RequestContext implements IContextSource, MutableContext {
$obj = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $language );
$this->lang = $obj;
}
OutputPage::resetOOUI();
}
/**
@ -480,6 +490,62 @@ class RequestContext implements IContextSource, MutableContext {
public function setSkin( Skin $skin ) {
$this->skin = clone $skin;
$this->skin->setContext( $this );
$this->skinName = $skin->getSkinName();
OutputPage::resetOOUI();
}
/**
* Get the name of the skin
*
* @since 1.41
* @return string
*/
public function getSkinName() {
if ( $this->skinName === null ) {
$this->skinName = $this->fetchSkinName();
}
return $this->skinName;
}
/**
* Get the name of the skin, without caching
*
* @return string
*/
private function fetchSkinName() {
$skinFromHook = $this->getSkinFromHook();
if ( $skinFromHook instanceof Skin ) {
// The hook provided a skin object
return $skinFromHook->getSkinName();
} elseif ( is_string( $skinFromHook ) ) {
// The hook provided a skin name
// Normalize the key, just in case the hook did something weird.
return Skin::normalizeKey( $skinFromHook );
}
// No hook override, go through normal processing
if ( !in_array( 'skin', $this->getConfig()->get( MainConfigNames::HiddenPrefs ) ) ) {
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
$userSkin = $userOptionsLookup->getOption( $this->getUser(), 'skin' );
// Optimisation: Avoid slow getVal(), this isn't user-generated content.
return $this->getRequest()->getRawVal( 'useskin', $userSkin );
} else {
return $this->getConfig()->get( MainConfigNames::DefaultSkin );
}
}
/**
* Get the skin set by the RequestContextCreateSkin hook, if there is any.
*
* @return Skin|string|null
*/
private function getSkinFromHook() {
if ( !$this->skinHookCalled ) {
$this->skinHookCalled = true;
( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
->onRequestContextCreateSkin( $this, $this->skinFromHook );
}
return $this->skinFromHook;
}
/**
@ -487,41 +553,21 @@ class RequestContext implements IContextSource, MutableContext {
*/
public function getSkin() {
if ( $this->skin === null ) {
$skin = null;
$services = MediaWikiServices::getInstance();
( new HookRunner( $services->getHookContainer() ) )->onRequestContextCreateSkin( $this, $skin );
$factory = $services->getSkinFactory();
if ( $skin instanceof Skin ) {
// The hook provided a skin object
$this->skin = $skin;
} elseif ( is_string( $skin ) ) {
// The hook provided a skin name
// Normalize the key, just in case the hook did something weird.
$normalized = Skin::normalizeKey( $skin );
$this->skin = $factory->makeSkin( $normalized );
$skinFromHook = $this->getSkinFromHook();
$skinName = null;
if ( is_string( $skinFromHook ) ) {
$skinName = Skin::normalizeKey( $skinFromHook );
} elseif ( $skinFromHook ) {
$this->skin = $skinFromHook;
} else {
// No hook override, go through normal processing
if ( !in_array( 'skin', $this->getConfig()->get( MainConfigNames::HiddenPrefs ) ) ) {
$userOptionsLookup = $services->getUserOptionsLookup();
$userSkin = $userOptionsLookup->getOption( $this->getUser(), 'skin' );
// Optimisation: Avoid slow getVal(), this isn't user-generated content.
$userSkin = $this->getRequest()->getRawVal( 'useskin', $userSkin );
} else {
$userSkin = $this->getConfig()->get( MainConfigNames::DefaultSkin );
}
// Normalize the key in case the user is passing gibberish query params
// or has old user preferences (T71566).
// Skin::normalizeKey will also validate it, so makeSkin() won't throw.
$normalized = Skin::normalizeKey( $userSkin );
$this->skin = $factory->makeSkin( $normalized );
$skinName = $this->getSkinName();
}
if ( $skinName !== null ) {
$factory = MediaWikiServices::getInstance()->getSkinFactory();
$this->skin = $factory->makeSkin( $skinName );
}
// After all that set a context on whatever skin got created
$this->skin->setContext( $this );
}
return $this->skin;
}