Mozilla

DOM MutationObserver – reacting to DOM changes without killing browser performance.

DOM Mutation Events seemed like a great idea at the time – as web developers create a more dynamic web it seems natural that we would welcome the ability to listen for changes in the DOM and react to them. In practice however DOM Mutation Events were a major performance and stability issue and have been deprecated for over a year.

The original idea behind DOM Mutation Events is still appealing, however, and so in September 2011 a group of Google and Mozilla engineers announced a new proposal that would offer similar functionality with improved performance: DOM MutationObserver. This new DOM Api is available in Firefox and Webkit nightly builds, as well as Chrome 18.

At it’s simplest, a MutationObserver implementation looks like this:

// select the target node
var target = document.querySelector('#some-id');
 
// create an observer instance
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation.type);
    });
});
 
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true }
 
// pass in the target node, as well as the observer options
observer.observe(target, config);
 
// later, you can stop observing
observer.disconnect();

The key advantage to this new specification over the deprecated DOM Mutation Events spec is one of efficiency. If you are observing a node for changes, your callback will not be fired until the DOM has finished changing. When the callback is triggered, it is supplied a list of the changes to the DOM, which you can then loop through and choose to react to.

This also means that any code you write will need to process the observer results in order to react to the changes you are looking for. Here is a compact example of an observer that listens for changes in an editable ordered list:

<!DOCTYPE html>
<ol contenteditable oninput="">
  <li>Press enter</li>
</ol>
<script>
  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  var list = document.querySelector('ol');
 
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.type === 'childList') {
        var list_values = [].slice.call(list.children)
            .map( function(node) { return node.innerHTML; })
            .filter( function(s) {
              if (s === '<br />') {
                return false;
              }
              else {
                return true;
              }
        });
        console.log(list_values);
      }
    });
  });
 
  observer.observe(list, {
  	attributes: true,
  	childList: true,
  	characterData: true
   });
</script>

If you want to see this code running, I’ve put it up on jsbin here:

http://jsbin.com/ivamoh/53/edit

If you play with the live example, you’ll notice some quirks in behaviour, in particular that the callback is triggered when you press enter in each li, in particular when the user action results in a node being added or removed from the DOM. This is an important distinction to be made from other techniques such as binding events to key presses or more common events like ‘click’. MutationObservers work differently from these techniques because they are triggered by changes in the DOM itself, not by events generated either via JS or user interaction.

So what are these good for?

I don’t expect most JS hackers are going to run out right now and start adding mutation observers to their code. Probably the biggest audience for this new api are the people that write JS frameworks, mainly to solve problems and create interactions they could not have done previously, or at least not with reasonable performance. Another use case would be situations where you are using frameworks that manipulate the DOM and need to react to these modifications efficiently ( and without setTimeout hacks! ).

Another common use of the Dom Mutation Events api is in browser extensions, and in the next week or so I’m going to publish a follow-up post on how MutationObservers are particularly useful when interacting with web content in a Firefox Add-on.

Resources

19 comments

Comments are now closed.

  1. smaug wrote on May 11th, 2012 at 01:41:

    The right link to the spec is http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#mutation-observers

    Also, the API will be un-prefix very soon.
    We want to get rid of the mutation events, so using mutation events will
    cause ‘deprecated’ warnings in the error console.

    1. Jeff Griffiths wrote on May 11th, 2012 at 08:40:

      Ah, thanks! I’ve corrected the link.

  2. Henri Sivonen wrote on May 11th, 2012 at 02:05:

    “window.WebKitMutationObserver || window.MozMutationObserver”

    It saddens me that we make the new thing that we want developers to migrate to hard to use by naming it differently in each engine.

    1. Masatoshi Kimura wrote on June 5th, 2012 at 10:18:

      Good News: Firefox will be shipped with the unprefixed MutationObserver from the start.

    2. codeviking wrote on February 15th, 2013 at 10:39:

      Is it really that hard to do:

      var MutationObserver = MutationObserver || WebKitMutationObserver || MozMutationObserver;

      I don’t think it’s that bad :)

  3. Dao wrote on May 11th, 2012 at 02:18:

    > Also, the API will be un-prefix very soon.

    Which means that this code will break:

    var MutationObserver = window.WebKitMutationObserver || window.MozMutationObserver;

    You need to check for MutationObserver already being defined or add || window.MutationObserver.

    1. Jeff Griffiths wrote on May 11th, 2012 at 08:41:

      I’ve corrected the example code to look for the non-prefixed version first.

  4. David Mulder wrote on May 11th, 2012 at 02:25:

    As far as the API /design/ the original API seems far more beautiful in it’s design and I can sincerely not see how this API got through… I mean, I understand some of the flaws of the original model, but that doesn’t change that what happened where *in essence* events. I mean, rather than some odd new object it could have been made into a document only event. And the new vocabulary etc. could have been implemented within the event data as well without all this random new stuff. :'(

    1. Jeff Griffiths wrote on May 11th, 2012 at 08:45:

      I think the main argument against anything like the old api ( besides the differing implementation issues Smaug rightly pointed out ) is just that it was far too difficult to use DOM Mutation Events in a performant way, particularly given very ‘active’ sites like Gmail.

  5. smaug wrote on May 11th, 2012 at 07:11:

    The original mutation events API has never been defined properly and browsers implement various parts of it in different ways. It is also slow and has caused lots of crash bugs in the implementations.
    The new API was designed to be faster, run js callbacks less often and at
    “stable”, exactly defined time. (Currently the new API implementations are 5-10x faster than the old API)

    Adding some API to document was discussed but there are cases when it doesn’t work well (for example if you move nodes from a document to another and back)

  6. aa wrote on May 15th, 2012 at 02:46:

    Nice! A small comment on the code.
    The filter function can be a bit easier:
    filter( function(s) {return s !== ‘<br'})

  7. Misha Reyzlin wrote on May 22nd, 2012 at 02:35:

    Hey, I have filled a bug, for when select box element’s options are selected, Mutation Observer doesnt trigger the callback: https://bugzilla.mozilla.org/show_bug.cgi?id=757077

    1. Robert Nyman wrote on May 22nd, 2012 at 02:36:

      Great, thank you!

  8. Robert Hurst wrote on June 17th, 2012 at 13:52:

    This is a terrible replacement for Mutation events. It looks like it was lifted from another language. I really have a hard time believing mutation events caused performance issues, unless the engineers who implemented them did something lazy, like fire the event even when they’re are no listeners bound to the event.

    The right solution would have been to only fire the event if a listener is bound. The API above is not consistent with The DOM node model and to be honest doesn’t look like it belongs in a JavaScript application. The API is clunky and requires a lot of extra code.

    1. Jeff Griffiths wrote on June 18th, 2012 at 15:26:

      While Mutation Events can be used without impacting performance much, it is far too easy given their api to create a pathological case. As well, the events api has never been implemented well across all browsers, making cross-browser use difficult for developers for years. See this post for a more detailed evisceration of Events:

      http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html

      I agree that MutationObservers as an API does not feel very convenient, however it is hard to say, given how the DOM can be mutate d by JS code how to do it better without running into the problems we’ve already had with events.

  9. Dan wrote on March 25th, 2013 at 10:15:

    This is really cool. Correct me if I’m wrong, but it seems I must specify each and every element I’d like to observe for change?

    Is there a reason I can’t just watch the entire DOM? My use-case is that I’m creating a script that would be used on sites that I’m not personally developing, and I’d really like to respond when any piece of the user interface changes.

  10. muneer wrote on April 7th, 2013 at 22:35:

    please can any one help me, we use onPropertyChange in more than 1000 times in our projects and now we have problem with chrome and and others, we using this event only to call some function in case the value of the textbox value element change by none user action (DOM level change), so how can solve this problem please help me on how to code, as i could not understand above example
    Thanks

    1. Jeff Griffiths wrote on April 8th, 2013 at 10:36:

      My best suggestion would be to ask this question on Stackoverflow.com or a similar site, where there is a dedicated community interested in answering specific coding questions.

  11. ToRo wrote on April 9th, 2013 at 10:01:

    Is there a way to view the actual attribute and value upon change? Right now i’m only seeing type : “attributes” and attributeName : “style” but it would be truly helpful to also know which style changed and what’s it’s new (or old) value is.
    Thanks

Comments are closed for this article.