Question

In a userscript, I do lots of detection of element changes using waitForKeyElements().
However, I've run into a specific example where waitForKeyElements doesn't reliably fire on change.
My userscript adjusts theirID to insert the internal span, such as you see here:

<span id='theirID' class='myclass'>
  <span class='myclass2'>text</span> more text
</span>

The site then changes this code to erase my internal span, so that it then looks like:

<span id='theirID' class='myclass'>
  some text
</span>

For some reason, on some cases, waitForKeyElements just doesn't get triggered on that change. I've tried to detect changes on the outer span, the inner span, the outer span's parent, its parent, etc.

So I'm now wondering if there's some other way to detect, for example, whether the element w/ myclass2 (the inner span) has vanished? I could poll $('.myclass2').length I suppose, but I wouldn't know where in the document it vanished from. I'd ideally know the specific parent that held the now-missing item.

Any ideas?

Was it helpful?

Solution

Here are two answers, one for this specific case (you're using waitForKeyElements, and another for the general case.

For this specific case:

Since you are using waitForKeyElements() already, you can take advantage of the return value in the node handler.

Suppose the target page originally had HTML like this:

<span id='theirID1' class='myclass'>
    Original text 1.
</span>

And your script used waitForKeyElements() like this:

waitForKeyElements ("span.myclass", wrapImportantWords);

function wrapImportantWords (jNode) {
    var newContent  = jNode.text ().replace (/Original/ig, "<span class='myclass2'>My GM</span>");
    jNode.html (newContent);
}

Yielding:

<span class="myclass" id="theirID1">
    <span class="myclass2">My GM</span> text 1.
</span>


Then, you could:

  1. Have wrapImportantWords return true -- which tells waitForKeyElements that the node was not found after all, so it keeps checking.
  2. Have that function also check to see if the appropriate span.myclass2 is (still) present.

Like so:

waitForKeyElements ("span.myclass", wrapImportantWords);

function wrapImportantWords (jNode) {
    if (jNode.has ("span.myclass2").length == 0) {
        var newContent  = jNode.text ().replace (/Original/ig, "<span class='myclass2'>My GM</span>");
        jNode.html (newContent);
    }
    return true;
}


Detecting vanished elements in general using the new MutationObserver:

In your specific case, this seems to be overkill. But, here's a method for general reference.

Notes:

  1. If you want to detect that a node is deleted, you have to set up the observer on its parent. Mutuation records don't seem to be available for when a node itself is deleted or completely rewritten.
    In this case, we want to know when span.myclass2s are deleted so we observe their parents (span.myclass).
  2. The Mutation Summary library allegedly makes this easier.

Here is a complete Firefox Greasemonkey script. You can test it against this page. (Note that the Chrome code is the same except for the usual changes due to the lack of @require.)

// ==UserScript==
// @name     _Node watcher 1
// @include  http://jsbin.com/exigal/*
// @include  http://YOUR_SERVER.COM/YOUR_PATH/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant    GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change introduced
    in GM 1.0.   It restores the sandbox.
*/

waitForKeyElements ("span.myclass", wrapImportantWords);

function wrapImportantWords (jNode) {
    var newContent  = jNode.text ().replace (
        /Original/ig, "<span class='myclass2'>My GM</span>"
    );
    jNode.html (newContent);
}

/*--- Start of Mutation observer code...
*/
var targetNodes      = $("span.myclass");
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var myObserver       = new MutationObserver (mutationHandler);
var obsConfig        = {
    childList: true, characterData: true, attributes: true, subtree: true
};

//--- Add a target node to the observer. Can only add one node at a time.
targetNodes.each ( function () {
    myObserver.observe (this, obsConfig);
} );

function mutationHandler (mutationRecords) {

    mutationRecords.forEach ( function (mutation) {

        if (    mutation.type                == "childList"
            &&  typeof mutation.removedNodes == "object"
        ) {
            var remdNodes       = $(mutation.removedNodes);
            if (remdNodes.is ("span.myclass2") ) {
                console.log ("Desired node was deleted!   Restoring...");
                var targNode    = $(mutation.target);
                wrapImportantWords (targNode);
            }
        }
    } );
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top