A DOM use case that can't be emulated with direct proxies

# David Bruant (12 years ago)

A good question by Anne van Kesteren [1] followed by good remarks by Boris Zbarsky [2][3] made me try a little something [4][5]. The WindowProxy object returned as the 'contentWindow' property of iframes never changes; whatever you do when changing the @src, always the same object is returned. However, based on whether the @src is changed, the WindowProxy proxies to a different Window instance. I thing this is something that can't be implemented with direct proxies. Is this conclusion shared?

Assuming I'm not wrong in my analysis, now is time to wonder if the direct proxy design should be change to make this part writable in JavaScript. If self-hosting the browser APIs as pure ECMAScript 6 is a goal, something needs to be changed indeed. If it's possible to relax this use case a little bit, following the direct proxies model described some time ago [6], it would be possible to do the following:

  • Model WindowProxy objects as ES proxies
  • Allow the UA to change the target of this proxy at will (which is very close to what's actually done and spec'ed anyway)

I wish to point out that apparently iframe.contentWindow does break quite a lot of "eternal invariants" [7] which isn't really good news, because it questions their eternity.

Among alternatives I'm thinking of:

  • define a new type of proxies for which the target can be changed (either only as a spec device of as an actual object that can be instantiated in scripts)
  • change the behavior of WindowProxy instances when it comes to doing things that would commit them to eternal invariants to throw instead of forwarding. This solution may still be possible, because it's unlikely that Object.defineProperty is widely used in web content today. But this change should happen pretty fast before content relies on it.

David

[1] lists.w3.org/Archives/Public/public-script-coord/2012OctDec/0188.html [2] lists.w3.org/Archives/Public/public-script-coord/2012OctDec/0266.html [3] lists.w3.org/Archives/Public/public-script-coord/2012OctDec/0267.html [4] davidbruant.github.com/iframeProxyIssueDemo [5] DavidBruant/iframeProxyIssueDemo/blob/master/index.html [6] esdiscuss/2012-September/025243 [7] esdiscuss/2011-May/014150

# Kevin Reid (12 years ago)

On Wed, Dec 12, 2012 at 11:19 AM, David Bruant <bruant.d at gmail.com> wrote:

A good question by Anne van Kesteren [1] followed by good remarks by Boris Zbarsky [2][3] made me try a little something [4][5]. The WindowProxy object returned as the 'contentWindow' property of iframes never changes; whatever you do when changing the @src, always the same object is returned. However, based on whether the @src is changed, the WindowProxy proxies to a different Window instance.

I bumped into this myself just recently while attempting to implement virtualized navigable iframes in Caja — I need to emulate exactly this behavior.

[...] I wish to point out that apparently iframe.contentWindow does break quite a lot of "eternal invariants" [7] which isn't really good news, because it questions their eternity.

Indeed!

Among alternatives I'm thinking of:

  • define a new type of proxies for which the target can be changed (either only as a spec device of as an actual object that can be instantiated in scripts)
  • change the behavior of WindowProxy instances when it comes to doing things that would commit them to eternal invariants to throw instead of forwarding. This solution may still be possible, because it's unlikely that Object.defineProperty is widely used in web content today. But this change should happen pretty fast before content relies on it.

The best option I see at the moment would be that a WindowProxy refuses to commit, but a Window does. That is, code operating on 'window' within the iframe can still Object.defineProperty, but from the outside every property of Window appears to be configurable. This is what I have implemented in my current draft.

On the other hand, it seems that in browsers either 'window' is also the same (!) proxy, or === invariants are broken, or the WindowProxy is acting as a membrane:

> f.contentWindow === f.contentWindow.window
true

This would seem to prohibit the distinction I propose.

# Brandon Benvie (12 years ago)

This can be handled by proxies and in fact it's something I've done with proxies for App.js for pretty much the same scenario. A single Window object exists for each desktop window created, but the page can be navigated as it does in a browser, wiping out the entire context's object graph. The proxy simply reestablishes a connection with the new JS context upon navigating and changes targets. All the object references to objects from the old JS context (which forms a membrane, as they are all proxies) are neutered at the same time.

The specified proxy target doesn't correspond to the various context globals; rather it can be used to house expando properties that may be shown on every transitory target object.

appjs/appjs/blob/master/lib/window.js#L35

# David Bruant (12 years ago)

Le 12/12/2012 20:29, Kevin Reid a écrit :

On Wed, Dec 12, 2012 at 11:19 AM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

A good question by Anne van Kesteren [1] followed by good remarks
by Boris Zbarsky [2][3] made me try a little something [4][5].
The WindowProxy object returned as the 'contentWindow' property of
iframes never changes; whatever you do when changing the @src,
always the same object is returned. However, based on whether the
@src is changed, the WindowProxy proxies to a different Window
instance.

I bumped into this myself just recently while attempting to implement virtualized navigable iframes in Caja — I need to emulate exactly this behavior.

Do you have a pointer to the code for that, just out of curiosity?

[...] I wish to point out that apparently iframe.contentWindow
does break quite a lot of "eternal invariants" [7] which isn't
really good news, because it questions their eternity.

Indeed!

Among alternatives I'm thinking of:
* define a new type of proxies for which the target can be changed
(either only as a spec device of as an actual object that can be
instantiated in scripts)
* change the behavior of WindowProxy instances when it comes to
doing things that would commit them to eternal invariants to throw
instead of forwarding. This solution may still be possible,
because it's unlikely that Object.defineProperty is widely used in
web content today. But this change should happen pretty fast
before content relies on it.

The best option I see at the moment would be that a WindowProxy refuses to commit, but a Window does. That is, code operating on 'window' within the iframe can still Object.defineProperty, but from the outside every property of Window appears to be configurable. This is what I have implemented in my current draft.

Let's say that the window has a non-configurable, non-writable property, what happens to Object.getOwnPropertyDescriptor on the WindowProxy? Does it throw? (I would be fine with this behavior, but I'm just wondering)

On the other hand, it seems that in browsers either 'window' is also the same (!) proxy, or === invariants are broken, or the WindowProxy is acting as a membrane:

> f.contentWindow === f.contentWindow.window
true

I think it's a membrane. The HTML5 spec [1] makes pretty clear that the window property isn't a Window, but a WindowProxy. HTML5 experts will know better, but I think no one ever manipulates directly a Window instance, there is always a WindowProxy mediating the access. Of course, the implementation is free to optimize this mediation.

David

[1] www.whatwg.org/specs/web-apps/current-work/multipage/browsers.html#the

# Alex Russell (12 years ago)

Window interceptors (as we call them in the browser world) are simply nuts. We simply shouldn't be terribly interested in re-creating this wart.

# David Bruant (12 years ago)

With the previous proxy design indeed. I see you're using Proxy.create [1] But it won't be possible to do this with the new design, because of invariant enforcement.

I recommend staying away from Proxy.create as it's likely to not appear in the spec and be removed from implementations.

David

[1] appjs/appjs/blob/master/lib/handlers.js#L21

Le 12/12/2012 20:30, Brandon Benvie a écrit :

# Kevin Reid (12 years ago)

On Wed, Dec 12, 2012 at 11:39 AM, David Bruant <bruant.d at gmail.com> wrote:

Le 12/12/2012 20:29, Kevin Reid a écrit :

On Wed, Dec 12, 2012 at 11:19 AM, David Bruant <bruant.d at gmail.com>wrote:

The WindowProxy object returned as the 'contentWindow' property of iframes never changes; whatever you do when changing the @src, always the same object is returned. However, based on whether the @src is changed, the WindowProxy proxies to a different Window instance.

I bumped into this myself just recently while attempting to implement virtualized navigable iframes in Caja — I need to emulate exactly this behavior.

Do you have a pointer to the code for that, just out of curiosity?

I haven't made it public yet, but it's just the obvious implementation of an (old-style, as implemented in Firefox/Chrome) proxy with a switchable “target”.

The best option I see at the moment would be that a WindowProxy refuses to commit, but a Window does. That is, code operating on 'window' within the iframe can still Object.defineProperty, but from the outside every property of Window appears to be configurable. This is what I have implemented in my current draft.

Let's say that the window has a non-configurable, non-writable property, what happens to Object.getOwnPropertyDescriptor on the WindowProxy? Does it throw? (I would be fine with this behavior, but I'm just wondering)

It returns a descriptor which is identical except that it claims to be configurable. Attempting to actually reconfigure it using defineProperty would throw.

On the other hand, it seems that in browsers either 'window' is also

the same (!) proxy, or === invariants are broken, or the WindowProxy is acting as a membrane:

 > f.contentWindow === f.contentWindow.window
true

I think it's a membrane. The HTML5 spec [1] makes pretty clear that the window property isn't a Window, but a WindowProxy. HTML5 experts will know better, but I think no one ever manipulates directly a Window instance, there is always a WindowProxy mediating the access. Of course, the implementation is free to optimize this mediation.

The disturbing thing about "window instanceof WindowProxy", if you will, (given that it accurately reports its mutability) is that since window is the global environment, it means that the global environment cannot have immutable things. Of course, SES actually establishes a new environment (using 'with') for secured eval.

# David Bruant (12 years ago)

Le 12/12/2012 20:44, Alex Russell a écrit :

Window interceptors (as we call them in the browser world) are simply nuts. We simply shouldn't be terribly interested in re-creating this wart.

I'm not sure I understand your point. Do you mean that emulating them in pure ECMAScript should be an exception to the "emulate DOM" proxy use case?

# Alex Russell (12 years ago)

Yep.

# David Bruant (12 years ago)

Le 12/12/2012 20:49, Kevin Reid a écrit :

On Wed, Dec 12, 2012 at 11:39 AM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

Le 12/12/2012 20:29, Kevin Reid a écrit :
On Wed, Dec 12, 2012 at 11:19 AM, David Bruant
<bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

    The WindowProxy object returned as the 'contentWindow'
    property of iframes never changes; whatever you do when
    changing the @src, always the same object is returned.
    However, based on whether the @src is changed, the
    WindowProxy proxies to a different Window instance.


I bumped into this myself just recently while attempting to
implement virtualized navigable iframes in Caja — I need to
emulate exactly this behavior.
Do you have a pointer to the code for that, just out of curiosity?

I haven't made it public yet, but it's just the obvious implementation of an (old-style, as implemented in Firefox/Chrome) proxy with a switchable “target”.

Interesting. As I said, target-switching won't be possible in direct proxies.

The best option I see at the moment would be that a WindowProxy
refuses to commit, but a Window does. That is, code operating on
'window' within the iframe can still Object.defineProperty, but
from the outside every property of Window appears to be
configurable. This is what I have implemented in my current draft.
Let's say that the window has a non-configurable, non-writable
property, what happens to Object.getOwnPropertyDescriptor on the
WindowProxy? Does it throw? (I would be fine with this behavior,
but I'm just wondering)

It returns a descriptor which is identical except that it claims to be configurable.

Direct proxies invariant checks would make such a "lie" about configurability impossible. The engine would throw if you try to pretend a non-configurable property is configurable. See harmony:direct_proxies#invariant_enforcement

Attempting to actually reconfigure it using defineProperty would throw.

On the other hand, it seems that in browsers either 'window' is
also the same (!) proxy, or === invariants are broken, or the
WindowProxy is acting as a membrane:

    > f.contentWindow === f.contentWindow.window
    true
I think it's a membrane. The HTML5 spec [1] makes pretty clear
that the window property isn't a Window, but a WindowProxy.
HTML5 experts will know better, but I think no one ever
manipulates directly a Window instance, there is always a
WindowProxy mediating the access. Of course, the implementation is
free to optimize this mediation.

The disturbing thing about "window instanceof WindowProxy", if you will, (given that it accurately reports its mutability) is that since window is the global environment, it means that the global environment cannot have immutable things. Of course, SES actually establishes a new environment (using 'with') for secured eval.

One idea would be that different handlers are used based on the context. Different handlers would encode whether to create non-configurable properties. If the WindowProxy instance has been obtained through accessing the global of your environement, you can add non-configurable properties. If the WindowProxy has been obtained with iframe.contentWindow then you can't create non-configurable properties.

# Kevin Reid (12 years ago)

On Wed, Dec 12, 2012 at 12:03 PM, David Bruant <bruant.d at gmail.com> wrote:

Le 12/12/2012 20:49, Kevin Reid a écrit :

I haven't made it public yet, but it's just the obvious implementation of an (old-style, as implemented in Firefox/Chrome) proxy with a switchable “target”.

Interesting. As I said, target-switching won't be possible in direct proxies.

I understand that direct proxies have an internal “target” object. Will it not be possible to simply never place any properties on said object (thus not constrain future behavior) while still appearing to have properties? This text suggests that is a possible and expected pattern:

Since this Proxy API requires one to pass an existing object as a target to

wrap, it may seem that this API precludes the creation of fully “virtual” objects that are not represented by an existing JSObject. It’s easy to create such “virtual” proxies: just pass a fresh empty object as the target to Proxy and implement all the handler traps so that none of them defaults to forwarding, or otherwise touches thetarget.

In my case there is an actual object, of course, but I implement forwarding to said object myself; the JS implementation never knows that I am “treating it as a target”.

# Brendan Eich (12 years ago)

Alex Russell wrote:

Window interceptors (as we call them in the browser world) are simply nuts. We simply shouldn't be terribly interested in re-creating this wart.

Having a stable object identity for the window proxy, while navigating the window through a series of page loads, requires this wart or an equivalent one. Browsers came to this design the hard way. Without the window proxy, with only one global object exposed (not just on the scope chain), insecurity or else inefficiency ensues.

You could say the wart is having a stable object identity for an object that proxies to more than one object over time (but not two at the same time).

That is plausible, except that this use-case looks a lot like a membrane use-case. Is there anything in the membrane literature where the membrane wraps different objects over its lifetime?

# David Bruant (12 years ago)

Le 12/12/2012 21:09, Kevin Reid a écrit :

On Wed, Dec 12, 2012 at 12:03 PM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

Le 12/12/2012 20:49, Kevin Reid a écrit :
I haven't made it public yet, but it's just the obvious
implementation of an (old-style, as implemented in
Firefox/Chrome) proxy with a switchable “target”.
Interesting. As I said, target-switching won't be possible in
direct proxies.

I understand that direct proxies have an internal “target” object. Will it not be possible to simply never place any properties on said object (thus not constrain future behavior) while still appearing to have properties? This text suggests that is a possible and expected pattern:

Since this Proxy API requires one to pass an existing object as a
|target| to wrap, it may seem that this API precludes the creation
of fully “virtual” objects that are not represented by an existing
JSObject. It’s easy to create such “virtual” proxies: just pass a
fresh empty object as the target to |Proxy| and implement all the
handler traps so that none of them defaults to forwarding, or
otherwise touches the|target|.

In my case there is an actual object, of course, but I implement forwarding to said object myself; the JS implementation never knows that I am “treating it as a target”.

I was a bit too strong in my statement, sorry. Let me rephrase: the internal [[Target]] can't be changed, but a proxy can emulate changing of "fake" target as long as what happens with this "fake" target doesn't involve invariant checking. That's the reason I was suggesting that WindowProxies could (maybe depending on how the object reference was obtained) throw whenever invariant checks are involved.

# Kevin Reid (12 years ago)

On Wed, Dec 12, 2012 at 12:35 PM, David Bruant <bruant.d at gmail.com> wrote:

I was a bit too strong in my statement, sorry. Let me rephrase: the internal [[Target]] can't be changed, but a proxy can emulate changing of "fake" target as long as what happens with this "fake" target doesn't involve invariant checking. That's the reason I was suggesting that WindowProxies could (maybe depending on how the object reference was obtained) throw whenever invariant checks are involved.

Exactly. So a user-defined switching proxy needs only to:

  1. refuse to commit to any invariant (non-configurable property or preventExtensions)
  2. even if its switchable-target has an invariant, do not expose that invariant (i.e. pretend each property is configurable)
# Brandon Benvie (12 years ago)

This use case is much more strongly aligned with the purely virtual object than it is for the forwarded one, despite the goal being to wrap real objects. Doing fancy things with object identity requires it.

# David Bruant (12 years ago)

Le 12/12/2012 21:42, Kevin Reid a écrit :

On Wed, Dec 12, 2012 at 12:35 PM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

I was a bit too strong in my statement, sorry. Let me rephrase:
the internal [[Target]] can't be changed, but a proxy can emulate
changing of "fake" target as long as what happens with this "fake"
target doesn't involve invariant checking.
That's the reason I was suggesting that WindowProxies could (maybe
depending on how the object reference was obtained) throw whenever
invariant checks are involved.

Exactly. So a user-defined switching proxy needs only to:

  1. refuse to commit to any invariant (non-configurable property or preventExtensions)
  2. even if its switchable-target has an invariant, do not expose that invariant (i.e. pretend each property is configurable)

Pretend that something non-configurable actually is configurable is an invariant violation. To be more concrete:

  • There is an webpage with an iframe
  • The same window object is proxied by 2 WindowProxy instances. One outside the iframe, one inside.
  • Inside of the iframe, scripts can add a non-configurable property "azerty" to their global.
  • Outside the iframe, what happens when Object.getOwnPropertyDescriptor(iframeWindow, 'azerty') is called? You're suggesting that {configurable: true} is returned. The problem is that on the actual Window instance, there is a non-configurable property, so if the WindowProxy handler tries to do that, an error will be thrown because of invariant checks.

I think throwing is the correct behavior here. The handler can't tell the truth about non-configurable properties (because a later different target may not have the same non-configurable properties), but also can't lie, because lies involves throwing... well, since I say that throwing is the correct behavior, I guess lying is too in a way.

# Kevin Reid (12 years ago)

On Wed, Dec 12, 2012 at 1:23 PM, David Bruant <bruant.d at gmail.com> wrote:

Le 12/12/2012 21:42, Kevin Reid a écrit :

Exactly. So a user-defined switching proxy needs only to:

  1. refuse to commit to any invariant (non-configurable property or preventExtensions)
  2. even if its switchable-target has an invariant, do not expose that invariant (i.e. pretend each property is configurable)

Pretend that something non-configurable actually is configurable is an invariant violation. To be more concrete:

  • There is an webpage with an iframe
  • The same window object is proxied by 2 WindowProxy instances. One outside the iframe, one inside.
  • Inside of the iframe, scripts can add a non-configurable property "azerty" to their global.
  • Outside the iframe, what happens when Object.getOwnPropertyDescriptor(iframeWindow, 'azerty') is called? You're suggesting that {configurable: true} is returned. The problem is that on the actual Window instance, there is a non-configurable property, so if the WindowProxy handler tries to do that, an error will be thrown because of invariant checks.

The JS runtime won't know that the proxy has anything to do with the actual Window instance. The Proxy's formal target will be just {}; only the handler interacts with the Window. This is the distinction I meant but did not state clearly by saying “switchable-target” as opposed to proxy-target.

# David Bruant (12 years ago)

Le 12/12/2012 22:30, Kevin Reid a écrit :

On Wed, Dec 12, 2012 at 1:23 PM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

Le 12/12/2012 21:42, Kevin Reid a écrit :
Exactly. So a user-defined switching proxy needs only to:
1. refuse to commit to any invariant (non-configurable property
or preventExtensions)
2. even if its switchable-target has an invariant, do not expose
that invariant (i.e. pretend each property is configurable)
Pretend that something non-configurable actually is configurable
is an invariant violation. To be more concrete:
* There is an webpage with an iframe
* The same window object is proxied by 2 WindowProxy instances.
One outside the iframe, one inside.
* Inside of the iframe, scripts can add a non-configurable
property "azerty" to their global.
* Outside the iframe, what happens when
Object.getOwnPropertyDescriptor(iframeWindow, 'azerty') is called?
You're suggesting that {configurable: true} is returned. The
problem is that on the actual Window instance, there is a
non-configurable property, so if the WindowProxy handler tries to
do that, an error will be thrown because of invariant checks.

The JS runtime won't know that the proxy has anything to do with the actual Window instance. The Proxy's formal target will be just {};

This target, even if dummy, is the one that will be used for invariants checks. You can't get away from this by design. This is one of the most important part of the direct proxies design. Even if you switch of fake target, the engine will still perform checks on the dummy internal [[Target]].

I feel we're cycling in what we say and I feel I can't find the right words to explain my point. One idea would be for you to implement a target-switching proxy based on direct proxies (Firefox has them natively or you can use Tom's shim [1]). I'm confident you'll understand my point through this exercise.

David

[1] tvcutsem/harmony

# Brandon Benvie (12 years ago)

The ability to break invariants of the language either requires an external object-capability provider to dole out permission to do this (such as what is currently done with "chrome" code being executed with access to privileged internals), or a mechanism in the language to dole out special permissions that goes beyond "first come, first serve".

# Brendan Eich (12 years ago)

Brandon Benvie wrote:

The ability to break invariants of the language either requires an external object-capability provider to dole out permission to do this (such as what is currently done with "chrome" code

Just to clarify for anyone following along (including me!), you must mean Firefox or another XUL app's UI "chrome" including privileged JS, which indeed is endowed with extra capabilities. Not Google Chrome -- right?

# Brandon Benvie (12 years ago)

Yeah I was using it in the Mozilla terminology way, not referring to the browser. =D

# Mark S. Miller (12 years ago)

On Wed, Dec 12, 2012 at 11:19 AM, David Bruant <bruant.d at gmail.com> wrote: [...]

  • change the behavior of WindowProxy instances when it comes to doing things that would commit them to eternal invariants to throw instead of forwarding. This solution may still be possible, because it's unlikely that Object.defineProperty is widely used in web content today. But this change should happen pretty fast before content relies on it.

I think this is the only viable solution. The current behavior violates ES5 in an unintended way[1]. As you say, to remain viable, it must be done quickly. From previous experience, I suggest that there's exactly one way to get quick universal deployment: add a test to test262 that fails when a browser's WindowProxy object violates this normative part of the ES5 spec.

[1] The Window vs WindowProxy hack in html5 also violates ES5 in an intended but unrelated way. That violation cannot be fixed, but does not bear on this one.

# David Bruant (12 years ago)

Le 13/12/2012 00:51, Mark S. Miller a écrit :

On Wed, Dec 12, 2012 at 11:19 AM, David Bruant <bruant.d at gmail.com> wrote: [...]

  • change the behavior of WindowProxy instances when it comes to doing things that would commit them to eternal invariants to throw instead of forwarding. This solution may still be possible, because it's unlikely that Object.defineProperty is widely used in web content today. But this change should happen pretty fast before content relies on it. I think this is the only viable solution.

Ok. What do you think of the idea of different handlers based on context? [1]

The current behavior violates ES5 in an unintended way[1].

Just to clarify, invariants described in ES5.1 - 8.6.2 are violated.

As you say, to remain viable, it must be done quickly. From previous experience, I suggest that there's exactly one way to get quick universal deployment: add a test to test262 that fails when a browser's WindowProxy object violates this normative part of the ES5 spec.

I feel such a test would rather belong to the HTML DOM. But either way, I agree. I'll reach out to public-script-coord to re-explain the issue and the suggested changes. I'll write a test case, submit it to the webapps directory [2] (I'll ask first to be sure it's the right one) and file bugs in different browsers.

David

[1] Last point of esdiscuss/2012-December/027092 [2] dvcs.w3.org/hg/webapps

# Mark S. Miller (12 years ago)

On Thu, Dec 13, 2012 at 1:12 AM, David Bruant <bruant.d at gmail.com> wrote:

I think this is the only viable solution.

Ok. What do you think of the idea of different handlers based on context? [1]

[1] Last point of esdiscuss/2012-December/027092

Only if these different way of obtaining the object actually obtain different objects.

The current behavior violates ES5 in an unintended way[1].

Just to clarify, invariants described in ES5.1 - 8.6.2 are violated.

yes.

As you say, to remain viable, it must be done quickly. From previous experience, I suggest that there's exactly one way to get quick universal deployment: add a test to test262 that fails when a browser's WindowProxy object violates this normative part of the ES5 spec.

I feel such a test would rather belong to the HTML DOM. But either way, I agree.

The spec that it violates is ES5.1. Therefore it will be uncontroversial to put such tests into test262. The modern versions of all major browsers (IE, Chrome, FF, Safari, Opera) all do so close to perfect on test262 that even adding one additional test failure creates a significant incentive to fix it -- especially since the others will.

Of course, it would also be good to get the HTML5 spec fixed and to get such tests into HTML/DOM test suites. But I'm not holding my breath. Your observation about needing to get this fixed soon in right, so let's move

# Mark S. Miller (12 years ago)

Something I just posted in the public-script-coord thread bears repeating here:

A single invariant-violating object can be leveraged by direct proxies to create any number of other objects that also violate invariants.

# Jason Orendorff (12 years ago)

On Wed, Dec 12, 2012 at 3:44 PM, David Bruant <bruant.d at gmail.com> wrote:

Le 12/12/2012 22:30, Kevin Reid a écrit :

The JS runtime won't know that the proxy has anything to do with the actual Window instance. The Proxy's formal target will be just {};

This target, even if dummy, is the one that will be used for invariants checks. You can't get away from this by design. This is one of the most important part of the direct proxies design. Even if you switch of fake target, the engine will still perform checks on the dummy internal [[Target]].

I feel we're cycling in what we say and I feel I can't find the right words to explain my point. One idea would be for you to implement a target-switching proxy based on direct proxies (Firefox has them natively or you can use Tom's shim [1]). I'm confident you'll understand my point through this exercise.

David: gist.github.com/4279162

I think this is what Kevin has in mind. Note in particular that the target of the Proxy is just a dummy object, and the handler ignores it entirely. The proxy uses it for invariant checks, but the intent is that those would always pass.

# Kevin Reid (12 years ago)

On Thu, Dec 13, 2012 at 11:47 AM, Jason Orendorff <jason.orendorff at gmail.com

wrote:

This target, even if dummy, is the one that will be used for invariants

checks. You can't get away from this by design. This is one of the most important part of the direct proxies design. Even if you switch of fake target, the engine will still perform checks on the dummy internal [[Target]].

I feel we're cycling in what we say and I feel I can't find the right words to explain my point. One idea would be for you to implement a target-switching proxy based on direct proxies (Firefox has them natively or you can use Tom's shim [1]). I'm confident you'll understand my point through this exercise.

David: gist.github.com/4279162

I think this is what Kevin has in mind. Note in particular that the target of the Proxy is just a dummy object, and the handler ignores it entirely. The proxy uses it for invariant checks, but the intent is that those would always pass.

Yes, exactly. I was just this minute in the process of writing such a proxy myself, and have not yet confirmed whether it is accepted by the invariant checks for all the cases I'm thinking of (testing against FF 18.0).

Note that either (1) all the switched-among targets need to have the same [[Prototype]], (2) the proxy has to pretend that all inherited properties are actually own, (3) or mutating [[Prototype]] (i.e. proto) needs to be possible. In my particular use case, (1) is not a suitable option, so I would implement (2) if (3) is not available. Not that I approve of (3), but one does what one must to accomplish virtualization.

# David Bruant (12 years ago)

Le 13/12/2012 20:47, Jason Orendorff a écrit :

On Wed, Dec 12, 2012 at 3:44 PM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

Le 12/12/2012 22:30, Kevin Reid a écrit :
The JS runtime won't know that the proxy has anything to do with
the actual Window instance. The Proxy's formal target will be
just {};
This target, even if dummy, is the one that will be used for
invariants checks. You can't get away from this by design. This is
one of the most important part of the direct proxies design.
Even if you switch of fake target, the engine will still perform
checks on the dummy internal [[Target]].

I feel we're cycling in what we say and I feel I can't find the
right words to explain my point. One idea would be for you to
implement a target-switching proxy based on direct proxies
(Firefox has them natively or you can use Tom's shim [1]). I'm
confident you'll understand my point through this exercise.

David: gist.github.com/4279162

I think this is what Kevin has in mind. Note in particular that the target of the Proxy is just a dummy object, and the handler ignores it entirely. The proxy uses it for invariant checks, but the intent is that those would always pass.

but they do not; try:

 var [p, setTarget] = retargetableProxy({}); // I love destructuring 

sooo much! Object.defineProperty(p, 'a', {configurable: false, value:31}); setTarget({}); Object.getOwnPropertyDescriptor(p, 'a'); // invariant check throws here

Any variant that can be written will have the same issue. Even trickeries with the defineProperty trap. The proxy is enforcing invariants against the dummy [[target]]. The same is to be expected from WindowProxy instances even if their underlying window changes. It doesn't matter if the invariant is enforced on the dummy target on an actual window instance. It is enforced and that's the "problem" (with WindowProxy implemented as they are now not being emulable with proxies)

# Kevin Reid (12 years ago)

On Thu, Dec 13, 2012 at 2:58 PM, David Bruant <bruant.d at gmail.com> wrote:

Le 13/12/2012 20:47, Jason Orendorff a écrit :

David: gist.github.com/4279162

I think this is what Kevin has in mind. Note in particular that the target of the Proxy is just a dummy object, and the handler ignores it entirely. The proxy uses it for invariant checks, but the intent is that those would always pass.

but they do not; try:

var [p, setTarget] = retargetableProxy({}); // I love destructuring

sooo much! Object.defineProperty(p, 'a', {configurable: false, value:31});

In my proposal, this would fail ("refuse to commit to any invariant" as I put it above). The handler specifically refuses anything non-configurable or non-writable-data.

# Brendan Eich (12 years ago)

Kevin Reid wrote:

(2) the proxy has to pretend that all inherited properties are actually own,

It strikes me that we need this for window objects anyway, to resolve

ecmascript#78

# Mark S. Miller (12 years ago)

On Thu, Dec 13, 2012 at 7:05 PM, Brendan Eich <brendan at secure.meer.net> wrote:

Boris Zbarsky pointed out on public-script-coord that window.location and window.document must be non-configurable ab initio, but perhaps this is achievable with direct proxies?

This resolved into two suggestions, both consistent with ES5 and with direct proxies:

  • windows.document and window.location must refuse to be configured, but they can still claim to be configurable. ES5 purposely forbids only the opposite mismatch: They can't claim to be non-configurable but still change state in ways that violate that claim.

  • Allen suggested that these could be non-configurable getter-only accessor properties, where the getter stays the same and the magic "switching" behavior is in the getter. (My words for Allen's suggestion)

Either is fine. I like Allen's better.

# Brendan Eich (12 years ago)

David Bruant wrote:

Le 12/12/2012 21:42, Kevin Reid a écrit :

On Wed, Dec 12, 2012 at 12:35 PM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

I was a bit too strong in my statement, sorry. Let me rephrase:
the internal [[Target]] can't be changed, but a proxy can emulate
changing of "fake" target as long as what happens with this
"fake" target doesn't involve invariant checking.
That's the reason I was suggesting that WindowProxies could
(maybe depending on how the object reference was obtained) throw
whenever invariant checks are involved.

Exactly. So a user-defined switching proxy needs only to:

  1. refuse to commit to any invariant (non-configurable property or preventExtensions)
  2. even if its switchable-target has an invariant, do not expose that invariant (i.e. pretend each property is configurable) Pretend that something non-configurable actually is configurable is an invariant violation. To be more concrete:
  • There is an webpage with an iframe
  • The same window object is proxied by 2 WindowProxy instances. One outside the iframe, one inside.

Just as a point of fact regarding the web-as-it-is, this can't happen, right? There's only one WindowProxy per Window and it is the only object referenced by any JS variable. JS code cannot construct another aliasing WindowProxy.

  • Inside of the iframe, scripts can add a non-configurable property "azerty" to their global.

Boris Zbarsky pointed out on public-script-coord that window.location and window.document must be non-configurable ab initio, but perhaps this is achievable with direct proxies?

# Brendan Eich (12 years ago)

Mark S. Miller wrote:

On Thu, Dec 13, 2012 at 7:05 PM, Brendan Eich<brendan at secure.meer.net> wrote:

Boris Zbarsky pointed out on public-script-coord that window.location and

window.document must be non-configurable ab initio, but perhaps this is

achievable with direct proxies?

This resolved into two suggestions, both consistent with ES5 and with direct proxies:

  • windows.document and window.location must refuse to be configured, but they can still claim to be configurable. ES5 purposely forbids only the opposite mismatch: They can't claim to be non-configurable but still change state in ways that violate that claim.

  • Allen suggested that these could be non-configurable getter-only accessor properties,

window.location can be set by assignment to navigate to a new URL. Yet it appears in Chrome, Firefox, Opera, and Safari to be a writable data property. In any event, it can't be a getter-only accessor.

# David Bruant (12 years ago)

Le 14/12/2012 08:25, Brendan Eich a écrit :

Mark S. Miller wrote:

On Thu, Dec 13, 2012 at 7:05 PM, Brendan Eich<brendan at secure.meer.net> wrote:

Boris Zbarsky pointed out on public-script-coord that window.location and window.document must be non-configurable ab initio, but perhaps this is achievable with direct proxies?

This resolved into two suggestions, both consistent with ES5 and with direct proxies:

  • windows.document and window.location must refuse to be configured, but they can still claim to be configurable. ES5 purposely forbids only the opposite mismatch: They can't claim to be non-configurable but still change state in ways that violate that claim.

  • Allen suggested that these could be non-configurable getter-only accessor properties,

To be more specific, [Unforgeable] properties would be described by non-configurable getter-only properties.

window.location can be set by assignment to navigate to a new URL.

location is [Unforgeable, PutForward], so it should be reflected as a non-configurable getter+setter according to WebIDL.

Yet it appears in Chrome, Firefox, Opera, and Safari to be a writable data property.

oh, web browsers and standards... There is indeed a pretty violent mismatch between WebIDL and reality, I guess. Specifically because of the behavior you're describing (assigning window.location having a behavior), location ought to be an accessor. What was the rationale that motivated all these browsers to go for data property? Is it still time to change this behavior?

# Andreas Rossberg (12 years ago)

On 13 December 2012 19:21, Mark S. Miller <erights at google.com> wrote:

On Thu, Dec 13, 2012 at 1:12 AM, David Bruant <bruant.d at gmail.com> wrote:

As you say, to remain viable, it must be done quickly. From previous experience, I suggest that there's exactly one way to get quick universal deployment: add a test to test262 that fails when a browser's WindowProxy object violates this normative part of the ES5 spec.

I feel such a test would rather belong to the HTML DOM. But either way, I agree.

The spec that it violates is ES5.1. Therefore it will be uncontroversial to put such tests into test262.

I have to strongly disagree here. By this argument, we could put in a test for any JS extension in the world that potentially violates proper ES semantics. I think test262 should test ECMA-262, nothing else.

In particular, consider that test262 currently is a headless test, i.e. no browser needed, a shell like d8 or jsc is enough to run it. Putting in browser-specific tests would put a huge burden on all kinds of automated testing environments running this suite.

# David Bruant (12 years ago)

Le 14/12/2012 11:01, Andreas Rossberg a écrit :

On 13 December 2012 19:21, Mark S. Miller <erights at google.com> wrote:

On Thu, Dec 13, 2012 at 1:12 AM, David Bruant <bruant.d at gmail.com> wrote:

As you say, to remain viable, it must be done quickly. From previous experience, I suggest that there's exactly one way to get quick universal deployment: add a test to test262 that fails when a browser's WindowProxy object violates this normative part of the ES5 spec. I feel such a test would rather belong to the HTML DOM. But either way, I agree. The spec that it violates is ES5.1. Therefore it will be uncontroversial to put such tests into test262. I have to strongly disagree here. By this argument, we could put in a test for any JS extension in the world that potentially violates proper ES semantics. I think test262 should test ECMA-262, nothing else.

In particular, consider that test262 currently is a headless test, i.e. no browser needed, a shell like d8 or jsc is enough to run it. Putting in browser-specific tests would put a huge burden on all kinds of automated testing environments running this suite.

I still believe the tests belong to HTML DOM, but it wouldn't be absurd to test this inside of test262. The invariants are an ES5 device. I think it makes sense for ES5 to say "we noticed that some platforms were defining host objects not respecting the invariants; here is how they were wrong". The invariants are a not-well-known and yet normative part of the spec. Offering some guidance in test262 on this part would be good I think.

I would organize things the following way: inside of the /test/suite directory bestPractice/ ch06/ .. ch15/ intl402/ platformSpecific/ readme (explains why this directory is here and what the different subdirectories are for) webBrowser/ readme (to explain how to run each test) test1 ... testn

And if different platforms use ES5, but do not conform, platformSpecific subdirectories can be added to test the non-conforming host objects on the different platforms. Each platform test can contain any sort of file, not just JS. For instance, in the case being discussed, it would make sense to create several HTML files some defining iframes, other defining iframe contents.

d8 or jsc would just have to skip the "platformSpecific" directory. I think it's a decent trade-off to explain how invariants should work on self-objects without polluting the test suite.

[cc'ing test262]

# Alex Russell (12 years ago)

+1. What Andreas said.

# Mark Miller (12 years ago)

Regarding what Andreas said and what Alex +1ed, we already have precedent. We already argued through this precedent in committee and agreed. I like David's suggestion about how to organize these tests.

# Andreas Rossberg (12 years ago)

On 14 December 2012 16:54, Mark Miller <erights at gmail.com> wrote:

Regarding what Andreas said and what Alex +1ed, we already have precedent. We already argued through this precedent in committee and agreed. I like David's suggestion about how to organize these tests.

Hm, unless you are talking about intl402, I wasn't aware of that. What's the precedent?

If the non ES tests are separated properly then it's probably less of an issue, though I still prefer that such tests are under a different umbrella. Just to make clear that they are not actually testing ES engines.

That is, I'd much rather have a structure like (modulo details of naming):

estests/ test262/ ch*/ intl402/ platforms/

# Mark Miller (12 years ago)

On Fri, Dec 14, 2012 at 8:36 AM, Andreas Rossberg <rossberg at google.com>wrote:

On 14 December 2012 16:54, Mark Miller <erights at gmail.com> wrote:

Regarding what Andreas said and what Alex +1ed, we already have precedent. We already argued through this precedent in committee and agreed. I like David's suggestion about how to organize these tests.

Hm, unless you are talking about intl402, I wasn't aware of that. What's the precedent?

I will find it when I have time. If anyone else finds it first, please post a link. Thanks.

If the non ES tests are separated properly then it's probably less of an issue, though I still prefer that such tests are under a different umbrella. Just to make clear that they are not actually testing ES engines.

That is, I'd much rather have a structure like (modulo details of naming):

estests/ test262/ ch*/ intl402/ platforms/

The violation is a violation of the normative ES-262 5.1 spec. Host objects as exposed to ES are part of the TCB, and constrained by the ES spec. The ES spec is does not just constrain ES engines. If you want to make a separate engines/ subdirectory of test262/ and move all the engine-only tests there, I would not object. But I also would not recommend bothering.

# Mark S. Miller (12 years ago)

On Fri, Dec 14, 2012 at 9:12 AM, Mark Miller <erights at gmail.com> wrote:

On Fri, Dec 14, 2012 at 8:36 AM, Andreas Rossberg <rossberg at google.com> wrote:

On 14 December 2012 16:54, Mark Miller <erights at gmail.com> wrote:

Regarding what Andreas said and what Alex +1ed, we already have precedent. We already argued through this precedent in committee and agreed. I like David's suggestion about how to organize these tests.

Hm, unless you are talking about intl402, I wasn't aware of that. What's the precedent?

I will find it when I have time. If anyone else finds it first, please post a link. Thanks.

hg.ecmascript.org/tests/test262/file/c84161250e66/test/suite/ch15/15.2/15.2.3/15.2.3.6/S15.2.3.6_A1.js

# Brendan Eich (12 years ago)

David Bruant wrote:

Le 14/12/2012 08:25, Brendan Eich a écrit :

window.location can be set by assignment to navigate to a new URL. location is [Unforgeable, PutForward], so it should be reflected as a non-configurable getter+setter according to WebIDL.

That would be correct -- and nice, I agree.

Yet it appears in Chrome, Firefox, Opera, and Safari to be a writable data property. oh, web browsers and standards... There is indeed a pretty violent mismatch between WebIDL and reality, I guess. Specifically because of the behavior you're describing (assigning window.location having a behavior), location ought to be an accessor. What was the rationale that motivated all these browsers to go for data property? Is it still time to change this behavior?

Let's not make the standards-are-prior-to-implementations mistake. All this came from Netscape 2, JS1. It got cloned by IE and other browsers. It mutated and was not standardized until a decade later, in HTML5 and then DOM4 -- and WebIDL is an even later (still not REC, it's in CR if I recall correctly) standard.

Nevertheless, since ES5-standard reflection is new, I doubt anyone cares that location appears to be a data property. It should be an accessor. But it needs to be non-configurable, so we still have a problem -- or do we?

# Mark S. Miller (12 years ago)

On Fri, Dec 14, 2012 at 10:19 AM, Brendan Eich <brendan at mozilla.com> wrote:

David Bruant wrote:

Le 14/12/2012 08:25, Brendan Eich a écrit :

window.location can be set by assignment to navigate to a new URL.

location is [Unforgeable, PutForward], so it should be reflected as a non-configurable getter+setter according to WebIDL.

That would be correct -- and nice, I agree.

Yet it appears in Chrome, Firefox, Opera, and Safari to be a writable data property.

oh, web browsers and standards... There is indeed a pretty violent mismatch between WebIDL and reality, I guess. Specifically because of the behavior you're describing (assigning window.location having a behavior), location ought to be an accessor. What was the rationale that motivated all these browsers to go for data property? Is it still time to change this behavior?

Let's not make the standards-are-prior-to-implementations mistake. All this came from Netscape 2, JS1. It got cloned by IE and other browsers. It mutated and was not standardized until a decade later, in HTML5 and then DOM4 -- and WebIDL is an even later (still not REC, it's in CR if I recall correctly) standard.

Nevertheless, since ES5-standard reflection is new, I doubt anyone cares that location appears to be a data property. It should be an accessor. But it needs to be non-configurable, so we still have a problem -- or do we?

AFAICT, a non-configurable accessor fits all the constraints.

# Brendan Eich (12 years ago)

Mark S. Miller wrote:

Nevertheless, since ES5-standard reflection is new, I doubt anyone cares

that location appears to be a data property. It should be an accessor. But it needs to be non-configurable, so we still have a problem -- or do we?

AFAICT, a non-configurable accessor fits all the constraints.

Great! Let's do that then. Can someone bring this to public-script-coord or where-ever else might be the best venue for getting implementors on board?

# David Bruant (12 years ago)

Le 14/12/2012 19:04, Mark S. Miller a écrit :

On Fri, Dec 14, 2012 at 9:12 AM, Mark Miller <erights at gmail.com> wrote:

On Fri, Dec 14, 2012 at 8:36 AM, Andreas Rossberg <rossberg at google.com> wrote:

On 14 December 2012 16:54, Mark Miller <erights at gmail.com> wrote:

Regarding what Andreas said and what Alex +1ed, we already have precedent. We already argued through this precedent in committee and agreed. I like David's suggestion about how to organize these tests. Hm, unless you are talking about intl402, I wasn't aware of that. What's the precedent?

I will find it when I have time. If anyone else finds it first, please post a link. Thanks. hg.ecmascript.org/tests/test262/file/c84161250e66/test/suite/ch15/15.2/15.2.3/15.2.3.6/S15.2.3.6_A1.js

Yes, this probably belongs somewhere else that the ch15 directory. For any other ECMAScript 5.1 implementation, there is no particular reason to consider the "document" global differently than any other.

# Tom Van Cutsem (12 years ago)

2012/12/12 Kevin Reid <kpreid at google.com>

On Wed, Dec 12, 2012 at 12:35 PM, David Bruant <bruant.d at gmail.com> wrote:

I was a bit too strong in my statement, sorry. Let me rephrase: the internal [[Target]] can't be changed, but a proxy can emulate changing of "fake" target as long as what happens with this "fake" target doesn't involve invariant checking. That's the reason I was suggesting that WindowProxies could (maybe depending on how the object reference was obtained) throw whenever invariant checks are involved.

Exactly. So a user-defined switching proxy needs only to:

  1. refuse to commit to any invariant (non-configurable property or preventExtensions)
  2. even if its switchable-target has an invariant, do not expose that invariant (i.e. pretend each property is configurable)

Sorry for arriving late to this thread.

The solution that Kevin described is also how I would approach a "retargetable proxy" (i.e. a proxy that can wrap different target objects over time).

# Tom Van Cutsem (12 years ago)

2012/12/13 David Bruant <bruant.d at gmail.com>

Le 13/12/2012 20:47, Jason Orendorff a écrit :

David: gist.github.com/4279162

I think this is what Kevin has in mind. Note in particular that the target of the Proxy is just a dummy object, and the handler ignores it entirely. The proxy uses it for invariant checks, but the intent is that those would always pass.

but they do not; try:

var [p, setTarget] = retargetableProxy({}); // I love destructuring

sooo much! Object.defineProperty(p, 'a', {configurable: false, value:31}); setTarget({}); Object.getOwnPropertyDescriptor(p, 'a'); // invariant check throws here

Any variant that can be written will have the same issue. Even trickeries with the defineProperty trap. The proxy is enforcing invariants against the dummy [[target]]. The same is to be expected from WindowProxy instances even if their underlying window changes. It doesn't matter if the invariant is enforced on the dummy target on an actual window instance. It is enforced and that's the "problem" (with WindowProxy implemented as they are now not being emulable with proxies)

To clarify, there won't be any invariant violations if you ensure all three of the following conditions hold:

a) the dummy target object never acquires any invariants (letting it be a dummy empty object and otherwise ignoring the target completely achieves that)

b) handler traps that query the proxy never reveal any invariants, even if the "real" target currently pointed-to has invariants (i.e. getOwnPropertyDescriptor always changes the returned property descriptor's configurable attribute to true, isExtensible always returns false, etc.)

c) handler traps that update the proxy refuse to commit (preventExtensions throws, defineProperty returns false when dealing with configurable:false properties, ...)

It's a heavyweight way of going about things, but as Brandon mentioned, it's the price to pay for wanting to do weird things no normal ES5 object could ever do. The more a proxy's behavior deviates from that of an ES5 object, the uglier its implementation will be to "circumvent" the invariant checks.

# Tom Van Cutsem (12 years ago)

2012/12/13 Kevin Reid <kpreid at google.com>

Yes, exactly. I was just this minute in the process of writing such a proxy myself, and have not yet confirmed whether it is accepted by the invariant checks for all the cases I'm thinking of (testing against FF 18.0).

Note that either (1) all the switched-among targets need to have the same [[Prototype]], (2) the proxy has to pretend that all inherited properties are actually own, (3) or mutating [[Prototype]] (i.e. proto) needs to be possible. In my particular use case, (1) is not a suitable option, so I would implement (2) if (3) is not available. Not that I approve of (3), but one does what one must to accomplish virtualization.

It's worth noting that direct proxies do not enforce any invariants w.r.t. inherited property access/update. For instance, the "get" trap is allowed to return arbitrary values over time for a non-configurable, non-writable inherited property.

In other words, it's as if direct proxies always have a mutable proto (even if Object.getPrototypeOf returns a stable result). Hence, (3) is the best way to rationalize the behavior of direct proxies.

# Tom Van Cutsem (12 years ago)

2012/12/14 Mark S. Miller <erights at google.com>

On Fri, Dec 14, 2012 at 10:19 AM, Brendan Eich <brendan at mozilla.com> wrote:

David Bruant wrote:

Le 14/12/2012 08:25, Brendan Eich a écrit :

window.location can be set by assignment to navigate to a new URL.

location is [Unforgeable, PutForward], so it should be reflected as a non-configurable getter+setter according to WebIDL. [...] Nevertheless, since ES5-standard reflection is new, I doubt anyone cares that location appears to be a data property. It should be an accessor. But it needs to be non-configurable, so we still have a problem -- or do we?

AFAICT, a non-configurable accessor fits all the constraints.

Also, when emulating this property using a proxy, reflecting it as a non-configurable accessor will not violate any proxy invariants, so this is good.

However, regarding the retargetable proxy pattern put forward by Kevin and Jason to emulate WindowProxy, that implementation cannot accurately expose window.location as non-configurable, but would need to expose it as configurable instead.