1

I'm trying to make a Greasemonkey script to add a button in a Facebook page. But I need to wait for the creation of an element to add the button on it.
I'm trying to use MutationObserver (As suggested here) to do it but there is an error.

function init() {
    console.log('Launched : ' + launched);
    if (!launched) {
        launched = true;
        main();
    }
}
console.log('barfoo');

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        if (!mutation.addedNodes) return;
        console.log(mutation.addedNodes.type());
        for (var i = 0; i < mutation.addedNodes.length; i++) {
            if (mutation.addedNodes[i].matches('div[class="pam _5shk uiBoxWhite bottomborder"]')) {
                console.log('init');
                init();
            }
        }
    })
});
console.log('bar');

try {
    observer.observe(document.body, {
        childlist: true,
        subtree: true,
        attributes: false,
        characterData: false,
    });
} catch (error) {
    console.log(error);
}
console.log('foobar');

But I got the following logs:

barfoo
bar
DOMException [TypeError: "The expression cannot be converted to return the specified type."
code: 0
nsresult: 0x805b0034
location: file:///home/vmonteco/.mozilla/firefox/f3xgmo8e.default/gm_scripts/Facebook_cleaner/Facebook_cleaner.user.js:230] Facebook_cleaner.user.js:239:3
foobar

What is the cause of this error? How could I fix this?

If the element is created before the execution of my script, will the MutationObserver still be triggered? (IE, if the element already exists when I create the MutationObserver).

NB: I tried to use $(document).ready(), but it's triggered before the creation of the element I want to add the button to.

NB-bis: I didn't post the whole script.

Community
  • 1
  • 1
vmonteco
  • 14,136
  • 15
  • 55
  • 86
  • Which line triggers the error? Add `debugger;` to invoke the debugger when devtools panel is opened and step through the script. Right now I see a nonsensical `mutation.addedNodes.type()` - there's no `type()` method in `addedNodes`. – wOxxOm Sep 01 '15 at 14:06
  • @wOxxOm I deleted the line with the `type()` call, it changes nothing. I don't know which line triggers the error, I think that's the `observer.observe()` call. Do you mean adding `debugger;` directly into my script? – vmonteco Sep 01 '15 at 14:12
  • 1
    1. `/Facebook_cleaner.user.js:230] Facebook_cleaner.user.js:239:3` which lines are these (230 and 239)? 2. Yes, I do. – wOxxOm Sep 01 '15 at 14:14
  • @wOxxOm The 230 one is the one with `observer.observe(document.body, {`, the 239 one is `console.log(error);`. – vmonteco Sep 01 '15 at 14:25
  • @wOxxOm But where is this error from? The fact I try to display it or from the `observer.observe()` call? – vmonteco Sep 01 '15 at 14:52
  • Remove try-catch, it's not needed, then you might see an actual line if the error occurs. – wOxxOm Sep 01 '15 at 15:11
  • @wOxxOm I removed it but I see no error, it just stops at the `observer.observe()` call. (That's why I added a try/catch statement in the first place). I can't figure out what the problem is. – vmonteco Sep 01 '15 at 15:16
  • Maybe it was a text node that doesn't have `matches`. Try testing for .nodeType==1 or simply print current node to console before invoking. matches. Or try simplifying the callback to one statement: `console.log(mutations)`. – wOxxOm Sep 01 '15 at 16:38

3 Answers3

13

That error message use totally vague unfortunately, but you have a typo:

observer.observe(document.body, {
  childlist: true,
  subtree: true,
  attributes: false,
  characterData: false,
});

should be

observer.observe(document.body, {
  childList: true,
  subtree: true,
  attributes: false,
  characterData: false,
});

e.g. childlist => childList

In this case, running your same code in Chrome gives a more helpful message than "cannot be converted to return the specified type". It says

The options object must set at least one of 'attributes', 'characterData', or 'childList' to true.

loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
1

Actually, MutationObserver is not the best way to do this (As you are learning the hard way. It quickly gets complicated and brittle. It can really bog down a web page, and it's not consistent across platforms).
Also, if the element exists before your script fires, MutationObserver will not catch it.

Use an approach like as shown in Run Greasemonkey script on the same page, multiple times?.

With that, your entire script becomes:

// ==UserScript==
// @name     _Wait for specific divs
// @include  http://YOUR_SERVER.COM/YOUR_PATH/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/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 (
    'div.pam _5shk.uiBoxWhite.bottomborder', processDiv
);

function processDiv (jNode) {
    //  YOUR CODE HERE.
    // For example:
    jNode.css ("background", "pink");
}

See how easy that is! :)
With waitForKeyElements() You can use any, complex jQuery selector as needed.


If you really insist on MutationObservers, use a library that takes much of the pain out of it.
This answer shows how to use MutationObserver in a Greasemonkey/Tampermonkey script.

Community
  • 1
  • 1
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • 2
    Arguable. I'm using MutationObserver for several years and had no pains with it except once. Moreover it allows you to create the new element immediately after the watched element appears without any visual delay. And there aren't that many sites/pages where observing the mutations would consume more than 0.1% of time. Or actually those are quite few. And in this case using the plain `for(var i=...)` loops for enumeration will considerably reduce the load. It's also possible to move the enumeration out of the current microjob into a new event job via `setTimeout(....,0)`. It can be flexible. – wOxxOm Sep 02 '15 at 13:54
-1

I see an opinion that MutationObserver is not a way to do this, but actually, it is. The nuance is that if you want to process nodes of a page that load right from the very beginning, then to not miss anything you have to run the Monkey script with the // @run-at document-start condition, and then attach the observer e.g. to the document.documentElement to catch absolutely everything (or to a later-appearing element if you are sure that nonthing will change in the page later that would disrupt this):

(function()
{
  new MutationObserver((mutationsList, observer) => { 
    for (let mutation of mutationsList)
    {
      if (mutation.type != 'childList' || !mutation.addedNodes.length)
      {
        continue;
      } 
      Array.from(mutation.addedNodes).forEach(function(e) // or forSome if you only want to catch one object such as a button
      {
        if (!(e instanceof Element))
        {
          return;
        }
        // do stuff
      });  
    }
  }).observe(document.documentElement, {childList: true, subtree: true});
})();
DDRRSS
  • 306
  • 3
  • 8