From 3ae9ad57fcae7c5767f8dd93a96d393b30807047 Mon Sep 17 00:00:00 2001 From: Toad06 Date: Wed, 30 Dec 2020 20:41:34 +0100 Subject: [PATCH] web: Prevent crashes when native `Window` is overridden --- web/packages/core/src/js-polyfills.ts | 58 ++++++++++++++++++++++++++- web/packages/core/src/load-ruffle.ts | 13 ++---- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/web/packages/core/src/js-polyfills.ts b/web/packages/core/src/js-polyfills.ts index 323f0ddfb..48e805e05 100644 --- a/web/packages/core/src/js-polyfills.ts +++ b/web/packages/core/src/js-polyfills.ts @@ -1,4 +1,5 @@ /* eslint @typescript-eslint/no-explicit-any: "off" */ +/* eslint @typescript-eslint/ban-types: "off" */ declare global { interface Window { @@ -14,7 +15,7 @@ declare global { * https://tc39.github.io/ecma262/#sec-array.prototype.reduce * */ -export function setArrayPrototypeReduce(): any { +function polyfillArrayPrototypeReduce(): any { Object.defineProperty(Array.prototype, "reduce", { value: function (...args: any) { 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(); + } +} diff --git a/web/packages/core/src/load-ruffle.ts b/web/packages/core/src/load-ruffle.ts index c37461a3d..0ff4eccb8 100644 --- a/web/packages/core/src/load-ruffle.ts +++ b/web/packages/core/src/load-ruffle.ts @@ -6,7 +6,7 @@ import { Ruffle } from "../pkg/ruffle_web"; -import { setArrayPrototypeReduce } from "./js-polyfills"; +import { setPolyfillsOnLoad } from "./js-polyfills"; /** * Load ruffle from an automatically-detected location. @@ -19,14 +19,9 @@ import { setArrayPrototypeReduce } from "./js-polyfills"; * instances. */ async function fetchRuffle(): Promise<{ new (...args: any[]): Ruffle }> { - if ( - typeof Array.prototype.reduce !== "function" || - Array.prototype.reduce.toString().indexOf("[native code]") === -1 - ) { - // 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(); - } + // Apply some pure JavaScript polyfills to prevent conflicts with external + // libraries, if needed. + setPolyfillsOnLoad(); try { // If ruffleRuntimePath is defined then we are executing inside the extension