Page MenuHomePhabricator

ResourceLoaderSkinModule features are not backwards compatible
Closed, ResolvedPublic

Description

In 1.35 ResourceLoaderSkinModule and the features property was introduced that allowed skin developers to define an array of feature keys.
In 1.36 we added support for an object of features to allow an opt-in policy.

Now after some time has passed i see two issues with this.

  1. Skins cannot use new features introduced in 1.36 e.g. normalize without breaking support in 1.35
  2. Skins cannot use opt in policy e.g. { "normalize": true } without breaking support for 1.35

This is because of 2 things

  1. The line throw new InvalidArgumentException( "Feature $feature` is not recognised" );` in ResourceLoaderSkinModule
  2. The opt in policy is not available in 1.35

I suggest we fix these 2 issues by backporting them.

Note for skin developers

Consider a skin that does the following in skin.json

 "requires": {
                "MediaWiki": ">= 1.35.0"
},
 "skins.name.styles": {
                        "class": "ResourceLoaderSkinModule",
                        "features": {
                                "normalize": true
                        },
                        "styles": [ "resources/skins.name.styles/skin.less" ]
 },

prior to this change, this would have broken in 1.35 as the normalize feature is available in 1.36

For skins that want to use the new normalize feature, but retain backwards compatibility in 1.36, they can now register a conditional shim in skin.json

"skins.name.styles.shim": {
                       "styles": [ "resources/skins.name.styles/normalize-shim.less" ]
},

and add it via hook:

public static function onBeforePageDisplay( OutputPage $out, $sk ) {
  $wgVersion = $out->getConfig()->get('Version');
  if ( version_compare( $wgVersion, '1.36', '<' ) ) {
   $out->addModuleStyles( 'skins.name.styles.shim' );
  }
}

When support for 1.35 is no longer needed the module can simply be removed.

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript

Change 654905 had a related patch set uploaded (by Jdlrobson; owner: Jdlrobson):
[mediawiki/core@master] Unknown features shouldn't break style output

https://gerrit.wikimedia.org/r/654905

Change 654854 had a related patch set uploaded (by Jdlrobson; owner: Jdlrobson):
[mediawiki/core@REL1_35] resourceloader: Give SkinModule 'features' option an extensible default

https://gerrit.wikimedia.org/r/654854

Change 654907 had a related patch set uploaded (by Jdlrobson; owner: Jdlrobson):
[mediawiki/core@REL1_35] Unknown features shouldn't break style output

https://gerrit.wikimedia.org/r/654907

Change 654854 merged by jenkins-bot:
[mediawiki/core@REL1_35] resourceloader: Give SkinModule 'features' option an extensible default

https://gerrit.wikimedia.org/r/654854

Jdlrobson claimed this task.

Change 654907 merged by jenkins-bot:
[mediawiki/core@REL1_35] Unknown features shouldn't break style output

https://gerrit.wikimedia.org/r/654907

Change 654905 merged by jenkins-bot:
[mediawiki/core@master] Unknown features shouldn't break style output

https://gerrit.wikimedia.org/r/654905

Krinkle reopened this task as Open.EditedJan 21 2021, 3:17 AM
Krinkle subscribed.

I'd like to re-open this as I feel the solution provides a subpar developer experience and I suspect it was chosen as (understandable) compromise, but for a problem that was misunderstood, and thus I think we do something better and more aligned with how we normally handle this type of support in the MediaWiki ecosystem.

Firstly, I would not classify the way features are added as a breaking change since they work out-of-the-box for places that use them internally, and any older skins also continue to work as-is without modificiation. The issue described is when a skin choses to adopt a new MW 1.36-alpha feature in its master branch, which indeed would break (correctly) if installed on an older version of MediaWiki. This is no different from how other core interfaces and extensions work, and is a result of intentional design choices. Those are open for discussion I suppose, but I would discourage deviating from it ad-hoc without good reason and perhaps a conversation with peers to see whether there are other simpler solutions available that others have already invested in and popularised.

The status quo for compatibility in extension and skin distribution, is that release branches of skins and extensions support their corresponding version of MediaWiki. When you download the MediaWiki 1.33 release tarball, that ships MediaWiki 1.33 with REL1_33 of the bundled extensions and skins, not master. Likewise, when downloading additional extensions or skins from mediawiki.org, the extension distributor offers you the appropiate version for you. As such, to a first approximation there is nothing that needs to be done here for 99% of skins and extensions.

If an individual extension or skin author wishes to offer a customised, broader, support policy for its master distribution then they are indeed free to do so. The Translate and SemanticMW extensions are examples of that, and indeed they generally involve runtime checks or if needed MW_VERSION compare checks, to vary between the old code and the new code. In the case of a module definition, that would look as follows. (The below is based on past examples from the Translate extension):

public function onResourceLoaderRegisterModules( ResourceLoader $rl ): void {
	$isOld = version_compare( MW_VERSION, '1.36', '<' );
	$rl->register( "skins.example", [
		"class" => "ResourceLoaderSkinModule",
		"features" => ( $isOld ? [] : [ "normalize" => true ] ) + [
			"elements" => true,
			"content" => true,
			"interface" => true,
			"legacy" => true
		],
		"styles" => array_merge(
			$isOld ? [ "resources/normalize-shim.css" ] : [],
			[ "resources/skin.less" ]
		],
		"localBasePath" => __DIR__,
		"remoteSkinPath" => "Example"
	] );
}

By comparison, the committed solution here would require a number of unusual state changes:

  • Disable error detection in core, preventing CI or runtime error logging from informing developers or admins about the apparant breakage. As well as preventing regular error reporting during development when genuine mistakes or bugs are made day-to-day.
  • Register a skin module with features we know aren't supported.
  • Developers then need to somehow learn or discover that they can fix this silent failure by:
    • Registering a second module with just a single file, and to register this unconditionally for all users. (This goes against best practice.)
    • Use an OutputPage to conditionally queue a second module, outside the control of the skin where its modules are usually queued.
    • For this to propagate through any caches the user might have, during which the skin is presumably broken.
    • Publicly expose the temporary module to caches until the next MW upgrade, after which cached responses will presumably load the same code twice with untested interactions between the two.

I recognise that registering a module in PHP code might not be pretty, but that is the supported and expected status quo for when a definition needs to be dynamic. I also note once again that to my knowledge this would not be needed anywhere, as I'm not aware of any skin in our ecosystem offering custom in-master support for older LTS releases.

Change 673320 had a related patch set uploaded (by Krinkle; owner: Krinkle):
[mediawiki/core@master] resourceloader: Restore feedback for SkinModule invalid argument

https://gerrit.wikimedia.org/r/673320

Jon mentioned this on https://gerrit.wikimedia.org/r/c/mediawiki/skins/Timeless/+/678785/:

According to Timo master versions should only be compatible with the /next/ release and only tarballs should be considered stable. See https://gerrit.wikimedia.org/r/c/mediawiki/core/+/673320 and https://phabricator.wikimedia.org/T271441#6764043 which proposes making skins fatal when they use ResourceLoaderSkinModule with non-existent features. I don't think that's great from a skin perspective as I worry that will discourage skin developers from adopting new features that make their lives easier, but feedback from yourself and Jack on your own experiences with skin development there would be helpful.

While I left more detailed responses/rants there, Jon is right. This will just make it that much harder to support future releases, and I suspect it will likely result in it becoming extremely uncommon for third-party skins and extensions to do so (it's already not exactly the norm). This is because for those of us running extant releases (largely anyone not the WMF), the 'next' release that we're invariably targeting is the current stable release, because that's what we're in the process of moving to. There is no point in us targeting something we are not using that may change again before we start anyway, so even if we have tried to target ahead in the past because it was feasible then to cover both at once as new features came out, it sounds like this will no longer be possible.

And I kind of want to say the ship has sort of already sailed. This is hardly the only place where it is already becoming very difficult to keep third party projects up to date/maintain backwards-compatible components (where the 'backwards' is the current release). It would be so much easier if we simply explicitly stopped supporting anything after the LTS for most third-party skins/extensions. Simply made it common documented practice that master in those will be targeting the (presently) 1.31 or 1.35 mw core releases, and that's it, because anything after is changing too fast, and in too breaking of a manner, to keep up with. The only exceptions will be things deployed to WMF sites or included in the tarball.

But it we do this, then how long will it take our users to also update? If it takes us that much longer to update the components we're maintaining for our own use because we need to wait for an LTS release to even know what will not fatal when we try to use it, how much more will this compound delays for those depending on these components for their own use, as well?

As an example of how this sort of thing fucks over third parties, Uncyclopedia is finally able to upgrade to 1.35 from 1.32 this year, because MCR completely broke a major dependency (which we weren't expecting to happen when we went to 1.32, or we would have stayed on the LTS) and only late last year did someone actually manage to fix this dependency... and hilariously it's still not entirely 'stable' for 1.35 as apparently 1.35 STILL uses the 'temporary transition' tables?

While usually easier to fix, a skin a site has been on can be a similarly important dependency for them, and similar reason to just not update anything, especially when in this case most of the changes are happening RIGHT after the LTS release... we'll all probably be staying on 1.35 for a long time.

What could anyone possibly contribute back to core at this point? This isn't how you maintain a community. This isn't an ecosystem anymore.

Seems like this would benefit from discussion. Can always backport at later date.

@Isarra wrote:

Also why are we removing backwards compatibility for released versions? Requiring a version that has not yet been released doesn't really seem like great practice...

[…] According to Timo master versions should only be compatible with the /next/ release and only tarballs should be considered stable. See https://gerrit.wikimedia.org/r/c/mediawiki/core/+/673320 and https://phabricator.wikimedia.org/T271441#6764043 which proposes making skins fatal when they use ResourceLoaderSkinModule with non-existent features. I don't think that's great from a skin perspective as I worry that will discourage skin developers from adopting new features that make their lives easier, […]

I assume this is referring to my March 29th comment on mediawiki/core, change 673320, where I explained the well-known and long-standing policy of MediaWiki software being maintained through branches. Where the 1.35-compatible version of a skin can be found in the REL1_35 branch of that skins' repository and thus, except for a very small handful of exceptional extensions that opt to maintain compatibility in-master, that you can generally "just" adopt new features as you see fit and bump the minimum required version of MediaWiki - in master.

This isn't something from me. This is well-established in MediaWiki development and has been for over a decade. I know that you know this, since you've done backports, raised minimum versions for prod code, and triaged bug reports from third parties, etc. I'm guessing there is something in particular about SkinModule.php, that is leading to this misunderstanding.

I think you and Timo (Krinkle) need to talk :-).

Thanks. We did. As developer of Timeless, Isarra has chosen to simultanously support MW master and MW 1.35 from the same master branch of Timeless. (I believe this is motivated by being able to continously deploy the latest development version of Timeless to a MW 1.35 wiki, such as for ShoutWiki.) That makes Timeless special, but that's no trouble at all. While supporting multiple versions of MW is not something most repositories need to concern themselves with, it is something MediaWiki makes possible. It is not encouraged, but it is certainly possible. Extensions like SemanticWiki and Translate are the other projects that have adopted such in-master policy.

The way to support these, as you know from commits like change 651654, and as you can see from removals like change 635018, is with a conditional statement where you use either the newer or older code path accordingly.

From chatting with Isarra, I believe they understand this as well. You can:

  1. Not adopt a new method call, class, module name, or parameter option. So long as that which you do use is supported across all relevant versions, this will be fine.
  2. Adopt the new thing, and bump the minimum version.
  3. If supporting older versions within the same Git branch, adopt the new thing under a conditional, and keep the old code in the "else" branch.

It remains unclear to me why SkinModule.php should be special in allowing a developer to shoot themselves in the foot by adopting a new option whilst supporting an older version, and allow the commit to merge whilst the conditional statement is still missing. Only to find later in some less-obvious way that it wasn't compatible (either by finding out that some CSS is missing, or through some rarely-looked-at server log). This is no different from the hundreds of methods, classes, modules, and options we rename, deprecate, warn for, and remove. Whether a new class (which obviously fatals if constructed if not present), or a method, or a variable, etc.. I would not want someone to install a proxy class that acts as if any unknown or misspelled method always exists in PHP or JS code, and returns null by default or something like that.

In any event, Isarra and I were unable to find a reason or benefit of avoiding regular error detection. One way or the other, adopting a feature that doesn't exist in a supported MW version will lead to breakage, and will require a developer to either not adopt it, or to raise support, or to write a conditional statement.

As I understand it, the only skin where you ran into an issue is Timeless, due to not realizing that the project supports older versions in-master, and after realizing this, you quickly patched that (change 651654) in the same way we've dealt with this for the past ten years. This sounds like a success to me. Would you agree? Or would you have preferred to see this end up differently?

Provided Isarra and Jack are fine with this approach of fataling no objections from me. Thanks for chatting through this with Isarra. I appreciate it!

I still an concerned a fatal is a bit extreme. I would prefer to warn via the developer console (possibly with a switch to fatal for production) when this happens as most of these styles are non-essential to rendering a wiki page and even without them a skin would be usable. Recently when we deprecated setupSkinCss that broke dozens of 3rd party skins in a similar way and in the end we had to manually patch every single one of them (with me providing many of those patches). The upcoming planned "legacy" feature deprecation could end up causing lots of skins to fatal with the new check.

Warning rather than fataling is not a position I'm going to hold strongly. Maybe that's the cost of doing business and we can can come up with special migration patterns to avoid it.

I'll get to this when time allows and the dust has settled on some of the legacy changes and the dust has settled with the concerns relating to T280723. Thanks for the patience!

Change 673320 merged by jenkins-bot:

[mediawiki/core@master] resourceloader: Restore feedback for SkinModule invalid argument

https://gerrit.wikimedia.org/r/673320

Are we all good to resolve this now @Krinkle ?

I am getting InvalidArgumentException: Feature 'content' is not recognised locally on Vector skin, but not seeing this on beta cluster... Is anyone able to reproduce?

Vector and vector useskinversion=2 both work fine for me locally. Latest mediawiki core and Vector master (89d5273e66a, 9213c51).

I got to the bottom of this:

I have a skin installed called Marginless which does the following:

"skins.marginless.styles": {
      "class": "ResourceLoaderSkinModule",
      "features": [
        "elements",
        "content",
        "interface",
        "legacy"
      ],
      "styles": [
        "resources/skin.css"
      ]
    },

it seems that applyFeaturesCompatibility doesn't work on array notation as the listMode check comes later now...

Change 692990 had a related patch set uploaded (by Jdlrobson; author: Jdlrobson):

[mediawiki/core@master] Skins in 1.36 shouldn't break when using deprecated features

https://gerrit.wikimedia.org/r/692990

The opt-in mode is supported to allow skins more control over what enters them - for example Minerva tightly controls feature modules that are added to ensure it's critical path is managed.

Right now a few third party skins are fataling because of the changes, so we are not quite done yet.

Jdforrester-WMF subscribed.

The opt-in mode is supported to allow skins more control over what enters them - for example Minerva tightly controls feature modules that are added to ensure it's critical path is managed.

Right now a few third party skins are fataling because of the changes, so we are not quite done yet.

Does that mean this should block the release of 1.36 too?

@Jdforrester-WMF no urgency with 1.36. Ideally it would go out with 1.36, but in 1.36 it won't fatal, it just will load skins without certain styles and I think very few skins are likely to be impacted, the assumption being that the number of skins using ResourceLoaderSkinModule are still quite low and I am aware of only a few that use content. I think @Mainframe98 is possibly the only skin developer impacted by this, but they have also been highly involved in all the changes here.

@Jdforrester-WMF no urgency with 1.36. Ideally it would go out with 1.36, but in 1.36 it won't fatal, it just will load skins without certain styles and I think very few skins are likely to be impacted, the assumption being that the number of skins using ResourceLoaderSkinModule are still quite low and I am aware of only a few that use content.

Understood.

Unassigning as afaik there is nothing for me to do, let me know if I missed a question.

The content option was indeed renamed, and it was not kept compatible for skins using list mode. I assumed this omission in the compat layer was intentional, but if not, I suppose your team should fix that. I don't think it's in scope for this task, but I don't mind it being re-purposed for this.

Given this was already repurposed (this was originally about removing the error) I think it makes sense to keep the same ticket since these features are still not truly backwards compatible.

Change 693980 had a related patch set uploaded (by Jdlrobson; author: Jdlrobson):

[mediawiki/core@master] Deprecated feature keys should be mapped to new key.

https://gerrit.wikimedia.org/r/693980

Change 692990 abandoned by Jdlrobson:

[mediawiki/core@master] Skins in 1.36 shouldn't break when using deprecated features

Reason:

See https://gerrit.wikimedia.org/r/c/mediawiki/core/ /693980

https://gerrit.wikimedia.org/r/692990

Change 693980 merged by jenkins-bot:

[mediawiki/core@master] ResourceLoaderSkinModule: Deprecated feature keys should be mapped

https://gerrit.wikimedia.org/r/693980

Jdlrobson claimed this task.

I'm going to skip the backport for this one. I don't think any of the skins impacted are actively used.