Extending the Mutation Observer API

January 07, 2016

I recently made a Chrome Extension called Blissfully, that filters a user’s Facebook News Feed based on blacklisted keywords saved by the user. It does this effectively by using the browser’s Mutation Observer API. The Mutation Observer efficiently detects DOM nodes that are loaded or removed from the DOM. Here’s what it looks like in action:

// 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()

It’s a fantastic feature and it was fun using it in Blissfully. However, due to the nature of Facebook behaving often as a single-page-application, I had to create some workarounds.

One of the things I did when using the Mutation Observer was wrap it in a function. This function can then have parameters, callbacks, and specific details that you’d want to pass for the Mutation Observer to utilize.

Since the MutationObserver doesn’t have a property that indicates whether an instance is connected or disconnected, I had no way of ensuring the state of my observer. In the end, this created issues because I wouldn’t want to create more than one instance, nor would I want to have my observer connected or disconnected when it shouldn’t be.

I needed a way to check whether the Mutation observer was active, because I only wanted it to be active on facebook’s homepage. The .isActive property was added, and the .prototype.observe and .prototype.disconnect methods were added to give more control and provide more information on the state of the Mutation observer.

Here’s the updated Observer:

/* Creates a MutationObserver instance that will detect */
function trackDOM(fn, config) {
  // create an observer instance
  this.observer = new MutationObserver(fn)
  this.isActive = false
  this.observerConfig = config || {
    attributes: true,
    childList: true,
    subtree: true,
    characterData: true,
  }
}

/* Observe target element and mark observer as active */
trackDOM.prototype.observe = function(target) {
  this.isActive = true
  this.observer.observe(target, this.observerConfig)
}

/* Disconnect Observer and mark as inactive */
trackDOM.prototype.disconnect = function() {
  this.isActive = false
  this.observer.disconnect()
}

By implementing these methods, we can then do something more interesting, such as automatically checking the current web page and ensuring that the observer is running when it should be running:

/**
 * Checks to see what page user is on by logging
 * current URL. Disconnects the observer if
 * not on homepage and reconnects if on homepage
 */
trackDOM.prototype.validateInstance = function() {
  var _this = this
  setInterval(function() {
    var homePage = window.location.pathname === '/'
    // Disconnect observer if user not on homepage
    if (!homePage) {
      if (_this.isActive) {
        _this.disconnect()
      }
    }
    // Observe the DOM if user is on homepage
    if (homePage) {
      if (!_this.isActive) {
        _this.observe()
      }
    }
  }, 250)
}

This is how I approached using the MutationObserver, and I think the wrapper and simple methods I added definitely improved my ability to make my Chrome Extension using MutationObserver!


Patrick El-Hage

I'm Patrick El-Hage and I live and work in San Francisco. I'm also on Twitter.