MediaWiki talk:Common.js

Page contents not supported in other languages.
Source: Wikipedia, the free encyclopedia.

Class-triggered gadgets

I propose adding the following to MediaWiki:Common.js and MediaWiki:Mobile.js:

mw.hook('wikipage.content').add(function ($content) {
    $content.find('.load-gadget').each(function (_, e) {
        mw.loader.load('ext.gadget.ondemand-' + e.dataset.gadget)
    });
});

Sample usage (within a template, which can then be used on pages):

<div class=load-gadget data-gadget=xyz>
</div>

Presence of such an html element triggers the gadget with name ondemand-xyz to load.

Past discussion is at Wikipedia:Interface_administrators'_noticeboard/Archive_1#Chess_viewer where @Xaosflux suggested using a central loader for content-enhancing gadgets. @User:isaacl also provided an implementation (the final comment on the page) but I'm proposing a smaller, more minimalist implementation that

  • only allows gadgets, because loading other pages in MediaWiki namespace with action=raw is not performant (no minimisation or caching).
  • avoids hooking wikipage.content because that is only useful if some other JS is adding a class to trigger a gadget load, an unlikely use-case.

Courtesy pings to others from the discussion: @User:Wugapodes, @Anomie, @קיפודנחש, @Galobtter, @Izno, @Writ Keeper. – SD0001 (talk) 12:43, 26 December 2023 (UTC)[reply]

avoids hooking wikipage.content because that is only useful if some other JS is adding a class to trigger a gadget load, an unlikely use-case It's used more than you think. Features such as mw:Extension:RevisionSlider, mw:Manual:Live preview, and mw:Extension:DiscussionTools use it. Even the "Convenient Discussions" script you use makes use of it. Anomie 13:14, 26 December 2023 (UTC)[reply]
Also, I note the implementation you refer to requires all "on demand" gadgets be named with an "ondemand-" prefix, which may be a useful property to preserve. Anomie 13:18, 26 December 2023 (UTC)[reply]
Right, I missed thinking about those uses of wikipage.content. Requiring an ondemand prefix seems reasonable as well. Amended the edit request. – SD0001 (talk) 13:42, 26 December 2023 (UTC)[reply]
 Not done this certainly needs wider discussion first. — xaosflux Talk 16:20, 26 December 2023 (UTC)[reply]
And echoing prior concerns already - this has to be run on every single load of every single page, where it will almost never be needed. Using an extension instead of a gadget is preferable for things like the last use-case (some interactive chess game). — xaosflux Talk 16:23, 26 December 2023 (UTC)[reply]
The point is that there would be only one check for N different gadgets. There are already some checks in Common.js for loading WikiMiniAtlas (lines 57-71) that could use this system instead. Some updates would be needed in Module:Coordinates and Module:Attached KML but once done, we could delete that block in favour of this one. So on the whole, there's no added JS cost. Would you be in favour of implementing this if it takes care of WikiMiniAtlas?
Converting any feature to an extension is always preferable, but it's not easy for volunteers to get them deployed on Wikimedia production as it requires some level of WMF support or sponsorship. I believe work is being done on the ChessBrowser extension for more than 4 years. It's not surprising it hasn't yielded a result yet. – SD0001 (talk) 19:13, 26 December 2023 (UTC)[reply]
I'm not a "hard no" on this, just that it needs to be very carefully considered. — xaosflux Talk 23:30, 26 December 2023 (UTC)[reply]
Refarding the extension idea, I just came across phab:T241524 asking for a generic solution which has been open since 2019. Anomie 02:20, 27 December 2023 (UTC)[reply]
I agree this change seems reasonable to implement. * Pppery * it has begun... 22:15, 26 December 2023 (UTC)[reply]
Provided this is to be implemented, couldn't the code be a little more efficient? $content can be rather large, and it has no use in the special namespace or on page history. How about putting it in if (mw.config.get('wgNamespaceNumber') >= 0 && mw.config.get('wgAction') !== 'history') { ... }? Nardog (talk) 04:31, 27 December 2023 (UTC)[reply]
Special:ExpandTemplates for one has valid use for it. Perhaps we shouldn't try to over-optimize. Anomie 04:37, 27 December 2023 (UTC)[reply]
Then how about using wikipage.categories instead? E.g.
mw.hook('wikipage.categories').add(function ($content) {
    $content.find('a[href^="/wiki/Category:Pages_using_on-demand_gadget_"]').each(function () {
        mw.loader.load('ext.gadget.ondemand-' + this.getAttribute('href').slice(44));
    });
});
Nardog (talk) 03:16, 30 December 2023 (UTC)[reply]
What would the advantage of that be, versus the potential disadvantage that that hook might not be fired in all places where a content (pre)view is rendered? Anomie 03:28, 30 December 2023 (UTC)[reply]
That it won't run on a large tree, e.g. recent changes with thousands of entries. Where does it not fire where a content (pre)view is rendered? Nardog (talk) 03:32, 30 December 2023 (UTC)[reply]
I see no firing of that hook in the code for DiscussionTools or RevisionSlider, for example. Anomie 04:09, 30 December 2023 (UTC)[reply]
Modern versions of jQuery just use native methods under the hood – querySelectorAll or getElementsByClassName. A single class as a selector is a very common use-case, so browsers would optimise for that. On the other hand, a complex selector like a[href^= is probably not optimised. – SD0001 (talk) 17:21, 2 January 2024 (UTC)[reply]
I have setup a documentation page for this feature: WP:On-demand gadgets. Also, Module:Coordinates and Module:Attached KML have been edited to support loading WikiMiniAtlas through this method (as a gadget, MediaWiki:Gadget-ondemand-WikiMiniAtlas.js), so that we can get rid of the special case loading code for it from Common.js. – SD0001 (talk) 17:21, 2 January 2024 (UTC)[reply]
I think the proposed implementation is good. It's similar to how we load the code for collapsible elements and sortable tables in MediaWiki ([1]). I would add a check that the requested gadget actually exists before calling mw.loader.load, to avoid warnings or errors occurring due to creative vandalism on pages.
On a general note, these gadgets themselves will have to be resilient against page vandalism. For example, being careful with how it handles content found on the page to avoid DOM-based XSS vulnerabilities, but also making sure that they can't be instructed to do something that disrupts editing, like covering the whole page with some interactive element. Matma Rex talk 17:38, 4 January 2024 (UTC)[reply]
I'd support this. I don't this needs any premature optimization. Especially if this replaces the wikiminiatlas load it will be very useful. I think the check for the gadget existing would be good per Matma Rex. Galobtter (talk) 01:54, 5 January 2024 (UTC)[reply]
@Galobtter @Matma Rex Not sure if the check is necessary. No http request gets raised for a non-existent gadget, only a console warning (no console error) – which could be useful in understanding why something doesn't load, detecting vandalism, etc. – SD0001 (talk) 08:03, 13 January 2024 (UTC)[reply]
Yeah that seems fine I guess. Probably all use cases should be templates so we can easily audit non-template uses of this in mainspace. Galobtter (talk) 01:51, 16 January 2024 (UTC)[reply]
information Note: In principle, this seems to be basically the same as mw:TemplateScripts, with a slightly different implementation. Would be nice to merge these two efforts instead of setting up "competing" systems. Jon Harald Søby (talk) 16:37, 10 January 2024 (UTC)[reply]
I think using the gadget system rather than raw JS pages is probably better as gadgets get minification and such. Using mw.hook() as is proposed here will work better with various kinds of dynamic content generation. Anomie 16:50, 10 January 2024 (UTC)[reply]
Yup, and phab:T8883 was refused, and phab:T241524 is lingering. Developers seem to hate the idea of doing this? — xaosflux Talk 15:25, 15 January 2024 (UTC)[reply]
I see mixed opinions there from different devs? (Tgr seems supportive.) Galobtter (talk) 01:51, 16 January 2024 (UTC)[reply]
There is support among MW devs for this. T241524 is only lingering because one developer doesn't think this is a good idea, for non-techincal reasons - such as that they don't think intadmins qualified to check that gadgets loaded this way have no-JS fallbacks, don't cause gaps in content when viewed by a search crawler or while printing, etc. That might be a reasonable point for mediawikis in general, but enwiki has lots of technical editors who can write code that conform to standards. Adding the feature to MW itself is a bigger deal - one of the ways get there is to first showcase that the feature can be used responsibly.
But if we don't implement this locally because MW isn't implementing it, we're stuck in a cycle where there's no progress at either level. – SD0001 (talk) 16:37, 25 January 2024 (UTC)[reply]
I have the proposed code at User:SD0001/MediaWiki:Common.js, if someone can give this a second look to ensure WikiMiniAtlas loads won't be impacted. One difference is that WikiMiniAtlas would now load on mobile domain as well (when MediaWiki:Mobile.js is edited to the same effect). The gadget seems to work fine on mobile, so I see no harm but if we don't want it on mobile, MediaWiki:Gadget-ondemand-WikiMiniAtlas.js can be modified accordingly. – SD0001 (talk) 12:10, 15 January 2024 (UTC)[reply]
I support this and appreciate SD0001 for working on it. I'm personally fine doing this without consensus on VPT but I don't think that's a unanimous view, so we might as well take it there first. Enterprisey (talk!) 00:53, 16 January 2024 (UTC)[reply]
Posted notifications at VPT and WT:Gadget. – SD0001 (talk) 16:40, 25 January 2024 (UTC)[reply]
Weakly against this, but only weakly. The risk is low, since they'd be gadgets, but I sincerely don't like the idea of a third party (another user) dictating what sort of JS is run for a user. I would think that means the level of scrutiny for each "ondemand" gadget has to be even higher, since they're not necessarily something a user—or reader!—has opted in to. The third party could theoretically swap out which ondemand is loaded or add one to an unrelated page, so the interaction between these becomes even more important. It also makes anything that pulls from an external wiki—where we don't get to audit changes as readily—more of a risk for an end-user. ~ Amory (utc) 13:40, 30 January 2024 (UTC)[reply]
The alternatives aren't much better. Would you rather have the whole script in MediaWiki:Common.js? Or custom loaders for each one, like WikiMiniAtlas has? Or would you rather have the same in a default-enabled gadget?
I agree with you that the level of scrutiny for any ondemand gadget should be as high as for code in MediaWiki:Common.js or in default-enabled gadgets, with the difference being that arguments about cluttering Common.js or the gadget list wouldn't apply. The fact that the corresponding ondemand gadgets would be clearly indicated by having titles beginning "Gadget-ondemand-" should make is straightforward enough for intadmins to know when the extra scrutiny is needed. Anomie 14:14, 30 January 2024 (UTC)[reply]
I don't dispute or disagree with any of that. We'll obviously ensure the "ondemands" are safe, but there's a difference between intadmins and consensus saying "this is safe and everyone can/will run it" and intadmins saying "this is safe" and then someone else deciding when you run it. I'm anathema to the idea that anyone could put a template on ANI and boom, code gets run for everyone. That doesn't feel good. ~ Amory (utc) 14:41, 30 January 2024 (UTC)[reply]
🤷 I can't say I see much difference between intadmins saying "this is safe and everyone can/will run it from common.js or a default-enabled gadget" and "this is safe and everyone can/will run it on pages that have the right bit of wikitext". Anomie 22:39, 30 January 2024 (UTC)[reply]
Well, I'd say the difference is what happens when someone creates a subpage that includes the wikitext for all the ondemand gadgets a couple dozen or hundred times, then quietly transcludes it on ANI or the like? I think mw.loader is smart enough not to overload the servers, but what's that do to everyone who visits the page? Both computationally and otherwise. That's the kind of thing I mean. ~ Amory (utc) 13:13, 1 February 2024 (UTC)[reply]
One or a hundred times shouldn't make a difference, each gadget should get loaded only once on the page. Anomie 01:05, 2 February 2024 (UTC)[reply]
To elaborate on what Anomie says, mw.loader is also smart enough to not overload clients. Invoking the same gadget multiple times causes it to load and execute just once. mw.loader.moduleRegistry records the module state as ready, and further load calls are no-op.
As for the one time it does load, no HTTP request takes place at all for gadgets that are <100 kb in size as they are cached in localStorage. Even for gadgets that are larger, they are typically present in the browser's disk caches (unless you are hard reloading or if the gadget was recently modified). There is research that shows the efficacy of the technique. – SD0001 (talk) 10:53, 9 February 2024 (UTC)[reply]
I don't mean to belabor the point—it's a weak opposition after all—but I'm specifically not concerned about server or network requests. I'm concerned about how trivially easy it is to disrupt large numbers of users en masse, and to do so in non-obvious ways for most folks. I know it won't be taxing as a network request, it will be taxing for the users. ~ Amory (utc) 23:27, 11 February 2024 (UTC)[reply]
How would it be taxing for the users, if not for the network requests? Gadgets would include checks to bail out quickly if invoked on a page where it shouldn't be. For example, enwiki's largest gadget, xfdcloser-core if invoked on this page – where it's not supposed to do anything, bails out in about 0.5 ms. That's a really tiny amount of time, and all of this is happening well after the first paint so interactivity (TTI) is not affected. – SD0001 (talk) 18:51, 15 February 2024 (UTC)[reply]
<mapframe>, <graph> and a few other tags today allow anyone to inject JS into wiki pages. That JS has gone through gerrit code review, whereas on-demand gadgets go through a community edit request process. That's a difference, I admit, but for default-loaded code, we could consider bridging the difference by requiring all changes to go through review even if the proposer is an int-admin.
It also makes anything that pulls from an external wiki—...—more of a risk for an end-user I agree this is a concern. To address this, I would propose to not allow cross-wiki on-demand loads. Where necessary (such as in the case of WMA), we copy-paste the code locally and setup a bot to raise edit requests every time upstream is edited. (This also improves performance as local code is RL-optimized, so it's a win-win.) – SD0001 (talk) 17:45, 30 January 2024 (UTC)[reply]
BRFA filed for this at Wikipedia:Bots/Requests_for_approval/SDZeroBot_13. – SD0001 (talk) 20:10, 10 March 2024 (UTC)[reply]
I have a similar request at Wikipedia talk:TemplateStyles#Template scripts, although the script is needed for the {{sticky header}} template to move header rows to the <thead> element. If a user has to add class=load-gadget data-gadget=xyz to the table or above it, then that would complicate the usage of that template. But if it were added to the transclusion above the table, then it shouldn't be problematic and only loads on certain pages. Ideally, the script would load once on any pages that have tables with .sticky-header:not(.sortable). The sticky gadget has a similar script that moves header rows to the <thead> element. The best solution would be MediaWiki moving the header rows for all tables (not just sortable), thereby making it work when no JS. From the discussion above, there seems to be concern about trusting one person to verify the JS. I agree that there could be a lot of damage done. In that case, maybe think about having two people verify it. Jroberson108 (talk) 00:07, 17 February 2024 (UTC)[reply]
It might be a good idea to implement it like somewhat like this given Amorymeltzer’s concerns above:
mw.hook( 'wikipage.content' ).add( ( $content ) => {
    var $gadgetTargets = $content.find( '.load-gadget' );
    var onDemandDeps = [];
    $gadgetTargets.each( ( i, e ) => {
        onDemandDeps.push( 'ext.gadget.ondemand-' + e.dataset.gadget );
        if ( i === $gadgetTargets.length - 1 ) {
            mw.loader.load( onDemandDeps );
        }
    } );
} );
This should cut down the impact to less requests. stjn 01:24, 21 February 2024 (UTC)[reply]
See the follow-ups above to that concern. mw.loader already does all this internally, in a less crude way. – SD0001 (talk) 14:38, 24 February 2024 (UTC)[reply]
@SD0001: To clarify, I can use mw.loader to add template scripts to a template? Not user scripts. Jroberson108 (talk) 16:18, 24 February 2024 (UTC)[reply]
Unless I don’t know something, non-default gadgets are not stored in the LS unless they are/were used before, and the part about cache/localStorage is incorrect. But yeah, given what you say overall, not a huge concern. stjn 00:42, 25 February 2024 (UTC)[reply]
Well, for anything to get cached, it needs to be loaded once. This is true for all RL modules, not just non-default gadgets. They will surely be fetched via http calls if someone is opening Wikipedia for the first time in a while on that browser/device. But not on subsequent page loads. – SD0001 (talk) 10:13, 29 February 2024 (UTC)[reply]

Ready to implement?

Discussion has been going on for more than 2 months, and I don't see any unresolved concerns. Can we implement this now? – SD0001 (talk) 17:14, 7 March 2024 (UTC)[reply]

This is no longer required as https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Gadgets/+/1005092 has been merged. Not the most elegant implementation as it relies on categories, but we'll take it. – SD0001 (talk) 14:33, 21 March 2024 (UTC)[reply]
Yup, seems like this entire hack can be skipped. We're going to need some serious testing with the category stuff. — xaosflux Talk 18:11, 21 March 2024 (UTC)[reply]