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

On Fri, Aug 10, 2012 at 5:17 PM, Allen Wirfs-Brock
<allen@wirfs-brock.com> wrote:
>
> On Aug 10, 2012, at 3:25 PM, Brendan Eich wrote:
>
> First, apologies for my posts based on partial knowledge -- not that I'll
> ever have total knowledge, but clearly a lot of people were confused about
> what ES5 and 5.1 said (me included!) vs. the post-ES5.1 erratum that engines
> implemented. It did not help that the WebKit IDL binding machinery
> introduced a mitigating effect, IDL attributes as "own" global properties,
> which benefited only some browsers.
>
> Vendor prefixes still suck, btw. :-|
>
> Onward to what we might do:
>
> Allen Wirfs-Brock wrote:
>
> These post ES5.1 errata was based upon these  desired semantics:
>
>
> 1) "variable" accesses that bind to inherited properties of the global
> object
>
> should return the current value of the inherited property.  (note such
> "variable" accesses
>
> may be to properties created by function declarations)
>
>
> You mean unqualified Identifier expressions here, right? Of course, this
> cannot change, it goes back to the dawn of JS and would break the web if we
> lost it.
>
> 2) "variable" assignments  to inherited properties of the global object
> should
>
> be equivalent to a [[Put]] to the global object.  Whether or not a own
> property
>
> is created depends upon the [[Writable]] attribute of the inherited property
>
> and the extensible internal property of the global object.
>
>
> Yes, same as with any object.
>
> 3) global function and var declarations always create own properties of the
>
> global object.
>
>
> So far so good.
>
> If an inherited property of the same name already exists it is
>
> over-ridden with an own property.
>
>
> Isn't this sentence really 4, below?
>
> true
>
>
> 4) The declaration instantiation rules relating to pre-existing bindings
>
> only consider own properties of the global object.  Inherited properties of
> the
>
> global object have no effect upon the processing of function and var
>
> declarations.
>
>
> This is the incompatible change from ES1-5.1 and reality that I question
> whether we can get away with.
>
>
> True, for var declarations.  For function declarations it changed in 5.1 as
> a result of https://bugzilla.mozilla.org/show_bug.cgi?id=577325 which
> initially concerned what happens with a global function declaration when
> there is an inherited access with the same name.  Is the inherited setter
> called?  We all concluded that it shouldn't. Rule 4 above is essentially an
> expression of that idea.
>
> Note this is a real world situation as Jonas notes in
> https://bugzilla.mozilla.org/show_bug.cgi?id=781739#c9 :
>
> Since we are on the subject, a similar thing which have been breaking in
> Firefox but working in Chrome is code which does:
>
> function onmessage(event) { ... }
>
> in global scope in workers.
>
> In Firefox the global scope object has on its prototype chain a setter for
> the 'onmessage' property. However this setter isn't run and instead a
> shadowing variable is declared.
>
> In Chrome the global scope object has the setter on the object itself,
> causing the setter to run.
>
> This caused the page to work in Chrome since the setter is run and thus an
> event handler was registered, while in Firefox a "expando" variable is
> created and nothing else happens.
>
>
> In this case, firing the setter is perhaps what the programmer wanted, even
> if it is a terrible way to accomplish that end.  However, the opposite could
> easily be true.  The programmer has a working program with a function
> declaration for Foo.  Sometime latter the browser adds an unrelated accessor
> property coincidentally named Foo to window's prototype.  The program stops
> working when the declaration doesn't over-ride the inherited property but
> instead calls some setter with the wrong shaped function..
>
>
> You didn't give motivation for it. Obviously the motivation involves not
> wanting var declarations to be trumped by non-writable/non-configurable
> properties of the global object's prototype or grand-proto, etc. But do we
> have such properties?
>
>
> It started with functions declarations as per bug 577325 between ES5 and
> ES5.1.  Post ES5 the var issue came up as
> https://bugs.ecmascript.org/show_bug.cgi?id=78 The attribute sniffing logic
> in the ES5.1 change was about trying to identify properties that have the
> characteristics of those created by function declarations (which should be
> overwriteable by subsequent declarations) from those that don't.
>
>
> One solution is to say that global proto-properties cannot be non-writable.
> I think that's an effective compatibility constraint already.
>
> By restricting [[DefineOwnProperty]] on proto-prototypes??
>
>
> ES5 made the "own" property from ES3, undefined AKA this.undefined in global
> code, non-writable and non-configurable, but we have separate logic to allow
> 'var undefined;' (which is all over the web). Please correct me if I'm
> mistaken here. This is a different case, because "own" and not involving the
> prototype chain.
>
>
> I don't think there is anything special about the global named undefined.
> As long as it is a own property of the global object  (which is is spec'ed
> to be) 'var undefined' is fine because redundant var declarations don't do
> anything anything.  If undefined was inherited from window.prototype it
> would be a different story.
>
>
> Supporting requirements 3&4 are where the "own" property checks were
> introduced.
>
>
> I don't see 3, first sentence, as novel or at issue. If (and only if) a new
> property is bound by var, it will be "own". And function always blows away
> any prior configurable, etc. (10.5 step 5(e)), binding.
>
>
> What you just said about functions is how we justified over-riding inherited
> function valued properties.
>
> But what about var.  Rule 3 also means that 'var foo' guarantees you a own
> property of the global object.  Otherwise we have the var analog of the the
> above function Foo issue.
>
> How do we know whether 'var onClick = null' is  intended to call an
> inherited setter or to create and initialize a global object property.
> Since there is an explicit 'var' that latter seems like a reasonable guess.
>
>
> However, I don't thing we can just drop them without some careful thought.
>
>
> Always think carefullly. But also think about this: we have shipped 4 and it
> is hurting.
>
>
> I know, somebody gets hurt however this goes.  Some are already being hurt
> by interop issues.
>
> The least painful solution to the immediate FF issue may be to make
> indexedDB an own property of the window object rather than an inherited
> property. Perhaps do that for all variable-like  properties (those that down
> need real get/set logic) of the window object.
>
>
> If we did that, we would reintroduce problems related to global declarations
> firing inherited setters and also interactions between inherited property
> attributes and global declarations.
>
>
> This is two-edged. The object detection code that people copied for
> indexedDB, combined with WebIDL and the IndexedDB spec based on WebIDL,
> *wants* inherited properties to prevent var from shadowing:
>
> var indexedDB = window.indexedDB || window.webkitIndexedDB ||
> window.mozIndexedDB || window.msIndexedDB || ...
>
>       if only that had recommended:  window.indexedDB = window.indexedDB ||
> ...
>
>
>
> This isn't the last such case of coincident naming derived from
> vendor-prefixed names.
>
>
> My sense is that the mistake may be making these properties be inherited.
> Perhaps WebIDL instead of thinking about the window object as just another
> object with various properties that are inherited from a prototype should be
> thinking about what global vars and functions it is implicitly declaring.
> ie, what is the standard prologue of var and function declarations that are
> processed as an ECMAScipt program production prior to processing any user
> programs.
>
>
> It feel like we are still playing semantics ping-pong with compat. bug
> paddles. I'm not sure that there is an ideal solution.
>
>
> On the Web, compat matters more. We had problems with var being sensitive to
> the prototype chain, particularly with proxies prototyped in SpiderMonkey
> and mutable __proto__. These were arguably implementation bugs, though.
>
> Compat vs. implementation, compat wins. Rock smashes scissors.
>
>
> Yes, but sometimes its just a matter which compatibility problem is noticed
> first.  The pingponging comes opposing compatibility requirements.  When
> those exist somebody has to loose.
>
>
> Setters predate ES5 and so do host-object readonly properties. I do not
> believe these were a problem for the 15+ years before engines tried
> switching to get ahead of ES5.1 by fixing
> https://bugs.ecmascript.org/show_bug.cgi?id=78 and shadowing
> proto-properties with var.
>
> but isn't widespread use of inherited setters to implement DOM properties
> fairly recent?
>
>
> Engines really all did, until very recently (and some popular ones may still
> do), refuse to shadow when var restates a proto-property by name.
>
> Perhaps we need to consider var and function declarations separately.
> Perhaps we need be have different redeclaration rules for own and for
> inherited global object properties.  In any chase I think we need to do a
> more care analysis then just fixing this bug and it needs be be coordinated
> between the ES spec. and WebIDL.
>
>
> Agreed, but we need to avoid delays and compounding hacks. Cameron's
> solution (3), having the global [[DefineOwnProperty]] check the global's
> proto chain, is interesting. It could be layered on top of the compat break
> wanted by https://bugs.ecmascript.org/show_bug.cgi?id=78. But is this
> playing Jenga? I smell it.
>
>
> I don't think [[DefineOwnProperty]] is the place to do this.  There are
> already too many complex interactions the spec. between declaration
> instantiation, environment record methods, internal methods like
> [[DefineOwnProperty]], etc.  Plus WebIDL probably shouldn't be placed in the
> position of have to change how ES global declarations work.  Instead, I
> think we need to mutually decide the semantic rules for globals including
> those introduce by Web APIs.  The 4 rules discussed above were a start at
> that.  Also, as I mention above, I don't see why WebIDL needs to restrict
> itself to think at the level of properties when it really is defining
> additional built-in declaration.  It pretty much can do what it wants in its
> own object  but perhaps for globals it should be following the same rules as
> regular ES built-ins.
>
>
> I think we might be better off looking again at the global proto-chain proxy
> bugs, which were more implementation than spec but also partly "spec", that
> we suffered, and seeing if they shouldn't be constrained or ruled out.
>
>
> that too

One potential solution that I think we should keep in mind is to
declare that WebIDL properties *on global objects* doesn't go on the
prototype chain, but rather on the global objects themselves. That
seems like it'll reduce a lot of the foot guns since they will behave
much more like "normal" javascript properties. I.e. we'd get a model
that is much simpler for developers.

I feel like whatever we'll do, the interactions with 'var X' and
'function X()' will end up getting complex when there are collisions
with properties on the prototype chain.

We'd likely also have to allow some "readonly" properties to be
overwritten using arbitrary values. I.e. evaluating |indexedDB = 42;|
or |var indexedDB = 42;| in the global scope would actually change
window.indexedDB to the value 42. We end up having to do that right
now anyway for a lot of properties since web pages otherwise break due
to name collisions with random global variables.

This also has the advantage that it's a change "only" on the WebIDL
side. So we'd be free to leave ES with fewer webcompat restrictions
when defining how to deal with var/function and properties on the
prototype chain.

It also has the advantage that Chrome already does this so we have
some basis for thinking that this could be web compatible. (Though
Chrome does this for not just global objects, which is a different
topic).

/ Jonas

Received on Saturday, 11 August 2012 01:01:00 UTC