Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicitly detect browser global in strict mode. #125

Merged
merged 2 commits into from
Oct 13, 2017

Conversation

cdata
Copy link
Contributor

@cdata cdata commented Sep 5, 2017

In strict mode, this is no longer sufficient to detect the
global object. This change adapts the detection of the global object
for the case of exporting to the browser global object.

Fixes #124

Note: this is but one potential strategy for correct global detection, and it explicitly supports the browser context. There are other (potentially more complicated) detections that will support non-browser contexts. Whether or not it is critical to support such contexts is a question I leave open for discussion.

In strict mode, `this` is no longer sufficient to detect the
global object. This change adapts the detection of the global object
for the case of exporting to the browser global object.
@jrburke
Copy link
Member

jrburke commented Sep 8, 2017

The problem statement might be:

"How to get a handle on the global that works in at least a browser window and a web worker (what other JS envs might need to be considered) while in "use strict" mode and when not in a "use strict" mode?"

This specific approach will not work in a web worker.

The example code in this repo does not have "use strict" at the top of the file, so they are not really assuming running in a "use strict" environment.

I will not comment any more on this PR, but wanted to at least give some quick feedback on this particular approach and to point to the difficulty in finding an answer given all the problem constraints. I also still believe it is incorrect to try to load UMD type="text/javascript" scripts in a type="module" environment, and I prefer if it is not encouraged.

@cdata
Copy link
Contributor Author

cdata commented Sep 8, 2017

Thanks @jrburke , great point about web workers. There is also self, which I believe is available in worker contexts as well as documents. Perhaps that is a good alternative.

@justinfagnani
Copy link

For reference, see the global proposal: https://github.com/tc39/proposal-global#rationale

Seems like this || window || self might suffice. Putting this first allows an embedder to override the global. I don't know enough about how bundlers handle UMD though.

@cdata
Copy link
Contributor Author

cdata commented Sep 8, 2017

@justinfagnani Thanks for the pointer to global.

What if window or self are never defined or declared? That would throw an exception.

@ryanflorence
Copy link

@addyosmani
Copy link
Member

addyosmani commented Sep 8, 2017

There is also self, which I believe is available in worker contexts as well as documents. Perhaps that is a good alternative.

Experimenting with self to evaluate how well it works sounds like a good next step.

Seems like this || window || self might suffice. Putting this first allows an embedder to override the global. I don't know enough about how bundlers handle UMD though.

As we're adjusting existing UMD templates here rather than introducing a new one specific to strict-mode browser global detection, minimizing breakage is desirable - I think this || window || self would throw.

With respect to bundlers, perhaps @sokra might have some input on how well Webpack might treat such changes?

Reading through https://github.com/tc39/proposal-global#rationale, paulmillr/es6-shim@2367e09 by @ljharb is interesting, but causes issues with CSP.

@ljharb
Copy link

ljharb commented Sep 8, 2017

The only guaranteed-correct approach is Function('return this')(), but that indeed causes problems with CSP environments.

Short of that, const globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : this; should do it, but you'd probably want to add a runtime assertion that checks things like [] instanceof globalObject.Array && Object(true) instanceof globalObject.Boolean etc, just to ensure you're not silently doing the wrong thing when that best-guess approach fails.

@cdata
Copy link
Contributor Author

cdata commented Sep 8, 2017

Thanks @ljharb .

const globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : this;

I like this for its completeness, but let me lay out some potential detection scenarios:

  1. In Node, require/module will be detected and used
  2. With AMD, require/define will be detected and used
  3. In not-strict mode, this is sufficient to detect the global
  4. In strict mode for browser documents or web workers, self is sufficient to detect the global

If we had global available to us, that would certainly be ideal. However, detecting the global object with self would address the use case for strict mode JavaScript in browsers. So, my question is: what important cases are covered (that I did not list) by first looking for global or window?

you'd probably want to add a runtime assertion that checks things like [] instanceof globalObject.Array && Object(true) instanceof globalObject.Boolean etc, just to ensure you're not silently doing the wrong thing when that best-guess approach fails.

I agree that simply looking for a recognized global name is not completely satisfying. However, if you don't trust the name used to detect the global object, it seems only marginally more satisfying to do this extra check because an object masquerading as global, window or self could also have references to those constructors as properties.

@ljharb
Copy link

ljharb commented Sep 8, 2017

Don't forget about iframes, web workers, and also other contexts like jsc on the command line (for which the eval approach is the only solution), and Adobe Photoshop :-D

One way you can know for sure if it's the global is something like foo = {}; global.foo === foo - that's not something malicious code could anticipate unless they'd somehow replaced the global object's [[Prototype]] with a Proxy, which I believe isn't possible in most engines.

@cdata
Copy link
Contributor Author

cdata commented Sep 8, 2017

and Adobe Photoshop

Haha, yes, ExtendScript FTW! But now I'm curious: does ExtendScript support strict mode?

Don't forget about iframes, web workers

I think these are covered by self.

@ljharb
Copy link

ljharb commented Sep 8, 2017

Ah yes, good call; I missed that bullet point.

Basically with the appropriate typeof waterfall, window → (process &&) global → self is the safest approach, but it requires eval to work in some places like jsc.

@cdata
Copy link
Contributor Author

cdata commented Sep 8, 2017

Ah yes, but if process is attached to the global, then it's Node, which means the global detection doesn't really come into the equation, right?

And window is only important for cases where the code is evaluated in strict mode but self is not available. So maybe the important question is: are there browsers that support strict mode but do not have a global self?

I'm not very familiar with jsc. I'm guessing UMD falls back to using the global when loaded in jsc. Does jsc support evaluating code in strict mode?

@ljharb
Copy link

ljharb commented Sep 8, 2017

Indeed, if process and global both exist, you know it's node.

I'm not sure what browsers have strict mode but also don't have self; I would be willing to bet it's nonzero tho.

jsc does support strict mode; it's the command line version of safari's JS engine - and in it, the only way to get the global object is Function('return this').

@cdata
Copy link
Contributor Author

cdata commented Sep 8, 2017

I would be willing to bet it's nonzero tho.

This seems like a worthy bet to take you up on. I will do some research and try to discover the answer.

jsc does support strict mode; it's the command line version of safari's JS engine - and in it, the only way to get the global object is Function('return this').

Considering the conflict this creates with CSP, I'm afraid this means that jsc has not been (and will continue to not be) supported in strict mode.

@cdata
Copy link
Contributor Author

cdata commented Sep 9, 2017

I have tested a selection of browsers for combinations of strict mode support and the presence of a global self property.

While this selection is not comprehensive, I have some preliminary conclusions:

  • The global self property was found everywhere I looked. As a colleague just said to me, it appears to be "as old as the hills."
  • Most modern browsers, even with a generous definition of modern that includes candidates such as UC Browser, seem to have self.

Some potentially interesting areas for further exploration include:

  • Are there any esoteric browsers that should be considered?
  • Do the web worker contexts in all browsers have a self property?

Here are the results of my testing so far:

Browser Version Supports strict mode Has global 'self' Conclusion
iPhone 5 iOS 10.3 👍
iPhone 5 iOS 10.0 👍
iPhone 5 iOS 9.3 👍
iPhone 5 iOS 9.0 👍
iPhone 5 iOS 8.4 👍
iPhone 5 iOS 8.0 👍
Android Internet 4.4 👍
Android UC Browser 11.4.2995 👍
Windows 10 Edge 15 👍
Windows 10 Edge 13 👍
Windows 7 IE 8 🚫 👍
Windows 7 IE 9 🚫 👍
Windows 7 IE 10 👍
Windows 7 Chrome 26 👍
Windows 7 Firefox 4 👍
Windows 7 Opera 11 👍
Windows 7 Safari 5 👍
OSX Safari 6 👍
PhantomJS 2.1 👍

@cdata
Copy link
Contributor Author

cdata commented Sep 9, 2017

As an addendum it should be noted that self is standard for both documents and web worker contexts.

@cdata
Copy link
Contributor Author

cdata commented Sep 11, 2017

I ran a subset of the browsers above through a test to see if their worker contexts had a global self. Here are my results:

Browser Version Worker has self
iPhone 5 iOS 10.3 👍
iPhone 5 iOS 8.1 👍
Android Internet 4.4 👍
Android UC Browser 11.4.2995 👍
Windows 10 Edge 15 👍
Windows 10 Edge 13 👍
Windows 7 IE 10 👍
Windows 7 Chrome 26 👍
Windows 7 Firefox 4 👍
Windows 7 Opera 11 👍
Windows 7 Safari 5 👍
OSX Safari 6 👍
PhantomJS 2.1 👍

@cdata
Copy link
Contributor Author

cdata commented Sep 26, 2017

Friendly ping

@cdata
Copy link
Contributor Author

cdata commented Oct 10, 2017

Friendly ping!

@EvanCarroll
Copy link
Contributor

So the problem is ES6 modules do not this defined, but they do have self defined?

@cdata
Copy link
Contributor Author

cdata commented Oct 11, 2017

@EvanCarroll the problem is that this has an unexpected value when used in strict mode. The problem is not specific to ES6 modules, but it affects them.

self appears to be a satisfactory alternative that will work in strict mode and non-strict mode.

@addyosmani
Copy link
Member

After carefully reviewing the due diligence @cdata has done here in the context of self, I'm personally fine with this set of changes landing. Given it's been a few weeks and we haven't heard of any strong objections since his last pings, I'm going to interpret this as we collectively don't have too many concerns this would be a controversial change.

If folks do have concerns but haven't had a chance to air them, feel free to ping this PR or open a new issue for us to discuss. I know everyone is time-constrained and appreciate the input provided on this thread so far. Cheers folks.

@DanielRuf
Copy link

Still global / root is undefined in many scenarios. Not sure if we have to add the window / root fix here or the other devs have to change their implementations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UMD is not compatible with JavaScript modules
8 participants