Running code whenever an AJAX page changes

From Chickenfoot Script Repository

Trigger scripts that trigger on a page load aren't as useful on AJAX web sites where the URL never changes. The trigger script runs when you first visit the site, but never thereafter. Here's a useful function that allows you to run some code every time the page's contents are changed. This will eventually be integrated into Chickenfoot as a built-in command.

function whenChanged(/*Function*/ handler) {
  var aboutToCallHandler = false;
  var docs = Chickenfoot.getAllFrameDocuments(document);
  for (var i = 0; i < docs.length; ++i) {
    var doc = docs[i];
    doc.addEventListener("DOMNodeInserted", changed, false);
    doc.addEventListener("DOMNodeRemoved", changed, false);
    doc.addEventListener("DOMAttrModified", changed, false);
  }
  // TODO: when new iframes appear, we need to attach listeners to
  // them too
  
  function changed() {
    if (!aboutToCallHandler) {
      aboutToCallHandler = true;
      setTimeout(callHandler, 1);
    }
  }
  
  function callHandler() {
    try {
      handler();
    } finally {
      aboutToCallHandler = false;
    }
  }
}

Here's an example of a script that uses whenChanged. It modifies the page so that clicking on any link opens the link in a new tab, rather than the current tab. By using whenChanged(), it ensures that any new links added to the page (by AJAX or Javascript) are modified the same way. I use this on my iGoogle home page.

// ==UserScript==
// @name iGoogle
// @when Pages Match
// @description Makes links open in a new tab.
// @includes http://www.google.com/ig
// ==/UserScript==

openLinksInNewTab()

whenChanged(function() {
  openLinksInNewTab()
});

function openLinksInNewTab() {
  for (var link in find("link")) link.element.target = "_new";
}

function whenChanged(/*Function*/ handler) {
  var aboutToCallHandler = false;
  var docs = Chickenfoot.getAllFrameDocuments(document);
  for (var i = 0; i < docs.length; ++i) {
    var doc = docs[i];
    doc.addEventListener("DOMNodeInserted", changed, false);
    doc.addEventListener("DOMNodeRemoved", changed, false);
    doc.addEventListener("DOMAttrModified", changed, false);
  }
  // TODO: when new iframes appear, we need to attach listeners to
  // them too
  
  function changed() {
    if (!aboutToCallHandler) {
      aboutToCallHandler = true;
      setTimeout(callHandler, 1);
    }
  }
  
  function callHandler() {
    try {
      handler();
    } finally {
      aboutToCallHandler = false;
    }
  }
}