It Came…From the Internet! MutationObserver

[Fade in Louis Armstrong singing On the Sunny Side of the Street.]

Grab your lab coat // Put on your hazmat, baby
Leave your worries on the doorstep
Life can be so sweet
On the sunny side of the street
[…]

Okay, MutationObserver isn’t really that scary. It’s part of the DOM4 specification as a replacement for Mutation Events. MDN: DOM: MutationObserver documents it.

What are Mutation Events? A DOM3 specification to allow you to watch the DOM change using the well worn events API. You know, the old

node.addEventListener('click', onNodeClick, false);

style events.

But Mutation Events made things slow. A lot of sites mutate or change the DOM quite often, and depending where you attach the listener and how frequent the changes come, it added up to a lot of event traffic. MutationObserver replaces this.

A MutationObserver doesn’t receive a callback for every change. Instead it receives periodic callbacks for a group of changes. That group may contain a single change, depending on the stability the DOM tree (if only a few changes occur at large intervals, they may be announced separately). Think of it like an announced log instead of alerts.

MutationObserver takes a bit more setup than just tagging an event listener on a node. But that setup makes it cleaner and more useful.

var observer = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) {
        console.log(mutation.type);
    });
});

That’s a simple MutationObserver. You provide a function that receives an Array of MutationRecord objects. But that just creates the observer, you have to activate it.

var config = { childList: true, attributes: false, characterData: false, subtree: true };

So first you need an object (technically a MutationObserverInit object) specifying what types of mutations you care about (you can also specify some extra options about which data to include in the MutationRecord objects you get back). You really just worry about making this give you what you want and not give you anything else. If you want to know about node added or removed from the DOM tree, you don’t need attributes or characterData, for example.

Once you have your configuration ready, you also need a root node to observe. You can observe document, the true root, but you should attempt to observe the minimal subtree you will need.

Say the page shows a chess game, and you want to add some automatic comments or classes (for styling) to the move log. You only need to observe the part of the tree that contains the log. The page logo, the board itself, the chat, and anything else don’t matter.

var moveLog = document.querySelector('#moveLog');

We can finally turn the MutationObserver on:

observer.observe(moveLog, config);

Now when new changes happen under moveLog, the function we specified when creating the observer will be called back. But we can go a bit further. Let’s say you have two separate subtrees to observe. You could either find their lowest common ancestor and observe that whole subtree, or you can do the smart thing and simply register the observer with the second subtree:

observer.observe(oneNode, config);
observer.observe(twoNode, config);

Note that was, “register the observer with” the node, not the other way around. Although you call on the observer, this tells it to add itself to the list of observers associated with the node in question.

You can also shut the whole thing down:

observer.disconnect();

This kills all the registrations, so unfortunately you cannot easily modify the actively observed subtrees.

Also note:

observer.observe(myTarget, config1);
observer.observe(myTarget, config2);

This replaces the options for observing myTarget.

Anyway, that’s a brief overview of the not-so-scary, highly useful MutationObserver. It’s particularly nice for user scripts that want to cope with dynamic web pages. Instead of being stuck with some modification only for the nodes that were there when the page loaded, you can now easily observe changes and carry your modifications over as new nodes appear.