Trim down CSS loaded in the head for anonymous users on mobile
Closed, ResolvedPublic

Description

Currently, MobileFrontend loads 137kb of uncompressed CSS (29kb compressed) in the head, which blocks rendering. That's a whole lot of CSS for a mobile skin and represents as much as 21 seconds worth of staring at a blank page on a 2G connection (simulated in Chrome devtools).

We need to trim this down as much as possible. Some initial ideas:

  • see what can be done about all the inlined images (move to their own non-head modules?)
  • identify styles that cover elements only appearing after a user action, and load those asynchronously and/or on action
  • identify styles that cover elements which are usually below the fold, which could be loaded asynchronously, as the chance of reflow seen by the user would be minimized if we load that extra CSS right after the initial pageload
  • clean up dead rules, if any

First, I will measure how much gain there is to be had for images, which are easily greppable.

Related Objects

StatusAssignedTask
DuplicateJhernandez
ResolvedJdlrobson
DuplicateNone
DuplicateNone
ResolvedJdlrobson
Resolvedbmansurov
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
Invalidmatmarex
ResolvedJdlrobson
DeclinedJdlrobson
OpenNone
ResolvedFlorian
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
DeclinedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedGilles
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedJdlrobson
ResolvedFlorian
InvalidFlorian
ResolvedJdlrobson
Gilles created this task.Apr 27 2015, 2:37 PM
Gilles updated the task description. (Show Details)
Gilles raised the priority of this task from to Normal.
Gilles claimed this task.
Gilles added a subscriber: Gilles.
Restricted Application added a project: Readers-Web-Backlog. · View Herald TranscriptApr 27 2015, 2:37 PM
Restricted Application added a subscriber: Aklapper. · View Herald Transcript

I realize now that the figures I measured above are in no-JS mode. I've identified some low-hanging fruit in that mode, but then I'll have to measure what traffic no-JS mobile represents, to see if it's worth optimizing that mode further or not.

phuedx added a subscriber: phuedx.EditedApr 27 2015, 3:20 PM

Other ideas might also include:

  • moving <link rel="stylesheet"> elements to the top of the head so the browser starts fetching it as quickly as possible
  • including critical CSS as a <style> element in the head element You already said this and I was looking at ?debug=true

Please, please, please reach out to the Mobile Web team on mobile-l or mobile-tech before you start on this kind of improvement. Product, Design, and Engineering should all be involved in carving out the smallest possible response (that also renders very quickly).

Change 206831 had a related patch set uploaded (by Gilles):
[WIP] Add ability to load plain CSS files at the bottom

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

clean up dead rules, if any

This is a good point. I think @Jdlrobson was experimenting with it. He may be able to share his findings. And identifying dead rules should be part of our workflow.

I've made some sub tasks but there are various other problems here :

  • Non-generalised css rules (I'm working on this)
  • Styles from desktop/ other extensions:

ext.gadget.switcher
mediawiki.sectionAnchor
mediawiki.ui.button

Out of the above the only module I know we use is mediawiki.ui.button but this should/couldprobably be loaded via JS for main pages and server side for special pages - however RL doesn't support that kind of behaviour.

I think that skins.minerva.drawers.styles and skins.minerva.icons.images are loaded in the head and shouldn't be. For the latter, only a strict subset need be loaded.

Getting skins.minerva.icons.images out of the head is what I'm working on at the moment. The changeset currently attached to this task is a prerequisite.

In theory for the latter we could load all icons at the end at the expense of icons at first appearing unstyled. Trade off we may want to consider

@Gilles another problem is MediaWiki:Mobile.css - we have no control over this and currently this (community loaded) is loaded in the head.

@Jdlrobson do we have stats about how large that community CSS is in practice across wikis?

Gilles added a comment.EditedApr 28 2015, 6:44 PM

In theory for the latter we could load all icons at the end at the expense of icons at first appearing unstyled. Trade off we may want to consider

That's what I had in mind for skins.minerva.icons.images. Seeing the page minus icons sooner (seconds sooner in the case of slow connections) is better than the status quo. As long as the content doesn't jump around when the icons appear, but from what I've seen in my experiments, that's not the case. People are visually trained to expect images appearing later anyway, since that's what happens for in-article images. The fact that the icons were embedded in the head was probably an accident, imho. It just happened that we moved to images embedded in the CSS a while ago and that all the CSS was in the head. I would be surprised if it was a conscious decision to put the icons in the head and block rendering for them.

As for the UX, users are more likely to start reading the content than press an icon the second the page appears.

@Gilles it's not so bad on mobile (right now) - seems we either wrongly or rightly already load it via JavaScript and it is 2.1k gzipped, 4.1k uncompressed

Desktop is much worse though - on enwiki 6.1k gzipped, 20.4k uncompressed for css
((hence why we have a separate page for this)

Jdlrobson renamed this task from Trim down CSS loaded in the head to Trim down CSS loaded in the head on mobile.May 6 2015, 7:39 PM
Jdlrobson set Security to None.

T97410 is resolved in mobile meaning only the necessary styles are being loaded in the head.

@phuedx do we want to move a lot of these cards into your next sprint? I think we can reduce firstPaint time drastically by doing them all...

Florian added a subscriber: Florian.May 7 2015, 3:28 PM

I found two css rules in mw-ui-icon that we can probably get rid of
https://gerrit.wikimedia.org/r/213982 and https://gerrit.wikimedia.org/r/213983
@phuedx I guess you'd be a good person to check this.

Excellent progress, on beta I see that we're down to 6.4kb (compressed) of CSS. It was originally 29kb when we started. Well done!

Gilles removed Gilles as the assignee of this task.Aug 5 2015, 8:22 AM
Jdlrobson renamed this task from Trim down CSS loaded in the head on mobile to Trim down CSS loaded in the head for anonymous users on mobile.Aug 14 2015, 6:12 PM
Jdlrobson closed this task as Resolved.Sep 26 2015, 12:42 AM
Jdlrobson claimed this task.

I did a CSS audit on http://en.m.wikipedia.beta.wmflabs.org/wiki/Unused_styles_test_page?mobileaction=stable (a fairly big page) with JavaScript disabled:

122 rules (46%) of CSS not used by the current page.
I recorded the rules we can clean up T113830 but I think this is sensible and I'm calling this card done.

For reference I've also include the css styles that I think we can keep below.

Can probably live with given they are probably used on certain pages but not all pages.
.notification-count.max
.content table.multicol > tbody > tr > td,.content table.multicol > tr > td
.content .thumb.tleft
.content .thumb.tright
.content blockquote
.content table caption
.nomobile
.content .thumb
.content .thumb .thumbinner
.content .thumb .thumbinner > div
.content .thumb .noresize
.content .thumb .noresize img
.content .thumbcaption
.content .floatright
.content .floatleft
.content div.magnify
h3
h4
blockquote
blockquote:before
blockquote:after
.content ul > li > ul > li > ul
dl
dl dt
pre
sub
.content table caption
.content table.wikitable
.content table.wikitable > tr > th,.content table.wikitable > tr > td,.content table.wikitable > * > tr > th,.content table.wikitable > * > tr > td
.content table.wikitable > tr > th,.content table.wikitable > * > tr > th
.content .sistersitebox,.content .tmbox,.content .ambox,.content #coordinates,.content .navbox,.content .vertical-navbox,.content .topicon,.content .metadata

Mediawiki ui styles that we should probably live with
.mw-ui-button.mw-ui-big
.mw-ui-button.mw-ui-block
.mw-ui-button.mw-ui-progressive.mw-ui-quiet,.mw-ui-button.mw-ui-primary.mw-ui-quiet
.mw-ui-button.mw-ui-progressive.mw-ui-quiet:hover,.mw-ui-button.mw-ui-primary.mw-ui-quiet:hover,.mw-ui-button.mw-ui-progressive.mw-ui-quiet:focus,.mw-ui-button.mw-ui-primary.mw-ui-quiet:focus
.mw-ui-button.mw-ui-progressive.mw-ui-quiet:active,.mw-ui-button.mw-ui-primary.mw-ui-quiet:active,.mw-ui-button.mw-ui-progressive.mw-ui-quiet.mw-ui-checked,.mw-ui-button.mw-ui-primary.mw-ui-quiet.mw-ui-checked
.mw-ui-button.mw-ui-progressive.mw-ui-quiet:disabled,.mw-ui-button.mw-ui-primary.mw-ui-quiet:disabled
.mw-ui-button.mw-ui-constructive
.mw-ui-button.mw-ui-constructive:hover
.mw-ui-button.mw-ui-constructive:focus
.mw-ui-button.mw-ui-constructive:focus::-moz-focus-inner
.mw-ui-button.mw-ui-constructive:active,.mw-ui-button.mw-ui-constructive.mw-ui-checked
.mw-ui-button.mw-ui-constructive:disabled
.mw-ui-button.mw-ui-constructive:disabled:hover,.mw-ui-button.mw-ui-constructive:disabled:active,.mw-ui-button.mw-ui-constructive:disabled.mw-ui-checked
.mw-ui-button.mw-ui-constructive.mw-ui-quiet
.mw-ui-button.mw-ui-constructive.mw-ui-quiet:hover,.mw-ui-button.mw-ui-constructive.mw-ui-quiet:focus
.mw-ui-button.mw-ui-constructive.mw-ui-quiet:active,.mw-ui-button.mw-ui-constructive.mw-ui-quiet.mw-ui-checked
.mw-ui-button.mw-ui-constructive.mw-ui-quiet:disabled
.mw-ui-button.mw-ui-destructive
.mw-ui-button.mw-ui-destructive:hover
.mw-ui-button.mw-ui-destructive:focus
.mw-ui-button.mw-ui-destructive:focus::-moz-focus-inner
.mw-ui-button.mw-ui-destructive:active,.mw-ui-button.mw-ui-destructive.mw-ui-checked
.mw-ui-button.mw-ui-destructive:disabled
.mw-ui-button.mw-ui-destructive:disabled:hover,.mw-ui-button.mw-ui-destructive:disabled:active,.mw-ui-button.mw-ui-destructive:disabled.mw-ui-checked
.mw-ui-button.mw-ui-destructive.mw-ui-quiet
.mw-ui-button.mw-ui-destructive.mw-ui-quiet:hover,.mw-ui-button.mw-ui-destructive.mw-ui-quiet:focus
.mw-ui-button.mw-ui-destructive.mw-ui-quiet:active,.mw-ui-button.mw-ui-destructive.mw-ui-quiet.mw-ui-checked
.mw-ui-button.mw-ui-destructive.mw-ui-quiet:disabled
.mw-ui-button.mw-ui-quiet
.mw-ui-button.mw-ui-quiet:hover,.mw-ui-button.mw-ui-quiet:focus
.mw-ui-button.mw-ui-quiet:active,.mw-ui-button.mw-ui-quiet.mw-ui-checked
.mw-ui-button.mw-ui-quiet:disabled
.mw-ui-button.mw-ui-quiet:hover,.mw-ui-button.mw-ui-quiet:focus
.mw-ui-button.mw-ui-quiet:active,.mw-ui-button.mw-ui-quiet:disabled
.mw-ui-button-group > *
.mw-ui-button-group > *:first-child
.mw-ui-button-group > *:not(:first-child)
.mw-ui-button-group > *:last-child
.mw-ui-icon.mw-ui-icon-before:before
.mw-ui-button-group
.mw-ui-button-group *
.mw-ui-button-group .mw-ui-block
span.mw-ui-icon