Re: "var" declarations shadowing properties from Window.prototype

Cameron McCormack wrote:
> Kyle Huey mailed to public-webapps about a problem we have with 
> unprefixing IndexedDB:
>
>   http://lists.w3.org/Archives/Public/public-webapps/2012JulSep/0392.html
>
> The summary is:
>
> 1. IndexedDB defines:
>
>     interface IDBEnvironment {
>       readonly attribute IDBFactory indexedDB;
>     };
>     Window implements IDBEnvironment;
>
> 2. People are trying to fall back to prefixed versions of IndexedDB by 
> writing code like (hooray prefixes):
>
>   var indexedDB = window.indexedDB || window.webkitIndexedDB ||
>                   window.mozIndexedDB || window.msIndexedDB;
>
> 3. Web IDL says that the "indexedDB" property for the IDL attribute 
> should live on Window.prototype.
>
> 4. Recent versions of the ECMAScript spec say that the var declaration 
> first will create a property on the global object (i.e. window) with 
> the value undefined, before performing the assignment.

As noted on twitter, from 
https://bugzilla.mozilla.org/show_bug.cgi?id=632003#c0 it is not a 
recent JS de-facto standard, rather an old one.

ES1-3 were not totally clear on "own" vs. "in", alas. ECMA-262 Edition 3 
clause 10.1.3, "Variable Instantiation": "... If there is already a 
property of the variable object with
the name of a declared variable, the value of the property and its 
attributes are not changed". Does "property of the variable object" mean 
"own", i.e. direct, or "in" (as in the 'in' operator, direct or indirect 
via prototypal delegation)?

The answer in reality was "in", so 'var indexedDB' would not shadow a 
prototype-homed property of the same name in real engines.

The most recent ECMA-262 spec, the 5th edition, tried going the other 
way, toward "own", but we at Mozilla for Firefox implemented ES5 and ran 
into trouble (see bug above), so we filed an ES5 erratum that was fixed 
for the ISO version of ES5, and reverted our implementation in 
SpiderMonkey to the long-standing de-facto standard.

Some time later (see bug ), SpiderMonkey regressed again to the ES5 
"own" not "in" test. Argh! See 
https://bugzilla.mozilla.org/show_bug.cgi?id=781739 -- this is the bug 
to fix.

Here is ES5.1 (the ISO version):

"10.2.1.2.1 HasBinding(N)

The concrete Environment Record method HasBinding for object environment 
records determines if its associated binding object has a property whose 
name is the value of the argument N:

1. Let envRec be the object environment record for which the method was 
invoked.
2. Let bindings be the binding object for envRec.
3. Return the result of calling the [[HasProperty]] internal method of 
bindings, passing N as the property name."

Note the use of [[HasProperty]], which (via [[GetProperty]] at 8.12.2 
step 5) walks the prototype chain, doing "in" not "own".

My point is not that JS was "right", just that it has *not* in reality 
changed recently. What's recent, as far as I can tell, is IndexedDB and 
its use of WebIDL. And at least Chrome is not following the combination 
of those specs, precisely in order to avoid the same trouble we fixed 
with https://bugzilla.mozilla.org/show_bug.cgi?id=632003.

> 5. This means that the "window.indexedDB" part of the expression on 
> the RHS evaluates to undefined.
>
> 6. This means that we cannot remove our unprefixed version of 
> IndexedDB at the same time as adding the prefixed version (which we 
> want to do), because then the "indexedDB" variable will just be 
> undefined.
>
> 7. Older versions of the ECMAScript spec did not say to create the 
> shadowing variable, so only implementations that follow that change 
> AND have implemented Web IDL's attributes-on-prototypes get tripped up 
> by this.

Again, not quite right: ES5 tried the change but we backed off and ES5.1 
matches long-standing reality and the intended meaning of ES1-3.

> 3. We could change Web IDL so that window's [[DefineOwnProperty]] 
> refuses to create the shadowing undefined property if the property 
> name matches one of the IDL attributes/operations on Window (or its 
> ancestors, etc.)  That effectively works around the ES spec shadowing 
> requirement for var statements without needing to "willfully violate" it.
>
>
> With options (2) and (3), we are assuming that existing scripts do not 
> rely on for example:
>
> <script>
>     var open;
>     assert(open === undefined);
> </script>
>
> Boris thinks that this assumption is safe, but evidence to the 
> contrary would be useful if someone has it.

That assertion would botch in JS as I created it in Netscape 2, and in 
most releases of most browsers ever since. The free variable 'open' 
would resolve to window.open.

> Another thing to be careful of is:
>
> <script>
>     function open() { }
>     assert(open != Window.prototype.open);
> </script>
>
> which I think scripts are doing (they want to call their own "open").

Could be. IE used to do funny things with function onload(){} vs. <body 
onload='...'> such that the two would not collide. They would fight over 
the one window.onload pigeon-hole in ur-JS and most implementations in 
most browsers.

> With (2) this keeps working, since the own property "open" on window 
> exists (which means the var statement doesn't try to overwrite it with 
> undefined) and the property is writable (so the assignment of the 
> Function object works).
>
> With (3) this also keeps working, since the [[DefineOwnProperty]] on 
> window for "open" would be ignored, and the assignment of the Function 
> object shadows the property from the prototype.
>
>
> The upshot of (3) is that for IDL attributes like "onclick", doing
>
>   var onclick = function() { ... };
>
> will be just like assigning to window.onclick.  Whether that's 
> desired, I'm not sure.

That is generally expected (if not desired), modulo the older-IE quirk 
noted above.

> Input on what to do here, especially from TC39 folks, is very welcome.

We don't need to do anything with ECMA-262 or WebIDL or IndexedDB. I was 
wrong to believe that ES5's change stuck. It did not stick in ES5.1. But 
SpiderMonkey re-regressed.

So the only thing I see to fix is SpiderMonkey.

I hope this helps. Again, ES5 tried a change that we backed off from 
making, so the long-standing reality is that var tests "in" not "own", 
and therefore won't shadow proto-properties with undefined-valued bindings.

Whew! for WebIDL and IndexedDB. Also good for ES5.1.

/be

Received on Friday, 10 August 2012 06:08:45 UTC