Skip to content

childList records across multiple MutationObservers are observed in fundamentally incorrect order #1111

Open
@trusktr

Description

Note Re-opening of #1105 because that issue was closed for not being succinct, although the problem was described fine. This time I will not include additional comments showing attempts to work around the problem.

I'm not asking for usage help.

I'm pointing out the difficulties involved with writing proper MutationObserver code for child tracking (across a tree, not just on a single element), and the intent of this issue is to spark ideas for a better API (again, not usage help).


Trying to port from DOM Mutation Events, or from dis/connectedCallbacks on children (which aren't available on builtin elements) to a MutationObserver pattern that initializes or cleans up code based on when children are connected then disconnected (which works with built-in and custom elements), is nearly impossible without an immense and cumbersome effort.

I've been using MutationObserver for years now, and am now discovering bugs in certain cases that I didn't notice before (I should have written better tests, but also MutationObserver is complicated and cumbersome, the latter being my point and wishing to ideate a better API).

The Problem

Note I will only describe the problem here. #1105 has examples of complicated user code that attempt to work around the problem, but I'm not asking for help with those examples. I'm only trying to show the complexity of the problem, and wishing for a better API.

Note Also note MutationObserver is not bad for all its use cases, the problem I will describe here only relates to tracking children. In the ideal future world, a new API would replace the childList feature only, and that is the only scope of this issue.

In the following example, the connected and disconnected events fire in the wrong order, which leave connected children erroneously cleaned up:

https://codepen.io/trusktr/pen/oNqrNXE/3aad3bb7315877d00c7c42e3d77fed5a?editors=1010

In this one, the behavior is a bit different, but still not as desired:

https://codepen.io/trusktr/pen/MWVMWKe/091e6f303bd773f2754304fb1c9bff30?editors=1000

With standard connectedCallback and disconnectedCallback, they always fire in this order per each custom element when an element disconnected and re-connected:

disconnectedCallback()
connectedCallback()

However, with this naive MutationObserver usage, the order (in the above two examples) when a child is removed then re-connected is

child connected
child removed

which will cause unexpected behavior, and will require people to start trying to work around the issue as I've described in further comments in #1105.


DOM Mutation Events are much much easier to use. MutationObserver is incredibly difficult to work with. (wrt observing children)

Possible Solutions:

  1. MutationObserver event callbacks could be called in such a way that childList records are perfectly interleaved in the same order as actions that happened in the DOM. This would complete solve the problem, and code would remain simple.
  2. All the issues of Mutation Events could be fixed and exposed in a new event API.
    • There is not any aspect of event patterns in general that make them inherently bad. It just happens to be that the way Mutation Events worked was not ideal and browsers implemented them differently with bugs. That's not to say we can't come up with an event pattern that works well.
  3. A new synchronous alternative to Mutation Events could be created that would be no worse than today's connectedCallback and disconnectedCallback methods (those are synchronous, and synchronicity is not the issue, but code ordering is), being an opt-in feature with no such thing as event bubbling in the way or the heaviness of EventTarget (Event being designed specifically for a tree of nodes, when not all event patterns need to be associated with a tree at all).

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions