On 09/18/2015 06:30 AM, Zibi Braniecki wrote:
Hi,
One of the major use cases for MutationObserver is all kinds of libraries that
either shim APIs or provide intrinsic modifications to DOM experience.
Examples of such libraries may be:
* A library that provides Date/Time pickers only caring about <input
type="date|time">
* A library that extends behavior of a particular web component from outside
* A library that extends behavior of common elements with particular attributes
* our l10n library only looks for element with data-l10n-id|data-l10n-args
* a resource API shim may be looking for <links> with a given attribute
Unfortunately, at the moment, MutationObserver API makes it particularly hard
for libraries to narrow down the scope of elements that are monitored by them
which results in three things:
*) Higher CPU/power cost of running a MutationObserver on a DOM tree
*) More noise inside the MutationObserver callback
*) Requirement for fairly sophisticated filtering to get the right elements
The reason for so much noise is that there's no way to instrument
MutationObserver to notice only specific elements. The only filtering can be
done for attributes, but for node adding/removing MutationObserver reports
*all* elements and often in form of a DOMFragment which has to be filtered.
Example from our l10n library:
const observerConfig = {
attributes: true,
characterData: false,
childList: true,
subtree: true,
attributeFilter: ['data-l10n-id', 'data-l10n-args']
};
var mo = new MutationObserver(onMutations);
mo.observe(this.doc, observerConfig);
function onMutations(mutations) {
const targets = new Set();
for (let mutation of mutations) {
switch (mutation.type) {
case 'attributes':
targets.add(mutation.target);
break;
case 'childList':
for (let addedNode of mutation.addedNodes) {
if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
if (addedNode.hasAttribute('data-l10n-id')) {
targets.add(addedNode);
}
if (addedNode.childElementCount) {
element.querySelectorAll('[data-l10n-id]').forEach(
elem => targets.add(elem));
}
}
}
break;
}
}
if (targets.size === 0) {
return;
}
translateElements(targets);
}
Proposal:
const observerConfig = {
attributes: true,
attributeFilter: ['data-l10n-id', 'data-l10n-args']
querySelector: '*[data-l10n-id]'
};
I don't understand what querySelector would filter here. We're observing only
attributes but then what...
var mo = new MutationObserver(onMutations);
mo.observe(this.doc, observerConfig);
function onMutations(mutations) {
const targets = new Set();
for (let mutation of mutations) {
switch (mutation.type) {
case 'attributes':
targets.add(mutation.target);
break;
case 'childList':
for (let addedNode of mutation.addedNodes) {
targets.add(addedNode);
}
Hmm, or are you just missing childList from observerConfig.
How would the filtering work for node removals? Would it need to run the filter
right before removal?
Are you thinking that querySelector filtering would map also to all the
descendants of the actually added child node?
That would be quite a big change how the API works currently. addedNodes
wouldn't be about childNodes anymore, but any descendant. Would it be
also about any removed descendants?
break;
}
}
if (targets.size === 0) {
return;
}
translateElements(targets);
}
And it's not only about cleaner code. In Firefox OS' Settings app, our
onMutations is fired hundreds of times as we construct DOM dynamically, while
l10n cares about 70 elements out of those.
I believe that there's a substantial value in extending MutationObserver API to
help with such filtering for all scenarios listed at the top and many similar
ones.
Would it be possible to do within MutationObserver API or would it be material
for a separate API?
selector based observing/filtering was discussed when MutationObserver API was
being designed. Some old documentation here
http://www.w3.org/2008/webapps/wiki/MutationReplacement (note, that
documentation is from the era when there were no microtasks yet)
IIRC there were concerns selector based filtering being too slow, and also it
is a higher level thing, so first
we wanted a low level API. (one could say even attribute filtering should have
been left out)
If you want new web exposed changes to the API, better to file a spec bug
https://github.com/whatwg/dom/issues/new
so that also other browser vendors and API users can easily comment the
proposal.
But in principle I think some kind of filtering would be nice. Implementing it for node removals would be a bit annoying in Gecko case (since our
internal nsIMutationObserver::ContentRemoved happens after the removal) but it is doable. However it is unclear to me what kind of performance
characteristics we'd get - that is the main concern I have atm. (I'd like to avoid adding APIs which can be easily used in such way that it slows down
all DOM operations dramatically.)
-Olli
Thanks,
zb.
_______________________________________________
dev-platform mailing list
dev-platform@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-platform