Bugzilla – Bug 20821
What happens if you spin the event loop from inside "perform a microtask checkpoint"? (showModalDialog, document.close()...)
Last modified: 2014-02-05 01:00:04 UTC
What happens if you spin the event loop from inside "perform a microtask
checkpoint"? (showModalDialog, document.close()...)
Posted from: 220.127.116.11
User agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.27 (KHTML, like Gecko) Chrome/26.0.1386.0 Safari/537.27
Because a microtask checkpoint is not a task, it's not defined.
Specifically, the problem is that the DOM spec says:
# [...] invoke mo's callback [...]
...during the "invoke MutationObserver objects" step, but doesn't set up an environment that allows the event loop to be re-entrant. Everything else that the event loop does while it's not running a task is explicitly designed to not make it possible to spin the event loop.
If we want mutation observers to be able to call showModalDialog() and synchronously invoke the parser, then we need to define how this works.
It might be easier to just say that while mutation observers are running, we act as if there's a parser-blocking script around, and we make showModalDialog() throw an exception when called.
Any opinions from implementors or DOM people?
I'd prefer if we made that impossible.
Should we also block synchronous XMLHttpRequest from those contexts?
I don't see any reason to do anything differently inside a callback that is triggered at the end of a microtask, than what we do outside of such callbacks.
I.e. I think showModalDialog and sync XHR should behave the same in the callback from a mutation observer as any other time.
I think otherwise we'll introduce a lot of inconsistencies that just make it harder for people to develop against the web platform.
It's definitely the case that spinning the event loop does raise a lot of complex issues with regards to things that need to happen at end-of-microtask. However many of these issues arise no matter what.
I.e. there are no perfect solutions for when to notify about mutations that happen during a task if that task then spins the event loop.
It feels like the issue here is just a slight variation of that.
That said, I think all APIs that spin the event loop are of evil and we should try to get rid of them. But that's obviously a separate issue from this bug.
Sync XHR doesn't spin event loop per spec, showModalDialog does.
And I think showModalDialog should work when MutationObserver callbacks are called.
(In reply to comment #3)
> I don't see any reason to do anything differently inside a callback that is
> triggered at the end of a microtask, than what we do outside of such
Yes, I agree. Implementations should already be handling similar scenarios (where an event loop can be spun-up from showModalDialog). However, I'm not sure what the proper way to spec this is.
Spinning up the event loop from showModalDialog in a task is handled. It's spinning it up inside a microtask checkpoint that's non-trivial, since those don't execute in the context of a task, so the continuation mechanism breaks.
What happens if you get multiple mutation callbacks, but in the first one you spin showModalDialog() and in that instance of the event loop you get more tasks that perform DOM mutations? Should they fire in their own checkpoint before showModalDialog() returns, or should they all get stacked for afterwards?
If we're ok with mutation observers being queued up until showModalDialog() returns (so mutation observers inside the dialog never fire), then I can spec this by just having the "perform a microtask checkpoint" stuff be run as an artificial task with an internal task source, using all the existing infrastructure.
If we want mutation observers to keep firing except for the one that called showModalDialog(), then we can do something similar, except the artificial task is just done as part of "invoke mo's callback" in the DOM spec. This will mean that the DOM spec has to use all the event loop terminology, or provide a hook for me to do the magic in the HTML spec (e.g. I can provide a callback to the "invoke MutationObserver objects" algorithm that actually calls the callback, wrapping that in an artificial task).
What do implementors want? showModalDialog() from a mutation observer causes mutation observers to no longer fire until showModalDialog() returns, wrapping all the mutation callbacks into a single artificial task, or, each callback is its own separately-run artificial task that can independently be continued?
(Note that right now, if a mutation observer callback causes more things to be added to a MutationObserver object, if that object hasn't yet started processing its queue, its callback will be run in this instance, whereas if we've already started processing the queue, it'll wait until the next spin of the event loop. What we could do is batch up _all_ the callbacks into a list of tasks that are then queued and run using the normal event loop mechanism, except that we always prioritise mutation observer tasks, or some such. That would be out of scope for this bug except that it would solve the problem this bug's about automatically.)
(In reply to comment #6)
> If we're ok with mutation observers being queued up until showModalDialog()
> returns (so mutation observers inside the dialog never fire),
That is not ok
> What do implementors want?
MutationObserver callbacks are called at the end of the outermost microtask of the innermost
task, or at the end of task.
showModalDialog() from a mutation observer causes
> mutation observers to no longer fire until showModalDialog() returns,
> wrapping all the mutation callbacks into a single artificial task, or, each
> callback is its own separately-run artificial task that can independently be
Each callback is called separately, and if such callback creates more mutation records, those
get queued possible other mutation observer record lists
> Note that right now, if a mutation observer callback causes more things to
> be added to a MutationObserver object, if that object hasn't yet started
> processing its queue, its callback will be run in this instance, whereas if
> we've already started processing the queue, it'll wait until the next spin
> of the event loop.
Yes, this sounds right.
Ok then this needs changes in DOM.
Anne, which strategy do you want to employ here, do it all yourself, or provide a hook for me to do it?
I have a hard time following this conversation :-(
I filed bug 22296 today about an alternate way of defining the way microtasks work given that we intend to have more of them.
Fixing bug 22296 and 22185 together with this one would probably be good. To resolve all the confusion there is today.
For this bug, basically, just change this:
"To invoke MutationObserver objects, run these steps"
...to this (adding the argument):
"To invoke MutationObserver objects, with /wrapper/ as the steps to
invoke each callback, run these steps"
...and replace this:
"invoke mo's callback with ..."
...with this (which invokes the wrapper to do the work):
"use /wrapper/ to invoke mo's callback with ..."
Right now, doing that is a no-op. Once you've done that, I'll fix the wrapper algorithm I'm passing you to do the right magic.
Checked in as WHATWG revision r7980.
Check-in comment: Lay the groundwork for DOM Core to handle reentrant mutation observers.
I feel like we should address bug 22296 first given that it is about redesigning this entire algorithm to become more generic to accommodate more bits we want to do as "microtask". If you disagree I'll make the edits proposed in comment 10.
I attempted to solve this as part of bug 22296. Basically I made microtasks be "tasks" as far as "spin the event loop" goes. They just end up in a regular task queue later if the event loop is spun.