8

I have a Chrome extension, and I want to wait until an element is loaded before injecting content into the page.

I'm trying to inject a button:

myButton = document.createElement('button');
myButton.class = 'mybutton';
document.querySelector('.element_id').appendChild(myButton)

I have this at the top of my content script. It used to work just fine, but then it stopped working. The error that was displayed was:

Uncaught TypeError: Cannot read property 'appendChild' of null

In order to wait for the element with class id .element_id to load, I tried to use a MutationObserver

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        if (!mutation.addedNodes) return

        for (var i = 0; i < mutation.addedNodes.length; i++) {
            if (mutation.addedNodes[i].parentNode == document.querySelector('#outer-container')) {

                myButton = document.createElement('button');
                myButton.class = 'mybutton';
                document.querySelector('.element_id').appendChild(myButton)
        }
            var node = mutation.addedNodes[i]
        }
    })
})

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

When I used the mutation observer, the page would load an outer div element called outer-container, and there was no way for me to directly compare the class .element_id. The class .element_id is nested a number of layers into the outer div.

HOWEVER, the above did not work, and I still received the null property error.

Is there a better way to wait for some element to be loaded (which is loaded async), before injecting?

2 Answers 2

4

Don't forget to add childList and subtree property when observing changes.

var observer = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) {
        if (!mutation.addedNodes) {
            return;
        }
        for (var i = 0; i < mutation.addedNodes.length; i++) {
            if (mutation.addedNodes[i].classList.contains("element_id")) {
                // Your logic here
            }
        }
    });
});

observer.observe(document.body, {
    childList: true,
    subtree: true
});
Sign up to request clarification or add additional context in comments.

5 Comments

Sorry, I forgot to include the properties. I had childList, but it still fails :(
@confused, could you please try my code? I have verified and I believe it should work.
@HaibaraAi See my answer.
Also, don't forget subtree: true.
It's more reliable to observe document.documentElement instead of document.body, i.e. observer.observe(document.documentElement, ...)
1

An insertion into DOM may have the element in question deeper in the added node.

For example, this can be inserted into the DOM:

<div class="container">
  <div class="element_id">...</div>
  ...
</div>

In that case, the added node list will only contain the .container node.

The mutation will not list everything added, it's your responsibility to recursively dig into the added fragment looking through added nodes.

Using mutation-summary library may help you avoid such headaches.

var observer = new MutationSummary({
  rootNode: document.body,
  callback: function(summaries) {
    summaries.forEach(function(summary) {
      summary.added.forEach(function(idElement) {
        /* ... */
        idElement.appendChild(myButton);
      });
    });
  },
  queries: [{element: ".element_id"}]
});

If you don't want to use a library, you can try calling querySelector or querySelectorAll on addedNodes[i].

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.