This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 5850 - JS global object
Summary: JS global object
Status: VERIFIED FIXED
Alias: None
Product: HTML WG
Classification: Unclassified
Component: pre-LC1 HTML5 spec (editor: Ian Hickson) (show other bugs)
Version: unspecified
Hardware: All All
: P4 normal
Target Milestone: ---
Assignee: Ian 'Hixie' Hickson
QA Contact: HTML WG Bugzilla archive list
URL:
Whiteboard:
Keywords: NoReply
Depends on:
Blocks:
 
Reported: 2008-07-10 06:51 UTC by Ian 'Hixie' Hickson
Modified: 2010-10-04 14:31 UTC (History)
6 users (show)

See Also:


Attachments

Description Ian 'Hixie' Hickson 2008-07-10 06:51:50 UTC
Define that the "global object" for JS scripts is the script execution context.

Define that for JS scripts each script execution context implies a new set of prototype objects.

Change "script execution context's associated browsing context" to "script browsing context" or something.
Comment 1 Ian 'Hixie' Hickson 2008-07-10 07:00:26 UTC
See also: https://bugzilla.mozilla.org/show_bug.cgi?id=292731
Comment 2 Adam Barth 2008-07-10 07:30:24 UTC
Are you planning to spec what happens when a frame holds on to objects from a frame and then navigates that frame to another location?  Things get complicated in that case.  For example, you start to see split windows:

http://wiki.mozilla.org/Gecko:SplitWindow
https://bugzilla.mozilla.org/show_bug.cgi?id=296639

The issue here is that pointers to contentWindows (say obtained via window.frames[0]) persist across navigations (meaning they refer to the new contents of a frame after the frame has been navigated), but objects inside the navigated frame can't have their global object switched upon navigation (otherwise their prototypes and scope chains will be confused, i.e. exploitable).

Comment 3 Ian 'Hixie' Hickson 2008-07-10 08:02:51 UTC
Yes, I do plan to specify that (indeed most of it already is) -- but I don't see why that would be especially difficult to spec. Do you have test cases showing the complexity?
Comment 4 Adam Barth 2008-07-10 08:56:16 UTC
> Yes, I do plan to specify that (indeed most of it already is)

I searched the spec for a few key words, but I didn't find it.  Can you give me a pointer?

> -- but I don't
> see why that would be especially difficult to spec. Do you have test cases
> showing the complexity?

Maybe it's more simple than I thought.  Here are a couple test cases:

http://crypto.stanford.edu/~abarth/research/html5/split/test01.html
http://crypto.stanford.edu/~abarth/research/html5/split/test01.html

Safari 3.1 seems a bit confused.  I recommend trying one of the nightly builds instead.  IE7 seems to avoid this case by not letting you call closures from old frames.  Firefox 3 seems pretty aggressive about clearing out the old frame's objects (maybe to help garbage collection?).  Opera 9.5 throws an uncatchable exception, which kind of messes up the tests.

Comment 6 Ian 'Hixie' Hickson 2008-07-12 10:33:12 UTC
Adam: What's there now is in the session history section (search for "list of added properties").
Comment 7 Adam Barth 2008-07-12 17:02:01 UTC
I see.  The complexity arises when you access objects (like closures, etc) related to once-active but now non-active documents.  I should write another test case that uses a top-level browsing context instead of a nested browsing context because I bet those behave differently.
Comment 8 Adam Barth 2008-07-12 17:16:19 UTC
Here's a test-case for top-level windows:

http://crypto.stanford.edu/~abarth/research/html5/split/test03.html

The results are different than test01.html, which uses a nesting browsing context.  Here are some odd things from the "--- calling the before frame after navigating ---" case on Firefox 3:

document.body.innerHTML: Before...
window.document.body.innerHTML: After... 

document.location: http://crypto.stanford.edu/~abarth/research/html5/split/resources/after03.html
document.documentURI: http://crypto.stanford.edu/~abarth/research/html5/split/resources/before03.html

The results make sense if you understand how the implementation works.
Comment 9 Ian 'Hixie' Hickson 2008-07-15 09:25:49 UTC
(Note to self: This should overhaul the sentence that says "The <code>Window</code> object also provides the scope for script execution".)
Comment 10 Ian 'Hixie' Hickson 2008-07-15 12:07:50 UTC
So, how much do I need to define here? Does JS define that, e.g., "document" in a closure refers to the document object from the global scope at the time the function is compiled / closure is evaluated? Is it clear when/whether you obtain the value of window.String to handle a function doing "x".toString() ?

If so, then I don't know that we have to say anything really, except to make sure we define that all the built-in objects get renewed as well when the list of added properties is poked at.
Comment 11 Adam Barth 2008-07-16 18:48:29 UTC
> Does JS define that, e.g., "document" in
> a closure refers to the document object from the global scope at the time the
> function is compiled / closure is evaluated?

JavaScript has no notion of "at the time."  Each closure has a static scope chain that terminates in a global object.  The objects in this scope chain never change and names are resolved relative to this scope change.  The tricky bit is if the browser wants to reuse the global object for the contents of the frame after navigation.  If it does this, the scope chain for these closures is screwed up.

> Is it clear when/whether you
> obtain the value of window.String to handle a function doing "x".toString() ?

Yes.  They specify when and how to resolve names against the scope chain.

> So, how much do I need to define here?

One approach is to follow the design that Sam implemented in WebKit:

1) Each browsing context has a window object which also serves as the global object for JavaScript.  This is the normal way of thinking about JavaScript contexts.

2) Whenever any script gets a pointer to a window object, they instead get a pointer to a "window shell."  The window shell masquerades (using magic) as the window object currently occupying a fixed frame.  (The one exception to this rule is that the scope chain itself uses the real window/global object).

3) When a frame navigates, the new document gets a new window/global object.  The window shell is re-used, but now masquerades as the new window/global object (no longer as the old one).

The whole reason to this mess is so the following code works:

var win = frames[0];  // win now points to this frame's window shell
win.location.href = "http://newlocation.com/";
// Frame zero now contains a new window/global object, a new document, etc.
// ... time pass ...
alert(win.document.body.innerHTML);  // This is the document from the new frame
// because the window shell switched over to pretending to be the new window/global object.

> If so, then I don't know that we have to say anything really, except to make
> sure we define that all the built-in objects get renewed as well when the list
> of added properties is poked at.

I don't think this is the right approach.  You don't want to be mutating the window/global object.  In order to get things right you need a new window/global object for each browsing context.  The window shell lets scripts hold pointers to the "outside" of a frame (so they get the current contents whenever they ask for it).

I think this approach covers all the observed behavior, except for the behavior of "location."  That's because "location" is magical and actually refers to the frame (not the window or document of which it is a property.)
Comment 12 Ian 'Hixie' Hickson 2008-07-18 06:04:28 UTC
so basically the Window object reflects the properties of the global object of the active document and you can never get a pointer to the actual global object?

should, as heycam suggests, html5 say something like:  "when you go to run a script, instead of evaluating it with the ecma-262 global object as the 'this' value, evaluate it with this strange Window object that mostly reflects the ecma-262 global object as the 'this' value"?

...while keeping the _actual_ global scope object as the one on the scope chain?

does that even make sense?
Comment 13 Cameron McCormack 2008-07-18 07:30:30 UTC
I don't know if just adding that wording I suggested would give the behaviour that Adam describes.  Various places in ECMA-262 refer to the global object.  For example if you do:

  (function() { return this })()

then what's returned must be the global object.  It wouldn't be consistent with ECMA-262 to return some other object (like this window shell object).
Comment 14 Adam Barth 2008-07-18 07:45:21 UTC
(In reply to comment #12)
> so basically the Window object reflects the properties of the global object of
> the active document and you can never get a pointer to the actual global
> object?

Yes.  It does more than just reflect the properties.  It answers every question, and performs every action, as if it were the global object.  For me, this is easier to understand in code:

http://trac.webkit.org/browser/trunk/WebCore/bindings/js/JSDOMWindowShell.cpp

Notice that the object just forwards everything to m_window (which is the real global object).

> does that even make sense?

I'd try something like this:

Upon navigation, create a new global object for the new active document.  All script references to the old global object now refer to the new global object.  (Than add some proviso that the references in the scope chain are not "script" references, or some-such hair splitting.)

The implementation doesn't work by re-writing the references.  Instead it works by introducing a level of indirection between the references and the object to which they refer.
Comment 15 Ian 'Hixie' Hickson 2008-07-18 08:09:07 UTC
So if i have a Window object |window|, and I do:

   var x = window.foo;

...where "foo" is some object or function, and then I navigate |window|, and then I call x() or access x.bar, does that access the original object's foo? Or what?

I don't want to specify something that says that "all the references now point to a different object". That's weird. It's the same object in every sense that you can check from script. You can't ever get a reference to the underlying object. So I think it makes more sense to define Window as an object that forwards everything to an underlying object and just having that object change as you traverse back and forth.

But then we have:

   (function() { return this })()

...and this breaks down, unless we override ECMA 262 as well, which seems unwise.

Note to self: When fixing this, I need to go through and deal with references to things like "the origin of the active document of the browsing context of the Window object", now that we've split Window.
Comment 16 Adam Barth 2008-07-18 15:11:37 UTC
> So if i have a Window object |window|, and I do:
> 
>    var x = window.foo;
> 
> ...where "foo" is some object or function, and then I navigate |window|, and
> then I call x() or access x.bar, does that access the original object's foo? Or
> what?

Yes.  Only references to the object pointed to by |window| get magically changed to the new window object.

> I don't want to specify something that says that "all the references now point
> to a different object". That's weird. It's the same object in every sense that
> you can check from script.

Except for when you access properties of the object via the scope chain.  For example, after navigation |foo| and |window.foo| will give different answers in the global scope because the first comes off the scope chain whereas the second gets indirected through the window shell.

> You can't ever get a reference to the underlying
> object. So I think it makes more sense to define Window as an object that
> forwards everything to an underlying object and just having that object change
> as you traverse back and forth.

That's fine.  The important bit is that the underlying object sits atop the scope chain and ECMAScript thinks its the global object for the purposes of computing prototypes, etc.

> But then we have:
> 
>    (function() { return this })()
> 
> ...and this breaks down, unless we override ECMA 262 as well, which seems
> unwise.

Why does this break down?  I added this to test03.html and it appears to behave just like |window|.  That is, you get a reference to the window shell.
Comment 17 Ian 'Hixie' Hickson 2008-07-18 21:15:37 UTC
It breaks down because the JS spec says it returns the global object, not the proxy.
Comment 18 Adam Barth 2008-07-19 09:18:58 UTC
> It breaks down because the JS spec says it returns the global object, not the
> proxy.

I don't really see a way around this issue.  From test03.html:

document.body.innerHTML: Before... 
window.document.body.innerHTML: After... 
((function () { return this; })()).document.body.innerHTML: After... 
this.document.body.innerHTML: After... 

The first test pulls the document off the global object via the scope chain.  The second goes via window (aka the window shell).  The third and fourth case use "this" to get at "the global object," but they actually get the window shell.  That means "this" isn't acting the same as the global object (i.e., the object atop the scope chain).

> I don't want to specify something that says that "all the references now point
> to a different object". That's weird. It's the same object in every sense that
> you can check from script.

You mean that its the same object, except that all of its properties have changed to reflect the new contents of the frame?

Another way to think about this behavior is the way that Mozilla thinks about it.  In this view, there is one window/global object, but it is a "split" object, with an inside and an outside.  As long the object is belongs is currently occupying the frame, the two halves act as one.  After the frame navigates, the object splits in two, with the outer half now attached to the inner half of the new window/global object.  Most folks have pointers to the outer half the object, so they now are talking to the new inner object.  Some folks, like the scope chain, have pointers to the inner object and so they keep referring to the same inner object.

There is another wrinkle to this whole business that might be worth considering at the same time.  Suppose script in one frame has a pointer to the window object of another frame, but the two frames are from different security origins.  Not consider the the second frame (the one whose object this is) modifies (e.g., replaces) a property of the window object that is visible across domains (e.g., the "history" property).  Now the first frame (the one from a foreign domain) tries to access this property.  What does it see?  There are three possibilities:

1) It sees the modified object.
2) It sees the original object.
3) It sees some kind of error, like undefined.

Possibility (1) is a security vulnerability.  (I can explain more details if you're interested.)  As I recall, (2) is the Firefox/Safari behavior and (3) is the IE behavior.  I don't recall off-hand what Opera does in this case.

In Mozilla's split object way of thinking about the world, the cross-domain frame sees the outer half the object which doesn't contain the modifications.  (Of course, same-domain interrogators of the outer half will see the modifications.)  In Safari, the actual global object (not the shell) just appears to have different properties depending on who is asking.  (Literally, they check the security descriptor and then either give a fixed answer or look up the current state depending on how the access check turns out.)

I suspect none of this is allowed by the ECMAScript spec.
Comment 19 Ian 'Hixie' Hickson 2008-07-20 00:01:28 UTC
or 4) changing that kind of object itself is not allowed and raises a security exception.
Comment 20 Ian 'Hixie' Hickson 2008-07-20 00:04:50 UTC
In fact right now:
   http://www.whatwg.org/specs/web-apps/current-work/#security3
...the spec says that you can't get to window.history if you aren't same-origin, and the only non-readonly member that you can access cross-origin is 'location', which the spec forbids anyone from changing the setter of. (Similarly, cross-origin you can only access Location.href as a setter, and nobody is allowed to modify that setter.)
Comment 21 Ian 'Hixie' Hickson 2008-07-23 11:14:07 UTC
Opera is doing something weird with multiple Window objects where other UAs just have one. q.v.:
   http://damowmow.com/playground/demos/global-object/008.html

IE won't let you access code in pages that are no longer the frame's active document:
   http://damowmow.com/playground/demos/global-object/010.html

For properties on the global object that are defined in the IDL:
   http://damowmow.com/playground/demos/global-object/009.html
Both Safari and Firefox seem to refer to the properties for the most active document, even for the scope chain, if using a nested browsing context. If we use a separate window, as in:
   http://crypto.stanford.edu/~abarth/research/html5/split/test03.html
...then things on the scope chain refer to the old objects, except the window property returns the window object which has been updated to point to the new objects.

For properties on the global object that aren't IDL-defined properties:
   http://damowmow.com/playground/demos/global-object/004.html
Safari does the thing where the Window object acts like a proxy to the global object at the top of the scope chain, so things stay in scope but can't be seen from old or new references to 'this' or 'window'. If it's a nested browsing contex,t then Firefox clears out the global object, much like the spec says to today, but if it's a top-level browsing context, then it does what Safari does (c.f. test03 above).
Comment 22 Ian 'Hixie' Hickson 2008-07-23 11:18:24 UTC
Seems like we have enough lack of interop here that we can basically do whatever we want, so long as the browser vendors agree to it.

I kind of like what IE does -- it leaves the data accessible:
   http://damowmow.com/playground/demos/global-object/011.html
...but makes actually calling any code in that script from that era throw an exception.

That's nice and simple.
Comment 23 Ian 'Hixie' Hickson 2008-07-23 21:24:25 UTC
If we do that, I need to make sure history.go() and location.reload() become async or have defined behaviour for the script that called them if that script is in the same browsing context as the affected one.
Comment 24 Adam Barth 2008-07-27 09:22:37 UTC
As we discussed on IRC, I think you're right that there is a lack of interop here.  The IE behavior of refusing to run script from non-active documents is the most predictable and might be the semantics to adopt.  This has the added benefit of garbage collecting memory that might otherwise be kept around.

We also discussed the issue of synchronously navigating a frame when that frame already has script on the stack.  When control returns to that script, the script will be running in a non-active document.  I'm not wholly convinced you can eliminate all synchronous navigation of this sort, but it might be worth a try.
Comment 25 Ian 'Hixie' Hickson 2008-08-02 11:23:41 UTC
Ok, I guess I'll have to speak to the browser vendors about this and see what they think.
Comment 26 Ian 'Hixie' Hickson 2008-12-30 01:10:30 UTC
I ended up speccing the IE model, but there are performance concerns with this. I'll take this to e-mail.
Comment 27 Maciej Stachowiak 2010-03-14 13:15:10 UTC
This bug predates the HTML Working Group Decision Policy.

If you are satisfied with the resolution of this bug, please change the state of this bug to CLOSED. If
you have additional information and would like the editor to reconsider, please reopen this bug. If you would like to escalate the issue to the full HTML Working Group, please add the TrackerRequest keyword to this bug, and suggest title and text for the tracker issue; or you may create a tracker issue yourself, if you are able to do so. For more details, see this document:
   http://dev.w3.org/html5/decision-policy/decision-policy.html

This bug is now being moved to VERIFIED. Please respond within two weeks. If this bug is not closed, reopened or escalated within two weeks, it may be marked as NoReply and will no longer be considered a pending comment.