Page MenuHomePhabricator

Make sure limited width toggle is persistent for anonymous users
Closed, ResolvedPublicBUG REPORT

Description

In T319449 we will create a toggle for switching between max width and non-max-width mode. Initially this will only persist for logged in users. We hit a similar problem with T246427 but decided in that case it was not worthwhile adding code for one preference however as the web interface has evolved, a generalized solution would be very valuable.

We should explore generalizing a way for certain features to be set on the client side using localStorage and a small inline script that reads localStorage prior to rendering. I've sketched out a proof of concept at https://gerrit.wikimedia.org/r/c/mediawiki/skins/Vector/+/845667/5/includes/templates/skin.mustache

It's hoped in future this generalized code could be used to support the following future features:

  • Dark mode
  • Font-size changing

Possibly with some refactoring:

  • Table of contents collapse behaviour
  • Sidebar state (previously explored in T246427)

QA Steps

  • As anonymous user open any page in Vector 2022 skin
  • Hit the toggle button in the bottom right to disable the limited width
  • refresh page

Expected: limited width remains disabled

QA Results - Beta

ACStatusDetails
1T321498#8566999

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes

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

[mediawiki/skins/Vector@master] Persistent client side preferences

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

Test wiki created on Patch demo by Jdlrobson using patch(es) linked to this task:
https://patchdemo.wmflabs.org/wikis/60b297219d/w

ovasileva moved this task from Not ready to estimate to Groomed on the Web-Team-Backlog board.
ovasileva raised the priority of this task from Low to High.Jan 19 2023, 6:24 PM
ovasileva subscribed.

While waiting for a resolution to this widespread problem, please simply make the limited-width mode opt-in for all users and let them adjust their browser window width manually (for a persistent narrower view) or click the toggle (on individual pages that they want narrower).

@Jonesey95 I am not sure I understand your comment. You just need to tick these boxes which is little hardship... I don't see what the advantage is of inverting it - that would just cause annoy people who have already set that preference.
https://en.wikipedia.org/wiki/Special:Preferences#mw-prefsection-rendering

Screen Shot 2023-01-19 at 2.52.39 PM.png (115×355 px, 13 KB)

This bug report is about anonymous users, who, as it has been made clear hundreds of times in the last day or two by commenters on en.WP, do not have access to preferences. Disabling wide mode for them would go a long way toward improving acceptance of this new skin.

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

[mediawiki/core@master] OutputPage: Basic client side user preferences

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

This bug report is about anonymous users, who, as it has been made clear hundreds of times in the last day or two by commenters on en.WP, do not have access to preferences. Disabling wide mode for them would go a long way toward improving acceptance of this new skin.

I don't agree. You'd just be switching one audience of annoyed people with another.It's my experience that people who are unhappy are more likely to express frustration than people are to express gratitude and your proposal sounds like it would just lead to complaints from people who are enjoying the current default and improved readability. I'd rather focus my time (which I'm doing as we speak) on solving this problem for everyone.

Added patchdemo to T91201 - approach looks promising and pretty generic and maintainable.

Just to say: agreed that this looks promising and maintainable. Thank you for tackling this.

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

[mediawiki/core@master] OutputPage: Basic client side user preferences

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

Notes from today's meeting:

Strongly recommend exploring server-side instead of client-side. Our CDN is already fragmented in at least two (m-dot vs canonical), with I believe a few subvariants already as well. We've previously fragmented mobile cache with its logged-out preferences for disableImages and optin= cookies which were all server-side rendered as well.

Most of those have since been decom'ed but, I suggest checking with SRE Traffic whether we can introduce 1 variant for desktop to utilize here. That would be an even simpler patch than what you have drafted as it would instead mean the server just checks the cookie and adds the class to the existing bodyClass list in Vector skin class.

If this isn't feasible, then I suggest the following restrictions on the client-side approach:

  • No JSON parse at runtime. Instead, prefer a plain string.
  • No localStorage reads. Instead, prefer cookie which are already primed in memory.
  • No inline script in body or after stylesheets.
    • These break the browser's ability to parse the article concurrently with the stylesheet download, because inline scripts are spec'ed to be able to see document.styleSheets. Instead, place the inline script in the head, before the first stylesheet.
    • I suggest placing it in RL/Client, which is already reponsibile for setting the documentElement <html> class.
  • No elaborate JavaScript processing code beyond taking a cookie value and appending it after a space to className.
    • Avoids the untestable nature of the inline script, as you can test the JS method that stores the cookie instead, which will include all the processing logic there. The cookie is internal, direct setting isn't allowed.
  • No classList (too new) or try-catch (de-opt).
    • Use a simple className assignment instead. See the current inline script for reference.

You'll need to be prepared for changes in the future, where e.g. we're dealing with cached HTML interpreting newer-style cookie values, or new HTML reading old-style cookie values placed by long-running tabs. By making the contract as small as possible (cookie X becomes a class name) you eliminate pretty much all need for manual testing and maintenance/upgrade risk.

Latest patchset takes care of the feedback above (https://gerrit.wikimedia.org/r/c/mediawiki/core/+/881728). I'll be adding a follow up that moves from localStorage to cookies but I had a question about that: How would you suggest reading the cookie without $.cookie being available without using elaborate JS processing?

No elaborate JavaScript processing code beyond taking a cookie value and appending it after a space to className.

Could you expand on this? While the other points seem like restrictions we can work with. I think this one is problematic as we have -enabled and -disabled variants as we need different CSS for both versions and we need to set a default version of this on the body tag - for example in the case of the limited width, limiting width is the default, so the class would need to remove that class and add a new one. We considered enabled only classes a while back, but ran into issues with CSS specificity problems which is why we have specific classes for the 2 binary states.

Avoids the untestable nature of the inline script, as you can test the JS method that stores the cookie instead, which will include all the processing logic there. The cookie is internal, direct setting isn't allowed.

I'm sure we could make the code testable. For example, having a local version of it with unit tests would be one option, or adding the code to JavaScriptTest.

No classList (too new) or try-catch (de-opt).

Too new for which browsers? Note, this feature is not expected to work in grade C browsers. In fact, enabling it there might lead to unexpected (broken) results.
https://caniuse.com/classlist

I can modify this to use string replacement, but that seems like added JS complexity to me.

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

[mediawiki/core@master] ResourceLoader: Cookie based client preferences

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

How would you suggest reading the cookie without $.cookie […]?

document.cookie. Codesearch.

No elaborate JavaScript processing code beyond taking a cookie value and appending it after a space to className.

Could you expand on this? […] I think this one is problematic as we have [use cases and CSS limitations].

Afak none of that relates to the current task. As I understand it, this is an urgent task. I suggest de-scoping anything you don't need for the width toggle, as it requires more complexity than you have time and space to handle responsibly.

It seems like the highest complexity we might encounter, is that the default changes to fluid width, which could mess with a -disable cookie. To avoid that conflict, we can in the same deployment both change the default and remove/change the inline script. Scap deployments have been atomic by default for about a year, across both wmf-config and mediawiki repos.

Long-term, if this general direction is proven viable (it isn't yet). Then we can incorporate extra complexity and generalised capabilities, and think through how to manage that appropiately such that we only need to deal with a single string, yet enjoy all the developer experience benefits you seek. I'm confident that's very feasible. It might require a few minutes of brainstorming between various people to map out. For example, one might use an idle callback after the page renders, similar to how we do other housekeeping in VE, CN, RL, mw.storage, etc. The idle callback would look at the "current" skin defaults and perhaps invert or prune things that are no longer needed. None of this needs to happen now, however.

I note that Patch Set 2 doesn't just compute values that are constant, it also calls various functions and reads DOM properties that generally force a recalc. There is a reason that the current inline script is just a static assignmment. Writing to the DOM is cheap for things that have a naturally deferred impact on the next rendering. Reading the DOM, however, generally causes a forced style recalc.

No classList (too new) or try-catch (de-opt).

Too new for which browsers? Note, this feature is not expected to work in grade C […]

I can modify this to use string replacement, but that seems like added JS complexity to me.

What I meant is, it is unsafe to use unconditionally as otherwise the payload would crash. Thus to use classList, requires adding complexity, which is exactly what I found in Gerrit. Patch Set version 2 currently adds 20 lines of code with function calls, try-catch blocks, conditionals, loops, and inline comments.

I am not suggesting to add more complexity to meet the restrictions. I am suggesting to internalise the restrictions and look back at changing/reducing the code to meet the restrictions. For example, the 20 lines can be reduced to a single line that reads:

if ( /(^|; )mwclientpref=limited-width-disabled/.test( document.cookie ) ) {
  document.documentElement.className.replace( 'vector-feature-limited-width-enabled', 'vector-feature-limited-width-disabled' );
}

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

[mediawiki/core@master] ResourceLoader: Minimize client side preference code

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

How would you suggest reading the cookie without $.cookie […]?

Sorry I guess I wasn't clear here. I am aware of how to use document.cookie. However, what was not clear was whether using match and in particular regular expressions / string parsing would count as "elaborate JavaScript processing code". Your Gerrit review implies that it's not so nothing further needed here.

I note that Patch Set 2 doesn't just compute values that are constant, it also calls various functions and reads DOM properties that generally force a recalc. There is a reason that the current inline script is just a static assignmment. Writing to the DOM is cheap for things that have a naturally deferred impact on the next rendering. Reading the DOM, however, generally causes a forced style recalc.
I am not suggesting to add more complexity to meet the restrictions. I am suggesting to internalise the restrictions and look back at changing/reducing the code to meet the restrictions. For example, the 20 lines can be reduced to a single line that reads:

Okay that's great context. If replacing works, that definitely seems like a viable approach. We can also limit it to this single class. I'm not convinced this can be a single line however -we currently vary the class for logged in users, and need to cater for users who use the toggle, and then login. Does https://gerrit.wikimedia.org/r/c/mediawiki/core/+/882758 go far enough?

Test wiki created on Patch demo by Jdlrobson using patch(es) linked to this task:
https://patchdemo.wmflabs.org/wikis/d8e99ef814/w

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

[mediawiki/skins/Vector@master] Moves feature classes from BODY element to HTML element

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

Strongly recommend exploring server-side instead of client-side. Our CDN is already fragmented in at least two (m-dot vs canonical), with I believe a few subvariants already as well. We've previously fragmented mobile cache with its logged-out preferences for disableImages and optin= cookies which were all server-side rendered as well.

Most of those have since been decom'ed but, I suggest checking with SRE Traffic whether we can introduce 1 variant for desktop to utilize here. That would be an even simpler patch than what you have drafted as it would instead mean the server just checks the cookie and adds the class to the existing bodyClass list in Vector skin class.

To close the loop on the task here: SRE Traffic rejected the idea of doing this server-side, so we're now pursuing a client-side-only cookie solution.

If this isn't feasible, then I suggest the following restrictions on the client-side approach:

  • No JSON parse at runtime. Instead, prefer a plain string.
  • No localStorage reads. Instead, prefer cookie which are already primed in memory.

Just to confirm: https://wikitech.wikimedia.org/wiki/Cookies discourages client-side-only cookies, and favors localStorage instead. I'm assuming that that's valid as general advice, but that in this particular instance you're making the opposite recommendation because we're talking about accessing it in a performance-sensitive place, is that right?

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

[mediawiki/skins/Vector@wmf/1.40.0-wmf.19] Moves feature classes from BODY element to HTML element

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

Change 883220 abandoned by Jdlrobson:

[mediawiki/skins/Vector@wmf/1.40.0-wmf.19] Moves feature classes from BODY element to HTML element

Reason:

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

Most of those have since been decom'ed but, I suggest checking with SRE Traffic whether we can introduce 1 variant for desktop to utilize here. That would be an even simpler patch than what you have drafted as it would instead mean the server just checks the cookie and adds the class to the existing bodyClass list in Vector skin class.

To close the loop on the task here: SRE Traffic rejected the idea of doing this server-side, so we're now pursuing a client-side-only cookie solution.

It would be appreciated if a more detailed rationale could be provided than just "X said no" as this has come up a few different times on-wiki now; in https://en.wikipedia.org/w/index.php?title=Wikipedia_talk:Vector_2022&diff=prev&oldid=1134727611#%22Limitations_in_MediaWiki's_caching_system%22 @suffusion_of_yellow said:

Given the backlash from non-logged-in editors here, I think some "hard numbers" would go a long way. If you can say straight out "Desktop requires X terabtyes, mobile requires Y terabytes, and adding support for classic vector would require Z terabytes. We have X + Y < W < X + Y + Z capacity, and increasing storage would cost D dollars" would at least get us all on the same page. Right now all we have in the FAQ is something vague about "overloaded" servers.

I replied with "Someone on the SRE team could probably get you numbers. ..." - since the SRE team has considered this, a more detailed explanation would be helpful for the rest of us to communicate to others why this is infeasible or not practical or whatever.

Change 883267 merged by jenkins-bot:

[mediawiki/skins/Vector@master] Moves feature classes from BODY element to HTML element

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

Most of those have since been decom'ed but, I suggest checking with SRE Traffic whether we can introduce 1 variant for desktop to utilize here. That would be an even simpler patch than what you have drafted as it would instead mean the server just checks the cookie and adds the class to the existing bodyClass list in Vector skin class.

To close the loop on the task here: SRE Traffic rejected the idea of doing this server-side, so we're now pursuing a client-side-only cookie solution.

It would be appreciated if a more detailed rationale could be provided than just "X said no" as this has come up a few different times on-wiki now; in https://en.wikipedia.org/w/index.php?title=Wikipedia_talk:Vector_2022&diff=prev&oldid=1134727611#%22Limitations_in_MediaWiki's_caching_system%22 @suffusion_of_yellow said:

Given the backlash from non-logged-in editors here, I think some "hard numbers" would go a long way. If you can say straight out "Desktop requires X terabtyes, mobile requires Y terabytes, and adding support for classic vector would require Z terabytes. We have X + Y < W < X + Y + Z capacity, and increasing storage would cost D dollars" would at least get us all on the same page. Right now all we have in the FAQ is something vague about "overloaded" servers.

I replied with "Someone on the SRE team could probably get you numbers. ..." - since the SRE team has considered this, a more detailed explanation would be helpful for the rest of us to communicate to others why this is infeasible or not practical or whatever.

I was not in this meeting due to time zones, but pretty good notes were taken, so I'll answer based on that. I'll paraphrase what @BBlack said and he can supplement/correct me if needed. Caching anonymous page views is critical to the performance and resilience of the site. Splitting the cache based on a flag like this makes that cache less efficient, and if we expect this to be used for multiple features, it'd get exponentially worse, we'd have to split the cache 2^n ways. It doesn't make sense to split the cache for such a tiny change (just one CSS class name in the <html> tag). ESI or something like it might be able to do something like this, but we're not remotely ready to implement that now. Custom code at the edge cache could potentially be used, but is very fragile and complex.

All this considered we decided to go with a client-side-only cookie-based approach, rather than a server-side cookie-based approach. We'll set a cookie from JavaScript, and read it in render-blocking JavaScript. This is basically the originally proposed approach, but with "localStorage" replaced with "cookie" per @Krinkle's guidance.

Change 882758 abandoned by Krinkle:

[mediawiki/core@master] [For testing] Enable skin client preferences

Reason:

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

Change 882758 restored by Krinkle:

[mediawiki/core@master] [For testing] Enable skin client preferences

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

Change 883298 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/core@master] ResourceLoader: Follow-up to adding ResourceLoaderClientPreferences

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

Change 881728 merged by jenkins-bot:

[mediawiki/core@master] ResourceLoader: Basic client side user preferences

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

Change 882756 abandoned by Jdlrobson:

[mediawiki/core@master] ResourceLoader: localStorage based client preferences

Reason:

Abandoning for now.

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

Change 883298 merged by jenkins-bot:

[mediawiki/core@master] ResourceLoader: Follow-up to adding ResourceLoaderClientPreferences

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

Change 845667 merged by jenkins-bot:

[mediawiki/skins/Vector@master] Limited width made persistent for anonymous users

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

Jdlrobson added a subscriber: Edtadros.

Summarizing the plan from this morning.

  • We decided we will let this ride the train
  • On Tuesday we will configure MediaWiki.org and/or group 0 wikis to enable the feature flag. @nray will run tests.

I've captured this in https://phabricator.wikimedia.org/T327979.

@Edtadros we'd like to QA this on the beta cluster by end of day Monday using the usual process. Can you make that happen? (Skip QA in production for this one)

Jdlrobson updated the task description. (Show Details)
Jdlrobson added a subscriber: nray.

[…]

  • No localStorage reads. Instead, prefer cookie which are already primed in memory.

Just to confirm: https://wikitech.wikimedia.org/wiki/Cookies discourages client-side-only cookies, and favors localStorage instead. I'm assuming that that's valid as general advice […]

That's right. Thanks for connecting the dots! Indeed, our advice is and remains to generally avoid cookies when working within JavaScript, and favour localStorage. My above point does not stand as general advice. I do note that, the above page has only existed for a month and I wasn't aware of it. It does accurately reflect current consensus and recommendations though, so all good! (Thanks @matmarex for summarising it there!) Perhaps we can factor some of that into Performance/Guides, e.g. under the "Frontend performance practices" chapter.

The mere notion of JavaScript code on our platform before page render is effectively non-existent and intentionally made impossible. The concept was deprecated and subsequently removed in 2015 with T107399 (and related tasks). The ResourceLoader\ClientHtml class in MediaWiki controls the one singular <script> tag we ship, and it provides no extension interface footgun to break that. The declarative APIs like addModule and addJsConfigVar naturally take care of themselves through the module system. The code that controls this has good doc comments and code stewardship within ResourceLoader to make sure the right people get involved and have the relevant information to decide how to evolve it if if it comes up again.

As such, I'm fine risking a once-a-decade event where I put in a bit of extra interrupt time, compared to documenting more advice that will almost exclusively lead to confusion or misuse where neither party "benefits". (Both approahces are simple key-value string stores, if anything localStorage is simpler and more modern! The natural inclination here works to our advantage, and we don't want to discourage that.) That is to say, I think defaulting to localStorage here was natural, understandable, and desirable. No judgement on my part!

Jdlrobson renamed this task from Make max-width toggle persistent for anonymous users to Make sure limited width toggle persistent for anonymous users.Jan 26 2023, 6:14 PM
Jdlrobson updated the task description. (Show Details)
Sj renamed this task from Make sure limited width toggle persistent for anonymous users to Make sure limited width toggle is persistent for anonymous users.Jan 27 2023, 6:33 AM

Test Result - Beta

Status: ✅ PASS
Environment: beta
OS: macOS Ventura
Browser: Chrome
Device: MBP
Emulated Device:NA

Test Artifact(s):

QA Steps

As anonymous user open any page in Vector 2022 skin
Hit the toggle button in the bottom right to disable the limited width
refresh page
✅ AC1: limited width remains disabled

Screen Recording 2023-01-28 at 4.46.19 PM.mov.gif (706×1 px, 1 MB)

This can skip QA in production. That will be done as part of T327979.

Edtadros updated the task description. (Show Details)

Test wiki on Patch demo by Jdlrobson using patch(es) linked to this task was deleted:

https://patchdemo.wmflabs.org/wikis/60b297219d/w/

Test wiki on Patch demo by Jdlrobson using patch(es) linked to this task was deleted:

https://patchdemo.wmflabs.org/wikis/d8e99ef814/w/