Skip to content

Instantly share code, notes, and snippets.

@CertainPerformance
Created March 11, 2019 10:48
Show Gist options
  • Save CertainPerformance/68d3f2f675b477b3b762a17adfb6b579 to your computer and use it in GitHub Desktop.
Save CertainPerformance/68d3f2f675b477b3b762a17adfb6b579 to your computer and use it in GitHub Desktop.
// For reference:
// https://stackoverflow.com/questions/54776759/how-to-avoid-accidentally-implicitly-referring-to-properties-on-the-global-objec
const fakeWindow = (() => {
// Declare Object and ReferenceError as variable names in this scope
// since implicit references to them on window will eventually fail during the below loop
const { Object, ReferenceError } = window;
const fakeWindow = Object.create(Object.getPrototypeOf(window));
const descriptors = Object.getOwnPropertyNames(window)
.reduce((a, propName) => {
a[propName] = Object.getOwnPropertyDescriptor(window, propName);
return a;
}, {});
let current = window;
while (current !== null) {
Object.getOwnPropertyNames(current).forEach((propName) => {
const windowDescriptor = descriptors[propName];
// Keep a reference to the current values on window, so they can be accessed through fakeWindow later:
if (current === window) {
Object.defineProperty(fakeWindow, propName, windowDescriptor);
// Some properties on window are non-configurable and cannot be redefined
// such as Infinity, NaN, undefined... and unfortunately, location
// location could be removed by running in a worker scope instead, like in other answer
if (windowDescriptor && !windowDescriptor.configurable) {
return;
}
}
// constructor property cannot be redefined, and console must stay so that throwing works
// (`throw` internally calls `window.console`)
const keepProps = ['constructor', 'console'];
if (keepProps.includes(propName)) {
return;
}
// Set *all* properties anywhere on the prototype chain directly onto window
// so that implicit references to window properties will fail,
// but so will implicit references to anything on the prototypes, like __defineGetter__
const doThrow = () => {
throw new ReferenceError(propName + ' is not defined');
};
Object.defineProperty(window, propName, { get: doThrow, set: doThrow });
});
current = Object.getPrototypeOf(current);
}
return fakeWindow;
})();
(() => {
// The actual original window instance doesn't have accessible properties
// but we can access the values originally on the instance via fakeWindow
// and the values on the prototype via Object.getPrototypeOf(fakeWindow.window)
const window = new fakeWindow.Proxy(
{},
{
getDescriptor(propName) {
const { Object } = fakeWindow;
let descriptor = Object.getOwnPropertyDescriptor(fakeWindow, propName);
let current = Object.getPrototypeOf(fakeWindow.window);
while (!descriptor && current !== null) {
descriptor = Object.getOwnPropertyDescriptor(current, propName);
current = Object.getPrototypeOf(current);
}
return descriptor;
},
get(_, propName) {
const descriptor = this.getDescriptor(propName);
if (!descriptor) {
return;
}
const { get, value } = descriptor;
if (get) {
return get.call(fakeWindow.window);
}
return typeof value !== 'function'
? value
: function(...args) {
// if this was called with a calling context of the *proxy*, call it with a calling context of the true window
// else, call with the default calling context
return value.apply(this === window ? fakeWindow.window : this, args);
};
},
set(_, propName, newVal) {
const directDescriptor = fakeWindow.Object.getOwnPropertyDescriptor(fakeWindow, propName);
if (directDescriptor) {
const { set } = directDescriptor || {};
return set
? set.call(fakeWindow.window, newVal)
: fakeWindow[propName] = newVal;
}
const { set } = this.getDescriptor(propName) || {};
return set
? set.call(fakeWindow.window, newVal)
: fakeWindow[propName] = newVal;
}
}
);
// Implicitly referencing or assigning to a window property will throw:
try {
name = 3; // would refer to window.name normally
} catch(e) { console.log('err', e) }
try {
const foo = name; // would refer to window.name normally
} catch(e) { console.log('err', e) }
// Can assign and retrieve properties
window.foo = 'foo';
window.console.log(window.foo);
// Can invoke setter (with the proper calling context)
window.onclick = () => window.console.log('click');
// and getter
window.console.log('getting onclick', window.onclick);
// Can .call
window.addEventListener.call(document.querySelector('div'), 'click', () => window.console.log('div clicked'));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment