Event loops in navigated-away-from windows

# Boris Zbarsky (11 years ago)

Now that JS is growing an event loop, what should happen to it in navigated-away-from windows?

To make this concrete:

  1. Say I have an object that came from such a window and it has an Object.observe observer on it that mutates the property being observed.

    Should this continue to run forever ever after the window is navigated away from?

  2. Say someone runs this in a web page:

    (function f() Promise.resolve().then(f))()
    

    what should happen when the user navigates away from that web page and why?

# Anne van Kesteren (11 years ago)

On Sat, Sep 27, 2014 at 4:03 AM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

Now that JS is growing an event loop, what should happen to it in navigated-away-from windows?

I still don't understand why JavaScript needs to have the loop itself and can't just queue jobs for the Host to take care of. Given that the Host has a more complicated model, that would be much saner and wouldn't cause all this confusion of jobs vs microtasks vs tasks.

# Mark S. Miller (11 years ago)

What happens if someone runs

(function f() {setImmediate(f);})();

in a web page?

# Mark S. Miller (11 years ago)

What confusion is being caused? AFAICT, this change is causing only the clarification of things that were/are confusing, such as Boris' question.

# Anne van Kesteren (11 years ago)

On Sat, Sep 27, 2014 at 3:52 PM, Mark S. Miller <erights at google.com> wrote:

What happens if someone runs

(function f() {setImmediate(f);})();

in a web page?

As far as I can tell from developer.mozilla.org/en-US/docs/Web/API/Window.setImmediate that seems like a proprietary API from Microsoft. What would it do at a specification level?

# Anne van Kesteren (11 years ago)

On Sat, Sep 27, 2014 at 3:54 PM, Mark S. Miller <erights at google.com> wrote:

What confusion is being caused? AFAICT, this change is causing only the clarification of things that were/are confusing, such as Boris' question.

Well, I for one find it confusing that while HTML had a fairly worked out event loop concept, ECMAScript added another and now I somehow mentally need to integrate them. It would be way clearer if ECMAScript just queued tasks/jobs/microtasks to the Host so we'd keep a single concept of a loop.

# Mark S. Miller (11 years ago)

My intent was not to refer to a nonstd api. Instead, what happens with

(function f() {setTimeout(f, 0);})();

in a web page that is navigated away from?

# Anne van Kesteren (11 years ago)

On Sat, Sep 27, 2014 at 4:02 PM, Mark S. Miller <erights at google.com> wrote:

My intent was not to refer to a nonstd api. Instead, what happens with

(function f() {setTimeout(f, 0);})();

in a web page that is navigated away from?

Now we get to why having two loops is bad...

Tasks in a browsing context's event loop have an associated document. If that document is not fully active, those tasks are skipped when selecting the oldest task to run for that event loop. Meaning that if you navigate away the tasks for the document you navigated away from stop executing, until you navigate back to it.

html.spec.whatwg.org/multipage/webappapis.html#processing-model-9 covers this processing model.

# Boris Zbarsky (11 years ago)

On 9/27/14, 9:52 AM, Mark S. Miller wrote:

What happens if someone runs

 (function f() {setImmediate(f);})();

in a web page?

You get into dvcs.w3.org/hg/webperf/raw-file/tip/specs/setImmediate/Overview.html#processingmodel step 5, which waits forever (modulo back/forward caches) because the document is not fully active.

# Boris Zbarsky (11 years ago)

On 9/27/14, 10:02 AM, Mark S. Miller wrote:

My intent was not to refer to a nonstd api. Instead, what happens with

 (function f() {setTimeout(f, 0);})();

in a web page that is navigated away from?

See html.spec.whatwg.org/multipage/webappapis.html#processing-model-9 step 1 and also, if that were not enough, html.spec.whatwg.org/multipage/webappapis.html#dom-windowtimers-settimeout step 11, both of which refer to the concept of "fully active" documents.

# Ian Hickson (11 years ago)

On Sat, 27 Sep 2014, Anne van Kesteren wrote:

Well, I for one find it confusing that while HTML had a fairly worked out event loop concept, ECMAScript added another and now I somehow mentally need to integrate them. It would be way clearer if ECMAScript just queued tasks/jobs/microtasks to the Host so we'd keep a single concept of a loop.

Allen and I discussed how they should be integrated, and the long and short of it is that there's only one event loop; HTML just interrupts the ES6 loop at NextJob step 4 (the "implementation defined manner"), and resumes the HTML event loop, and when the HTML event loop needs to resume running code, it resumes the NextJob algorithm. That and a few other hooks ensures that all the jobs end up as tasks and all the ordering semantics are preserved.

The discussion was at: esdiscuss.org/topic/the-initialization-steps-for-web-browsers

I haven't yet done this in HTML because I'm waiting for Allen to make the changes he talked about in that thread; this is being tracked here: ecmascript#3138

Once that's done I can update HTML. (I don't want to update HTML before, because otherwise I'll have to do it twice.)

The HTML bug for this is: www.w3.org/Bugs/Public/show_bug.cgi?id=25981

I certainly wouldn't object to the ES spec's event loop algorithms being turned inside out (search for "RunCode" on the esdiscuss thread above for an e-mail where I propose this) but that would be purely an editorial change, it wouldn't change the implementations.

# Anne van Kesteren (11 years ago)

On Mon, Sep 29, 2014 at 8:18 PM, Ian Hickson <ian at hixie.ch> wrote:

I certainly wouldn't object to the ES spec's event loop algorithms being turned inside out (search for "RunCode" on the esdiscuss thread above for an e-mail where I propose this) but that would be purely an editorial change, it wouldn't change the implementations.

The proposed setup from Allen will start failing the moment ECMAScript wants something more complicated with its loop. At that point you'll have to propose another set of hacks to make the integration with HTML work again. And given this integration is so weird, I doubt implementations will match it as written. Seems more likely they'll implement the more straightforward alternative.

(Also, the proposed setup does seem to require exactly that kind of mental integration I was worried about. With HTML hijacking the ES loop to do its bidding.)

# Ian Hickson (11 years ago)

On Mon, 29 Sep 2014, Anne van Kesteren wrote:

The proposed setup from Allen will start failing the moment ECMAScript wants something more complicated with its loop. At that point you'll have to propose another set of hacks to make the integration with HTML work again. And given this integration is so weird, I doubt implementations will match it as written. Seems more likely they'll implement the more straightforward alternative.

(Also, the proposed setup does seem to require exactly that kind of mental integration I was worried about. With HTML hijacking the ES loop to do its bidding.)

Certainly editorially I would much rather have the "inside out" version of the spec hooks that I mentioned in my earlier e-mail, yes.

# David Bruant (11 years ago)

Le 29/09/2014 23:08, Anne van Kesteren a écrit :

The proposed setup from Allen will start failing the moment ECMAScript wants something more complicated with its loop.

How likely is this?

# Boris Zbarsky (11 years ago)

On 9/26/14, 10:03 PM, Boris Zbarsky wrote:

  1. Say someone runs this in a web page:
(function f() Promise.resolve().then(f))()

what should happen when the user navigates away from that web page and why?

Given the lack of response from other implementors, I guess we'll just implement whatever is simplest in Gecko for now...

# Adam Klein (11 years ago)

Sorry for my delay in responding. Can you say what is simplest for Gecko in this case? One of the reasons I didn't respond to the thread immediately is that from my perspective Chromium has fairly ill-defined behavior for navigated-away pages, due to the fact that they often die due to factors that can't be explained by the platform (e.g., their host process dies). So I'd be interested in what makes sense for Gecko and whether it might just happen to be compatible with what we do in Chromium/Blink/V8.

Also, for case (1), is this all happening within one frame, or is this a cross-frame example?

# Boris Zbarsky (11 years ago)

On 10/15/14, 1:10 PM, Adam Klein wrote:

Sorry for my delay in responding. Can you say what is simplest for Gecko in this case?

We're still deciding that, but probably something like this: since in Gecko every Promise is associated with a global from the point when it's created (this is the global whose Promise.prototype is used as the proto), we can hang off each Window the list of Promises for that window, and then when we decide the Window is no longer active we'd just mark all those promises as inactive as well, so they will not invoke any callbacks.

One of the reasons I didn't respond to the thread immediately is that from my perspective Chromium has fairly ill-defined behavior for navigated-away pages

Sure. Other browsers do as well.

due to the fact that they often die due to factors that can't be explained by the platform (e.g., their host process dies

That's not observable, really. The observable case is when someone is still holding references to objects in the navigated-away-from page and manipulating them, and in your case that would mean the process is very much alive.

Note that there are actually two somewhat interesting cases here: a page being navigated away from, and the browsing context the page is in being torn down entirely. Thing location.href set on an iframe for the former case, and the <iframe> being removed from the DOM for the latter case.

In my ideal world, the two cases would behave identically, of course.

Also, for case (1), is this all happening within one frame, or is this a cross-frame example?

The latter is more interesting to me personally, since I think it's the harder problem.

# Allen Wirfs-Brock (11 years ago)

On Oct 14, 2014, at 7:50 PM, Boris Zbarsky wrote:

On 9/26/14, 10:03 PM, Boris Zbarsky wrote:

  1. Say someone runs this in a web page:

(function f() Promise.resolve().then(f))()

what should happen when the user navigates away from that web page and why?

Given the lack of response from other implementors, I guess we'll just implement whatever is simplest in Gecko for now...

From an ECMAScript perspective we need to reason about this in terms of realms and vats and ES jobs.

As a reminder:

A Vat is what ECMA262 defines. It is a self contained ES environment/processor/object space that has a single execution thread that runs ECMAScript evaluation "jobs" to completion. A Vat has a set of job queues containing jobs that are waiting to execute. When the current job is completed another waiting job is started. A Vat is a serialization boundary. Object references (ie, pointers) can not be directly exchanged/shared between Vats. Some sort of serialization/proxy mechanism needs to be used to communicate between Vats.

A Realm represents an ECMAScript GlobalEnviroment/ global object and all functions object whose scope includes that set of globals. A Vat may contain multiple Realms. Object references may be freely exchanged between Realms within the same Vat.

So, let's see if we can describe the behavior of: (function f() Promise.resolve().then(f))() in these terms:

The above expression must be evaluated as part of some job running in a Vat V. The code of the expression must be associated with some Realm R within V. The function expression creates a new function object (let's call it f0) that is associated with Realm R this means that the reference to 'Promise' within its body will resolve to the Promise Global provided by Realm R. The immediate call starts evaluating the body of f0 as part of the current job. A new, already resolved (it's state is "fulfilled") promise (let's call it p0.0) is created. Invoking the 'then' method on p0.0 notices that p.0.0 is in the "fulfilled" state, so it enqueues on V's PromiseJobs job queue a PromiseReactionJob with f0 as its reaction function. Let's call that pending job J0 'then' returns a new promise, p0.1 as the result of the 'then' method. p0.1 is in the "pending" state. It is the promise that will be resolved when the PromiseReactoinJob created in step 4 completes. The returned reference to p0.1 is discarded, so no subsequent code can invoke methods on it and it has no PromiseReactions registered on it. So, when p0.1 is ultimately resolved, nothing will happen. The current invocation of f0 returns and execution of the current job continues to completion. A new job is selected from V's job queues and executed. This may occur 0 or more times until ultimately... ...eventually J0 is selection as the next job and execution of it starts J0, as a PromiseReactionJob, invokes f0 as its handler, this starts evaluation of the the body of f0 as part of the current job A new, already resolved (it's state is "fulfilled") promise (let's call it p1.0) is created. Invoking the 'then' method on p1.0 notices that p.1.0 is in the "fulfilled" state, so it enqueues on V's PromiseJobs job queue a PromiseReactionJob with f0 as its reaction function. Let's call that pending job J1. 'then' returns a new promise, p1.1 as the result of the 'then' method. p1.1 is in the "pending" state. It is the promise that will be resolved when the PromiseReactoinJob created in step 12 completes. The returned reference to p1.1 is discarded, so no subsequent code can invoke methods on it and it has no PromiseReactions registered on it. So, when p1.1 is ultimately resolved, nothing will happen. The current invocation of f0 initiated in step 10 returns and execution of the current PromiseReactionJob completes. There are no longer any reachable references to p0.1 so it may not be garbage collected. A new job is selected for execution from V's job queues. Eventually that will be J1. Execution continues in this manner (essentially looping steps 8-16 with new Jn jobs) as long as V continues to execute jobs.

So, the basic question becomes one how the browser maps web pages to Vats. If each pages get a separate Vat then the above loop would presumably terminate when the user navigates away. If several "web pages" share a Vat then the loop would continue as long as any of those pages remained active. If the entire browser environment was represent as one vat then the loop continues as long as the browser is running. If a single Vat is used for multiple pages, a browser also has flexibility in how (and if) it selects ES jobs for execution, so it might impose some additional metadata and management policies on the Jn jobs. For example, perhaps it could purge all Jn jobs if Realm R is destroyed.

So, it all comes back to what is the mapping between various browser concepts and the above ES concepts. BTW, I'm not saying that the ES model can't evolve to make it easier to describe browser semantics, but this is our starting point for discussion.

# Boris Zbarsky (11 years ago)

On 10/15/14, 3:05 PM, Allen Wirfs-Brock wrote:

The above expression must be evaluated as part of some job running in a Vat V. The code of the expression must be associated with some Realm R within V.

Correct.

  1. Execution continues in this manner (essentially looping steps 8-16 with new Jn jobs) as long as V continues to execute jobs.

Right, this is the behavior that's not really desirable in the context of the web.

So, the basic question becomes one how the browser maps web pages to Vats. If each pages get a separate Vat

They don't. Basically, same-origin web pages correspond to multiple Realms within a single Vat, since from their point of view direct object references exist between them.

Worse yet, different-origin web pages might be able to become same-origin (due to document.domain).

So in practice in a web browser a bunch of different web pages are all separate Realms in the same Vat.

If the entire browser environment was represent as one vat then the loop continues as long as the browser is running.

Yes, this is precisely the failure mode in Firefox right now.

For example, perhaps it could purge all Jn jobs if Realm R is destroyed.

You can't really "destroy" a realm while someone is holding references to functions from that realm, yes?

What Gecko does do is mark a realm that corresponds to a navigated-away-from web page (but NOT a web page loaded in an <iframe>

that is then removed from the DOM, for web compat reasons) as "inactive", and Web IDL callbacks that are backed by functions from such a Realm become no-ops. Functions in that Realm can still be called directly, but not via a Web IDL callback.

Other browsers do different things here.

So, it all comes back to what is the mapping between various browser concepts and the above ES concepts. BTW, I'm not saying that the ES model can't evolve to make it easier to describe browser semantics, but this is our starting point for discussion.

Sure.

# Allen Wirfs-Brock (11 years ago)

On Oct 15, 2014, at 12:14 PM, Boris Zbarsky wrote:

On 10/15/14, 3:05 PM, Allen Wirfs-Brock wrote:

...

So, the basic question becomes one how the browser maps web pages to Vats. If each pages get a separate Vat

They don't. Basically, same-origin web pages correspond to multiple Realms within a single Vat, since from their point of view direct object references exist between them.

Worse yet, different-origin web pages might be able to become same-origin (due to document.domain).

So in practice in a web browser a bunch of different web pages are all separate Realms in the same Vat.

Right, but in practice, for different-origin pages, don't you use various forms of serialization/proxying to provide Vat-like object reference isolation? If you do that, then it feels like multiple Vats that share a single execution thread. We didn't current have that concept in ES, but we probably could.

Don't multi-process (eg, process per tab, etc.) browsers in fact have multiple execution threads?

If the entire browser environment was represent as one vat then the loop continues as long as the browser is running.

Yes, this is precisely the failure mode in Firefox right now.

For example, perhaps it could purge all Jn jobs if Realm R is destroyed.

You can't really "destroy" a realm while someone is holding references to functions from that realm, yes?

What Gecko does do is mark a realm that corresponds to a navigated-away-from web page (but NOT a web page loaded in an <iframe> that is then removed from the DOM, for web compat reasons) as "inactive", and Web IDL callbacks that are backed by functions from such a Realm become no-ops. Functions in that Realm can still be called directly, but not via a Web IDL callback.

I intentionally used "destroy" as a ill defined, non-technical terms. What you describe above is along the lines of what I was thinking. A PromiseReactionJob that is associated with a marked Realm might plausibly by purged (perhaps by replacing its reaction function with a "Thrower", which would break the loop).

Regardless, it seems desirable to be able to describe what happens in terms of the specified Promise/Realm/Job semantics.

# Boris Zbarsky (11 years ago)

On 10/15/14, 3:45 PM, Allen Wirfs-Brock wrote:

Right, but in practice, for different-origin pages, don't you use various forms of serialization/proxying to provide Vat-like object reference isolation?

Maybe.

First, note that being "different-origin" is not a static property due to document.domain. In practice, things can start out different-origin but become same-origin, if their origins are "related enough". Or start same-origin and become different-origin.

Second, while Gecko does effectively have a proxy at each Realm boundary (including same-origin Realms), other UAs do not. As a result, in some UAs it's possible to get hold of an object that's not same-origin with you at this moment, if it or other objects related to it were same-origin with you in the past.

If you do that, then it feels like multiple Vats that share a single execution thread. We didn't current have that concept in ES, but we probably could.

That's true.

We do need to pin this down in some way, since this is all very much observable in terms of event ordering, at least for things that can reach each other, because it matters which things share event queues with which other things.

Don't multi-process (eg, process per tab, etc.) browsers in fact have multiple execution threads?

Yes.

So do some single-process browsers (e.g. Presto-based Opera did, I think).

A PromiseReactionJob that is associated with a marked Realm

Are PromiseReactionJobs associated with a Realm at all? If so, how is that Realm determined?

Regardless, it seems desirable to be able to describe what happens in terms of the specified Promise/Realm/Job semantics.

Agreed!

# Allen Wirfs-Brock (11 years ago)

On Oct 15, 2014, at 1:36 PM, Boris Zbarsky wrote:

On 10/15/14, 3:45 PM, Allen Wirfs-Brock wrote: ...

A PromiseReactionJob that is associated with a marked Realm

Are PromiseReactionJobs associated with a Realm at all? If so, how is that Realm determined?

Every PendingJob [1] has has a [[Realm]] field that is set to the realm of the active execution context when the job is enqueued [2]. For the PromiseResolutionJobs we are talking about that will be the realm of the 'then' method [3] that enqueues them. For the example I worked through that would be realm R, the realm of the active execution context when f0 is created in stop 1.

Allen

[1] people.mozilla.org/~jorendorff/es6-draft.html#sec-jobs-and-job-queues [2] people.mozilla.org/~jorendorff/es6-draft.html#sec-enqueuejob [3] people.mozilla.org/~jorendorff/es6-draft.html#sec-promise.prototype.then

# Boris Zbarsky (11 years ago)

On 10/15/14, 5:10 PM, Allen Wirfs-Brock wrote:

Every PendingJob [1] has has a [[Realm]] field that is set to the realm of the active execution context when the job is enqueued [2].

Ah, I see. That makes sense, thanks.

For the PromiseResolutionJobs we are talking about that will be the realm of the 'then' method [3] that enqueues them.

Yeah, agreed. That would certainly work in this case, in that we could just stop running jobs associated with such a realm.