Page MenuHomePhabricator

Add a polyfill for IntersectionObserver to ResourceLoader
Closed, ResolvedPublic

Description

T300666 proposes widening the criteria we use to determine whether a browser is "ES6-compatible" (currently Safari and mobile Safari prior to version 14 are excluded from those criteria, meaning a large number of users can no longer access MediaSearch (T300094) or the Suggested Tags page (T299253).

Whether or not we support older versions of Safari has special implications because when it comes to iOS devices, users cannot install other browsers and thus have no way of accessing features designated as ES6-only. For truly outdated versions of Safari (such as v9) this is unavoidable, but for more recent versions that mostly support modern JS, we can hopefully do better.

Safari is mostly-ES6 compatible from 11.1 onwards (see this table). But the one feature we are using in some of our Vue apps that is not supported this far back is IntersectionObserver.

Introducing a polyfill for this feature (specifically, the W3C polyfill here) would allow us to restore some functionality for Safari users while still allowing us to rely on the (IMO quite useful) IntersectionObserver API.

If we use a skipFunction to load this polyfill, then users of more modern browsers should not be burdened by the additional code.

Event Timeline

Change 759567 had a related patch set uploaded (by Eric Gardner; author: Eric Gardner):

[mediawiki/core@master] Add the W3C Intersection Observer to ResourceLoader

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

Sounds good to me. I suggest taking an approach similar to dom2 and es6-polyfils in the past, that is register the module as a named more generically than one API. The W3C specs no longer roll-up into HTML versions like they used to, but we could call it something like web2017 based on when two major browsers released their implementations for the Web APIs in question, and bundle other polyfills that we need up to that point as well, behind a shared skipFunction.

This strikes a balance between loading exactly what's needed for one particular browser and page view, loading polyfills that you can cache and are likely to need at some point, and not loading more than is needed for this browser. In general I've found over the years that a small amount of (theoretically) unused code can go a long way to optimising the big picture (faster load times by avoiding network roundtrips with the browser already having preloaded and cached what you need from a previous interaction, reducing module registry size, simpler dependency management, reducing chances of missing a related polyfill).

Yet, it's not as generic as "web-polyfills" which would likely grow larger over time and eventually result in everything depending on it even when most extensions no longer need the polyfils it once used it for. web2017 we can grep for and remove when the Grade A marker goes past that point.

fetch-polyfill could probably be rolled up into this as well, which I believe also exists mainly for IE11 and Safari 9.

Krinkle triaged this task as Medium priority.Feb 3 2022, 7:27 PM
Krinkle moved this task from Inbox to Accepted Enhancement on the MediaWiki-ResourceLoader board.

Sounds good to me. I suggest taking an approach similar to dom2 and es6-polyfils in the past, that is register the module as a named more generically than one API. The W3C specs no longer roll-up into HTML versions like they used to, but we could call it something like web2017 based on when two major browsers released their implementations for the Web APIs in question, and bundle other polyfills that we need up to that point as well, behind a shared skipFunction.
...
fetch-polyfill could probably be rolled up into this as well, which I believe also exists mainly for IE11 and Safari 9.

I like this idea. One thing worth considering is: how far back in browser versions is it worth going if we want to support an ES6 environment with polyfillls? Our original ES6 criteria were too strict and excluded Safari 11.1, 12, and 13. Now we are widening the criteria but have to polyfill IntersectionObserver to ensure a consistent environment. We have fetch and promise polyfills – would it make sense to further widen our "ES6-compatible" criteria to include browsers that don't support these features natively but can get support via a polyfill.

Proxy Objects are an important feature for us (Vue 3's reactivity model relies on them), but there is a polyfill available for this as well. I guess the syntax changes (let, const, () =>, template literals, etc) will be the hardest to polyfill.

Maybe our strategy should be something like the following:

  1. Test for basic ES6 compatibility by using new Function( '(a = 0) => a' );
  2. If a browser is capable of running that code, ensure that the following features are present (via skipFunction) or provide them via polyfill: promise, fetch, Proxy Object, Intersection Observer, modern array methods, etc.
  3. Encourage developers to write new code against this baseline (avoiding newer features like ES2017+ stuff) and update linters to help

Just trying to think of ways we can deliver the most modern code to the widest range of users, without transpiling.

I would also caution against looking too much at older Safari versions. Safari 15, currently is supported on devices from September 2015 (iPhone 6s) and onwards... iOS 12 supports devices from September 2013 onward (iPhone 5s / also IE11 release date, 8,5 years ago, last security fix late 2019). That's a LOT of supported devices and as you can see from our statistics (2nd graph, select Mobile Safari) usage from before 12.5 is minimal. I suspect that is because older versions don't support our TLS requirements, as iOS 10/Safari 10.3 is more popular globally than for us (it was the last to support iPhone 5 and a lot of older iPads).

There is an obvious chance that by the time our Vue stuff really starts hitting large amounts of users, these users simply won't be using an iPhone/iPad from 2013-2015 any longer either (not because of us, but because they can't do banking any longer, and most other websites won't work either). So I'd say going through any sort of trouble, supporting anything before 12.5 is simply not worth it as long as we safely degrade in functionality (with proper CSS) and from before iOS 14, not for as long as their is a somewhat solid fallback.

So while we claim Safari 9 in our matrix..

  1. I don't think that is useful
  2. I don't think we actually DO because of TLS
  3. I don't think anyone tests this even sporadically

So while we claim Safari 9 in our matrix..

  1. I don't think that is useful
  2. I don't think we actually DO because of TLS
  3. I don't think anyone tests this even sporadically

Strongly agree with this – we should revisit the compatibility table soon and demote some of the really old browsers to Grade C support. The Product department started talking about this early last year in regards to Internet Explorer – the idea being that new features should only support IE11 and other non-ES6 browsers through a no-JS fallback if at all. But the conversation has stalled since then.

I'd like to go one step further at this point and see what it would take to officially downgrade all non-ES6 browsers to Grade C per T178356 – if necessary making an exception for mission-critical tools like Visual Editor.

According to the "All Sites by Browser" stats in Dashiki (traffic stats for Wikipedias, not sure about Commons), we're looking at the following percentages:

  • Safari 12 : <0.1%
  • Safari 13: 0.29%
  • Mobile Safari 9: <0.1%
  • Mobile Safari 10: <0.1%
  • Mobile Safari 11: <0.1%
  • Mobile Safari 12: 0.6%
  • Mobile Safari 13: 0.43%
  • IE: 0.46%

All together this represents something like 2% of web traffic these days.

Change 759567 merged by jenkins-bot:

[mediawiki/core@master] Add the W3C Intersection Observer to ResourceLoader

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

@egardner Could you rename the module to web2017-polyfill or some such? (and update usage before it goes to production).

I'd like to add the URL constructor for use by Editing team (T103379) and would prefer we not keep increasing startup module and bundle entrypoints for all of these. I can add url to the same bundle that way. I'll also work on deprecating fetch-polyfill and moving that to this.

Regarding es6-polyfill, I think we should treat JS engine APIs and Web APIs separately for now. They're large enough to be worth loading separately (I'll run some more tets to confim this), and it also seems useful to be thinking more actively about the ES environment one can assume (API-wise that is, not syntax-wise). Note that upstream ESLint handles its parser version for syntax and globals preset for API references separately to accomodate this.

@egardner Could you rename the module to web2017-polyfill or some such? (and update usage before it goes to production).

I've started that task here: T301489. Let me know if any other features ought to be added. I'll post a patch that replaces the IntersectionObserver polyfill module entirely. We could do the same with fetch but I'm not sure how widely it is used at this point.