// manifest.json ------------------------------------------------------------------------------------------ { "manifest_version": 2, "name": "peephole", "version": "1.0", "description": "peephole", "permissions": [], "browser_action": { "default_title": "peephole" }, "content_scripts": [ { "matches": ["*://*/*"], "js": ["main.js"] } ] } // main.js ------------------------------------------------------------------------------------------------ (() => { /** * Check and set a global guard variable. * If this content script is injected into the same page again, * it will do nothing next time. */ console.log(browser); if (window.hasRun) { return; } window.hasRun = true; var matchText = function(node, regex, callback, excludeElements) { excludeElements || (excludeElements = ['script', 'style', 'iframe', 'canvas']); var child = node.firstChild; while (child) { switch (child.nodeType) { case 1: if (excludeElements.indexOf(child.tagName.toLowerCase()) > -1) break; matchText(child, regex, callback, excludeElements); break; case 3: var bk = 0; child.data.replace(regex, function(all) { var args = [].slice.call(arguments), offset = args[args.length - 2], newTextNode = child.splitText(offset+bk), tag; bk -= child.data.length + all.length; newTextNode.data = newTextNode.data.substr(all.length); tag = callback.apply(window, [child].concat(args)); child.parentNode.insertBefore(tag, newTextNode); child = newTextNode; }); regex.lastIndex = 0; break; } child = child.nextSibling; } return node; }; // Function to recursively modify all text nodes in a node and its children function modifyAllTextNodes(node) { matchText(node, /o|0/gi, function(node, match, offset) { var button = document.createElement("span"); const href = node.parentNode.attributes["href"]?.value; // button.innerText = match; if (node.parentNode.tagName === "A" && href) { button.className = "fabulation-ornament"; const iframe = document.createElement("iframe"); iframe.className = "fabulation-peephole"; iframe.width = "500"; iframe.height = "500"; iframe.src = href; button.appendChild(iframe); } else { button.innerText = match; } return button; }); } modifyAllTextNodes(document.body); const style = document.createElement("style"); style.innerText = ` .fabulation-ornament { position: relative; width: 20px; height: 20px; border: 2px solid black; border-radius: 50px; display: inline-block; vertical-align: middle; } .fabulation-peephole { position: absolute; transform: scale(0.04); transform-origin: top left; border-radius: 9999px; top: 0; left: 0; } `; document.body.appendChild(style); })();