"var" declarations shadowing properties from Window.prototype

Brendan Eich brendan at mozilla.com
Sat Aug 11 19:42:08 PDT 2012

Cameron McCormack wrote:
> Brendan Eich:
>> As bz and others point out, the object detection w/ var pattern can
>> arise for operations, e.g. for requestAnimationFrame, using the same
>>    var requestAnimationFrame = window.mozRequestAnimationFrame || ... ||
>> window.requestAnimationFrame;
>> pattern. So WebIDL operations (JS methods) on the global would be
>> promoted to "own" too. They'd be configurable, if I recall correctly,
>> and either writable or replaceable. Do I have that right?
> OK.  So one thing that I think has been pointed out is that moving 
> properties for operations on to window makes it harder to monkeypatch 
> addEventListener and friends.

This is a recent feature, right? I can't even find EventTarget in Opera 
12, e.g.

It's plausible the global object exception breaks symmetry here. Could 
we live with the inability to monkey-patch? As Jonas points out, playing 
deep prototype chain games with the global object when var (and 
function) shadow as in ES6 or ES5.1+erratum-fix is just bug-making and 

>   We *could*, if we thought it was important, have the properties on 
> window forward on to the ones from the prototype.

Less magic if we can avoid it.

> Some other questions on specifics:
> 1. If we don't do that auto-forwarding, does Window.prototype still 
> need to exist?  What should window.__proto__ be?

There's no need to over-invent for uniformity, if the global object is 
exceptional. The simpler the exception, the better.

However, implementations (at least Gecko) give window a proto chain that 
is "interesting":

[19:29:57.405] Object.prototype.toString.call(window.__proto__).slice(8,-1)
[19:29:57.408] "XPC_WN_ModsAllowed_NoCall_Proto_JSClass"
[19:30:06.145] "Global Scope Polluter"
[19:30:11.631] "Object"
[19:30:18.375] "Null"

This is from a stable-release Firefox Web Console. I believe it matches 
a real DOM content window. I don't see EventTarget, because its methods 
get flattened into the direct proto, Window.prototype:

[19:33:30.076] Window.prototype === this.__proto__
[19:33:30.077] true
[19:33:50.323] Object.getOwnPropertyNames(this.__proto__)
[19:33:50.325] ["addEventListener", "removeEventListener", 
"dispatchEvent", "dump", "name", "parent", "top", "self", 
"sessionStorage", "localStorage", "onmouseenter", "onmouseleave", 
"getSelection", "scrollByLines", "getComputedStyle", "__lookupGetter__", 
"__lookupSetter__", "__defineGetter__", "__defineSetter__", "toString", 
"QueryInterface", "window", "document", "location", "history", 
"locationbar", "menubar", "personalbar", "scrollbars", "statusbar", 
"toolbar", "status", "close", "stop", "focus", "blur", "length", 
"opener", "frameElement", "navigator", "applicationCache", "alert", 
"confirm", "prompt", "print", "showModalDialog", "postMessage", "atob", 
"btoa", "matchMedia", "screen", "innerWidth", "innerHeight", "scrollX", 
"pageXOffset", "scrollY", "pageYOffset", "scroll", "scrollTo", 
"scrollBy", "screenX", "screenY", "outerWidth", "outerHeight", 
"scrollByPages", "sizeToContent", "content", "closed", "crypto", 
"pkcs11", "controllers", "defaultStatus", "mozInnerScreenX", 
"mozInnerScreenY", "scrollMaxX", "scrollMaxY", "fullScreen", "back", 
"forward", "home", "moveTo", "moveBy", "resizeTo", "resizeBy", 
"updateCommands", "find", "mozPaintCount", "mozRequestAnimationFrame", 
"mozCancelAnimationFrame", "mozCancelRequestAnimationFrame", 
"mozAnimationStartTime", "URL", "onafterprint", "onbeforeprint", 
"onbeforeunload", "onhashchange", "onmessage", "onoffline", "ononline", 
"onpopstate", "onpagehide", "onpageshow", "onresize", "onunload", 
"ondevicemotion", "ondeviceorientation", "ondeviceproximity", 
"onuserproximity", "ondevicelight", "setTimeout", "setInterval", 
"clearTimeout", "clearInterval", "setResizable", "captureEvents", 
"releaseEvents", "routeEvent", "enableExternalCapture", 
"disableExternalCapture", "open", "openDialog", "frames", "onabort", 
"onblur", "oncanplay", "oncanplaythrough", "onchange", "onclick", 
"oncontextmenu", "ondblclick", "ondrag", "ondragend", "ondragenter", 
"ondragleave", "ondragover", "ondragstart", "ondrop", 
"ondurationchange", "onemptied", "onended", "onerror", "onfocus", 
"oninput", "oninvalid", "onkeydown", "onkeypress", "onkeyup", "onload", 
"onloadeddata", "onloadedmetadata", "onloadstart", "onmousedown", 
"onmousemove", "onmouseout", "onmouseover", "onmouseup", 
"onmozfullscreenchange", "onmozfullscreenerror", 
"onmozpointerlockchange", "onmozpointerlockerror", "onpause", "onplay", 
"onplaying", "onprogress", "onratechange", "onreset", "onscroll", 
"onseeked", "onseeking", "onselect", "onshow", "onstalled", "onsubmit", 
"onsuspend", "ontimeupdate", "onvolumechange", "onwaiting", "oncopy", 
"oncut", "onpaste", "onbeforescriptexecute", "onafterscriptexecute", 
"indexedDB", "performance"]

The global object's prototype chain must end with Object.prototype (ES5 
says so, IIRC). But the exact chain is not something that should be 
standardized in detail.

> 2. If Window.prototype still does exist, should it be empty?

Rather have no Window.prototype if everything currently there becomes 
"own" in each window object, than have an empty proto as an attractive 
nuisance. You can't patch if shadowed by an own property, but it might 
seem like you ought to be able to.
> 3. The only writable IDL attributes on Window are:
>      attribute DOMString name;
>      attribute DOMString status;
>      attribute WindowProxy? opener;
> and all of the event handler attributes like "onclick".  How do these 
> need to behave if script blithely tries to use variables of the same 
> name?  With this:
> <script>
>     alert([window.status, typeof window.status]);
>     window.status = "hello";
>     alert([window.status, typeof window.status]);
> </script>
> <script>
>     var status;
>     alert([window.status, typeof window.status]);
>     status = 1;
>     alert([window.status, typeof window.status]);
> </script>
> Gecko and Opera alert:
>   ,string
>   hello,string
>   ,undefined
>   1,number

Yes, this is more lossage compared to historical norms that WebKit-based 
browsers still uphold.

The lack of a var initialiser means that the (historically own) status 
accessor must have its getter or setter invoked. 'var' doesn't replace 
an existing own property in any scenario, historical or ES5+erratum-fix.

> while Chrome and Safari alert:
>   ,string
>   hello,string
>   hello,string
>   1,string
> which seems to indicate that they're setting the IDL attribute.

Did you try IE8, 9 and 10? I suspect they match but can't test.

>   I guess this is related to whether we want "function onclick(){}" to 
> invoke the setter.

We decided (as Allen points out; related to the var issue but distinct 
from it) we do not.

But function onmessage(){} in a worker script then doesn't do what some 
devs seem to like (or at least expect) from Chrome and Safari.

> With this:
> <body onerror="alert('exception')">
> <script>
>     alert([String(window.onclick), typeof window.onclick)]);
> </script>
> <script>
>     var onclick = 1;
>     alert([String(window.onclick), typeof window.onclick]);
> </script>
> Gecko, Opera and Chrome alert:
>   null,object
>   1,number
> which could mean shadowing or treating "onclick" as IDL type "any" and 
> treating non-Function values as null, while Safari alerts:
>   null,object
>   null,object
> which looks like it's invoking the setter but ignoring the assignment 
> of a non-Function value.

Seems a Safari deviation.


More information about the es-discuss mailing list