medikoo / es5-ext

ECMAScript extensions (with respect to upcoming ECMAScript features)
ISC License
168 stars 81 forks source link

global.js throws an uncaught error in environment where preventExtensions() called on Object.prototype #92

Closed jdatskuid closed 4 years ago

jdatskuid commented 4 years ago

Some frameworks (such as Salesforce Lightning), in an attempt to ensure components from disparate vendors work nicely together, have locked down Object.prototype to prevent scope leakage and conflicting behavior.

This means that this neat little solution to retrieving the global throws an uncaught "TypeError":

Object.defineProperty(Object.prototype, "__global__", {
    get: function () { return this; },
    configurable: true
});
try { return __global__; }
finally { delete Object.prototype.__global__; }

To replicate the error, simply open a new tab in your browser of choice and run the following in your developer console:

// Set by the framework:
Object.preventExtensions(Object.prototype);
// Attempting to access the global:
Object.defineProperty(Object.prototype, "__global__", {
    get: function () { return this; },
    configurable: true
});

I would like to propose a slight alteration which uses the original approach by default (and would thus be fully backwards compatible), but which catches the error and attempts to find the global using the es6-shim approach:

try {
    Object.defineProperty(Object.prototype, "__global__", {
        get: function () { return this; },
        configurable: true
    });
    try { return __global__; }
    finally { delete Object.prototype.__global__; }
} catch {
    if (typeof self !== 'undefined') { return self; }
    if (typeof window !== 'undefined') { return window; }
    if (typeof global !== 'undefined') { return global; }
}

I am prepared to submit a PR if this issue is accepted.

medikoo commented 4 years ago

Technically the method of hacking Object.prototype is pursued as fallback, for scenarios where CJS was forced to run in strict mode (CJS modules normally are expected to be run in sloppy mode). Otherwise this patch doesn't happen.

I take it's your case (?)

jdatskuid commented 4 years ago

Yes. Using webpack to bundle multiple NPM packages together with our own code. The fallback is failing with a TypeError.

I realize that this is a very edge-case scenario, which is why I'm not suggesting a major change to the existing logic and merely adding a catch with a dumber fallback. The Object.prototype hack is elegant, but doesn't work in the scenario I've described.

jdatskuid commented 4 years ago

(Technically, we are unable to run in sloppy mode in one of the platforms that we deploy code to, so even without webpack, we'd still be stuck with the broken fallback.)

medikoo commented 4 years ago

Technically, we are unable to run in sloppy mode in one of the platforms that we deploy code

Just wondering, how is that enforced? Are you bundle into ESM module? Or is simply question of some test suite setup?

jdatskuid commented 4 years ago

It's complicated, but simply put, our code is not loaded directly but wrapped in a template which forces "use strict" and then eval-ed. We have no control or ability to bypass this process. (It's called "Lightning Locker Service")

medikoo commented 4 years ago

@jdatskuid Ok, all clear. I'm open for PR (still just wrap with try the Object.defineProperty call).

Thank you!

medikoo commented 4 years ago

@jdatskuid it's fixed now, and published with v0.10.52