// DOM-mode capture: on each click, serialize the live document into a // self-contained HTML snapshot (R3 client half — runs inside the user's auth // context so protected images/styles resolve). Scripts stripped; form state // frozen; [data-dp-mask] elements masked. (function () { if (window.__dpDomCaptureInstalled) return; window.__dpDomCaptureInstalled = true; var busy = Promise.resolve(); async function toDataUrl(url) { try { var res = await fetch(url, { credentials: "include" }); if (!res.ok) return null; var blob = await res.blob(); if (blob.size > 4 * 1024 * 1024) return null; // cap single asset at 4MB return await new Promise(function (resolve) { var r = new FileReader(); r.onload = function () { resolve(r.result); }; r.onerror = function () { resolve(null); }; r.readAsDataURL(blob); }); } catch (_) { return null; } } async function collectCss() { var parts = []; for (var i = 0; i < document.styleSheets.length; i++) { var sheet = document.styleSheets[i]; try { var rules = sheet.cssRules; var buf = []; for (var j = 0; j < rules.length; j++) buf.push(rules[j].cssText); parts.push(buf.join("\n")); } catch (_) { // cross-origin: fetch the stylesheet ourselves if (sheet.href) { try { var res = await fetch(sheet.href, { credentials: "include" }); if (res.ok) parts.push(await res.text()); } catch (_) { /* skip */ } } } } return parts.join("\n\n"); } async function serialize() { var root = document.documentElement.cloneNode(true); // strip live/script-ish elements root.querySelectorAll("script,noscript,iframe,link[rel=stylesheet],style,object,embed").forEach(function (el) { el.remove(); }); // mask sensitive elements root.querySelectorAll("[data-dp-mask]").forEach(function (el) { el.textContent = "•••••"; }); // freeze form state from the LIVE dom onto the clone (index-aligned) var liveInputs = document.querySelectorAll("input,textarea,select"); var cloneInputs = root.querySelectorAll("input,textarea,select"); liveInputs.forEach(function (live, i) { var c = cloneInputs[i]; if (!c) return; if (live.tagName === "TEXTAREA") c.textContent = live.value; else if (live.tagName === "SELECT") { Array.prototype.forEach.call(c.options, function (o, k) { if (k === live.selectedIndex) o.setAttribute("selected", ""); else o.removeAttribute("selected"); }); } else { if (live.type === "checkbox" || live.type === "radio") { if (live.checked) c.setAttribute("checked", ""); else c.removeAttribute("checked"); } else if (live.type === "password") c.setAttribute("value", "•••••"); else c.setAttribute("value", live.value); } }); // inline images from the LIVE dom (auth context), index-aligned var liveImgs = document.querySelectorAll("img"); var cloneImgs = root.querySelectorAll("img"); for (var i = 0; i < liveImgs.length; i++) { var src = liveImgs[i].currentSrc || liveImgs[i].src; if (!src || src.startsWith("data:")) continue; var d = await toDataUrl(src); if (cloneImgs[i]) { if (d) { cloneImgs[i].setAttribute("src", d); cloneImgs[i].removeAttribute("srcset"); } else cloneImgs[i].setAttribute("src", src); // fall back to absolute URL } } var css = await collectCss(); var head = root.querySelector("head") || root.insertBefore(document.createElement("head"), root.firstChild); var style = document.createElement("style"); style.textContent = css; head.appendChild(style); var base = document.createElement("base"); base.setAttribute("href", location.origin); head.insertBefore(base, head.firstChild); return "\n" + root.outerHTML; } document.addEventListener( "click", function (e) { var xPct = (e.clientX / window.innerWidth) * 100; var yPct = (e.clientY / window.innerHeight) * 100; busy = busy.then(async function () { var html = await serialize(); chrome.runtime.sendMessage({ type: "domstep", html: html, xPct: xPct, yPct: yPct }); }); }, true, ); })();