## Preamble
The convention for MediaWiki features is to not depend on JavaScript for critical user actions. For various reasons, this means the initial page render must not depend on blocking JavaScript.
1. Performance: Things render a lot faster if you don't wait for blocking JS to download, parse and execute before anything useful can be shown.
2. Availability: Minimise risk of failure by being able to recover if the JS payload fails to download, parse or execute for any reason.
3. Reach: With the limited (human) resources we have, we can't support all historical versions and variations of JavaScript engines in web browsers. For that reason, we use a capability test in our JavaScript environment that decides early whether to continue with or without the JavaScript payload. This means we can still reach users of browsers we can't currently support the JavaScript engine of. (Without errors)
See also <https://www.mediawiki.org/wiki/Compatibility>
In summary, our pages typically load without dependency on any JavaScript for the first render, and without depending on JavaScript for critical user actions. Which means the most important user actions (e.g. search, create account, login, editing, preferences, etc.) are available via regular anchor links and HTML forms, that are handled by the server.
From an asynchronous request, we also try to initialise Grade A ("the JavaScript environment") in supported browsers. But even in supported browsers, the experience will de-facto fall back to Grade C if this fails.
## Problem
In the common case of Grade A being supported and initialising without issue, there is typically a considerable amount of time spent waiting, at which point the user is sometimes unable to interact with the page (despite it being visible). This problem can manifest in a number of different ways. The exact problem varies based on how closely related the no-js and js experiences are.
(**TODO:** //Enumerate examples of current problems//)
* {T183624}
* Clicking the watch/unwatch star too fast shows [a confirmation page](https://en.wikipedia.org/w/index.php?title=Example&action=watch) users don't expect.
## Goal
### Avoid
Where possible, **avoid waiting time**. Most likely by avoiding JavaScript for the initial render, e.g. by rendering the interactive widget server-side and sending only HTML/CSS. If needed, use the `.client-js` or `.client-nojs` selectors to hide widgets from the no-js experience.
### Minimise
**Minimise waiting time**. For example:
* By reducing the JS payload size.
* By reducing number of network roundtrips (e.g. avoid indirect discovery or lazy-load, or supplement with preloading).
* Avoiding idle waiting for an event like "document ready" or "window load".
Wait time to document-ready can be avoided by using delegate event handlers on a parent element that always exists. For example:
```lang=js,counterexample,name=slow.js
(function () {
// Wait for document-ready (aka "DOMContentLoaded" or "document.readyState=interactive")
$(function () {
// Synchronously query the document and build a node list.
// Iterate over each of the found elements and attach an event handler to each element
$('.mw-stuff').on('click', mw.stuff.handle);
});
}());
```
```lang=js,name=faster.js
(function () {
// Immediately attach delegate event handler to <body>
$(document.body).on('click', '.mw-stuff', mw.stuff.handle);
}());
```
### Improve
While avoiding is sometimes possible, and minimising can sometimes result in a wait time short enough to not need communication, there will still be plenty of cases where we need to **improve the wait experience**. This can take form in a number of ways:
* Clearly **communicate non-interactive state**. We should avoid a situation where the user wrongly believes something can be interacted with, or at least once they try, they should know that no interaction took place. For example:
* By being visually muted (greyed out, or reduced opacity).
* Marked as disabled (disabled, aria-disabled).
* Through explicit negative response when hovering or touching (e.g. `cursor: not-allowed`) or absence of any positive response (e.g. `pointer-events: none`, no hover/focus state appears).
* Provide a **fallback while waiting**. Depending on how different the no-js and js-only interactions are, it might make sense not to disable the js-only button, but instead have the no-js button be visible and enabled during the wait. A good example of this is the "Add to watchlist" button ("Watch star" in Vector skin). If clicked before js loads, it loads the action=watch form. Once js is loaded and initialised, it becomes an AJAX button instead. Either way it works, there is no disabled or waiting state.
* **Capture early interactions** and provide a "pending" experience.
The last point, "Capture early interactions", is something we do not currently do, but I have been thinking about it for a long time. It might be interesting to provide a generic way in MediaWiki to capture interactions as early as possible (with a small amount of blocking JavaScript that registers a delegate event handler for click/focus etc.), and immediately acknowledge it to the user in a generic way, and once the JS pipeline has initialised, forward the event accordingly.
For example, we could do something like this:
* (Like now) Server outputs JS-only button with `.client-js` styles, hidden for `.client-nojs`.
* (Different) Instead of showing it as disabled until the JS is ready, show it as normal always.
* New:
* Page output has a generic inline script that listens for clicks on any element with a special attribute. And when clicked, does nothing, except add a class name.
* The component can immediately respond to (acknowledge) the click, from the stylesheet, with styles for the added class name.
* The component's JS payload will retroactive handle events by interacting with the embedded script in some way (e.g. via `mw.hook` or something else).
```lang=html,name=page.html
<html>
<body>
<script>{{ inline/script.js }}</script>
..
<button class="mw-thing" data-mw="capture">..</button>
```
```lang=js,name=inline/script.js
var captureQueue = [];
document.body.addEventListener('click', function (e) {
if (e.target.getAttribute('data-mw') === 'capture') {
e.target.className += ' mw-captured-pending'; // Add class
captureQueue.push( e ); // Enqueue for later
e.preventDefault();
}
});
```
```lang=js,name=async/stuff.js
mw.stuff = {
handle: function (..) { .. }
};
mw.capture('.mw-thing', 'click', function (e) {
mw.stuff.handle(e.target);
});
```
This can has the benefit of allowing the user to **start interaction immediately**, whilst the rest is being prepared behind the scenes. It also reduces the number of perceived stages of page loading by eliminating the intermediary state where the interface is visible but intentionally disabled.
## Proposed solutions
(None at the moment, please suggest ideas below.)