Relationship between globals, Realms, and global environment records

# Anne van Kesteren (9 years ago)

I thought these to be 1:1:1, but e.g. HTML defines the document.open() API html.spec.whatwg.org/multipage/webappapis.html#dom-document-open which replaces the global object. It's not currently defined in excruciating detail, but I suspect it will be at some point since content depends on this working.

Should that affect the Realm and global environment record?

# Boris Zbarsky (9 years ago)

On 11/22/14, 6:20 AM, Anne van Kesteren wrote:

Should that affect the Realm and global environment record?

Yes, imo.

# Anne van Kesteren (9 years ago)

I filed www.w3.org/Bugs/Public/show_bug.cgi?id=27419 to track this on the HTML side.

# Allen Wirfs-Brock (9 years ago)

There is definite a 1:1:1 relationship between a global object, realmRec, global environment record. And ES views those relationships are immutable. You can't just change the global object of a realmRec or give it a different global environment record after it has been initialized.

Note that the ES6 spec. defines InitializeFirstRealm that define how the first Realm gets created. Creating additional realms would have to perform a similar set of steps.

Subsequent to the ES6 spec. we will be defining a ES level Realm object and associated API that supports creation and initialization of additional realms.

# Domenic Denicola (9 years ago)

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Allen Wirfs-Brock

There is definite a 1:1:1 relationship between a global object, realmRec, global environment record.  And  ES views those relationships are immutable.  You can't just change the global object of a realmRec or give it a different global environment record after it has been initialized.

Which brings up the questions:

  1. Would it be possible to define realms (and possibly the other things) purely by attaching internal slots to the global objects?
  2. How elegant/awkward would that be? Both conceptually and in concrete terms of structuring the spec.

Ian has previously expressed concerns that things on the web platform that start out 1:1 often become no-longer 1:1, like windows and documents (which are strangely enough many-to-many). Enforcing this 1:1 relationship would be a good idea, whether we do it by consolidating concepts or otherwise.

# Anne van Kesteren (9 years ago)

On Tue, Nov 25, 2014 at 1:51 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

There is definite a 1:1:1 relationship between a global object, realmRec, global environment record. And ES views those relationships are immutable. You can't just change the global object of a realmRec or give it a different global environment record after it has been initialized.

How would you test whether all three of them have changed?

# Allen Wirfs-Brock (9 years ago)

On Nov 25, 2014, at 2:14 AM, Anne van Kesteren wrote:

On Tue, Nov 25, 2014 at 1:51 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

There is definite a 1:1:1 relationship between a global object, realmRec, global environment record. And ES views those relationships are immutable. You can't just change the global object of a realmRec or give it a different global environment record after it has been initialized.

How would you test whether all three of them have changed?

Changed in what sense and from what perspective?

Every ES function object is associated with a realm when the function object is created. That association can't be changed as it would violated internal invariants of the function. For example:

function f() {
     let gPO = Object.getPrototypeOf;
     let a = gPO( [ ] );
     doSomething();
     let b = gPO( [ ] );
     assert(a===b);

}

The "current realm" dynamically tracks the realm of the actively executing ES function. The only way to change the "current realm" is to transfer control (via call, return, throw) to a function that was created with a different realm association.

regarding

var document = someDocument.open(...);

perhaps you can model this by saying that every Document object is created in a new Realm. Then the open call is a cross-realm call that changes the current Realm to someDocument's realm. When open returns the current Realm is restored to the original caller's realm. document is just a property of the caller realm's global object so the assignment just changes the value of that property. Any subsequent method invocations on 'document' will be cross-realm calls into someDocument's realm.

# Boris Zbarsky (9 years ago)

On 11/25/14, 11:11 AM, Allen Wirfs-Brock wrote:

regarding

var document = someDocument.open(...);

perhaps you can model this by saying that every Document object is created in a new Realm.

Can you explain what problem you're trying to solve here, precisely? I'm not sure there's even a problem here, fwiw. But creating every Document object in a new Realm is not obviously web-compatible (or more precisely would require some pretty major gyrations around the transition from the initial about:blank to a same-origin document to make it web-compatible: the entire state of the Realm at the point of transition would need to be copied over to the new Realm. It's simpler to just use the same Realm).

# Allen Wirfs-Brock (9 years ago)

I'm simply trying to understand what what is meant when HTML talks about changing the global object/realm/etc. and how that can be reconciled with a rational language semantics such as exhibited by the function I presented.

# Anne van Kesteren (9 years ago)

On Tue, Nov 25, 2014 at 5:45 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

I'm simply trying to understand what what is meant when HTML talks about changing the global object/realm/etc.

onload = function() {
  self.x = "test"
  console.log(self.x) // "test"
  document.open("text/html")
  console.log(self.x) // undefined
}
# Domenic Denicola (9 years ago)

However, interestingly:

onload = function () {
  var oldSelf = self;
  self.x = "test";
  console.log(self.x); // "test" of course
  console.log(x); // "test"
  document.open("text/html");
  console.log(self.x); // undefined
  console.log(x); // "test" still
  console.log(self === oldSelf); // true
};

So, if I'm using my terms correctly, the "global environment record" contains the same variables, even though the global object has a completely new set of properties?

# Boris Zbarsky (9 years ago)

On 11/25/14, 11:45 AM, Allen Wirfs-Brock wrote:

I'm simply trying to understand what what is meant when HTML talks about changing the global object/realm/etc.

Ah. That part is simple.

Per current HTML spec (and the Gecko implementation) when document.open() is performed, a new Realm and global object (Window) is created. The WindowProxy involved is changed to point to the new global object. The new Window points to the same Document that the old Window pointed to.

and how that can be reconciled with a rational language semantics such as exhibited by the function I presented.

Again per current HTML spec existing functions don't change in any way. They continue to perform bareword lookups against the global that corresponds to their Realm. At least that's how I interpret the spec and how Gecko implements it; the spec itself is not very clear on this, because it doesn't actually define how WindowProxy works.

In any case, that's my take on the situation. ;)

# Boris Zbarsky (9 years ago)

On 11/25/14, 12:01 PM, Domenic Denicola wrote:

So, if I'm using my terms correctly, the "global environment record" contains the same variables, even though the global object has a completely new set of properties?

You just said "the global object".

There are two different global objects here, per spec. Let's call them X and Y (I considered B for "before" and A for "after", but that would be a bit confusing).

We start out with global X (a Window instance). The function statement is evaluated with global X, so the function that's created has the Realm of X as its Realm. This function is then assigned to the onload property of X.

The load event fires, and we call the function. It calls the "self" getter, which returns a WindowProxy. This is a proxy which is currently pointing to X. When we do self.x = "test", the set is forwarded along to X. When you do console.log(self.x) the get is forwarded along to X. When you do console.log(x), the "x" property is obviously found on X, since that's the global of the function.

Alright, now you do document.open(). This creates a new global Y and changes the WindowProxy we have stored in oldSelf to point to Y instead of X. Getting "self" from Y returns the same WindowProxy as it used to return, so self === oldSelf.

Now console.log(self.x) forwards the get to Y, which has no such property. console.log(x) is still looking up the property "x" on X, since the function we're in comes from that global, so it gets the value "test".

Everything here seems sane to me so far. Well, as sane as anything ever is when WindowProxy is involved.

# Allen Wirfs-Brock (9 years ago)

On Nov 25, 2014, at 8:53 AM, Anne van Kesteren wrote:

onload = function() {
 self.x = "test"
 console.log(self.x) // "test"
 document.open("text/html")
 console.log(self.x) // undefined
}

This example does tell me anything useful because 'self' is just a upbinding of the function.

Even if I assume that 'self' is a property of the global object, the example isn't interesting because the open function, if it was created in the same Realm as the function, would also have access to the same global object and could modify the object that is the value of its 'self' property.

# Allen Wirfs-Brock (9 years ago)

On Nov 25, 2014, at 9:07 AM, Boris Zbarsky wrote:

On 11/25/14, 11:45 AM, Allen Wirfs-Brock wrote:

I'm simply trying to understand what what is meant when HTML talks about changing the global object/realm/etc.

Ah. That part is simple.

Per current HTML spec (and the Gecko implementation) when document.open() is performed, a new Realm and global object (Window) is created. The WindowProxy involved is changed to point to the new global object. The new Window points to the same Document that the old Window pointed to.

and how that can be reconciled with a rational language semantics such as exhibited by the function I presented.

Again per current HTML spec existing functions don't change in any way. They continue to perform bareword lookups against the global that corresponds to their Realm. At least that's how I interpret the spec and how Gecko implements it; the spec itself is not very clear on this, because it doesn't actually define how WindowProxy works.

In any case, that's my take on the situation. ;)

Assuming that the global object is a WindowProxy then (and assuming the semantics of Window Proxy allows it) HTML can retarget the proxy however it wants. Under the ES semantics if a Realm is created is with such a proxy as its global object then its global environment record will also (immutably) reference the same proxy when doing name lookups. So by changing the target of the proxy you can change the observed global bindings of IdentifierReferences.

But there are many places (anywhere you see something like %foo% in the ES6 spec) where ES accesses built-ins directly from the current realm without doing a name resolution. That is why in the test function I provided I used [ ] to create Array instances. The prototype of such objects is %ArrayPrototype% from the function's realm, not the result of evaluating 'Array.prototype'.

# Domenic Denicola (9 years ago)

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Allen Wirfs-Brock

So by changing the target of the proxy you can change the observed global bindings of IdentifierReferences.

But that's not how browsers behave, as my email's test case shows.

# Boris Zbarsky (9 years ago)

On 11/25/14, 1:06 PM, Allen Wirfs-Brock wrote:

Assuming that the global object is a WindowProxy then

The global object is a Window. A WindowProxy is not the global object.

HTML can retarget the proxy however it wants.

Yep.

So by changing the target of the proxy you can change the observed global bindings of IdentifierReferences.

That's actually not a goal, and is not what's either specced or what Firefox implements.

The global is a Window. That's where global bindings live. The WindowProxy is a proxy that has a Window as target, and which Window it has as target can change. Various things like "window" and "self" and "top" and so forth return WindowProxy objects. In fact, it's not possible for script to get an explicit reference to a Window object, as currently specified.

# Allen Wirfs-Brock (9 years ago)

On Nov 25, 2014, at 10:47 AM, Domenic Denicola wrote:

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Allen Wirfs-Brock

So by changing the target of the proxy you can change the observed global bindings of IdentifierReferences.

But that's not how browsers behave, as my email's test case shows.

I know, I don't see how in you example to rationalize (using ES concepts) that 'self.x' evaluate to undefined and but 'x' evaluates to 'test'.

# Boris Zbarsky (9 years ago)

On 11/25/14, 2:02 PM, Allen Wirfs-Brock wrote:

I know, I don't see how in you example to rationalize (using ES concepts) that 'self.x' evaluate to undefined and but 'x' evaluates to 'test'.

"x" is doing a lookup on the global.

"self" is a proxy whose target is not that global but some other object.

# Allen Wirfs-Brock (9 years ago)

On Nov 25, 2014, at 11:06 AM, Boris Zbarsky wrote:

On 11/25/14, 2:02 PM, Allen Wirfs-Brock wrote:

I know, I don't see how in you example to rationalize (using ES concepts) that 'self.x' evaluate to undefined and but 'x' evaluates to 'test'.

"x" is doing a lookup on the global.

"self" is a proxy whose target is not that global but some other object.

OK, I think I got it.

You're saying that, for a Realm, the global object never changes, but that the (initial values) of 'window', 'self', 'document'(??) etc. are proxies (all the same one?) whose target can dynamically change.

So, yes, that seems to explain things. It also means that Anne's original question about "replacing the global" object seems to be relevant as the actual global object binding never changes.

# Domenic Denicola (9 years ago)

The only remaining issue is that ES specifies that this is the global object in script code, whereas in browsers this === window, i.e. it is not actually the global object but instead the WindowProxy.

# Boris Zbarsky (9 years ago)

On 11/25/14, 2:17 PM, Allen Wirfs-Brock wrote:

You're saying that, for a Realm, the global object never changes, but that the (initial values) of 'window', 'self', 'document'(??) etc. are proxies

So first of all, these are all accessor properties, so I'm not sure about "initial values".

But yes, the value returned by the getter for "window" and "self" is a proxy (and is the same proxy for both getters). The value returned by the getter for "document" is not a proxy.

(all the same one?) whose target can dynamically change.

Yes.

# Boris Zbarsky (9 years ago)

On 11/25/14, 2:23 PM, Domenic Denicola wrote:

The only remaining issue is that ES specifies that this is the global object in script code, whereas in browsers this === window, i.e. it is not actually the global object but instead the WindowProxy.

Yep. This is the one remaining problem with this whole setup.

Note that this is kinda needed for web compat no matter what we do with the document.open() case, by the way, because if people grab "this" and hand it out to some script outside the window in question and then navigate the window, the "some script" needs to end up having a proxy for the new global.

# Allen Wirfs-Brock (9 years ago)

On Nov 25, 2014, at 11:27 AM, Boris Zbarsky wrote:

Yep. This is the one remaining problem with this whole setup.

Note that this is kinda needed for web compat no matter what we do with the document.open() case, by the way, because if people grab "this" and hand it out to some script outside the window in question and then navigate the window, the "some script" needs to end up having a proxy for the new global.

It would be trivial to change the ES spec. to allow for the script this binding and global object to be set to independent value when a Realm is created.

Can anybody in ES land see any negative consequences to allowing this?

Note that the top-level this binding in a module has the value undefined. This means that at a module top level: 'window.x' access 'x' via the window proxy, 'x' accesses via the global object, and 'this.x' throws.

# Anne van Kesteren (9 years ago)

On Tue, Nov 25, 2014 at 8:48 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

It would be trivial to change the ES spec. to allow for the script this binding and global object to be set to independent value when a Realm is created.

Yeah, you said so in 2013 as well, yet nothing happened:

:-(

(That said, it's still unclear to me why we need to have three constructs to describe one object.)

# Allen Wirfs-Brock (9 years ago)

On Nov 25, 2014, at 11:57 AM, Anne van Kesteren wrote:

Yeah, you said so in 2013 as well, yet nothing happened:

Given that ES6 doesn't have a public Realm API it really doesn't make much difference. But if it will make you more comfortable I will make it explicit in the ES6 spec. that a Realm can be created where the this binding and the global object are different values.

(That said, it's still unclear to me why we need to have three constructs to describe one object.)

Architectural layering, separation of concerns, the single responsibility principle ...

They aren't all objects.

A Realm record, is a ES6 specification artifact that represents the complete the execution context of ES code. It is not an object and does not have object-like semantics. It is at a lower layer of of the ES specification and using it the complete semantics of ES execution can be defined without depending upon any real metacircularity. As a specification device, an implementation is free to represent it any way it wants. Even as hidden state on an object. However, the ES spec. intentionally does not associate any of the realm record state with a global object. To do so would make it a requirement that pass a reference to a global object implies availability (at some level) of the rest of the realm state.

ES environment records are used to define the semantics of lexical scoping within the ES language. One kind of environment record is a Global environment record that defines the global scope. Note that a global environment records includes more that just a reference to a global object. It also includes bindings for global lexical declarations (lets/const/class) which are global but not accessible as properties of the global objects. Environment records are also specification artifacts, it up to an implementation to decide how to represent environment records. Note that prior to ES5 the ES spec. actually required that objects be used at runtime to represent the scoping environment. This resulted in various undesirable (but technically required) quirks in the scoping semantics. ES5 eliminated these problems.

The global object is an actual object. It has to have ES object semantics, so it is at a higher level of the design than either realm records and or environment record.

# Boris Zbarsky (9 years ago)

On 11/25/14, 2:48 PM, Allen Wirfs-Brock wrote:

It would be trivial to change the ES spec. to allow for the script this binding and global object to be set to independent value when a Realm is created.

I think we do want this, but that's not enough, unfortunately. Consider this script, executed at toplevel:

Object.defineProperty(Object.prototype, "foo", { get: function() { return this; } });
var z = foo;

Is z a Window or a WindowProxy? In browsers, as far as I can tell, it's interoperably a WindowProxy. I put a testcase at web.mit.edu/bzbarsky/www/testcases/windowproxy/getter-on-proto-chain-root.html if people want to take a look. This wouldn't be covered by the "script this binding" bit, though, or would it?

# Anne van Kesteren (9 years ago)

On Wed, Nov 26, 2014 at 1:48 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Given that ES6 doesn't have a public Realm API it really doesn't make much difference.

Why not? Currently HTML overrides to make the existing setup work... That's the whole reason we wanted to change this in the first place, so that HTML has less reason to correct ES.