You are right in that using "DOMNodeInserted" is not a good approach. If nothing else, it is part of the obsolete Mutation Events API, which has been deprecated (among other reasons) for being notoriously inefficient.
It has been replaced by the MutationObserver API, so this is what you should use instead. You can utilize a MutationObserver to observe "childList" DOM mutations on a root node and its descendants.
(If you choose this approach the mutation-summary library might also come in handy.)
After a (really shallow) search, I found out that (at least for me) Google places its results in a div
with id search
. Below is the code of a sample extension that does the following:
Registers a MutationObserver to detect the insertion of
div#search
into the DOM.Registers a MutationObserver to detect "childList" changes in
div#search
and its descendants.Whenever a
<a>
node is added, a function traverses the relevant nodes and modifies the links. (The script ignores<script>
elements for obvious reasons.)
This sample extension just encloses the link's text in ~~
, but you can easily change it to do whatever you need.
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"content_scripts": [{
"matches": [
...
"*://www.google.gr/*",
"*://www.google.com/*"
],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": false
}],
}
content.js:
console.log("Injected...");
/* MutationObserver configuration data: Listen for "childList"
* mutations in the specified element and its descendants */
var config = {
childList: true,
subtree: true
};
var regex = /<a.*?>[^<]*<\/a>/;
/* Traverse 'rootNode' and its descendants and modify '<a>' tags */
function modifyLinks(rootNode) {
var nodes = [rootNode];
while (nodes.length > 0) {
var node = nodes.shift();
if (node.tagName == "A") {
/* Modify the '<a>' element */
node.innerHTML = "~~" + node.innerHTML + "~~";
} else {
/* If the current node has children, queue them for further
* processing, ignoring any '<script>' tags. */
[].slice.call(node.children).forEach(function(childNode) {
if (childNode.tagName != "SCRIPT") {
nodes.push(childNode);
}
});
}
}
}
/* Observer1: Looks for 'div.search' */
var observer1 = new MutationObserver(function(mutations) {
/* For each MutationRecord in 'mutations'... */
mutations.some(function(mutation) {
/* ...if nodes have beed added... */
if (mutation.addedNodes && (mutation.addedNodes.length > 0)) {
/* ...look for 'div#search' */
var node = mutation.target.querySelector("div#search");
if (node) {
/* 'div#search' found; stop observer 1 and start observer 2 */
observer1.disconnect();
observer2.observe(node, config);
if (regex.test(node.innerHTML)) {
/* Modify any '<a>' elements already in the current node */
modifyLinks(node);
}
return true;
}
}
});
});
/* Observer2: Listens for '<a>' elements insertion */
var observer2 = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes) {
[].slice.call(mutation.addedNodes).forEach(function(node) {
/* If 'node' or any of its desctants are '<a>'... */
if (regex.test(node.outerHTML)) {
/* ...do something with them */
modifyLinks(node);
}
});
}
});
});
/* Start observing 'body' for 'div#search' */
observer1.observe(document.body, config);