web: Prevent crashes when native `Window` is overridden

This commit is contained in:
Toad06 2020-12-30 20:41:34 +01:00 committed by Mike Welsh
parent 4f980becb2
commit 3ae9ad57fc
2 changed files with 61 additions and 10 deletions

View File

@ -1,4 +1,5 @@
/* eslint @typescript-eslint/no-explicit-any: "off" */ /* eslint @typescript-eslint/no-explicit-any: "off" */
/* eslint @typescript-eslint/ban-types: "off" */
declare global { declare global {
interface Window { interface Window {
@ -14,7 +15,7 @@ declare global {
* https://tc39.github.io/ecma262/#sec-array.prototype.reduce * https://tc39.github.io/ecma262/#sec-array.prototype.reduce
* *
*/ */
export function setArrayPrototypeReduce(): any { function polyfillArrayPrototypeReduce(): any {
Object.defineProperty(Array.prototype, "reduce", { Object.defineProperty(Array.prototype, "reduce", {
value: function (...args: any) { value: function (...args: any) {
if ( if (
@ -67,3 +68,58 @@ export function setArrayPrototypeReduce(): any {
}, },
}); });
} }
/**
* Polyfills the `Window` function.
*
*/
function polyfillWindow(): void {
if (
typeof window.constructor !== "function" ||
!isNativeFunction(window.constructor)
) {
// Don't polyfill `Window` if `window.constructor` has been overridden.
return;
}
// @ts-expect-error: `Function not assignable to { new (): Window; prototype: Window; }`
window.Window = window.constructor;
}
/**
* Determines whether a function is native or not.
*
* @param func The function to test.
* @returns True if the function hasn't been overridden.
*/
function isNativeFunction(func: Function): boolean {
const val =
typeof Function.prototype.toString === "function"
? Function.prototype.toString()
: null;
if (typeof val === "string" && val.indexOf("[native code]") >= 0) {
return (
Function.prototype.toString.call(func).indexOf("[native code]") >= 0
);
}
return false;
}
/**
* Checks and applies the polyfills to the current window, if needed.
*
*/
export function setPolyfillsOnLoad(): void {
if (
typeof Array.prototype.reduce !== "function" ||
!isNativeFunction(Array.prototype.reduce)
) {
// Some external libraries override the `Array.prototype.reduce` method in a way
// that causes Webpack to crash (#1507, #1865), so we need to override it again.
polyfillArrayPrototypeReduce();
}
if (typeof Window !== "function" || !isNativeFunction(Window)) {
// Overriding the native `Window` function causes issues in wasm-bindgen, as a
// code like `window instanceof Window` will no longer work.
polyfillWindow();
}
}

View File

@ -6,7 +6,7 @@
import { Ruffle } from "../pkg/ruffle_web"; import { Ruffle } from "../pkg/ruffle_web";
import { setArrayPrototypeReduce } from "./js-polyfills"; import { setPolyfillsOnLoad } from "./js-polyfills";
/** /**
* Load ruffle from an automatically-detected location. * Load ruffle from an automatically-detected location.
@ -19,14 +19,9 @@ import { setArrayPrototypeReduce } from "./js-polyfills";
* instances. * instances.
*/ */
async function fetchRuffle(): Promise<{ new (...args: any[]): Ruffle }> { async function fetchRuffle(): Promise<{ new (...args: any[]): Ruffle }> {
if ( // Apply some pure JavaScript polyfills to prevent conflicts with external
typeof Array.prototype.reduce !== "function" || // libraries, if needed.
Array.prototype.reduce.toString().indexOf("[native code]") === -1 setPolyfillsOnLoad();
) {
// Some external libraries override the `Array.prototype.reduce` method in a way
// that causes Webpack to crash (#1507, #1865), so we need to override it again.
setArrayPrototypeReduce();
}
try { try {
// If ruffleRuntimePath is defined then we are executing inside the extension // If ruffleRuntimePath is defined then we are executing inside the extension