Search code examples
javascripthtmldomgoogle-chrome-extensioncontent-script

Unable to append <img> into <span> in content script of Chrome extension


I'm trying to append an <img> element into a <span> in the content script of my chrome extension. However, it seems that the append only has an effect when I append into the body of the document, as explained in content-script.js. To reproduce this, click the following link and open dev tools:

http://kart.finn.no/?mapType=norge&tab=soek_i_annonser&searchKey=search_id_realestate_lettings&mapTitle=Kart+over+bolig+til+leie+&ztr=1&

Search for the <img> whose id is seenIcon. It will be defined when appending into the body, but undefined in all other cases.

manifest.json

{
    "manifest_version": 2,

    "name": "Finn.no Property Blacklist",
    "description": "Hides finn.no property search results that you've marked as \"seen\".",
    "version": "1.0",

    "permissions": [
        "activeTab",
        "http://kart.finn.no/*",
        "storage"
    ],
    "content_scripts": [
        {
            "matches": ["http://kart.finn.no/*"],
            "js": ["content-script.js"]
        }
    ],
    "web_accessible_resources": [
        "*.png"
    ]
}

content-script.js

console.log("content script!")

function getIconSpanIfExists() {
    var spanClassName = "imagePoi";
    var matchingElements = document.getElementsByClassName(spanClassName);
    if (matchingElements.length > 1) {
        console.error(failureMessage("wasn't expecting more than one element with class name " + spanClassName));
        return null;
    }

    if (matchingElements.length === 0) {
        return null;
    }

    return matchingElements[0].parentNode;
}

function htmlReady() {
    return getIconSpanIfExists();
}

function addIcons() {
    var iconSpan = getIconSpanIfExists();

    // Append into body - works.
//    var icon = document.createElement("img");
//    icon.id = "seenIcon";
//    icon.src = chrome.extension.getURL("seen.png");
//    document.body.appendChild(icon);
//    console.log("appended " + icon.id + " into body");

    // Append into span - doesn't work, even though it says childNodes.length is 2.
    var icon = document.createElement("img");
    icon.id = "seenIcon";
    icon.src = chrome.extension.getURL("seen.png");
    icon.style.left = "200px";
    icon.style.top = "200px";
    iconSpan.appendChild(icon);
    console.log("appended " + icon.id + " into span with class imagePoi" + " new children: " + iconSpan.childNodes.length);

    // Modify innerHTML of span - doesn't work, even though innerHTML has the icon.
//    iconSpan.innerHTML += "\n<img id=\"seenIcon\""
//        + "src=\"" + chrome.extension.getURL("seen.png") + "\""
//        + "style=\"left: 200px; top: 200px;\">";
//    console.log(iconSpan.parentNode.id, iconSpan.innerHTML);
}

function init() {
    console.log("initialising content script");

    if (!htmlReady()) {
        console.log("not all HTML is loaded yet; waiting");

        var timer = setInterval(waitForHtml, 200);

        function waitForHtml() {
            console.log("waiting for required HTML elements...");
            if (htmlReady()) {
                clearInterval(timer);
                console.log("... found them!");
                addIcons();
            }
        }

        return;
    }
}

if (document.readyState === "complete") {
    console.log("document is complete")
    init();
} else {
    console.log("document is not yet ready; adding listener")
    window.addEventListener("load", init, false);
}

seen.png

seen.png

Why are the changes not reflected in the DOM?


Solution

  • The node is recreated by the site, so you need to wait a little after it's first appearance and only then add the new image.

    I've tested it with a simple userscript with MutationObserver which adds the new icon each time .imagePoi is added to the document, including the first two appearances and subsequent on zoom-in/out.

    setMutationHandler(document, '.imagePoi', function(observer, node) {
      node.parentNode.insertAdjacentHTML('beforeend',
        '<img src="http://www.dna-bioscience.co.uk/images/check-mark.gif">');
    });
    
    function setMutationHandler(baseNode, selector, cb) {
      var ob = new MutationObserver(function(mutations) {
        for (var i=0, ml=mutations.length, m; (i<ml) && (m=mutations[i]); i++)
          for (var j=0, nodes=m.addedNodes, nl=nodes.length, n; (j<nl) && (n=nodes[j]); j++)
            if (n.nodeType == 1) 
              if (n = n.matches(selector) ? n : n.querySelector(selector))
                if (!cb(ob, n))
                  return;
      });
      ob.observe(baseNode, {subtree:true, childList:true});
    }
    

    You can simplify the handler by utilizing the fact that .imagePoi's mutation record's target has finnPoiLayer in its class list. But it'll be prone to breaking when site layout slightly changes.