I\'m writing a Chrome extension that injects scripts to the Google\'s search result page and modified all the results\' anchor elements.
My problem is that the resu
In general, you can use Mutation Observers to listen for document changes. To avoid recursion, simply disconnect the mutation observer before changing the document, then enable it again.
Conceptually, it is not much different from the DOMNodeInserted
event, so you can also remove the event listener, insert your nodes, then rebind the event listener. However, Mutation observers are more efficient, so you should use these instead of the DOM mutation events.
In this specific case (Google's search results), you can also use the hashchange event to detect when Google has rendered new search results. This method is only useful because there's a correlation between the location fragment, the search terms and the search results:
https://www.google.com/search?q=old#q=<new term>
).Example:
// On document load
printResult();
// Whenever the search term is changed
window.addEventListener('hashchange', function(event) {
printResult();
});
function printResult() {
// Example: Print first search result
console.log(document.querySelector('h3 a').href);
}
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);