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 23226 - Need for Virtual MutationRecords
Summary: Need for Virtual MutationRecords
Status: RESOLVED WONTFIX
Alias: None
Product: WebAppsWG
Classification: Unclassified
Component: DOM (show other bugs)
Version: unspecified
Hardware: All All
: P2 normal
Target Milestone: ---
Assignee: Anne
QA Contact: public-webapps-bugzilla
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-09-12 18:51 UTC by Bradley Meck
Modified: 2014-02-13 10:20 UTC (History)
5 users (show)

See Also:


Attachments
IRC Discussion About Topic (6.05 KB, application/octet-stream)
2013-09-12 18:51 UTC, Bradley Meck
Details

Description Bradley Meck 2013-09-12 18:51:22 UTC
Created attachment 1395 [details]
IRC Discussion About Topic

I am still a bit new to writing to standards groups so please forgive any faux pas I make in this report.

Right now various state changes are not observable using MutationObservers, in particular: <input>, <textarea>, and <select> states/values. These are visible if we listen to "change" events, but do not provide a clear abstraction for:

* the name of the host object property that represents the state
* the old value
* the new value

After some time and discussion on the mailing list, it was shown that we cannot fix the getter/setter specification for these various host properties. So, as a counter proposal an opt-in way to notify observers of host object state changes seems the best way to bring back what I consider a base use case of MutationObservers. After more discussion some points came up as important:

* A full event dispatch appears to be overkill because propagation, prevention, bubbling, etc. are not necessary when compiling a list of MutationRecords for MutationObservers.
* There should be a clear distinction of host object properties/getters/setters and DOM Node attributes.

There appears to be some tooling for web inspection of various browsers allowing enumeration of MutationObservers viewing a Node (when in privileged code), but this sounds a little extreme to me especially considering the lack of ability for people to see what EventListeners are on a Node.

Therefore, I propose something like smaug mentioned in the #whatwg IRC:

* node.notifyMutationObservers({property: "value"});

Which would create something like:

{property:"value", target: node, type: "virtual"}

If it is determined that old/new values and or arbitrary data are relevant to this, I would think that to be amenable.

This would create a mutation record with a specified type such as "virtual" that would be propagated to any MutationObservers watching a Node.

By doing this, we add an addendum to elements that use "change" events so that they fire these virtual records without affecting older code. This also may become relevant with WebComponents needing to announce state transitions, that are not directly observable in the DOM.

As always I am completely open to alternatives that would cover these use cases.

Cheers,
Bradley

Related History:

http://w3-org.9356.n7.nabble.com/MutationObserver-does-not-observe-state-changes-tt260972.html#none

https://code.google.com/p/chromium/issues/detail?id=260881&can=1&q=reporter%3Abradley.meck&colspec=ID%20Pri%20M%20Iteration%20ReleaseBlock%20Cr%20Status%20Owner%20Summary%20OS%20Modified
Comment 1 Olli Pettay 2013-09-12 19:00:38 UTC
> Therefore, I propose something like smaug mentioned in the #whatwg IRC:
> 
> * node.notifyMutationObservers({property: "value"});
I assume you don't mean here that there is automatic
notification when value property has changed, since observing properties would be really hard and slow.


> 
> Which would create something like:
> 
> {property:"value", target: node, type: "virtual"}

So MutationRecord would have to be extended to have also attribute 'property'?
Comment 2 Bradley Meck 2013-09-12 19:10:57 UTC
(In reply to Olli Pettay from comment #1)
> > Therefore, I propose something like smaug mentioned in the #whatwg IRC:
> > 
> > * node.notifyMutationObservers({property: "value"});
> I assume you don't mean here that there is automatic
> notification when value property has changed, since observing properties
> would be really hard and slow.
> 

Correct, the process would be manual, not automatic. This is opt-in and default notification would only occur for things that currently have state that is not observable with only the DOM attributes, such as <input>, <select>, and <textarea>.

There was talk of using something like Object.observe for automatic notification, but as you said, this would most likely be slow. And may cause confusion when storing symbol keyed values or private data on Nodes.

> 
> > 
> > Which would create something like:
> > 
> > {property:"value", target: node, type: "virtual"}
> 
> So MutationRecord would have to be extended to have also attribute
> 'property'?

I think that would be the best course of action. We could overload attributeName, but I would be hesitant of having the same name for very different concepts (name of HTML attribute vs scripting property name).
Comment 3 Rafael Weinstein 2013-09-12 19:26:38 UTC
Object.observe seems like the right solution for this problem.

http://wiki.ecmascript.org/doku.php?id=harmony:observe

It already has exactly this mechanism, e.g. Object.getNotifier(node).notify({ type: "myCustomChangeRecordType", data: ..., otherData: ... });

This mechanism already has built into it a notion of observers 'accepting' a specific set of change record types, so that observing code isn't at risk of breaking if observed nodes suddenly update to emit new change record types.
Comment 4 Bradley Meck 2013-09-12 19:59:36 UTC
(In reply to Rafael Weinstein from comment #3)
> Object.observe seems like the right solution for this problem.
> 
> http://wiki.ecmascript.org/doku.php?id=harmony:observe
> 
> It already has exactly this mechanism, e.g.
> Object.getNotifier(node).notify({ type: "myCustomChangeRecordType", data:
> ..., otherData: ... });
> 
> This mechanism already has built into it a notion of observers 'accepting' a
> specific set of change record types, so that observing code isn't at risk of
> breaking if observed nodes suddenly update to emit new change record types.

Object.observe poses some problems we should look at with relation to notification creation / subtree observation:

* It would need a way to propagate up the DOM subtree
  * we iterate up node.parentNode if we add observers / notifiers to all Nodes and chaining appropriately

* The properties that make up the base case for this are getter/setters which are not covered using Object.observe / getNotifier [1][2][3]
  * We can work around this by adding node.notify(...) to the getter / setter algorithms
  * These notifications would be propagated to MutationObservers

The key problem for me with using *only* Object.observe is the disconnect of purposes. MutationObservers is a means of monitoring the state of a DOM subtree. Object.observe does not have the concept of a tree structure. For subtree observation present in MutationObservers it would be very odd to deal with during Object.observe because other notification callbacks could change the DOM tree while notifications are being passed down the tree to an observer of a DOM subtree. We could cache the list of parentNodes on first notification, but only if we can reliably cause the notification to create this cache before any DOM mutation occurs again.

[1] http://www.w3.org/TR/WebIDL/#es-attributes
[2] http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#the-input-element
[3] https://code.google.com/p/chromium/issues/detail?id=260881#c6
Comment 5 Rafael Weinstein 2013-09-12 20:54:33 UTC
It's not clear to me why observing a subtree is necessary. Can you please explain this?

AFACT, The original complaint is that state changes of input elements (input.value, select.selectedIndex, etc...) are not directly observable (other than by registering event listeners).

The original thread you created seemed to resolve in fairly good agreement that those elements should emit changeRecords via notifier.notify() when their value changes.

Why is that not sufficient?
Comment 6 Bradley Meck 2013-09-13 01:54:42 UTC
(In reply to Rafael Weinstein from comment #5)
> It's not clear to me why observing a subtree is necessary. Can you please
> explain this?
> 

The original use case problems was that MutationObservers could not view host object property changes. This came up during a workflow of watching a <form>'s subtree.

>
> AFACT, The original complaint is that state changes of input elements
> (input.value, select.selectedIndex, etc...) are not directly observable
> (other than by registering event listeners).
>

This is true, but came up when using MutationObservers, if I wanted to merely listen for these change events on those specific properties I can use a "change" event handler.

> 
> The original thread you created seemed to resolve in fairly good agreement
> that those elements should emit changeRecords via notifier.notify() when
> their value changes.
> 
> Why is that not sufficient?

Resolve might be a poor word choice here. It was agreed that Object.observe was a possible solution. However, this is not going to fix the start of the discussion in which MutationObservers did not watch for state mutation that was clearly observable.

If we are merely going for sufficient, we could add "change" event handlers on any DOM insertion/removal of any element that *may* have "change" events and propagate them to a WeakMap that contains any observers listening to changes, and iterate up the element that dispatched the "change" event's parent Node chain and checking against the WeakMap. Then we can detect the type of Node and what should have changed during a "change" even. Then if we setImmediate and it looks like the change was not prevented; we can do our work assuming we know the type of element and it is not something custom.

There is no need to bring notifier.notify() into this if we are ok with the above; and it would have a similar workflow using notifier.notify() except for that last 2 steps since we can tell what property changed and it is not preventable.

This workflow may be simplified with delegating the event listener to the top of the document, but may cause problems if the observed Node changes documents that it is in, because then the next document would need to have a "change" delegating handler on it as well.

This workflow sounds terrible. 

I abandoned the original thread due to talking an many people asking "why" without listening to the original case. This is my attempt to reassess the problems I was having, and have seen no progress on. I have spent some time, not only thinking about the problem but also about the question "why".

Instead of "why" as the question of workflow, "what" may be pertinent to assess first.

--- "what" is the use case ---

My use case comes from MutationObservers being unable to see state changes that are clearly available from screen readers, scripting languages, and visible renders of a page. The cause of this is host objects *for Nodes* containing internal state. This is likely to become more pressing as custom elements/document.register begin to standardize more.

--- "why" should we care ---

First of all, this can be done in the way mentioned above. Right now Polymer, Angular, etc. are implementing it in somewhat similar manners to above. As we mentioned in the "what" section though, this is going to get increasingly complex if custom elements start to have "change" events.

Now I like words, and although it may not be the best place to start; a "MutationObserver" not being able to observe mutation, even if that mutation notification must be opt-in, seems a little suspect.

An immediate use case, and how I stumbled across this, is when you want to tell when form data has changed. This gets more convoluted if we have essentially nested forms with the "form" attribute and no true <form> parent Node. As we have discussed above, it is possible to just have "change" event handlers on Nodes / Documents. But having "change" and "mutation" of a Node be entirely different concepts, that would make me argue for a nomenclature clarification rather than keeping the word mutation.

Lets delve a little deeper though. These data binding layers are emulating behavior similar to MutationObservers using events. One of the problems with events is that they are cancelable, and can make other events queue for dispatch during their handler. From my previous reading that is part of the problem of DOMMutationEvent.

Enter Object.observe / notify. These do not suffer the recursive dispatch problems of an event handler. However, they also do not come with a propagation up the parent Node chain like events. So doing behavior like watching a <form> relies on similar tactics of manually firing .notify() up the parent Node chain.

MutationObservers already fire up the parent Node chain *and* do not suffer the problems of recursive dispatch and cancellation. Any future custom element may desire to pass down notifications that match both of these criteria (any input widget for example that tries to follow what <input>, <select>, and <textarea> did with .defaultValue for example).

--- "who" has experience with this problem ---

Data binding library authors. I will try to see if some want to loop into this discussion.

People just learning about MutationObservers; this is quite a shock to most web developers I have told about MutationObservers.
Comment 7 Bradley Meck 2013-09-18 20:40:40 UTC
I have attempted to contact multiple developer communities to comment on this, with only Mozilla providing a response so far with concerns about DOM tree representation vs host state.
I have opened issues on several projects seeking developer comments/concerns on this as well.
Based upon personal investigation this is what I have learned so far:

# Dugg through some Angular code:

All the directives for state changes on native controls gather the value/checked/selectedIndex/selectedIndices in the change event to propagate changes to their view.
They then propagate these values to JS only listeners (not on the dom) in order to state the nature of mutation in a uniform way.

For <input type=text>

- https://github.com/angular/angular.js/blob/master/src/ng/directive/select.js#L337

For <input type=radio>

- https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L630

For <input type=checkbox>

- https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L652

For <select>

- https://github.com/angular/angular.js/blob/master/src/ng/directive/select.js#L263

For <select multiple>

- https://github.com/angular/angular.js/blob/master/src/ng/directive/select.js#L294

Event Propagation

- https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L1075

There is not current use of MutationObservers.

# Dugg through some Polymer code:

This does "change" event propagation directly onto objects rather than through listeners by use of node.bind (https://github.com/Polymer/NodeBind/blob/master/src/NodeBind.js).
Custom elements must implement their own "bind" function and defer to the parent as necessary.
It is unclear on what to do when custom elements inherit from custom elements unless you hard code the parent "bind" function.
After changes Object observers in JS propagate the state as needed.
This allows models to be bound to Nodes directly does not need subtree observation for models that are split across DOM subtrees; however, this approach leads to more observers and reminds me of why people us jQuery.delegate rather than attaching event listeners to every Node.

Event binding

- https://github.com/Polymer/NodeBind/blob/master/src/NodeBind.js

Custom Element binding

- http://www.polymer-project.org/platform/node_bind.html#custom-element-bindings

Object observation

- https://github.com/Polymer/observe-js

All of these are independent of the use of MutationObservers.

# Dugg through some Knockout code:

All data binding for native controls go through an expression engine, with two way data bindings such as those from native forms requiring a bit more effort to figure out what changed. Had a bit of trouble reading through the actual data flow on this one.

Data binding propagation

- https://github.com/knockout/knockout/blob/master/src/binding/defaultBindings/checked.js
- https://github.com/knockout/knockout/blob/master/src/binding/defaultBindings/selectedOptions.js
- https://github.com/knockout/knockout/blob/master/src/binding/defaultBindings/value.js

Interesting comments

- https://github.com/knockout/knockout/blob/b173d028cc044c48ef8bcc570dfa2e414d73c41f/src/binding/expressionRewriting.js#L103
- https://github.com/knockout/knockout/blob/b173d028cc044c48ef8bcc570dfa2e414d73c41f/src/binding/expressionRewriting.js#L188

# Summary

I did not find any reference to needing to make attribute based state special snowflake code such as the "open" attribute on <details> (http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-details-element), although this is not fully supported in these libraries.

These frameworks are not using attempts to unify MutationObservers with "change" events, but each are creating specific code for monitoring non-DOM state changes on Nodes.
Angular provides custom directives to handle changes on native controls.
Polymer is explicitly creating a means to propagate changes with the DOM with "bind" but I do not have the experience to comment on how the two-way binding works in practice.
Knockout uses expressionRewriting and explicitly has to declare values as "two way", several comments suggest these as special cased situations.

# Conclusion

A DOM Node based approach using MutationObservers is still lacking data from developers of these libraries.
However, there is a need for unification of what state changes occur on Nodes.
Whether this should be on the DOM Tree, or DOM host objects is still undetermined.
The unification should most likely include custom elements in the future given the Shadow DOM and Custom Elements specifications.
Polymer has an existing idea of this with "bind", but would certainly need deeper investigation.
"bind" can suffer from a recursive dispatch style situation currently, but this needs further investigation as well.
Comment 8 Anne 2014-02-13 10:20:05 UTC
Bradley, I think the idea is that the unification will come with Object.observe() for those type of changes. Given that this is mostly brainstorming I'm going to close this bug. I'd prefer brainstorming to happen on whatwg@whatwg.org, es-discuss@mozilla.org, or public-webapps@w3.org.