This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.
It's a common use to want to have the mutation callback not report any changes it makes in the following invocation. This is trivially accomplished by calling disconnect() at the beginning of the callback and observe() immediately before exiting. disconnect() clears any queue records, but this is safe inside a mutation callback because the observer has just been delivered all mutations and is guaranteed that no other actor could have made new ones. However, if the observer would like to ignore changes which are made outside the mutation callback (say in response to a XHR callback), suspending observation by calling disconnect/observe risks losing mutation records already queued. [This came up via a user of the MutationSummary library whose use case is synchronizing a JSON structure with DOM. The problem arises when trying to ignore changes made by applying remote JSON changes to the DOM] It seems to me there are two approaches to this: 1) Add an explicit suspect/resume API to MutationObserver which does what you might guess. 2) Add some sort of pullMutations() call which would allow code to synchronously fetch any queued records, thus making it safe to use disconnect/observe. Editorial: 1) Has the benefit of being a fairly clear API given the use and might also allow the user agent to better optimize usage because what's needed is actually being expressed. However (as Adamk noted), it has the down side that suspend() may be confused with disconnect() and cause the DOM to be doing alot of unneeded work 2) Is less direct in its form, but it addresses this use case, and also another use which we've suspected we'd eventually need: the desire to "pull" mutations because you'd like to synchronize your state one an imperative API call. My $.02: I prefer adding something like pullMutations() because I think we'll need it for other reasons, and it kills two birds with one stone. Also, as much as I like the directness of suspend/resume, I'm worried about pages calling suspend and never calling resume. Opinions?
Sorry: Option (1) should read: ..."Add an explicit suspend/resume API"
suspend/resume feels quite awkward. pullMutations() is simpler, though I don't quite like the name. Maybe takeMutations() ?
I don't have a strong opinion about naming. Just off the top of my head, here are some other options: -clear -retrieve -empty -deliver -fetch
I'm OK with #2. Could we also have disconnect return the list of records it's clearing? That way, users of the API are more likely to realize that they might be losing records if they ignore the return value.
disconnect() will possibly return the mutation observer itself. There is another bug open for that.
Depends on what should happen when method is called. deliver() sounds good, if the idea is that the mutation observer callback is called and the current mutation records passed as parameters.
I agree with that option 2 (adding deliver()) will be better than option 1. On the other hand, having disconnect() return the list of mutation records it cleared would satisfy this particular use case ("ignor[ing] changes made by applying remote JSON changes to the DOM") without adding new API. Can we just do that (having disconnect() return the list) and avoid adding new API for now?
It feels odd if disconnect() returns the records. Is there any reason to not add deliver() now ?
(In reply to comment #8) > It feels odd if disconnect() returns the records. > Is there any reason to not add deliver() now ? I'm not certain the specific behavior of deliver() you've described (i.e. it calls the mutation observer) would work. e.g. what happens if the script calls deliver() inside a mutation observer?
(In reply to comment #9) > (In reply to comment #8) > > It feels odd if disconnect() returns the records. > > Is there any reason to not add deliver() now ? > > I'm not certain the specific behavior of deliver() you've described (i.e. it > calls the mutation observer) would work. e.g. what happens if the script calls > deliver() inside a mutation observer? Inside MutationObserver? You mean MutationObserver's callback? Well, if there are new records, the callback would be called again, if not, nothing would happen.
(In reply to comment #10) > Inside MutationObserver? You mean MutationObserver's callback? > Well, if there are new records, the callback would be called again, if not, > nothing would happen. Right, and I don't think that's a desirable behavior (called again) because now mutation observer callback can be called mutually recursively (with deliver()) without starting an event loop. i.e. I prefer takeMutations() over deliver().
(In reply to comment #11) > (In reply to comment #10) > > Inside MutationObserver? You mean MutationObserver's callback? > > Well, if there are new records, the callback would be called again, if not, > > nothing would happen. > > Right, and I don't think that's a desirable behavior (called again) because now > mutation observer callback can be called mutually recursively (with deliver()) > without starting an event loop. Why is that not desirable behavior? And what has mutation observer callback to do with event loop? > > i.e. I prefer takeMutations() over deliver(). takeMutations() or some such which just returns the current records and clears the record list is ok to me. I don't see much difference between takeMutations() and deliver().
(In reply to comment #12) > (In reply to comment #11) > > Right, and I don't think that's a desirable behavior (called again) because now > > mutation observer callback can be called mutually recursively (with deliver()) > > without starting an event loop. > > Why is that not desirable behavior? Because writing a re-entrant callback is hard. This is one big issue with the existing mutation events. If you make any DOM mutations inside a mutation event handler, then you might get called synchronously, and it's really hard to reason about. > And what has mutation observer callback to do with event loop? I thought a mutation observer callback can be called recursively if you start a new event loop by e.g. showModalDialog inside the callback itself, and observable DOM mutations are made within that event loop. Am I missing / misunderstanding something?
(In reply to comment #13) > > Because writing a re-entrant callback is hard. This is one big issue with the > existing mutation events. If you make any DOM mutations inside a mutation event > handler, then you might get called synchronously, and it's really hard to > reason about. > > > And what has mutation observer callback to do with event loop? > > I thought a mutation observer callback can be called recursively if you start a > new event loop by e.g. showModalDialog inside the callback itself, and > observable DOM mutations are made within that event loop. Am I missing / > misunderstanding something? Yes, callbacks can be called recursively. But how would deliver() make that situation any worse? But sounds like you don't like deliver() too much, and I don't care whether it is deliver() or takeMutations(), so perhaps we should do the latter ?
I'm not worried about reentrancy (exactly) because if you ended up being reentrant, you did it to yourself (i.e. it'd be pretty insane to call deliver inside of a delivery callback. However, if you didn't want your callback invoked, then you have to jump through some hoops to save the records away somewhere. Basically, this might lead to some awkward code patterns. So, yeah, let's go with something that simply returns the records directly and doesn't invoke the callback.
From IRC discussion (rafael, olli & anne): takeRecords().
https://bitbucket.org/ms2ger/dom-core/changeset/addc94636886 https://bitbucket.org/ms2ger/dom-core/changeset/1b83e1342293 (editorial)