zloirock / core-js

Standard Library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to execute core-js in firefox's content_script environment

iamolivinius opened this issue · comments

My team and I are using core-js via the babel/polyfill to develop a (cross) brower extension.

During today's debugging session I found out that importing/requireing the polyfill or core-js causes SEGFAULTs in Firefox. Don't get me wrong I'm not gonna blame corejs for segfaults in FF ❤️

So I went all the way down to the modules/$.js to find out that corejs references the self variable.

https://github.com/zloirock/core-js/blob/master/modules/%24.js#L2

Obviously that's not a problem in most cases but for some reason the FF team thought it would be a great idea to use self as a namespace within an extensions content_script environment.

https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts#Accessing_port_in_the_content_script

Honestly I didn't read your entire code base to find out why you're checking for self. With this issue I just want to rise a discussion if we somehow can fix this problem.

Apart form this: Thanks for this great project 👍

I don't know anything about writing FF extensions, I use this way getting global object because it's most appropriate way of those that I know.

Browsers ~IE8 Node NW Web Workers VM FF extensions
self yes broken after changing document.domain yes broken
window yes yes yes broken broken
global yes broken
Function('return this')() breaks CSP yes yes yes breaks CSP yes yes

You can propose more correct way.

@iamolivinius can you try replace this line in core-js, for example, to

var global = typeof self == 'object' && !self.port ? self : Function('return this')()

and test your script?

I was able to solve this minor issue within our project. I went with the "facebook kinda way", see:

https://github.com/facebook/regenerator/blob/master/runtime.js#L645-650

browserify uses a similar solution but in a slightly different order, see:

https://github.com/substack/insert-module-globals/blob/master/index.js#L14-16

webpack as far as I can see, uses a simple function

(function() { return this; }()

Not sure if there is some quantitative data to argue for one or the other.

As you can see in the table above, first 2 examples will not be correct in some cases. Last example returns undefined in strict mode.

FWIW, this is what Lodash uses which is seemingly pretty reliable.

@sebmck unfortunately not reliable enough to run in a content script within firefox (bootstrapped) extension. (add-on sdk untested)

There are several ways to develop Firefox extensions. In this comment I focus on

  1. Add-on SDK extensions: https://developer.mozilla.org/en-US/Add-ons/SDK and
  2. primarily Bootstraped extensions: https://developer.mozilla.org/en-US/Add-ons/Bootstrapped_extensions

For an overview, see: https://blog.mozilla.org/addons/2014/06/05/how-to-develop-firefox-extension/

Due to the API design of add-ons SDK and/or security mechanisms in general some really awesome javascript "modules" like core-js and lodash break within a firefox extension.

Reason One

Add-on SDK declares the self object and freezes it with the following signature

Object {
  port: Object,
  postMessage: onEvent(),
  on: on(, ),
  once: once(, ),
  removeListener: removeListener(, )
}

Many libraries check for self to get the global object basically to patch or inject methods. Obviously this will fail on a frozen object or if you rely on methods that can usually be found in the self object.

Reason Two

(This is 50% guessing. I haven't found much documentation on this.)

Firefox executes content script code within a sandbox with higher privileges than ordinary code from a requested webpage. Nevertheless you can access the web page environment from the content script. Obviously this would open some security issues if it wasn't for Xray vision, see: https://developer.mozilla.org/en-US/docs/Xray_vision

As far as I understand it: If you reference a native object or method from a web page environment within a content script you'd get a vanilla version of it regardless of any changes a web page may have introduced to this object before.

Within a Bootstraped extension sandbox window and self and reference the window/tab of a requested webpage. Neither of those two are global. If you'd run the following example code you'd expect the eternal 🎱.

var global = (function () { /* SOME MAGIC TO GET window, self, etc. */ })();
var windowMath = global['Math'];
windowMath.pow(2, 3) // TypeError: windowMath.pow is not a function

Firefox Browser Console:

XrayWrapper denied access to property pow (reason: object is not safely Xrayable). See 
https://developer.mozilla.org/en-US/docs/Xray_vision for more information. Note that only the first 
denied property access from a given global object will be reported.

Turns out global['Math'] returns an [object Opaque]. Whatever that is?! Unfortunately, I haven't found any serious information on that. This behavior is also true for several other references you may want to acquire like Map, WeakMap, Set, etc.

Solution

Find and use the correct global object.

  • global: not present 💥
  • window: reference to window/tab environment. Opaque objects break code 💥
  • self: reference to window/tab environment. Opaque objects break code 💥
  • self: froozen and different object than expected in add-on SDK 💥
  • (function() { return this; })(): works, but just with strict mode disabled 😢
  • Function('return this')(): works! ✅ ❤️

Would you be open to changing the global.js implementation to something along the lines of what is outlined in this blog post? It would resolve the CSP issue.

module.exports = (function() {
  if (typeof globalThis === "object") return globalThis;
  try {
    Object.defineProperty(Object.prototype, "__magic__", {
      get: function() {
        return this;
      },
      configurable: true
    });

    var result = __magic__;
    delete Object.prototype.__magic__;

    return result;
  } catch (error) {
    // In IE8, Object.defineProperty only works on DOM objects.
    // If we hit this code path, assume `window` exists.
    return window;
  }
})();

@EmilTholin the actual implementation works in all known environments without any problems. I hope that the example from this blog post will be used just as an example - modification of Object.prototype theoretically could cause deoptimizations in some engines and much more complex than the actual solution. Also, I'm not sure that it will work in some embed engines.

Thank you for your quick reply and insight.

I see, that's great. I just got a reply on an issue in the es5-ext repository where I suggested changing the global implementation to something along the lines of the current core-js implementation, but got the answer that it had caused CSP issues in the past. If the current implementation works in all known environments, that's fantastic.

I have already commented on this issue.

Just stumbled across the latest iteration of this code in a project that uses Babel, Webpack, etc.

Consider that - this - referenced in the global context is always the global object. It doesn't matter what environment. As Node is modular, it provides a - global - reference. Should have been end of story about a decade ago.

The reason that this issue has been tossed around for a dozen years is an irrational reliance on host objects to detect the global object (e.g. window, self, etc.) There's clearly no end to that road.

So pass - this - to your module from the global context. Or, as mentioned and inexplicably discarded above, use something like this:

(function() { return this; })()

...which will also work every time, as long as you don't nest it inside a strict mode function. If a design requires doing that, then the design should be adjusted to avoid painting us into a corner. Introducing Math and legacy IE sniffing "magic" to the equation is not the answer.

Another thing to consider here is that one-size-fits-all scripting solutions usually turn out poorly, typically requiring endless maintenance. No amount of documenting aberrations and busted assumptions will ever solve this particular problem once and for all. Thankfully, it never needed solving. :)

@david-mark 🤦

JavaScript is much more complicated and confusing than you think.

Mentioned by you tools like Babel or Webpack too often add strict mode by default, don't forget about native modules context in browsers or Node where this mode enabled by default too - but core-js should work everywhere. @ljharb could give you a more detailed answer about why was required globalThis proposal.