Previously scripts were failing to find attributes that they assigned to
window and then tried to use from the global scope. Eg
window.newthing = function() {...};
newthing(...); // newthing is not defined error
This wasn't the case for things that already existed in the global scope
and were just being overwritten.
This change just overrides the `Proxy.has()` function which seems to fix
it. Probably the `while` implementation was failing to pick up new
attributes because of the lack.
I also tweaked some comments and variable names and const-ness to be a
little more production ready.
Since the global namespace of javascript in the browser is accessible
there where issues with scripts clobbering things that the page expected
to be attributes of window pages clobbering things that userscripts
saved to `window`. The latter was occuring with OneeChan. OneeChan was
setting `window.$` to a function that took a css selector and the 4chan
catalog script was setting `window.$` to some object. Since OneeChan was
set to run at document-start this broke OneeChan, switching it to
document-end broke scripts on 4chan.
I used OneeChan and 4chan-X on 4chan as the test case for this and
TamperMonkey as a guide for what is the correct way to handle scoping. I
didn't manage to pick apart just how TamperMonkey does what it does (I
think it might just be the environment that Chrome provides extensions
actually) but I got close to the same behaviour.
TamperMonkey provides a `window` object that appears to be what the
global window looked like before the webpage modified it. The global
scope though does have the pages modifications accessible. If the script
assigns something to an attribute `window` it can see that attribute in
the global scope. This implementation differs from that one in that, to
the scipt, `window` and the global scope always look the same, and that
is the same as the global scope looks in the environment provided by
TamperMonkey.
I am using the ES6 `Proxy` feature to allow the `window` object to look
like the actual (unsafe) one while allowing writing to it that doesn't
clobber the unsafe one. I am then using the ES4 `with` function to make
attributes of that window (proxy) object visible in the scope chain.
There may be other ways to do this without using `with` by using nested
functions and setting `this` creatively. There are notes around
alleging `with` to be various states of uncool[1].
I also ran into an issue where a userscript calling
`window.addEventListener(...)` would fail with `TypeError: Illegal
Execution` which is apparently due to `this` not being set correctly. I
looked at the functions which threw that error and those that didn't and
am using whether they have a `prototype` attribute or not to tell
whether I need to bind them with `window` as `this`. I am not sure how
correct that is but it worked for all the cases I ran into.
[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with