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:
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
Why are the changes not reflected in the DOM?
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.