57

I am creating a Chrome extension and trying to include a small text beside the SEND button of the gMail compose box.

I am using a MutationObserver to know when the compose box window appears. I am doing this by observing an element with class no since the compose box element is created as child of this element (class no).

When the user clicks on the compose button and the compose box window appears, then I place an element beside the SEND button using the .after() method. SEND button class name is .gU.Up.

These are the real class names of gMail and pretty weird too.

Below is the code I am using:

var composeObserver = new MutationObserver(function(mutations){ 
    mutations.forEach(function(mutation){
        mutation.addedNodes.forEach(function(node){
            $(".gU.Up").after("<td> <div> Hi </div> </td>");
        });
    });
});

var composeBox = document.querySelectorAll(".no")[2];
var config = {childList: true};
composeObserver.observe(composeBox,config);

The problem is that I constantly get following error:

Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'

Can anyone help? I have tried quite a few things and also looked at other answers here, but still am unable to get rid of this error.

Here is my manifest.json file:

{
    "manifest_version": 2,
    "name": "Gmail Extension",
    "version": "1.0",

    "browser_action": {
        "default_icon": "icon19.png",   
        "default_title": "Sales Analytics Sellulose"    
    },

    "background": {
        "scripts": ["eventPage.js"],
        "persistent": false
    },

    "content_scripts": [
    {
        "matches": ["https://mail.google.com/*"],
        "js": ["jquery-3.1.1.js", "insQ.min.js", "gmail_cs.js"]
    }
],

    "web_accessible_resources":[
        "compose_icon.png",
        "sellulosebar_icon.png" 
    ]
}

P.S. I have already tried the insertionquery library, but it has a few shortcomings. It doesn't let me be specific as to the changes in the specific element. I am yet to try the mutationsummary library, but since it uses MutationObserver, I figured the issue will persist.

Added from comment:
It is true that the selector is not giving me a node. I checked in the console, it's giving a object. I also checked in the console and it's selecting the appropriate element that I want to be observed.

However, when I add console.log for the element selected, it's showing as undefined. Which means, you are probably right about code executing prior to nodes coming into existence. Can you tell me how to make sure the delay happens? will 'setTimeout' work? How does it work in case of MutationObserver?

Makyen
  • 31,849
  • 12
  • 86
  • 121
geet mehar
  • 642
  • 1
  • 5
  • 10
  • 4
    The error makes it clear that the result of `document.querySelectorAll(".no")[2]` does not evaluate to a [Node](https://developer.mozilla.org/en-US/docs/Web/API/Node). You will need to look at the structure of the page to determine how to select the appropriate Node. It is possible/likely that your code is executing prior to the nodes you desire to find existing in the DOM. If so, you will need to delay adding the observer until such time as they exist (many methods to do so). – Makyen Nov 03 '16 at 11:52
  • thanks @Makyen I have added the manifest file. It is true that the selector is not giving me a node and i checked in console, it's giving a object. I also checked in console and it's selecting the appropriate element that I want to be observed.

    However, when I add 'console.log' for the element selected, it's coming as undefined. Which means, you are probably right about code executing prior to nodes coming into existence. Can you tell me how to make sure the delay happens? will 'setTimeout' work ? How does it work in case of mutationobserver ?
    – geet mehar Nov 04 '16 at 07:33

4 Answers4

53

As I mentioned in a comment, and Xan stated an answer, the error makes it clear that the result of document.querySelectorAll(".no")[2] does not evaluate to a Node.

From the information you provided in a comment, it is clear that the issue is that the node you desire to observe does not exist when your code executes. There are many ways to delay the execution of your code until that node is available. Some possibilities are:

  1. Using a setTimeout loop to poll until you detect that the element on which you want to put the MutationObserver is available:

    function addObserverIfDesiredNodeAvailable() {
        var composeBox = document.querySelectorAll(".no")[2];
        if(!composeBox) {
            //The node we need does not exist yet.
            //Wait 500ms and try again
            window.setTimeout(addObserverIfDesiredNodeAvailable,500);
            return;
        }
        var config = {childList: true};
        composeObserver.observe(composeBox,config);
    }
    addObserverIfDesiredNodeAvailable();
    

    This will find the appropriate node relatively shortly after it exists in the DOM. The viability of this method depends on how long after the insertion of the target node do you need the observer to be placed on it. Obviously, you can adjust the delay between polling attempts based on your needs.

  2. Create another MutationObserver to watch an ancestor node higher in the DOM for the insertion of the node on which you want to place your primary observer. While this will find the appropriate node immediately when it is inserted, it may be quite resource (CPU) intensive, depending on how high in the DOM you have to observe and how much activity there is with respect to DOM changes.
Makyen
  • 31,849
  • 12
  • 86
  • 121
  • Alright.. I tried 1st and 2nd way but not 3rd yet. 1st - I think you meant document_end since document_idle is the default state of "run_at" but anyway. Using 'document_end' the entire content script is not getting executed. 2nd - It worked but I have an issue now since I am adding an element beside SEND button, I use '$(".SendButtonClass").after()' . Issue is if i open one more compose box then '$(".SendButtonClass").after()' is executed twice for first compose box as well. Thanks for the help so far. – geet mehar Nov 04 '16 at 11:32
  • 1
    Just to let you know: I came up with another solution to add element beside SEND button without using observer. I am watching compose button for a click and adding a callback along with setTimeout to make the desired change. – geet mehar Nov 04 '16 at 11:35
  • @geetmehar, I should not have included #1 (now removed). I was miss-remembering the default as `document_end` when the default is actually `document_idle`. Thus, that suggestion was useless. Sorry for wasting your time on it. Using `document_end` should not have prevented your script from running, unless there was some other dependency which threw an error elsewhere. To diagnose the new issue you report I will need to see more code, and it really should be a new question (link to this one for context. (continued) – Makyen Nov 04 '16 at 11:46
  • @geetmehar, (continued) The brief description of the problem sounds like an event listener is being added too many times (or at least that is the beginning of the typical pattern for such). Other solutions than MutationObservers are often better. What is most appropriate depends on your code, what you are trying to do and the page on which you are doing it. It is hard to comment as to which is better without seeing all the code. However, I would normally lean to not using a MutationObserver unless necessary due to them having a tendency to be CPU intensive. – Makyen Nov 04 '16 at 11:47
  • Many thanks @Makyen for helping me out. I eventually used mutationobserver as it was easy to pick on current addednode through it. I didn't want previously added nodes to be changed when I do something inside it. I don't think new question would be necessary as I sorted it out. I wish we were in same place, we could catch up for a beer :) – geet mehar Nov 05 '16 at 06:29
  • is it possible to observe an `{}` for changes with `MutationObserver`?? – oldboy Jan 24 '20 at 22:47
  • 1
    @oldboy No, it's not possible to use MutationObserver to watch for changes in an `Object`. – Makyen Jan 24 '20 at 22:57
  • do u know of any way to do this other than constant polling? – oldboy Jan 24 '20 at 22:57
  • in firefox I can observer (listen) for a missing ID, when created the observer triggers. In chrome it gives an error if said ID is missing at the time I create the observer, to write compatible code, is it possible to observer `` and then narrow to only newly added ID of my interest? – S. W. G. Apr 19 '22 at 14:17
16

Try to use this with jQuery.

If you are developing a Chrome Extension and you are getting HTML element(Node) from the page or DOM in the content_script, then you will get Object and Node will be returned as property of the Object. You can then get Node from the Object to pass into observe(Node,config) method.

Example

var node = $("#elementId");  //this is wrong because if you logged this in the console you will get Object

var node = $("#elementId")[0];  //This gives the first property of the Object returned, and this is correct because if you logged this in the console you will get the Node element for which you want to detect changes in the DOM.
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
Code Me
  • 161
  • 1
  • 3
6

This error means that document.querySelectorAll(".no")[2] is not a Node.

Most likely this means there isn't such an element; querySelectorAll will always return a NodeList, even if it's empty; accessing non-existent members of the list succeeds without a runtime error, but returns undefined: in this sense, NodeList acts like an array.

"Wait, but it does! I run this code in the console and it works!" you may exclaim dramatically. That's because at the time you execute it, long after the document finishes loading, those elements exist.

So, you need to wait for this root element to be added; likely, with another MutationObserver to do the job.

Xan
  • 74,770
  • 16
  • 179
  • 206
  • Thanks @Xan for helping me out, you were right about the issue and I have sorted it out now using 'setTimeout' suggested by Makyen in the other answer. FYI: I didn't use another mutationobserver since it is resource intensive eventhough better than mutationevents but I think definitely more than setTimeout. Thanks again. – geet mehar Nov 05 '16 at 06:32
0

If you use jQuery, put this code in

$(document).ready(function(){ // your code });
wyz
  • 29
  • 5
  • While that could help, depending on how the observed element is loaded/created, it *still* might not be available. (Ask me how I know.) – Will Belden Jan 23 '22 at 12:52