The global object in browsers
On Tue, Feb 17, 2009 at 2:02 PM, Ian Hickson <ian at hixie.ch> wrote:
For HTML5, this behaviour has been defined in more detail. The global object is a Window object. This object is per-Document. The object returned by the "window" attribute on that global object is actually a WindowProxy object, which forwards everything to the "current" Window object. However, doing this has required that I require browsers to violate the requirement that the ES3 spec has, namely that "this" and the object at the top of the scope chain are both the global object, because in this model an invariant is that script cannot access the actual global object directly, only the proxy. The HTML5 spec says:
If the script's global object is a Window object, then in JavaScript, the this keyword in the global scope must, contrary to the ECMAScript specification, return the Window object's WindowProxy object.
If it would be possible for the ECMAScript specification to have a hook that allowed me to require this without violating the spec, that would be great.
I don't understand. If the object you're calling Window is inaccessible from ES code, and if the object you're calling WindowProxy forwards everything to your Window, why not just relabel Window -> InternalWindow, WindowProxy -> Window? And in any case, why
not just provide your WindowProxy as the global object to ES code? Why does ES need to be aware of your Window at all?
The deeper problem here is that ES specs to date -- including the draft ES3.1 spec -- have not yet admitted the existence of multiple global objects. We all know we need to, but it is way too late to consider such a change for ES3.1. For ES-Harmony, I think we all agree we will take this step. Also, the draft module proposal at docs.google.com/Doc?id=dfgxb7gk_34gpk37z9v, proposed by Ihab
Awad and Kris Kowal for ES-Harmony, relies on the introduction of a "hermetic eval" which omits the global object from the bottom of the scope chain.
On Tue, Feb 17, 2009 at 2:02 PM, Ian Hickson <ian at hixie.ch> wrote:
Now, if the other page's script calls f() and g(), it will get different results (2 and 1 respectively, if I didn't screw up the example code).
For HTML5, this behaviour has been defined in more detail. The global object is a Window object. This object is per-Document. The object returned by the "window" attribute on that global object is actually a WindowProxy object, which forwards everything to the "current" Window object.
What do you mean by "current"? Are you proposing to legitimize the dynamic scoping behavior demonstrated by your example?
If all major browsers agree on this bizarre behavior, we will indeed be stuck. But if some existing browsers use lexical capture (i.e., return 1 in both cases), then ES-Harmony should feel free to specify that. What do each of the major browsers do?
On Tue, 17 Feb 2009, Mark S. Miller wrote:
I don't understand. If the object you're calling Window is inaccessible from ES code, and if the object you're calling WindowProxy forwards everything to your Window, why not just relabel Window -> InternalWindow, WindowProxy -> Window?
I don't really mind what the objects are called, the point is just that the object at the top of the scope chain is not the same as the object returned by "this" (or "window" on the global object).
And in any case, why not just provide your WindowProxy as the global object to ES code? Why does ES need to be aware of your Window at all?
When a browsing context navigates from page A to page B, the object at the top of the scope chain in code from page A and the oject at the top of the scope chain in code from page B are not the same object, but the object returned by the global-scope "this" in scripts from A and B are the same object (===).
The deeper problem here is that ES specs to date -- including the draft ES3.1 spec -- have not yet admitted the existence of multiple global objects. We all know we need to, but it is way too late to consider such a change for ES3.1.
Ok. This unfortunately leaves us in the status quo position where HTML5 has to require something that violates the ES specs.
On Tue, 17 Feb 2009, Mark Miller wrote:
On Tue, Feb 17, 2009 at 2:02 PM, Ian Hickson <ian at hixie.ch> wrote:
Now, if the other page's script calls f() and g(), it will get different results (2 and 1 respectively, if I didn't screw up the example code).
For HTML5, this behaviour has been defined in more detail. The global object is a Window object. This object is per-Document. The object returned by the "window" attribute on that global object is actually a WindowProxy object, which forwards everything to the "current" Window object.
What do you mean by "current"? Are you proposing to legitimize the dynamic scoping behavior demonstrated by your example?
I'm not really trying to "legitimize" anything so much as accurately describe the status quo so that new browsers can be written without having to reverse engineer other browsers.
If all major browsers agree on this bizarre behavior, we will indeed be stuck. But if some existing browsers use lexical capture (i.e., return 1 in both cases), then ES-Harmony should feel free to specify that. What do each of the major browsers do?
My understanding (assuming I got the code right) is that Webkit and Gecko return different values, and Trident returns 2 for the g() and throws an exception for f().
Here are some demos. 001 is a control test. If it says "false", you have a violation of ES, and are likely incompatible with legacy content. If it says "true", then test 002. If 002 says "false", then ES is being violated in some way. If 002 doesn't say anything, then code is being blocked when the global object doesn't match the current document; Mozilla, Apple, and Opera have all told me not to do that for performance reasons. If it says false, then test 005 -- if that says true before and false after, then the browser is probably incompatible with legacy content.
damowmow.com/playground/demos/global-object/001.html, damowmow.com/playground/demos/global-object/002.html, damowmow.com/playground/demos/global-object/005.html
On Feb 17, 2009, at 2:48 PM, Mark Miller wrote:
On Tue, Feb 17, 2009 at 2:02 PM, Ian Hickson <ian at hixie.ch> wrote: Now, if the other page's script calls f() and g(), it will get
different results (2 and 1 respectively, if I didn't screw up the example code).For HTML5, this behaviour has been defined in more detail. The global object is a Window object. This object is per-Document. The object returned by the "window" attribute on that global object is actually a WindowProxy object, which forwards everything to the "current" Window object.
What do you mean by "current"? Are you proposing to legitimize the
dynamic scoping behavior demonstrated by your example?
What Ian showed is not dynamic scoping.
var global = this; function g() { return global.x; }
The issue is what global.x means after the current page (the one
containing the script including these two lines) has been unloaded and
a new page loaded (while some other window keeps a reference to g).
If all major browsers agree on this bizarre behavior, we will indeed
be stuck. But if some existing browsers use lexical capture (i.e.,
return 1 in both cases), then ES-Harmony should feel free to specify
that. What do each of the major browsers do?
What the proxy forwards to, the current inner window or the one
associated with function g (associated how?) when the expression
global.x is evaluated, is the current inner window for the outer
(persistent, === identity) proxy denoted by global in the example.
The identity of the inner window, the one at the tail (top is
confusing, which way is up?) of the scope chain in ECMA-262 terms,
must not leak or it could be used to subvert access checks done by the
proxy.
On Feb 17, 2009, at 3:09 PM, Brendan Eich wrote:
On Feb 17, 2009, at 2:48 PM, Mark Miller wrote:
On Tue, Feb 17, 2009 at 2:02 PM, Ian Hickson <ian at hixie.ch> wrote: Now, if the other page's script calls f() and g(), it will get
different results (2 and 1 respectively, if I didn't screw up the example
code).For HTML5, this behaviour has been defined in more detail. The global object is a Window object. This object is per-Document. The object returned by the "window" attribute on that global object is
actually a WindowProxy object, which forwards everything to the "current" Window object.What do you mean by "current"? Are you proposing to legitimize the
dynamic scoping behavior demonstrated by your example?What Ian showed is not dynamic scoping.
var global = this; function g() { return global.x; }
The issue is what global.x means after the current page (the one
containing the script including these two lines) has been unloaded
and a new page loaded (while some other window keeps a reference to
g).
The call to g comes from another window, and it would be dynamic
scoping if that window were the global object for the activation of g.
It's not. The other window is nowhere on the scope chain. The only
issue is whether the proxy forwards to the inner in which g was bound,
or the current inner.
Finding the inner in which g was bound from the proxy is not feasible
in general. But we could find g's statically linked scope parent
([[Scope]] in ES3), which would be the inner in which g was defined. I
raised this idea on IRC recently, and Hixie replied that the proxy
(denoted by global) might have no relation to g's [[Scope]].
But that could in theory be handled, I think, by comparing the proxy
to which global was bound to the proxy for g's [[Scope]] (inners keep
track fo their outer, that is easy; the reverse is 1:N and not easy).
If the same proxy is associated with g's [[Scope]] as is the base of
the reference being evaluated in g, then use g's [[Scope]]. Else use
the current inner global for the proxy.
This could be done, but it would add a kind of alias checking to all
property accesses. And it's not what any browser does. So it's
probably right out, but I wanted to raise it in detail.
On Tue, Feb 17, 2009 at 2:36 PM, Mark S. Miller <erights at google.com> wrote:
On Tue, Feb 17, 2009 at 2:02 PM, Ian Hickson <ian at hixie.ch> wrote:
The deeper problem here is that ES specs to date -- including the draft ES3.1 spec -- have not yet admitted the existence of multiple global objects. We all know we need to, but it is way too late to consider such a change for ES3.1. For ES-Harmony, I think we all agree we will take this step. Also, the draft module proposal at docs.google.com/Doc?id=dfgxb7gk_34gpk37z9v, proposed by Ihab Awad and Kris Kowal for ES-Harmony, relies on the introduction of a "hermetic eval" which omits the global object from the bottom of the scope chain.
That document is not completely readable (at least in Firefox).
I see the first heading: "roduction". All subsequent headings appear truncted.
h1 { margin-left:-0.5in; }
Garrett
On Feb 17, 2009, at 2:02 PM, Ian Hickson wrote:
Right now ES3 assumes that there is a single global object, which is
used at the top of the scope chain and that is returned for "this" in the global scope.It is possible to show that this is now what some browsers do:
var x = 1; function f() { return x; } var global = this; function g() { return global.x; }
// some other page's script takes references to f and g // browser navigates to a new page
var x = 2;
Now, if the other page's script calls f() and g(), it will get
different results (2 and 1 respectively, if I didn't screw up the example code).
Actually it's the other way around - f() will return 1 and g() will
return 2. (I think this might have been the reason Mark thought this
example showed dynamic scoping; scope is purely lexical and reflects
original global bindings, but a persistent handle to the global object
reflects the new global object bought in by navigation.)
, Maciej
On Tue, Feb 17, 2009 at 3:35 PM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
That document is not completely readable (at least in Firefox). I see the first heading: "roduction". All subsequent headings appear truncted.
Rats.
Ok, so it works fine in Safari, and some previous version of it used to work in FF.
Sorry about that folks.
Ihab
On Tue, Feb 17, 2009 at 3:02 PM, Ian Hickson <ian at hixie.ch> wrote:
Here are some demos. 001 is a control test. If it says "false", you have a violation of ES, and are likely incompatible with legacy content. If it says "true", then test 002. If 002 says "false", then ES is being violated in some way. If 002 doesn't say anything, then code is being blocked when the global object doesn't match the current document; Mozilla, Apple, and Opera have all told me not to do that for performance reasons. If it says false, then test 005 -- if that says true before and false after, then the browser is probably incompatible with legacy content.
damowmow.com/playground/demos/global-object/001.html, damowmow.com/playground/demos/global-object/002.html, damowmow.com/playground/demos/global-object/005.html
FF = Firefox 3.0.6 WK = WebKit nightly for Safari Version 3.2.1 (5525.27.1) CR = Chrome 1.0.154.48 OP = Opera 9.62 IE = IE 6.0.2900
T = true F = false B = apparently blocked, since nothing happened
(view in a fixed width font)
test 1 test 2 before after test 5 before after
FF T T B T B WK T T F T T CR T T T T B OP T T T T F IE T T B F B
Since cross-browser legacy content must work across this range of behaviors, it seems there is not any one legacy behavior to codify.
Nice tests!
On Tue, 17 Feb 2009, Mark Miller wrote:
Since cross-browser legacy content must work across this range of behaviors, it seems there is not any one legacy behavior to codify.
Indeed, I noted this earlier. The behavior HTML5 codifies is the behavior that the majority of browser vendors have asked me to codify.
Perhaps this would be a good initial W3C HTML WG/ECMA TC-39 joint work item if we can expeditiously get past the bureaucratic hurdles. The fact that there isn't an existing consensus behavior among the major browsers would seem to present an opportunity to step back a bit and take a new look at the problem.
My sense, is that there are issues that go deeper here simply the binding of the ECMAScript global object to a browser Window object. It's really about providing a clear definition of the execution environments for scripts (and potentially other code) within browsers and how such execution environments are associated with browser abstractions such as windows, pages, frames, etc. In particular, what are the life times of such execution environments and when are direct object references required/permitted/forbidden to be shared between them. Also when are proxy references required/permitted/forbidden and what are the characteristics of such proxies.
I don't think we should wait to address these issues as part of the larger ES-Harmony effort As I've previously mentioned I think from an ES perspective it could be initially addressed in an ECMA Tech Report that describes how the single ES global environment should map into the browser execution context. I think the most important point is that these issues should be addressed from a holistic browser perspective rather than just from a ES or HTML perspective.
Allen
From: es-discuss-bounces at mozilla.org [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Brendan Eich Sent: Tuesday, February 17, 2009 3:17 PM To: Mark Miller; Ian Hickson Cc: es-discuss Steen Subject: Re: The global object in browsers
On Feb 17, 2009, at 3:09 PM, Brendan Eich wrote:
On Feb 17, 2009, at 2:48 PM, Mark Miller wrote:
On Tue, Feb 17, 2009 at 2:02 PM, Ian Hickson <ian at hixie.ch<mailto:ian at hixie.ch>> wrote:
Now, if the other page's script calls f() and g(), it will get different results (2 and 1 respectively, if I didn't screw up the example code).
For HTML5, this behaviour has been defined in more detail. The global object is a Window object. This object is per-Document. The object returned by the "window" attribute on that global object is actually a WindowProxy object, which forwards everything to the "current" Window object.
What do you mean by "current"? Are you proposing to legitimize the dynamic scoping behavior demonstrated by your example?
What Ian showed is not dynamic scoping.
var global = this; function g() { return global.x; }
The issue is what global.x means after the current page (the one containing the script including these two lines) has been unloaded and a new page loaded (while some other window keeps a reference to g).
The call to g comes from another window, and it would be dynamic scoping if that window were the global object for the activation of g. It's not. The other window is nowhere on the scope chain. The only issue is whether the proxy forwards to the inner in which g was bound, or the current inner.
Finding the inner in which g was bound from the proxy is not feasible in general. But we could find g's statically linked scope parent ([[Scope]] in ES3), which would be the inner in which g was defined. I raised this idea on IRC recently, and Hixie replied that the proxy (denoted by global) might have no relation to g's [[Scope]].
But that could in theory be handled, I think, by comparing the proxy to which global was bound to the proxy for g's [[Scope]] (inners keep track fo their outer, that is easy; the reverse is 1:N and not easy). If the same proxy is associated with g's [[Scope]] as is the base of the reference being evaluated in g, then use g's [[Scope]]. Else use the current inner global for the proxy.
This could be done, but it would add a kind of alias checking to all property accesses. And it's not what any browser does. So it's probably right out, but I wanted to raise it in detail.
On Tue, Feb 17, 2009 at 5:03 PM, Ian Hickson <ian at hixie.ch> wrote:
Indeed, I noted this earlier. The behavior HTML5 codifies is the behavior that the majority of browser vendors have asked me to codify.
Majority, huh? Which vendors? How does the behavior they ask for correlate with what their browsers do?
At ECMAScript, the committee has representatives from the main browsers. ECMA works on consensus, not majority, so we get to codify only what all the players agree on. This discipline has repeatedly saved us from standardizing something unnecessarily complex. Cross-browser legacy is generally assumed to be constrained only by what the main browsers agree on (though there are exceptions), giving us some room to clean up areas where existing browser behaviors differ.
On Tue, 17 Feb 2009, Mark Miller wrote:
On Tue, Feb 17, 2009 at 5:03 PM, Ian Hickson <ian at hixie.ch> wrote:
Indeed, I noted this earlier. The behavior HTML5 codifies is the behavior that the majority of browser vendors have asked me to codify.
Majority, huh? Which vendors? How does the behavior they ask for correlate with what their browsers do?
Opera, Apple, and Mozilla. The HTML5 spec originally specced what IE does, namely throw an exception when running code whose global object doesn't match the current Window object, but Opera, Apple, and Mozilla rejected this on the grounds that it could not be implemented in a high-performance manner. They requested that the spec be changed to match what Mozilla and Safari do. What Opera does is known to be incompatible with deployed content (they expose Window objects that aren't === to each other).
The browsers all do slightly different things. The HTML5 spec right now is a mix of what Gecko and Webkit do.
HTH,
On Feb 17, 2009, at 4:03 PM, ihab.awad at gmail.com wrote:
On Tue, Feb 17, 2009 at 3:35 PM, Garrett Smith
<dhtmlkitchen at gmail.com> wrote:That document is not completely readable (at least in Firefox). I see the first heading: "roduction". All subsequent headings
appear truncted.Rats.
Ok, so it works fine in Safari, and some previous version of it used to work in FF.
My CSS guru says:
"It's equally broken (text off the left edge) for me in Firefox 3.0 and 1.5 as it is on trunk. The page asks for a negative half an inch of margin, and that's what it gets.
There is a rule that gives body a half-inch margin-left to compensate (line 283), but that's overriden by a later rule (line 328) at the very beginning of the next style element."
Brendan & folks,
On Tue, Feb 17, 2009 at 5:35 PM, Brendan Eich <brendan at mozilla.com> wrote:
My CSS guru says:
Thanks for looking at it. :) It turns out I was looking at the Google docs editor view, not the published web page view. The latter is indeed broken as you describe (for all browsers), and happens to be the view that all y'all are looking at.
I'll fix asap then ping the group. And I agree with Mark that we should move this to the ES Wiki.
Ihab
On Tue, Feb 17, 2009 at 5:24 PM, Ian Hickson <ian at hixie.ch> wrote:
Opera, Apple, and Mozilla. The HTML5 spec originally specced what IE does, namely throw an exception when running code whose global object doesn't match the current Window object, but Opera, Apple, and Mozilla rejected this on the grounds that it could not be implemented in a high-performance manner. They requested that the spec be changed to match what Mozilla and Safari do. What Opera does is known to be incompatible with deployed content (they expose Window objects that aren't === to each other).
The browsers all do slightly different things. The HTML5 spec right now is a mix of what Gecko and Webkit do.
Now that I think I understand "current" and how weak the legacy constraints are, why not simply spec that your WindowProxy is the object to treated as the ECMAScript global object? The consequence would be that both f() and g() in your original example would return 2.
On Feb 17, 2009, at 6:31 PM, Mark Miller wrote:
Now that I think I understand "current" and how weak the legacy
constraints are, why not simply spec that your WindowProxy is the
object to treated as the ECMAScript global object? The consequence
would be that both f() and g() in your original example would return
2.
That either opens a huge security hole, or imposes costly runtime
checks on all lexical references to global variables.
+1
On Tue, Feb 17, 2009 at 6:38 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Feb 17, 2009, at 6:31 PM, Mark Miller wrote:
Now that I think I understand "current" and how weak the legacy
constraints are, why not simply spec that your WindowProxy is the object to treated as the ECMAScript global object? The consequence would be that both f() and g() in your original example would return 2.
That either opens a huge security hole, or imposes costly runtime checks on all lexical references to global variables.
If Ian's Window object were provided as the ES global, then we'd have the security problem you're concerned about. But, IIUC, with the WindowProxy we don't. As for performance, wouldn't a shallow binding implementation[1] make switching which page is current be more expensive, but variable access within a page, even after a switch, would have no added expense?
[1] The two primary methods of implementing dynamic scoping were known as "shallow binding" and "deep binding". Although "current" is not dynamic scope, some of the same issues and tradeoffs apply. In a shallow bound implementation. the WindowProxy's properties are direct properties -- not forwarded. On page navigation, these properties are copied into the Window object corresponding to the old page, and the properties on the Window object corresponding to the new page are copied onto the WindowProxy.
Another analogy is with the register store in thread control blocks in an OS implementation. One can describe the semantics of computation in a given OS as directly updating the "current" per-thread register set. But instead of paying the indirection on register access, we'd rather pay the copying overhead on context switch.
On Feb 17, 2009, at 8:17 PM, Mark Miller wrote:
On Tue, Feb 17, 2009 at 6:38 PM, Brendan Eich <brendan at mozilla.com>
wrote: On Feb 17, 2009, at 6:31 PM, Mark Miller wrote:Now that I think I understand "current" and how weak the legacy
constraints are, why not simply spec that your WindowProxy is the
object to treated as the ECMAScript global object? The consequence
would be that both f() and g() in your original example would return
2.That either opens a huge security hole, or imposes costly runtime
checks on all lexical references to global variables.If Ian's Window object were provided as the ES global, then we'd
have the security problem you're concerned about. But, IIUC, with
the WindowProxy we don't.
That seems half-right. The ES global at the end of scope chains is the
"inner" Window object. The perisistent-identity global that you get
your hands on via the DOM, |this|, |self|, and |window| is the "outer"
or WindowProxy object.
Which one did you mean by "the ES global" in the first sentence cited
above?
As for performance, wouldn't a shallow binding implementation[1]
make switching which page is current be more expensive, but variable
access within a page, even after a switch, would have no added
expense?[1] The two primary methods of implementing dynamic scoping were
known as "shallow binding" and "deep binding". Although "current" is
not dynamic scope, some of the same issues and tradeoffs apply.
You are missing the requirement that the identity of the "outer"
WindowProxy, which contains a series of documents loaded into it over
time, remain the same, while the identities, plural, of the "inner"
Window objects, each the global for its own document, also remain the
same.
In a shallow bound implementation. the WindowProxy's properties are
direct properties -- not forwarded. On page navigation, these
properties are copied into the Window object corresponding to the
old page, and the properties on the Window object corresponding to
the new page are copied onto the WindowProxy.
And the scope chains for all the functions and activations in the old
page are rewritten to refer to the Window object corresponding to the
old page, not the WindowProxy? This does not scale at all.
And the reference monitor that checks access rights in the WindowProxy
must run when the accesses come from only certain code. How does that
work without disjoint global (end of scope chain) objects but
persistent |this|? You can't know to skip the check and avoid a bad
performance hit. The lexical references must suffer no penalty.
Another analogy is with the register store in thread control blocks
in an OS implementation. One can describe the semantics of
computation in a given OS as directly updating the "current" per- thread register set. But instead of paying the indirection on
register access, we'd rather pay the copying overhead on context
switch.
This doesn't work for security or performance.
You misunderstood me a bit, but no matter. Now that I better understand the constraints -- thanks! -- what I was trying to say is irrelevant. What I mess. I am at a loss to find anything sensible to recommend.
On Feb 17, 2009, at 4:52 PM, Mark Miller wrote:
On Tue, Feb 17, 2009 at 3:02 PM, Ian Hickson <ian at hixie.ch> wrote: Here are some demos. 001 is a control test. If it says "false", you
have a violation of ES, and are likely incompatible with legacy content. If
it says "true", then test 002. If 002 says "false", then ES is being
violated in some way. If 002 doesn't say anything, then code is being blocked
when the global object doesn't match the current document; Mozilla,
Apple, and Opera have all told me not to do that for performance reasons. If it
says false, then test 005 -- if that says true before and false after,
then the browser is probably incompatible with legacy content.damowmow.com/playground/demos/global-object/001.html, damowmow.com/playground/demos/global-object/002.html, damowmow.com/playground/demos/global-object/005.html
FF = Firefox 3.0.6 WK = WebKit nightly for Safari Version 3.2.1 (5525.27.1) CR = Chrome 1.0.154.48 OP = Opera 9.62 IE = IE 6.0.2900
T = true F = false B = apparently blocked, since nothing happened
(view in a fixed width font)
test 1 test 2 before after test 5 before after
FF T T B T B WK T T F T T CR T T T T B OP T T T T F IE T T B F B
Since cross-browser legacy content must work across this range of
behaviors, it seems there is not any one legacy behavior to codify.
There's really two basic requirements that have to be met here:
-
For compatibility, a reference to the Window object (which acts as
the global object of a frame or window) must act as a persistent
handle for code outside the frame/window and must continue to reflect
the current contents of the given frame/window even if it is navigated
to a new document. -
For security, it must not be possible for a function saved from a
an older document in a given Window to see variables created when some
other document is current, if those documents are not same-origin.
(The same applies in reverse - code associated with new documents must
not see old variable bindings).
In addition, there are two constraints strongly suggested by
performance considerations:
-
Variable lookup via the scope chain shouldn't have to do a same- origin check for every access, as that is too slow compared to the
cost of variable lookup alone. -
JavaScript-to-JavaScript function calls shouldn't have to do a
check to see if the function being called is associated with a non- current document - doing this at every JS-to-JS call boundary is quite
expensive.
Given these constraints, the most natural solution is a "split window"
type approach, which both Gecko and WebKit implement.
, Maciej
On Feb 17, 2009, at 6:31 PM, Mark Miller wrote:
On Tue, Feb 17, 2009 at 5:24 PM, Ian Hickson <ian at hixie.ch> wrote: Opera, Apple, and Mozilla. The HTML5 spec originally specced what IE
does, namely throw an exception when running code whose global object
doesn't match the current Window object, but Opera, Apple, and Mozilla
rejected this on the grounds that it could not be implemented in a high- performance manner. They requested that the spec be changed to match what
Mozilla and Safari do. What Opera does is known to be incompatible with deployed content (they expose Window objects that aren't === to each other).The browsers all do slightly different things. The HTML5 spec right
now is a mix of what Gecko and Webkit do.Now that I think I understand "current" and how weak the legacy
constraints are, why not simply spec that your WindowProxy is the
object to treated as the ECMAScript global object? The consequence
would be that both f() and g() in your original example would return
2.
That would either allow access to new variable bindings to functions
in old documents from different origins (violating security
requirements, my constraint 2, from another email) or would require a
security check for every global variable lookup (violating my
performance constraint 3).
, Maciej
On Feb 17, 2009, at 11:18 PM, Mark Miller wrote:
You misunderstood me a bit, but no matter. Now that I better
understand the constraints -- thanks! -- what I was trying to say is
irrelevant. What I mess. I am at a loss to find anything sensible to
recommend.
I think that's how most of us who have studied the issue feel. The
split window solution is in some ways inelegant, but there is no
obvious other option that satisfies the performance, compatibility and
security requirements.
, Maciej
On Feb 17, 2009, at 11:18 PM, Mark Miller wrote:
You misunderstood me a bit, but no matter.
Sorry, I couldn't see how to interpret your proposal otherwise. Let me
know what I missed if you like.
Maciej's right, the object identities practically dictate split
windows. I suppose the original DOM level 0 could have made the split
explicit, but it was not implemented this way at all back in the day.
Different browser implementors solved the security and performance
problems in similar ways, to preserve the view of the persistent
window container as the one true "window object", really a proxy with
multiple global objects hidden within it.
The ability to "use lexical scope" (however the syntax turns out) and
make the global variables truly lexical bindings in a top-level
environment, not properties of some grotty object, is something I look
forward to in Harmony:
On Tue, 17 Feb 2009, Allen Wirfs-Brock wrote:
Perhaps this would be a good initial W3C HTML WG/ECMA TC-39 joint work item if we can expeditiously get past the bureaucratic hurdles. The fact that there isn't an existing consensus behavior among the major browsers would seem to present an opportunity to step back a bit and take a new look at the problem.
This would be fine by me, though it should be noted that the people who will ultimately decide what the answer is here are those who will be implementing it, and there is nobody who falls into that camp who is on the public-html at w3.org list and is not on the es-discuss list as far as I am aware. So it's not clear that there would be any benefit to the getting past the bureaucratic hurdles.
I'm happy to spec into HTML5 whatever the majority of implementations eventually do (i.e. HTML5 will just track reality, whatever that is), I would hope the same applies to the ES specs. :-)
On Feb 18, 2009, at 3:24 PM, Ian Hickson wrote:
I'm happy to spec into HTML5 whatever the majority of implementations eventually do (i.e. HTML5 will just track reality, whatever that
is), I would hope the same applies to the ES specs. :-)
We do try to pave the cowpaths (without giving the biggest herds undue
influence, or falling into other such traps -- I get lost driving
around Boston, I hear it's cuz they paved the cowpaths back a century
or two).
You may remember us from such reality-inspired movies as "Attack of
the /[/]/ Killer RegExps" and "Don't throw for(i in null) from the
Train". :-/
Ian Hickson wrote:
On Tue, 17 Feb 2009, Mark S. Miller wrote:
I don't understand. If the object you're calling Window is inaccessible from ES code, and if the object you're calling WindowProxy forwards everything to your Window, why not just relabel Window -> InternalWindow, WindowProxy -> Window?
I don't really mind what the objects are called, the point is just that the object at the top of the scope chain is not the same as the object returned by "this" (or "window" on the global object).
MarkM's point is that given that the object called Window is inaccessible, there's no way to observe that the object called Window is at the top of the scope chain. An implementation could reflect all property changes in that object to the object called WindowProxy, by some unspecified mechanism (which is allowed since they are both host objects).
And in any case, why not just provide your WindowProxy as the global object to ES code? Why does ES need to be aware of your Window at all?
When a browsing context navigates from page A to page B, the object at the top of the scope chain in code from page A and the oject at the top of the scope chain in code from page B are not the same object,
It's not possible to observe that, since by hypothesis neither object is accessible to ECMAScript code.
but the object returned by the global-scope "this" in scripts from A and B are the same object (===).
That would still be the case in MarkM's relabelling.
I'm confused by the motivation of the change in HTML5. It seems like it is imposing most of the complexity that would be needed to fix some of the security problems associated with the global object, without actually fixing those problems.
Also, it is a breach of standards development etiquette for the HTML WG to make a a change (even in a draft) that it believes to be incompatible with the ECMAScript spec, without consulting TC39. It should not have been left to you in the role of an implementor to point out the incompatibility.
Ian Hickson wrote:
On Tue, 17 Feb 2009, Mark Miller wrote:
On Tue, Feb 17, 2009 at 5:03 PM, Ian Hickson <ian at hixie.ch> wrote:
Indeed, I noted this earlier. The behavior HTML5 codifies is the behavior that the majority of browser vendors have asked me to codify. Majority, huh? Which vendors? How does the behavior they ask for correlate with what their browsers do?
Opera, Apple, and Mozilla. The HTML5 spec originally specced what IE does, namely throw an exception when running code whose global object doesn't match the current Window object, but Opera, Apple, and Mozilla rejected this on the grounds that it could not be implemented in a high-performance manner.
That is clearly false. It would be a single pointer comparison when entering a new context.
I make no comment here on whether this behaviour would be a good idea on other criteria, just that rejecting it on performance grounds is absurd.
On Thu, 19 Feb 2009, David-Sarah Hopwood wrote:
MarkM's point is that given that the object called Window is inaccessible, there's no way to observe that the object called Window is at the top of the scope chain.
Granted, but there is a way to observe that the object at the top of the scope chain isn't the same as the object returned by |this|, which is what I am concerned about.
When a browsing context navigates from page A to page B, the object at the top of the scope chain in code from page A and the oject at the top of the scope chain in code from page B are not the same object,
It's not possible to observe that, since by hypothesis neither object is accessible to ECMAScript code.
The object itself isn't, but properties on the object are. If two scripts check to see what value a variable "x" on their global object is, and they get different results, in the absence of any code changing anything, one can tell that they are different global objects.
I'm confused by the motivation of the change in HTML5. It seems like it is imposing most of the complexity that would be needed to fix some of the security problems associated with the global object, without actually fixing those problems.
What security problems does in not fix?
The motivation is to make HTML5 describe what browsers do.
Also, it is a breach of standards development etiquette for the HTML WG to make a a change (even in a draft) that it believes to be incompatible with the ECMAScript spec, without consulting TC39. It should not have been left to you in the role of an implementor to point out the incompatibility.
I am the editor of the HTML5 spec. My e-mail was an attempt at the consultation to which you refer.
HTH,
On Thu, 19 Feb 2009, David-Sarah Hopwood wrote:
Ian Hickson wrote:
On Tue, 17 Feb 2009, Mark Miller wrote:
On Tue, Feb 17, 2009 at 5:03 PM, Ian Hickson <ian at hixie.ch> wrote:
Indeed, I noted this earlier. The behavior HTML5 codifies is the behavior that the majority of browser vendors have asked me to codify. Majority, huh? Which vendors? How does the behavior they ask for correlate with what their browsers do?
Opera, Apple, and Mozilla. The HTML5 spec originally specced what IE does, namely throw an exception when running code whose global object doesn't match the current Window object, but Opera, Apple, and Mozilla rejected this on the grounds that it could not be implemented in a high-performance manner.
That is clearly false. It would be a single pointer comparison when entering a new context.
I make no comment here on whether this behaviour would be a good idea on other criteria, just that rejecting it on performance grounds is absurd.
To be honest it doesn't really matter to me what the reason is -- if three browser vendors tell me they're not implementing the spec, I change the spec. My goal is to have a specification that browser vendors implement.
Brendan Eich wrote:
On Feb 17, 2009, at 2:48 PM, Mark Miller wrote:
On Tue, Feb 17, 2009 at 2:02 PM, Ian Hickson <ian at hixie.ch> wrote: Now, if the other page's script calls f() and g(), it will get different results (2 and 1 respectively, if I didn't screw up the example code).
For HTML5, this behaviour has been defined in more detail. The global object is a Window object. This object is per-Document. The object returned by the "window" attribute on that global object is actually a WindowProxy object, which forwards everything to the "current" Window object.
What do you mean by "current"? Are you proposing to legitimize the dynamic scoping behavior demonstrated by your example?
What Ian showed is not dynamic scoping.
var global = this; function g() { return global.x; }
The issue is what global.x means after the current page (the one containing the script including these two lines) has been unloaded and a new page loaded (while some other window keeps a reference to g).
Whatever is specified should not prevent an implementation from throwing an exception (ReferenceError would be most appropriate) in this case.
I disagree, strongly, with the position attributed by Ian Hickson to "Mozilla, Apple, and Opera" that this option should be excluded on performance grounds; I think that position has no sound technical justification.
Ian Hickson wrote:
On Thu, 19 Feb 2009, David-Sarah Hopwood wrote:
Ian Hickson wrote:
On Tue, 17 Feb 2009, Mark Miller wrote:
On Tue, Feb 17, 2009 at 5:03 PM, Ian Hickson <ian at hixie.ch> wrote:
Indeed, I noted this earlier. The behavior HTML5 codifies is the behavior that the majority of browser vendors have asked me to codify.
Majority, huh? Which vendors? How does the behavior they ask for correlate with what their browsers do?
Opera, Apple, and Mozilla. The HTML5 spec originally specced what IE does, namely throw an exception when running code whose global object doesn't match the current Window object, but Opera, Apple, and Mozilla rejected this on the grounds that it could not be implemented in a high-performance manner.
That is clearly false. It would be a single pointer comparison when entering a new context.
I make no comment here on whether this behaviour would be a good idea on other criteria, just that rejecting it on performance grounds is absurd.
To be honest it doesn't really matter to me what the reason is -- if three browser vendors tell me they're not implementing the spec, I change the spec. My goal is to have a specification that browser vendors implement.
I think it should matter. The vendors should be asked to give a reason that makes technical sense. That this option "could not be implemented in a high-performance manner" does not make sense -- which means that it is quite possible that the vendors were asked the wrong question, or had some misunderstanding about how such a specification could be implemented.
On Thu, 19 Feb 2009, David-Sarah Hopwood wrote:
I think it should matter. The vendors should be asked to give a reason that makes technical sense. That this option "could not be implemented in a high-performance manner" does not make sense -- which means that it is quite possible that the vendors were asked the wrong question, or had some misunderstanding about how such a specification could be implemented.
I don't believe there was any misunderstanding, but regardless: if you are able to convince the implementors to implement something other than what is currently specced in HTML5, I'm very happy to change the spec to require that instead. I don't really mind how this works, I just want to make sure the spec is clear on what is required and that the spec matches what actually gets implemented, and ideally I would like it to not contradict the ES specs as it unfortunately does now.
On Feb 19, 2009, at 1:39 AM, David-Sarah Hopwood wrote:
Ian Hickson wrote:
On Tue, 17 Feb 2009, Mark Miller wrote:
On Tue, Feb 17, 2009 at 5:03 PM, Ian Hickson <ian at hixie.ch> wrote:
Indeed, I noted this earlier. The behavior HTML5 codifies is the behavior that the majority of browser vendors have asked me to
codify. Majority, huh? Which vendors? How does the behavior they ask for correlate with what their browsers do?Opera, Apple, and Mozilla. The HTML5 spec originally specced what
IE does, namely throw an exception when running code whose global object
doesn't match the current Window object, but Opera, Apple, and Mozilla
rejected this on the grounds that it could not be implemented in a high- performance manner.That is clearly false. It would be a single pointer comparison when
entering a new context.I make no comment here on whether this behaviour would be a good
idea on other criteria, just that rejecting it on performance grounds is
absurd.
In modern JITing implementations, adding (at least) two memory reads
and a conditional branch to every function call would be a significant
performance hit. In JavaScriptCore for instance, the hot path at the
machine code level is optimized down to essentially just one memory
read, one conditional branch, and a jump to the callee's code (also
some memory writes but those don't stall the pipeline so they don't
matter as much). The next hottest path has three branches and a few
extra memory reads. Adding an extra branch (and associated memory
reads) to that would be a big hit. We know because we have measured
the benefit of reducing the number of branches. It makes a huge
difference in call-heavy code.
In general, I would advise against making performance claims in
absolute terms (like "clearly false") without testing or studying the
relevant implementation internals.
, Maciej
On Feb 19, 2009, at 1:20 AM, David-Sarah Hopwood wrote:
Ian Hickson wrote:
On Tue, 17 Feb 2009, Mark S. Miller wrote:
I don't understand. If the object you're calling Window is
inaccessible from ES code, and if the object you're calling WindowProxy forwards everything to your Window, why not just relabel Window -> InternalWindow, WindowProxy -> Window?I don't really mind what the objects are called, the point is just
that the object at the top of the scope chain is not the same as the
object returned by "this" (or "window" on the global object).MarkM's point is that given that the object called Window is
inaccessible, there's no way to observe that the object called Window is at the
top of the scope chain. An implementation could reflect all property changes in that object to the object called WindowProxy, by some unspecified mechanism (which is allowed since they are both host objects).
It is possible to observe that the object at the top of the scope
chain behaves differently than the global "this" value (it may have an
entirely different set of properties), so this arguably violates the
ECMAScript requirement that these be the same object, even though
object identity cannot be observed directly.
However, this can only be observed in the presence of multiple global
objects and global object replacement via navigation, which is a
situation not adequately covered by the ECMAScript spec at present.
One could make the case that because the difference is not observable
under a single global object, then the behavior is conforming.
I'm confused by the motivation of the change in HTML5. It seems like
it is imposing most of the complexity that would be needed to fix some of
the security problems associated with the global object, without
actually fixing those problems.
The HTML5 spec behavior does fix important security problems, relative
to what older browsers used to do. Older behavior was to reuse the
same global object across navigations, but simply clear the variables.
This creates easily exploitable XSS vulnerabiliites. Note though that
HTML5 is following the implementations here rather than leading.
Also, it is a breach of standards development etiquette for the HTML
WG to make a a change (even in a draft) that it believes to be
incompatible with the ECMAScript spec, without consulting TC39. It should not have
been left to you in the role of an implementor to point out the incompatibility.
I think Ian discharged his obligation by notifying TC39 of the issue
and starting this discussion. At this point, the important thing is to
come up with a solution we can agree on.
, Maciej
-----Original Message----- From: es-discuss-bounces at mozilla.org [mailto:es-discuss- bounces at mozilla.org] On Behalf Of Maciej Stachowiak ...
I think Ian discharged his obligation by notifying TC39 of the issue and starting this discussion. At this point, the important thing is to come up with a solution we can agree on.
, Maciej
Perhaps it would be useful to have a half day (or even full day) session on this general topic at the March F2F. Since Ian is affiliated with an ECMA member he could attend if he chooses and I might get an appropriate IE platform representative to attend.
On Thu, 19 Feb 2009, Allen Wirfs-Brock wrote:
Perhaps it would be useful to have a half day (or even full day) session on this general topic at the March F2F. Since Ian is affiliated with an ECMA member he could attend if he chooses and I might get an appropriate IE platform representative to attend.
I really don't have an opinion on this topic. I'm happy to spec whatever the browser vendors are willing to implement.
Maciej Stachowiak wrote:
On Feb 19, 2009, at 1:39 AM, David-Sarah Hopwood wrote:
Ian Hickson wrote:
On Tue, 17 Feb 2009, Mark Miller wrote:
On Tue, Feb 17, 2009 at 5:03 PM, Ian Hickson <ian at hixie.ch> wrote:
Indeed, I noted this earlier. The behavior HTML5 codifies is the behavior that the majority of browser vendors have asked me to codify. Majority, huh? Which vendors? How does the behavior they ask for correlate with what their browsers do?
Opera, Apple, and Mozilla. The HTML5 spec originally specced what IE does, namely throw an exception when running code whose global object doesn't match the current Window object, but Opera, Apple, and Mozilla rejected this on the grounds that it could not be implemented in a high-performance manner.
That is clearly false. It would be a single pointer comparison when entering a new context.
I make no comment here on whether this behaviour would be a good idea on other criteria, just that rejecting it on performance grounds is absurd.
In modern JITing implementations, adding (at least) two memory reads and a conditional branch to every function call would be a significant performance hit.
It is not every function call; only function calls that are not known to be same-context (in a particular copy of the generated code). The majority (by call frequency) of function calls can be inferred to be same-context by analyses that a high-performance implementation should be doing already. Furthermore, the context check can be hoisted to the first point in a function body where the target function is known (so calls to the same function in a loop will require only one check).
There are most sophisticated optimizations that could be done, but they're probably not needed.
In JavaScriptCore for instance, the hot path at the machine code level is optimized down to essentially just one memory read, one conditional branch, and a jump to the callee's code (also some memory writes but those don't stall the pipeline so they don't matter as much). The next hottest path has three branches and a few extra memory reads. Adding an extra branch (and associated memory reads) to that would be a big hit. We know because we have measured the benefit of reducing the number of branches. It makes a huge difference in call-heavy code.
In general, I would advise against making performance claims in absolute terms (like "clearly false") without testing or studying the relevant implementation internals.
I see no point in equivocating on points that I am quite sure about, having studied similar issues in implementations of other languages.
Maciej Stachowiak wrote:
On Feb 19, 2009, at 1:20 AM, David-Sarah Hopwood wrote:
Ian Hickson wrote:
On Tue, 17 Feb 2009, Mark S. Miller wrote:
I don't understand. If the object you're calling Window is inaccessible from ES code, and if the object you're calling WindowProxy forwards everything to your Window, why not just relabel Window -> InternalWindow, WindowProxy -> Window?
I don't really mind what the objects are called, the point is just that the object at the top of the scope chain is not the same as the object returned by "this" (or "window" on the global object).
MarkM's point is that given that the object called Window is inaccessible, there's no way to observe that the object called Window is at the top of the scope chain. An implementation could reflect all property changes in that object to the object called WindowProxy, by some unspecified mechanism (which is allowed since they are both host objects).
It is possible to observe that the object at the top of the scope chain behaves differently than the global "this" value (it may have an entirely different set of properties), so this arguably violates the ECMAScript requirement that these be the same object, even though object identity cannot be observed directly.
Ian Hickson's description of the two objects was:
The global object is a Window object. This object is per-Document.
The object returned by the "window" attribute on that global object
is actually a WindowProxy object, which forwards everything to the
"current" Window object.
I don't understand how the WindowProxy "forwarding everything" to the Window object is is consistent with the two objects having "entirely different set[s] of properties".
In any case, note that host objects can have arbitrary [[Put]] and [[Get]] internal methods, so it is possible for them to implement almost any conceivable behaviour that HTML5 could specify (given that ES code cannot obtain a reference to the object at the top of the scope chain, and so === cannot be used on it). That was my point above. In other words, ES3 has such a weak specification for host objects that literally any behaviour for property accesses on them is conformant.
[...]
I'm confused by the motivation of the change in HTML5. It seems like it is imposing most of the complexity that would be needed to fix some of the security problems associated with the global object, without actually fixing those problems.
The HTML5 spec behavior does fix important security problems, relative to what older browsers used to do. Older behavior was to reuse the same global object across navigations, but simply clear the variables. This creates easily exploitable XSS vulnerabilities.
That's obviously broken, yes. When you say "older behaviour", what versions of the main browsers are you talking about, approximately?
Note though that HTML5 is following the implementations here rather than leading.
Also, it is a breach of standards development etiquette for the HTML WG to make a a change (even in a draft) that it believes to be incompatible with the ECMAScript spec, without consulting TC39. It should not have been left to you in the role of an implementor to point out the incompatibility.
I think Ian discharged his obligation by notifying TC39 of the issue and starting this discussion. At this point, the important thing is to come up with a solution we can agree on.
I was confused partly by the fact that Ian said:
For HTML5, this behaviour has been defined in more detail. [...]
The HTML5 spec says: [...]
as opposed to something like: "It has been proposed for the next draft of HTML5 to define this in more detail. [...] The proposal is: [...]"
Ian Hickson wrote:
Right now ES3 assumes that there is a single global object, which is used at the top of the scope chain and that is returned for "this" in the global scope.
It is possible to show that this is now what some browsers do:
var x = 1; function f() { return x; } var global = this; function g() { return global.x; }
// some other page's script takes references to f and g // browser navigates to a new page
var x = 2;
Now, if the other page's script calls f() and g(), it will get different results [...]
Suppose that a browser allocates all JavaScript objects associated with some unit of content [*], in an arena. When the browser navigates away from that unit of content, the arena is deallocated; to preserve memory safety, all references into it from objects that are still live will throw an exception.
This behaviour has clear advantages for robustness against denial of service from JavaScript code, both deliberate and inadvertent -- which is a definite weak spot of current browsers, and a very common cause of complaint from knowledgeable users. Is there any reason why it should be considered nonconformant?
[*] Possibly a frame, or consecutive sequence of navigated frames with the same origin. What is the minimum granularity for a "unit of content" for this would be compatible with the current web?
On Feb 21, 2009, at 1:49 AM, David-Sarah Hopwood wrote:
Ian Hickson wrote:
Right now ES3 assumes that there is a single global object, which
is used at the top of the scope chain and that is returned for "this" in the global scope.It is possible to show that this is now what some browsers do:
var x = 1; function f() { return x; } var global = this; function g() { return global.x; }
// some other page's script takes references to f and g // browser navigates to a new page
var x = 2;
Now, if the other page's script calls f() and g(), it will get
different results [...]Suppose that a browser allocates all JavaScript objects associated
with some unit of content [*], in an arena. When the browser navigates
away from that unit of content, the arena is deallocated; to preserve memory
safety, all references into it from objects that are still live will throw an exception.This behaviour has clear advantages for robustness against denial of
service from JavaScript code, both deliberate and inadvertent -- which is a
definite weak spot of current browsers, and a very common cause of complaint
from knowledgeable users. Is there any reason why it should be considered nonconformant?
This behavior has two problems:
-
Likely incompatible with the Web. Though Web sites apparently don't
depend on ability to call older functions, they certainly do use
objects originally allocated in other global objects and quite likely
after the originating context has navigated. It's hard to quantify
this since no browser has ever tried. -
Makes implementation of back/forward caching (as present in Safari
and Firefox) impractical; b/f cache fully restores a context that you
navigated away from, including the live JavaScript object graph. -
Has the same performance problems as the model of checking at
function call boundaries, since at calls you'd have to check if your
function has turned into a magical exception object. You are on record
as not believing these, I will leave it to others to judge for
themselves whether your "I'm quite sure" argument outweighs the
experience of those who actually tried it and measured the results. -
If the arena is truly deallocated rather than filled with magic
exception objects, many additional checks must be inserted each time a
value reference is used in almost any way, to check whether it is a
reference into a deallocated arena. This would likely be a significant
perf hit as well. -
As far as I can tell, this violates the ECMA spec even more than
the current HTML5 solution; the ECMA spec does not include the concept
of reachable values turning into exception-throwing zombies. -
It has poor properties for reliability and integrity of ECMAScript
programs. Your code may have a perfectly good string or object stored
away. But depending on where it originally came from, that reference
can suddenly go bad.
[*] Possibly a frame, or consecutive sequence of navigated frames with the same origin. What is the minimum granularity for a "unit of
content" for this would be compatible with the current web?
A unit of content of one page is almost certainly incompatible with
the Web. A series of frames with the same origin would likely also be
incompatible, since it is quite likely an initially empty frame may be
used as a helper before navigating elsewhere. Not only that, but it
would be bizarrely arbitrary if your object references from another
frame go dead or not depending on whether the frame navigates to same-
origin or non-same-origin content. Either way, problems 2-5 remain.
I don't think we need to try to redesign how browsers do navigation
here. I don't see any justification provided for preferring your
approach to what HTML5 specifies.
, Maciej
On Feb 20, 2009, at 4:13 PM, David-Sarah Hopwood wrote:
Ian Hickson's description of the two objects was:
The global object is a Window object. This object is per-Document.
The object returned by the "window" attribute on that global object
is actually a WindowProxy object, which forwards everything to the
"current" Window object.
I don't understand how the WindowProxy "forwarding everything" to the Window object is is consistent with the two objects having "entirely different set[s] of properties".
Let's say you have a top-level browsing context currently displaying a
document loaded from example.com. This document has a
subframe displaying example.com/frame1.html. Inside the
frame1 browsing context, a variable X containing the value "frame1" is
defined. A function F is also defined which accesses variable X via
scope, and via a retained reference to the global object. The top-
level context gets a reference to function F. Now the nested browsing
context is navigated to example.com/frame2.html. This
establishes a new Window, which the WindowProxy now points to. Let's
say that inside this new global object, another variable X is defined
which has the value "frame2". Now when F is called from the outer
context, it will consistently see the value "frame1" when looking up
variable X via scope, since the scope chain contains its Window. But
when accessing X via a saved reference to the global object (for
example via window.X) it will consistently see "frame2", since this is
a WindowProxy reference that forwards to the now-current Window.
Taking this further, the old Window and new Window (visible via the
WindowProxy) could have totally different sets of properties.
Note that to observe the difference requires at least two frames, at
least one of which is navigated, for a total of 3 different global
objects.
Does that help?
In any case, note that host objects can have arbitrary [[Put]] and [[Get]] internal methods, so it is possible for them to implement almost any conceivable behaviour that HTML5 could specify (given that ES code cannot obtain a reference to the object at the top of the scope chain, and so === cannot be used on it). That was my point above. In other words, ES3 has such a weak specification for host objects that literally any behaviour for property accesses on them is conformant.
If we take this approach, then the requirement that the top level
scope entry and the global object are the same object would be
meaningless. I'm willing to buy the argument that it is in fact
meaningless and imposes no real requirement, but perhaps others will
disagree. Since there is no way to observe identity directly, one
could always make the claim that two different objects are in fact one
object with strange property access behavior.
, Maciej
Picking up this older thread, I owed you and others in TC39 a reply
from two face-to-face meetings ago.
On Feb 18, 2009, at 3:57 AM, Brendan Eich wrote:
On Feb 17, 2009, at 11:18 PM, Mark Miller wrote:
You misunderstood me a bit, but no matter.
Sorry, I couldn't see how to interpret your proposal otherwise. Let
me know what I missed if you like.Maciej's right, the object identities practically dictate split
windows. I suppose the original DOM level 0 could have made the
split explicit, but it was not implemented this way at all back in
the day. Different browser implementors solved the security and
performance problems in similar ways, to preserve the view of the
persistent window container as the one true "window object", really
a proxy with multiple global objects hidden within it.
The issue we were debating in the TC39 meeting in March at Yahoo! was
whether |this|, window, self, and any other synonyms should bind to
the inner or outer window object.
Recall that split windows arise because the outer window is the
persistent object reference returned by window.open, frames[n],
document.getElementById(frameId), etc. that perdures across document
loads into that window or frame. The inner window is the ECMA-262
global object, a capability through which unmediated access to
sensitive variables including much of the DOM happens.
The inner window is thus the final object on the scope chain of all
objects created for a particular page, whether induced by markup or
created by DOM API calls. The outer window is a proxy that monitors
accesses and forwards them to the current inner window, or denies them.
During the evolution of split windows in Mozilla's codebase, we
observed that it's frightfully easy to leak the inner-window
capability if it is allowed to escape to script. So we made |this|,
window, and self bind to the outer window.
We even made our parent extension, the read-only and undeletable
property reifying the intrinsic scope chain link in SpiderMonkey,
return the outer window, not the inner window that is the last object
on the scope chain of objects subordinate to that inner window, e.g.,
the current document, where document.parent would seemingly
reference the inner window containing document.
Instead, we "outerize" inner window references as they try to escape
to user-level JS. Only privileged API can get the capability, and of
course it's baked in as the lexical scope chain link by the JS
compiler for top-level functions and variously optimized closures.
If the outer window sees that the "outerized" access is from the same
origin as the current inner window, it allows access. This is
expensive, or at least it has been until recently. With tracing and
polymorphic inline caching and the like, it's no longer too expensive
to consider imposing on all accesses.
(Maciej and I debated this with David-Sarah Hopwood for function
calls, and the jury's still out; more on this later, but see the paper
cited below for some encouraging results. But this is only one of the
reasons for split windows, so even if we did check access always, we
wouldn't give up split windows.)
The DOM does not use a capability model, rather it monitors certain
references to implement access control policies, again because
monitoring all references is too costly (or was in the nineties); also
access control was still the rage back when JS and Java were co-
evolving their security models. These access control judgments depend
on the baked-in and unforgeable intrinsic scope chain link, in order
to find the principals for subject script and target object. But the
DOM checks only some accesses, not all -- and in particular not access
to JS variables.
Here's the paper I mentioned at the last face-to-face but forgot to
follow up on (perhaps you've seen it already): www.adambarth.com/papers/2009/barth-weinberger-song.pdf
-- a nice paper on the problem of mixing capability with access-
control models -- old news to you I'm sure in its general results, but
perhaps novel in the split window and DOM vs. JS browser conflict.
Section 4.2 in the paper talks about a split window bug, or possibly
the lack of split windows altogether -- I think; I haven't looked
closely at the WebKit code in question. 4.3 describes a bug that is
very familiar to Mozilla JS hackers; nothing against WebKit here,
we're all in the same boat.
The paper's conclusion that defense in depth requires universal access
control checks at the JS level may not sit well with obj-cap fans, but
it fits the threat-space we've been facing all these years. Blame the
DOM, try to reform it or rewrite it (Caja, ADSafe) to use capabilities
instead of ids and names and too many references, but in the short run
the right fix for browser vendors dealing with the web-as-it-is looks
to me like this:
Convolve security labels with polymorphic inline cache keys ("shapes")
to fast-path same-origin accesses, and check all others for complete
mediation. This work is happening in SpiderMonkey now. It will not
relieve us of compatibility requirements for split windows, but it
might allow us to bind |this| to the inner window. That doesn't say we
*should * so bind in the HTML 5 spec, of course.
IIRC from the March meeting, you and Waldemar were in favor of binding
|this| etc. to the inner window. I believe if we had done that in
Mozilla starting with Firefox 1.5 (when split windows were finally
implemented) we would have had significantly more exploits than we
have had given the "outerizing" defense we chose.
Of course you shouldn't be able to tell whether |this| binds to inner
or outer window, except when attacking a browser that access-checks
only via the outer window. One that checks all accesses would stop all
attacks, even when the inner-window capability leaked.
If we must have split windows in all top browser implementations (so
the argument goes), then we must have them in a spec. If not ECMA-262,
then HTML5. If split windows, then what |this| binds to (outer or
inner) needs to be spec'ed. So here we are. Comments?
On Mon, 6 Jul 2009, Brendan Eich wrote:
If we must have split windows in all top browser implementations (so the argument goes), then we must have them in a spec. If not ECMA-262, then HTML5. If split windows, then what |this| binds to (outer or inner) needs to be spec'ed. So here we are. Comments?
Your e-mail is in line with my understanding.
In HTML5, the "inner window" is the Window object and the "outer window" is the WindowProxy object. With one minor exception to do with about:blank, there's a 1:1 mapping from Document to Window, and a 1:1 mapping from WindowProxy to browsing context. Each browsing context can have many Window/Document pairs.
For what it's worth, right now HTML5 says:
If the script's global object is a Window object, then in JavaScript, the |this| keyword in the global scope must return the Window object's WindowProxy object.
This is a willful violation of the JavaScript specification current at the time of writing (ECMAScript edition 3). The JavaScript specification requires that the this keyword in the global scope return the global object, but this is not compatible with the security design prevalent in implementations as specified herein. [ECMA262]
I'd love to be able to include this requirement without it being a violation of another spec.
HTH,
Right now ES3 assumes that there is a single global object, which is used at the top of the scope chain and that is returned for "this" in the global scope.
It is possible to show that this is now what some browsers do:
var x = 1; function f() { return x; } var global = this; function g() { return global.x; }
// some other page's script takes references to f and g // browser navigates to a new page
var x = 2;
Now, if the other page's script calls f() and g(), it will get different results (2 and 1 respectively, if I didn't screw up the example code).
For HTML5, this behaviour has been defined in more detail. The global object is a Window object. This object is per-Document. The object returned by the "window" attribute on that global object is actually a WindowProxy object, which forwards everything to the "current" Window object. However, doing this has required that I require browsers to violate the requirement that the ES3 spec has, namely that "this" and the object at the top of the scope chain are both the global object, because in this model an invariant is that script cannot access the actual global object directly, only the proxy. The HTML5 spec says:
If the script's global object is a Window object, then in JavaScript, the this keyword in the global scope must, contrary to the ECMAScript specification, return the Window object's WindowProxy object.
If it would be possible for the ECMAScript specification to have a hook that allowed me to require this without violating the spec, that would be great.