domenic / promises-unwrapping

The ES6 promises spec, as per September 2013 TC39 meeting
1.23k stars 95 forks source link

The spec should define which incumbent globals and entry globals are used, especially when enqueueing tasks. #108

Open nikhilm opened 10 years ago

getify commented 10 years ago

I for one would like to have a clearer explanation of what this issue/assertion is about. I am a bit confused by the title.

anba commented 10 years ago

EnqeueTask is responsible for retaining the correct Realm. How incumbent and entry settings objects should be mapped to ExecutionContexts and Realms is a different issue. The Specification Styles thread on es-discuss did cover this topic, but I don't think a proper solution was found.

domenic commented 10 years ago

This is not a concept ECMAScript has; environments that have it will need to define it for themselves.

allenwb commented 10 years ago

Wait a minute, this is all precisely specified in the ES6 spec. When EnqueueTask is used to enqueue a task the the Realm of the current execution context is associated with the newly enqueued Task. The "current execution context" is almost always the context of the most recently invoked ES or built-in function (not abstract operations) and every such function is associated with a Realm when the function object is created.

There are only two places EnqueueTask is actually called in the ES6 promise spec. One place is from Promise.prototype.then, so the the Realm of the enqueued tasks will be the Realm of the 'then' method that did the enqueuing.

The other use of EnqueueMethod is in the TriggerPromiseReaction abstract operation. That is called by the FulfillPromise and RejectPromise abstract operations. These are only called by internally generated Reject and Resolve anonymous ES-builtin functions. Those functions are created by the CreateResolvingFunctions abstract operation which is called by the InitializePromise abstract operation which is called from the Promise constructor. So resolving functions will be associated with the same Realm as the Promise constructor that caused them to be created, and that is the Realm that will be used by TriggerPromiseReaction when it enqueues Tasks using those resolver functions.

bzbarsky commented 10 years ago

allenwb, realms and incumbent/entry globals are not the the same thing. For example, when typical script calls a function from another realm, the current realm changes by the entry global does not.

This issue is not talking about the realm. It's talking about the incumbent and entry globals.

We need some way for the web platform to define them as needed for these tasks.

bzbarsky commented 10 years ago

@domenic There is no way for the web platform to "define" anything here as things stand because ES completely specifies the control flow here here with no chance for the web platform to inject the necessary metadata. We need some sort of hooks to handle this.

@getify Please read http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#prepare-to-run-a-callback and in fact the entirety of http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#processing-model-6

allenwb commented 10 years ago

@bzbarsky So you are talking about something completely ifferent from Realms and the concept of a Realm specific Global object. For lack of a better term, I call it a browser controlled context. As long as you impose a membrane between such context you can have a lot of control because you aren't directly calling the target function.

But if you want to directly call a ES function then you are in the the world of ES semantics where every function is, at the time it is created, associated with a Realm which essentially means a specific global objects. ES functions and in particular built-in are not designed to have the global object change out from under them and much of the specification (of built-ins) depend upon that invariant.

bzbarsky commented 10 years ago

@allenwb no one is talking about realms changing anywhere. Again, this has absolutely nothing to do with realms.

I really wish people would just read the spec that Ian wrote and what I wrote about this on es-discuss in the past instead of making random assumptions...

Let me try again to explain the issue, for the umpteenth time. For compatibility reasons, some commonly used functions in the web platform need to behave differently based on who called them. This has nothing to do with their realm; it has to to with what's higher up the stack. When a function is handed over to some web platform thing that will invoke it later (sync or async, doesn't matter), currently the web platform is defined as capturing the stack information at the handoff point and then reinstates it before calling the function later.

So if I, for example, do setTimeout(someFunction, 0) then the caller information when the timer fires will be the same as if I had directly called someFunction().

This matches the behavior of, for example, passing a function as a callback to Array.prototype.map: when that function is invoked its stack fundamentally looks the way the stack of the call to map() does, since the map implementation itself is opaque from the point of view of the scripts involved.

The current setup in the web platform specs was sufficient until ES grew the ability to make async calls to functions itself, with no web platform bits involved in any way at all. At that point, we have several choices:

  1. We create some hooks for the web platform to store the stack information in whatever stores the function to call async and reinstate it before the function is called.
  2. We define that sort of behavior in ES directly. This probably requires moving some of the concepts involved from the whatwg spec into ES.
  3. We make no attempt to preserve the stack information across the async operation and leave it to the web platform to define what happens when there is no stack information present.

I'm not a big fan of option 3 (which is what we seem to de facto be doing right now) because it means that you get different behavior depending on whether you call a function directly or whether you call it as a promise resolution handler, say. That seems a bit undesirable to me.

Is the issue clearer now, at least?

Note that I don't think membranes can help with this issue, though it'd be somewhat interesting to see me proved wrong on that.

allenwb commented 10 years ago

@bzbarsky I think a point of potential confusion here is what you mean by "the stack". This actually isn't a concept we have in the ES6 spec. For example, I don't really know what you are talking about when you say that the stack at the time of a call from the Array map function to a callaback looks fundamentally the same as the stack at the time the map function was called. But there are a lot of things that are different between those two calls and what they return to, so I'm not sure what you are talking about as being fundamentally the same.

The reason we now have concepts such as Realms, execution contexts, environment records, etc. in the ES6 spec. is so we can talk more concretely about specific requirements.

We need to get more concrete about "stack information" you are talking about to see how in can be incorporated into the ES execution model. Hopefully, it can be done in a manner that isn't too specific to the web platform so that ES remains adaptable to other platform environments.

bzbarsky commented 10 years ago

This actually isn't a concept we have in the ES6 spec.

Yes, I'm aware.

so I'm not sure what you are talking about as being fundamentally the same.

The incumbent and entry settings object, which are maintained by the web platform in an out-of-band stack for various reasons. One of those reasons is precisely that ES didn't really have any hooks for doing that sort of thing.

So concretely (and again I have already sent all of this to the es-discuss list in the past!) there are two ways things in the web platform care about the stack:

  1. Some things, like setting location.href, need a base URI and for compat reasons they use the base URI of the document whose window was the "place where script was first entered". The precise definition of what it means to "first enter" script in various edge cases is what the entry settings object is about. But the short of it is that if a <script> in window A executes and at toplevel calls a function defined in window B that then sets window.location.href on window C, the base URI used is that of window A.
  2. Some things, like calling window.postMessage make security decisions based on the realm of the code that was running before the postMessage function was invoked. So if you have code in window A that calls window.postMessage() on window B, those security decisions will be made based on the realm of window A, not that of window B. Precisely defining "code that was running" in various edge cases is what the incumbent settings object is about.
anba commented 10 years ago

Does providing callbacks when EnqueueTask and NextTask are executed help you out? So similar to storing the current Realm in EnqueueTask and later creating an appropriate ExecutionContext in NextTask, the web platform defines that the current stack of script settings objects is saved in EnqueueTask and later restored in NextTask?

allenwb commented 10 years ago

On May 20, 2014, at 4:02 PM, Boris Zbarsky wrote:

This actually isn't a concept we have in the ES6 spec.

Yes, I'm aware.

so I'm not sure what you are talking about as being fundamentally the same.

The incumbent and entry settings object, which are maintained by the web platform in an out-of-band stack for various reasons. One of those reasons is precisely that ES didn't really have any hooks for doing that sort of thing.

So concretely (and again I have already sent all of this to the es-discuss list in the past!) there are two ways things in the web platform cares about the stack:

It's hard to keep track of these things scatter across too many DLs. It's probably best if we just put our heads together and figure out what sort support the web platform needs in the area.

allenwb commented 10 years ago

On May 21, 2014, at 2:33 AM, André Bargull wrote:

Does providing callbacks when EnqueueTask and NextTask are executed help you out? So similar to storing the current Realm in EnqueueTask and later creating an appropriate ExecutionContext in NextTask, the web platform defines that the current stack of script settings objects is saved in EnqueueTask and later restored in NextTask?

Sounds like a plausible approach. I wouldn't call them callbacks, but rather platform hooks or extension points similar to what we currently have in NextTask for choosing which Task to run next. I could easily add this to the ES6 spec. if it sounds like it is a sufficient foundation.

Allen

bzbarsky commented 10 years ago

@anba, I think that might be enough as long as the callbacks let us effectively save some state in the task. @bholley, does that sound right?

allenwb commented 10 years ago

@brbarsky I can just add a [[HostDefined]] field to the PendingTask Record

Of course this is all just spec. hand-wave and how it actually works will be up to each implementations hosting interface.

bzbarsky commented 10 years ago

@allenwb Sure, but at that point we can just have the whatwg spec cover the thing that needs to be done. And yes, actual implementations will need to do whatever they need to do.

bholley commented 10 years ago

Well, for entry script settings objects, this should all be pretty straightforward. Assuming we want this stuff to work the same as it does in HTML/WebIDL, then we just use the realm of the callable to pick the entry global when it's invoked. All of that can happen by just instrumenting NextTask.

To get incumbent globals right, you're correct that we'll need to instrument EnqueueTask and that we'll need to attach state to the callable to implement the Callback Context machinery from WebIDL: http://heycam.github.io/webidl/#idl-callback-function

Again, this is all assuming we want to be consistent with that stuff.

domenic commented 10 years ago

Are @allenwb's most recent updates to the ES spec sufficient for this purpose, @bzbarsky?

bzbarsky commented 10 years ago

I don't think it's quite enough. We need to clean up after step 10 (the Perform the abstract operation) step (e.g. pop things off the script settings stack). So just need one more host environment defined step there.

annevk commented 10 years ago

I would like @Hixie to sign off here as well as he'll end up doing the specification side of this merge.

Hixie commented 10 years ago

I find the ES6 spec to be impenetrable, to be honest. I've no idea what's going on there. So I can't really sign off or not sign off.

It would be helpful if someone could file a bug on me (http://whatwg.org/newbug) that described what I need to do to hook the Web platform into the ES6 spec's hooks so that ES6 triggers microtasks correctly.

As far as the script settings objects go, we presumably want promise invocation to work the same way as setTimeout() callback invocation, no? How that works is defined in WebIDL. Basically when the callback is set, WebIDL saves both the pointer to the code to call, and the incumbent script settings object (IIRC), and then when the callback is invoked, it calls HTML's hooks to reconfigure the stack accordingly. ES6 just needs to do the same thing, or at least give us the hooks to do so.