Page MenuHomePhabricator

Omit `<link rel="mw-deduplicated-inline-style">` from page view HTML
Open, Needs TriagePublic

Description

From T168333: CSS is duplicated on each template usage.

For the deduplicated styles, do we really need the <link> tags? Couldn't we completely remove the tag?

It doesn’t do anything bad to keep them, the only thing that might be interested in removing them is probably mobile version (since every byte seems to count there).

I don't see any particular reason to do this specifically in MobileFrontend. More generally, this is a matter of distinguishing between canonical HTML and presentation mode.

When editing in VisualEditor, or when rendering partials of pages on mobile web or mobile apps, it is important that the client can keep the stylesheet around, even if it removed the section it was a part of, as long as there is another section that had a reference to it. Likewise, when a section if viewed without any references to that template, it can be omitted.

However, for page views as output by a skin (Vector, Minerva, MobileFrontend), they are indeed not needed. It seems like this falls in the same category as a lot of Parsoid attributes not needed that we strip on rendering. Except this is the first (obvious) instance of such concept for the PHP parser output.

Event Timeline

I note TemplateStyles has no control over this, the code doing it lives in core.

Should we create a hook for post-processing rendered HTML, called with different arguments in canonical and presentation mode? (Although not sure what is canonical in this case? action=render?)

Can't the <style> tags be moved to some centralized place altogether? Their scattered presence in HTML causes problems like mentioned here, in T186965 and in T200704 (I opened the last task just recently).

They could but it would probably negatively impact time to first paint.

I came across another use case where omitting <link/> could be desirable. English Wikipedia has style .navbox+.navbox{margin-top:-1px} that merges borders for adjacent boxes {{navbox}}{{navbox}}. Using <templatestyles/> at the top of this template, before <div class="navbox">...</div> that the content is enclosed in, however ensures that given style is not applied since <link/> sits between the boxes.

Can't the <style> tags be moved to some centralized place altogether?

That was considered during discussion of T155813: Decide on storage and delivery method for TemplateStyles CSS, but was rejected for client-side performance reasons: the one centralized place would have to be before all the content, which would mean that clients would have to download all the styles before getting to the actual content.

I came across another use case where omitting <link/> could be desirable. English Wikipedia has style .navbox+.navbox{margin-top:-1px} that merges borders for adjacent boxes {{navbox}}{{navbox}}. Using <templatestyles/> at the top of this template, before <div class="navbox">...</div> that the content is enclosed in, however ensures that given style is not applied since <link/> sits between the boxes.

You could move the <templatestyles> tag to just inside the <div class="navbox"> (a noticeable FOUC seems unlikely to me since it'd just be an unstyled div, although I'm no expert on FOUC), or you could change the style rule to .navbox + link + .navbox.

[...] or you could change the style rule to .navbox + link + .navbox.

This might be a use case for the general sibling combinator, which would look like .navbox ~ .navbox.

You could move the <templatestyles> tag to just inside the <div class="navbox"> (a noticeable FOUC seems unlikely to me since it'd just be an unstyled div, although I'm no expert on FOUC), or you could change the style rule to .navbox + link + .navbox.

Just in this case it is also preferred because without this navbox will be removed from mobile view, but its styles will still be loaded. (But in other ones, it is a sound argument for removing these tags.)

We had a related issue on frwiki (see talk, involving template Liste éléments):

A parent template, containing a child template, was expecting this child template to return no content, but the child template was still returning the style/link tag, thus interpreted as non-empty content.
Basically we were encountering this: {{#if: {{child}} | gotten result | expected result }}

Ideally the style/link tag shouldn't be considered as content, but I doubt this is doable (or even advisable, actually).

Ideally, this would mean that Mediawiki would not generate only one content, but two in parallel: the content to transclude (and that can be tested with #if:) and separately the metadata which will be inserted elsewhere.

And may be we could have a #ifmeta: to test the expanded value of this metadata).

Then the metadata ill be used to generate the page header:; it would contain "links", or other special accessibility elements.

It would also resolve the current expansion cost caused by the inline insertion of the "link" tag everywhere the template is used, even if only one will be finally kept dring the deduplication: there's been a case where the current expansion caused a page to explode in size: the whole size of the stylesheet was multiplied by the number of occurence, later it was replaced by the size of the minified "link" element containing a unique id, but in fact this minitag is still too large

(e.g. on a page where the stylesheet was included more than a few thousands times, via more than a few thousands inclusion of a template using these templated stylesheets; to solve it, I had to modify the templates to specifiy if we should include or not the stylesheet -- the inclusion being the default, but being suppressable contextually by the caller)

Basically this means that instead of returning a single string object, many API would be allowed to return an array, whose index 0 would be the string content and other keys would be used for the collection of metadata; that array could have a get-accessor that would return or set the string content of key 0.

Many templates, or parserfunctions could then generate their own metadata, using specific keys. All their input parameters would not be only strings, but such objects, and these parserfuntions could recombine the metadata of parameters or create new metadata in the return object.

Finally, Medawiki would use only some keys of the object: the default key 0 for the content, other keys for specific metadata that can be placed in the organized output (e.g in the HTML "head"), or at top of page or footnotes, or additional tools generated in the side bar; other keys would be dropped. It would facilitate the integration of sitewide features, personal features, and the general organization of the page (which could then be templated as well, with parts cachable or not cachable, or using separate caches where appropriate for privacy).

Ideally the style/link tag shouldn't be considered as content, but I doubt this is doable (or even advisable, actually).

One way of doing this, if only one content is returned, would be to encapsulate it in an ignorable tag with the unique id (like "nowiki" tags) whose presence can be easily filtered out by "#if:"; its actual hidden value would be in some external collection keyed by the unique id.

The ignorable tag inside the string countent should be clearly distinctable from other content (it can be safely delimited by using characters that are forbidden in HTML, such as ASCII C0 controls (STX/ETX, DEL) because they are very compact.

Some parserfuntions (notably those extracting substrings or computing the "visible" length) need revision to avoid breaking these hidden contents: I would just return the string content, terminated by a DEL control before the meta data which can have its own local encoding for a collection of keyed items.

The collection would include for each keyed item a "scoped region", i.e. the index position in the initial part of the string and its length for which it applies: taking a "substring" from the text content would also append the applicable keyed metadata that have a matching region. Regions would need special length values for 0-length regions, to mean "before position n" or "after position n", or "include this keyed meta if the meta encoded at position n is included" (so groups of metadata could be managed for successive ranges of content fragments could be managed)

The alternative is just to allow functions to return a DOM-like tree instead of just a single string, recursively mixing parts that are basic string contents, and parts that are keyed arrays (my opinion is that it would be more efficient than having to reparse a string format with a complex structure). In PHP, Lua and Javascript, this is very trivial (and such format is trivially serializable and cachable in JSON format) !

[...] or you could change the style rule to .navbox + link + .navbox.

This might be a use case for the general sibling combinator, which would look like .navbox ~ .navbox.

Just to document, that selector would also hit templates like Campaignbox, which is used in a rather different place than most navboxes usually right after the relevant infobox. We have another navbox that has links to places north, south, east, and west the name of which I do not remember that also is sometimes not where you would expect it. (Those are the two I can think of that would have their border clobbered by that selector in some way [or, at least I think clobbered].)

The other thing we need to consider is the use of other styles embedded in each navbox; right now Navboxes among other templates have generic style parameters that it would be nice to override with TemplateStyles rather than those generic parameters, outside the navbox in question. That would lead to selectors like:

.navbox + link + .navbox,
.navbox + link + style + .navbox,
.navbox + link + style + style + .navbox,
.navbox + link + link + style + .navbox,
... {
    margin-top: -1px
}

which is not exactly fun. It's not hard to do though and once in place it's 'done' (until someone makes some 3x or 4x embedded template i.e. Final template with custom styles transcluding 3x template with custom styles transcluding 2x template with custom styles transcluding top level template which has core styles). I think you always have link in front to represent the top level navbox styles. (Hlist will eventually be a template style transcluded by the core module but it's not necessary for rendering until you hit the navbar so it could 'safely' be placed inside the table today.)

(Aside: I'm not sure if .mw-parser-output gets injected before each name or not with the + selector. Pretty sure not. Might be something to test.)

Navbox could be worked around I think with minimal worry for a FOUC with the aforementioned "put it inside the element" given that most are not at the top of the page or anywhere most people would link to a section for.

Unfortunately, Ambox has the same CSS hanging out, which does appear at the top (but not always at the top). Many instances of these templates are in multiple-problems-like templates that cause them to collapse, but not all. I do expect we'd see an FOUC for at least the first instance of such templates if we took the same work around there.

As another documentation point, I've now got a couple of wikis that have to work around this (search for the task number in sanitized CSS content model pages on en.wp and MediaWiki wiki), so a Tech News if/when this gets fixed would be appreciated! :) (I suspect some of the others quoted in the task description would also like to know if/when this gets fixed.)

I've gotten to another possible solution for the .x + .y problem. Besides just adding styles and links in some combinatorial type selector madness, I'm just adding a div container to hold them all. Live now in MediaWiki wiki's Module:Navbox. This was done as part of a separate feature request to prevent navbox styles from going to the mobile user with an additional nomobile class thanks to MobileFrontend. I'd say this is probably a reasonable workaround for that problem.

[…] A parent template, containing a child template, was expecting this child template to return no content, but the child template was still returning the style/link tag, thus interpreted as non-empty content. Basically we were encountering this: {{#if: {{child}} | gotten result | expected result }}

Ideally the style/link tag shouldn't be considered as content, but I doubt this is doable (or even advisable, actually).

The first use of a template on any given page will always have the <style> element appear in the output where <templatestyles> is used. This task is about optimising the HTML output to remove duplicate placeholders such as <link rel="mw-deduplicated-inline-style">. It is not about removing, disabling, or moving TemplateStyles itself.

The template in question should apply the same condition to its <templatestyles> call as for the rest of the content returned. Otherwise, it would be loading unused styles.

I suppose you mean conditionally loading the templatestyles from Lua, using frame:extensionTag as described here: How can Lua modules interact with styles?

I had fixed the issue I described above, by workarounding in the parent template: edit (applied again here). Coincidentally, I had recently thought of making this conditional loading by Lua in the template {{Liste éléments}}. However, the above issue is an exceptional one, on the other hand the template {{Liste éléments}} is very used, possibly many times on a given page, and I considered it would be a shame to alter its performances (even slightly) only because ot the above edge case.