How to polyfill `globalThis` into older browsers?
A number of us using browsers other than the latest Chromium or Firefox browsers noticed in the last quarter of 2021 that a lot of things on the web started breaking within a few weeks of each other. It quickly became apparent that it wasn't something isolated, but something much larger going on. All of the issues seem to be relatively new JavaScript being used by sites that isn't compatible with older or non-Chromium/Firefox browsers, and their presence in many libraries is probably what made the issue pop up everywhere at once.
As a case in point, there are no browsers that I use that support things on many websites which used to work. That is, this is a case of websites that used to work perfectly fine but because some software devs were bored and decided to replace perfectly compatible JS with less-compatible JS, no longer do.
As examples, Iron (Chromium) 70 no longer works with many things, and Pale Moon / New Moon 28 no longer work with many things. Some things don't work with either of them and some only don't work with either.
StackOverflow.com has recently joined this bleeding edge JS bandwagon: I can no longer vote on or comment on anything in Iron 70, and posting questions is half-broken, though it all still works (for now) in New Moon 28. Here are some of the JS errors I get in the Developer Tools console when this page loads:
Uncaught ReferenceError: globalThis is not defined
at stub.en.js?v=05770adb5484:1
at stub.en.js?v=05770adb5484:1
at stub.en.js?v=05770adb5484:1
how-do-i-escape-only-single-quotes:45 Uncaught TypeError: StackExchange.ready is not a function
at how-do-i-escape-only-single-quotes:45
how-do-i-escape-only-single-quotes:79 Uncaught TypeError: StackExchange.ready is not a function
at how-do-i-escape-only-single-quotes:79
how-do-i-escape-only-single-quotes:87 Uncaught TypeError: StackExchange.init is not a function
at how-do-i-escape-only-single-quotes:87
how-do-i-escape-only-single-quotes:90 Uncaught TypeError: Cannot read property 'setCacheBreakers' of undefined
at how-do-i-escape-only-single-quotes:90
how-do-i-escape-only-single-quotes:519 Uncaught TypeError: StackExchange.ready is not a function
at how-do-i-escape-only-single-quotes:519
how-do-i-escape-only-single-quotes:3598 Uncaught TypeError: StackExchange.ifUsing is not a function
at how-do-i-escape-only-single-quotes:3598
how-do-i-escape-only-single-quotes:3609 Uncaught TypeError: StackExchange.ready is not a function
at how-do-i-escape-only-single-quotes:3609
how-do-i-escape-only-single-quotes:4137 Uncaught TypeError: StackExchange.ready is not a function
at how-do-i-escape-only-single-quotes:4137
how-do-i-escape-only-single-quotes:4181 Uncaught TypeError: StackExchange.ready is not a function
at how-do-i-escape-only-single-quotes:4181
how-do-i-escape-only-single-quotes:4294 Uncaught TypeError: StackExchange.ready is not a function
at how-do-i-escape-only-single-quotes:4294
how-do-i-escape-only-single-quotes:4306 Uncaught TypeError: StackExchange.ready is not a function
at how-do-i-escape-only-single-quotes:4306
Yeah, I know this is a 3+ year old browser, but the fact that it used to work fine, until all this JavaScript got replaced, makes me think that if I hack around with the code enough, maybe I can get things to work again. In any case, it's an interesting exercise I'm trying to see if it works. (And even though Iron 70 is 3+ years old, recent Pale Moon/New Moon are not brand new, and are affected by many of the same compatibility issues, so for the sake of a more open web that's more widely browser compatible, I'm trying to figure this out.) So, rather than complain about this, I'm trying to see how much of these compatibility issues can be reversed or fixed with polyfills. A lot of people have run into this same issue and so some kind of resolution to this would be really nice.
The first error in the list is easy to see what the issue: globalThis
wasn't introduced in Chromium until version 71, exactly the version after the one I happen to be using. However, there are a number of polyfills out there for globalThis
, to add support to older browsers. So in theory, it seems like I should be able to polyfill support for this into Chromium 70.
At first, I took a look at the core-js
polyfill, but realized that was all way more than I really needed for a proof of concept.
I ended up trying to use the polyfill here: https://mathiasbynens.be/demo/globalthis.mjs
The polyfill itself is explained quite well here: https://mathiasbynens.be/notes/globalthis
To test this out, I went ahead and put together a simple Chrome extension, consisting of the following:
manifest.json
:
{
"manifest_version": 2,
"name": "My Extension",
"version": "0.1",
"description": "My Description",
"author": "Me",
"permissions": ["https://stackoverflow.com/*"],
"content_scripts": [{
"matches": ["https://stackoverflow.com/*"],
"js": ["corejsmin.js"],
"run_at": "document_start",
"all_frames": true
}
]
}
corejsmin.js:
// https://mathiasbynens.be/notes/globalthis
// The polyfill starts here.
(function() {
if (typeof globalThis === 'object') return;
Object.defineProperty(Object.prototype, '__magic__', {
get: function() {
return this;
},
configurable: true
});
__magic__.globalThis = __magic__;
delete Object.prototype.__magic__;
}());
// The polyfill ends here.
console.log(String(globalThis));
The polyfill seems to work, to some extent, because in the Developer Tools console, I do see the following printed out, before any of the page JS errors:
[object Window]
Yet, the errors persist - specifically here, the globalThis
error remains and is unchanged.
I imagined that by injecting this JavaScript into the page before any of the JS on it executes, I would be able to polyfill globalThis
so that when it is referenced, in this case in StackOverflow's JS code, it'll work properly. Yet, it doesn't seem to, even though it appears to work fine in the extension code.
What am I missing here? Why is globalThis
defined before the page loads, but not in the Stack Overflow script? Ultimately, this seems like probably the simplest of the errors I'm seeing to address, but something here isn't quite right and I don't think there's any hope for this method if this can't be gotten to work. Is there something about this approach that wouldn't work, and is a different method of injection needed here?
Solution 1:
Content script's JS environment is isolated from the page so changes to objects or prototypes do not affect the page scripts.
In Chromium-based browsers you need to run the code inside a script element (more info):
document.documentElement.appendChild(
Object.assign(document.createElement('script'), {
textContent: 'window.globalThis = window',
})
).remove();
In Firefox-based browsers you can use wrappedJSObject:
wrappedJSObject.globalThis = wrappedJSObject;