Figuring out the behavior of WindowProxy in the face of non-configurable properties
On Sun, Nov 30, 2014 at 12:21 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
Per spec ES6, it seems to me like attempting to define a non-configurable property on a WindowProxy should throw and getting a property descriptor for a non-configurable property that got defined on the Window (e.g. via "var") should report it as configurable.
Yes, both of these conclusions are correct.
But that matches precisely 0 UAs.... and throwing seems like a compat worry. :(
Anyway, what are reasonable behaviors here? What are UAs willing to align on?
The only reasonable behavior that I see is the one you specified. Introducing an invariant violation of this sort would kill these invariants in general, as the Proxy target mechanism relies on these invariants to enforce that Proxies cannot introduce more violations.
Put another way, if this invariant is preserved by WindowProxy, then anyone else seeking to create another object that violates this invariant can create a Proxy whose target is a WindowProxy. Its violation enables further violations. The invariants are inductive. A violation breaks the induction.
From prior similar experiences, the way to get this fixed quickly is to add test262 tests which fail on these violations. All browsers have been much quicker to fix breakage that shows up in test262 results than to mere bug reports.
On Sun, Nov 30, 2014 at 6:12 PM, Mark S. Miller <erights at google.com> wrote: [...]
Put another way, if this invariant is preserved by WindowProxy, then
Should be: "is not preserved by" or "is violated by"
On 1 December 2014 at 03:12, Mark S. Miller <erights at google.com> wrote:
On Sun, Nov 30, 2014 at 12:21 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
Per spec ES6, it seems to me like attempting to define a non-configurable property on a WindowProxy should throw and getting a property descriptor for a non-configurable property that got defined on the Window (e.g. via "var") should report it as configurable.
Can you clarify? Do you mean that it should report properties as configurable, but still reject attempts to actually reconfigure them? Also, how would you allow 'var' to even define non-configurable properties? If you want DefineProperty to throw on any such attempt, then 'var' semantics would somehow have to bypass the MOP.
From prior similar experiences, the way to get this fixed quickly is to add test262 tests which fail on these violations. All browsers have been much quicker to fix breakage that shows up in test262 results than to mere bug reports.
Well, let's find a workable semantics first. :)
I feel like I've been in an equivalent discussion some time ago, so taking the liberty to answer.
Le 02/12/2014 13:59, Andreas Rossberg a écrit :
On 1 December 2014 at 03:12, Mark S. Miller <erights at google.com> wrote:
On Sun, Nov 30, 2014 at 12:21 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
Per spec ES6, it seems to me like attempting to define a non-configurable property on a WindowProxy should throw and getting a property descriptor for a non-configurable property that got defined on the Window (e.g. via "var") should report it as configurable. Can you clarify? Do you mean that it should report properties as configurable, but still reject attempts to actually reconfigure them?
Yes. This is doable with proxies (which the WindowProxy object needs to be anyway).
- the defineProperty trap throws when it sees configurable:false
- the getOwnPropertyDescriptor trap always reports configurable:true
- and the target has all properties actually configurable (but it's almost irrelevant to the discussion)
Also, how would you allow 'var' to even define non-configurable properties? If you want DefineProperty to throw on any such attempt, then 'var' semantics would somehow have to bypass the MOP.
Thinking in terms of proxies, the runtime can have access to the target and the handler while userland scripts only have access to the proxy (which the HTML Living standard mandates anyway with the difference between Window and WindowProxy objects. No userland script ever have access to the Window object). The handler can have access to the list all declared variable to know which property should behave as if non-configurable.
Le 02/12/2014 14:24, David Bruant a écrit :
I feel like I've been in an equivalent discussion some time ago
The topic felt familiar :-p lists.w3.org/Archives/Public/public-script-coord/2012OctDec/0322.html
Yes. I was glad to find in that message a pointer back to esdiscuss/2012-December/027114
On 12/2/14, 4:59 AM, Andreas Rossberg wrote:
Can you clarify? Do you mean that it should report properties as configurable, but still reject attempts to actually reconfigure them?
Yes, correct.
Also, how would you allow 'var' to even define non-configurable properties?
Because "var" operates on the global directly. The global is a Window, not a WindowProxy and has no magic behavior.
If you want DefineProperty to throw on any such attempt, then 'var' semantics would somehow have to bypass the MOP.
The idea is that WindowProxy's [[DefineOwnProperty]] would throw as needed. Window's [[DefineOwnProperty]] is just people.mozilla.org/~jorendorff/es6-draft.html#sec
On 12/2/14, 5:24 AM, David Bruant wrote:
The handler can have access to the list all declared variable to know which property should behave as if non-configurable.
That's not even needed. If the handler just passes configurable defines on through to the target for a property declared via "var", they will end up in people.mozilla.org/~jorendorff/es6-draft.html#sec-validateandapplypropertydescriptor and throw in step 5.
On 11/30/14, 6:12 PM, Mark S. Miller wrote:
Yes, both of these conclusions are correct.
OK. What do we do if we discover that throwing from the defineProperty call with a non-configurable property descriptor is not web-compatible? I'm going to try doing it in Firefox, and would welcome other UAs doing it ASAP to figure out whether we're in that situation.
On Thu, Dec 4, 2014 at 2:58 AM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
OK. What do we do if we discover that throwing from the defineProperty call with a non-configurable property descriptor is not web-compatible?
What we always do, for example, when we found that having
Object.prototype.toString.call(null)
throw was not web compatible. We look into the specifics of the incompatibility encountered and design a non-web-breaking workaround that is least painful for the semantics we desire. For example, in this case, we changed it to return "[object Null]" even though that string itself had never previously been returned. The specific web compatibility we encountered for this case merely required a non-throw. It did not care what the contents of the string were. This outcome could not have been predicted from first principles.
Other times, as when we found that introducing a new global variable named "JSON" was not web compatible, we found we could evangelize the origin of that incompatibility to fix it at the source, rather than change the spec.
I'm going to try doing it in Firefox, and would welcome other UAs doing it ASAP to figure out whether we're in that situation.
Excellent! Bravo!
On 12/4/14, 10:44 AM, Travis Leithead wrote:
So... this will prevent defining non-configurable properties on the global?
It will prevent using
Object.defineProperty(window, "name", non-configurable-descriptor);
to define a property.
Note that "window" is not the global. It's a proxy whose target is the global.
Combined with [PrimaryGlobal], this seems at odds with what browsers do internally to prevent re-definition of some properties like "document"?
Browsers can define properties on the actual global, so there is no problem here.
Are we sure we want this restriction?
Well, good question. If we don't do this restriction (by which I assume defineProperty throwing; I assume getOwnPropertyDescriptor claiming configurable always is less controversial), what do we want to do?
Note that I did a bit of digging into the history here and as far as I can tell every single UA screwed up when implementing Object.getOwnPropertyDescriptor and company in ES5. ES5 clearly spells out the rules for these methods, and browsers just didn't follow those rules. Plus lack of testing and here we are.
On 12/4/14, 1:36 PM, Travis Leithead wrote:
Note that "window" is not the global. It's a proxy whose target is the global.
Yes, but within a browser UA, there is no way to get a reference to the naked global because all entry-points return window proxies ;-)
Well, no way from web script. The browser internals can do it, presumably, right?
Well, good question. If we don't do this restriction (by which I assume defineProperty throwing; I assume getOwnPropertyDescriptor claiming configurable always is less controversial), what do we want to do?
As I look back on your original message, I fail to see what the problem is. You seem to think that the window proxy is referring to the same window object before and after the navigation.
The window proxy object identity does not change before and after the navigation.
The window object the proxy is pointing to changes.
In fact, in most implementations that I'm aware of, there is the concept of the "inner" and "outer" window.
Yes, I'm well aware.
The "outer" window is the window proxy, which is the object that implements the cross-origin access control.
In Gecko, the cross-origin access control is actually implemented using a separate security membrane proxy whose target is the "outer" window. But sure.
In IE's implementation, the window proxy has no storage as a typical JS var--it's only a semi-intelligent forwarder to its companion "inner" window.
That's an IE implementation detail. In Gecko, the "window proxy" is a JS proxy object with a proxy handler written in C++. That, too, is an implementation detail.
What matters here is what JS consumers see. Consumers typically (there are some exceptions involving scope chains) just see the window proxy, yes?
So when a script does:
Object.defineProperty(frames[0], "foo", { value: true; });
It is defining a property on frames[0]. The fact that this is actually a proxy for some other object (the global inside that iframe) is somewhat of an implementation detail, again. From the consumer's and the spec's point of view, frames[0] is something with some internal methods ([[GetOwnProperty]], [[DefineOwnProperty]], etc) which are implemented in some way. Still from the spec's point of view, the implementation of these internal methods must satisfy people.mozilla.org/~jorendorff/es6-draft.html#sec-invariants-of-the-essential-internal-methods.
So, in your code sample, your "defineProperty" call forwarded to the "inner" window where the property was defined.
Sure. I understand that. As in, the proxy's [[DefineOwnProperty]] invoke's the target's [[DefineOwnProperty]].
After the navigation, the "inner" window was swapped out for a new one (and whole new type system at that) which the existing window proxy ("outer" window) now refers.
Sure.
This gave the appearance of the non-configurable property disappearing
This isn't about "appearance". The relevant spec invariant for [[GetOwnProperty]], for example, is:
If P’s attributes other than [[Writable]] may change over time or if the property might disappear, then P’s [[Configurable]] attribute must be true.
And Object.getOwnPropertyDescriptor is clearly defined to invoke [[GetOwnProperty]].
So when a page does Object.getOwnPropertyDescriptor(window, "foo") this is invoking the window proxy's [[GetOwnProperty]]. That's allowed to do all sorts of stuff as long as it preserves the invariants involved, including the one I quote above. The fact that the "disappearing" is due to the target changing is an implementation detail of the window proxy.
but in reality it would still be there if you could get a reference to the "inner" window
Which doesn't matter, because the consumer is not interacting with the "inner" window.
*I wonder if you can capture the inner window in a scope chain or closure somehow
Sure, for a scope chain. Testcase at web.mit.edu/bzbarsky/www/testcases/windowproxy/use-old-window-1.html shows "OLD WINDOW" on the first line in Firefox, Chrome, and Safari. In IE11 it throws a "Can't execute code from a freed script" exception; I can't find anything in the specs that allows that, fwiw.
so that you could observe that "foo" is still there even though you can't directly see it anymore?
Absolutely.
I think that might work if the executing code was defined in the old iframe's environment and executed after navigation...
Right.
But we're not talking about indirect probes like this here, just about the basic invariants object internal methods are supposed to preserve.
On Thu, Dec 4, 2014 at 4:32 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
Sure, for a scope chain. Testcase at web.mit.edu/bzbarsky/www/testcases/windowproxy/use-old-window-1.html
That page demands a client certificate. Is that intentional?
Er, sorry. web.mit.edu/bzbarsky/www/testcases/windowproxy/use-old-window-1.html should work for everyone.
Here's an unexpected weirdness, probably not deeply related. Change your first helper page to
<script>
var someName = "OLD WINDOW";
var evil = eval;
function f() {
return someName;
}
function g() {
return (1,evil)("3");
}
</script>
On FF and Safari, I get 3 as expected. On Chrome, I get on my console:
Uncaught EvalError: The "this" value passed to eval must be the global object from which eval originated
Especially weird, because this code doesn't pass any this to the renamed eval. I don't know what this means.
This seems to be an attempt to kill off dead window contexts as early as possible, in order to avoid memory leaks. Toon might be able to say more.
On Thu, 4 Dec 2014, Boris Zbarsky wrote:
On 12/4/14, 1:36 PM, Travis Leithead wrote:
In IE's implementation, the window proxy has no storage as a typical JS var--it's only a semi-intelligent forwarder to its companion "inner" window.
That's an IE implementation detail.
It's more than that. It's how the HTML spec defines WindowProxy.
So when a script does:
Object.defineProperty(frames[0], "foo", { value: true; });
It is defining a property on frames[0]. The fact that this is actually a proxy for some other object (the global inside that iframe) is somewhat of an implementation detail, again.
According to the HTML spec, all operations that would be performed on the WindowProxy object must be performed on the Window object of the browsing context's active document instead. So the above would set a property on the underlying Window object, not the WindowProxy.
This isn't about "appearance". The relevant spec invariant for [[GetOwnProperty]], for example, is:
If P’s attributes other than [[Writable]] may change over time or if the property might disappear, then P’s [[Configurable]] attribute must be true.
And Object.getOwnPropertyDescriptor is clearly defined to invoke [[GetOwnProperty]].
So when a page does Object.getOwnPropertyDescriptor(window, "foo") this is invoking the window proxy's [[GetOwnProperty]].
...but the window proxy's [[GetOwnProperty]] just forwards that straight to the Window's [[GetOwnProperty]].
The property is on the Window, not the WindowProxy. It can't disappear from the Window. The invariant is thus maintained.
There is no way to directly query the WindowProxy. To all intents and purposes, it's not a real object. It's a reference to another object, which happens to change during navigation.
On 1/14/15 3:17 PM, Ian Hickson wrote:
It's more than that. It's how the HTML spec defines WindowProxy.
The point is, the HTML spec's definition is not expressible in ES terms. So how do go about bridging this gap?
According to the HTML spec, all operations that would be performed on the WindowProxy object must be performed on the Window object of the browsing context's active document instead. So the above would set a property on the underlying Window object, not the WindowProxy.
It would call the [[DefineOwnProperty]] trap of the WindowProxy. That then forwards to the Window, yes?
...but the window proxy's [[GetOwnProperty]] just forwards that straight to the Window's [[GetOwnProperty]].
Yes, but since which window it forwards to changes you get an invariant violation for the WindowProxy object itself.
The property is on the Window, not the WindowProxy. It can't disappear from the Window. The invariant is thus maintained.
I think you misunderstand what the invariant is.
There is no way to directly query the WindowProxy.
It doesn't matter. The user sees the WindowProxy, not the Window. After you navigate, you still have the same WindowProxy (e.g. .contentWindow returns something that is === to the thing you had before you navigated). But properties it claimed to have that were non-configurable are now gone. That is precisely a violation of the invariants.
To all intents and purposes, it's not a real object.
It looks like an object and quacks like an object. Sorry, but it's an object as far as all consumers are concerned; they have no way to tell it apart from an object except maybe via these invariant violations. But then you've entered circular argument territory.
It's a reference to another object
JS doesn't have such a type in the language, sadly, so we can't model it that way for consumers.
Boris has this exactly right. Further, a malicious proxy handler can leverage the presence of a single object that violates these invariants into the creation of arbitrary other proxies objects that also violate these invariants. The key is that the enforcement of the invariants relies on the proxy's target being constrained by these invariants.
I didn't follow. Could you expand, assuming less background knowledge? Thanks.
From: Travis Leithead [mailto:travis.leithead at microsoft.com]
WindowProxies are going to be a challenge when it comes to a pure ES implementation.
Shouldn't be too hard to implement a Direct Proxy that follows this proto-spec: domenic/window
On Wed, 14 Jan 2015, Boris Zbarsky wrote:
On 1/14/15 3:17 PM, Ian Hickson wrote:
It's more than that. It's how the HTML spec defines WindowProxy.
The point is, the HTML spec's definition is not expressible in ES terms.
So how do go about bridging this gap?
I don't understand what you mean by "expressible in ES terms".
It's expressed in English.
According to the HTML spec, all operations that would be performed on the WindowProxy object must be performed on the Window object of the browsing context's active document instead. So the above would set a property on the underlying Window object, not the WindowProxy.
It would call the [[DefineOwnProperty]] trap of the WindowProxy. That then forwards to the Window, yes?
No. WindowProxy isn't an ES Proxy. (The term WindowProxy predates ES Proxy). When you have a WindowProxy object, it acts exactly like the Window object to which it currently points. It is indistinguishable from the Window object. When the WindowProxy changes what it's pointing at, it's exactly as if the browser had reached in and changed every WindowProxy that pointed to the former and made it point to the latter.
...but the window proxy's [[GetOwnProperty]] just forwards that straight to the Window's [[GetOwnProperty]].
Yes, but since which window it forwards to changes you get an invariant violation for the WindowProxy object itself.
I don't understand how that follows.
The property is on the Window, not the WindowProxy. It can't disappear from the Window. The invariant is thus maintained.
I think you misunderstand what the invariant is.
Maybe. Please elaborate.
There is no way to directly query the WindowProxy.
It doesn't matter. The user sees the WindowProxy, not the Window.
No. What the author has is a WindowProxy, but in every sense it acts like a Window. (Which Window it acts like changes occasionally.)
After you navigate, you still have the same WindowProxy (e.g. .contentWindow returns something that is === to the thing you had before you navigated).
You have no way to actually test this. Since every reference to the old Window is now a reference to the new Window, you have no way to test if the WindowProxy references something new. It's just like if the browser had reached in and changed all your references from under you.
But properties it claimed to have that were non-configurable are now gone. That is precisely a violation of the invariants.
Suppose you have this code:
// part 1 let a = {}; let b = {}; let c = a;
// part 2 c.foo = 1;
// part 3 c = b;
// part 4 console.log(c.foo);
Is it surprising that the log doesn't log "1"?
This is what is going on here, except that part 3 is done by the browser.
To all intents and purposes, it's not a real object.
It looks like an object and quacks like an object.
It looks and quacks like a Window.
It's a reference to another object
JS doesn't have such a type in the language, sadly, so we can't model it that way for consumers.
It turns out that it does have such a type. WindowProxy is it. I agree that that makes it a special snowflake.
On Wed, 14 Jan 2015, Mark S. Miller wrote:
Boris has this exactly right. Further, a malicious proxy handler can leverage the presence of a single object that violates these invariants into the creation of arbitrary other proxies objects that also violate these invariants. The key is that the enforcement of the invariants relies on the proxy's target being constrained by these invariants.
If you're exposing your Window object to untrusted code you are so far beyond losing that this is the least of your concerns.
On 1/14/15 5:58 PM, Travis Leithead wrote:
The other challenge that just came to mind, is the document object—at least in Gecko and IE: a document.write call will “re-init” the object (subbing out the old var for a new one)
What Gecko does on document.open is just create a new Window, just like navigation. This part is actually pretty clearly defined in the HTML spec.
On 1/14/15 7:40 PM, Ian Hickson wrote:
I don't understand what you mean by "expressible in ES terms".
The behavior of ES objects is expressed in terms of various internal methods. That's what you have to do to express what an ES object does.
No. WindowProxy isn't an ES Proxy.
It's an ES object.
It is indistinguishable from the Window object.
It's distinguishable in various ways, including things like "if I get this property from it in 5 seconds, will I get the same value as from the Window?" The answer to that is "maybe not".
When the WindowProxy changes what it's pointing at, it's exactly as if the browser had reached in and changed every WindowProxy that pointed to the former and made it point to the latter.
You say "every WindowProxy", but in practice in an ES implementation you have some object, it has some internal methods. This is the last time I'm bothering to go through this with you, since clearly we're getting nowhere, as I said in www.w3.org/Bugs/Public/show_bug.cgi?id=27128
I don't understand how that follows.
I don't understand how you can possibly not understand, except via your assumption that a WindowProxy is not an object.
I think you misunderstand what the invariant is.
Maybe. Please elaborate.
The internal methods of every object must have behavior that preserves the invariants.
For ES proxies, the internal methods are defined in the spec in a way that preserves the invariant.
For other ES objects they are also defined in the spec.
ES also allows other "exotic objects" that define some other behavior for those internal methods, but requires that the invariants be preserved.
It doesn't matter. The user sees the WindowProxy, not the Window.
No. What the author has is a WindowProxy, but in every sense it acts like a Window.
You mean in every sense except what property values you'll get five seconds from now.
But again, we agree the author has a WindowProxy. Good.
After you navigate, you still have the same WindowProxy (e.g. .contentWindow returns something that is === to the thing you had before you navigated).
You have no way to actually test this. Since every reference to the old Window is now a reference to the new Window, you have no way to test if the WindowProxy references something new.
=== on objects tests object identity. It's not affected by internal methods in any way. Therefore, if === returns true then you actually have the same object.
// part 1 let a = {}; let b = {}; let c = a; // part 2 c.foo = 1; // part 3 c = b; // part 4 console.log(c.foo);
Is it surprising that the log doesn't log "1"?
None of this affects the invariants involved.
This is what is going on here, except that part 3 is done by the browser.
No, because in the code above if I do |var d = c;| then the d won't change in part 3. Having an object with sane internal methods here is really much simpler than magic "update all the references" behavior. But again, I've said this to you numerous times.
Let me try it again: the fact that the definition in the HTML spec has not lead to interop is not an accident. It's not something browsers can sanely achieve interop on because it involves handwavy magic that they are extremely likely to interpret differently. On the other hand, defining a single object with internal methods that do what you want would be very clear to an DOM or JS engine implementor.
It looks and quacks like a Window.
No, it, doesn't, see above.
It turns out that it does have such a type. WindowProxy is it. I agree that that makes it a special snowflake.
My point is that this special snowflake is unnecessary and confusing.
- Boris Zbarsky wrote:
You say "every WindowProxy", but in practice in an ES implementation you have some object, it has some internal methods. This is the last time I'm bothering to go through this with you, since clearly we're getting nowhere, as I said in www.w3.org/Bugs/Public/show_bug.cgi?id=27128
What are the odds that the behavior observable by web pages can actually be defined sanely such that ES invariants and compatibility requirements are satisfied? www.w3.org/Bugs/Public/show_bug.cgi?id=27128#c15 indicates, as I understand it, the odds may be quite good. In that case, looking for a volunteer to come up with a proposal might be a good next step.
Domenic's domenic/window-proxy-spec seems headed in the right direction. I suggest starting there.
Test proposals by self-hosting in ES6. Such self hosting would use direct proxies not because the term "proxy" in WindowProxy has any historical relation -- it does not -- but because the rest of ES6 is not powerful enough to self-host its behavior.
On 1/14/15 8:49 PM, Travis Leithead wrote:
and existing elements and their document are disconnected from the window, while a new document and the new elements created from parsing are put in their place.
More precisely, the old elements are disconnected from the document, the document is disconnected from the window, and the document is connected to the new window.
Implementations differ on how they handle the scenario. Boris notes that Gecko just creates a new window (with a new document, etc.)
No, it's the same document.
Boris can correct me if I got this wrong.
You got it wrong. ;)
On 1/14/15 8:56 PM, Bjoern Hoehrmann wrote:
What are the odds that the behavior observable by web pages can actually be defined sanely such that ES invariants and compatibility requirements are satisfied?
My personal best estimate is about 30% chance for the one such definition we have so far being web compatible. But this is mostly based on gut feeling.
www.w3.org/Bugs/Public/show_bug.cgi?id=27128#c15 indicates, as I understand it, the odds may be quite good. In that case, looking for a volunteer to come up with a proposal might be a good next step.
We have a proposal. It's at domenic/window-proxy-spec
I have an implementation that I've been trying to land in Firefox for a few weeks. I had to fix a bunch of our tests, then restrict the change to web pages because it breaks various extensions. I'm pretty hopeful that I can at least get it to the point where I can try doing some web compat testing...
bugzilla.mozilla.org/show_bug.cgi?id=1107443 for those who want to follow along.
On Wed, 14 Jan 2015, Boris Zbarsky wrote:
The behavior of ES objects is expressed in terms of various internal methods. That's what you have to do to express what an ES object does.
Not necessarily. We can also just say "WindowProxy is its own thing and here is how it works".
No. WindowProxy isn't an ES Proxy.
It's an ES object.
That's debatable.
It is indistinguishable from the Window object.
It's distinguishable in various ways, including things like "if I get this property from it in 5 seconds, will I get the same value as from the Window?" The answer to that is "maybe not".
No, because by the time it returns a different value, it no longer acts like it's the same object.
You're holding a magical thing that looks and acts exactly like a potted plant. It's indistinguishable from a potted plant. Then five seconds later, you're holding a cat. It's indistinguishable from a cat. You can't tell that the potted plant and the cat are the same thing. You can presume that since you were holding one, and then you were holding the other, they might have some relationship, but there's no way to tell that they're the same thing. Even if the assume they're the same thing, you can't tell the difference between the being the same thing, and someone just quickly stealing the potted plant and replacing it with a cat.
When the WindowProxy changes what it's pointing at, it's exactly as if the browser had reached in and changed every WindowProxy that pointed to the former and made it point to the latter.
You say "every WindowProxy", but in practice in an ES implementation you have some object, it has some internal methods. This is the last time I'm bothering to go through this with you, since clearly we're getting nowhere, as I said in www.w3.org/Bugs/Public/show_bug.cgi?id=27128
You are dismissing what I'm describing as being a misunderstanding of how things should be considered to work, because they're not the same as what you're describing. But maybe what I'm describing is how things should be considered to work, and what you're describing is wrong. Or maybe both our points of view are valid, and we should figure out which is easiest to describe, or easiest to implement, or simplest to explain to authors, or most compatible with the Web, or least likely to involve contortions around unnecessary invariants, or whatever other priorities we want to apply to this decision.
The internal methods of every object must have behavior that preserves the invariants.
For ES proxies, the internal methods are defined in the spec in a way that preserves the invariant.
For other ES objects they are also defined in the spec.
ES also allows other "exotic objects" that define some other behavior for those internal methods, but requires that the invariants be preserved.
So one option would be to just say that WindowProxy is not an ES object.
It doesn't matter. The user sees the WindowProxy, not the Window.
No. What the author has is a WindowProxy, but in every sense it acts like a Window.
You mean in every sense except what property values you'll get five seconds from now.
Five seconds from now, the author will in every sense appear to have a different Window. It still appears to be a Window. Just a different one.
After you navigate, you still have the same WindowProxy (e.g. .contentWindow returns something that is === to the thing you had before you navigated).
You have no way to actually test this. Since every reference to the old Window is now a reference to the new Window, you have no way to test if the WindowProxy references something new.
=== on objects tests object identity.
Except on WindowProxy, because WindowProxy forwards all operations to the underlying Window.
It's not affected by internal methods in any way. Therefore, if === returns true then you actually have the same object.
Consider:
let a = {}; let b = {};
let c = a; let d = a;
assert(c === d); // true
opaqueCode();
assert(c === d); // still true
If the opaqueCode() function just does:
function opaqueCode() { c = b; d = b; }
...then === doesn't help you tell that c and d have changed identity. They are still triple-equals to each other, but they're different objects.
This is exactly what happens with WindowProxy, except that the browser is the code doing opaqueCode().
// part 1 let a = {}; let b = {}; let c = a; // part 2 c.foo = 1; // part 3 c = b; // part 4 console.log(c.foo);
Is it surprising that the log doesn't log "1"?
None of this affects the invariants involved.
The point is that the invariants apply to the actual objects originally assigned to a and b, they don't apply to the variables.
This is what is going on here, except that part 3 is done by the browser.
No, because in the code above if I do |var d = c;| then the d won't change in part 3.
If in the code above you do var d = c, then add d = b to part 3 to simulate what the browser does.
Having an object with sane internal methods here is really much simpler than magic "update all the references" behavior. But again, I've said this to you numerous times.
I'm not actually proposing updating all the references. That's just a convenient way to think about it that is isomorphic in behaviour to what is specced. What is specced is just that you have a placeholder pseudo-object that forwards all behaviour to an underlying object, where which object it's forwarding to can change over time.
Let me try it again: the fact that the definition in the HTML spec has not lead to interop is not an accident. It's not something browsers can sanely achieve interop on because it involves handwavy magic that they are extremely likely to interpret differently. On the other hand, defining a single object with internal methods that do what you want would be very clear to an DOM or JS engine implementor.
I have asked before, but would like to reiterate:
If there is any behaviour that is underdefined by the HTML spec's current prose, please tell me, so that I can spec it.
It looks and quacks like a Window.
No, it, doesn't, see above.
It is literally indistinguishable from a Window in every way at all times. That's the whole point. Which Window it appears to be indistinguishable from at any particular time varies over time.
It turns out that it does have such a type. WindowProxy is it. I agree that that makes it a special snowflake.
My point is that this special snowflake is unnecessary and confusing.
Well it predates all the ES6 stuff we're talking about here by several years, so I'd argue that the confusion doesn't stem from WindowProxy. I also don't really agree that it's confusing. It's really simple to explain, certainly much simpler than anything involving ES proxies and so forth. It's one sentence in the spec, and as far as I'm aware, it doesn't leave anything undefined.
I also disagree that what the spec says doesn't match implementations. I think it matches implementations pretty darn closely. I mean, in particular, it matches exactly what Travis described as IE's implementation. (This is not an accident; matching implementations was one of the main goals of the HTML spec, and at the time this was specced, IE was by far the biggest UA out there.)
bz said:
ES also allows other "exotic objects" that define some other behavior for those internal methods, but requires that the invariants be preserved.
Hixie said:
So one option would be to just say that WindowProxy is not an ES object.
If it's available to JavaScript consumers it must look like an object that obeys ES invariants to JavaScript consumers. No (new, ugh) exceptions.
On Jan 14, 2015, at 7:14 PM, Ian Hickson <ian at hixie.ch> wrote:
On Wed, 14 Jan 2015, Boris Zbarsky wrote:
The behavior of ES objects is expressed in terms of various internal methods. That's what you have to do to express what an ES object does.
Not necessarily. We can also just say "WindowProxy is its own thing and here is how it works".
No. WindowProxy isn't an ES Proxy.
It's an ES object.
That's debatable.
It is indistinguishable from the Window object.
It's distinguishable in various ways, including things like "if I get this property from it in 5 seconds, will I get the same value as from the Window?" The answer to that is "maybe not".
No, because by the time it returns a different value, it no longer acts like it's the same object.
You're holding a magical thing that looks and acts exactly like a potted plant. It's indistinguishable from a potted plant. Then five seconds later, you're holding a cat. It's indistinguishable from a cat. You can't tell that the potted plant and the cat are the same thing. You can presume that since you were holding one, and then you were holding the other, they might have some relationship, but there's no way to tell that they're the same thing. Even if the assume they're the same thing, you can't tell the difference between the being the same thing, and someone just quickly stealing the potted plant and replacing it with a cat.
When the WindowProxy changes what it's pointing at, it's exactly as if the browser had reached in and changed every WindowProxy that pointed to the former and made it point to the latter.
You say "every WindowProxy", but in practice in an ES implementation you have some object, it has some internal methods. This is the last time I'm bothering to go through this with you, since clearly we're getting nowhere, as I said in www.w3.org/Bugs/Public/show_bug.cgi?id=27128
You are dismissing what I'm describing as being a misunderstanding of how things should be considered to work, because they're not the same as what you're describing. But maybe what I'm describing is how things should be considered to work, and what you're describing is wrong. Or maybe both our points of view are valid, and we should figure out which is easiest to describe, or easiest to implement, or simplest to explain to authors, or most compatible with the Web, or least likely to involve contortions around unnecessary invariants, or whatever other priorities we want to apply to this decision.
The internal methods of every object must have behavior that preserves the invariants.
For ES proxies, the internal methods are defined in the spec in a way that preserves the invariant.
For other ES objects they are also defined in the spec.
ES also allows other "exotic objects" that define some other behavior for those internal methods, but requires that the invariants be preserved.
So one option would be to just say that WindowProxy is not an ES object.
It doesn't matter. The user sees the WindowProxy, not the Window.
No. What the author has is a WindowProxy, but in every sense it acts like a Window.
You mean in every sense except what property values you'll get five seconds from now.
Five seconds from now, the author will in every sense appear to have a different Window. It still appears to be a Window. Just a different one.
After you navigate, you still have the same WindowProxy (e.g. .contentWindow returns something that is === to the thing you had before you navigated).
You have no way to actually test this. Since every reference to the old Window is now a reference to the new Window, you have no way to test if the WindowProxy references something new.
=== on objects tests object identity.
Except on WindowProxy, because WindowProxy forwards all operations to the underlying Window.
It's not affected by internal methods in any way. Therefore, if === returns true then you actually have the same object.
Consider:
let a = {}; let b = {};
let c = a; let d = a;
assert(c === d); // true
opaqueCode();
assert(c === d); // still true
If the opaqueCode() function just does:
function opaqueCode() { c = b; d = b; }
...then === doesn't help you tell that c and d have changed identity. They are still triple-equals to each other, but they're different objects.
Are you saying that it’s easier to spec having the browser find all variables (local or otherwise) that reference some object and replace them with references to a different object?
On 1/14/15 10:14 PM, Ian Hickson wrote:
You are dismissing what I'm describing as being a misunderstanding of how things should be considered to work,
No, I'm dismissing it as introducing magic that doesn't need to be introduced and hence making the platform more complicate than it needs to be, for no benefit I can perceive.
I do understand what you're describing, believe me. I just have no plans to implement it or ask anyone else to implement it.
Or maybe both our points of view are valid, and we should figure out which is easiest to describe, or easiest to implement, or simplest to explain to authors, or most compatible with the Web, or least likely to involve contortions around unnecessary invariants, or whatever other priorities we want to apply to this decision.
Yes, that's what I've been saying all along.
I will claim that my description is easier to describe rigorously, easier to implement, probably just as nasty to explain to authors, can completely replicate the observable behavior of your description if needed (e.g. by just directly forwarding all internal methods to the Window, at the cost of violating the invariants this thread is about), so can be just as compatible with the web, and gives at least a chance of preserving invariants people care about if that's what people want to do.
Whereas your description basically forecloses all discussion on how things should behave, because it locks them in to behaving in the single way you like; doing any other behavior requires going to a different model.
ES also allows other "exotic objects" that define some other behavior for those internal methods, but requires that the invariants be preserved.
So one option would be to just say that WindowProxy is not an ES object.
You were talking about explaining something to authors or making it implementable, yes?
Except on WindowProxy, because WindowProxy forwards all operations to the underlying Window.
There is no "operation" to forward here. === is literally just bitwise memory comparison equality in implementations.
If the opaqueCode() function just does:
function opaqueCode() { c = b; d = b;
It doesn't have c and d in its scope, so can't do that.
And yes, you're going to say that the browser has everything "in scope", but the point is that your model involves some sort of non-local complicated heap-walking effect whereas changing the target of a proxy ... not that.
The point is that the invariants apply to the actual objects originally assigned to a and b, they don't apply to the variables.
"variables" don't even exist as a useful concept by the time you're running the code. You've got values on the heap, on the stack, in registers, etc, etc. Just saying.
No, because in the code above if I do |var d = c;| then the d won't change in part 3.
If in the code above you do var d = c, then add d = b to part 3 to simulate what the browser does.
Yes, I know that's your mental model. My point is that no sane implementation would possibly want to actually implement it that way, whereas implementing it in terms of internal methods is what at least some implementations actually do.
I'm not actually proposing updating all the references. That's just a convenient way to think about it
Except it's not a convenient way to think about it!
What is specced is just that you have a placeholder pseudo-object
Why is it a pseudo-object when an object would do?
I have asked before, but would like to reiterate:
If there is any behaviour that is underdefined by the HTML spec's current prose, please tell me, so that I can spec it.
Care to ask the UA implementors who are clearly not doing anything even resembling your spec? Because it might turn out they might have reasons for it...
Well it predates all the ES6 stuff we're talking about here by several years
All the "stuff" involved was in ES5. It's not new in ES6. I've also pointed this out to you several times now.
it doesn't leave anything undefined.
Implementations seem to disagree by their actions, sadly.
I also disagree that what the spec says doesn't match implementations.
Uh... Have you tried testing this stuff in Safari? I have; please see archives for the results. They look nothing like your spec.
On Wed, Jan 14, 2015 at 7:14 PM, Ian Hickson <ian at hixie.ch> wrote:
On Wed, 14 Jan 2015, Boris Zbarsky wrote:
The behavior of ES objects is expressed in terms of various internal methods. That's what you have to do to express what an ES object does.
Not necessarily. We can also just say "WindowProxy is its own thing and here is how it works".
No. WindowProxy isn't an ES Proxy.
It's an ES object.
That's debatable.
In the sense that we're debating it, sure.
WindowProxy is however a first class value observable by JS code, and therefore covered by the ES specs. Further, the various meta-object operations like Object.defineProperty accept it as an argument. If it isn't even an object, why is it observable all? Why do methods specified to operate on object arguments even accept it? Starting from your stance, I'd argue that all these operations should at least reject it with a TypeError.
Neither does the magic replacement theory excuse it from violating the spec. See esdiscuss/2011-May/014150 , where we first clarified the difference between momentary and eternal invariants. Let's test the invariant stated in that message as
The ES5 constraints on updating non configurable properties are eternal invariants.
by writing test2c, in the same spirit as test2a and test2b:
(function(){"use strict";
// assume "gopd" & "is" initialization runs first, so these are // the original functions. var gopd = Object.getOwnPropertyDescriptor; var is = Object.is;
function test2c(obj, name, interleave) { name = String(name); var desc1 = gopd(obj, name); interleave(); var desc2 = gopd(obj, name); if (!desc1.configurable) { assertFalse(desc2.configurable); assertTrue(desc2.enumerable === desc2.enumerable); if (gopd(desc1, 'writable') && !desc1.writable) { assertFalse(desc2.writable); assertTrue(is(desc1.value, desc2.value)); } } }
return test2c;
}());
In a conforming ES6 implementation (or a conforming ES5 one given the std "is" polyfill), it must not be possible to call test2c such that these asserts fail. No where do these specs say "unless interleave() replaces all pointers to obj to point at a different object". If it did, the whole notion of eternal invariants would be nonsense.
On 12/4/14 5:58 AM, Boris Zbarsky wrote:
OK. What do we do if we discover that throwing from the defineProperty call with a non-configurable property descriptor is not web-compatible?
Based on a nice long conversation Yehuda and I just had, if we end up there and really want to preserve all the current invariants we could try a hybrid approach inspided by the Safari implementation. To whit:
-
Assume that we can distinguish when we're "inside" a window and when we're "outside" it. For example, compare the current Realm's global to the window.
-
When inside the window, everything works as normal: you can define a non-configurable property on the window you're inside, getOwnPropertyDescriptor will claim it's non-configurable, etc. At least while that window is current. Once it's non-current, I haven't tested what Safari does and am not sure what we could/should spec.
-
When outside the window, where observing navigations is easy, we try to apply one of the possible mitigations: throw on defineProperty that's non-configurable or whatnot, report all props as configurable, etc. This seems to be all kosher if we assume a membrane between inside and outside the window, such that we're actually operating on different objects in the two cases...
This may mitigate the compat impact depending on what compat issues we run into.
Just wanted to get this written down while it's fresh in my mind, Boris
Thanks. It is great to accumulate such retreats in case we need them. However, it is also good to remember that, should we run into problems, that experience will give us specific data that we might not have been able to anticipate. The least painful retreat may very well take that specific data into account.
The case of Object.prototype.toString.call(null) was an informative precedent.
On Thu, 15 Jan 2015, Yehuda Katz wrote:
If it's available to JavaScript consumers it must look like an object that obeys ES invariants to JavaScript consumers. No (new, ugh) exceptions.
This isn't a new exception. It predates the invariants by years.
On Wed, 14 Jan 2015, Filip Pizlo wrote:
Are you saying that it’s easier to spec having the browser find all variables (local or otherwise) that reference some object and replace them with references to a different object?
Easier than what?
What I'm proposing is the following text, which has been in the spec for years: "all operations that would be performed on it must be performed on the Window object of the browsing context's active document instead"
It doesn't talk about finding variables or anything, though it's isomorphic with doing that in practice, and talking about WindowProxy objects as if they were assigned to variables whose values changed is easier for me to describe to authors than the forwarding behaviour.
On Wed, 14 Jan 2015, Boris Zbarsky wrote:
My point is that no sane implementation would possibly want to actually implement it that way
It's what IE does, according to Travis.
If there is any behaviour that is underdefined by the HTML spec's current prose, please tell me, so that I can spec it.
Care to ask the UA implementors who are clearly not doing anything even resembling your spec? Because it might turn out they might have reasons for it...
I've asked you many times.
Well it predates all the ES6 stuff we're talking about here by several years
All the "stuff" involved was in ES5. It's not new in ES6. I've also pointed this out to you several times now.
I don't really care about the version number. The HTML side of this dates back to the ES3 days.
it doesn't leave anything undefined.
Implementations seem to disagree by their actions, sadly.
Implementations disagreeing with each other doesn't mean the spec is undefined. It's at least as common for the implementations to just not match the spec.
I also disagree that what the spec says doesn't match implementations.
Uh... Have you tried testing this stuff in Safari? I have; please see archives for the results. They look nothing like your spec.
It doesn't match all implementations, certainly.
On Wed, 14 Jan 2015, Mark S. Miller wrote:
WindowProxy is however a first class value observable by JS code
My point is that it's not. WindowProxy can literally not be observed. You can only ever observe the Window it points to, and the only way to tell that it changes Window is through circumstancial evidence (e.g. the properties change).
Neither does the magic replacement theory excuse it from violating the spec.
Based on when the spec prose was written, the ES spec is violating the HTML spec here, not vice versa.
The ES5 constraints on updating non configurable properties are eternal invariants.
Yes. Nothing here is violating these invariants. The invariants apply to the Window object.
by writing test2c, in the same spirit as test2a and test2b:
(function(){"use strict";
// assume "gopd" & "is" initialization runs first, so these are // the original functions. var gopd = Object.getOwnPropertyDescriptor; var is = Object.is;
function test2c(obj, name, interleave) { name = String(name); var desc1 = gopd(obj, name); interleave(); var desc2 = gopd(obj, name); if (!desc1.configurable) { assertFalse(desc2.configurable); assertTrue(desc2.enumerable === desc2.enumerable); if (gopd(desc1, 'writable') && !desc1.writable) { assertFalse(desc2.writable); assertTrue(is(desc1.value, desc2.value)); } } }
return test2c;
}());
In a conforming ES6 implementation (or a conforming ES5 one given the std "is" polyfill), it must not be possible to call test2c such that these asserts fail. No where do these specs say "unless interleave() replaces all pointers to obj to point at a different object". If it did, the whole notion of eternal invariants would be nonsense.
Per the HTML spec, there's two ways that WindowProxy can change. One is session history traversal. That can never happen with JS on the stack, so it can't violate your example above.
The other is document.open(). Behaviour around that is less interoperable, and the current behaviour of replacing the singletons might be something we can change, I dunno. From my understanding of what Travis described, IE behaves exactly like the spec says, which would cause the above to assert false. Firefox certainly does currently:
software.hixie.ch/utilities/js/live-dom-viewer/?saved=3369
Chrome and Safari don't seem to replace the Window when you do document.open(), which seems likely to be the source of bugs. I'm surprised I haven't noticed it before. I would have assumed pages would depend on the previous document's fiddling with Window to be removed.
On 1/15/15 1:22 PM, Ian Hickson wrote:
It's what IE does, according to Travis.
I'll let Travis address this.
Care to ask the UA implementors who are clearly not doing anything even resembling your spec? Because it might turn out they might have reasons for it...
I've asked you many times.
I'm not one of those implementors. What Gecko does right now is more or less black-box indistinguishable from your spec, modulo cross-origin issues. However we may be changing what we do; we'll see.
Implementations disagreeing with each other doesn't mean the spec is undefined.
But it can well mean they're not willing to implement it as written.
It doesn't match all implementations, certainly.
OK, but have you bothered to find out why?
Per the HTML spec, there's two ways that WindowProxy can change. One is session history traversal. That can never happen with JS on the stack
Are you sure, given showModalDialog?
On Thu, 15 Jan 2015, Boris Zbarsky wrote:
It doesn't match all implementations, certainly.
OK, but have you bothered to find out why?
The usual reason is just that there was no spec initially, and that the relevant implementations haven't bothered to fix it since the spec was written.
Per the HTML spec, there's two ways that WindowProxy can change. One is session history traversal. That can never happen with JS on the stack
Are you sure, given showModalDialog?
I'm assuming that showModalDialog() is dead. showModalDialog() breaks every assumption under the sun and is generally bad news.
On 12/4/14 11:49 AM, Mark S. Miller wrote:
On Thu, Dec 4, 2014 at 2:58 AM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
OK. What do we do if we discover that throwing from the defineProperty call with a non-configurable property descriptor is not web-compatible?
What we always do
So just for the record, jQuery (at least all the 2.* versions I've looked at) contains that following bits:
Data.prototype = {
key: function( owner ) {
...
var descriptor = {},
...
// Secure it in a non-enumerable, non-writable property
try {
descriptor[ this.expando ] = { value: unlock };
Object.defineProperties( owner, descriptor );
// Support: Android < 4
// Fallback to a less secure definition
} catch ( e ) {
descriptor[ this.expando ] = unlock;
jQuery.extend( owner, descriptor );
}
This function is called from Data.prototype.get, which is called from jQuery.event.add. So the upshot is that trying to add an event listener to the window via the jQuery API will hit this codepath.
Now the good news is that the try/catch is present there, so this doesn't immediately break sites. But it's something to watch out for, and we will be changing the behavior of jQuery here in a way that the jQuery developers clearly think is undesirable.
Yehuda and I just talked about this code and realized that we can allow this code to proceed on the success path without violating the invariants. However, this analysis reveals that the intent stated in the comment is unwarranted, but even that intent can be adequately honored in the scenario of interest.
The reason why the intent is unwarranted is that the descriptor omits "configurable:" rather than explicitly saying "configurable: true". If the owner object already has a configurable own property of the same name, then a defineProperty where the "configurable:" is omitted defines an own property preserving the configurability of the original own property.
Even if owner could not have already had an own property of this name, owner might be an ES6 proxy whose target is, say, an empty object. The handler's defineProperty trap could still first define a configurable own property of this name, and then proceed with the normal logic.
Since the WindowProxy is not a Proxy, or more relevantly, even if it were a Proxy, the underlying Window is not its target, we can even do the following:
When the WindowProxy sees the defineProperty with the omitted "configurable:" and determines that the underlying Window does not already have this property. WindowProxy can even preserve the unwarranted intent expressed in the comment by actually defining a non-configurable own property on the Window itself. However, the behavior of the WindowProxy is not observably different than our Proxy example: It acts as if it created a configurable own property on the WindowProxy of this same name, and then proceeds with the normal defineProperty behavior, which preserves that alleged configurability.
I'd like to understand better the suggestion here, because I'm not sure I'm entirely following it. Specifically, I'd like to understand it in terms of the internal methods defined by domenic/window-proxy-spec.
Presumably you're proposing that we keep all of that as-is except for [[DefineOwnProperty]], right?
For [[DefineOwnProperty]], are we basically talking about changing step 1 to:
- If the [[Configurable]] field of Desc is present and Desc.[[Configurable]] is false, then throw a TypeError exception.
while keeping everything else as-is, as opposed to the behavior I'd understood we were aiming for, which was:
- If the [[Configurable]] field of Desc is not present or Desc.[[Configurable]] is false, then throw a TypeError exception.
? If so, that's certainly a change that is much more likely to be web-compatible...
Mark S. Miller wrote:
The reason why the intent is unwarranted is that the descriptor omits "configurable:" rather than explicitly saying "configurable: true". If the owner object already has a configurable own property of the same name, then a defineProperty where the "configurable:" is omitted defines an own property preserving the configurability of the original own property.
Wild, and genius. How many more narrow escapes can we make and keep both web compat and integrity? :-P
Is there any downside? What is the bad case that observably changes behavior, if any (not involving proxies)? I'm too tired to search the state space right now, throwing this out as a challenge.
On Tue, Jan 27, 2015 at 5:53 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
For [[DefineOwnProperty]], are we basically talking about changing step 1 to:
- If the [[Configurable]] field of Desc is present and Desc.[[Configurable]] is false, then throw a TypeError exception.
while keeping everything else as-is,
Exactly correct. I didn't realize until reading your reply is that this is all that's necessary -- that it successfully covers all the cases I was thinking about without any further case division.
? If so, that's certainly a change that is much more likely to be web-compatible...
Good! It certainly takes care of the one concrete breakage we know about so far.
On Tue, Jan 27, 2015 at 7:22 PM, Brendan Eich <brendan at mozilla.org> wrote:
Wild, and genius.
(blush)
How many more narrow escapes can we make and keep both web compat and integrity? :-P
How many will we need? ;)
Is there any downside? What is the bad case that observably changes behavior, if any (not involving proxies)?
You get the following non-intuitive but allowed behavior.
if (!hasOwnProperty(W, P)) {
defineProperty(W, P, { value: V })
console.log(getOwnPropertyDescriptor(W, P).configurable); // true
}
However, you could also get this behavior if W is a proxy, so it doesn't introduce any new cases beyond what's already possible. It is only surprising.
That's not much of a downside, and I can't think of any other downside.
I'm too tired to search the state space right now, throwing this out as a challenge.
On Tue, Jan 27, 2015 at 9:45 PM, Mark S. Miller <erights at google.com> wrote:
Exactly correct. I didn't realize until reading your reply is that this is all that's necessary -- that it successfully covers all the cases I was thinking about without any further case division.
Here's another option, not clearly better or worse:
[[DefineOwnProperty]] (P, Desc)
- let R be the result of calling the [[DefineOwnProperty]] internal method of W with arguments P and Desc.
- If desc.[[Configurable]] is present and false, then throw a TypeError exception.
- return R.
This is exactly like your solution, but with the order of the two steps switched. Perhaps the next breakage we see will tell us which of these to choose. If both are web compatible, then we need only pick which one we like better.
From: Mark S. Miller [mailto:erights at google.com]
Exactly correct. I didn't realize until reading your reply is that this is all that's necessary -- that it successfully covers all the cases I was thinking about without any further case division.
I'm having a bit of trouble understanding how this maps to the solution described in your previous message, Mark. Your "I didn't realize until reading your reply is that this is all that's necessary" indicates I'm probably just missing something, so help appreciated.
My question is, what happens if Desc.[[Configurable]] is not present, and P does not already exist on W? By my reading, we then fall through to calling the [[DefineOwnProperty]] internal method of W with arguments P and Desc.
Assuming W's [[DefineOwnProperty]] is that of an ordinary object, I believe that takes us through OrdinaryDefineOwnProperty(W, P, Desc). Since P does not exist on W, and W is extensible, that takes us to ValidateAndApplyPropertyDescriptor(O, P, true, Desc, undefined). Then according to step 2.c, " If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value." The default value is false, right? So won't this try to define a non-configurable property on W?
I would have thought the modification needed to be more like:
[[DefineOwnProperty]] (P, Desc)
- If desc.[[Configurable]] is not present, set desc.[[Configurable]] to true.
- If desc.[[Configurable]] is false, then throw a TypeError exception.
- Return the result of calling the [[DefineOwnProperty]] internal method of W with arguments P and Desc.
(here I have inserted step 1, but step 2 and 3 are unchanged from the previous incarnation).
On Wed, Jan 28, 2015 at 8:51 AM, Domenic Denicola <d at domenic.me> wrote:
Assuming W's [[DefineOwnProperty]] is that of an ordinary object, I believe that takes us through OrdinaryDefineOwnProperty(W, P, Desc). Since P does not exist on W, and W is extensible, that takes us to ValidateAndApplyPropertyDescriptor(O, P, true, Desc, undefined). Then according to step 2.c, " If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value." The default value is false, right? So won't this try to define a non-configurable property on W?
In this situation, it will try and succeed. This more closely obeys the intent in the original code (e.g., the comment in the jQuery code), since it creates a non-configurable property on the Window W. It does not violate any invariant, since all that's observable on the WindowProxy (given the rest of your draft spec, which remain unchanged) is a configurable property of the same name.
Ah, I see! So then another non-intuitive (but invariant-preserving) consequence would be:
Object.defineProperty(window, "prop", { value: "foo" });
var propDesc = Object.getOwnPropertyDescriptor(window, "prop");
if (propDesc.configurable) {
Object.defineProperty(window, "prop", { value: "bar" });
// this will fail, even though the property is supposedly configurable,
// since when it forwards from the WindowProxy `window` to the underlying
// Window object, the Window's [[DefineOwnProperty]] fails.
}
Am I getting this right?
On Wed, Jan 28, 2015 at 11:08 AM, Domenic Denicola <d at domenic.me> wrote:
Am I getting this right?
Exactly, yes. And again, if window is an ES6 proxy rather that a WindowProxy, it could also cause this behavior, so it doesn't create any situation which is not otherwise possible.
The key points are:
-
The throw does (arguably) better obey the code's intent, since the property mostly acts like a non-configurable property until the window is navigated.
-
If a window navigation happens between your first step and your second, the second step may well succeed, which is what we (arguably) want, but which would have been prohibited if propDesc.configurable evaluated to true.
I like the shorter one (filling in from cited text below, here it is in full:
[[DefineOwnProperty]] (P, Desc)
- If desc.[[Configurable]] is present and desc.[[Configurable]] is false, then throw a TypeError exception.
- Return the result of calling the [[DefineOwnProperty]] internal method of W with arguments P and Desc.
Besides being shorter, this doesn't call through to [[DOP]], which could have effects, and only then maybe-throw.
[ccing public-script-coord because I'm not sure what list is best for this; mostly I'm looking for feedback from other UA implementors.]
We really need to create an actual specification for WindowProxy. One of the serious problems is what to do with non-configurable properties.
Consider this testcase (live version at web.mit.edu/bzbarsky/www/testcases/windowproxy/non-configurable-props-1.html with slightly different logging):
The console in Firefox nightly and Chrome dev shows:
The console in Safari 7 and WebKit nightly shows:
The console in IE 11 shows:
and if I examine the actual descriptor returned, .configurable is false. No exceptions are thrown by any of the browsers.
As I understand the ES spec, none of these browsers are enforcing the fundamental invariants: three of them because they have a non-configurable property go away and one because it silently doesn't define a non-configurable property when you try to do it.
Though the Safari behavior is actually quite interesting. web.mit.edu/bzbarsky/www/testcases/windowproxy/non-configurable-props-2.html shows that if the property is defined from inside the subframe then getOwnPropertyDescriptor does not see it from the outside, even though from the inside it's visible. And web.mit.edu/bzbarsky/www/testcases/windowproxy/non-configurable-props-3.html shows that when defining from "outside" the property is in fact being defined as far as scripts "inside" are concerned.
Oh, and getting .foo from "outside" returns 1, but doing getOwnPropertyDescriptor up the proto chain of "subframe" consistently returns undefined in Safari.
This last bit has nothing to do with configurability, by the way Object.getOwnPropertyDescriptor returns undefined from the "outside" in general in Safari.
Per spec ES6, it seems to me like attempting to define a non-configurable property on a WindowProxy should throw and getting a property descriptor for a non-configurable property that got defined on the Window (e.g. via "var") should report it as configurable. But that matches precisely 0 UAs.... and throwing seems like a compat worry. :(
Anyway, what are reasonable behaviors here? What are UAs willing to align on?