Re: [whatwg/dom] Proposal: add `children` option to `MutationObserverInit` (#905)

> would be great to understand better the value of proposal

would be great to have a conversation before a thumb down ;-) but I actually agree with you the `children` choice is poor, so how about **childFilter** ?

## Symmetry

Accordingly to your reaction, there was no reason to have [attributeFilter](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit/attributeFilter) option neither, because any callback could've simply checked if the attribute name is one that was meant to be observed, but in more than a occasion I've used this great option that helps a lot avoiding repeated checks over and over, and the callback being triggered for no reason whatsoever.

The *MutationObserver* callback comes with a cost: it requires looping over all records and each record requires a loop over each children, but in highly dynamic pages, and optimized reactive libraries, looping endlessly for every bacsic `element.textContent = update` is a huge waste of CPU cycles, GC operations, and all for something unintended to observe.


## Performance

Take the classic [DBMonster demo](https://webreflection.github.io/uhtml/test/dbmonster.html) as example, and try this code:

```js
let children = 0;
new MutationObserver(records => {
  for (const record of records) {
    for (const node of record.addedNodes) {
      children += node.nodeType === 1 ? 1 : 0;
    }
  }
  console.log('Desired nodes', children);
}).observe(document, {subtree: true, childList: true});
```

Every 3rd party code that uses a `MutationObserver` on the document in search of new *elements* will be triggered unnecessary times "*forever*" without actually doing anything ever, because the only changes happening in that case are `#text` nodes.

And not only every code on the planet that looks only for elements only, since these can invalidate the layout, require reflow, repaint, etc, will be invoked for no reason, accessing `nodeType` is also a quite expensive operation.

Changing the previous observer with this:
```js
let children = 0;
new MutationObserver(records => {
  console.time('MutationObserver');
  for (const record of records) {
    for (const node of record.addedNodes) {
      children += node.nodeType === 1 ? 1 : 0;
    }
  }
  console.timeEnd('MutationObserver');
}).observe(document, {subtree: true, childList: true});
```

Would show execution times between `0` and `2` milliseconds on an intel i7 CPU, and on top of that, if the budget we have for great performance is `16ms`, having any *ms* down for code that doesn't need to run, and doesn't help developer intents, seems like a relatively *huge* issue, but could we do better?

## Proposal

Add a `childFilter` option that is identical to `attributeFilter` in the sense that it's an optional *array* (list) that accepts the kind of child that a developer would like to be notified about:

```js
new MutationObserver(callback)
  .observe(document, {
    subtree: true,
    childList: true,
    childFilter: [
      Node.ELEMENT_NODE
    ]
  });
```

## Polyfill (not-optimized)

```js
function child(node) {
  return this.includes(node.nodeType);
}

const {filter} = Array.prototype;

class BetterMutationObserver extends MutationObserver {
  #childFilter = [];
  constructor(callback) {
    super((records, self) => {
      if (this.#childFilter.length) {
        records = records.map(record => ({
          ...record,
          addedNodes: filter.call(record.addedNodes, child, this.#childFilter),
          removedNodes: filter.call(record.removedNodes, child, this.#childFilter)
        }));
      }
      callback(records, self);
    });
  }
  observe(target, options) {
    if ('childFilter' in options)
      this.#childFilter = options.childFilter;
    return super.observe(target, options);
  }
}
```

This basic implementation, due its simplicity, will still trigger the callback even when no `addedNodes` or `removedNodes` were involved, but empty loops are quite cheap, compared with `node.nodeType` filter dance each time, but an improved version of the polyfill could better check initial options and avoid invoking the callback if it's not needed, and I'd be happy to provide a better polyfill whenever there's interest in this proposal.

Thank you for considering the `childFilter` improvement.




-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/905#issuecomment-713353843

Received on Wednesday, 21 October 2020 06:58:40 UTC