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 20701 - Security: Redesign how cross-origin-visible objects work (Location, Window)
Summary: Security: Redesign how cross-origin-visible objects work (Location, Window)
Status: RESOLVED MOVED
Alias: None
Product: WHATWG
Classification: Unclassified
Component: HTML (show other bugs)
Version: unspecified
Hardware: Other Linux
: P1 critical
Target Milestone: Unsorted
Assignee: Ian 'Hixie' Hickson
QA Contact: contributor
URL: http://www.whatwg.org/specs/web-apps/...
Whiteboard: spec_lags_implementation
Keywords:
Depends on:
Blocks: 21674 22346 27128
  Show dependency treegraph
 
Reported: 2013-01-17 22:04 UTC by contributor
Modified: 2017-11-27 00:53 UTC (History)
27 users (show)

See Also:


Attachments
Tests. v1 (17.80 KB, patch)
2014-01-29 03:35 UTC, Bobby Holley (:bholley)
Details
it is a crowdflower extention from crowdflower.com to do tasks with. (196 bytes, text/plain)
2016-01-23 09:44 UTC, jen1.6.15m
Details

Description contributor 2013-01-17 22:04:36 UTC
Specification: http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html
Multipage: http://www.whatwg.org/C#security-3
Complete: http://www.whatwg.org/c#security-3

Comment:
Location security restrictions are over-restrictive

Posted from: 173.48.81.109 by bzbarsky@mit.edu
User agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20130108 Firefox/21.0
Comment 1 Boris Zbarsky 2013-01-17 22:06:22 UTC
Since Location objects are per-Window, if you have your hand on one that means you're same-origin (or at least same-origin-enough) with that Window to start with.  There is therefore no need to prevent sets of random non-IDL properties on the object if the browsing context of its Window has since been navigated to a different-origin document.

The thing that needs to be prevented are the getters and setters of various of the IDL attributes on the interface.
Comment 2 Ian 'Hixie' Hickson 2013-03-13 23:48:24 UTC
There's just one Location object per Document, shared between all origins, according to the spec, unless I'm missing something major (in which case I really need to make that clearer). Did we conclude that there should be a unique instance per origin and I just screwed up actually doing that or something? That sounds familiar, but I don't recall what the conclusion was. From what I can tell, the spec matches what WebKit and IE do.


(If Location object were per-origin, then:

So the main risk we're worried about is someone finding out where the user navigated to, right? That, and the author navigating a frame to a different (spoof) page than the user intended to go to.

So we have to prevent reads of the URL data when the effective script origin of the Location's Document's browsing context's active document doesn't match the entry script's effective script origin.

And we have to prevent writes to any of the URL data and all the methods, when the Location's Document's browsing context's active document's effective script origin doesn't match the entry script's effective script origin, except that we allow invocations of .href's setter and the .replace() method in the specific case of the entry script's browsing context being allowed to navigate the Location's Document's browsing context.)
Comment 3 Boris Zbarsky 2013-03-14 00:53:06 UTC
> There's just one Location object per Document,

Yes, but the origin of a Document is immutable, so the origin of that Location object is likewise immutable: it's the origin of the document.

Or do you mean there's one Location object per WindowProxy? I was pretty sure that this was NOT the setup we agreed on.

I agree that we need various restrictions on the IDL-defined bits of Location.  I just don't see why we need restrictions on setting .myMagicBeansProperty on some Location object you already have a reference to.
Comment 4 Ian 'Hixie' Hickson 2013-03-14 22:39:31 UTC
Per the spec today, the Location object's members expose the address of the object's associated Document object's browsing context's active document, not address of the object's associated Document.

Gecko does what the spec says as far as I can tell (scroll down in the iframe and click "fun"):
   http://software.hixie.ch/utilities/js/live-dom-viewer/saved/2162

Chrome seems to neuter the Location object entirely after navigation. Not sure what's going on there.

Did we decide to change the spec on this? I do recall discussing this, but I thought that thread had concluded with all the relevant changes being made. It's possible that I poorly communicated about what I had concluded needed changing, or that I edited the spec incorrectly, or that I haven't actually dealt with the relevant feedback yet, or that you disagreed and that it is still open somewhere that I haven't gotten to yet... :-|
Comment 5 Boris Zbarsky 2013-03-15 01:02:48 UTC
> Did we decide to change the spec on this?

No, I don't think we did.

We all agree what should happen with the IDL-defined location members.

This bug is about "expandos" on Location objects.  The current spec phrasing prohibits them based on whether your origin matches the origin of the browsing context's current document, which doesn't seem necessary unless I'm missing something.
Comment 6 contributor 2013-03-15 23:01:00 UTC
Checked in as WHATWG revision r7758.
Check-in comment: Allow custom properties on Location objects to work for the Document whose Location object it originally was.
http://html5.org/tools/web-apps-tracker?from=7757&to=7758
Comment 7 Ian 'Hixie' Hickson 2013-03-15 23:05:08 UTC
Oh, oh, I get it, I'm sorry. I'm just slow. And dumb. Please don't mind me.

Ok, I've tried to fix this. Given my clear lack of competence in this bug, I highly recommend you check my work. :-)
Comment 8 Boris Zbarsky 2013-03-21 15:10:43 UTC
So what it says now is:

  Any properties not defined in the IDL for the Location object or indirectly
  via one of those properties (e.g. toString(), which is defined via the
  stringifier keyword), if the entry script's effective script origin is the
  same origin as the Location object's associated Document's effective script
  origin 

but this is assuming that the entry script's effective script origin is the same as the effective script origin of the script doing the access, which is generally not a good assumption.  In any case, it's better to just fall back to the default same-origin policy (whatever that ends up being) rather than redefining it here.

I think it would be better to lift the language about "IDL-defined" into the paragraph that talks about the restriction instead of listing it as an exception.  So more like so:

  When accessing any attribute or calling any method defined in the IDL for a
  Location object, or indirectly via the IDL (e.g. toString(), which is
  defined via the stringifier keyword), user agents must throw a SecurityError
  when the entry script's effective script origin is not the same as the
  Location object's associated Document's browsing context's active document's
  effective script origin, with the following exceptions:

  * The href setter, if the entry script's script's browsing context is allowed
    to navigate the browsing context with which the Location object is
    associated
  * The replace() method, if the entry script's script's browsing context is
    allowed to navigate the browsing context with which the Location object is
    associated

or something.  Note that this also makes it clear that doing .assign on the Location is fine; it's _calling_ it that throws, which is I assume the intent.
Comment 9 Ian 'Hixie' Hickson 2013-04-26 23:30:17 UTC
> this is assuming that the entry script's effective script origin is the
> same as the effective script origin of the script doing the access, which is
> generally not a good assumption.

The only time it can be anything else is when a script has changed document.domain after passing a reference to one of its functions to another script in the same original origin as it had, with that function then being invoked by that other script, as far as I can tell.
 
Let's first define some terms:

Script A is a script in origin X.
Script B is a script in origin X, which then sets document.domain to switch to origin Y.
Script C is a script in origin Z, which then sets document.domain to switch to origin Y as well.
Script D is a script in origin Z.

If we check the entry script, then what matters is what script begins the sequence of events that ends with a function in A, B, C, or D accessing the Location object.

If we check the calling script, then it has to be A doing the final access, but any of B, C, or D can actually start the process that invokes that function.

I guess for completeness we really want to check both. If either is possible (i.e. if A or B leaked a function one way or the other, and that function gets called by someone), the other is probably possible too.

If we want to check the calling script, this probably becomes dependent on the bug about defining what that means exactly.


> Note that this also makes it clear that doing .assign on the
> Location is fine; it's _calling_ it that throws

Do we really want it to be possible to take a reference to Location.assign without throwing? That seems like asking for trouble...
Comment 10 Boris Zbarsky 2013-04-29 00:07:42 UTC
> The only time it can be anything else is when a script has changed
> document.domain after passing a reference to one of its functions to another
> script

This is the case in the web platform as you've defined it, but not in all implementations of the web platform now, and not necessarily in all such implementations in the future.

A simple counterexample would be a UA that implements parts of its DOM in (self-hosted) JS, which for example Blink is claiming it wants to do.  At that point you have to worry about which scripts are part of the "UA" and which are part of "web pages", if nothing else.

> Do we really want it to be possible to take a reference to Location.assign
> without throwing?

You can always do that: just run |var x = Object.getOwnPropertyDescriptor(location, "assign")| in a window which you control.  (It's an own property because of the unforgeable bits; otherwise you can do that on the prototype with the same effect.)
Comment 11 Adam Barth 2013-04-29 00:36:32 UTC
(In reply to comment #10)
> > The only time it can be anything else is when a script has changed
> > document.domain after passing a reference to one of its functions to another
> > script
> 
> This is the case in the web platform as you've defined it, but not in all
> implementations of the web platform now, and not necessarily in all such
> implementations in the future.
> 
> A simple counterexample would be a UA that implements parts of its DOM in
> (self-hosted) JS, which for example Blink is claiming it wants to do.  At
> that point you have to worry about which scripts are part of the "UA" and
> which are part of "web pages", if nothing else.

We're unlikely to use self-hosted JavaScript for objects that are visible across origins.  That's just not worth the complexity.
Comment 12 Boris Zbarsky 2013-04-29 13:37:32 UTC
My point is that unless the use of self-hosting is incredibly restricted I expect that the self-hosted JS will effectively run with a different origin from the page scripts, because it needs to be able to do things that normal scripts can't do (e.g. it may well need to be able to set the values of file controls and such).  Which means that if you have page script calling into self-hosted JS that then calls into some method on an object that method needs to know that it's called from the self-hosted JS and not from the page.

Again, unless the expected use of self-hosting is very very limited.
Comment 13 Adam Barth 2013-04-30 15:42:02 UTC
I don't fully understand the problem.  It's likely we haven't gotten far enough into the implementation to run into this issue.

It seems that the spec only covers web site JS.  Any JS used by the user agent internally is an implementation detail of the UA and need not follow the rules in the spec.
Comment 14 Boris Zbarsky 2013-04-30 15:58:48 UTC
While that's true, the spec is making requirements on the underlying DOM implementation that assume things about the script entry point being the relevant thing for security purposes, and the fact that UA JS is on the stack won't change the script entry point (because the fact that UA JS even exists needs to be transparent to the web page).

The upshot of all of which is that the spec becomes much less useful once you start implementing parts of your UA in JS and you have to read between the lines to try to figure out what it _really_ meant instead of just implementing the spec exactly like it says.

Which is why I would prefer, if possible, just defining things in the spec in such a way that they work whether your UA is implemented in privileged JS, or in C++, or in Rust, or in whatever the heck you want.  If it's not possible, of course, then the spec will just need to get ignored for internal UA JS as needed, as you say.
Comment 15 Ian 'Hixie' Hickson 2013-04-30 17:42:48 UTC
If you want to change the security model of the Web, please file a separate bug. For the purposes of this bug, let's assume that the security model is what the spec says.

I'm not familiar with Object.getOwnPropertyDescriptor(); can you elaborate on how you would use that to fiddle with a different Location object? (Which would have a different prototype, etc.)
Comment 16 Boris Zbarsky 2013-04-30 18:25:43 UTC
> let's assume that the security model is what the spec says.

<shrug>.  To the extent that this assumption is false and the spec wants to assume it when it's not needed the spec is less useful. But fine.

> I'm not familiar with Object.getOwnPropertyDescriptor()

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor

> can you elaborate on how you would use that to fiddle with a different Location
> object?

Assuming that "location" is from your window and "otherLocation" is from some other window:

  var desc = Object.getOwnPropertyDescriptor(location, "assign");
  desc.value.call(otherLocation, "some string here");

This requires the actual assign method to perform the security check if you want there to be a security check here; performing it while getting the method doesn't work, since it's gotten from a same-origin object.
Comment 17 Ian 'Hixie' Hickson 2013-06-06 20:40:04 UTC
How is that different from:

  var ourAssign = location.assign;
  ourAssign.call(otherLocation, "some string here");

...? Why are location and otherLocation, which have different prototypes (right?) type-compatible enough for this to work?

Is this specific to Location? It seems like if you could work around restrictions like this, you could easily work around Document's, too, and walk any random origin's DOM tree by just using your own methods on their objects.
Comment 18 Boris Zbarsky 2013-06-06 20:44:58 UTC
> How is that different from:

It's not.

> Why are location and otherLocation, which have different prototypes
> (right?) type-compatible enough for this to work?

Because that's how DOM objects generally work.  There are libraries that depend on this behavior in various case for nodes, for sure.

> Is this specific to Location?

No.

> you could easily work around Document's, too, and walk
> any random origin's DOM tree by just using your own methods on their objects.

Which is why WebIDL methods have to do security checks at call time, conceptually.  And do in UAs, last I checked.  There's been lots of discussion about the fact that the spec needs to define this behavior.
Comment 19 Boris Zbarsky 2013-06-06 20:47:14 UTC
> Which is why WebIDL methods have to do security checks at call time,

And getters and setters, of course.

> Because that's how DOM objects generally work.

And more precisely, different-origin objects should _not_ be considered "type-compatible enough".  Which is exactly what the call-time security check would need to check.
Comment 20 Ian 'Hixie' Hickson 2013-06-13 00:23:29 UTC
Ok, then this is a more generic problem, it's not about Location. Is there a bug filed on the more generic problem already?

I've filed bug 22346 for this for now, on the assumption that there isn't.

I've closed this bug, since that is the only remaining issue here as far as I'm aware. If there's something else I missed specific to Location, please feel free to reopen this bug.
Comment 21 Boris Zbarsky 2013-06-13 05:01:13 UTC
Are the issues from comment 8 now addressed?  They sure don't seem to be.

Apart from the editorial aspects, why are we still disallowing .replace (as opposed to .replace())?  There's no reason to disallow it, imo.

This also still using the entry script's effective script origin, not the origin we really want...
Comment 22 Ian 'Hixie' Hickson 2013-06-20 22:52:14 UTC
(In reply to comment #21)
>
> Apart from the editorial aspects, why are we still disallowing .replace (as
> opposed to .replace())?  There's no reason to disallow it, imo.

Are you interpreting "The replace() method" as meaning that .replace is disallowed when .replace() is not? If so, that isn't my intent. My intent is to prevent access to the method, which is called "replace()", and which is accessed using .replace, and then called by using "()". The wording here is consistent with the wording throughout the spec.


> This also still using the entry script's effective script origin, not the
> origin we really want...

Gecko uses the incumbent script.
IE uses the entry script.
Safari uses the incumbent script but uses a different failure mode (undefined rather than throwing an exception).

http://www.hixie.ch/tests/adhoc/dom/level0/location/cross-origin/003.html

(Chrome fails 001, the control test, so I'm ignoring it here.)

I guess we could change this to the entry script, but it's not clear to me it really matters (see comment 9 for details). Is matching Gecko better than matching IE here?


For comment 8:
> So what it says now is:
> 
>   Any properties not defined in the IDL for the Location object or indirectly
>   via one of those properties (e.g. toString(), which is defined via the
>   stringifier keyword), if the entry script's effective script origin is the
>   same origin as the Location object's associated Document's effective script
>   origin 
> 
> but this is assuming that the entry script's effective script origin is the
> same as the effective script origin of the script doing the access, which is
> generally not a good assumption.

It's a fine assumption in the absence of document.domain. For a discussion of the implications of document.domain, see comment 9. For the issue of changing the security model of the Web in general, I have a pending e-mail from you to respond to.


> In any case, it's better to just fall back to the default same-origin policy
> (whatever that ends up being) rather than redefining it here.

What's "the default same-origin policy"? As far as I can tell, there is no such thing. All the objects that present origin barriers (Window, Document, Location, and Storage are the ones that come to mind) have subtly different rules.


> I think it would be better to lift the language about "IDL-defined" into the
> paragraph that talks about the restriction instead of listing it as an
> exception.  So more like so:
> 
>   When accessing any attribute or calling any method defined in the IDL for a
>   Location object, or indirectly via the IDL (e.g. toString(), which is
>   defined via the stringifier keyword), user agents must throw a
>   SecurityError when the entry script's effective script origin is not the 
>   same as the Location object's associated Document's browsing context's 
>   active document's effective script origin, with the following exceptions:
> 
>   * The href setter, if the entry script's script's browsing context is
>     allowed to navigate the browsing context with which the Location object
>     is associated
>   * The replace() method, if the entry script's script's browsing context is
>     allowed to navigate the browsing context with which the Location object
>     is associated
> 
> or something. 

The reason it's written as it is now is to make the security sections have a consistent format between each such section, so you can tell how they differ more easily. I agree that in a vacuum, the above might be clearer. But in context, it would obscure the fact that the Window object doesn't have that exception (such that you can't actually access its "expando" properties at all while you're cross-origin with the active Document), unlike with Location, where you can if it is "your" Location object, despite the active document being in a different origin.


> Note that this also makes it clear that doing .assign on the
> Location is fine; it's _calling_ it that throws, which is I assume the
> intent.

The intent is that getting it fails. If we wanted to make just calling it fail, we'd just put that in the method's definition. (Calling it will fail if getting it fails, because of bug 22346.)
Comment 23 Boris Zbarsky 2013-06-21 03:51:57 UTC
> Are you interpreting

I'm interpreting it as ".replace is not allowed".  My point is that there is no reason to disallow it.  What _should_ be disallowed is the invocation of the method, whether it's as otherLocation.replace() or location.replace.call(otherLocation).  We're now disallowing that, but for some reason still also disallowing otherLocation.replace, which I think there's no particular reason to do.

> I guess we could change this to the entry script

Did you mean the "incumbent script"?

I think that any time we're doing a security check we should be using the actual thing doing the access.  Note that this matches neither "entry script" nor "incumbent script" as you've defined them.

> What's "the default same-origin policy"? 

Whenever you talk about things being "same-origin" you have to first say what those things are.

For purposes of property access on the web, typically, what you are interested in is the effective script origin of the thing ("code"?  It's a bit complicated) doing the access, which is not exactly well-defined in the specs right now but should be, and the effective script origin of the object being accessed (which is typically derived from the effective script origin of the document or Window it's associated with).  Presumably this setup will get well-defined at some point, at which point we should, imo, just use that definition here.

Is that clearer?

> so you can tell how they differ more easily

I'm not sure that's more important than understanding what the sections actually say, though...  Anyway, I think the difference is just clarity, not substance, so I'm not going to fight too hard over it.

> The intent is that getting it fails. 

I think I disagree with that being desirable.  As you note, calling it will already fail; there is no particular reason to prevent getting it.

But again, I'm not going to push on that too hard, depending on what Bobby thinks.
Comment 24 Boris Zbarsky 2013-06-21 03:52:22 UTC
Oh, and for the next two weeks or so responses from me will be very laggy at best, unfortunately.  :(
Comment 25 Boris Zbarsky 2013-06-21 18:00:38 UTC
> I think I disagree with that being desirable.

Here is why I disagree, after thinking on it.

If the security checks on Location just happen in the normal way (on all property gets for cross-origin access, but only on actual DOM getter/setter/method invocations for your own Location, then you can just have Location be a normal object, with the cross-origin case mediated by a security membrane.

But if you require random property gets which would normally find the property on the _prototype_ to not work on same-origin Location objects, then you require Location to be implemented as some sort of proxy, which significantly over-constrains implementations.

So the spec is introducing additional implementation complexity (given that the check in the DOM getter/setter/method need to be done no matter what anyway), for no particularly good reason that I can see.
Comment 26 Bobby Holley (:bholley) 2013-06-26 18:08:20 UTC
(In reply to comment #25)
> If the security checks on Location just happen in the normal way (on all
> property gets for cross-origin access, but only on actual DOM
> getter/setter/method invocations for your own Location, then you can just
> have Location be a normal object, with the cross-origin case mediated by a
> security membrane.
> 
> But if you require random property gets which would normally find the
> property on the _prototype_ to not work on same-origin Location objects,
> then you require Location to be implemented as some sort of proxy, which
> significantly over-constrains implementations.
> 
> So the spec is introducing additional implementation complexity (given that
> the check in the DOM getter/setter/method need to be done no matter what
> anyway), for no particularly good reason that I can see.

Yes. This.

In response to our discussion about Location last year, I reimplemented our Location security checks to happen in the actual C++ implementation of the operations. This means that window.location is just a regular object like window.document, with no special security wrapper or anything.

But since Gecko implements property-access checks with security wrappers, we don't have any way to deny access to the property itself for script running in the same global (since, aside from Location, all the security checks in the spec involve the origin of associated document, not the current origin of the browsing context). Changing that, as Boris points out, would be a huge undertaking requiring rewriting our Location implementation as some sort of Proxy. And it's not one I'm likely to undertake without more compelling rationale.
Comment 27 Ian 'Hixie' Hickson 2013-07-02 23:29:52 UTC
I think we're talking at cross-purposes here.

What the spec is saying is that in the cross-origin case:

   getting Location.assign throws a SecurityError exception

   directly invoking Location.assign isn't possible since you can't get it

   indirectly invoking Location.assign's [[Call]] does too, because of bug 22346

   setting Location.assign has no effect

   getting Location.replace throws a SecurityError exception if certain
   conditions aren't met (to do with "allows to navigate")

   directly invoking Location.replace's [[Call]] works if getting it would work,
   as per bug 22346

   indirectly invoking Location.replace's [[Call]] also works if getting it
   would work, as per bug 22346

Are you saying you want .assign to return a method (which can never be invoked on the object since we're cross-origin)? This would be inconsistent with the way that every non-method property throws an exception on Location, and the way every method property throws an exception on, e.g., Window. For example:

   <!-- on software.hixie.ch: -->
   <!DOCTYPE html>
   <iframe src="http://damowmow.com/"></iframe>
   <script>
    onload = function () {
      var f = frames[0];
      w(f.showModalDialog);
    }
   </script>

This throws a SecurityError, because you can't get that method.

   http://software.hixie.ch/utilities/js/live-dom-viewer/saved/2390

Why would things be different for Location?
Comment 28 Boris Zbarsky 2013-07-02 23:48:06 UTC
> What the spec is saying is that in the cross-origin case:

Define "cross-origin case"?

What the spec is saying right this second is that whether I can get Location.assign depends on "the Location object's associated Document's browsing context's active document's effective script origin".

This means that whether script in some window can touch Location.assign in that same window (which is normally considered a _same_ origin access) depends on some random other bits of state.  I don't think it should depend on those bits of state.

> Are you saying you want .assign to return a method (which can never be invoked
> on the object since we're cross-origin)?

Yes.  For example, the method can be invoked on some other object.

> This would be inconsistent with the way that every non-method property throws
> an exception on Location

How so?  Value properties on Location shouldn't throw exceptions on getting: that's what this bug is about.  The WebIDL getters and setters for WebIDL-defined accessor properties throw exceptions when invoked with the "wrong" Location object (as determined by the active document stuff quoted above).

> and the way every method property throws an exception on, e.g., Window.

With a Window there's an actual cross-origin vs same-origin check, not taking and object that's actually in your global and treating it as different-origin sometimes randomly.

> Why would things be different for Location?

Because the definition of "cross-origin" you're trying to use for Location is a crazy one and shouldn't be used as a definition of "cross-origin".
Comment 29 Ian 'Hixie' Hickson 2013-07-03 22:18:33 UTC
>> This would be inconsistent with the way that every non-method property throws
>> an exception on Location
> 
> How so?  Value properties on Location shouldn't throw exceptions on getting:
> that's what this bug is about.

I'm talking about ".href" and ".hash" and ".hostname" and so on.

http://software.hixie.ch/utilities/js/live-dom-viewer/saved/2391

Click the button once the cat has loaded, and you get a security error.
Why would you not want the same security error for .assign?

I understand that at some theoretical level this is different than Window, but honestly I don't think most authors are going to get the difference between grabbing the Location object of the iframe when it was created, and grabbing a Location object from that iframe after they've made the iframe load a cross-origin document. It just seems very confusing.
Comment 30 Bobby Holley (:bholley) 2013-07-03 23:03:06 UTC
(In reply to comment #29)
> Click the button once the cat has loaded, and you get a security error.

'you get' is underdefined here, but I'll interpret it as "In Firefox", since that's the behavior that Boris and I are advocating here.

> Why would you not want the same security error for .assign?

Because, from an EcmaScript/WebIDL perspective, accessing L.href calls a setter, whereas accessing L.assign just accesses a value prop. If you look at it another way, Object.getOwnPropertyDescriptor(L, 'href') does not throw a security error. Why should Object.getOwnPropertyDescriptor(L, 'assign') throw one?

> I understand that at some theoretical level this is different than Window

It is _very_ different, because of the way our discussion about Location ended up this past winter. As we ended up speccing it, Location is an object with a per-Document identity, but per-BrowsingContext security characteristics. If we could just treat Location either like Window or like Document, it would be fine. As it stands, the current spec is very difficult for Gecko to implement, and the rationale for making it that way is not super compelling.

In Gecko, property-access checks happen at the boundary between different globals. Because the security characteristics of Location change dynamically with the state of the browsing context, we currently do Location security checks by doing security checks in the underlying C++ method. The spec requires that these checks happen at property descriptor level (even if no DOM functionality is ever invoked). If we were to follow it, we'd need to make Location some sort of Proxy with dynamic property lookup behavior, which is needlessly complicated. So implementing the current spec is a hardship for Gecko.

On the flip side, implementing Boris' proposal is unlikely to be a hardship for other implementations, because everybody already has to guard against:

var hrefGetter = Object.getOwnPropertyDescriptor(win.location, 'href').get;
/* Navigate win */
hrefGetter.apply(win.location);

That is to say - the security situation in the current world already requires the underlying methods to do security checks. But it doesn't require them at property access time.

> but honestly I don't think most authors are going to get the difference
> between grabbing the Location object of the iframe when it was created, and
> grabbing a Location object from that iframe after they've made the iframe
> load a cross-origin document. It just seems very confusing.

Quite honestly, most authors aren't going to grok the details no matter what we do. In practice, the only observably-different behavior here happens when running code in navigated-away-from Windows. My primary interest here is a sane spec that vendors will implement, so that we get consistent behavior.
Comment 31 Boris Zbarsky 2013-07-04 03:17:42 UTC
> I'm talking about ".href" and ".hash" and ".hostname" and so on.

Those aren't value properties.  They're accessor properties.

> Why would you not want the same security error for .assign?

Because that requires adding a third kind of security thing (in addition to the existing "Window" and "every other object" cases) to the platform.  That's a steep price to pay for making .assign not work... while properties that the page polyfills on Location or Location.prototype continue to work.  And more to the point there is no security reason for .assign to work.

> I understand that at some theoretical level this is different than Window,

It's not a theoretical level.  It's a practical one: it significantly increases the implementation burden for no benefit I can see.
Comment 32 Ian 'Hixie' Hickson 2013-07-09 20:59:38 UTC
Well I don't want to make it hard to implement; that's good to know.


So what you're saying is that you want the security model here to be totally different than Window's. Rather than doing something for all properties, what you really want is:

- The getters for everything on URLUtils imported by Location must throw
  SecurityError if the incumbent script has an effective script origin that's
  not the same as Location object's associated Document's browsing context's 
  active document's effective script origin.

- The setters for everything on URLUtils imported by Location other than 'href' 
  must throw SecurityError if the incumbent script has an effective script 
  origin that's not the same as Location object's associated Document's
  browsing context's active document's effective script origin.

- All the methods on Location other than replace() must throw SecurityError
  when called if the incumbent script has an effective script origin that's
  not the same as Location object's associated Document's browsing context's
  active document's effective script origin.

- The href setter and the replace() method must throw SecurityError when called
  if the incumbent script's script browsing context is not allowed to navigate
  the Location object's associated Document's browsing context.

Furthermore, we're saying Location is immune to bug 22346's future logic.

We'd be leaving the current mechanism by which each Document gets its own Location (which reflects the browsing context's location, not the Document's), and we'd be leaving these two paragraphs:

# When the entry script's effective script origin is different than a Location 
# object's associated Document's effective script origin, the user agent must 
# act as if any changes to that Location object's properties, getters, setters, 
# etc, were not present, and as if all the properties of that Location object 
# had their [[Enumerable]] attribute set to false.
#
# For members that return objects (including function objects), each distinct 
# effective script origin that is not the same origin as the Location object's 
# Document's effective script origin must be provided with a separate set of 
# objects. These objects must have the prototype chain appropriate for the 
# script for which the objects are created (not those that would be appropriate 
# for scripts whose script's global object is the Location object's Document's 
# Window object).

Is all that right?
Comment 33 Boris Zbarsky 2013-07-09 21:20:39 UTC
That sounds about like what I'm after to me, yes.  Bobby?
Comment 34 Bobby Holley (:bholley) 2013-07-10 20:58:36 UTC
Thanks for taking the time to dive into this, Hixie. Boris and I spoke about this on the phone for almost an hour last night, which (along with the length of this comment) should be some indication of how much of a swamp this Location stuff is. ;-)

I think the proposal in comment 32 would be quite straightforward for us to implement But I have some concerns, and I'd like to make sure we think through its implications first, for the sake of future implementations. Let's start by defining some terms.

There are two origins at play here: the origin of the Document associated with the Location object (henceforth 'Document Origin'), and the origin of the Location object's associated Document's associated Browsing Context's active Document (henceforth 'Browsing Context Origin'). The former is immutable modulo document.domain, the latter is not (this has important implications, which I'll discuss shortly).

There are also two places where security checks might be performed. The first is in the implementation of the ES5 Meta-Object Protocol, when properties are accessed (henceforth 'Property-Access Checks'). The second is in the implementation of the various getters, setter, and methods returned by the MOP, when executed (henceforth 'Method-Call Checks').

IIUC, the spec currently mandate Property-Access Checks that compare the Incumbent Script Origin to the Browsing Context Origin. The spec currently requires no Method-Call Checks, but we know that's broken and are fixing the spec in bug 22346.

Gecko currently does both Property-Access Checks with the Document origin and Method-Call Checks with the Browsing Context Origin. 

Boris filed this bug to argue that Property-Access Checks on the Browsing Context Origin are needlessly burdensome on the implementation, because it requires Location to be some sort of Proxy under the hood.

Hixie's proposal in comment 32 is to remove Property-Access Checks on Location entirely, and do Method-Call Checks comparing the Incumbent Script Origin to the Browsing Context Origin.

This proposal is satisfactory security-wise in terms of the methods and attributes defined in the Location interface. However, when it comes to other properties, it exposes very new behavior to the web: for some cross-origin iframe window |xoWin|, script can now set |xoWin.location.foopy = 2;|. That is to say, it permits "expandos" (Gecko parlance, not sure if there's a spec equivalent) on objects in the scope of cross-origin globals. Since there can by any number of origins involved, and since we presumably don't want them to see each others' expandos, there would need to be a separate view of the target object for each origin. The spec would probably have to be updated to specify this explicitly.

The de-facto web platform currently never allows expando properties to be set on any object that is not same-origin with the incumbent script. The spec requires a "vanilla object" to be created for cross-origin access to Window, but that object is immutable, which means that implementations could potentially share the same instance between _all_ non-same-origin consumers, or have read-only view to the native properties.

Because of its embedding requirements, Gecko already has a mechanism to maintain such a map of per-origin expandos on cross-origin objects. But that mechanism is quite complicated. And so while we could implement the proposal here without any trouble, it could be a major roadblock for other implementations. Gecko's machinery also just ignores document.domain for the expando map, and I'm not convinced that there's a sane way to take it into account.

In a nutshell, the security requirements of the web mandate Method-Call Checks against the Browsing Context Origin. But we also need something on the MOP level to prevent multiple third-party origins from viewing each other's expandos on a Location object they're both observing.

Because the Browsing Context Origin is mutable, it's not an ideal thing to use here (since a page could set an expando and then have it suddenly become inaccessible due to a navigation). As such, we probably want to use the Document origin, here. At which point we have two options that I can think of:

(1) Property-Access Checks against the Document origin
(2) No checks, but mandate that implementations somehow keep different origins from viewing each others' expandos on the same object.

I think (2) is likely to be much harder to spec clearly, and much harder to implement. (1) is what Gecko currently does, and what I think makes the most sense.
Comment 35 Ian 'Hixie' Hickson 2013-07-13 15:22:14 UTC
> There are two origins at play here:

There's also the incumbent effective script origin, so at least three origins.

In general, I agree with your description.


> However, when it comes to
> other properties, it exposes very new behavior to the web: for some
> cross-origin iframe window |xoWin|, script can now set |xoWin.location.foopy
> = 2;|. That is to say, it permits "expandos" (Gecko parlance, not sure if
> there's a spec equivalent) on objects in the scope of cross-origin globals.
> Since there can by any number of origins involved, and since we presumably
> don't want them to see each others' expandos, there would need to be a
> separate view of the target object for each origin. The spec would probably
> have to be updated to specify this explicitly.

The idea of the first of the two paragraphs quoted at the end of comment 32 is that if it's not "your" Location object, you can't modify it, and you can't see any modifications on it.

Also note that Window.location returns a "new" object for each origin:

# For members that return objects (including function objects), each distinct 
# effective script origin that is not the same as the Window object's Document's 
# effective script origin must be provided with a separate set of objects. These 
# objects must have the prototype chain appropriate for the script for which the 
# objects are created (not those that would be appropriate for scripts whose 
# script's global object is the Window object in question).

Is that text insufficient? (I will be the first to admit that I'm not sure if that text is written correctly and clearly enough to achieve the effect I want here. I'm still not fully versed in the implications of such things.)
Comment 36 Bobby Holley (:bholley) 2013-07-16 22:51:21 UTC
(In reply to comment #35)
> > There are two origins at play here:
> 
> There's also the incumbent effective script origin, so at least three
> origins.

Right, fair point.

> Is that text insufficient? (I will be the first to admit that I'm not sure
> if that text is written correctly and clearly enough to achieve the effect I
> want here. I'm still not fully versed in the implications of such things.)

I think it's problematic. Consider the following passage:

> These objects must have the prototype chain appropriate for the script for
> which the objects are created

The notion of 'the script for which the objects are created' is underdefined here. Suppose we have two windows, A and B, with origin foo.com. These two windows access cross-origin objects from window C, which has origin bar.com. What prototype chain does A observe for objects from C, and what does B observe? Are they the same? If they're the same, then we need to specify whether the prototype chain leads to A.Object.prototype or B.object.prototype, which seems pretty arbitrary and hard to do.

The more elegant solution is for them to be different - each observer gets its own view. But then we have to figure out what to do about expandos. If A can set C.location.foopy = 2 and read it back, then presumably B should be able to see it too. But this requires dynamically sharing expando properties between multiple distinct objects - something that Gecko has the machinery to do, but IMO not something we should bake into the web platform when we don't need to.

The simpler thing to do here is just to forbid expandos on cross-origin objects, since it makes the objects immutable and makes this issue unobservable. This brings us back to my proposal in comment 34.
Comment 37 Ian 'Hixie' Hickson 2013-08-13 20:30:26 UTC
I don't think forbidding custom properties helps, since the prototype still has to be distinct per origin in the same way, and you could just fiddle with the prototype to get the same effect. No?
Comment 38 Bobby Holley (:bholley) 2013-08-14 04:09:05 UTC
(In reply to comment #37)
> I don't think forbidding custom properties helps, since the prototype still
> has to be distinct per origin in the same way

Well, it depends on how one interprets "These objects must have the prototype chain appropriate for the script for which the objects are created", which I maintain (from comment 36) is underdefined. There are a number of things it could mean:

(1) For any set of same-origin scripts observing cross-origin objects, the objects have a prototype chain in the scope of whichever script observed them first (if A touches C first, then B sees C.__proto__ == A.__proto__).
(2) Each script sees a prototype chain in the scope of its global (A sees C.__proto__ == A.__proto__, B sees C.__proto__ == B.__proto__).
(3) C gets its own unique proto that is neither in A's scope nor in B's scope. This object may or may not do security checks (these objects are behind opaque security wrappers in Gecko, so you can't do much with them).
(4) The objects get some cop-out proto like null or undefined.

IMO (1) is crappy. (2) is possible in Gecko, but I severely doubt it's possible in other UAs. (3) is what Gecko does. (4) might not be ES-kosher, but that could maybe be fixed.

I wrote a testcase to see what different browsers do here, and the results aren't pretty.

http://people.mozilla.org/~bholley/testcases/xoprotos/main.html

Chrome and Safari throw a security error while trying to read the prototype, and return undefined. IE (9) returns null.

So Gecko is the only UA I tested with actual interesting behavior here. This makes sense, because our architecture (frontend implemented in privileged JS) forces us to think about this stuff, and makes it worthwhile for us to treat this as a general problem, rather than a specific edge case involving Window and Location.

Anyway, the prototype discussion might be fodder for another bug. But this is all to say that there aren't any UAs out there that let you twiddle with the prototypes of cross-origin objects, and that's unlikely to change. So I don't think the point in comment 37 (if I'm reading it right) is valid.
Comment 39 Ian 'Hixie' Hickson 2013-09-23 19:22:09 UTC
I meant 2, I think. I don't quite understand the difference between 2 and 3.

Throwing an exception seems like a pretty reasonable option as well, though presumably that breaks things like "instanceof" pretty badly (not that that isn't already pretty broken in cross-global JS).
Comment 40 Bobby Holley (:bholley) 2013-09-23 21:56:33 UTC
(In reply to Ian 'Hixie' Hickson from comment #39)
> I meant 2, I think. I don't quite understand the difference between 2 and 3.

The difference is whether window.location.prototype == xoifr.contentWindow.location.prototype. In (2) it is, in (3) it isn't.

> Throwing an exception seems like a pretty reasonable option as well, though
> presumably that breaks things like "instanceof" pretty badly (not that that
> isn't already pretty broken in cross-global JS).

Broken for ES objects yes, but not DOM objects. I think we should continue to make it work for DOM objects.

Anyway, this is all a bit of a digression from the bug at hand. Comment 34 and comment 36 still reflect my view on things here.
Comment 41 Ian 'Hixie' Hickson 2013-09-24 22:35:31 UTC
(In reply to Bobby Holley (:bholley) from comment #40)
> (In reply to Ian 'Hixie' Hickson from comment #39)
> > I meant 2, I think. I don't quite understand the difference between 2 and 3.
> 
> The difference is whether window.location.prototype ==
> xoifr.contentWindow.location.prototype. In (2) it is, in (3) it isn't.

Ah, ok. Yeah, I meant 2.


> > Throwing an exception seems like a pretty reasonable option as well, though
> > presumably that breaks things like "instanceof" pretty badly (not that that
> > isn't already pretty broken in cross-global JS).
> 
> Broken for ES objects yes, but not DOM objects. I think we should continue
> to make it work for DOM objects.

How are they not broken for DOM objects? Do origins have cross-global prototypes a là option 3 above? What spec requires that?

Note that Firefox and Chrome/Safari disagree here:
   http://software.hixie.ch/utilities/js/live-dom-viewer/saved/2542


> Anyway, this is all a bit of a digression from the bug at hand. Comment 34
> and comment 36 still reflect my view on things here.

I think we need to resolve all these issues at the same time, so we should consider them in scope for this. Otherwise I'll keep putting it off...
Comment 42 Boris Zbarsky 2013-09-25 00:04:21 UTC
> Do origins have cross-global prototypes a là option 3 above?

No, but WebIDL interface objects have a non-default [[HasInstance]].

> What spec requires that?

http://dev.w3.org/2006/webapi/WebIDL/#es-interface-hasinstance

> Note that Firefox and Chrome/Safari disagree here:

Yep, they haven't implemented this part of WebIDL (or indeed most of WebIDL) yet.
Comment 43 Bobby Holley (:bholley) 2013-09-25 01:00:42 UTC
(In reply to Ian 'Hixie' Hickson from comment #41)
> (In reply to Bobby Holley (:bholley) from comment #40)
> > (In reply to Ian 'Hixie' Hickson from comment #39)
> Ah, ok. Yeah, I meant 2.

Ok. As noted in comment 38, I believe that (2) is only possible in UAs like Gecko with a membrane between globals, since it requires that script from different same-origin globals observe different prototypes for the same object. And it wouldn't be all that pretty in Gecko either.

I think (3) is the best option here. Just as each origin effectively sees a unique version of xowin.location, each origin should also see a unique version of xowin.location.__proto__. So xowin.location.__proto__ is neither winA.location.__proto__ nor winB.location.__proto__, but both winA and winB observe the same value for xowin.location.__proto__.
Comment 44 Ian 'Hixie' Hickson 2013-09-25 02:00:18 UTC
(In reply to Boris Zbarsky from comment #42)
> WebIDL interface objects have a non-default [[HasInstance]].

Oh, wow, I didn't know you could even override that. That's wild.


> Ok. As noted in comment 38, I believe that (2) is only possible in UAs like
> Gecko with a membrane between globals, since it requires that script from
> different same-origin globals observe different prototypes for the same
> object. And it wouldn't be all that pretty in Gecko either.

I don't understand why this would require anything that fancy. You "just" have to return a different object when you're fetching the prototype. As far as I can tell that's exactly what we're doing with window.location in the first place anyway ("the user agent must act as if any changes to that Location object's properties, getters, setters, etc, were not present [in cross-origin accesses]").


> I think (3) is the best option here. Just as each origin effectively sees a
> unique version of xowin.location, each origin should also see a unique
> version of xowin.location.__proto__.

How is that not #2?


> So xowin.location.__proto__ is neither
> winA.location.__proto__ nor winB.location.__proto__, but both winA and winB
> observe the same value for xowin.location.__proto__.

Doesn't that say exactly the opposite of your previous sentence?

How can anyone tell if winA and winB observe the same value, if they're cross-origin? I'm not sure I understand what that means.
Comment 45 Boris Zbarsky 2013-09-25 02:06:12 UTC
> Oh, wow, I didn't know you could even override that.

Fwiw, ES6 drafts have an explicit @@hasInstance that can be defined as needed to do whatever you want (though there's no way to do it from user script yet, afaict).
Comment 46 Bobby Holley (:bholley) 2013-09-25 02:18:25 UTC
(In reply to Ian 'Hixie' Hickson from comment #44)

I think the key misunderstanding underlying all of this relates to the origins of winA and winB. I'm talking about the case when winA and winB are same-origin, but are jointly observing a cross-origin winC. Per spec and per the web, winA and winB jointly observe the same identity for winC.location.

The question here is what prototype chain they observe. (2) requires that they do _not_ observe the same prototype chain for winC.location, which I think is problematic.

Does that clear things up?
Comment 47 Bobby Holley (:bholley) 2013-09-25 13:44:40 UTC
(In reply to Bobby Holley (:bholley) from comment #46)
> (In reply to Ian 'Hixie' Hickson from comment #44)
> 
> I think the key misunderstanding underlying all of this relates to the
> origins of winA and winB.

(and in particular, note that the origins I'm referring to here are the origins of the document/global, not the origins of the browsing context).
Comment 48 Ian 'Hixie' Hickson 2013-09-25 18:38:05 UTC
Sure, if they're same-origin scripts, then they should see the same object and it should have the same prototype chain. Doing anything else would be massively confusing. I apologise if anything I said gave you the impression that I thought that was not the case.

(Browsing contexts don't have origins.)
Comment 49 Bobby Holley (:bholley) 2013-09-25 18:55:05 UTC
(In reply to Ian 'Hixie' Hickson from comment #48)
> Sure, if they're same-origin scripts, then they should see the same object
> and it should have the same prototype chain. Doing anything else would be
> massively confusing. I apologise if anything I said gave you the impression
> that I thought that was not the case.

Right. Does my proposal for (3) in comment 38 make more sense in that light?

> (Browsing contexts don't have origins.)

Yeah, it was shorthand for "the origin of the document of the active window in the browsing context".
Comment 50 Ian 'Hixie' Hickson 2013-10-10 19:58:28 UTC
IRC discussion suggests that comment 32's first bit is ok, but the #-prefixed paragraphs should be changed to something like (proposal):

---8<---
When the entry script's effective script origin is different than a Location object's associated Document's effective script origin, the user agent must act as if the Location object is an alien object.

An alien object must act as if any changes to that object's properties, getters, setters, etc, were not present, as if all the properties of that object had their [[Enumerable]] attribute set to false, as if the object's prototype was null, and as if any objects returned by members of that object (including, for instance, function objects) were themselves alien objects.
---8<---
Comment 51 Bobby Holley (:bholley) 2013-10-10 20:10:58 UTC
(In reply to Ian 'Hixie' Hickson from comment #50)
> When the entry script's

Don't we want |incumbent script| here?

> An alien object

The key part that I'm pushing for with alien objects is that callers should not be able to define or modify properties.

> must act as if any changes to that object's properties,
> getters, setters, etc, were not present, as if all the properties of that
> object had their [[Enumerable]] attribute set to false, as if the object's
> prototype was null, and as if any objects returned by members of that object
> (including, for instance, function objects) were themselves alien objects.

I was mistaken about the function objects and companyin Gecko - they're currently just regular functions whose prototype is the caller's Function.prototype. But this understandably introduces the same problem that caused us to null out the prototype of alien objects. So I'm happy to try to modify our implementation to null out the prototypes and Freeze the objects themselves to make them behave like alien objects, if we're willing to define alien objects that way.
Comment 52 Ian 'Hixie' Hickson 2013-10-10 20:14:43 UTC
I'm fine with changing to incumbent script here, I think. (The only possible difference is with document.domain in non-Gecko-like environments, I think.)

Reusing the freeze logic would make sense if the freeze logic is staying and does what we want in non-strict and strict JS modes.
Comment 53 Boris Zbarsky 2013-10-10 20:16:56 UTC
> The key part that I'm pushing for with alien objects is that callers should not
> be able to define or modify properties.

Isn't that the "over-restrictive" bit I initially filed this bug on?
Comment 54 Boris Zbarsky 2013-10-10 21:48:27 UTC
> Isn't that the "over-restrictive" bit I initially filed this bug on?

No, I just can't read.  The important part in comment 50 is "associated Document's effective script origin".
Comment 55 Ian 'Hixie' Hickson 2013-10-10 21:52:24 UTC
Adam, if you could comment on this that'd be great. In particular, if you don't like the last proposal above, it would be good if you could describe how you would like Location and Window to work cross-origin.
Comment 56 Ian 'Hixie' Hickson 2013-10-10 23:08:34 UTC
So the proposal from bholley and I, as I understand it, is:

For Location:

- The getters for everything on URLUtils imported by Location must throw
  SecurityError if the incumbent script has an effective script origin that's
  not the same as the Location object's associated Document's browsing
  context's active document's effective script origin.

- The setters for everything on URLUtils imported by Location other than 'href' 
  must throw SecurityError if the incumbent script has an effective script 
  origin that's not the same as the Location object's associated Document's
  browsing context's active document's effective script origin.

- All the methods on Location other than replace() must throw SecurityError
  when called if the incumbent script has an effective script origin that's
  not the same as the Location object's associated Document's browsing
  context's active document's effective script origin. (bug 22346)

- The href setter and the replace() method must throw SecurityError when called
  if the incumbent script's script browsing context is not allowed to navigate
  the Location object's associated Document's browsing context.

- When the incumbent script's effective script origin is different than a
  Location object's associated Document's effective script origin, the user
  agent must act as if the Location object is an Alien Object.

For Window:

- The getters for everything on Window except those Exempted Properties listed 
  below must throw SecurityError if the incumbent script has an effective script 
  origin that's not the same as the Window object's Document's effective script
  origin.

- The setters for everything on Window must throw SecurityError if the
  incumbent script has an effective script origin that's not the same as the
  Window object's Document's effective script origin.

- All the methods on Window except those in the Exempted Properties list below 
  must throw SecurityError when called if the incumbent script has an effective
  script origin that's not the same as the Window object's Document's effective
  script origin. (bug 22346)

- When the incumbent script's effective script origin is different than a
  Window object's Document's effective script origin, the user agent must act
  as if the Window object is an Alien Object.

- The Exempted Properties are:
   The location attribute
   The postMessage() method
   The window attribute
   The frames attribute
   The self attribute
   The top attribute
   The parent attribute
   The opener attribute
   The closed attribute
   The close() method
   The blur() method
   The focus() method
   Any supported property names that refer to other browsing contexts and that
     do not have a name that clashes with a property that is defined on the
     Window object itself (e.g. "history")

For both:

- An Alien Object must:
   * promote all the members found on its prototypes, including supported
     property indices and supported property names if any, to "own" properties
     on the object itself.
   * act as if any changes to that object's properties, getters, setters, etc,
     were not present (i.e. can't see custom properties and changes from the
     object's "home" origin).
   * act as if all the properties of that object had their [[Enumerable]]
     attribute set to false.
   * have their prototype set to null.
   * throw a SecurityError for any attempt to set or configure a property on
     the object.
   * act as if all objects returned by members of that object (including, for
     instance, function objects) were frozen.

(We're not sure if the last bullet point in particular is enough. Does that prevent you from messing around with e.g. the other origin's Function prototype or whatnot? Also, we're not sure if Window and Location should throw SecurityError or instead try to more precisely simulate being frozen. Naturally we can't actually freeze them, since they have mutable state and stuff.)


This would replace all the security stuff for cross-origin accesses currently in the spec, and would be partly implemented using bug 22346's hooks. We would especially like to know if this is something other vendors are interested in implementing. Right now the spec has all kinds of security vulnerabilities and generally doesn't match reality (or even make sense, in some places).
Comment 57 Adam Barth 2013-10-11 02:02:16 UTC
> We would especially like to know if this is something other vendors are interested in implementing.

I understand why folks who work on Gecko are excited about this approach.  It matches some technology that exists in Gecko for other purposes.  However, implementing this in Blink (and WebKit for that matter) would require developing the same wrapper technology.  However, that technology is extremely subtle, and Mozilla has spent years fighting through all the security vulnerabilities in their implementation.  We're not interested in implementing wrapper technology in Blink.  In fact, we've gone through great lengths to avoid having to do that.  For example, we moved the entire Web Inspector to a separate process to avoid having to create wrapper technology.  (JavaScriptCore in WebKit briefly had wrapper technology, but it was full of security bugs.)

The approach used in Blink (and WebKit) today is much simpler and doesn't require any additional technology apart from what's already required to implement the web platform.  Essentially the way it works is that when a script views a cross-origin object (say the Location object associated with a cross-origin Document), the script doesn't receive a reference to the same JS object that same-origin viewers receive.  Instead, the viewer receives a brand new JS interface object with a reduced set of properties and behavior appropriate to the cross-origin scenario.  There is no connection between this JS interface object and the JS interface object that same-origin viewers receive.  Furthermore, this new JS interface object's prototype chain is connected up to the *viewer* prototype objects.  For that reason, there's no subtly about promoting properties from the prototype to the object itself and there's no need to freeze or protect anything in the prototype chain.

Personally, I don't have any interest in changing Blink's implementation in this area.  It's simple, and it works well.  I can understand why Mozilla would want to make use of their extensively debugged wrapper technology in Gecko, but I don't see any real benefit from implementing that technology in Blink.
Comment 58 Adam Barth 2013-10-11 02:03:43 UTC
> We're not sure if the last bullet point in particular is enough.

I'm pretty sure that's insufficient.  The document might store secret values in its prototype chain.  Freezing them just makes them read-only.  It doesn't stop a malicious viewer from learning those secret values.
Comment 59 Ian 'Hixie' Hickson 2013-10-11 02:20:58 UTC
> Essentially the way it works is that when a
> script views a cross-origin object (say the Location object associated with
> a cross-origin Document), the script doesn't receive a reference to the same
> JS object that same-origin viewers receive. Instead, the viewer receives a
> brand new JS interface object with a reduced set of properties and behavior
> appropriate to the cross-origin scenario.  There is no connection between
> this JS interface object and the JS interface object that same-origin
> viewers receive.

The model described above is intended to actually be implementable in an indistinguishable fashion using either the WebKit/Blink model or the Gecko model an the backend. What you describe here is, as far as I can tell, the same thing, basically.


> Furthermore, this new JS interface object's prototype
> chain is connected up to the *viewer* prototype objects.

In the Blink model, this isn't observable, because trying to read the prototype throws an exception (right?). The only difference between this and the model above is that the prototype instead returns null.

The reason the model above also promotes the members to the object (essentially flattening the prototype chain into the object) is to make the object appear sane from a JavaScript perspective. However, there's actually no way that I can see to tell if this is what is going on or not, since all the methods that can be used to introspect the object will throw in the cross-origin case. (Right?)

So maybe the solution here is to just not mention the flattening. It's possible that there's not actually any observable difference in behaviour one way or the other.

(Is returning null instead of throwing for the prototype a problem? I would imagine that for Gecko it's much the same one way or the other, so if you prefer to throw I guess we can do that instead.)


> object itself and there's no need to freeze or protect anything in the
> prototype chain.

The idea of the freezing is that it enables implementations to avoid the cost of having multiple objects (one per origin) for all the objects involved here. This seems like a decent win in terms of flexibility for different implementation strategies. Would it be an issue to implement this in WebKit/Blink?


> Personally, I don't have any interest in changing Blink's implementation in
> this area.  It's simple, and it works well.

The idea here is to find a compromise that everyone can implement, that is still simple and works well, and that gets us interoperability, without requiring anyone to change their architecture, while simultaneously allowing other optimisations in the future (e.g. using a way to display "views" of the objects on the fly, so that everyone can have the same objects, yet still not have any security problems). I would hope you agree that's a good goal.
Comment 60 Adam Barth 2013-10-11 02:39:23 UTC
(In reply to Ian 'Hixie' Hickson from comment #59)
> The model described above is intended to actually be implementable in an
> indistinguishable fashion using either the WebKit/Blink model or the Gecko
> model an the backend. What you describe here is, as far as I can tell, the
> same thing, basically.

That's not correct.  For example, suppose you have three frames, A, B, and C, with A and B in the same origin and C in a different origin.  Script running in A and B grab C's Location object and then compare the JS interface object they receive with ===.  In Blink's implementation, they'll get back |false| because they each receive separate interface objects that are wired up to their respective prototypes.

> > Furthermore, this new JS interface object's prototype
> > chain is connected up to the *viewer* prototype objects.
> 
> In the Blink model, this isn't observable, because trying to read the
> prototype throws an exception (right?). The only difference between this and
> the model above is that the prototype instead returns null.

alert(frames[0].location.replace.apply === Function.prototype.apply)

In Blink, that will alert |true| because the cross-origin Location object's prototype is wired up to the viewer's own Function.prototype.  That means that function objects you grab off the prototype are identical to the function objects you can get from inspecting your own constellation of prototype objects.  Importantly, that also means they're safe to return to the script because the script already has access to them.

> The reason the model above also promotes the members to the object
> (essentially flattening the prototype chain into the object) is to make the
> object appear sane from a JavaScript perspective.

I understand.  However, the objects appear sane in the model I described above.  They are in fact more sane than in the promotion case because all the properties are stored in the usual places.

> However, there's actually
> no way that I can see to tell if this is what is going on or not, since all
> the methods that can be used to introspect the object will throw in the
> cross-origin case. (Right?)

There are lots of ways to tell where the object's prototypes come from.  Here's one example:

Object.prototype.foo = "bar";
alert(frames[0].location.replace.foo);

This alerts |bar| in Blink.
 
> (Is returning null instead of throwing for the prototype a problem? I would
> imagine that for Gecko it's much the same one way or the other, so if you
> prefer to throw I guess we can do that instead.)

Throwing is much better.  Returning |null| is problematic because the objects don't actually have null prototypes.  They have non-null prototypes, and that's observable.

Experimenting a bit, it looks like Blink only throws when you access __proto__ on the original interface (e.g., Location), not on any objects that you can obtain transitively from that interface (e.g., replace).  For those objects, you can just get the prototype directly.

> The idea of the freezing is that it enables implementations to avoid the
> cost of having multiple objects (one per origin)

It's more than just one per origin.  It's one per cross-origin script.

> for all the objects
> involved here. This seems like a decent win in terms of flexibility for
> different implementation strategies. Would it be an issue to implement this
> in WebKit/Blink?

The problem with freezing the objects is that the object Blink returns are the same objects you'd get by other means.  There's no way to "act as if" they were frozen without magic.  They're either frozen or they're not.  We can't actually freeze them because that would disrupt the script when it accessed them normally.

> > Personally, I don't have any interest in changing Blink's implementation in
> > this area.  It's simple, and it works well.
> 
> The idea here is to find a compromise that everyone can implement, that is
> still simple and works well, and that gets us interoperability, without
> requiring anyone to change their architecture, while simultaneously allowing
> other optimisations in the future (e.g. using a way to display "views" of
> the objects on the fly, so that everyone can have the same objects, yet
> still not have any security problems). I would hope you agree that's a good
> goal.

I agree that interoperability is a good goal, but I don't see how to achieve it in this case because it's too easy for script to figure out which implementation approach is being used.  They just behave differently.  Trying to hide all the implementation details is going to end up being more complicated than the entire security mechanism.
Comment 61 Ian 'Hixie' Hickson 2013-10-11 02:48:15 UTC
(In reply to Adam Barth from comment #60)
> 
> That's not correct.  For example, suppose you have three frames, A, B, and
> C, with A and B in the same origin and C in a different origin.  Script
> running in A and B grab C's Location object and then compare the JS
> interface object they receive with ===.  In Blink's implementation, they'll
> get back |false| because they each receive separate interface objects that
> are wired up to their respective prototypes.

Isn't that considered a bug? That seems really bad. How do you decide what object to return, do you base it on the entry script or the incumbent script?


> alert(frames[0].location.replace.apply === Function.prototype.apply)
> 
> In Blink, that will alert |true| because the cross-origin Location object's
> prototype is wired up to the viewer's own Function.prototype.  That means
> that function objects you grab off the prototype are identical to the
> function objects you can get from inspecting your own constellation of
> prototype objects.  Importantly, that also means they're safe to return to
> the script because the script already has access to them.

That's kind of weird magic. Why would we want this? Is it also true of prototypes of things like HTMLAnchorElement and Object and so on?


> There are lots of ways to tell where the object's prototypes come from. 
> Here's one example:
> 
> Object.prototype.foo = "bar";
> alert(frames[0].location.replace.foo);
> 
> This alerts |bar| in Blink.

That seems like a really bad bug. That means that what code will execute depends in weird ways on how it was called, even in entirely same-origin situations, just because of what unrelated code has done to the prototypes in a different browsing context.


> Experimenting a bit, it looks like Blink only throws when you access
> __proto__ on the original interface (e.g., Location), not on any objects
> that you can obtain transitively from that interface (e.g., replace).  For
> those objects, you can just get the prototype directly.

Why do you throw at all, given the rest of how Blink works?


> > The idea of the freezing is that it enables implementations to avoid the
> > cost of having multiple objects (one per origin)
> 
> It's more than just one per origin.  It's one per cross-origin script.

All the more reason to avoid it...

(How do you avoid this being really expensive? Do you lazily create them?)


> I agree that interoperability is a good goal, but I don't see how to achieve
> it in this case because it's too easy for script to figure out which
> implementation approach is being used.  They just behave differently. 

I agree that the current Blink behaviour is different than the behaviour described above.


Do you have any suggestions on how to get interoperability here?
Comment 62 Adam Barth 2013-10-11 03:11:48 UTC
(In reply to Ian 'Hixie' Hickson from comment #61)
> (In reply to Adam Barth from comment #60)
> > 
> > That's not correct.  For example, suppose you have three frames, A, B, and
> > C, with A and B in the same origin and C in a different origin.  Script
> > running in A and B grab C's Location object and then compare the JS
> > interface object they receive with ===.  In Blink's implementation, they'll
> > get back |false| because they each receive separate interface objects that
> > are wired up to their respective prototypes.
> 
> Isn't that considered a bug?

I don't think it's important to preserve that behavior as such, but it's what makes the security so simple.

> That seems really bad.

Why?  What problem does it cause?  The benefit is that there's no hysteresis.

Looking into the implementation more, it looks like we might actually return the same Location interface object to all viewers, but we'll definitely return separate copies of objects reachable from the Location object.

> How do you decide what
> object to return, do you base it on the entry script or the incumbent script?

To be 100% sure, I'd have to review what these terms mean exactly now, but I suspect the incumbent script.

> > alert(frames[0].location.replace.apply === Function.prototype.apply)
> > 
> > In Blink, that will alert |true| because the cross-origin Location object's
> > prototype is wired up to the viewer's own Function.prototype.  That means
> > that function objects you grab off the prototype are identical to the
> > function objects you can get from inspecting your own constellation of
> > prototype objects.  Importantly, that also means they're safe to return to
> > the script because the script already has access to them.
> 
> That's kind of weird magic. Why would we want this? Is it also true of
> prototypes of things like HTMLAnchorElement and Object and so on?

I don't understand what part of that you think is magic.  It works just the same what it would if you accessed window.location instead of frames[0].location.  The only difference is that interacting with the latter Location instance manipulates the child frame whereas interacting with the former Location instance manipulates the parent frame.  Imagine the following JS code:

get location() { return new %GetIncumbentWindow().Location(this); }

Where %GetIncumbentWindow is a built-in function that returns the incumbent Window.

It's not possible to get an HTMLAnchorElement interface to a cross-origin object, so there's no way to know how that would work.

> > There are lots of ways to tell where the object's prototypes come from. 
> > Here's one example:
> > 
> > Object.prototype.foo = "bar";
> > alert(frames[0].location.replace.foo);
> > 
> > This alerts |bar| in Blink.
> 
> That seems like a really bad bug. That means that what code will execute
> depends in weird ways on how it was called, even in entirely same-origin
> situations, just because of what unrelated code has done to the prototypes
> in a different browsing context.

You must be misunderstanding what I've described because that's not the case.  None of this happens in same-origin scenarios.  In same-origin scenarios, all viewers get the identical instance of the Location interface.  It's only in cross-origin scenarios that each (cross-origin) viewer gets a fresh instance of the Location interface.

> > Experimenting a bit, it looks like Blink only throws when you access
> > __proto__ on the original interface (e.g., Location), not on any objects
> > that you can obtain transitively from that interface (e.g., replace).  For
> > those objects, you can just get the prototype directly.
> 
> Why do you throw at all, given the rest of how Blink works?

We wouldn't have to, but we wanted to work similarly to other browsers.  Compatibility doesn't require that we let scripts grab __proto__, so we might as well throw.

> > > The idea of the freezing is that it enables implementations to avoid the
> > > cost of having multiple objects (one per origin)
> > 
> > It's more than just one per origin.  It's one per cross-origin script.
> 
> All the more reason to avoid it...
> 
> (How do you avoid this being really expensive? Do you lazily create them?)

Yes, they're created on-demand, similarly to the JavaScript mentioned above.  I believe we cache them, but I wasn't able to find that code immediately.

> Do you have any suggestions on how to get interoperability here?

I'm happy to consider solutions that don't involve developing wrapper technology.  If the folks who work on Gecko are willing to use a mechanism other that wrappers to solve this problem, then we can probably find something interoperable.  If you all insist on a wrapper-based model, then we're not going to get interoperability anytime soon.
Comment 63 Bobby Holley (:bholley) 2013-10-11 13:53:20 UTC
(In reply to Adam Barth from comment #62)
> I'm happy to consider solutions that don't involve developing wrapper
> technology.

Adam - Hixie and I are both well-aware that Blink doesn't plan to implement wrappers of any sort. Gecko has no plans to switch to Blink's architecture either. This is why we spent a number of hours together trying to come up with a security model for the spec that is easy to implement in either setting.

Most of the proposal in comment 56 does not describe what Gecko currently does. It was designed to be Blink-friendly, especially with respect to the null prototypes, which was what my sleuthing led me to believe that Blink was doing.

> If the folks who work on Gecko are willing to use a mechanism
> other that wrappers to solve this problem, then we can probably find
> something interoperable.

Anything we implement is going to use our technology, just like anything you implement is going to use your technology. The goal here is to specify behavior that is straightforward for both of us to implement.

> If you all insist on a wrapper-based model, then
> we're not going to get interoperability anytime soon.

We're not insisting on anything, nor are we asking you to implement wrappers. But we are asking you to consider the design challenges from both ends, as well as overall spec sanity.

Our wrapper setup in Gecko is quite flexible. Our only major design constraint (which itself is negotiable) is that we don't want to allow arbitrary properties to be defined on cross-origin objects. From a high level, I don't imagine that Blink would find this constraint either unreasonable or difficult to implement.

So. Let's talk about how you want this stuff to work.

I'm a bit confused about comment 62. Are you saying that a given Location object in Blink has (a) one JS reflection per global, (b) one JS reflection per origin, or (c) one JS reflection for the whole system? What does the prototype chain look like?

It sounds like the functions, getters, and setters you pull off cross-origin objects map to the Function.prototype of the script which is observing them. This is what Gecko does as well, and we only changed it in the proposal because I think it would be difficult for Blink and confusing to spec. If you want to keep doing that, I'm happy to as well. But note that it introduces some weirdness: if |A| and |B| are same-origin, and both of them pull location.replace off of some cross-origin |C|'s window.location, they will get observably different results, despite the fact that presumably the window.location will compare equal.
Comment 64 Adam Barth 2013-10-11 21:49:08 UTC
(In reply to Bobby Holley (:bholley) from comment #63)
> (In reply to Adam Barth from comment #62)
> > I'm happy to consider solutions that don't involve developing wrapper
> > technology.
> 
> Adam - Hixie and I are both well-aware that Blink doesn't plan to implement
> wrappers of any sort. Gecko has no plans to switch to Blink's architecture
> either. This is why we spent a number of hours together trying to come up
> with a security model for the spec that is easy to implement in either
> setting.

What you proposed is not possible to implement securely without wrappers.

> Most of the proposal in comment 56 does not describe what Gecko currently
> does. It was designed to be Blink-friendly, especially with respect to the
> null prototypes, which was what my sleuthing led me to believe that Blink
> was doing.

As far as I can tell, the concept of an Alien Object implies the existence of wrappers.  The way you can tell is that is is specified in terms of a JavaScript object acting as if it had some quality that it doesn't actually have (e.g., being frozen).  The only way I know how to do that is to apply an interception layer between the code manipulating the object and the actual JS object.  That's what I meant by "magic" above.  As far as I can tell, that sort of interposition cannot be implemented without wrappers because that's what the wrapper does!

> > If the folks who work on Gecko are willing to use a mechanism
> > other that wrappers to solve this problem, then we can probably find
> > something interoperable.
> 
> Anything we implement is going to use our technology, just like anything you
> implement is going to use your technology.

Why?  Wrappers are not required for security here.  There's also not required for compatibility.  Is there some reason you're opposed to a non-wrapper design?

> The goal here is to specify
> behavior that is straightforward for both of us to implement.

The two approach are different in observable ways.  In particular, the identity of various objects differ between the two approaches.  Without an interposition layer (read: wrapper) to make the objects "act as if" something else were true (read: magic), those observables are going to stay different.

> > If you all insist on a wrapper-based model, then
> > we're not going to get interoperability anytime soon.
> 
> We're not insisting on anything, nor are we asking you to implement
> wrappers.

I don't see any way of implementing your proposal without wrappers.  It requires magic.  In practice, you as insisting on wrappers.

> But we are asking you to consider the design challenges from both
> ends, as well as overall spec sanity.

I'm happy to consider designs that don't require Blink to implement wrappers.  I don't know of any designs that are secure other than ones that mint new JS interface objects for each cross-origin viewer.

There's a basic requirement that these objects act differently for different viewers.  You can either accomplish that by using wrappers to make the "same" JS object act differently in situations or you can actually use different JS objects that have different behavior.  I don't know how to have the same JS object have different behavior for different viewers without magic.

> Our wrapper setup in Gecko is quite flexible. Our only major design
> constraint (which itself is negotiable) is that we don't want to allow
> arbitrary properties to be defined on cross-origin objects. From a high
> level, I don't imagine that Blink would find this constraint either
> unreasonable or difficult to implement.

It depends what you mean by a cross-origin object.  In the example earlier in the thread, would you consider frames[0].location.replace to be a cross-origin object?  In Blink, it's just a normal JS object, exactly the same kind you'd get from window.location.replace.  It's even the case that frames[0].location.replace.__proto__ === window.location.replace.__proto__.

The only thing that's different about frames[0].location.replace and window.location.replace is that the former operates on frames[0] whereas the latter operates on window.  In particular, frames[0].location.replace is mutable just like window.location.replace.

> So. Let's talk about how you want this stuff to work.
> 
> I'm a bit confused about comment 62. Are you saying that a given Location
> object in Blink has (a) one JS reflection per global, (b) one JS reflection
> per origin, or (c) one JS reflection for the whole system? What does the
> prototype chain look like?

None of those.  The way it works is just the way the following JavaScript would work:

get location() {
  var activeWindow = %GetIncumbentWindow();
  if (%IsSameOrigin(this, activeWindow)) {
    // Return the same full-featured Location instance
    // to all same-origin viewers.
    return this._location;
  return new activeWindow.Location(this);
}

Where %GetIncumbentWindow is a magic function that returns a reference to the Window object for the incumbent script and %IsSameOrigin is a magic function that returns true iff the two windows belong to the same origin.

Notice that all same-origin viewers get exactly the same instance of the Location object, and that object has a prototype chain that connects up with it's window's prototypes.

Each cross-origin viewer gets a fresh instance of the Location interface with a prototype chain that connects up with the viewer's prototype objects.  Notice that we pass |this| to the activeWindow.Location constructor to indicate that the newly constructed object should operate on |this| rather than on |activeWindow|.

That's the whole security mechanism in a nutshell.  It's super simple.  The only other facet is that the Location throws some security exceptions when the incumbent script tries to perform operations (like get the href property) that aren't allowed.  The JS interface object isn't really involved in those security checks.  They're done by the underlying implementation object.  The only one that's done by the JS engine is throwing an exception for getting __proto__, but that's not needed for security.  It's just to give us more flexibility in the future.

> It sounds like the functions, getters, and setters you pull off cross-origin
> objects map to the Function.prototype of the script which is observing them.

Correct.

> This is what Gecko does as well, and we only changed it in the proposal
> because I think it would be difficult for Blink and confusing to spec.

It's easy to spec.  In fact, there isn't really any spec text to write.  You just say that when you get the |location| property across origins, you get an instance of the incumbent's Location interface rather than an instance of the callee's Location interface.  Everything else follows from normal JavaScript rules.

> If
> you want to keep doing that, I'm happy to as well. But note that it
> introduces some weirdness: if |A| and |B| are same-origin, and both of them
> pull location.replace off of some cross-origin |C|'s window.location, they
> will get observably different results,

That's fine.  They each get objects that belong to their own global object.  We haven't had any compat problems from this behavior.

> despite the fact that presumably the window.location will compare equal.

There's no reason for the objects to compare equal.  We can make the comparison throw if you'd like, but they're not the same object.  Cross-origin viewers get minted fresh objects.  That's what makes it secure.
Comment 65 Ian 'Hixie' Hickson 2013-10-11 22:21:49 UTC
(In reply to Adam Barth from comment #60)
> 
> That's not correct.  For example, suppose you have three frames, A, B, and
> C, with A and B in the same origin and C in a different origin.  Script
> running in A and B grab C's Location object and then compare the JS
> interface object they receive with ===.  In Blink's implementation, they'll
> get back |false| because they each receive separate interface objects that
> are wired up to their respective prototypes.

I don't understand how two scripts operating in the same origin, operating on the same object, getting different results, is not a bug.


> frames[0].location.replace.__proto__ === window.location.replace.__proto__.

I don't know how to reconcile that with the fact that this is true (in a same-origin iframe):

   parent.document.appendChild.__proto__ !== document.appendChild.__proto__

Why do these two methods work differently?

Why is it not a bug that the following first alerts B? Why does it not alert B in the second call as well?

   assert(parent.location.__proto__ !== self.location.__proto__);
   self.location.replace.__proto__.toString = function () { return 'A' };
   parent.location.replace.__proto__.toString = function () { return 'B' };
   alert(location.replace); // "B"
   alert(parent.document.body.appendChild); // "function appendChild() ..."

The two Location objects here have different prototypes; it seems very strange to me that their functions can have the same prototype. This is "magic".


The model in comment 56 is not intended to require wrappers. I don't understand why it would need wrappers. It just means making Window and Location check the origin of the caller when their properties are accessed, and acting differently. This already happens in Blink -- if you try to access otherWindow.localStorage cross-origin, you get an exception, but if you're same-origin, it works. Indeed, it seems to be exactly the same kind of logic that would enable making Location's method's prototypes appear to be local Function prototypes instead of being from the other browsing context. I don't understand what it is in comment 56 that's different from that.
Comment 66 Adam Barth 2013-10-11 23:28:10 UTC
(In reply to Ian 'Hixie' Hickson from comment #65)
> I don't understand how two scripts operating in the same origin, operating
> on the same object, getting different results, is not a bug.

What makes you say that it's the same object?  They're just accessing the same getter, which is returning different objects at different times.  It's easy to write in idiomatic JavaScript, and we do it all the time in the platform.  For example, document.body.firstChild will return different objects at different times.  That certainly isn't a bug.

> > frames[0].location.replace.__proto__ === window.location.replace.__proto__.
> 
> I don't know how to reconcile that with the fact that this is true (in a
> same-origin iframe):
> 
>    parent.document.appendChild.__proto__ !== document.appendChild.__proto__
> 
> Why do these two methods work differently?

Is |parent| same-origin with |window|?  For the remainder of this response, I'm going to assume they are not same-origin.  (If they were same-origin, then the location property works the same way the locationbar property works, which is to say extremely normally.)

Because parent.document instanceof parent.Document and window.document instance window.Document.  In the cross-origin location case, parent.location instanceof window.Location and window.location instanceof window.Location.

> Why is it not a bug that the following first alerts B? Why does it not alert
> B in the second call as well?
> 
>    assert(parent.location.__proto__ !== self.location.__proto__);

This throws an exception when trying to [[Get]] parent.location.__proto__.  If it didn't, the assert would fail.  The two objects have the same prototype.

>    self.location.replace.__proto__.toString = function () { return 'A' };
>    parent.location.replace.__proto__.toString = function () { return 'B' };
>    alert(location.replace); // "B"
>    alert(parent.document.body.appendChild); // "function appendChild() ..."
> 
> The two Location objects here have different prototypes;

Why?  In the approach I'm describing, they have the same prototype (again, assuming we're talking about the cross-origin case).

> it seems very
> strange to me that their functions can have the same prototype. This is
> "magic".

It's not magic at all.  You can implement it directly in normal JavaScript using the snippet I posted earlier.

> The model in comment 56 is not intended to require wrappers.

I claim it does.  Do you have an implementation that doesn't use wrappers?

> I don't understand why it would need wrappers.

Because it uses the phrase "as if" to make the internal properties of the object appear to have different values than they do in reality.  That requires some sort of interposition to make these behavioral "fixups."  The only way I know how to do that is to use wrappers.  That's in fact what the wrappers do!

> It just means making Window and
> Location check the origin of the caller when their properties are accessed,
> and acting differently.

That's not what the text you wrote says.

> This already happens in Blink -- if you try to
> access otherWindow.localStorage cross-origin, you get an exception, but if
> you're same-origin, it works.

Slow down.  You just switched from talking about Location to talking about Window.  The Window object is deeply magic and there's no getting away from that.  This bug is about Location.  Let's stick to that topic and not get distracted by the magic in Window.

I agree that Window cannot be implemented without magic.  I claim Location can be implemented securely without magic.  I don't want to introduce any more magic to the system than absolutely required.

> Indeed, it seems to be exactly the same kind
> of logic that would enable making Location's method's prototypes appear to
> be local Function prototypes instead of being from the other browsing
> context. I don't understand what it is in comment 56 that's different from
> that.

The simplest example is the following:

"act as if all objects returned by members of that object (including, for instance, function objects) were frozen."

parent.location.replace doesn't act as if it is frozen.  It's not frozen, and it doesn't act that way either.

I'll copy-and-paste this snippet again, because this is really the whole story:

get location() {
  var activeWindow = %GetIncumbentWindow();
  if (%IsSameOrigin(this, activeWindow)) {
    // Return the same full-featured Location instance
    // to all same-origin viewers.
    return this._location;
  }
  return new activeWindow.Location(this);
}

There's no magic "act as if."  There's just normal JavaScript that plays by the normal JavaScript rules.
Comment 67 David Bruant 2013-10-12 01:04:25 UTC
(In reply to Adam Barth from comment #66)
> get location() {
>   var activeWindow = %GetIncumbentWindow();
>   if (%IsSameOrigin(this, activeWindow)) {
>     // Return the same full-featured Location instance
>     // to all same-origin viewers.
>     return this._location;
>   }
>   return new activeWindow.Location(this);
> }
The last line is where the prototype objects (Function.prototype, Object.prototype) of the current frame (activeWindow) are grabbed to be used in the returned Location instance prototype chain, right?

This also means that as a cross-origin script, I get a different object everytime I ask for the location object? (since no record is kept of which activeWindow is being passed which Location instance)


> > > frames[0].location.replace.__proto__ === window.location.replace.__proto__.
> > 
> > I don't know how to reconcile that with the fact that this is true (in a
> > same-origin iframe):
> > 
> >    parent.document.appendChild.__proto__ !== document.appendChild.__proto__
> > 
> > Why do these two methods work differently?
> 
> Is |parent| same-origin with |window|?  For the remainder of this response,
> I'm going to assume they are not same-origin.  (If they were same-origin,
> then the location property works the same way the locationbar property
> works, which is to say extremely normally.)
> 
> Because parent.document instanceof parent.Document and window.document
> instance window.Document.  In the cross-origin location case,
> parent.location instanceof window.Location and window.location instanceof
> window.Location.
The last sentence is scary. It means that while cross-origin parent and window can communicate directly via window.Location.prototype. Is it a mistake? Am I missing something?


(In reply to Bobby Holley (:bholley) from comment #34)
> The de-facto web platform currently never allows expando properties to be
> set on any object that is not same-origin with the incumbent script. The
> spec requires a "vanilla object" to be created for cross-origin access to
> Window, but that object is immutable, which means that implementations could
> potentially share the same instance between _all_ non-same-origin consumers,
> or have read-only view to the native properties.
In the "same instance" case, what does the prototype chain looks like? Specifically, are Function.prototype and Object.prototype objects created at this occasion? Frozen versions of these objects?
Comment 68 Bobby Holley (:bholley) 2013-10-12 12:22:16 UTC
(In reply to Adam Barth from comment #64)
> What you proposed is not possible to implement securely without wrappers.

> As far as I can tell, the concept of an Alien Object implies the existence
> of wrappers.

As noted, that was not the intention. We came up with the notion of "Alien Object" specifically as something that would be implementable with or without wrappers.

> The way you can tell is that is is specified in terms of a
> JavaScript object acting as if it had some quality that it doesn't actually
> have (e.g., being frozen).

Who says the object doesn't have the property? The "act as if" bit was just a wink and a nod to the fact that Gecko would use wrapper magic to accomplish this. In Blink, we presumed that you'd just freeze the object.

> > Anything we implement is going to use our technology, just like anything you
> > implement is going to use your technology.
> 
> Why?  Wrappers are not required for security here.  There's also not
> required for compatibility.  Is there some reason you're opposed to a
> non-wrapper design?

Our architecture is designed around wrappers. In particular, we have to use them here, because we can only have one JS reflector per native DOM object. This is true in Servo as well.
 
> > The goal here is to specify
> > behavior that is straightforward for both of us to implement.
> 
> The two approach are different in observable ways.

Only if we fail to specify behavior that we can both implement.

> In particular, the
> identity of various objects differ between the two approaches.

Which objects? Location? Function? We're quite flexible about what we do here in Gecko-land, so I'm reasonably confident we can come up with behavior that works for everyone.

> > We're not insisting on anything, nor are we asking you to implement
> > wrappers.
> 
> I don't see any way of implementing your proposal without wrappers.  It
> requires magic.  In practice, you as insisting on wrappers.

Adam, it's a proposal. It is something that we are distinctly _not_ insisting on. We are actively soliciting your feedback.
 
> I'm happy to consider designs that don't require Blink to implement
> wrappers.

Great.

> I don't know of any designs that are secure other than ones that
> mint new JS interface objects for each cross-origin viewer.

This should be "that are secure in Blink". I'm aware that Blink is very unlikely to change its approach here. So the goal of the proposal was to massage the implementations (by neutering prototypes and freezing objects) such that Blink could keep doing what it's down, and Gecko could keep doing what it's doing.
 
> I don't know how to have
> the same JS object have different behavior for different viewers without
> magic.

To discuss this meaningfully, we need to clarify what we mean by "viewers". See below.

> It depends what you mean by a cross-origin object.

That too is open for discussion.

> In the example earlier
> in the thread, would you consider frames[0].location.replace to be a
> cross-origin object?  In Blink, it's just a normal JS object, exactly the
> same kind you'd get from window.location.replace.  It's even the case that
> frames[0].location.replace.__proto__ === window.location.replace.__proto__.

As noted in the last paragraph of comment 63, this is what Gecko does as well, and I'm entirely open to keeping this behavior.

> The only thing that's different about frames[0].location.replace and
> window.location.replace is that the former operates on frames[0] whereas the
> latter operates on window.

Is there anything intrinsic to those methods that make them only able to operate on the original object (that is to say - are they bound)? This used to be the case in Gecko, but we considered it a bug and I fixed it. You can now call/apply any version of |replace| you find anywhere on any Location object, cross-origin or not.

> The way it works is just the way the following JavaScript
> would work:
> 
> get location() {
>   var activeWindow = %GetIncumbentWindow();
>   if (%IsSameOrigin(this, activeWindow)) {
>     // Return the same full-featured Location instance
>     // to all same-origin viewers.
>     return this._location;
>   return new activeWindow.Location(this);
> }

As David points out in the first paragraph of comment 67, this implies that |xoWin.location != xoWin.location|, because you'd get a new object each time. I'm quite sure this isn't true in Blink, both from my testing and from my understanding of how Blink works. IIUC, you have a per-global (or per-origin?) hash that maps from the C++ DOM object to the JS reflector.

> Notice that all same-origin viewers get exactly the same instance of the
> Location object

This would imply that the hash is per-origin. Is that correct?

> and that object has a prototype chain that connects up with
> it's window's prototypes.

Which prototypes, exactly? In the case of multiple same-origin windows jointly observing a cross-origin Location object - do they share the same instance? If so, where does the prototype chain lead? To the Location.prototype of whoever touched it first?
 
> That's fine.  They each get objects that belong to their own global object. 
> We haven't had any compat problems from this behavior.
> 
> > despite the fact that presumably the window.location will compare equal.
> 
> There's no reason for the objects to compare equal.

And _this_ implies that the hash is per-global. Which is it?

> We can make the
> comparison throw if you'd like, but they're not the same object.

In terms of how the spec describes the DOM, they very much are the same object. 

> Cross-origin viewers get minted fresh objects.  That's what makes it secure.

This is an implementation detail of Blink, as much as wrappers are an implementation detail of Gecko.

(In reply to Adam Barth from comment #66)
> (In reply to Ian 'Hixie' Hickson from comment #65)
> What makes you say that it's the same object?  They're just accessing the
> same getter, which is returning different objects at different times.  It's
> easy to write in idiomatic JavaScript, and we do it all the time in the
> platform.  For example, document.body.firstChild will return different
> objects at different times.  That certainly isn't a bug.

Yes, but this generally doesn't change depending on the incumbent script.

Conceptually, it's a difference between the binding layer (with JS reflector objects) and the actual DOM. If the DOM changes (as in the firstChild case) you should get a different result. If it doesn't, you should get the same result.

> The problem with freezing the objects is that the object Blink returns are
> the same objects you'd get by other means.  There's no way to "act as if"
> they were frozen without magic.  They're either frozen or they're not.  We
> can't actually freeze them because that would disrupt the script when it
> accessed them normally.

I don't follow this part. As you noted, each origin gets a freshly-minted JS object in Blink. Why can't those fresh ones be frozen while the canonical one not be frozen?

> > > Personally, I don't have any interest in changing Blink's implementation in
> > > this area.  It's simple, and it works well.

> I agree that interoperability is a good goal, but I don't see how to achieve
> it in this case because it's too easy for script to figure out which
> implementation approach is being used.

I don't agree. I believe that we can spec a compromise if the vendors come together in good faith.

> They just behave differently. 
> Trying to hide all the implementation details is going to end up being more
> complicated than the entire security mechanism.

Are you really suggesting that we should cease our efforts to interoperate on security behavior?
Comment 69 Adam Barth 2013-10-12 15:38:00 UTC
(In reply to David Bruant from comment #67)
> (In reply to Adam Barth from comment #66)
> > get location() {
> >   var activeWindow = %GetIncumbentWindow();
> >   if (%IsSameOrigin(this, activeWindow)) {
> >     // Return the same full-featured Location instance
> >     // to all same-origin viewers.
> >     return this._location;
> >   }
> >   return new activeWindow.Location(this);
> > }
> The last line is where the prototype objects (Function.prototype,
> Object.prototype) of the current frame (activeWindow) are grabbed to be used
> in the returned Location instance prototype chain, right?

Correct.

> This also means that as a cross-origin script, I get a different object
> everytime I ask for the location object? (since no record is kept of which
> activeWindow is being passed which Location instance)

Correct

> > Because parent.document instanceof parent.Document and window.document
> > instance window.Document.  In the cross-origin location case,
> > parent.location instanceof window.Location and window.location instanceof
> > window.Location.
>
> The last sentence is scary. It means that while cross-origin parent and
> window can communicate directly via window.Location.prototype. Is it a
> mistake? Am I missing something?

Recall that the entire comment is describing the world from the perspective of the cross-origin viewer.  All it means is that the script can communicate with itself, which isn't scary.

(In reply to Bobby Holley (:bholley) from comment #68)
> (In reply to Adam Barth from comment #64)
> > What you proposed is not possible to implement securely without wrappers.
> 
> > As far as I can tell, the concept of an Alien Object implies the existence
> > of wrappers.
> 
> As noted, that was not the intention. We came up with the notion of "Alien
> Object" specifically as something that would be implementable with or
> without wrappers.

I understand that was your intent, but as far as I can tell, the thing you created cannot be implemented without wrappers.  Do you have a non-wrapper-based implementation I can study and learn from?

> > The way you can tell is that is is specified in terms of a
> > JavaScript object acting as if it had some quality that it doesn't actually
> > have (e.g., being frozen).
> 
> Who says the object doesn't have the property? The "act as if" bit was just
> a wink and a nod to the fact that Gecko would use wrapper magic to
> accomplish this. In Blink, we presumed that you'd just freeze the object.

Why do we need winks and nods to wrapper magic?  If the behavior is observationally equivalent to normal JavaScript behavior, lets specify it as normal JavaScript behavior instead of with magic.

> > Why?  Wrappers are not required for security here.  There's also not
> > required for compatibility.  Is there some reason you're opposed to a
> > non-wrapper design?
> 
> Our architecture is designed around wrappers. In particular, we have to use
> them here, because we can only have one JS reflector per native DOM object.
> This is true in Servo as well.

That's an odd limitation.  In any case, it's not really a limitation here because you can create a new native DOM object at the same time you create the new JS interface objects.

> > In particular, the
> > identity of various objects differ between the two approaches.
> 
> Which objects? Location? Function?

Yes.

> We're quite flexible about what we do
> here in Gecko-land, so I'm reasonably confident we can come up with behavior
> that works for everyone.

Great!  Would you be willing to adopt the behavior I've proposed in this thread?  I'm confident that it's both secure and doesn't require any additional technology beyond what's already required to implement the web platform.

> > I don't see any way of implementing your proposal without wrappers.  It
> > requires magic.  In practice, you as insisting on wrappers.
> 
> Adam, it's a proposal. It is something that we are distinctly _not_
> insisting on. We are actively soliciting your feedback.

My feedback is that proposal doesn't work for me because it requires complication technology that I don't want to build.  If you can show me how to implement it with normal JavaScript semantics (i.e., without wrapper magic), I'd be willing to re-consider.

> > I don't know of any designs that are secure other than ones that
> > mint new JS interface objects for each cross-origin viewer.
> 
> This should be "that are secure in Blink". I'm aware that Blink is very
> unlikely to change its approach here. So the goal of the proposal was to
> massage the implementations (by neutering prototypes and freezing objects)
> such that Blink could keep doing what it's down, and Gecko could keep doing
> what it's doing.

That's a good goal, but the current proposal does not achieve that goal, as I've explained repeatedly.

> > In the example earlier
> > in the thread, would you consider frames[0].location.replace to be a
> > cross-origin object?  In Blink, it's just a normal JS object, exactly the
> > same kind you'd get from window.location.replace.  It's even the case that
> > frames[0].location.replace.__proto__ === window.location.replace.__proto__.
> 
> As noted in the last paragraph of comment 63, this is what Gecko does as
> well, and I'm entirely open to keeping this behavior.

Great!

> > The only thing that's different about frames[0].location.replace and
> > window.location.replace is that the former operates on frames[0] whereas the
> > latter operates on window.
> 
> Is there anything intrinsic to those methods that make them only able to
> operate on the original object (that is to say - are they bound)?

No, I just meant in the way they're commonly used (i.e., called with |this| bound to frames[0].location).  You'd get exactly the same results via window.location.replace.call(frames[0].location, ...).  That's, in fact, one of the ways to see that this approach is secure.  You never give a cross-origin script any objects that it didn't already have access to, which means you're not leaking anything dangerous.

> This used
> to be the case in Gecko, but we considered it a bug and I fixed it. You can
> now call/apply any version of |replace| you find anywhere on any Location
> object, cross-origin or not.

Great!

> > The way it works is just the way the following JavaScript
> > would work:
> > 
> > get location() {
> >   var activeWindow = %GetIncumbentWindow();
> >   if (%IsSameOrigin(this, activeWindow)) {
> >     // Return the same full-featured Location instance
> >     // to all same-origin viewers.
> >     return this._location;
> >   return new activeWindow.Location(this);
> > }
> 
> As David points out in the first paragraph of comment 67, this implies that
> |xoWin.location != xoWin.location|, because you'd get a new object each
> time. I'm quite sure this isn't true in Blink, both from my testing and from
> my understanding of how Blink works. IIUC, you have a per-global (or
> per-origin?) hash that maps from the C++ DOM object to the JS reflector.

The JS interface object minted in this way is never stored in the hash map.  It's possible the model I've described in this thread doesn't match the implementation 100% precisely, but it's a model we'd be happy to implement and is probably simpler to what Blink's implementation does currently.  For example, it's possible that we cache the minted Location objects, but that's not necessary.

> > Notice that all same-origin viewers get exactly the same instance of the
> > Location object
> 
> This would imply that the hash is per-origin. Is that correct?

Conceptually, the hash is static (meaning global for all frames).  We just don't query the hash in the cross-origin case.  Instead, we mint new objects.  (The reality is a bit more complex due to workers and a performance optimization that lets us avoid the hash lookup in some cases, but let's imagine a world without workers or observationally equivalent performance optimizations.)

> > and that object has a prototype chain that connects up with
> > it's window's prototypes.
> 
> Which prototypes, exactly? In the case of multiple same-origin windows
> jointly observing a cross-origin Location object - do they share the same
> instance?

Yes.  When I wrote the JavaScript "return this._location", what that means in JavaScript is that they all get the same instance: the one that's stored in the (hidden) _location property of the Window object.

> If so, where does the prototype chain lead? To the
> Location.prototype of whoever touched it first?

No.  For same-origin viewers, sameOriginWindow.location.__proto__ === sameOriginWindow.Location.prototype, just as with sameOriginWindow.locationbar.

I feel like I keep repeating myself.  Here's where I wrote that before:

"If they were same-origin, then the location property works the same way the locationbar property works, which is to say extremely normally."

> > > despite the fact that presumably the window.location will compare equal.
> > 
> > There's no reason for the objects to compare equal.
> 
> And _this_ implies that the hash is per-global. Which is it?

I don't see how you've arrived at that conclusion.

Really, this all works exactly the way the JavaScript snippet I keep writing works.  Same-origin callers of the location getter get identical objects---exactly the same sort of object they'd get for the locationbar property.  Cross-origin callers of the location getter get minted new objects each time, as described in that snippet.  There's no magic.  Just normal JavaScript semantics as described by the normal JavaScript I wrote earlier.

I think the problem is that you're so used to magic being required here that you keep looking for the magic.  There is no magic.  Everything really is as simple as described by those 9 lines of JavaScript.

> > We can make the
> > comparison throw if you'd like, but they're not the same object.
> 
> In terms of how the spec describes the DOM, they very much are the same
> object. 

They don't need to be.  They're just different objects that both happen to navigate the same window.

> > Cross-origin viewers get minted fresh objects.  That's what makes it secure.
> 
> This is an implementation detail of Blink, as much as wrappers are an
> implementation detail of Gecko.

No, that's the design!  That's what makes the design secure using normal JavaScript semantics.  Using fresh objects maintains the invariant that a given script only ever has references to JS objects that are associated with global objects associated with its origin (except the super-magic WindowProxy object, of course).

> (In reply to Adam Barth from comment #66)
> > (In reply to Ian 'Hixie' Hickson from comment #65)
> > What makes you say that it's the same object?  They're just accessing the
> > same getter, which is returning different objects at different times.  It's
> > easy to write in idiomatic JavaScript, and we do it all the time in the
> > platform.  For example, document.body.firstChild will return different
> > objects at different times.  That certainly isn't a bug.
> 
> Yes, but this generally doesn't change depending on the incumbent script.

So?  What's going on in both cases is that the property is implemented by a JavaScript getter, which returns different objects based on some internal state of the user agent.  In the case of Node#firstChild, the internal state is the state of the DOM tree.  In the case Window#location, the internal state is the JS runtime stack.

> Conceptually, it's a difference between the binding layer (with JS reflector
> objects) and the actual DOM. If the DOM changes (as in the firstChild case)
> you should get a different result. If it doesn't, you should get the same
> result.

That's a distinction without a difference.  If the user agent was implemented entirely in JavaScript, that distinction wouldn't exist.  Similarly, you can implement the proposal I've described above by creating a new native Location object instead of just creating a new JS interface object to the same native Location object.  That's observationally equivalent.

> I don't follow this part. As you noted, each origin gets a freshly-minted JS
> object in Blink. Why can't those fresh ones be frozen while the canonical
> one not be frozen?

We can freeze the objects if you like:

-  return new activeWindow.Location(this);
+  return Object.freeze(new activeWindow.Location(this));

Rather than saying the object "acts as if" it is frozen, we would say that this newly minted object *is* frozen.

> > I agree that interoperability is a good goal, but I don't see how to achieve
> > it in this case because it's too easy for script to figure out which
> > implementation approach is being used.
> 
> I don't agree. I believe that we can spec a compromise if the vendors come
> together in good faith.

I believe I've been clear about my requirements, but I'll restate them to avoid any doubt:

1) The design must be secure.
2) The design must not involve non-ECMAScript semantics.

I've made a proposal that meets these requirements.  I've explained the invariant that makes it secure.  I've proved that it follows ECMAScript semantics by exhibiting an ECMAScript implementation of the proposal.  Is my proposal acceptable to you?  If not, would you be willing to make a counter proposal that meets these requirements?

> > They just behave differently. 
> > Trying to hide all the implementation details is going to end up being more
> > complicated than the entire security mechanism.
> 
> Are you really suggesting that we should cease our efforts to interoperate
> on security behavior?

I'm saying we shouldn't try to paper over the differences in our implementations by adding some complicated machinery because the machinery to paper over the differences will end up being more complicated than the security mechanism itself.

Instead, we should implement the same behavior using a similar approach.  That way the behavior really will be interoperable.
Comment 70 David Bruant 2013-10-12 17:45:15 UTC
(In reply to Adam Barth from comment #69)
> > > I agree that interoperability is a good goal, but I don't see how to achieve
> > > it in this case because it's too easy for script to figure out which
> > > implementation approach is being used.
> > 
> > I don't agree. I believe that we can spec a compromise if the vendors come
> > together in good faith.
> 
> I believe I've been clear about my requirements, but I'll restate them to
> avoid any doubt:
> 
> 1) The design must be secure.
> 2) The design must not involve non-ECMAScript semantics.
Everything I've read about wrappers so far can be implemented in ECMAScript... 6 with proxies. (being able to create "membranes" was a design goal for proxies).
The proxies design is now very stable and has been agreed upon at the TC39 May 2013 meeting [1]. There is one detail that is not entirely sorted out yet about private state (like for Date or Map objects), but this detail doesn't affect what's being discussed here.
Note that one purpose of proxies and something that influence its design was closing the gap between platform objects and what can be done in pure ECMAScript. With ES6 proxies, you can implement live NodeList as well as WindowProxy with changing the underlying Window (modulo a few details about property non-configurability I raised about a year ago, but it's just a matter of adjusting HTML5/WebIDL to match ECMAScript semantics and its invariants in particular).

You've been very clear on not wanting to implement wrappers, so I'm not trying to convince you, just trying to clear out potential ambiguities. I imagine you refer to ES5 when you write "ECMAScript semantics."?
But to me, it begs the question: when will ES6 semantics be ok?


[1] https://github.com/rwaldron/tc39-notes/blob/master/es6/2013-05/may-21.md#44-proxies
Comment 71 Adam Barth 2013-10-12 18:24:33 UTC
> > I believe I've been clear about my requirements, but I'll restate them to
> > avoid any doubt:
> > 
> > 1) The design must be secure.
> > 2) The design must not involve non-ECMAScript semantics.
>
> Everything I've read about wrappers so far can be implemented in
> ECMAScript... 6 with proxies. (being able to create "membranes" was a design
> goal for proxies).

Ok.  Let me be more precise:

2) The design must not involve non-ECMAScript5 semantics.

> You've been very clear on not wanting to implement wrappers, so I'm not
> trying to convince you, just trying to clear out potential ambiguities. I
> imagine you refer to ES5 when you write "ECMAScript semantics."?

Correct.

> But to me, it begs the question: when will ES6 semantics be ok?

For security-critical semantics?  Probably never.  Proxies are much more complicated than necessary.  Complexity is the enemy of security.
Comment 72 David Bruant 2013-10-12 23:51:53 UTC
(In reply to Adam Barth from comment #71)
> > But to me, it begs the question: when will ES6 semantics be ok?
> 
> For security-critical semantics?  Probably never.  Proxies are much more
> complicated than necessary.  Complexity is the enemy of security.
Interesting. The way I see it, some security properties of JS programs (like revocability) cannot be achieved without proxies. Anyway not really the topic at hand, but sheds an interesting light on the opposition in this thread.

Back to the topic, although it respects ES5 semantics, it really is awkward that the result of the getter (same or different object) depends on the caller context, especially as it creates garbage that could be otherwise (with wrappers) avoided. At the same time, this is such a stupid use case with few practical value for authors (from what I understand), that I don't feel too strongly about avoiding garbage.

Are there other cases than location? What's the strategy for these? Is this creating a precedent? Has this strategy a chance to be reused elsewhere where creating garbage would be less appropriate?
Comment 73 Adam Barth 2013-10-13 05:00:01 UTC
> Are there other cases than location?

Just |location| and a handful of functions (e.g., postMessage, location.replace).  There used to be |history|, but we managed to block accessing that across origins.  Well, there's Window, of course, but that's super magical in more ways.

> What's the strategy for these?

The same approach works for all the cases I'm aware.

> Is this creating a precedent?

Everything is a precedent for something, but I don't think we're going to expose any more JS objects across origins.  Hopefully we've learned our lesson.

> Has this strategy a chance to be reused elsewhere
> where creating garbage would be less appropriate?

I'm not worried about that.  Exposing JS object across origins is an anti-pattern and we should avoid it in the future.
Comment 74 Ian 'Hixie' Hickson 2013-10-13 05:16:19 UTC
(In reply to Adam Barth from comment #66)
> > I don't know how to reconcile that with the fact that this is true (in a
> > same-origin iframe):
> > 
> >    parent.document.appendChild.__proto__ !== document.appendChild.__proto__
> > 
> > Why do these two methods work differently?
> 
> Is |parent| same-origin with |window|?

Yes (as noted in the parenthetical above).

Assuming A and B are in the same origin, and C is a separate origin, and the script is running in B, then in Blink:

   A.document.appendChild.__proto__ !== B.document.appendChild.__proto__
   A.locationbar.__proto__ !== B.locationbar.__proto__
   A.locationbar.toString.__proto__ !== B.locationbar.toString.__proto__
   A.location.__proto__ !== B.location.__proto__
   A.location.toString.__proto__ !== B.location.toString.__proto__
   A.location.replace.__proto__ === B.location.replace.__proto__ // !
   A.location === A.location
   C.location === C.location
   C.location.replace.__proto__ === B.location.replace.__proto__ // !
   A.location instanceof A.Location
   !(A.location instanceof B.Location)
   !(C.location instanceof B.Location)

See: http://software.hixie.ch/utilities/js/live-dom-viewer/saved/2560

The two lines marked "// !" are the ones I find surprising. That, and the way that the second script in comment 65 operates:

   http://software.hixie.ch/utilities/js/live-dom-viewer/saved/2561

Or to put it another way, I think it's very odd that this prints true:

   http://damowmow.com/playground/demos/cross-window/001.html

...while this prints false:

   http://damowmow.com/playground/demos/cross-window/002.html

Same-origin, two different windows get hold of the same object (a Location) object, which is triple-equals whichever side you look at it from. But then you compare that object's method's prototype, and you get different results based on which window you read it from? This is very strange. And it's only true of the methods on Location, for some reason the methods on Object are different, e.g. compare assign (!==) and toString (===) on the same Location objects:

   http://damowmow.com/playground/demos/cross-window/003.html (assign)
   http://damowmow.com/playground/demos/cross-window/004.html (toString)

I don't have an opinion as to what the spec should require, other than wanting everyone to agree on something and implement it. But I do think that Blink's current behaviour isn't intuitive. An object's identity, heritage, etc, shouldn't change based merely on how that object is accessed, in a script-detectable fashion. So if the proposal is Blink's behaviour, I would prefer (maybe slight, I don't really know) modifications. A concrete proposal in the form of comment 56's would help a lot.
Comment 75 Bobby Holley (:bholley) 2013-10-13 08:00:52 UTC
(In reply to Adam Barth from comment #69)
> 
> > This also means that as a cross-origin script, I get a different object
> > everytime I ask for the location object? (since no record is kept of which
> > activeWindow is being passed which Location instance)
> 
> Correct

> The JS interface object minted in this way is never stored in the hash map. 
> It's possible the model I've described in this thread doesn't match the
> implementation 100% precisely, but it's a model we'd be happy to implement
> and is probably simpler to what Blink's implementation does currently.  For
> example, it's possible that we cache the minted Location objects, but that's
> not necessary.

Ok. So you're proposing that xoWin.location != xoWin.location. This isn't what Blink does today (presumably due to your optimization), nor any other UA that I know if. It's not at all obvious to me that this would be web-compatible.

It's also contrary to the semantics of the web platform, because getters aren't supposed to mint a new JS value each time they're invoked (you're supposed to use a method if you want to do that).

> > Which prototypes, exactly? In the case of multiple same-origin windows
> > jointly observing a cross-origin Location object - do they share the same
> > instance?
> 
> Yes.  When I wrote the JavaScript "return this._location", what that means
> in JavaScript is that they all get the same instance: the one that's stored
> in the (hidden) _location property of the Window object.
>
> I feel like I keep repeating myself.

Probably because you misread my comment. I was asking about the case where |A| and |B| are same-origin with each other but not same-origin with |C|. The Location object they're observing is cross-origin.

Anyway. Adam has now clarified that his proposal really is to mint a new JS object on each call to the location getter from a cross-origin script. This provides a clean answer to most of the prototype details, but has two glaring issues:
(1) The behavior with respect to identity comparisons is extremely unintuitive and un-web.
(2) Blink does not actually implement this behavior, and given the == issue, it very well may not be web-compatible.
Comment 76 Adam Barth 2013-10-15 05:37:55 UTC
(In reply to Ian 'Hixie' Hickson from comment #74)
> (In reply to Adam Barth from comment #66)
> > > I don't know how to reconcile that with the fact that this is true (in a
> > > same-origin iframe):
> > > 
> > >    parent.document.appendChild.__proto__ !== document.appendChild.__proto__
> > > 
> > > Why do these two methods work differently?
> > 
> > Is |parent| same-origin with |window|?
> 
> Yes (as noted in the parenthetical above).

Sorry, then I gave you completely the wrong answer.  In the same-origin case, the |location| property works identically to the |locationbar| property.

> Assuming A and B are in the same origin, and C is a separate origin, and the
> script is running in B, then in Blink:
> 
>    A.document.appendChild.__proto__ !== B.document.appendChild.__proto__
>    A.locationbar.__proto__ !== B.locationbar.__proto__
>    A.locationbar.toString.__proto__ !== B.locationbar.toString.__proto__
>    A.location.__proto__ !== B.location.__proto__
>    A.location.toString.__proto__ !== B.location.toString.__proto__
>    A.location.replace.__proto__ === B.location.replace.__proto__ // !

That's not the intended behavior.  The intended behavior is that these objects would be !==.

>    A.location === A.location
>    C.location === C.location
>    C.location.replace.__proto__ === B.location.replace.__proto__ // !

This is the intended behavior.

>    A.location instanceof A.Location
>    !(A.location instanceof B.Location)
>    !(C.location instanceof B.Location)
> 
> See: http://software.hixie.ch/utilities/js/live-dom-viewer/saved/2560
>
> The two lines marked "// !" are the ones I find surprising.

As noted, I find only one of them surprising.

> Same-origin, two different windows get hold of the same object (a Location)
> object, which is triple-equals whichever side you look at it from. But then
> you compare that object's method's prototype, and you get different results
> based on which window you read it from?

The intent is that the same-origin case works the same way for |location| and |locationbar|.  I'm certainly willing to change the same-origin behavior to follow the normal rules for WebIDL interfaces.

However, in the cross-origin case, the second "// !" is expected and is essential to the security mechanism, as explained above.

> I don't have an opinion as to what the spec should require, other than
> wanting everyone to agree on something and implement it. But I do think that
> Blink's current behaviour isn't intuitive. An object's identity, heritage,
> etc, shouldn't change based merely on how that object is accessed, in a
> script-detectable fashion. So if the proposal is Blink's behaviour, I would
> prefer (maybe slight, I don't really know) modifications. A concrete
> proposal in the form of comment 56's would help a lot.

I've given a concrete proposal several times in the form of a JavaScript program.  I can re-write the JavaScript in prose if that would help.  My proposal in this thread doesn't match Blink's behavior exactly, but it meets the requirements I've written above.  Currently, it's the only proposal on the table that meets those requirements.

> Ok. So you're proposing that xoWin.location != xoWin.location. This isn't
> what Blink does today (presumably due to your optimization), nor any other
> UA that I know if. It's not at all obvious to me that this would be
> web-compatible.

I assume you mean !== rather than !=.

> It's also contrary to the semantics of the web platform, because getters
> aren't supposed to mint a new JS value each time they're invoked (you're
> supposed to use a method if you want to do that).

That sounds like an aesthetic issue.  All things being equal, I'd prefer to follow web aesthetics, but the requirements I've written about above are more important to me than aesthetics.  Do you have a more aesthetic counter-proposal that meets the above requirements?

> > > Which prototypes, exactly? In the case of multiple same-origin windows
> > > jointly observing a cross-origin Location object - do they share the same
> > > instance?
> > 
> > Yes.  When I wrote the JavaScript "return this._location", what that means
> > in JavaScript is that they all get the same instance: the one that's stored
> > in the (hidden) _location property of the Window object.
> >
> > I feel like I keep repeating myself.
> 
> Probably because you misread my comment. I was asking about the case where
> |A| and |B| are same-origin with each other but not same-origin with |C|.
> The Location object they're observing is cross-origin.

You've trimmed too much context.  I'm not sure what |A|, |B|, and |C| refer to.

> Anyway. Adam has now clarified that his proposal really is to mint a new JS
> object on each call to the location getter from a cross-origin script. This
> provides a clean answer to most of the prototype details, but has two
> glaring issues:
> (1) The behavior with respect to identity comparisons is extremely
> unintuitive and un-web.
> (2) Blink does not actually implement this behavior, and given the == issue,
> it very well may not be web-compatible.

I'm open to other proposals that meet the requirements above.  Do you have one?
Comment 77 Bobby Holley (:bholley) 2013-10-15 09:40:01 UTC
(In reply to Adam Barth from comment #76)
> > Ok. So you're proposing that xoWin.location != xoWin.location. This isn't
> > what Blink does today (presumably due to your optimization), nor any other
> > UA that I know if. It's not at all obvious to me that this would be
> > web-compatible.
> 
> I assume you mean !== rather than !=.

Why would it make a difference? Both values are of type |object|, which means that we just do a pointer comparison (Step 1-f in http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3 ).

> I'm open to other proposals that meet the requirements above.  Do you have
> one?

Yes. The original one Hixie proposed in comment 56. You claim that it requires wrappers to implement, but this seems mostly based around the usage of the phrase "act as if", which carries no semantic value and can certainly be removed if it bothers you.

From what I understand, here's what it would take to implement it in Blink:
(1) For each origin that is not same-origin with the canonical Location object's document, create a separate (cached) AlienLocation object. This satisfies the requirement that the design be secure.
2) Make the cross-origin-accessible properties (|replace| and |href|) |own| properties of an AlienLocation object. Freeze the AlienLocation object, the |replace| method, and the |href| setter. Give them all null prototypes. These operations are all implementable in ES5, which satisfies Adam's second requirement.

(Note that AlienLocation object is purely a theoretical concept. |instanceof Location| will still work, even with the null prototype, because WebIDL overrides instanceof to operate on object branding for DOM objects)

I want to be very clear about two things:

(1) - This proposal explicitly seeks to avoid the need for wrappers. If there are concrete ways in which the proposal requires them in a script-observable way, I consider that a bug and would like them to be pointed out.

(2) - I am not in any way insisting on this proposal. I am totally willing to implement Adam's proposal, on the condition that the following three things happen:
(A) - We first sort out any bugs in the counter-proposal that would prevent Blink from easily implementing it.
(B) - The spec editors (Hixie, David, Anne, etc) prefer it.
(C) - Blink ships first, thereby doing the leg work of determining whether the proposal is web-compatible.

Adam's proposal has the advantage that it's simple to spec, since we don't have to mess around with freezing and nulling out prototypes and whatnot. This would be a pretty killer argument for it in my mind, except for the fact that we still need to figure out what to do about the prototype chains for cross-origin Windows and their methods/getters/setters. I understand that Adam feels that discussing Window is out-of-scope for this bug. However, we're eventually going to need to sort out the cross-origin prototype issue for that case as well. So if we go with Adam's proposal for Location, we should be very sure that we're not going to have to end up doing the same freeze/null dance for Window.
Comment 78 David Bruant 2013-10-15 09:59:06 UTC
(In reply to Adam Barth from comment #76)
> > I don't have an opinion as to what the spec should require, other than
> > wanting everyone to agree on something and implement it. But I do think that
> > Blink's current behaviour isn't intuitive. An object's identity, heritage,
> > etc, shouldn't change based merely on how that object is accessed, in a
> > script-detectable fashion. So if the proposal is Blink's behaviour, I would
> > prefer (maybe slight, I don't really know) modifications. A concrete
> > proposal in the form of comment 56's would help a lot.
> 
> I've given a concrete proposal several times in the form of a JavaScript
> program.  I can re-write the JavaScript in prose if that would help.  My
> proposal in this thread doesn't match Blink's behavior exactly, but it meets
> the requirements I've written above.  Currently, it's the only proposal on
> the table that meets those requirements.
Web-compatibility is an implicit requirement of any proposal and it hasn't been proven yet.


(In reply to Bobby Holley (:bholley) from comment #77)
> (B) - The spec editors (Hixie, David, Anne, etc) prefer it.
Which David? me? I'm not a spec editor. Just trying to follow along and sending responses mostly to understand what's going on :-) I care to understand enough in order to be able to document that on MDN (or wherever) eventually, but I don't think my opinion should be taken into account.


> (C) - Blink ships first, thereby doing the leg work of determining whether
> the proposal is web-compatible.
Yep. Looking forward to Blink feedback ;-)
Comment 79 Bobby Holley (:bholley) 2013-10-15 10:18:19 UTC
(In reply to David Bruant from comment #78)
> > (B) - The spec editors (Hixie, David, Anne, etc) prefer it.
> Which David? me? I'm not a spec editor.

Oh, for some reason I thought you were involved with TC39 stuff. Anyway, it doesn't really matter - I'm just talking about the general community of people that thinks about these sorts of details and what they want it to look like. Adam and I both suffer from the unavoidable bias of living in a given implementation, so more neutral voices are helpful.

But yes, Hixie's opinion probably matters most. :-)
Comment 80 David Bruant 2013-10-15 10:47:56 UTC
(In reply to Bobby Holley (:bholley) from comment #79)
> (In reply to David Bruant from comment #78)
> > > (B) - The spec editors (Hixie, David, Anne, etc) prefer it.
> > Which David? me? I'm not a spec editor.
> 
> Oh, for some reason I thought you were involved with TC39 stuff.
I participate a lot on es-discuss, but that's where it stops. Of course, after some time now, I'm starting to get a sense of where TC39 is going to (hence my comment on proxies and ES6 and the goal to close the gap between ECMAScript expressiveness and what the platform needs to express).


> Adam and I both suffer from the unavoidable bias of living in a
> given implementation, so more neutral voices are helpful.
I noticed indeed :-) Whenever relevant/possible, I try to carry the voice and point of view of authors based on my experience (I've been doing website/apps for the last couple of years now). In that instance, my opinion, as I wrote is:
"this is such a stupid use case with few practical value for authors (from what I understand)". So, unless another author expresses a different opinion, the priority of constituency suggests that implementors are allowed to fight based on their bias :-)

I'm usually vocal when I feel the needs of implementors is taken too much into account, but I don't feel this bug is such a case, so I keep it low profile.

But I keep watching just to be sure things don't leak where that would be inappropriate (for instance, I'm keeping an eye open to see if the decision of this bug will leak to what happens for Window)
Comment 81 Ian 'Hixie' Hickson 2013-10-15 22:19:32 UTC
I'm happy to spec whatever is going to get implemented in multiple browsers, but I would definitely prefer it if it didn't have weird behaviour like, in a purely same-origin environment, a Location object having different behaviour for the __proto__ of .toString(), assign(), and replace().


I must admit to not understanding the JS code in comment 66 (the % stuff confuses me, in particular). Since the whole topic is something that is going somewhat over my head in the first place, the idea proposal for me would be one in spec terms, somewhat like the text in comment 56. Also, it would be ideal if the proposal could completely cover how Window works as well, so that we could put the whole topic of cross-origin JS stuff to rest all at once.
Comment 82 David Bruant 2013-10-15 22:34:38 UTC
(In reply to Ian 'Hixie' Hickson from comment #81)
> I must admit to not understanding the JS code in comment 66 (the % stuff
> confuses me, in particular).
It's a browser-specific extension. I think V8 started it when self-hosting ECMAScript built-ins. I think it was mostly for performance purposes.
Example: http://code.google.com/p/v8/source/browse/trunk/src/array-iterator.js
At some point, SpiderMonkey started self-hosting too and reused that convention, but I can't find examples anymore, maybe they removed their use of %
%Something is a function that is only accessible to privileged code.
Comment 83 Bobby Holley (:bholley) 2013-10-16 08:17:20 UTC
(In reply to Ian 'Hixie' Hickson from comment #81)
> I'm happy to spec whatever is going to get implemented in multiple browsers

If possible, I want to get at least two proposals on the table that vendors will implement, so as to allow you to exercise some editorial control. Additionally, once Adam and I are satisfied, I'd like to get some input from the Trident folks as well.

> but I would definitely prefer it if it didn't have weird behaviour like, in
> a purely same-origin environment, a Location object having different
> behaviour for the __proto__ of .toString(), assign(), and replace().

Unless I misunderstand you, I don't think that proposal is on the table.

> I must admit to not understanding the JS code in comment 66 (the % stuff
> confuses me, in particular). Since the whole topic is something that is
> going somewhat over my head in the first place, the idea proposal for me
> would be one in spec terms, somewhat like the text in comment 56.

Adam's proposal is that cross-origin callers get a new, unique object from the location getter each time they call it. This solves all the prototype issues (since the object can be created in the scope of whoever did the call), but has the unfortunate behavior that |xoWin.location != xoWin.location|.

> Also, it
> would be ideal if the proposal could completely cover how Window works as
> well, so that we could put the whole topic of cross-origin JS stuff to rest
> all at once.

I absolutely agree.

Adam, can you tell us how you think cross-origin Window access should work? In particular, supposing |A| and |B| are Windows which are same-origin with each other and |C| is a cross-origin Window jointly observed by |A| and |B|, I'm curious about the following things:

(1) Should |A| and |B| observe the same identity for |C|? I'm assuming so, but I want to double-check.

(2) Should |A| and |B| observe the same prototype chain for |C|? If so, where does it lead? Possibilities include A.__proto__, B.__proto__, some unique object, or null.

(3) If the prototype chain isn't null, does it include the named properties object?

(4) When |A| and |B| grab |C.close|, do they get the same function? In either case, what does the prototype chain of the function look like?


Sorting this out should give us some helpful perspective on the Location issue.
Comment 84 Bobby Holley (:bholley) 2013-10-27 13:50:32 UTC
 Adam, do you have an estimate on when you'll be able to weigh in here? I'd really like to sort this out before it goes stale in all of our heads.
Comment 85 Adam Barth 2013-11-20 23:28:19 UTC
(In reply to Ian 'Hixie' Hickson from comment #81)
> I must admit to not understanding the JS code in comment 66 (the % stuff
> confuses me, in particular). Since the whole topic is something that is
> going somewhat over my head in the first place, the idea proposal for me
> would be one in spec terms, somewhat like the text in comment 56.

Sorry, I can re-phrase in spec terms.

When the getter for the location property of a Window object |window| is invoked:

1) If the incumbent settings object specifies an effective script origin that is the same as the Window object's Document:
  a) Return the real |location| property of |window| and abort these steps.
2) Otherwise, let |activeWindow| be the Window object of the responsible browsing context of the incumbent settings object.
3) Let |location| be a fresh instance of the Location interface associated with |activeWindow|.
4) Associate |location| with |window| (e.g., make it so that setting the href property of |location| acts on |window| rather than on |activeWindow|---not sure what the right spec terminology is for this operation).
5) Return |location|.

(In reply to Bobby Holley (:bholley) from comment #83)
> Adam's proposal is that cross-origin callers get a new, unique object from
> the location getter each time they call it. This solves all the prototype
> issues (since the object can be created in the scope of whoever did the
> call), but has the unfortunate behavior that |xoWin.location !=
> xoWin.location|.

Correct.  Each get of the location property will hit step (3) above, which means each will get a fresh instance of |activeWindow|'s Location interface.

> Adam, can you tell us how you think cross-origin Window access should work?
> In particular, supposing |A| and |B| are Windows which are same-origin with
> each other and |C| is a cross-origin Window jointly observed by |A| and |B|,
> I'm curious about the following things:
> 
> (1) Should |A| and |B| observe the same identity for |C|? I'm assuming so,
> but I want to double-check.

I don't really want to talk about Window objects.  They're a big can of worms.  I'd much rather get the simpler Location object sorted out first.

To answer your question, I do think they should observe the same identity for |C|.

> (2) Should |A| and |B| observe the same prototype chain for |C|? If so,
> where does it lead? Possibilities include A.__proto__, B.__proto__, some
> unique object, or null.

I think getting |C|'s __proto__ property should throw, which means |A| and |B| can't observe the answer to this question.

> (3) If the prototype chain isn't null, does it include the named properties
> object?

There's no way to tell because you can't get the __proto__ property.

> (4) When |A| and |B| grab |C.close|, do they get the same function?

No.

> In either case, what does the prototype chain of the function look like?

Executing as |A|:

var closeFunc = C.close;
assert closeFunc.__proto__ === Function.prototype
assert closeFunc.__proto__ !== B.Function.prototype

Executing as |B|:

var closeFunc = C.close;
assert closeFunc.__proto__ === Function.prototype
assert closeFunc.__proto__ !== A.Function.prototype
assert A.closeFunc !== closeFunc

> Sorting this out should give us some helpful perspective on the Location
> issue.

I don't think the Location object should work the same way as the Window object.  The Window object is deeply magic because of WindowProxy.  The Location object, by contrast, doesn't need any magic for compatibility or security.
Comment 86 Ian 'Hixie' Hickson 2013-11-20 23:31:53 UTC
I really don't like having otherWindow.location !== otherWindow.location , or having otherWindow.location !== x where x is otherWindow.location obtained from a script with a different global. That to me seems highly unintuitive and confusing.
Comment 87 Bobby Holley (:bholley) 2013-11-21 02:36:00 UTC
Thanks for the input, Adam.

Given the current bug title, both Location and Window are in-scope for discussion.

====== The Window Object ======

To summarize what Adam wants for the Window object:
* The identity of a cross-origin Window object is consistent among all same-origin observers.
* The prototype of a cross-origin Window object is unobservable.
* The functions returned from property look-ups on the Window object have prototype chains leading to the Function.prototype associated with the incumbent script performing the look-up.

This is roughly consistent with what Gecko does, which is great.

Adam, can you clarify the following points?
* Are all properties on a cross-origin Window object reported as |own|? Presumably they should be, if the prototype is unobservable.
* Can we just make the prototype of cross-origin Window objects null, or is it important for Blink that we actually throw? Naively, it seems like whatever code does the throwing could equally just lie to the caller and return null. We could probably do either in Gecko, so I'd like to clarify which options are on the table.
* In the example from comment 83, what is the result of executing |C.close == C.close|? is the value cached, or minted afresh each time? We cache it in Gecko.


====== The Location Object ======

The Location object appears to be the bigger source of disagreement. In Gecko, we handle all cross-origin objects with the same code, so we'd have the same behavior for Location as we'd have for Window. This code is general, so it doesn't cost us anything extra.

In Blink, it sounds like it's a different story. The machinery there is specific to Window, and does not generalize as easily. This is why Adam wants to mint a new Location object on each access (so xoWin.location != xoWin.location). From here on, let's refer to Adam's proposal as "Minting".

Here are the various stakes, as I see them:
* Implementing Location similarly to Window would be a lot of work in Blink.
* Implementing Location similarly to Window is the default in Gecko. Doing otherwise requires nontrivial work, though perhaps not as much as the aforementioned work in Blink.
* Given that we already have to spec Window, speccing Minting for Location does not simplify the spec. It complicates it, though not by very much, because Minting itself is simple to spec.
* Speccing Minting for the Location object leads to very confusing and potentially-foot-gun semantics, which Hixie outlines in comment 86.
* Blink does not currently implement Minting, nor does any other UA. So we don't know if it's web compatible. I'm personally concerned that it may not be, given the breakage of equality comparisons.

Did I miss anything?

Hixie, what's the next step here? Should we get input from Microsoft? I know they don't participate in the WHATWG, but this is probably important enough that we should talk to them about it at some point.
Comment 88 Ian 'Hixie' Hickson 2013-11-21 19:16:32 UTC
Getting input from Microsoft would be great; I didn't realise they weren't cc'ed.
Comment 89 Travis Leithead [MSFT] 2013-11-21 19:29:13 UTC
Oh man, thrown into the fire. I'll try to catch up and provide some useful feedback...
Comment 90 Bobby Holley (:bholley) 2013-11-21 22:38:54 UTC
(In reply to Travis Leithead [MSFT] from comment #89)
> Oh man, thrown into the fire. I'll try to catch up and provide some useful
> feedback...

Yeah, reading this bug in its entirety is going to tie your brain into a pretzel. I'd suggest starting with comment 87, and searching back for more context when necessary.
Comment 91 Adam Barth 2013-11-22 02:02:32 UTC
(In reply to Bobby Holley (:bholley) from comment #87)
> Adam, can you clarify the following points?
> * Are all properties on a cross-origin Window object reported as |own|?
> Presumably they should be, if the prototype is unobservable.

What APIs can to call to tell?  Without testing, I'd recommend having any ways of observing the answer to that question throw exceptions.

> * Can we just make the prototype of cross-origin Window objects null, or is
> it important for Blink that we actually throw? Naively, it seems like
> whatever code does the throwing could equally just lie to the caller and
> return null. We could probably do either in Gecko, so I'd like to clarify
> which options are on the table.

What benefit is there in returning null?  If we return null, folks could start depending on that behavior.  If we throw, it's harder to add dependencies.  It's not impossible, of course, but it's harder.

> * In the example from comment 83, what is the result of executing |C.close
> == C.close|? is the value cached, or minted afresh each time? We cache it in
> Gecko.

The value appears to be cached in Blink as well, but I couldn't find the mechanism that caches it, which makes me worry that there's some hidden detail I don't fully understand.

> ====== The Location Object ======
> 
> The Location object appears to be the bigger source of disagreement. In
> Gecko, we handle all cross-origin objects with the same code, so we'd have
> the same behavior for Location as we'd have for Window. This code is
> general, so it doesn't cost us anything extra.

You seem happy with the minting approach for |close|.  I'm just suggesting that we use minting for |location| as well.  You seem to already be using both approaches in Gecko.  Is there some reason minting is ok for |close| but not for |location|?

> In Blink, it sounds like it's a different story. The machinery there is
> specific to Window, and does not generalize as easily.

It's just that there's a big pile of Window-specific machinery.

> This is why Adam
> wants to mint a new Location object on each access (so xoWin.location !=
> xoWin.location). From here on, let's refer to Adam's proposal as "Minting".

Do you agree that window.close also uses the "minting" approach?  Maybe there's some subtle distinction I'm missing.

> Here are the various stakes, as I see them:
> * Implementing Location similarly to Window would be a lot of work in Blink.
> * Implementing Location similarly to Window is the default in Gecko. Doing
> otherwise requires nontrivial work, though perhaps not as much as the
> aforementioned work in Blink.

Why does it require any more work than what you've already implemented for window.close?

> * Given that we already have to spec Window, speccing Minting for Location
> does not simplify the spec. It complicates it, though not by very much,
> because Minting itself is simple to spec.

Don't we need to spec minting already for |close|?

> * Speccing Minting for the Location object leads to very confusing and
> potentially-foot-gun semantics, which Hixie outlines in comment 86.

Why doesn't using minting for |close| cause the same confusion?

> * Blink does not currently implement Minting, nor does any other UA. So we
> don't know if it's web compatible. I'm personally concerned that it may not
> be, given the breakage of equality comparisons.

My proposal is what we do internally for Location.  We just throw a bunch of exceptions to try to make it hard to observe what we're doing.  We can spec throwing those exceptions if you want something that works exactly like an existing implementation.
Comment 92 Bobby Holley (:bholley) 2013-11-22 06:11:39 UTC
(In reply to Adam Barth from comment #91)
> > * Are all properties on a cross-origin Window object reported as |own|?
> > Presumably they should be, if the prototype is unobservable.
> 
> What APIs can to call to tell?  Without testing, I'd recommend having any
> ways of observing the answer to that question throw exceptions.

Object.getOwnPropertyDescriptor. Unless we want to declare war on TC39, that has to work.
 
> > * Can we just make the prototype of cross-origin Window objects null, or is
> > it important for Blink that we actually throw? Naively, it seems like
> > whatever code does the throwing could equally just lie to the caller and
> > return null. We could probably do either in Gecko, so I'd like to clarify
> > which options are on the table.
> 
> What benefit is there in returning null?  If we return null, folks could
> start depending on that behavior.  If we throw, it's harder to add
> dependencies.  It's not impossible, of course, but it's harder.

The whole point of speccing it is to allow people to depend on it.

I don't really have an opinion on it. I just want to help Hixie clearly distinguish between actual implementor constraints with the aesthetic opinions of those implementors.

> > ====== The Location Object ======

> You seem happy with the minting approach for |close|.  I'm just suggesting
> that we use minting for |location| as well.  You seem to already be using
> both approaches in Gecko.  Is there some reason minting is ok for |close|
> but not for |location|?

> Do you agree that window.close also uses the "minting" approach?  Maybe
> there's some subtle distinction I'm missing.

Conceptually, I think functions and DOM objects are very different beasts. But more importantly, there _is_ a subtle distinction - whether or not the value is cached, and how that happens (i.e. whether window.close === window.close).

> Why does it require any more work than what you've already implemented for
> window.close?

Because Gecko treats functions and WebIDL objects quite differently.

> > * Speccing Minting for the Location object leads to very confusing and
> > potentially-foot-gun semantics, which Hixie outlines in comment 86.
> 
> Why doesn't using minting for |close| cause the same confusion?

Because people are much less likely to do equality comparisons of window.close than they are to on window.location. Moreover: the caching issue.

> > * Blink does not currently implement Minting, nor does any other UA. So we
> > don't know if it's web compatible. I'm personally concerned that it may not
> > be, given the breakage of equality comparisons.
> 
> My proposal is what we do internally for Location.  We just throw a bunch of
> exceptions to try to make it hard to observe what we're doing.  We can spec
> throwing those exceptions if you want something that works exactly like an
> existing implementation.

No. My point is that, in Chrome today, |xoWin.location === xoWin.location|. Therefore, Chrome does not implement the most controversial aspect of Minting as it has been proposed.


So maybe we should modify the minting proposal for Location by adding caching? In particular, what if we specced that the |location| getter on cross-origin Window objects minted a brand new Location object, _but_ that object was cached for all incumbent scripts that share a given script settings object. This gets us |xoWin.location === xoWin.location|, even though two collaborating same-origin iframes could still detect that they receive different results.

I'm not really wild about this proposal, but it seems like it's probably more likely to get us interop than anything that's been proposed so far. Indeed, I'd guess that this is probably what Blink does today (even though the source of the caching is a bit of a mystery according to Adam).
Comment 93 Boris Zbarsky 2013-11-22 17:06:50 UTC
> What APIs can to call to tell?

Object.getOwnPropertyDescriptor is the obvious one.

Object.defineProperty would be another, if there is ever a situation in which _setting_ a non-unforgeable property on a cross-origin window is allowed.  I'm not sure whether there are such cases.

Object.getOwnPropertyNames is another one.

We may in fact be able to make these throw as desired, I suspect, if WindowProxy is very carefully specced as something akin to an ES proxy and all of its hooks are very carefully defined (e.g. the get() and set() hooks need to work even if getOwnPropertyDescriptor() throws).  I strongly suggest checking with the TC39 folks and the various JS engine implementors whether this sort of thing is possible.

The result would be pretty fragile, though, especially as changes happen to the ES specification in terms of how the MOP works.  It's also not obvious to me how web-compatible such a change would be at this point, unless UAs already throw for this stuff.
Comment 94 Ian 'Hixie' Hickson 2013-11-22 18:02:05 UTC
FWIW, I really think we should maintain x.close === x.close (and x.close === y, where y is x.close from another same-origin or similar-origin context). The idea that you could, in one script, have two objects that don't have the same identity but that actually are the same object is really weird and unintuitive.
Comment 95 Bobby Holley (:bholley) 2013-11-22 18:06:34 UTC
(In reply to Ian 'Hixie' Hickson from comment #94)
> FWIW, I really think we should maintain x.close === x.close

Yes, this is covered by "minting with caching".

> (and x.close === y, where y is x.close from another same-origin or
> similar-origin context).

This is not covered by minting with caching. Doing it correctly is going to be very hard (both for Gecko and Blink).

> The idea that you could, in one script, have two objects that don't have the
> same identity but that actually are the same object is really weird and
> unintuitive.

Yes. But we have to consider the likelihood of confusion. I think at least  90% of the pain is covered by the |x.close === x.close| case as opposed to the |x.close === y| case.
Comment 96 Adam Barth 2013-11-23 02:55:01 UTC
(In reply to Bobby Holley (:bholley) from comment #92)
> Object.getOwnPropertyDescriptor. Unless we want to declare war on TC39, that
> has to work.

Looks like Blink currently returns undefined, which seems like plausible solution.

> > What benefit is there in returning null?  If we return null, folks could
> > start depending on that behavior.  If we throw, it's harder to add
> > dependencies.  It's not impossible, of course, but it's harder.
> 
> The whole point of speccing it is to allow people to depend on it.

I don't want people to depend on it.  We might want to change it in the future.  The fewer dependencies people have on these crazy corner cases, the better.

> I don't really have an opinion on it. I just want to help Hixie clearly
> distinguish between actual implementor constraints with the aesthetic
> opinions of those implementors.

This falls more into the aesthetic camp.

> Conceptually, I think functions and DOM objects are very different beasts.

Why?  That seems like an odd distinction.  Maybe there's some implementation reason for that in Gecko, but in Blink they're just part-and-parcel.

> But more importantly, there _is_ a subtle distinction - whether or not the
> value is cached, and how that happens (i.e. whether window.close ===
> window.close).

Ok, if caching is a big deal for you, we can add caching to the proposal.  We just need to make sure that everyone who gets the same object out of the cache is in the same origin.

> > Why does it require any more work than what you've already implemented for
> > window.close?
> 
> Because Gecko treats functions and WebIDL objects quite differently.

That's unfortunate.  :(

> > > * Speccing Minting for the Location object leads to very confusing and
> > > potentially-foot-gun semantics, which Hixie outlines in comment 86.
> > 
> > Why doesn't using minting for |close| cause the same confusion?
> 
> Because people are much less likely to do equality comparisons of
> window.close than they are to on window.location. Moreover: the caching
> issue.

So, would minting+caching work for you as a solution?  We'd still need to work out the details of when you get a cache hit and when you need to mint a new object, but if the general approach works for you, we can probably work out the details.

> So maybe we should modify the minting proposal for Location by adding
> caching?

Sure, I'm happy to explore that idea.  My only hard requirement is the one I wrote above, which is that no single object is exposed to more than one origin.

Aesthetically, it would also be nice if the properties of the object you see aren't history-dependent.  For example, we'd probably want to null out or throw when getting the __proto__ property so that the cached object doesn't have an obvious "home" global object.

Unlike in the proxy approach, these details aren't security-critical.  They're just for developer sanity.

> In particular, what if we specced that the |location| getter on
> cross-origin Window objects minted a brand new Location object, _but_ that
> object was cached for all incumbent scripts that share a given script
> settings object. This gets us |xoWin.location === xoWin.location|, even
> though two collaborating same-origin iframes could still detect that they
> receive different results.

That's fine, but I would like to make the design history-independent.  Specifically, if a script reads xoWin.location, there should be no observables which would let it determine that another script had previously read that same property.

> I'm not really wild about this proposal, but it seems like it's probably
> more likely to get us interop than anything that's been proposed so far.

The best signs of a good compromise.  :)

> Indeed, I'd guess that this is probably what Blink does today (even though
> the source of the caching is a bit of a mystery according to Adam).

It's more likely that we just lie about ===, but I'd have to dig into the implementation to either find the === lie or the cache.  I haven't been able to find either yet.
Comment 97 David Bruant 2013-11-23 17:24:06 UTC
(In reply to Boris Zbarsky from comment #93)
> > What APIs can to call to tell?
> 
> Object.getOwnPropertyDescriptor is the obvious one.
> 
> Object.defineProperty would be another, if there is ever a situation in
> which _setting_ a non-unforgeable property on a cross-origin window is
> allowed.  I'm not sure whether there are such cases.
> 
> Object.getOwnPropertyNames is another one.
> 
> We may in fact be able to make these throw as desired, I suspect, if
> WindowProxy is very carefully specced as something akin to an ES proxy and
> all of its hooks are very carefully defined (e.g. the get() and set() hooks
> need to work even if getOwnPropertyDescriptor() throws).  I strongly suggest
> checking with the TC39 folks and the various JS engine implementors whether
> this sort of thing is possible.
I'm not TC39 and don't carry an implementor POV, but from what I know of ES proxies, this is possible indeed. One thing to be extra careful about when spec'ing the traps (which you call "hooks") is respecting "eternal invariants"
https://mail.mozilla.org/pipermail/es-discuss/2011-May/014150.html
These are enforced by proxies at runtime by throwing errors. My personal recommendation to be sure to get that part right would be to write code first with ES proxies to see if what's planned as spec is actually something allowed in the language. To my knowledge, the most up-to-date implementation of ES proxies ("direct proxies") is this library (by Tom Van Cutsem, one of the TC39-ers champion of ES proxies) https://github.com/tvcutsem/harmony-reflect


> The result would be pretty fragile, though, especially as changes happen to
> the ES specification in terms of how the MOP works.
I feel that spec'ing ES proxies will freeze how the MOP works and changes there are very unlikely to happen. In lots of occasions, discussions of the ES proxies design led to MOP discussions (which I take as a sign that the MOP needed to be nailed down and robust before the freeze that proxies would cause). But that's just a feeling.

> It's also not obvious
> to me how web-compatible such a change would be at this point, unless UAs
> already throw for this stuff.
Since IE8 is very much around, I doubt Object.getOwnPropertyDescriptor/defineProperty/getOwnPropertyNames are being used too much. But mobile web is rising and has these.
At the same time, web browsers have been very inconsistent in how they expose the global object, so I also doubt people are playing around too much with ES5 introspection function on the global object. The web may prove me wrong, of course.

(In reply to Adam Barth from comment #96)
> (In reply to Bobby Holley (:bholley) from comment #92)
> > Object.getOwnPropertyDescriptor. Unless we want to declare war on TC39, that
> > has to work.
> 
> Looks like Blink currently returns undefined, which seems like plausible
> solution.
I'm willing to bet that *at least* one of these undefined breaks one of the "eternal invariants". For why these invariants are important, either write to Mark Miller directly or ask on es-discuss.
Comment 98 Ian 'Hixie' Hickson 2013-11-25 18:41:00 UTC
I don't understand why it has to be difficult to return the same object for the same attribute regardless of who gives it. Why can't we just have the attributes and methods and setters and getters check the identity of the script and act accordingly, rather than having multiple objects going around?
Comment 99 Bobby Holley (:bholley) 2013-11-25 19:45:27 UTC
(In reply to Adam Barth from comment #96)

> Sure, I'm happy to explore that idea.  My only hard requirement is the one I
> wrote above, which is that no single object is exposed to more than one
> origin.
> 
> Aesthetically, it would also be nice if the properties of the object you see
> aren't history-dependent.  For example, we'd probably want to null out or
> throw when getting the __proto__ property so that the cached object doesn't
> have an obvious "home" global object.

I don't understand the issue here. If the cache is per-incumbent-script-settings-object (per-global), the cached object very much has a "home" global object.

If the idea is that the cache is per-origin, and the particular global is unobservable due to null prototypes and whatnot, then this is starting to sound very much like the original "alien object" proposal.

> That's fine, but I would like to make the design history-independent. 
> Specifically, if a script reads xoWin.location, there should be no
> observables which would let it determine that another script had previously
> read that same property.

Yes, minting-with-caching has this property.

> > Indeed, I'd guess that this is probably what Blink does today (even though
> > the source of the caching is a bit of a mystery according to Adam).
> 
> It's more likely that we just lie about ===, but I'd have to dig into the
> implementation to either find the === lie or the cache.  I haven't been able
> to find either yet.

Oh, interesting. I don't think lying about === would be acceptable in Gecko, but if the spec can be implemented either way then that's fine too.

(In reply to Ian 'Hixie' Hickson from comment #98)
> I don't understand why it has to be difficult to return the same object for
> the same attribute regardless of who gives it. Why can't we just have the
> attributes and methods and setters and getters check the identity of the
> script and act accordingly, rather than having multiple objects going around?

Are you referring to single-object-per-origin, or single-object-period?

The former requires neutering the object in such a way that its "home" global is unobservable (i.e. alien objects). The latter violates the "hard requirement" that Adam gives in comment 96.

The third option is minting-with-caching. We could probably do any of the three in Gecko with varying amounts of work, but I'd want to do a final deep-dive before irrevocably signing off on any of them.
Comment 100 Ian 'Hixie' Hickson 2013-11-26 20:14:17 UTC
> Are you referring to single-object-per-origin, or single-object-period?

single-object-period.


> The former requires neutering the object in such a way that its "home"
> global is unobservable (i.e. alien objects).

Why? Can't we just return a different proto depending on who looks, just like we do for everything else?
Comment 101 Bobby Holley (:bholley) 2013-11-27 00:34:17 UTC
(In reply to Ian 'Hixie' Hickson from comment #100)
> > Are you referring to single-object-per-origin, or single-object-period?
> 
> single-object-period.

Ok. I'll let you take that up with Adam then.

> > The former requires neutering the object in such a way that its "home"
> > global is unobservable (i.e. alien objects).
> 
> Why? Can't we just return a different proto depending on who looks, just
> like we do for everything else?

What is "everything else" here?
Comment 102 Ian 'Hixie' Hickson 2013-11-27 23:51:31 UTC
> > Why? Can't we just return a different proto depending on who looks, just
> > like we do for everything else?
> 
> What is "everything else" here?

I meant just like we'd return a different value (or throw instead of returning a value) for attributes and methods.
Comment 103 Adam Barth 2013-12-04 17:10:31 UTC
(In reply to Ian 'Hixie' Hickson from comment #98)
> I don't understand why it has to be difficult to return the same object for
> the same attribute regardless of who gives it. Why can't we just have the
> attributes and methods and setters and getters check the identity of the
> script and act accordingly, rather than having multiple objects going around?

Because it's too difficult to implement that securely.  The bugs are subtle and when they occur, the result is universal XSS.  That's not a risk I'm willing to take.

(In reply to Bobby Holley (:bholley) from comment #99)
> I don't understand the issue here. If the cache is
> per-incumbent-script-settings-object (per-global), the cached object very
> much has a "home" global object.

Ok, we can make it per-global.

> If the idea is that the cache is per-origin, and the particular global is
> unobservable due to null prototypes and whatnot, then this is starting to
> sound very much like the original "alien object" proposal.

The difference is that these tweaks aren't required for security.  They're just for aesthetics.  If we go with per-global, then we can skip them.
Comment 104 Ian 'Hixie' Hickson 2013-12-05 18:21:39 UTC
Window already has the same object across origins, right?
Why is it ok for Window but not Location?
Comment 105 Bobby Holley (:bholley) 2013-12-06 00:21:51 UTC
(In reply to Ian 'Hixie' Hickson from comment #104)
> Window already has the same object across origins, right?
> Why is it ok for Window but not Location?

Adam's claim is that Window is special-cased up the wazoo in Blink and he doesn't want to duplicate all the special-casing for Location.


(In reply to Adam Barth from comment #103)
> > this is starting to
> > sound very much like the original "alien object" proposal.
> 
> The difference is that these tweaks aren't required for security.  They're
> just for aesthetics.

By "aesthetics", do  you mean "providing speccable behavior that doesn't depend on the order in which different scripts access different properties"? If so, then those bits (in comment 56) were always for aesthetics.

Does clearing that up change your willingness to implement Alien Objects? If so, what are the important differences between Alien Objects and your proposal in comment 96?

> If we go with per-global, then we can skip them.

Yes.

If I understand correctly, there are now two proposals on the table that Adam would consider implementing:

(1) One Location object per (accessing script origin, target Window) pair, with the appropriate bits neutered to make the "home global" of that Location object unobservable.

(2) One Location object per (accessing script global, target Window) pair. No neutering needs to be done.

Approach (2) makes identity issues observable, but only between same-origin scripts jointly observing a cross-origin Window. Approach (1) fixes the identity issues, at the cost of some amount of funky neutering.

Did I get all that right?
Comment 106 Ian 'Hixie' Hickson 2013-12-06 18:57:09 UTC
(In reply to Bobby Holley (:bholley) from comment #105)
> (In reply to Ian 'Hixie' Hickson from comment #104)
> > Window already has the same object across origins, right?
> > Why is it ok for Window but not Location?
> 
> Adam's claim is that Window is special-cased up the wazoo in Blink and he
> doesn't want to duplicate all the special-casing for Location.

If it's safe enough for Window, I don't understand why it wouldn't be safe enough for Location. This isn't a slippery slope, we're talking about exactly two objects here, down from four originally; we've been trimming this down as much as possible. It's not like we'll ever add new objects. I understand that it probably wouldn't be pretty code.


> (1) One Location object per (accessing script origin, target Window) pair,
> with the appropriate bits neutered to make the "home global" of that
> Location object unobservable.

So the "===" operator would return true? They either wouldn't have, or would share, custom properties? Prototypes on their members wouldn't be visible?

In what sense are they different objects, then, other than as an implementation detail? I'm not really understanding this proposal in detail.


> (2) One Location object per (accessing script global, target Window) pair.

Obviously I don't get a veto here, but if I did, I would want to veto anything that involves the accessing script global controlling the identity of objects returned from an attribute getter.
Comment 107 Bobby Holley (:bholley) 2013-12-06 20:34:18 UTC
(In reply to Ian 'Hixie' Hickson from comment #106)
> (In reply to Bobby Holley (:bholley) from comment #105)
> > (In reply to Ian 'Hixie' Hickson from comment #104)
> > > Window already has the same object across origins, right?
> > > Why is it ok for Window but not Location?
> > 
> > Adam's claim is that Window is special-cased up the wazoo in Blink and he
> > doesn't want to duplicate all the special-casing for Location.
> 
> If it's safe enough for Window, I don't understand why it wouldn't be safe
> enough for Location. This isn't a slippery slope, we're talking about
> exactly two objects here, down from four originally; we've been trimming
> this down as much as possible. It's not like we'll ever add new objects. I
> understand that it probably wouldn't be pretty code.

I'll leave that between you and Adam. It would be good to get Travis' input too.

> > (1) One Location object per (accessing script origin, target Window) pair,
> > with the appropriate bits neutered to make the "home global" of that
> > Location object unobservable.
> 
> So the "===" operator would return true?

Yes. There would only be one object for any set of same-origin scripts. Though what would we do for document.domain?

> They either wouldn't have, or would
> share, custom properties? Prototypes on their members wouldn't be visible?
> 
> In what sense are they different objects, then, other than as an
> implementation detail? I'm not really understanding this proposal in detail.

I don't really grok what you mean by "They" here. The proposal is just to spec that there is a unique object per origin. The difference between this and "one object" isn't really observable modulo document.domain (and as noted, I'm not sure what we would do there). In Gecko, we'd implement this by just doing "one object" and giving cross-origin consumers XrayWrappers to it.

> > (2) One Location object per (accessing script global, target Window) pair.

> Obviously I don't get a veto here, but if I did, I would want to veto
> anything that involves the accessing script global controlling the identity
> of objects returned from an attribute getter.

That's good to know.
Comment 108 Ian 'Hixie' Hickson 2013-12-06 22:31:23 UTC
Oh, I see. I missed the difference between "accessing script origin" and "accessing script global". Making it dependent on the accessing script _origin_ is something I think would be fine — as you say, it's not distinguishable from there being one object. Except, also as you say, with document.domain. But I'm happy to throw document.domain under the bus here. I guess we'd return a different object per effective script origin, such that:

   var L1 = location;
   document.domain = slightlyDifferentDomain;
   var L2 = location;
   // L1 !== L2

Assuming that's not Web-incompatible, I'm fine with it.
Comment 109 Bobby Holley (:bholley) 2013-12-07 02:06:42 UTC
(In reply to Ian 'Hixie' Hickson from comment #108)
> Oh, I see. I missed the difference between "accessing script origin" and
> "accessing script global". Making it dependent on the accessing script
> _origin_ is something I think would be fine — as you say, it's not
> distinguishable from there being one object. Except, also as you say, with
> document.domain. But I'm happy to throw document.domain under the bus here.
> I guess we'd return a different object per effective script origin, such
> that:
> 
>    var L1 = location;
>    document.domain = slightlyDifferentDomain;
>    var L2 = location;
>    // L1 !== L2

Well, that's the kicker. In Gecko, we'd actually be implementing Location just like Window (as you suggest, in comment 104), since the difference is (almost) unobservable. So in the document.domain case, they'd compare equal for us. We don't have a good way of making them compare not equal.

If we go with this approach, the location-identity-after-document-domain issue is going to be as maddening to solve (for whoever lands on the wrong side of the spec) as it is silly.

So in short, I would not be ok with speccing L1 !== L2 as you propose. Whether that scuttles this whole proposal, I don't know.
Comment 110 Ian 'Hixie' Hickson 2013-12-09 18:28:42 UTC
Doesn't Gecko's security model mean you couldn't actually compare L1 and L2 anyway? I thought you were ignoring the spec's model and using a different one where document.domain prevents you from accessing objects from earlier or something.
Comment 111 Boris Zbarsky 2013-12-09 18:32:02 UTC
We revoke no-longer-same-origin objects, so that basically all MOP operations on them throw.

But == and === don't invoke a MOP operation when comparing objects; they're done directly without asking the object anything.  So they can still be performed, even on objects you otherwise have no access to, as long as you have a reference to them.
Comment 112 Bobby Holley (:bholley) 2013-12-09 19:23:37 UTC
What Boris says is true. But the issue is actually deeper than that.

Suppose we have the following scopes:
A: a.foo.com
B: b.foo.com
C: bar.org

Imagine that A and B both grab a reference to C.location, which is a cross-origin Location object. Then, they each set document.domain to |foo.com| so that they can collaborate with each other. They each have independent and valid cross-origin references to C.location. At this point, the identity issue becomes observable.
Comment 113 Ian 'Hixie' Hickson 2013-12-09 20:59:21 UTC
So the problem is that you (Gecko) want to return the same object, rather than one per origin? Why not just return one per origin?
Comment 114 Bobby Holley (:bholley) 2013-12-09 21:11:46 UTC
(In reply to Ian 'Hixie' Hickson from comment #113)
> So the problem is that you (Gecko) want to return the same object, rather
> than one per origin? Why not just return one per origin?

Because then we have to deal with the nastiness of where that object actually lives - i.e. in which global's scope. Creating a special global per-origin to hold this thing is a non-starter memory-wise. And if we just put it in the scope of whichever global happens to access it first, we have to figure out what to do if _that_ global's script does document.domain. Yuck.

My top preference would be for us to just continue using one Location object, like we do now. Whether we spec that explicitly or just spec something that allows us to do that under the hood isn't so much of a concern for me.

If we can't do that, the only remaining option that I can think of is to have the identity of a cross-origin Location object depend on the incumbent global when the access happens (the idea that you said merited your veto).
Comment 115 Ian 'Hixie' Hickson 2013-12-12 21:20:52 UTC
If we can get the one-object and multiple-object cases to be indistinguishable (notwithstanding document.domain), then let's go with that. It seems to be the option that everyone can implement. (Or at least, everyone who's commented.)

Anyone want to try to describe that in the style of comment 56?
Comment 116 Bobby Holley (:bholley) 2013-12-12 23:29:39 UTC
Just spoke with Ian and Adam. We're going to meet up at Google SF next friday (the 20th) to hash this out. If anyone wants to join or has any input before then, please speak up.
Comment 117 Bobby Holley (:bholley) 2013-12-13 03:08:29 UTC
(In reply to Ian 'Hixie' Hickson from comment #115)
> If we can get the one-object and multiple-object cases to be
> indistinguishable (notwithstanding document.domain), then let's go with
> that. It seems to be the option that everyone can implement. (Or at least,
> everyone who's commented.)

I just got some feedback from Boris about this, and he's very skeptical of trying to make the home global of methods unobservable. His points:

(1) Nulling out prototypes means that you lose apply(), bind(), call(), toString(), etc, unless we manually copy all of those down, which is more work.

(2) This approach, in his view, is doomed by TC39's realm work, in which mechanisms to access the Realm of an object are quickly proliferating. It would basically result in a game of spec whack-a-mole in which HTML5 would need to enumerate all the things that would have to throw for alien objects. All in all, very fragile.

(3) This approach wouldn't work for window, because you can just invoke a window method with an undefined |this|, and it will operate on its global, at which point it's generally possible to exfiltrate information.
Comment 118 Bobby Holley (:bholley) 2013-12-13 03:10:13 UTC
Also, we should make every effort to get some idea of Trident's setup before next friday. Travis, can you describe your setup a bit? It would be massively beneficial.
Comment 119 Ian 'Hixie' Hickson 2014-01-06 19:29:46 UTC
Here's what we concluded at the meeting, based on my notes:

- Window and Location objects represent a concept. In the case of Window it's
  the set of APIs associated with a Document in a browsing context; in the case
  of Location it's the set of APIs relating to that document's URL and
  navigating that document's browsing context.

- When a script from one origin tries to get a Window or Location object
  representing a concept from another origin, it gets an instance of that object 
  that is specially minted for that origin. If that origin is not the same 
  origin as the origin of the Document for which the underlying concept exists, 
  then the object is known as a "non-native object".

- The === operator, when applied to two Window or Location objects representing 
  the same underlying concept, returns true.

- Location object properties defined in WebIDL cannot be changed.

- Non-native Window and Location objects refuse new properties (maybe they are 
  frozen? We weren't quite sure what that implied.)

- Object.getPrototypeOf() when applied to non-native Window and Location objects
  returns null.

- Non-native objects' properties fall into two buckets, whitelisted and not 
  whitelisted (the lists are in the spec today).

- When you get a property on a non-native object that is not whitelisted, it 
  throws a SecurityError.

- When you get a property on a non-native object that is a function, it returns
  a Function object that is specially minted for that origin, object, and 
  property.

- When you get a property on a non-native object that is not a function, it
  returns a primitive value.

bholley and I also discussed something to do with own properties on Window, but I forget the details and apparently didn't write it down. :-(
Comment 120 Boris Zbarsky 2014-01-06 19:45:54 UTC
> - When you get a property

This needs to be defined more carefully.  Really, what you need to do is to define what the various things in the table in http://people.mozilla.org/~jorendorff/es6-draft.html#sec-object-internal-methods-and-internal-slots do.  The proposal here defines [[GetPrototypeOf]] explicitly and presumably defines [[SetPrototypeOf]], [[IsExtensible]], [[Set]], [[Delete]], [[DefineOwnProperty]] implicitly by freezeing the object.  What remain to be defined are [[GetOwnProperty]], [[Enumerate]], and [[OwnPropertyKeys]].  

>- When you get a property on a non-native object that is not a function, it
>  returns a primitive value.

Is that web-compatible?  I thought the web allowed indexed/named access into even cross-origin windows, returning child frames...  Bobby?

>- The === operator, when applied to two Window or Location objects representing 
>  the same underlying concept, returns true.

This needs either an ES6 spec change or we're claiming that it's impossible to detect through black-box testing that two things that are testing === in this sense are actually different objects, right?
Comment 121 Ian 'Hixie' Hickson 2014-01-06 21:02:46 UTC
> Is that web-compatible?  I thought the web allowed indexed/named access into
> even cross-origin windows, returning child frames...  Bobby?

Oops, yeah, sorry, for Window there's a few exceptions around returning other Windows or Location objects.


> This needs either an ES6 spec change or we're claiming that it's impossible
> to detect through black-box testing that two things that are testing === in
> this sense are actually different objects, right?

I don't know; this was something that bholley and abarth seemed to think was the less controversial of the points here...
Comment 122 Bobby Holley (:bholley) 2014-01-07 00:45:00 UTC
(In reply to Ian 'Hixie' Hickson from comment #119)

> - When you get a property on a non-native object that is a function, it
> returns
>   a Function object that is specially minted for that origin, object, and 
>   property.

I'm pretty sure this isn't quite right. I believe it was per (incumbent script, object, property).

Basically, we wanted the following to be true in the scope of A:

C.close !== B.eval("C.close")

Otherwise, the weirdness becomes observable with document.domain.

(In reply to Boris Zbarsky from comment #120)> 
> >- The === operator, when applied to two Window or Location objects representing 
> >  the same underlying concept, returns true.
> 
> This needs either an ES6 spec change or we're claiming that it's impossible
> to detect through black-box testing that two things that are testing === in
> this sense are actually different objects, right?

I believe that the idea was that it would be impossible to differentiate them, even with document.domain. The prototypes are unobservable, and the properties change depending on who accesses them.

Basically, Gecko's current implementation should more or less implement what we discussed, modulo potentially a bit of tweaking for the proto hiding.
Comment 123 Ian 'Hixie' Hickson 2014-01-17 22:12:54 UTC
I've been looking into what this is going to take, at the spec level.

The biggest problem I see so far is that the assumption that there's just one Window object per Document is baked really deep into the spec. There's 900+ mentions of Window in the spec, every one of which would have to be audited and probably significantly edited.

One option is to just mention this problem in the Security section, but that's pretty awful.
Comment 124 Bobby Holley (:bholley) 2014-01-17 23:48:43 UTC
(In reply to Ian 'Hixie' Hickson from comment #123)
> I've been looking into what this is going to take, at the spec level.
> 
> The biggest problem I see so far is that the assumption that there's just
> one Window object per Document is baked really deep into the spec. There's
> 900+ mentions of Window in the spec, every one of which would have to be
> audited and probably significantly edited.
> 
> One option is to just mention this problem in the Security section, but
> that's pretty awful.

I'm not convinced it's such a big change. Can't we just let "a Window Object" continue to refer to what it means now, and introduce the terminology of "a non-native Window Object"?

Anyway, before doing any massive surgery on the spec, it seems like we should first:

(1) Come up with a very clear a concise plan of what we're proposing (similar to comment 119, but with all subsequent feedback addressed, especially comment 122). If we don't want to keep pasting this in the bug, we could put it in a wiki or etherpad somewhere.

(2) Make sure that it matches the memory of the 3 people who were in the room.

(3) Get feedback/signoff from Travis (MSFT), who is now back from vacation and want to know what the status is here. Feedback from Apple would be good too.

(4) Write tests in some sort of easily-sharable format.

(5) Have the implementors take a look at the tests and what it will take to pass them. If the proposal is as I remember, I'm pretty sure I can make Gecko pass those with pretty quick turnaround.

(6) Once we've ironed out any kinks, figure out how to make it all fit elegantly into the spec.

Does that sound ok? If so, Hixie, can you do (1)?

What's the best way to do (3)?
Comment 125 Simon Pieters 2014-01-20 13:37:27 UTC
(In reply to Bobby Holley (:bholley) from comment #124)
> (4) Write tests in some sort of easily-sharable format.

web-platform-tests (testharness.js + wpt-serve) seems like a good fit.
Comment 126 Travis Leithead [MSFT] 2014-01-20 19:03:19 UTC
(In reply to Bobby Holley (:bholley) from comment #124)
> (3) Get feedback/signoff from Travis (MSFT), who is now back from vacation
> and want to know what the status is here. Feedback from Apple would be good
> too.

I'm watching this thread (more closely now then I was in the past months...), so we can continue with this dialog here in the bug. I would like to see a comprehensive summary though, to avoid having to piece it together based on the comment history :-)
Comment 127 Bobby Holley (:bholley) 2014-01-24 06:17:21 UTC
I'm working on a detailed writeup and comprehensive web-platform-tests. Stay tuned.
Comment 128 Bobby Holley (:bholley) 2014-01-25 01:34:35 UTC
Alright, so I've defined this stuff in pretty excruciating detail, largely in terms of ES Internal Methods. Here's my work-in-progress. Please comment! Doing so directly in the Etherpad is fine by me.

https://etherpad.mozilla.org/html5-cross-origin-objects

I've also written thorough w3c tests for everything above. No UA passes them at present. Before publishing them, I want to take a brief crack at getting Firefox to pass them, which will hopefully reveal any bugs in the tests before other people spend time trying to pass them.
Comment 129 Bobby Holley (:bholley) 2014-01-25 21:38:02 UTC
(In reply to Bobby Holley (:bholley) from comment #128)
> Before publishing them, I want to take a brief crack at getting
> Firefox to pass them, which will hopefully reveal any bugs in the tests
> before other people spend time trying to pass them.

To clarify, I plan to post them very soon, probably early next week. The issue is just that there are some large portions of various tests that currently never run at all on any of the UAs I have installed, because they fail some basic sanity checks (i.e. being able to access cross-origin properties with Object.getOwnPropertyDescriptor). I can fix most of these very easily in Gecko, and doing so has the potential to reveal bugs (i.e. misspelled variables) that occur later in the tests.

Put another way, the tests are 90% done, and I just didn't have the time or energy to finish them on friday evening. ;-)
Comment 130 Ian 'Hixie' Hickson 2014-01-28 23:34:58 UTC
Sounds good.
Comment 131 Bobby Holley (:bholley) 2014-01-29 03:35:17 UTC
Created attachment 1433 [details]
Tests. v1

OK, here's a reasonable first pass at the tests. It'll still take some work to support the behavior in Firefox, so I want to make sure we have a tentative consensus here.

David Bruant promised to review these - David, do you have time to take a look?
Comment 132 David Bruant 2014-01-29 09:03:50 UTC
(In reply to Bobby Holley (:bholley) from comment #131)
> David Bruant promised to review these - David, do you have time to take a
> look?
No later than today. This morning (Paris time) even maybe :-)
Comment 133 David Bruant 2014-01-29 14:58:04 UTC
(yet another long comment in this thread, yay!)
I'm mostly verifying coherence with the ES6 object model.
I lack proper experience understanding if this is web-compatible. Someone else will have to check that part.
I didn't run the tests, only read the code.

In the first part, I talk about test content and things that will affect conformance. In the second part I touch on more cosmetic aspects.

## Content

* Whitelist behavior
=> What if inside the iframe, I do "window.postMessage = {}"? What do I see in the parent?
=> is a test missing for location prop === 'href'? For symetry, whitelistedLocationProps might help

* getPrototypeOf/[[GetOwnProperty]]
=> To tell a coherent story from an ES point of view, all whitelisted properties (and location.replace) must be own properties. Is it the case? (not all whitelisted properties are tested)
=> Since tests below show Function.prototype for C.close, why forcing null and not Object.prototype for instance?

* [[SetPrototypeOf]]
=> maybe add tests for Object.setPrototypeOf if present.
=> From an ES point of view, throwing on [[SetPrototypeOf]], [[PreventExtensions]], etc. is only possible if location is an ES6 proxy (since it's reported as extensible).

* checkPropertyDescriptor
=> it expects properties to be non-enumerable. If they are not enumerable, they won't appear in the whitelist test behavior since props are traversed with for-in. Unless enumerability changes with cross-origin-ness?
=> configurable === false is a very strong commitment (see eternal invariants from [1]) from an ES perspective for a WindowProxy. It means, for instance, that the postMessage instance will remain even if the underlying Window instance changes.
Although awkward, to respect the invariants, we can have configurable=true with some of the expected behavior of configurable=false (throw on defineProperty, etc.)

* checkDefine function
=> copy/paste error, valueDesc used twice in assert_throws, accessorDesc not used (change error message accordingly too)

* [[OwnPropertyKeys]]
=> Not sure about toSource. Is it standard? Can it be relied on?
I think test harness has methods for array values equality.

* "Cross-origin functions get local Function.prototype" & "Cross-origin Window accessors get local Function.prototype"
=> This is possible only if getting C.close returns a ES6 Proxy (at least if the cross-origin context sees a proxy) as well as if the target of this proxy remains extensible. If this is a story everyone is comfortable with, some tests should be added to nail it down (decide of what happens when inside the iframe Object.preventExtensions(close) is attempted).

* assert_true(close != C.close, 'cross-origin Window functions get their own object');
=> Is it specific to cross-origin windows?
this is true also for B, also, no? close !== B.close?

* assert_true(close_B != C.close, 'different Window functions per-incumbent script settings object');
=>  maybe rename close_B with C_close_from_B
So if I followed well, that was the part that was mostly controversial in this thread?
a test for close !== close_B is lacking I feel. Or is it implicitely tested via the [[Prototype]] tests with Function.prototype and B.Function.prototype?

* doDocumentDomainTest
=> Just to be sure I'm not missing something, this is testing 2 things:
1) references within the iframe remain consistent after changing origin
2) references from outside the iframe remain consistent after changing origin
Is it enough? I have a gut feeling that no, but can't find something relevant to add yet.
Maybe that some cross-origin restrictions have been removed (like the inability to [[delete]] a property)?
Follow-up from above, if D.close is a proxy in the cross-origin case, it can't be === to D.close after the origin changed. Is it the intended behavior?



## Cosmetic

* instead of frameElement.uriToLoad, use data attributes (and dataList?)

* for (var p in whitelistedWindowProps)
whitelistedWindowProps is an array. Traverse with forEach, map and friends (for-in is for objects used as key->value maps).

* Be careful with typeof x === 'object' as it returns true for null. Does the test harness have an isObject helper function?
Object(x) === x is otherwise a safer test.

* [[Enumerate]]
=> "assert_true(false, (...)"
isn't there an assert_fail?

* checkFunction
=> Not sure f.name can be relied on with DOM built-in on different browsers. To be tested or the function can be changed to accept a label like checkPropertyDescriptor

* I worry about how these tests are run. They rely on top being the main test file. If they're run as iframes, they may fail in subtle ways. Maybe add a comment at the top of the test file to explain the proper conditions in which this test should be run as we're in one of the rare cases where it really matters.

* // The document.domain test is unavoidably side-effect-y, so we do it last.
=> Maybe they belong in their own file?

* is the E iframe used somewhere?
* Maybe add an F that is @sandbox="allow-scripts" (different origin)
* Maybe add a G that is @sandbox="allow-scripts allow-same-origin"


[1] https://mail.mozilla.org/pipermail/es-discuss/2011-May/014150.html
Comment 134 Ian 'Hixie' Hickson 2014-01-31 18:39:59 UTC
(In reply to David Bruant from comment #133)
> 
> * Whitelist behavior
> => What if inside the iframe, I do "window.postMessage = {}"? What do I see
> in the parent?

Assuming they're same-origin, you see {}.

If they're cross-origin, setting it on the non-native object should throw, because it's not configurable, and setting it on the native Window should have no effect on the cross-origin Window, since they're not the same object.


> * getPrototypeOf/[[GetOwnProperty]]
> => To tell a coherent story from an ES point of view, all whitelisted
> properties (and location.replace) must be own properties. Is it the case?
> (not all whitelisted properties are tested)

Also, how does this work for the frame names?


I'll let bholley and abarth respond to the rest, but I have to say, I still don't really understand why we're trying so hard to fit Window, WindowProxy, and Location into obeying the language invariants. I don't understand what negative consequences there are to making these specific highly magical objects be so magical that ES can't describe them generically.
Comment 135 Boris Zbarsky 2014-01-31 18:53:08 UTC
> I still don't really understand why we're trying so hard to fit Window,
> WindowProxy, and Location into obeying the language invariants.

Two reasons off the top of my head:

1)  A bunch of ES spec algorithms depend on these invariants and will go completely off the rails if applied to objects for which the invariants do not hold.

2)  Things like Caja depend on these invariants and will end up with security bugs if the invariants do not hold.

For both cases, it's better to lie and say things are configurable if they _might_ change even if in practice they never do than to lie and say things are not configurable and then have them change out from under code that doesn't expect them to.
Comment 136 David Bruant 2014-01-31 18:55:58 UTC
(In reply to Ian 'Hixie' Hickson from comment #134)
> (In reply to David Bruant from comment #133)
> > 
> > * Whitelist behavior
> > => What if inside the iframe, I do "window.postMessage = {}"? What do I see
> > in the parent?
> 
> If they're cross-origin, setting it on the non-native object should throw,
> because it's not configurable, and setting it on the native Window should
> have no effect on the cross-origin Window, since they're not the same object.
It looks like against the semantics of Bobby's document-domain tests (references don't change after an iframe becomes same-origin)


> I still don't really understand why we're trying so hard to fit Window,
> WindowProxy, and Location into obeying the language invariants.
> I don't understand what negative consequences there are to making these
> specific highly magical objects be so magical that ES can't describe them generically.
Among the reasons I care about it enables self-hostability. If browsers don't care about implementing things partly in JS, it gives authors an opportunity to fix a non-compliant browser. We've been needed that. Granted, this is much less a problem than it used to be, but who knows about future browsers.
Self-hostability also allow for faithful confinement via Caja or else (without having to rewrite the JS code or prevent the use of iframes).

The invariants allow for defensive code. They guarantee that when you succesfully freeze an object, new properties won't magically pop up creating a new communication channel among parties that shouldn't be talking to one another, etc.
Comment 137 Ian 'Hixie' Hickson 2014-01-31 21:34:24 UTC
Can you elaborate on #1? What algorithms will fail? Last time we talked about this, I don't recall we came up with any that actually fail in a meaningful way.

As far as Caja goes, if you're handing Caja code a WindowProxy or Location object, you've already lost so hard it's not even worth talking about. I don't see how this is relevant here.

Nobody's going to be able to patch cross-domain Window behaviour in a useful way regardless of whether we do all this with the precise invariants followed or not. I really don't see what that would concretely look like. Can you give an example of useful code that would be possible with these invariants met but not if these invariants are subtly broken for Window/WindowProxy and Location?

(In reply to David Bruant from comment #136)
> It looks like against the semantics of Bobby's document-domain tests
> (references don't change after an iframe becomes same-origin)

Can you elaborate on this? References to what, when what about an iframe changes origin? (There's about 6 possible origins involved with an iframe. :-) )
Comment 138 Boris Zbarsky 2014-01-31 22:03:20 UTC
> What algorithms will fail? 

I'm pretty sure correct functioning of Proxy relies on the target object enforcing the invariants....
Comment 139 David Bruant 2014-01-31 23:26:06 UTC
(In reply to Ian 'Hixie' Hickson from comment #137)
> As far as Caja goes, if you're handing Caja code a WindowProxy or Location
> object, you've already lost so hard it's not even worth talking about.
How so? In the current state of affairs, I agree, but if they comply to Bholley's tests (modulo a few tweaks), "cajoled" (confined) code won't be able to do anything harmful with them.

> I don't see how this is relevant here.
I was more thinking that if WindowProxy can be expressed in ES semantics, Caja can provide a compliant emulation of it to cajoled code even if the browser doesn't comply itself.


> Nobody's going to be able to patch cross-domain Window behaviour in a useful
> way regardless of whether we do all this with the precise invariants
> followed or not. I really don't see what that would concretely look like.
> Can you give an example of useful code that would be possible with these
> invariants met but not if these invariants are subtly broken for
> Window/WindowProxy and Location?
In our case, the postMessage instance, described as non-configurable on a WindowProxy can change over time if the underlying Window changes. In ES code, it's expected that the value of a non-configurable data property doesn't change, so that the thing you get from window.postMessage once remains the same value.
You may have a WeakMap using "someIframe.contentWindow.postMessage" as key, expecting it won't change over time.
You may have written Object.freeze(someIframe.contentWindow.postMessage) at some point. Given postMessage is exposed as non-configurable, you would expect that the thing you acquire from "someIframe.contentWindow.postMessage" will always be frozen from this point on. Since the postMessage function can change over time, it's not the case.

Granted, these examples aren't very concrete. But they all relate to the idea of defensive code.
Some signals have been added to ES5 to basically say "if you do x and it doesn't throw or observe y, you're guaranteed of z". They're all related to non-configurability and non-extensibilty.
I wasn't there at the time, but from what I understand, they've been added after the experience of Caja trying to make JavaScript a securable language (note the "-able") without having to rewrite the source code to add runtime checks (which they did to secure ES3 code). That's actually fairly minimal, but that's what they need to write defensive code. Since the release of ES5, they haven't reported they missed a primitive to secure without rewriting.


> (In reply to David Bruant from comment #136)
> > It looks like against the semantics of Bobby's document-domain tests
> > (references don't change after an iframe becomes same-origin)
> 
> Can you elaborate on this? References to what, when what about an iframe
> changes origin? (There's about 6 possible origins involved with an iframe.
> :-) )
Bobby's tests do the following at the end:
1) inside a cross-domain iframe, save all indexed references of the parent.
2) in both parent and iframe, set document.domain to the same valid domain.
3) The checkWindowReferences makes sure that the indexed references after 2) === the ones saved at 1)
From this test, we can infer that all the WindowProxy objects remain the same objects whether they're found same or different origin. 
My understanding of your message is that you were saying the opposite. Did I misunderstand?
Comment 140 Bobby Holley (:bholley) 2014-02-01 00:21:27 UTC
(In reply to Ian 'Hixie' Hickson from comment #134)
> > * getPrototypeOf/[[GetOwnProperty]]
> > => To tell a coherent story from an ES point of view, all whitelisted
> > properties (and location.replace) must be own properties. Is it the case?
> > (not all whitelisted properties are tested)
> 
> Also, how does this work for the frame names?

Yeah, since these appear and disappear, presumably we have to claim that these properties are configurable (this is also why we have to claim that cross-origin objects are extensible). In fact, given that we return different property descriptors to different same-origin callers, their properties probably _always_ need to be reported as configurable (and we can just throw if somebody tries to actually configure them). I'll make a note to fix that.

> I'll let bholley and abarth respond to the rest, but I have to say, I still
> don't really understand why we're trying so hard to fit Window, WindowProxy,
> and Location into obeying the language invariants.

Caja stuff aside, I think this is hugely important for the feasibility of implementation. Different JS engines do different things, and we want to be as sure as possible that everyone can implement what we come up with. SpiderMonkey (and I'm sure V8 as well) is steadily aligning its API and internal semantics with ES, and so those are the hooks that I (as an embedder) have available to implement custom behavior.

We're kind of cheating with the post-document-domain identity stuff - it either requires overriding operator===, or having a security membrane (though the latter is arguably describable in ES). But it's a corner-case, and was the best that the three of us could come up with - so we're going to have to live with it. But for the 99%, it seems like we should use the ES object model to describe ES objects.

> I don't understand what
> negative consequences there are to making these specific highly magical
> objects be so magical that ES can't describe them generically.

Precisely describing something more magical than ES is a pretty tall order. Even if you can manage to specify everything that happens to be observable in current browsers, it'll all come tumbling down as soon as TC39 adds another Object.getSomeFancyThing method.

There are a lot of smart people who've spent an eternity coming up with a way to specify ES object behavior. I think it's pretty foolish to fork that work.

I'm out of time tonight, but will respond to the rest of the specific feedback here next week.
Comment 141 Ian 'Hixie' Hickson 2014-02-03 20:20:04 UTC
Consider the same-origin case. You have a frame A, containing an iframe B. B is navigated to page X, then navigated to page Y. While it's at page X, the page in A gets a reference to the WindowProxy for X's Window object, and it grabs a reference to postMessage's Function object on X. Then B navigates to page Y, and A does the same again. Those two references won't be ===, right? But they can be manipulated like any other Function object (they're same-origin, after all).

Then introduce page Z, which is cross-origin. B is navigated to Z. The same WindowProxy object now has to defer to Z's Window. Regardless of what else is going on, Z's Window's postMessage function isn't the same one as the one from X or the one from Y, right?

But you can still navigate back to Y or X, and then the same WindowProxy object had better return the same-origin stuff from X and Y.

I don't see how we can ever make WindowProxy sane. It just points to whatever the "current" Window is, and that will go from being a same-origin Window object with one behaviour to a cross-origin Window object with another wildly different behaviour.

I'm not objecting to it being defined in a way that fits ES semantics. It seems like a lost cause to me, and the gains seem minimal, but if it's possible...
Comment 142 Bobby Holley (:bholley) 2014-02-04 19:17:20 UTC
(In reply to David Bruant from comment #139)
> Bobby's tests do the following at the end:
> 1) inside a cross-domain iframe, save all indexed references of the parent.
> 2) in both parent and iframe, set document.domain to the same valid domain.
> 3) The checkWindowReferences makes sure that the indexed references after 2)
> === the ones saved at 1)
> From this test, we can infer that all the WindowProxy objects remain the
> same objects whether they're found same or different origin. 

The document.domain identity issues are definitely where we come closest to violating ES language invariants, I think. In the spec, we have "The === operator, when applied to two objects representing the same underlying concept, returns true (even after document.domain)", which obviously isn't ES-kosher. Gecko implements this with a membrane layer, FWIW. But nobody really wants to spend that much time worrying about document.domain. I'm somewhat willing to say "you don't get ES language invariants if you use document.domain", though I haven't really thought through all the implications of that.

(In reply to Ian 'Hixie' Hickson from comment #141)
> But you can still navigate back to Y or X, and then the same WindowProxy
> object had better return the same-origin stuff from X and Y.
> 
> I don't see how we can ever make WindowProxy sane.

So far I don't see any problem. The WindowProxy is just a proxy whose referent changes. As long as it always reports itself as extensible and never reports a non-configurable property, this is totally kosher.

It's possible that the weird semantics of WindowProxy will eventually require some changes on the ES side. But I think we should make an honest effort to respect the language invariants, and raise issues with TC39 where we can't.
Comment 143 David Bruant 2014-02-04 23:22:27 UTC
(In reply to Bobby Holley (:bholley) from comment #142)
> (In reply to David Bruant from comment #139)
> > Bobby's tests do the following at the end:
> > 1) inside a cross-domain iframe, save all indexed references of the parent.
> > 2) in both parent and iframe, set document.domain to the same valid domain.
> > 3) The checkWindowReferences makes sure that the indexed references after 2)
> > === the ones saved at 1)
> > From this test, we can infer that all the WindowProxy objects remain the
> > same objects whether they're found same or different origin. 
> 
> The document.domain identity issues are definitely where we come closest to
> violating ES language invariants, I think. In the spec, we have "The ===
> operator, when applied to two objects representing the same underlying
> concept, returns true (even after document.domain)"
oooooh.... I think I see it now... Trying to explain it with my own words:
* a script in the parent see their global object.
* a script in a different origin see the same object as their "parent".
* the fact that it's the "same" object can be observed via === after setting document.domain on both sides.
* However *before* setting document.domain, both context actually observe different objects (the iframe see a version with only whitelisted properties for instance)
For instance, both side would Object.getOwnPropertyDescriptor the same object, send one another their list via postMessage and notice the difference of what they observe.

I don't think there are tests for this behavior currently?

> which obviously isn't ES-kosher. Gecko implements this with a membrane layer, FWIW.
Alright... Crazy time now to demonstrate that the observed behavior may actually be expressible in ES6. Buckle up!
It's technically possible to implement transparent membranes in ES with proxies. Among other things, it's possible to implement wet/dry membranes [1] (some scripts only see wet objects, other scripts only see dry objects, but they can communicate via function calls, object properties, etc. transparently as if the membranes weren't here)
We can say the parent is dry, the iframe is wet (note: with the WindowProxy, it's two layers of proxies before the Window :-) #Composability). From this setup, we have the following:
* inside the parent, references are self-consistent (respecting tests in for loop in doDocumentDomainTest)
* inside the iframe, references are self consistent (respecting checkWindowReferences tests)
* The WindowProxy objects both context can look at are effectively !== but neither side can ever observe it, because each is "locked" in its side of the membrane.

To have the "same" WindowProxy object that look differently from the parent and from the iframe, the handler to WindowProxy objects isn't simply a transparent dry/wet handler; it enforces the rules that are being attempted to be codified in this bug (property whitelist, throw otherwise, etc.) on one side of the membrane only.
On document.domain being set to the same thing on both side, the handler changes its behavior to just be a transparent dry/wet membrane.

So I think the chosen semantics is expressible in pure ES terms (granted, with quite an overhead and elaborate design).
That was obviously just a thought exercise. I don't recommend standardizing nor ever implementing it like that (though from "Gecko implements this with a membrane layer", it looks Gecko already did implement it like that in a way...).
Standardizing and implementing invoking magic ("wilful violations") works as far as I'm concerned.


[1] see slide 27 of https://docs.google.com/file/d/0B9iYRsLxmdqUd1RsdHZtazliWmc/edit for the super-short version
Comment 144 Bobby Holley (:bholley) 2014-02-05 00:20:20 UTC
Finally found some time to go through and address David's feedback. Sorry for the delay here.

(In reply to David Bruant from comment #133)
> * Whitelist behavior
> => What if inside the iframe, I do "window.postMessage = {}"? What do I see
> in the parent?

Good catch. It depends on whether the iframe is same-origin or not. I've updated the etherpad spec to reflect that, and added tests.

> => is a test missing for location prop === 'href'?

It's true that we don't test invoking the setter. But doing that makes the tests complicated, because it causes us to navigate. I didn't see anything really crucial in there that wouldn't be covered by other stuff.

> For symetry, whitelistedLocationProps might help

It doesn't really make sense, because |replace| is the only cross-origin readable property, and |href| is the only cross-origin writable property. So we kinda jus have to special

> * getPrototypeOf/[[GetOwnProperty]]
> => To tell a coherent story from an ES point of view, all whitelisted
> properties (and location.replace) must be own properties. Is it the case?

Yes.

> (not all whitelisted properties are tested)

Which ones?

> => Since tests below show Function.prototype for C.close, why forcing null
> and not Object.prototype for instance?

Because the functions are just regular functions, and have different identities depending on who is accessing them. There's no identity relationship between the value the caller gets for |C.postMessage|, the value B sees for |parent.C.postMessage|, and the value C sees for |window.postMessage|. |Window| and |Location| have to compare equal (especially between same-origin observers)
 
> * [[SetPrototypeOf]]
> => maybe add tests for Object.setPrototypeOf if present.

Added (though untested at present).

> => From an ES point of view, throwing on [[SetPrototypeOf]],
> [[PreventExtensions]], etc. is only possible if location is an ES6 proxy
> (since it's reported as extensible).

Is there any meaningful reason this has to be the case?

> * checkPropertyDescriptor
> => it expects properties to be non-enumerable. If they are not enumerable,
> they won't appear in the whitelist test behavior since props are traversed
> with for-in. Unless enumerability changes with cross-origin-ness?

Yep, that's the reason. This stuff is normally enumerable, but we decided to make it non-enumerable for cross-origin objects. We still have to list them for [[GetOwnPropertyKeys]] though, so we could also just make them enumerable. It doesn't matter much.

> => configurable === false is a very strong commitment (see eternal
> invariants from [1]) from an ES perspective for a WindowProxy. It means, for
> instance, that the postMessage instance will remain even if the underlying
> Window instance changes.
> Although awkward, to respect the invariants, we can have configurable=true
> with some of the expected behavior of configurable=false (throw on
> defineProperty, etc.)

Totally agree. We really can't enforce configurable=false on any sort of WindowProxy, cross-origin or not. Updated the spec.

> * checkDefine function
> => copy/paste error, valueDesc used twice in assert_throws, accessorDesc not
> used (change error message accordingly too)

Good catch. Fixed.
 
> * [[OwnPropertyKeys]]
> => Not sure about toSource. Is it standard? Can it be relied on?
> I think test harness has methods for array values equality.

Sorry, bad habit from writing Gecko tests. Fixed.
 
> * "Cross-origin functions get local Function.prototype" & "Cross-origin
> Window accessors get local Function.prototype"
> => This is possible only if getting C.close returns a ES6 Proxy (at least if
> the cross-origin context sees a proxy) as well as if the target of this
> proxy remains extensible.

Hm, I'm not sure I agree. |C| has to be proxy-like, but |close| is just a regular function.

> * assert_true(close != C.close, 'cross-origin Window functions get their own
> object');
> => Is it specific to cross-origin windows?
> this is true also for B, also, no? close !== B.close?

Sure. I just wasn't testing the latter. I can add one.

> * assert_true(close_B != C.close, 'different Window functions per-incumbent
> script settings object');
> =>  maybe rename close_B with C_close_from_B

Honestly it seems like there's too much relevant information to encode in the variables names. :-(

> So if I followed well, that was the part that was mostly controversial in
> this thread?

Bingo. I'm impressed. :-)

> a test for close !== close_B is lacking I feel. Or is it implicitely tested
> via the [[Prototype]] tests with Function.prototype and B.Function.prototype?

I can add one.
 
> * instead of frameElement.uriToLoad, use data attributes (and dataList?)

I need to generate the URIs programatically, at which point I don't know if there's a functional difference between expandos and data attributes.
 
> * for (var p in whitelistedWindowProps)
> whitelistedWindowProps is an array. Traverse with forEach, map and friends
> (for-in is for objects used as key->value maps).

ok.

> * Be careful with typeof x === 'object' as it returns true for null. Does
> the test harness have an isObject helper function?
> Object(x) === x is otherwise a safer test.

It does not have one, but I'll add one locally in the form that you suggest.
 
> * [[Enumerate]]
> => "assert_true(false, (...)"
> isn't there an assert_fail?

I don't see one.

> * checkFunction
> => Not sure f.name can be relied on with DOM built-in on different browsers.
> To be tested or the function can be changed to accept a label like
> checkPropertyDescriptor

Eh, it's just cosmetic for the error message. I'll just do:

var name = f.name || '<missing name>';
 
> * I worry about how these tests are run. They rely on top being the main
> test file. If they're run as iframes, they may fail in subtle ways. Maybe
> add a comment at the top of the test file to explain the proper conditions
> in which this test should be run as we're in one of the rare cases where it
> really matters.

Ok, Added a comment.
 
> * // The document.domain test is unavoidably side-effect-y, so we do it last.
> => Maybe they belong in their own file?

Maybe. We can do that later or something.
 
> * is the E iframe used somewhere?

See the comment: "The last two are only used for the document.domain tests, in which D ceases to be same-origin with E and becomes same-origin with B."


> * Maybe add an F that is @sandbox="allow-scripts" (different origin)
> * Maybe add a G that is @sandbox="allow-scripts allow-same-origin"

We can add this at some point, but it feels out of scope to me right now.

From the etherpad:

> I call this a "reflected" property, since it reflects back its identity
> to the caller :)

IMO reflected is too overloaded of a term to use here. ;-)

>> If the property is a value-prop and whitelisted for reading,
>> return a property descriptor with |configurable| set to true,
>>|enumerable| set to false, |writable| set to false, and |value|
>> to a Caller-Appropriate Representation of the value.
> whos internal [[Prototype]] is Object.prototype of the incumbant script?) 

not really - it'll either be another cross-origin object (for which the prototype is null), or a same-origin object with normal prototype semantics.
Comment 145 Bobby Holley (:bholley) 2014-02-05 00:34:59 UTC
Alright, I've updated the tests.

Tests are here:
https://github.com/bholley/web-platform-tests/compare/submission;bholley

Proposed spec is here:
https://etherpad.mozilla.org/html5-cross-origin-objects

To make progress here, there are a few things that I want to leave out of scope for now:

(1) The finer points of speccing this wrt document.domain (I think that's likely to be a massive distraction)
(2) The intricacies of cross-origin named subframe access (since I think the ideal behavior here is still evolving, per [1]).

Hixie and David - Putting those things aside, do you think this is in a state that we can ask Microsoft and WebKit for feedback? Do you have any outstanding concerns?

[1] https://bugzilla.mozilla.org/show_bug.cgi?id=916945
Comment 146 Simon Pieters 2014-02-05 12:07:34 UTC
(In reply to Bobby Holley (:bholley) from comment #145)
> Alright, I've updated the tests.
> 
> Tests are here:
> https://github.com/bholley/web-platform-tests/compare/submission;bholley

It seems this isn't a pull request yet, right? Can you make it so, please?
Comment 147 David Bruant 2014-02-05 12:29:54 UTC
(In reply to Bobby Holley (:bholley) from comment #144)
> > * getPrototypeOf/[[GetOwnProperty]]
> > => To tell a coherent story from an ES point of view, all whitelisted
> > properties (and location.replace) must be own properties. Is it the case?
> 
> Yes.
> 
> > (not all whitelisted properties are tested)
> 
> Which ones?
I was referring to the [[GetOwnProperty]] tests where only C.close and C.top are being verified (for Window). But it's tested in the very first test :
assert_true(Object.prototype.hasOwnProperty.call(C, prop), "hasOwnProperty for " + prop)
My mistake.

> > => Since tests below show Function.prototype for C.close, why forcing null
> > and not Object.prototype for instance?
> 
> Because the functions are just regular functions, and have different
> identities depending on who is accessing them. There's no identity
> relationship between the value the caller gets for |C.postMessage|, the
> value B sees for |parent.C.postMessage|, and the value C sees for
> |window.postMessage|.
This is magic (especially that C.postMessage !== parent.C.postMessage given C === parent.C).
In "naïve" ES terms, a consequence of "The === operator, when applied to two objects representing  the same underlying concept, returns true" is that all parties seeing a concept also see the same functions (same object identity) for all the methods and objects attached to the concept.
Now that I think about it, I think starting a sentence with "The === operator, when applied to..." is dangerous because it can question everything that comes with object equality.
A less foot-gunny option would be to use the phrase "are the same objects". And that's an assertion that cannot change in time regardless of whatever happens in the runtime. It obviously comes with consequences when it comes to object properties.

Another option is to buy the dry/wet membranes thing (now that I think about it, pretty much every scenario of object in/equality can be explained via dry/wet membranes).


> > * [[SetPrototypeOf]]
> > => maybe add tests for Object.setPrototypeOf if present.
> 
> Added (though untested at present).
There is some activity in bug 885788; might arrive soon in Firefox. I think it's already in V8.

> > => From an ES point of view, throwing on [[SetPrototypeOf]],
> > [[PreventExtensions]], etc. is only possible if location is an ES6 proxy
> > (since it's reported as extensible).
> 
> Is there any meaningful reason this has to be the case?
ES objects cannot choose their behavior to built-ins. ES6 objects internal operations are regulated by specific semantics
http://people.mozilla.org/~jorendorff/es6-draft.html#sec-ordinary-object-internal-methods-and-internal-slots
Exception exists and are codified in the ES spec (Array [[GetOwnProperty]] for instance).
There used to be a place for "host objects" that could do literally everything they wanted (and that was the hole the whole DOM and browser APIs were fitting into). In ES6, host objects are gone. The only way to do something different from the ordinary objects semantics is to use proxies. And proxies are codified by the ES spec to respect a couple of constraints (ES invariants).

The meaningful reason is to keep everything exposed to an ECMAScript environment saner than what host objects have historically been. In a way it relates to your argument to "why care about ES-expressibility" about implementation feasability and hooks to JS engines.

From an author perspective, I think it's good if specs state clearly that some object is a proxy. If I read a spec and it doesn't say the object is a proxy, I know exactly what to expect from its internal operations and the algorithms that use these internal operations (Object.* functions, but also Array.prototype.* methods, JSON.stringify, soon promises with their thenable shit, etc.).
If the spec says an object is a proxy, then I can look at the details to understand how the object differs from ES ordinary objects semantics.

To be honest, when compared to no spec or host-object-magic-land, proxies in spec doesn't sound so bad. We might need WebIDL ways to express proxies.


> > * checkPropertyDescriptor
> > => it expects properties to be non-enumerable. If they are not enumerable,
> > they won't appear in the whitelist test behavior since props are traversed
> > with for-in. Unless enumerability changes with cross-origin-ness?
> 
> Yep, that's the reason. This stuff is normally enumerable, but we decided to
> make it non-enumerable for cross-origin objects. We still have to list them
> for [[GetOwnPropertyKeys]] though, so we could also just make them
> enumerable. It doesn't matter much.
Agreed. Enumerability is pretty much a dead feature anyway.
https://mail.mozilla.org/pipermail/es-discuss/2014-January/035638.html
It's only used to control for-in. And for-in will die in a world with for-of.
Control over enumerability was added in ES5 only to give authors the same level of control over properties that the JS engine internals have.
Note also that there are no ES invariants related to enumerability.


> > * "Cross-origin functions get local Function.prototype" & "Cross-origin
> > Window accessors get local Function.prototype"
> > => This is possible only if getting C.close returns a ES6 Proxy (at least if
> > the cross-origin context sees a proxy) as well as if the target of this
> > proxy remains extensible.
> 
> Hm, I'm not sure I agree. |C| has to be proxy-like, but |close| is just a
> regular function.
This is the same thing than postMessage above. C is the iframe global seen from the parent (so it === the window inside the iframe). Both inside and outside the iframe should see the same object when getting 'C.close'/'window.close'(outside/inside resp.) and Object.getPrototypeOf can only return a single value, so it has to be the Function.prototype from only one of the contexts.
I was "wrong" when suggesting proxies since it would lead to close being two objects depending on where it's obtained seen from (in cross-origin case).
Unless the behavior is explained via dry/wet membranes when thinking in ES terms and wilful violations in WHATWG spec.


> > * assert_true(close_B != C.close, 'different Window functions per-incumbent
> > script settings object');
> > =>  maybe rename close_B with C_close_from_B
> 
> Honestly it seems like there's too much relevant information to encode in
> the variables names. :-(
Yeah... I made a suggestion, but was aware that even that was lacking info.
I was just confused by "close_B", because it gave me the impression it was a close function from B (just reading at the variable name of course).

(In reply to Bobby Holley (:bholley) from comment #145)
> Hixie and David - Putting those things aside, do you think this is in a
> state that we can ask Microsoft and WebKit for feedback? Do you have any
> outstanding concerns?
Nothing major stands out. I think it's a right time to ask for feedback. Thanks a lot for the work on the tests, it does help a lot understanding the intended behavior!
Comment 148 Bobby Holley (:bholley) 2014-02-05 17:00:01 UTC
(In reply to Simon Pieters from comment #146)
> (In reply to Bobby Holley (:bholley) from comment #145)
> > Tests are here:
> > https://github.com/bholley/web-platform-tests/compare/submission;bholley
> 
> It seems this isn't a pull request yet, right? Can you make it so, please?

It seems like we shouldn't land the tests until the behavior they're testing actually ends up in the spec, right?
Comment 149 Bobby Holley (:bholley) 2014-02-05 17:16:35 UTC
(In reply to David Bruant from comment #147) 
> Now that I think about it, I think starting a sentence with "The ===
> operator, when applied to..." is dangerous because it can question
> everything that comes with object equality.
> A less foot-gunny option would be to use the phrase "are the same objects".
> And that's an assertion that cannot change in time regardless of whatever
> happens in the runtime. It obviously comes with consequences when it comes
> to object properties.

That the spec can be implemented with entirely separate objects is very important to abarth. During our meeting, we agreed to the principle that "No object is ever exposed to code running from a different origin".

So even if the observables end up being the same, I think it's important that we spec it this way, at least for now. If, down the line, we can find a way to spec it with proxies/membranes that maintain the same observables, so much the better.

 
> > > => From an ES point of view, throwing on [[SetPrototypeOf]],
> > > [[PreventExtensions]], etc. is only possible if location is an ES6 proxy
> > > (since it's reported as extensible).
> > 
> > Is there any meaningful reason this has to be the case?

> From an author perspective, I think it's good if specs state clearly that
> some object is a proxy.

Ok, sure. I've added a note to the spec indicating that these things should be considered to be proxies.

> > Hm, I'm not sure I agree. |C| has to be proxy-like, but |close| is just a
> > regular function.
> This is the same thing than postMessage above. C is the iframe global seen
> from the parent (so it === the window inside the iframe). Both inside and
> outside the iframe should see the same objectwhen getting
> 'C.close'/'window.close'(outside/inside resp.) and Object.getPrototypeOf can
> only return a single value, so it has to be the Function.prototype from only
> one of the contexts.

Er, the whole basis of this proposal is that different callers get different results. Assuming we make sure to mark these properties as non-configurable, how does this violate ES language invariants?

> (In reply to Bobby Holley (:bholley) from comment #145)
> > Hixie and David - Putting those things aside, do you think this is in a
> > state that we can ask Microsoft and WebKit for feedback? Do you have any
> > outstanding concerns?
> Nothing major stands out. I think it's a right time to ask for feedback.
> Thanks a lot for the work on the tests, it does help a lot understanding the
> intended behavior!

Ok, great! Thanks for your feedback :-)
Comment 150 Ian 'Hixie' Hickson 2014-02-05 19:26:12 UTC
Looks fine to me, insofar as I understand it. :-)
Comment 151 Travis Leithead [MSFT] 2014-02-05 22:22:52 UTC
Some bugs in your tests (couldn’t comment on github directly for some reason, so playing back my findings here):

Line 164: href should not be in the cross-domain allowable check—that’s why all the browser are failing that test.

Line 181: delete this line, you already have "prop" defined.
Comment 152 Travis Leithead [MSFT] 2014-02-05 22:31:23 UTC
Chrome/IE throw on attempted enumeration of the cross-domain window, either via for..in, getOwnPropertyNames() or keys().

Is there a reason why this should be allowed? Seems to make life more complicated given that allowing this might lead a developer to expect to use for..of and apply an iterator (for what scenario or purpose I cannot imagine)...
Comment 153 Bobby Holley (:bholley) 2014-02-05 22:45:45 UTC
(In reply to Travis Leithead [MSFT] from comment #151)
> (couldn’t comment on github directly for some
> reason, so playing back my findings here):

That's probably better anyway, since github stuff tends to be ephemeral.
 
> Line 164: href should not be in the cross-domain allowable check—that’s why
> all the browser are failing that test.

It's an accessor prop, right? It has a getter and a setter. Invoking the getter should throw, but I don't see any reason why looking up the property descriptor should throw.

> Line 181: delete this line, you already have "prop" defined.

Good catch! I'll fix that.

(In reply to Travis Leithead [MSFT] from comment #152)
> Chrome/IE throw on attempted enumeration of the cross-domain window, either
> via for..in, getOwnPropertyNames() or keys().
> 
> Is there a reason why this should be allowed? Seems to make life more
> complicated given that allowing this might lead a developer to expect to use
> for..of and apply an iterator (for what scenario or purpose I cannot
> imagine)...

The basic issue here is that throwing makes it a lot harder to write tests that verify that everything is kosher, because the tests will always get tripped up by the minefield of exceptions that tend to be subtly different between UAs. Removing various knee-jerk exceptions in my own attempt to get these tests to pass in Gecko revealed various subtle issues that I wouldn't have otherwise noticed.

UAs have historically been exception-happy on this stuff because it was underdefined and they wanted to be safe. We're defining it now (very carefully), and part of that is going to require making it possible to inspect these objects in predictable ways.
Comment 154 Travis Leithead [MSFT] 2014-02-05 22:58:08 UTC
(In reply to Bobby Holley (:bholley) from comment #153)
> (In reply to Travis Leithead [MSFT] from comment #151)
> > (couldn’t comment on github directly for some
> > reason, so playing back my findings here):
> 
> That's probably better anyway, since github stuff tends to be ephemeral.
>  
> > Line 164: href should not be in the cross-domain allowable check—that’s why
> > all the browser are failing that test.
> 
> It's an accessor prop, right? It has a getter and a setter. Invoking the
> getter should throw, but I don't see any reason why looking up the property
> descriptor should throw.

But you assert on line 85 that non-cross-domain accessible properties throw when requesting their property descriptors...
Comment 155 Bobby Holley (:bholley) 2014-02-05 23:07:54 UTC
(In reply to Travis Leithead [MSFT] from comment #154)
> > It's an accessor prop, right? It has a getter and a setter. Invoking the
> > getter should throw, but I don't see any reason why looking up the property
> > descriptor should throw.
> 
> But you assert on line 85 that non-cross-domain accessible properties throw
> when requesting their property descriptors...

Good point.

Thinking about it again, I think the correct reasoning is that cross-origin callers do have a legitimate reason to lookup the |href| property descriptor, because they're allowed to call the setter.

So I think in this case Object.getOwnPropertyDescriptor(xoWin.location, 'href') should succeed, and that it should have a setter, but no getter. If that doesn't work for some reason, I guess the next-best thing would be to have the getter throw. But the former seems better.
Comment 156 Simon Pieters 2014-02-06 14:34:04 UTC
(In reply to Bobby Holley (:bholley) from comment #148)
> It seems like we shouldn't land the tests until the behavior they're testing
> actually ends up in the spec, right?

Right, but making a pull request is not landing.

When you make a pull request, a critic review will be created automatically where people can put their comments. You can still make changes to the branch in response to comments.
Comment 157 Bobby Holley (:bholley) 2014-02-06 23:11:25 UTC
(In reply to Simon Pieters from comment #156)
> When you make a pull request, a critic review will be created automatically
> where people can put their comments. You can still make changes to the
> branch in response to comments.

Ok. Opened a PR.
Comment 158 Ian 'Hixie' Hickson 2014-02-21 21:06:56 UTC
How are things going? Are we going ahead with the proposal?
Comment 159 Bobby Holley (:bholley) 2014-02-21 21:08:35 UTC
(In reply to Ian 'Hixie' Hickson from comment #158)
> How are things going? Are we going ahead with the proposal?

Adam said he was going to take a look a couple of weeks ago - not sure what the status on that is.

Travis, do you have any other feedback here? Does the proposal sound workable?

No word from WebKit.
Comment 160 Travis Leithead [MSFT] 2014-02-21 22:05:11 UTC
(In reply to Bobby Holley (:bholley) from comment #159)
> (In reply to Ian 'Hixie' Hickson from comment #158)
> > How are things going? Are we going ahead with the proposal?
> 
> Travis, do you have any other feedback here? Does the proposal sound
> workable?

If the test suite represents what is in the proposal, than I believe I am on board. Separately, I ran through the test suite and identified the 6 or so bugs that IE needs to fix in order to pass the test suite--and each of the issues seem like legitimate things we either 1) already knew about and just have in our backlog or 2) are gaps where the spec'd behavior makes sense conceptually and we shouldn't have a problem implementing. (Of course, I reserve the right to have more feedback once we do start implementing, but from what I see right now, things look fine.
Comment 161 Bobby Holley (:bholley) 2014-02-22 00:30:00 UTC
(In reply to Travis Leithead [MSFT] from comment #160)
> If the test suite represents what is in the proposal, than I believe I am on
> board.

That's great to hear!

> Separately, I ran through the test suite and identified the 6 or so
> bugs that IE needs to fix in order to pass the test suite--and each of the
> issues seem like legitimate things we either 1) already knew about and just
> have in our backlog or 2) are gaps where the spec'd behavior makes sense
> conceptually and we shouldn't have a problem implementing. (Of course, I
> reserve the right to have more feedback once we do start implementing, but
> from what I see right now, things look fine.

Same story on the Gecko side. I've got patches that get us most of the way there, but I can't pass all the tests until we land a rewrite of our Window bindings, which should happen before the end of March. On a high level though, I think this is something we can do efficiently and securely.

Hixie - I sent mail a while back to Sam Weinig from WebKit, but never heard back. Any thoughts as to next steps?
Comment 162 Ian 'Hixie' Hickson 2014-02-24 18:39:46 UTC
I guess we should try shipping it, see if anything breaks.
Comment 163 Bobby Holley (:bholley) 2014-02-24 19:19:31 UTC
(In reply to Ian 'Hixie' Hickson from comment #162)
> I guess we should try shipping it, see if anything breaks.

Yes. Though ideally, we'd have some preliminary consensus before doing that. Though given Travis' support, and assuming Adam's still on board with what we discussed in SF, we're in relatively good shape here.

If there's no schedule slippage, this should land in Gecko around the end of Q1 (once we land the new WebIDL bindings for Window and Location). The bug to track is https://bugzilla.mozilla.org/show_bug.cgi?id=965898
Comment 164 Bobby Holley (:bholley) 2014-07-14 21:07:55 UTC
The relevant deps for this finally landed in Gecko (sorry it took way longer than predicted). I'll get on this in the next couple of days.
Comment 165 Bobby Holley (:bholley) 2014-08-07 04:42:08 UTC
(In reply to Bobby Holley (:bholley) from comment #164)
> The relevant deps for this finally landed in Gecko (sorry it took way longer
> than predicted). I'll get on this in the next couple of days.

This landed in Gecko a few weeks ago, and should ship in Firefox 34.
Comment 166 Ian 'Hixie' Hickson 2014-09-12 18:26:36 UTC
bholley: what's the verdict? Any compat issues?
Comment 167 Bobby Holley (:bholley) 2014-09-12 18:32:14 UTC
(In reply to Ian 'Hixie' Hickson from comment #166)
> bholley: what's the verdict? Any compat issues?

None yet. It's currently shipping on Aurora, and should hit release on November 24th.
Comment 168 Bobby Holley (:bholley) 2014-09-13 00:50:18 UTC
(In reply to Bobby Holley (:bholley) from comment #167)
> (In reply to Ian 'Hixie' Hickson from comment #166)
> > bholley: what's the verdict? Any compat issues?
> 
> None yet. It's currently shipping on Aurora, and should hit release on
> November 24th.

Needless to say, I'm pretty confident that the code is going to ship without difficulty. If you have time to start polishing up the spec, I don't think it would be premature to do so.
Comment 169 Ian 'Hixie' Hickson 2014-09-15 21:57:07 UTC
Given the importance and subtlety of this issue, I'm going to wait until it's been shipping for a few days before making any spec changes.

Do you have a description of what it is you ended up shipping?
Comment 170 Bobby Holley (:bholley) 2014-09-16 10:41:02 UTC
(In reply to Ian 'Hixie' Hickson from comment #169)
> Given the importance and subtlety of this issue, I'm going to wait until
> it's been shipping for a few days before making any spec changes.

Ok.
 
> Do you have a description of what it is you ended up shipping?

See https://bugzilla.mozilla.org/show_bug.cgi?id=965898#c1 .
Comment 171 Bobby Holley (:bholley) 2014-11-27 01:25:02 UTC
This is now shipping. \o/

Ball is now in Hixie's court to update the spec.
Comment 172 Ian 'Hixie' Hickson 2014-11-28 04:18:48 UTC
Nice, this is shipping and there's no sign that the world has ended?
Comment 173 Bobby Holley (:bholley) 2014-11-28 07:02:24 UTC
(In reply to Ian 'Hixie' Hickson from comment #172)
> Nice, this is shipping and there's no sign that the world has ended?

Correct.

Exhaustive tests are also in web-platform-tests: https://github.com/w3c/web-platform-tests/tree/master/html/browsers/origin/cross-origin-objects
Comment 174 Ian 'Hixie' Hickson 2014-12-01 18:57:55 UTC
Alright! Let's move this back to my "active" list!
Comment 175 Kiks soto 2015-06-22 04:32:54 UTC
Comment on attachment 1433 [details]
Tests. v1

https://www.w3.org/Bugs/Public/attachment.cgi?id=1433
Comment 176 Kiks soto 2015-06-22 04:33:03 UTC
Comment on attachment 1433 [details]
Tests. v1

https://www.w3.org/Bugs/Public/attachment.cgi?id=1433
Comment 177 Kiks soto 2015-06-22 04:33:35 UTC
Comment on attachment 1433 [details]
Tests. v1

https://www.w3.org/Bugs/Public/attachment.cgi?id=1433
Comment 178 Kiks soto 2015-06-22 04:34:37 UTC
Comment on attachment 1433 [details]
Tests. v1

# HG changeset patch
# User Bobby Holley <bobbyholley@gmail.com>

Cross-origin object behavior.


diff --git a/html/browsers/origin/cross-origin-objects/MANIFEST b/html/browsers/origin/cross-origin-objects/MANIFEST
new file mode 100644
index 0000000..8c9039e
--- /dev/null
+++ b/html/browsers/origin/cross-origin-objects/MANIFEST
@@ -0,0 +1 @@
+cross-origin-objects.html
diff --git a/html/browsers/origin/cross-origin-objects/cross-origin-objects.html b/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
new file mode 100644
index 0000000..320a7be
--- /dev/null
+++ b/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
@@ -0,0 +1,329 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Cross-origin behavior of Window and Location</title>
+<link rel="author" title="Bobby Holley (:bholley)" href="bobbyholley@gmail.com">
+<link rel="help" href="http://www.whatwg.org/specs/web-apps/current-work/#security-window">
+<link rel="help" href="http://www.whatwg.org/specs/web-apps/current-work/#security-location">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe name="B"></iframe>
+<iframe name="C"></iframe>
+<iframe name="D"></iframe>
+<iframe name="E"></iframe>
+<script>
+
+/*
+ * Setup boilerplate. This gives us a same-origin window "B" and a cross-origin
+ * window "C".
+ */
+
+setup({explicit_done: true});
+path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html';
+B.frameElement.uriToLoad = path;
+C.frameElement.uriToLoad = 'http://www1.web-platform.test:' + location.port + path;
+
+// The last two are only used for the document.domain tests, in which D ceases to
+// be same-origin with E and becomes same-origin with B.
+D.frameElement.uriToLoad = 'http://www2.web-platform.test:' + location.port + path;
+E.frameElement.uriToLoad = 'http://www2.web-platform.test:' + location.port + path;
+
+function reloadSubframes(cb) {
+  var iframes = document.getElementsByTagName('iframe');
+  iframes.forEach = Array.prototype.forEach;
+  var count = 0;
+  function frameLoaded() {
+    if (++count == iframes.length) {
+      iframes.forEach(function(ifr) { ifr.onload = null; });
+      cb();
+    }
+  }
+  iframes.forEach(function(ifr) { ifr.onload = frameLoaded; ifr.setAttribute('src', ifr.uriToLoad); });
+}
+
+/*
+ * Note: we eschew assert_equals in a lot of these tests, since the harness ends
+ * up throwing when it tries to format a message involving a cross-origin object.
+ */
+
+var testList = [];
+function addTest(fun, desc) { testList.push([fun, desc]); }
+
+
+/*
+ * Basic sanity testing.
+ */
+
+addTest(function() {
+  assert_equals(location.host, 'web-platform.test:8000', 'Need to run the top-level test from web-platform.test:8000');
+  assert_equals(B.parent, window, "window.parent works same-origin");
+  assert_equals(C.parent, window, "window.parent works cross-origin");
+  assert_equals(B.location.pathname, path, "location.href works same-origin");
+  assert_throws(null, function() { C.location.pathname; }, "location.pathname throws cross-origin");
+}, "Basic sanity-checking");
+
+/*
+ * Whitelist behavior.
+ *
+ * Also tests for [[GetOwnProperty]] and [[HasOwnProperty]] behavior.
+ */
+
+var whitelistedWindowProps = ['location', 'postMessage', 'window', 'frames', 'self', 'top', 'parent',
+                              'opener', 'closed', 'close', 'blur', 'focus', 'length'];
+addTest(function() {
+    for (var prop in window) {
+      if (whitelistedWindowProps.indexOf(prop) != -1) {
+        C[prop]; // Shouldn't throw.
+        Object.getOwnPropertyDescriptor(C, prop); // Shouldn't throw.
+        assert_true(Object.prototype.hasOwnProperty.call(C, prop), "hasOwnProperty for " + prop);
+      } else {
+        assert_throws(null, function() { C[prop]; }, "Should throw when accessing " + prop + " on Window");
+        assert_throws(null, function() { Object.getOwnPropertyDescriptor(C, prop); },
+                      "Should throw when accessing property descriptor for " + prop + " on Window");
+        assert_throws(null, function() { Object.prototype.hasOwnProperty.call(C, prop); },
+                      "Should throw when invoking hasOwnProperty for " + prop + " on Window");
+      }
+      if (prop != 'location')
+        assert_throws(null, function() { C[prop] = undefined; }, "Should throw when writing to " + prop + " on Window");
+    }
+    for (var prop in location) {
+      if (prop == 'replace') {
+        C.location[prop]; // Shouldn't throw.
+        Object.getOwnPropertyDescriptor(C.location, prop); // Shouldn't throw.
+        assert_true(Object.prototype.hasOwnProperty.call(C.location, prop), "hasOwnProperty for " + prop);
+      }
+      else {
+        assert_throws(null, function() { C[prop]; }, "Should throw when accessing " + prop + " on Location");
+        assert_throws(null, function() { Object.getOwnPropertyDescriptor(C, prop); },
+                      "Should throw when accessing property descriptor for " + prop + " on Location");
+        assert_throws(null, function() { Object.prototype.hasOwnProperty.call(C, prop); },
+                      "Should throw when invoking hasOwnProperty for " + prop + " on Location");
+      }
+      if (prop != 'href')
+        assert_throws(null, function() { C[prop] = undefined; }, "Should throw when writing to " + prop + " on Location");
+    }
+}, "Only whitelisted properties are accessible cross-origin");
+
+/*
+ * ES Internal Methods.
+ */
+
+/*
+ * [[GetPrototypeOf]]
+ */
+addTest(function() {
+  assert_true(Object.getPrototypeOf(C) === null, "cross-origin Window proto is null");
+  assert_true(Object.getPrototypeOf(C.location) === null, "cross-origin Location proto is null");
+}, "[[GetPrototypeOf]] should return null");
+
+/*
+ * [[SetPrototypeOf]]
+ */
+addTest(function() {
+  assert_throws(null, function() { C.__proto__ = new Object(); }, "proto set on cross-origin Window");
+  assert_throws(null, function() { C.location.__proto__ = new Object(); }, "proto set on cross-origin Location");
+  var protoSetter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set;
+  assert_throws(null, function() { protoSetter.call(C, new Object()); }, "proto setter |call| on cross-origin Window");
+  assert_throws(null, function() { protoSetter.call(C.location, new Object()); }, "proto setter |call| on cross-origin Location");
+}, "[[SetPrototypeOf]] should throw");
+
+/*
+ * [[IsExtensible]]
+ */
+addTest(function() {
+  assert_true(Object.isExtensible(C), "cross-origin Window should be extensible");
+  assert_true(Object.isExtensible(C.location), "cross-origin Location should be extensible");
+}, "[[IsExtensible]] should return true for cross-origin objects");
+
+/*
+ * [[PreventExtensions]]
+ */
+addTest(function() {
+  assert_throws(null, function() { Object.preventExtensions(C) },
+                "preventExtensions on cross-origin Window should throw");
+  assert_throws(null, function() { Object.preventExtensions(C.location) },
+                "preventExtensions on cross-origin Location should throw");
+}, "[[PreventExtensions]] should throw for cross-origin objects");
+
+/*
+ * [[GetOwnProperty]]
+ */
+
+addTest(function() {
+  assert_true(typeof Object.getOwnPropertyDescriptor(C, 'close') == 'object', "C.close is |own|");
+  assert_true(typeof Object.getOwnPropertyDescriptor(C, 'top') == 'object', "C.top is |own|");
+  assert_true(typeof Object.getOwnPropertyDescriptor(C.location, 'href') == 'object', "C.location.href is |own|");
+  assert_true(typeof Object.getOwnPropertyDescriptor(C.location, 'replace') == 'object', "C.location.replace is |own|");
+}, "[[GetOwnProperty]] - Properties on cross-origin objects should be reported |own|");
+
+function checkPropertyDescriptor(desc, propName, expectWritable) {
+  assert_equals(typeof desc, 'object', "property descriptor for " + propName + " should exist");
+  assert_equals(desc.enumerable, false, "property descriptor for " + propName + " should be non-enumerable");
+  assert_equals(desc.configurable, false, "property descriptor for " + propName + " should be non-configurable");
+  if ('value' in desc)
+    assert_equals(desc.writable, expectWritable, "property descriptor for " + propName + " should have writable: " + expectWritable);
+  else
+    assert_equals('set' in desc, expectWritable,
+                  "property descriptor for " + propName + " should " + (expectWritable ? "" : "not ") + "have setter");
+}
+
+addTest(function() {
+    for (var p in whitelistedWindowProps) {
+      var prop = whitelistedWindowProps[p];
+      var desc = Object.getOwnPropertyDescriptor(C, prop);
+      checkPropertyDescriptor(desc, prop, prop == 'location');
+    }
+    checkPropertyDescriptor(Object.getOwnPropertyDescriptor(C.location, 'replace'), 'replace', false);
+    checkPropertyDescriptor(Object.getOwnPropertyDescriptor(C.location, 'href'), 'href', true);
+    assertFalse('get' in Object.getOwnPropertyDescriptor(C.location, 'href'), "Cross-origin location should have no href getter");
+}, "[[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly");
+
+/*
+ * [[Delete]]
+ */
+addTest(function() {
+    assert_throws(null, function() { delete C.location; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.parent; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.length; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.document; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.foopy; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.location.href; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.location.replace; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.location.port; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.location.foopy; }, "Can't delete cross-origin property");
+}, "[[Delete]] Should throw on cross-origin objects");
+
+/*
+ * [[DefineOwnProperty]]
+ */
+function checkDefine(obj, prop) {
+  var valueDesc = { configurable: false, enumerable: false, writable: false, value: 2 };
+  var accessorDesc = { configurable: false, enumerable: false, get: function() {} };
+  assert_throws(null, function() { Object.defineProperty(obj, prop, valueDesc); }, "Can't define cross-origin property " + prop);
+  assert_throws(null, function() { Object.defineProperty(obj, prop, valueDesc); }, "Can't define cross-origin property " + prop);
+}
+addTest(function() {
+    checkDefine(C, 'length');
+    checkDefine(C, 'parent');
+    checkDefine(C, 'location');
+    checkDefine(C, 'document');
+    checkDefine(C, 'foopy');
+    checkDefine(C.location, 'href');
+    checkDefine(C.location, 'replace');
+    checkDefine(C.location, 'port');
+    checkDefine(C.location, 'foopy');
+}, "[[DefineOwnProperty]] Should throw for cross-origin objects");
+
+/*
+ * [[Enumerate]]
+ */
+
+addTest(function() {
+    for (var prop in C)
+      assert_true(false, "Shouldn't have been able to enumerate " + prop + " on cross-origin Window");
+    for (var prop in C.location)
+      assert_true(false, "Shouldn't have been able to enumerate " + prop + " on cross-origin Location");
+}, "[[Enumerate]] should return an empty iterator");
+
+/*
+ * [[OwnPropertyKeys]]
+ */
+
+addTest(function() {
+  assert_equals(whitelistedWindowProps.sort().toSource(), Object.getOwnPropertyNames(C).sort().toSource(),
+                "Object.getOwnPropertyNames() gives the right answer for cross-origin Window");
+  assert_equals(['href', 'replace'].toSource(), Object.getOwnPropertyNames(C.location).sort().toSource(),
+                "Object.getOwnPropertyNames() gives the right answer for cross-origin Location");
+}, "[[OwnPropertyKeys]] should return all properties from cross-origin objects");
+
+addTest(function() {
+  assert_true(B.eval('parent.C') === C, "A and B observe the same identity for C's Window");
+  assert_true(B.eval('parent.C.location') === C.location, "A and B observe the same identity for C's Location");
+}, "A and B jointly observe the same identity for cross-origin Window and Location");
+
+function checkFunction(f, proto) {
+  assert_equals(typeof f, 'function', f.name + " is a function");
+  assert_equals(Object.getPrototypeOf(f), proto, f.name + " has the right prototype");
+}
+
+addTest(function() {
+  checkFunction(C.close, Function.prototype);
+  checkFunction(C.location.replace, Function.prototype);
+}, "Cross-origin functions get local Function.prototype");
+
+addTest(function() {
+  assert_equals(typeof Object.getOwnPropertyDescriptor(C, 'top'), 'object',
+                "Need to be able to use Object.getOwnPropertyDescriptor do this test");
+  checkFunction(Object.getOwnPropertyDescriptor(C, 'top').get, Function.prototype);
+  checkFunction(Object.getOwnPropertyDescriptor(C.location, 'href').set, Function.prototype);
+}, "Cross-origin Window accessors get local Function.prototype");
+
+addTest(function() {
+  checkFunction(close, Function.prototype);
+  assert_true(close != C.close, 'cross-origin Window functions get their own object');
+  var close_B = B.eval('top.C.close');
+  assert_true(close_B != C.close, 'different Window functions per-incumbent script settings object');
+  checkFunction(close_B, B.Function.prototype);
+
+  checkFunction(location.replace, Function.prototype);
+  assert_true(location.replace != C.location.replace, "cross-origin Location functions get their own object");
+  var replace_B = B.eval('top.C.location.replace');
+  assert_true(replace_B != C.location.replace, 'different Location functions per-incumbent script settings object');
+  checkFunction(replace_B, B.Function.prototype);
+}, "Same-origin observers get different functions for cross-origin objects");
+
+addTest(function() {
+  assert_equals(typeof Object.getOwnPropertyDescriptor(C, 'parent'), 'object',
+                "Need to be able to use Object.getOwnPropertyDescriptor do this test");
+  var get_self_parent = Object.getOwnPropertyDescriptor(window, 'parent').get;
+  var get_parent_A = Object.getOwnPropertyDescriptor(C, 'parent').get;
+  var get_parent_B = B.eval('Object.getOwnPropertyDescriptor(top.C, "parent").get');
+  assert_true(get_self_parent != get_parent_A, 'different Window accessors per-incumbent script settings object');
+  assert_true(get_parent_A != get_parent_B, 'different Window accessors per-incumbent script settings object');
+  checkFunction(get_self_parent, Function.prototype);
+  checkFunction(get_parent_A, Function.prototype);
+  checkFunction(get_parent_B, B.Function.prototype);
+
+  var set_self_href = Object.getOwnPropertyDescriptor(window.location, 'href').set;
+  var set_href_A = Object.getOwnPropertyDescriptor(C.location, 'href').set;
+  var set_href_B = B.eval('Object.getOwnPropertyDescriptor(top.C.location, "href").set');
+  assert_true(set_self_href != set_href_A, 'different Location accessors per-incumbent script settings object');
+  assert_true(set_href_A != set_href_B, 'different Location accessors per-incumbent script settings object');
+  checkFunction(set_self_href, Function.prototype);
+  checkFunction(set_href_A, Function.prototype);
+  checkFunction(set_href_B, B.Function.prototype);
+}, "Same-origin observers get different accessors for cross-origin objects");
+
+// The document.domain test is unavoidably side-effect-y, so we do it last.
+function doDocumentDomainTest(cb) {
+    window.onmessage = function() {
+      test(function() {
+        assert_true(D.checkWindowReferences(), "D's Window references are still self-consistent after document.domain");
+        for (var i = 0; i < window.length; ++i) {
+          assert_true(window[i] === D.windowReferences[i],
+                      "Window reference " + i + " consistent between globals after document.domain");
+          assert_true(window[i].location === D.locationReferences[i],
+                      "Location reference " + i + " consistent between globals after document.domain");
+        }
+      }, "Cross-origin object identity preserved across document.domain");
+      cb();
+    }
+    B.document.domain = B.document.domain;
+    document.domain = document.domain;
+    D.postMessage('', '*');
+}
+
+// We do a fresh load of the subframes for each test to minimize side-effects.
+// It would be nice to reload ourselves as well, but we can't do that without
+// disrupting the test harness.
+function runNextTest() {
+  var entry = testList.shift();
+  test(entry[0], entry[1]);
+  if (testList.length != 0)
+    reloadSubframes(runNextTest);
+  else
+    doDocumentDomainTest(done); // Side-effect-y. Do it last.
+}
+reloadSubframes(runNextTest);
+
+</script>
diff --git a/html/browsers/origin/cross-origin-objects/frame.html b/html/browsers/origin/cross-origin-objects/frame.html
new file mode 100644
index 0000000..c84a245
--- /dev/null
+++ b/html/browsers/origin/cross-origin-objects/frame.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+<head>
+<script>
+  // If we get a postMessage, we grab references to everything and set
+  // document.domain to trim off our topmost subdomain.
+  window.onmessage = function(evt) {
+    window.windowReferences = [];
+    window.locationReferences = [];
+    for (var i = 0; i < parent.length; ++i) {
+      windowReferences.push(parent[i]);
+      locationReferences.push(parent[i].location);
+    }
+    document.domain = document.domain.substring(document.domain.indexOf('.') + 1);
+    evt.source.postMessage('', '*');
+  }
+
+  function checkWindowReferences() {
+    for (var i = 0; i < parent.length; ++i) {
+      if (windowReferences[i] != parent[i])
+        throw new Error("Window references don't match for " + i + " after document.domain");
+      if (locationReferences[i] != parent[i].location)
+        throw new Error("Location references don't match for " + i + " after document.domain");
+    }
+    return true;
+  }
+</script>
+</head>
+<body>
+</body>
+</html>
-- 
1.8.4.3
Comment 179 Kiks soto 2015-06-22 04:35:07 UTC
Comment on attachment 1433 [details]
Tests. v1

# HG changeset patch
# User Bobby Holley <bobbyholley@gmail.com>

Cross-origin object behavior.


diff --git a/html/browsers/origin/cross-origin-objects/MANIFEST b/html/browsers/origin/cross-origin-objects/MANIFEST
new file mode 100644
index 0000000..8c9039e
--- /dev/null
+++ b/html/browsers/origin/cross-origin-objects/MANIFEST
@@ -0,0 +1 @@
+cross-origin-objects.html
diff --git a/html/browsers/origin/cross-origin-objects/cross-origin-objects.html b/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
new file mode 100644
index 0000000..320a7be
--- /dev/null
+++ b/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
@@ -0,0 +1,329 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Cross-origin behavior of Window and Location</title>
+<link rel="author" title="Bobby Holley (:bholley)" href="bobbyholley@gmail.com">
+<link rel="help" href="http://www.whatwg.org/specs/web-apps/current-work/#security-window">
+<link rel="help" href="http://www.whatwg.org/specs/web-apps/current-work/#security-location">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe name="B"></iframe>
+<iframe name="C"></iframe>
+<iframe name="D"></iframe>
+<iframe name="E"></iframe>
+<script>
+
+/*
+ * Setup boilerplate. This gives us a same-origin window "B" and a cross-origin
+ * window "C".
+ */
+
+setup({explicit_done: true});
+path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html';
+B.frameElement.uriToLoad = path;
+C.frameElement.uriToLoad = 'http://www1.web-platform.test:' + location.port + path;
+
+// The last two are only used for the document.domain tests, in which D ceases to
+// be same-origin with E and becomes same-origin with B.
+D.frameElement.uriToLoad = 'http://www2.web-platform.test:' + location.port + path;
+E.frameElement.uriToLoad = 'http://www2.web-platform.test:' + location.port + path;
+
+function reloadSubframes(cb) {
+  var iframes = document.getElementsByTagName('iframe');
+  iframes.forEach = Array.prototype.forEach;
+  var count = 0;
+  function frameLoaded() {
+    if (++count == iframes.length) {
+      iframes.forEach(function(ifr) { ifr.onload = null; });
+      cb();
+    }
+  }
+  iframes.forEach(function(ifr) { ifr.onload = frameLoaded; ifr.setAttribute('src', ifr.uriToLoad); });
+}
+
+/*
+ * Note: we eschew assert_equals in a lot of these tests, since the harness ends
+ * up throwing when it tries to format a message involving a cross-origin object.
+ */
+
+var testList = [];
+function addTest(fun, desc) { testList.push([fun, desc]); }
+
+
+/*
+ * Basic sanity testing.
+ */
+
+addTest(function() {
+  assert_equals(location.host, 'web-platform.test:8000', 'Need to run the top-level test from web-platform.test:8000');
+  assert_equals(B.parent, window, "window.parent works same-origin");
+  assert_equals(C.parent, window, "window.parent works cross-origin");
+  assert_equals(B.location.pathname, path, "location.href works same-origin");
+  assert_throws(null, function() { C.location.pathname; }, "location.pathname throws cross-origin");
+}, "Basic sanity-checking");
+
+/*
+ * Whitelist behavior.
+ *
+ * Also tests for [[GetOwnProperty]] and [[HasOwnProperty]] behavior.
+ */
+
+var whitelistedWindowProps = ['location', 'postMessage', 'window', 'frames', 'self', 'top', 'parent',
+                              'opener', 'closed', 'close', 'blur', 'focus', 'length'];
+addTest(function() {
+    for (var prop in window) {
+      if (whitelistedWindowProps.indexOf(prop) != -1) {
+        C[prop]; // Shouldn't throw.
+        Object.getOwnPropertyDescriptor(C, prop); // Shouldn't throw.
+        assert_true(Object.prototype.hasOwnProperty.call(C, prop), "hasOwnProperty for " + prop);
+      } else {
+        assert_throws(null, function() { C[prop]; }, "Should throw when accessing " + prop + " on Window");
+        assert_throws(null, function() { Object.getOwnPropertyDescriptor(C, prop); },
+                      "Should throw when accessing property descriptor for " + prop + " on Window");
+        assert_throws(null, function() { Object.prototype.hasOwnProperty.call(C, prop); },
+                      "Should throw when invoking hasOwnProperty for " + prop + " on Window");
+      }
+      if (prop != 'location')
+        assert_throws(null, function() { C[prop] = undefined; }, "Should throw when writing to " + prop + " on Window");
+    }
+    for (var prop in location) {
+      if (prop == 'replace') {
+        C.location[prop]; // Shouldn't throw.
+        Object.getOwnPropertyDescriptor(C.location, prop); // Shouldn't throw.
+        assert_true(Object.prototype.hasOwnProperty.call(C.location, prop), "hasOwnProperty for " + prop);
+      }
+      else {
+        assert_throws(null, function() { C[prop]; }, "Should throw when accessing " + prop + " on Location");
+        assert_throws(null, function() { Object.getOwnPropertyDescriptor(C, prop); },
+                      "Should throw when accessing property descriptor for " + prop + " on Location");
+        assert_throws(null, function() { Object.prototype.hasOwnProperty.call(C, prop); },
+                      "Should throw when invoking hasOwnProperty for " + prop + " on Location");
+      }
+      if (prop != 'href')
+        assert_throws(null, function() { C[prop] = undefined; }, "Should throw when writing to " + prop + " on Location");
+    }
+}, "Only whitelisted properties are accessible cross-origin");
+
+/*
+ * ES Internal Methods.
+ */
+
+/*
+ * [[GetPrototypeOf]]
+ */
+addTest(function() {
+  assert_true(Object.getPrototypeOf(C) === null, "cross-origin Window proto is null");
+  assert_true(Object.getPrototypeOf(C.location) === null, "cross-origin Location proto is null");
+}, "[[GetPrototypeOf]] should return null");
+
+/*
+ * [[SetPrototypeOf]]
+ */
+addTest(function() {
+  assert_throws(null, function() { C.__proto__ = new Object(); }, "proto set on cross-origin Window");
+  assert_throws(null, function() { C.location.__proto__ = new Object(); }, "proto set on cross-origin Location");
+  var protoSetter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set;
+  assert_throws(null, function() { protoSetter.call(C, new Object()); }, "proto setter |call| on cross-origin Window");
+  assert_throws(null, function() { protoSetter.call(C.location, new Object()); }, "proto setter |call| on cross-origin Location");
+}, "[[SetPrototypeOf]] should throw");
+
+/*
+ * [[IsExtensible]]
+ */
+addTest(function() {
+  assert_true(Object.isExtensible(C), "cross-origin Window should be extensible");
+  assert_true(Object.isExtensible(C.location), "cross-origin Location should be extensible");
+}, "[[IsExtensible]] should return true for cross-origin objects");
+
+/*
+ * [[PreventExtensions]]
+ */
+addTest(function() {
+  assert_throws(null, function() { Object.preventExtensions(C) },
+                "preventExtensions on cross-origin Window should throw");
+  assert_throws(null, function() { Object.preventExtensions(C.location) },
+                "preventExtensions on cross-origin Location should throw");
+}, "[[PreventExtensions]] should throw for cross-origin objects");
+
+/*
+ * [[GetOwnProperty]]
+ */
+
+addTest(function() {
+  assert_true(typeof Object.getOwnPropertyDescriptor(C, 'close') == 'object', "C.close is |own|");
+  assert_true(typeof Object.getOwnPropertyDescriptor(C, 'top') == 'object', "C.top is |own|");
+  assert_true(typeof Object.getOwnPropertyDescriptor(C.location, 'href') == 'object', "C.location.href is |own|");
+  assert_true(typeof Object.getOwnPropertyDescriptor(C.location, 'replace') == 'object', "C.location.replace is |own|");
+}, "[[GetOwnProperty]] - Properties on cross-origin objects should be reported |own|");
+
+function checkPropertyDescriptor(desc, propName, expectWritable) {
+  assert_equals(typeof desc, 'object', "property descriptor for " + propName + " should exist");
+  assert_equals(desc.enumerable, false, "property descriptor for " + propName + " should be non-enumerable");
+  assert_equals(desc.configurable, false, "property descriptor for " + propName + " should be non-configurable");
+  if ('value' in desc)
+    assert_equals(desc.writable, expectWritable, "property descriptor for " + propName + " should have writable: " + expectWritable);
+  else
+    assert_equals('set' in desc, expectWritable,
+                  "property descriptor for " + propName + " should " + (expectWritable ? "" : "not ") + "have setter");
+}
+
+addTest(function() {
+    for (var p in whitelistedWindowProps) {
+      var prop = whitelistedWindowProps[p];
+      var desc = Object.getOwnPropertyDescriptor(C, prop);
+      checkPropertyDescriptor(desc, prop, prop == 'location');
+    }
+    checkPropertyDescriptor(Object.getOwnPropertyDescriptor(C.location, 'replace'), 'replace', false);
+    checkPropertyDescriptor(Object.getOwnPropertyDescriptor(C.location, 'href'), 'href', true);
+    assertFalse('get' in Object.getOwnPropertyDescriptor(C.location, 'href'), "Cross-origin location should have no href getter");
+}, "[[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly");
+
+/*
+ * [[Delete]]
+ */
+addTest(function() {
+    assert_throws(null, function() { delete C.location; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.parent; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.length; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.document; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.foopy; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.location.href; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.location.replace; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.location.port; }, "Can't delete cross-origin property");
+    assert_throws(null, function() { delete C.location.foopy; }, "Can't delete cross-origin property");
+}, "[[Delete]] Should throw on cross-origin objects");
+
+/*
+ * [[DefineOwnProperty]]
+ */
+function checkDefine(obj, prop) {
+  var valueDesc = { configurable: false, enumerable: false, writable: false, value: 2 };
+  var accessorDesc = { configurable: false, enumerable: false, get: function() {} };
+  assert_throws(null, function() { Object.defineProperty(obj, prop, valueDesc); }, "Can't define cross-origin property " + prop);
+  assert_throws(null, function() { Object.defineProperty(obj, prop, valueDesc); }, "Can't define cross-origin property " + prop);
+}
+addTest(function() {
+    checkDefine(C, 'length');
+    checkDefine(C, 'parent');
+    checkDefine(C, 'location');
+    checkDefine(C, 'document');
+    checkDefine(C, 'foopy');
+    checkDefine(C.location, 'href');
+    checkDefine(C.location, 'replace');
+    checkDefine(C.location, 'port');
+    checkDefine(C.location, 'foopy');
+}, "[[DefineOwnProperty]] Should throw for cross-origin objects");
+
+/*
+ * [[Enumerate]]
+ */
+
+addTest(function() {
+    for (var prop in C)
+      assert_true(false, "Shouldn't have been able to enumerate " + prop + " on cross-origin Window");
+    for (var prop in C.location)
+      assert_true(false, "Shouldn't have been able to enumerate " + prop + " on cross-origin Location");
+}, "[[Enumerate]] should return an empty iterator");
+
+/*
+ * [[OwnPropertyKeys]]
+ */
+
+addTest(function() {
+  assert_equals(whitelistedWindowProps.sort().toSource(), Object.getOwnPropertyNames(C).sort().toSource(),
+                "Object.getOwnPropertyNames() gives the right answer for cross-origin Window");
+  assert_equals(['href', 'replace'].toSource(), Object.getOwnPropertyNames(C.location).sort().toSource(),
+                "Object.getOwnPropertyNames() gives the right answer for cross-origin Location");
+}, "[[OwnPropertyKeys]] should return all properties from cross-origin objects");
+
+addTest(function() {
+  assert_true(B.eval('parent.C') === C, "A and B observe the same identity for C's Window");
+  assert_true(B.eval('parent.C.location') === C.location, "A and B observe the same identity for C's Location");
+}, "A and B jointly observe the same identity for cross-origin Window and Location");
+
+function checkFunction(f, proto) {
+  assert_equals(typeof f, 'function', f.name + " is a function");
+  assert_equals(Object.getPrototypeOf(f), proto, f.name + " has the right prototype");
+}
+
+addTest(function() {
+  checkFunction(C.close, Function.prototype);
+  checkFunction(C.location.replace, Function.prototype);
+}, "Cross-origin functions get local Function.prototype");
+
+addTest(function() {
+  assert_equals(typeof Object.getOwnPropertyDescriptor(C, 'top'), 'object',
+                "Need to be able to use Object.getOwnPropertyDescriptor do this test");
+  checkFunction(Object.getOwnPropertyDescriptor(C, 'top').get, Function.prototype);
+  checkFunction(Object.getOwnPropertyDescriptor(C.location, 'href').set, Function.prototype);
+}, "Cross-origin Window accessors get local Function.prototype");
+
+addTest(function() {
+  checkFunction(close, Function.prototype);
+  assert_true(close != C.close, 'cross-origin Window functions get their own object');
+  var close_B = B.eval('top.C.close');
+  assert_true(close_B != C.close, 'different Window functions per-incumbent script settings object');
+  checkFunction(close_B, B.Function.prototype);
+
+  checkFunction(location.replace, Function.prototype);
+  assert_true(location.replace != C.location.replace, "cross-origin Location functions get their own object");
+  var replace_B = B.eval('top.C.location.replace');
+  assert_true(replace_B != C.location.replace, 'different Location functions per-incumbent script settings object');
+  checkFunction(replace_B, B.Function.prototype);
+}, "Same-origin observers get different functions for cross-origin objects");
+
+addTest(function() {
+  assert_equals(typeof Object.getOwnPropertyDescriptor(C, 'parent'), 'object',
+                "Need to be able to use Object.getOwnPropertyDescriptor do this test");
+  var get_self_parent = Object.getOwnPropertyDescriptor(window, 'parent').get;
+  var get_parent_A = Object.getOwnPropertyDescriptor(C, 'parent').get;
+  var get_parent_B = B.eval('Object.getOwnPropertyDescriptor(top.C, "parent").get');
+  assert_true(get_self_parent != get_parent_A, 'different Window accessors per-incumbent script settings object');
+  assert_true(get_parent_A != get_parent_B, 'different Window accessors per-incumbent script settings object');
+  checkFunction(get_self_parent, Function.prototype);
+  checkFunction(get_parent_A, Function.prototype);
+  checkFunction(get_parent_B, B.Function.prototype);
+
+  var set_self_href = Object.getOwnPropertyDescriptor(window.location, 'href').set;
+  var set_href_A = Object.getOwnPropertyDescriptor(C.location, 'href').set;
+  var set_href_B = B.eval('Object.getOwnPropertyDescriptor(top.C.location, "href").set');
+  assert_true(set_self_href != set_href_A, 'different Location accessors per-incumbent script settings object');
+  assert_true(set_href_A != set_href_B, 'different Location accessors per-incumbent script settings object');
+  checkFunction(set_self_href, Function.prototype);
+  checkFunction(set_href_A, Function.prototype);
+  checkFunction(set_href_B, B.Function.prototype);
+}, "Same-origin observers get different accessors for cross-origin objects");
+
+// The document.domain test is unavoidably side-effect-y, so we do it last.
+function doDocumentDomainTest(cb) {
+    window.onmessage = function() {
+      test(function() {
+        assert_true(D.checkWindowReferences(), "D's Window references are still self-consistent after document.domain");
+        for (var i = 0; i < window.length; ++i) {
+          assert_true(window[i] === D.windowReferences[i],
+                      "Window reference " + i + " consistent between globals after document.domain");
+          assert_true(window[i].location === D.locationReferences[i],
+                      "Location reference " + i + " consistent between globals after document.domain");
+        }
+      }, "Cross-origin object identity preserved across document.domain");
+      cb();
+    }
+    B.document.domain = B.document.domain;
+    document.domain = document.domain;
+    D.postMessage('', '*');
+}
+
+// We do a fresh load of the subframes for each test to minimize side-effects.
+// It would be nice to reload ourselves as well, but we can't do that without
+// disrupting the test harness.
+function runNextTest() {
+  var entry = testList.shift();
+  test(entry[0], entry[1]);
+  if (testList.length != 0)
+    reloadSubframes(runNextTest);
+  else
+    doDocumentDomainTest(done); // Side-effect-y. Do it last.
+}
+reloadSubframes(runNextTest);
+
+</script>
diff --git a/html/browsers/origin/cross-origin-objects/frame.html b/html/browsers/origin/cross-origin-objects/frame.html
new file mode 100644
index 0000000..c84a245
--- /dev/null
+++ b/html/browsers/origin/cross-origin-objects/frame.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+<head>
+<script>
+  // If we get a postMessage, we grab references to everything and set
+  // document.domain to trim off our topmost subdomain.
+  window.onmessage = function(evt) {
+    window.windowReferences = [];
+    window.locationReferences = [];
+    for (var i = 0; i < parent.length; ++i) {
+      windowReferences.push(parent[i]);
+      locationReferences.push(parent[i].location);
+    }
+    document.domain = document.domain.substring(document.domain.indexOf('.') + 1);
+    evt.source.postMessage('', '*');
+  }
+
+  function checkWindowReferences() {
+    for (var i = 0; i < parent.length; ++i) {
+      if (windowReferences[i] != parent[i])
+        throw new Error("Window references don't match for " + i + " after document.domain");
+      if (locationReferences[i] != parent[i].location)
+        throw new Error("Location references don't match for " + i + " after document.domain");
+    }
+    return true;
+  }
+</script>
+</head>
+<body>
+</body>
+</html>
-- 
1.8.4.3
Comment 180 Bobby Holley (:bholley) 2015-07-03 19:29:26 UTC
It's been 7 months since comment 174 - any update here, Hixie?
Comment 181 Anne 2015-08-28 07:26:43 UTC
Okay, so the state of the art here is:

https://etherpad.mozilla.org/html5-cross-origin-objects
https://github.com/w3c/web-platform-tests/tree/master/html/browsers/origin/cross-origin-objects

Bobby, is there anything else we would need to study to fix this? Is there any chance you would be willing to provide a pull request for the specification?
Comment 182 Bobby Holley (:bholley) 2015-09-14 22:29:29 UTC
(In reply to Anne from comment #181)
> Okay, so the state of the art here is:
> 
> https://etherpad.mozilla.org/html5-cross-origin-objects
> https://github.com/w3c/web-platform-tests/tree/master/html/browsers/origin/
> cross-origin-objects
> 
> Bobby, is there anything else we would need to study to fix this?

Nope, each of those should be independently enough to generate prose (though it would be much, much easier to generate prose from the first)

> Is there
> any chance you would be willing to provide a pull request for the
> specification?

I think it would make more sense for somebody accustomed to writing standards prose to write it, and for me to review it (rather than the converse).
Comment 183 Bobby Holley (:bholley) 2015-09-14 22:30:01 UTC
(Sorry for the lag there, was on PTO)
Comment 184 Anne 2015-10-16 09:24:38 UTC
https://bugzilla.mozilla.org/show_bug.cgi?id=1214375 suggests we also need to retain security checks for same-origin objects. https://lists.w3.org/Archives/Public/www-archive/2015Oct/thread.html#msg9 has some related discussion on what to do here.
Comment 185 Bobby Holley (:bholley) 2015-10-16 18:27:15 UTC
(In reply to Anne from comment #184)
> https://bugzilla.mozilla.org/show_bug.cgi?id=1214375 suggests we also need
> to retain security checks for same-origin objects.

To be clear, this applies only to Location, not Window.
Comment 186 jen1.6.15m 2016-01-23 09:44:45 UTC
Created attachment 1633 [details]
it is a crowdflower extention from crowdflower.com to do tasks with.

I need this attatchment for google chrome. pls fix it!