[javascript] Is there a JavaScript / jQuery DOM change listener?

Essentially I want to have a script execute when the contents of a DIV change. Since the scripts are separate (content script in the Chrome extension & webpage script), I need a way simply observe changes in DOM state. I could set up polling but that seems sloppy.

This question is related to javascript jquery google-chrome-extension

The answer is


Many sites use AJAX/XHR/fetch to add, show, modify content dynamically and window.history API instead of in-site navigation so current URL is changed programmatically. Such sites are called SPA, short for Single Page Application.


Usual JS methods of detecting page changes

  • MutationObserver (docs) to literally detect DOM changes:

  • Event listener for sites that signal content change by sending a DOM event:

  • Periodic checking of DOM via setInterval:
    Obviously this will work only in cases when you wait for a specific element identified by its id/selector to appear, and it won't let you universally detect new dynamically added content unless you invent some kind of fingerprinting the existing contents.

  • Cloaking History API:

    let _pushState = History.prototype.pushState;
    History.prototype.pushState = function (state, title, url) {
      _pushState.call(this, state, title, url);
      console.log('URL changed', url)
    };
    
  • Listening to hashchange, popstate events:

    window.addEventListener('hashchange', e => {
      console.log('URL hash changed', e);
      doSomething();
    });
    window.addEventListener('popstate', e => {
      console.log('State changed', e);
      doSomething();
    });
    


Extensions-specific methods

All above-mentioned methods can be used in a content script. Note that content scripts aren't automatically executed by the browser in case of programmatic navigation via window.history in the web page because only the URL was changed but the page itself remained the same (the content scripts run automatically only once in page lifetime).

Now let's look at the background script.

Detect URL changes in a background / event page.

There are advanced API to work with navigation: webNavigation, webRequest, but we'll use simple chrome.tabs.onUpdated event listener that sends a message to the content script:

  • manifest.json:
    declare background/event page
    declare content script
    add "tabs" permission.

  • background.js

    var rxLookfor = /^https?:\/\/(www\.)?google\.(com|\w\w(\.\w\w)?)\/.*?[?#&]q=/;
    chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
      if (rxLookfor.test(changeInfo.url)) {
        chrome.tabs.sendMessage(tabId, 'url-update');
      }
    });
    
  • content.js

    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg === 'url-update') {
        // doSomething();
      }
    });
    

Another approach depending on how you are changing the div. If you are using JQuery to change a div's contents with its html() method, you can extend that method and call a registration function each time you put html into a div.

(function( $, oldHtmlMethod ){
    // Override the core html method in the jQuery object.
    $.fn.html = function(){
        // Execute the original HTML method using the
        // augmented arguments collection.

        var results = oldHtmlMethod.apply( this, arguments );
        com.invisibility.elements.findAndRegisterElements(this);
        return results;

    };
})( jQuery, jQuery.fn.html );

We just intercept the calls to html(), call a registration function with this, which in the context refers to the target element getting new content, then we pass on the call to the original jquery.html() function. Remember to return the results of the original html() method, because JQuery expects it for method chaining.

For more info on method overriding and extension, check out http://www.bennadel.com/blog/2009-Using-Self-Executing-Function-Arguments-To-Override-Core-jQuery-Methods.htm, which is where I cribbed the closure function. Also check out the plugins tutorial at JQuery's site.


In addition to the "raw" tools provided by MutationObserver API, there exist "convenience" libraries to work with DOM mutations.

Consider: MutationObserver represents each DOM change in terms of subtrees. So if you're, for instance, waiting for a certain element to be inserted, it may be deep inside the children of mutations.mutation[i].addedNodes[j].

Another problem is when your own code, in reaction to mutations, changes DOM - you often want to filter it out.

A good convenience library that solves such problems is mutation-summary (disclaimer: I'm not the author, just a satisfied user), which enables you to specify queries of what you're interested in, and get exactly that.

Basic usage example from the docs:

var observer = new MutationSummary({
  callback: updateWidgets,
  queries: [{
    element: '[data-widget]'
  }]
});

function updateWidgets(summaries) {
  var widgetSummary = summaries[0];
  widgetSummary.added.forEach(buildNewWidget);
  widgetSummary.removed.forEach(cleanupExistingWidget);
}

Edit

This answer is now deprecated. See the answer by apsillers.

Since this is for a Chrome extension, you might as well use the standard DOM event - DOMSubtreeModified. See the support for this event across browsers. It has been supported in Chrome since 1.0.

$("#someDiv").bind("DOMSubtreeModified", function() {
    alert("tree changed");
});

See a working example here.


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to google-chrome-extension

How to change the locale in chrome browser How to download PDF automatically using js? Install Chrome extension form outside the Chrome Web Store Google Chromecast sender error if Chromecast extension is not installed or using incognito Getting "net::ERR_BLOCKED_BY_CLIENT" error on some AJAX calls Disable developer mode extensions pop up in Chrome How to use jQuery in chrome extension? How to test REST API using Chrome's extension "Advanced Rest Client" Chrome Extension - Get DOM content Refused to apply inline style because it violates the following Content Security Policy directive