Page MenuHomePhabricator

Add a timeout to startup.js that re-enables client-nojs (Grade C interface)
Open, LowPublic

Description

Our current startup pipeline currently resembles a model that, while independently evolved, is now known in the industry as "Cutting the mustard" (coined by BBC News developer, Tom Maslen). Ref: http://responsivenews.co.uk/post/18948466399/cutting-the-mustard

  1. Page HTML sets <html class="client-nojs">.
    • Page HTML optimistically sets class to client-js from a small, synchronous, inline script, which runs from the <head> before stylesheets and main content, thus before rendering.
  2. Page HTML loads startup.js using <script async src="..">. This is asynchronous.
    • The startup.js scripts performs a feature test to decide whether the current browser supports our idea of a modern browsers, and based on that either continues to initialise the rest of the JavaScript-based enhancements (Grade A), or decides to gracefully fallback to a more basic experience in which we don't even attempt to load any further JavaScript (Grade C).

We use the client-nojs and client-js classes to dynamically vary our content and interfaces with the foresight of knowing whether or not the JS-enabled enhancements will be loaded. For example, by having the server render some of the will-be interactive interfaces so that users see them immediately without delay.

Our current approach applies Grade C both to modern browsers in which javascript is disabled, as well as older browsers that understand basic JavaScript but do not support the capabilities our current features require.

Our current approach also optimises for what we believe to be the most common case: a modern browser that has js enabled (by setting the class name to client-js optimistically without first showing the fallback experience, and without waiting for the async payload).

This comes at the cost of older browsers with JS-enabled sometimes seeing js-related interfaces until the async startup.js payload arrives, which toggles off those styles. This was a trade-off we deemed worth it.

output.html
<!DOCTYPE html>
<html class="client-nojs" lang="en" dir="ltr">
<head>
<meta charset="UTF-8"/>
<title>Wikipedia, the free encyclopedia</title>
<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>
startup.js
if ( !isCompatible() ) {
	// Undo class swapping in case of an unsupported browser. [..]
	document.documentElement.className = document.documentElement.className
		.replace( /(^|\s)client-js(\s|$)/, '$1client-nojs$2' );
	// [..]
	return;
}

// [..]
// Load remainder of code

This seems good, but after reading Enhancing Optimistically on the Filament Group blog, I realised that if the async script fails to load, the interface will be stuck on Grade A, despite those interfaces never coming to life.

This can happen due to intermittent network issues, or just because it is taking a very long (potentially infinite) amount of time.

Example code from the blog post:

enhancing-optimistically/final-snippet.js
if ( .. ) {
  // Let's enhance optimistically...
  addClass();

  // load enhanced JS file
  var script = loadJS( enhancedScriptPath );

  // if script hasn't loaded after 8 seconds, remove the enhanced class
  var fallback = setTimeout( removeClass, 8000 );

  // when the script loads, clear the timer out and add the class again just in case
  script.onload = function(){
    // clear the fallback timer
    clearTimeout( fallback ); 
    // add this class, just in case it was removed already (we can't cancel this request so it might arrive any time)
    addClass();
  };
}

We should do this :)

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript
Krinkle triaged this task as Medium priority.Feb 27 2018, 4:53 AM
Krinkle moved this task from Inbox to Accepted Enhancement on the MediaWiki-ResourceLoader board.

What happens if the async script is really slow to download but eventually runs? Stick to the no-js experience or let it make the page dynamic again?

The proposed snippet in the task description (as well as what Filament Group does), would switch once more to the dynamic styles.

Decision tree:

  • JS disabled: Grade C (final: 0 changes).
  • JS enabled: Grade A (optimistic, and start async script).
    • Async script:
      • Browser incompatible: Switch from Grade A to Grade C (final: 1 change, from A-optimistic to C-incompatible).
      • Browser compatible: Start base module fetch, and start timer.
        • Script completes before timer: No change, stay Grade A (final: 0 changes).
        • Timer completes before script: Switch from Grade A to Grade C.
          • If script never completes than Grade C is final (1 change, from A-optimistic to C-timed-out).
          • If script does eventually complete, switch back to Grade A (2 changes, from A-optimistic to C-timed-out to A-arrived).

We could consider whether we want that to happen, but if we do that, it will be considerably more work than just the setting of the class. Once the JavaScript has arrived, it will start doing things on the assumption that the page is in Grade A mode. If we were to not switch to Grade A mode, we'll have to introduce some sort of late check in mediawiki.js that will actively reject the payload if it arrives late (there's no aborting mechanism, so we have to set a boolean flag somewhere, and then make mw.loader.implement reject new code after that).

Moving to future goals because it requires research and design input, it's not immediately actionable as small task for developers.

Krinkle lowered the priority of this task from Medium to Low.Jun 6 2019, 9:54 AM

Lowering priority. In this general area, T183720 is more important.