1

I have a simple content script for my Chrome Extension that is supposed to grab the titles of YouTube videos. My problem is that it has only worked once. I tried accessing the child nodes of the HTMLCollection which should only be one but I get null of undefined. Doing something like:

element[0].innerText;

doesn't give me anything useful but from my understanding if I use getElementsByClassName and apply innerText on the first element with [0] it shoud work. It might be an issue with the html not being fully loaded as sometimes I get null but HTMLCollection always has the property that I want to access just sitting there.

Also:

element.length 

returns 0.

This is what I usually get with my script.

When it doesn't work.

Inside is the "innerText" property that I want to grab.

enter image description here

And this is what I got the one time it worked.

{
    "manifest_version": 2,
    "name": "test",
    "author": "Muhammad Amer",
    "description": "test",
    "version": "1.0",

    "content_scripts": [
    {
    "matches": [
      "https://www.youtube.com/*"
    ],

    "js": ["jquery-3.3.1.js", "content.js"]
    }
],

"permissions": [
    "https://www.youtube.com/*",
    "tabs",
    "activeTab", 
    "webNavigation"
    ]

}

    var element = document.getElementsByClassName("title style-scope ytd-video- 
    primary-info-renderer");
console.log(element);

    for (var i = 0; i < element.length; i++) {
    var songTitle = element[i].innerText;
    console.log(songTitle);
    }
muhammad amer
  • 45
  • 1
  • 8
  • `getElementsByClassName` returns a *live* `HTMLCollection`. Use `querySelectorAll` for a more intuitive construct, a static `NodeList`. Sounds like no such elements exist at the time you run the query. – CertainPerformance Jan 23 '19 at 08:14
  • try to convert your HTMLCollection to an Array -> `var element = Array.from(document.getElementsByClassName("title style-scope ytd-video- primary-info-renderer"));` – d-h-e Jan 23 '19 at 08:20
  • The problem is that youtube is a dynamic (AJAX) site that loads fully just once so your content script also runs just once. What you need is to detect the internal page transitions, see [this answer](https://stackoverflow.com/a/39508954) and [this example](https://stackoverflow.com/a/34100952). – wOxxOm Jan 23 '19 at 08:45
  • It seems that when I use `querySelectorAll` I get an empty nodeList so I tried querying for divs instead and actually got a result. It may be the class name causing issues now. I tried simplifying and grabbing by just the `.title` class but haven't had any success. Also converting the HTMLCollection to an array using `Array.from` just gives me a different form of the original issue. I'll keep trying. – muhammad amer Jan 23 '19 at 08:49
  • Okay, thank you, I'll take a look at those answers. – muhammad amer Jan 23 '19 at 08:52

1 Answers1

1

With the help of the following post, I found a solution: 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'

The first issue was my code being executed too early so the setTimeout() function solved that. The next issue was one I didn't know I would have until it was mentioned; YouTube loads it's pages dynamically so the content script only runs if the page refreshes which effectively may never happen. So mutationObserver() worked to solve that problem as suggested in the comments. One peculiar thing was that I had to observe the childlist and subtree for mutations instead of characterData even though the character data was what the thing that changed. From what I've read the old node is removed and the new one then inserted.

Here is the code:

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
        if (mutation.addedNodes.length) {
            if (mutation.type == 'childList') {
                console.log(mutation.target.innerText);
            }
        }
        })
    })

function checkNode() {
        var targetNode = document.querySelector("h1.title.style-scope.ytd-video-primary-info-renderer");
        if (!targetNode) {
            window.setTimeout(checkNode, 500);
            return;
        }
        console.log(targetNode.innerText);
        var config = {
            childList: true,
            subtree:true
        }
        observer.observe(targetNode, config)
    }
checkNode();
muhammad amer
  • 45
  • 1
  • 8