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 22185 - Microtask checkpoint wording is wrong -- and needs abstracting
Summary: Microtask checkpoint wording is wrong -- and needs abstracting
Status: RESOLVED FIXED
Alias: None
Product: WHATWG
Classification: Unclassified
Component: HTML (show other bugs)
Version: unspecified
Hardware: PC All
: P2 normal
Target Milestone: Unsorted
Assignee: Ian 'Hixie' Hickson
QA Contact: contributor
URL:
Whiteboard:
Keywords:
: 22146 (view as bug list)
Depends on:
Blocks: 22296
  Show dependency treegraph
 
Reported: 2013-05-28 22:18 UTC by Rafael Weinstein
Modified: 2013-06-10 22:16 UTC (History)
17 users (show)

See Also:


Attachments

Description Rafael Weinstein 2013-05-28 22:18:21 UTC
So the current wording: 

http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#calling-scripts

Means that step 6 gets run every time a script invocation exits regardless of whether it is the last open script invocation. This suggests that the microtask checkpoint will get have been run in the following example before "do more work":

<input>
<script>
document.querySelector('input').onfocus = function() {
  // dom manipulation goes here
};
document.body.addEventListener('DOMContentLoaded', function() {
  document.querySelector('input').focus();
  // do more work
});
</script>

---

More generally, there are multiple types of work which want to run at "end of microtask", or in ecmascript-land, "end of turn". We need an abstracted work queue which performs all of these types of work and the respective mechanisms and be spec'd in terms of.

Existing "clients"
-DOM Mutation Observers
-DOM Table sorting
-File blobs

Coming "clients"
-DOM Custom elements callbacks
-ECMAScript Object.observe
-ECMAScript promises/futures
-ECMAScript WeakRef
Comment 1 Jonas Sicking (Not reading bugmail) 2013-05-28 22:28:47 UTC
I think "File blobs" *might* be different here since they can never run script, or even cause other end-of-microtask work items to be scheduled. There's even debate whether there are "file blobs" work to be done.

So I suggest we leave that one out of the discussion here for now.
Comment 2 Dimitri Glazkov 2013-05-28 22:31:22 UTC
*** Bug 22146 has been marked as a duplicate of this bug. ***
Comment 3 Rafael Weinstein 2013-05-28 22:37:15 UTC
@Jonas. Seems fine. -File blob (for now).

So, to start, I'll observe that there are three "classes" of work types represented here:

1) "One-of" callbacks: E.g Custom Element callbacks, Promise/futures. These are simple callbacks which must run at some point.

2) "Logging" callbacks: E.g. Mutation Observers, Object.observe. These are callbacks which must deliver a "log" of accumulated work. When new data as added to a callback's log, it must be re-scheduled to be invoked and when invoked, it must be delivered all pending log items. Also, delivery order for both by creation time of the callback entity (MutationObserver creation, or the first time a callback is used in Object.observe).

3) "Side-effect" work. E.g. table sorting, or WeakRef collection. These are types which don't directly invoke script, but which *may cause* more script to be invoked (e.g. table sorting may enqueue more mutations for mutation observers)
Comment 4 Dimitri Glazkov 2013-05-28 22:49:22 UTC
(In reply to comment #3)
> @Jonas. Seems fine. -File blob (for now).
> 
> So, to start, I'll observe that there are three "classes" of work types
> represented here:
> 
> 1) "One-of" callbacks: E.g Custom Element callbacks, Promise/futures. These
> are simple callbacks which must run at some point.

https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html for reference.
Comment 5 Rafael Weinstein 2013-05-28 22:52:26 UTC
Sorry, that should have been "One-off" callbacks.
Comment 6 Glenn Maynard 2013-05-28 22:56:19 UTC
> I think "File blobs" *might* be different here since they can never run script, or even cause other end-of-microtask work items to be scheduled. There's even debate whether there are "file blobs" work to be done.

Do you mean auto-revoke URLs?  Those use the "global script clean-up jobs" concept rather than microtasks.

> Means that step 6 gets run every time a script invocation exits regardless of whether it is the last open script invocation. This suggests that the microtask checkpoint will get have been run in the following example before "do more work":

It doesn't suggest it, it requires it explicitly.  Can you describe what behavior is underspecified?
Comment 7 Rafael Weinstein 2013-05-28 22:58:48 UTC
Glenn, I didn't quite follow how auto-revoke URLs can't use the microtask checkpoint. Can you summarize the reason for me?
Comment 8 Glenn Maynard 2013-05-28 23:08:05 UTC
It leads to unexpected behavior unless you completely control the code path.  For example,

url = URL.createObjectURL(blob, {autoRevoke: true});
someLibrary.doSomething();
img.src = url;

If auto-revoke blobs are released at microtask checkpoints, this will usually work.  However, it will suddenly fail if doSomething() happens to do anything which synchronously triggers a checkpoint, since the URL will be revoked before it returns.  This means that seemingly harmless and unrelated behavior inside a JavaScript library would cause problems in the caller.

By delaying the revoke until the outermost script returns, this category of problem goes away: it's guaranteed that your URL will never be revoked on you before you return (unless you explicitly revoke it for some reason).
Comment 9 Rafael Weinstein 2013-05-28 23:12:56 UTC
Ok. So it seems to me that there's a misunderstand about end-of-microtask that is partially caused by the confusing wording in the spec.

In the spec as it is now, you are correct -- but that is a bug.

An invariant of the microtask checkpoint *is* (in current implementations), that script *cannot* trigger it synchronously. 

In other words, the microtask checkpoint does exactly what you want -- it runs

(a) every time the outer-most script invocation exits
(b) when the user agent is about to return to its Task Source to pick a new Task to run (at the end of the Task).

Assuming this, can blob urls be defined in terms of the microtask checkpoint?
Comment 10 Glenn Maynard 2013-05-28 23:29:10 UTC
As far as I understand, the wording in the spec is clear and intentional.  Microtasks happen at the end of every script invocation, not only the outermost one.  Also, any API which spins the event loop will cause microtask checkpoints to happen.  If that wasn't intended, I assume Hixie would have fixed it rather than add the new "clean-up jobs" concept.

If microtask checkpoints are changed to never happen while a script is on the entry script stack (eg. never happen while a script is running), then that would make them exactly the same as clean-up jobs.  In that case, yes, the two could be merged together.

I don't know much about the various users of microtasks, so I don't have any input on whether that change makes sense.
Comment 11 Rafael Weinstein 2013-05-28 23:33:57 UTC
So far, I believe that the current wording of microtasks is in error (possibly the result of a misunderstanding). The original client of microtasks (Mutation Observers) critically wants to avoid exactly the same thing that Glenn describes that blob URLs wish to avoid.

If anyone knows of some reason which came up that microtask checkpoints should be run after *every* script invocation, please speak up now!
Comment 12 Jonas Sicking (Not reading bugmail) 2013-05-28 23:41:27 UTC
Like I said, can we keep file blobs out of this? And yes, it's specifically the blob url revoking discussion that I'd like to keep out of this.
Comment 13 Glenn Maynard 2013-05-29 00:00:40 UTC
(In reply to comment #12)
> Like I said, can we keep file blobs out of this?

As far as it's relevant to the discussion, no, of course not.  It sounds closely related to what Rafael is asking for.  (Whether what he's asking for is correct or not, I don't know.)
Comment 14 Boris Zbarsky 2013-05-29 02:02:35 UTC
> If anyone knows of some reason which came up that microtask checkpoints should
> be run after *every* script invocation, please speak up now!

Consider a simple script which calls dispatchEvent, which triggers a bunch of unrelated event listeners which mutate the DOM.

Should microtask checkpoints for those mutations really not happen until the script that called dispatchEvent terminates?
Comment 15 Rafael Weinstein 2013-05-29 02:08:33 UTC
Yes. This was discussed at length when we designed mutation observers. It's what WebKit, Blink and Gecko (unless something very surprising has happened) implement.

I can try to persuade you that this is correct, but I think maybe Jonas & Olli should do that, as they were part of the consensus on the original semantics.
Comment 16 Boris Zbarsky 2013-05-29 02:41:26 UTC
Hmm.  Gecko seems to run microtask checkpoints at outermost script entry points or when the event loop spins, indeed.
Comment 17 Olli Pettay 2013-05-29 17:34:16 UTC
(In reply to comment #16)
> Hmm.  Gecko seems to run microtask checkpoints at outermost script entry
> points or when the event loop spins, indeed.
Yes, that is very much designed to work that way.

Spinning event loop let's one to call mutation observer callbacks synchronously, so comment 8 is right. Microtask checkpoint may be executed if doSomething()
spins the event loop.
But I'm not too worried about that.
Comment 18 Rafael Weinstein 2013-06-03 18:48:45 UTC
Ok. So I'm going to assume at this point that we have consensus on *when* the microtask checkpoint should be run, and tackle that as the first of two issues here -- because the spec is currently wrong in terms of reflecting what is implemented in Gecko, WebKit & Blink.

So again, conceptually: performing a microtask checkpoint should happen at two times:

1) Whenever a script invocation completes and it is the last script invocation on the stack.
2) At the end of the current Task (before the user agent selects a new Task from one of the Task Sources).

I think the simplest way to accomplish this is to

--Modify the language in "calling-scripts"--

http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#calling-scripts

To merge step 5 & 6 together into:

Step 5: If there is no longer an entry script, Perform a microtask checkpoint. (If this runs scripts, it will result in this algorithm being invoked reentrantly.)

--And, change the steps for performing a microtask checkpoint--

http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#perform-a-microtask-checkpoint

-Add run the global script clean-up jobs as step 4. Thus:

1. Let the running mutation observers flag be true.

2. Sort the tables with pending sorts.

3. Invoke MutationObserver objects for the unit of related similar-origin browsing contexts to which the script's browsing context belongs.

This will typically invoke scripted callbacks, which calls the jump to a code entry-point algorithm, which calls this perform a microtask checkpoint algorithm again, which is why we use the running mutation observers flag to avoid reentrancy.

4. Run the global script clean-up jobs as the final step

5. Let the running mutation observers flag be false.

---

Can we agree on this and get the spec fixed before we move on to

-discussing generalizing this mechanism to support additional clients and
-tackling the issue of scheduling delivery during a given microtask checkpoint
Comment 19 Glenn Maynard 2013-06-03 20:20:08 UTC
(In reply to comment #18)
> Ok. So I'm going to assume at this point that we have consensus on *when*
> the microtask checkpoint should be run

You're getting ahead of things, since the editor hasn't yet responded to this bug.  That may take some time.
Comment 20 Ian 'Hixie' Hickson 2013-06-06 18:59:18 UTC
blob: release has nothing to do with this, since it uses a different mechanism (the cleanup steps). It's covered by bug 17765. As sicking says, let's leave them out of this bug.

Regarding microtask checkpoints, I'm happy to do it whenever you want it done. The three places we fire the mutation observers have been the same since the spec was first written, as far as I can tell: when a script ends, when a task ends, and when a parser is about to run a script. The goal as far as I can tell is that when you start running a script, you know that your internal state, as updated by your mutation observers, matches the state of the DOM.

There's not much point running the observers at the end of a script if it's only done when it's the last script, as opposed to just doing it at the end of the task, as far as I can tell, unless you really want to make sure that tasks that synchronously fire two events back to back run the microtask checkpoint twice (once after each event) or some such.
Comment 21 Rafael Weinstein 2013-06-10 17:59:59 UTC
@Hixie, to make this more concrete, the following

<html>
<title>EOMT Delivery</title>
<body>
  <input>
  <script>
    var input = document.querySelector('input');

    var MutationObserver = MutationObserver || WebKitMutationObserver;
    var observer = new MutationObserver(function(r) {
      console.log(r.length + ' records delivered');
    });
    observer.observe(input, { attributes: true });


    input.addEventListener('focus', function(e) {
      console.log('input got focus');
      input.setAttribute('focus', 'was changed');
    });

    window.addEventListener('DOMContentLoaded', function() {
      console.log('content is loaded');
      input.focus();
      input.setAttribute('content', 'loaded');
    });

  </script>
</body>
</html>

(http://jsbin.com/iselaj/3/edit)

Today in In Firefox, Chrome and Safari, the above will output:

"content is loaded"
"input got focus"
"2 records delivered"


It seems to me that the spec suggests it should output:

"content is loaded"
"1 records delivered"
"input got focus"
"1 records delivered"

So the spec isn't reflecting what browsers do. Am I misreading the spec or are you arguing that browsers should do something different -- because that seems like a different discussion.
Comment 22 Ian 'Hixie' Hickson 2013-06-10 20:52:53 UTC
Rafael and I talked about this on IRC, and I think we figured out that we were basically on the same page.

Please re-open this bug if the patch in the next comment is not correct with respect to the microtask checkpoint stuff.

If there are issues with the script clean-up job stuff, please file new bugs.
Comment 23 contributor 2013-06-10 20:53:07 UTC
Checked in as WHATWG revision r7950.
Check-in comment: Bring the 'microtask checkpoint' invocation at the end of scripts more into line with implementations, and unrelated editorial fixes.
http://html5.org/tools/web-apps-tracker?from=7949&to=7950
Comment 24 Rafael Weinstein 2013-06-10 22:16:16 UTC
Just to clarify, Hixie points out correctly that microtask checkpoints should run at *three* times:

1) Whenever a script invocation completes and it is the last script invocation on the stack.
2) At the end of the current Task (before the user agent selects a new Task from one of the Task Sources).
3) When the parser encounters a close script tag (</script>).