Re: [whatwg/dom] MutationObserver callbacks happen in wrong order. (Issue #1105)

# Userland Solution

Currently, MOs run all at once per target, which is highly irrepresentative of the order in which mutations happened in the DOM.

The end user has two options, both of which get more complicated:

1. Use a single MutationObserver root node of the current tree.
2. Implement some additional tracking across all MutationObservers on individual targets

Here is an example of option 2 in userland:

https://codepen.io/trusktr/pen/ExEBxmw/401b4869c60721e0fca18840036cbdee?editors=0010

# Possible Ideal Solutions (in the engine)

## 1 MutationObserver

I think the ideal solution would be for the browser engine to intelligently split MO changesets into chunks, and call them in the correct order (by calling MO callbacks for a single target one or more times) within the MO microtask, such that we can guarantee that each for loop in any callback is always iterating in the correct order relative to the whole tree in which the MutationObservers are observing.

This would eliminate the need for end users to write the complicated code required for the userland solutions.

## 2 Mutation Events

Fix DOM Mutation Events (make a new one with different names perhaps, or a new API that is similar to events).

-------


Finally, compare how drastically complicated this code,

```js
const connected = new Set 
const disconnected = new Set 
let scheduled = false

class XEl extends HTMLElement {
 connectedCallback() {
  this.o = new MutationObserver(changes => {
   for (const change of changes) {
    console.log('--- change', change.target)
    for (const child of change.addedNodes) {
     console.log('track added child', child)
     connected.add(child)
    }
    for (const child of change.removedNodes) {
     console.log('track removed child', child)
     disconnected.add(child)
    }
   }

   if (!scheduled) {
    scheduled = true
    queueMicrotask(() => {
     console.log('--------------- MICROTASK MO')
     scheduled = false
     const allNodes = new Set([...connected, ...disconnected])
     for (const child of allNodes) {
      if (child.parentElement) {
       if (disconnected.has(child)) console.log('child removed:', child)
       if (connected.has(child)) console.log('child added:', child)
      } else {
       if (connected.has(child)) console.log('child added:', child)
       if (disconnected.has(child)) console.log('child removed:', child)
      }
     }
     connected.clear()
     disconnected.clear()
    })
   }
  })

  this.o.observe(this, {childList: true})
 }

 disconnectedCallback() {
  this.o.disconnect()
 }
}

customElements.define('x-el', XEl)

setTimeout(() => {
 queueMicrotask(() => console.log('--------------- MICROTASK before'))
 const t = three
 t.remove()
 one.append(t)
 queueMicrotask(() => console.log('--------------- MICROTASK after'))
}, 1000)
```

is compared to DOM Mutation Events code:

```js
const connected = new Set 
const disconnected = new Set 
let scheduled = false

const events = new Set()

class XEl extends HTMLElement { 
 lastConnected = new Set
 
 childConnected = (event) => {
  if (event.target.parentElement !== this) return
  this.lastConnected.add(event.target)
  console.log('child added:', this, event.target)
 }

 childDisconnected = (event) => {
  if (!this.lastConnected.has(event.target)) return
  this.lastConnected.delete(event.target)
  console.log('child removed:', this, event.target)
 }

 connectedCallback() {
  for (const child of this.children) this.lastConnected.add(child)

  this.addEventListener('DOMNodeInserted', this.childConnected)
  this.addEventListener('DOMNodeRemoved', this.childDisconnected)
 }

 disconnectedCallback() {
  this.removeEventListener('DOMNodeInserted', this.childConnected)
  this.removeEventListener('DOMNodeRemoved', this.childDisconnected)
 }
}

customElements.define('x-el', XEl)

setTimeout(() => {
 queueMicrotask(() => console.log('--------------- MICROTASK before'))
 const t = three
 t.remove()
 one.append(t)
 queueMicrotask(() => console.log('--------------- MICROTASK after'))
}, 1000)
```

And that's not even simple enough.

We didn't need to make a complicated API (MutationObserver) in order to have a deferred-batched event system.

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

Message ID: <whatwg/dom/issues/1105/1229056530@github.com>

Received on Saturday, 27 August 2022 00:17:41 UTC