1document.addEventListener("DOMContentLoaded", function () { 2 const STORAGE_KEY = "iab_utm_values"; 3 const utmParams = [ 4 "utm_id", 5 "utm_source", 6 "utm_medium", 7 "utm_campaign", 8 "utm_term", 9 "utm_content", 10 ]; 11 12 function readStoredUTMs() { 13 try { 14 const raw = localStorage.getItem(STORAGE_KEY); 15 if (!raw) return {}; 16 const parsed = JSON.parse(raw); 17 if (!parsed || typeof parsed !== "object") return {}; 18 const filtered = {}; 19 utmParams.forEach(function (key) { 20 if (parsed[key]) filtered[key] = parsed[key]; 21 }); 22 return filtered; 23 } catch (e) { 24 return {}; 25 } 26 } 27 28 function writeStoredUTMs(utms) { 29 try { 30 localStorage.setItem(STORAGE_KEY, JSON.stringify(utms)); 31 } catch (e) { 32 // localStorage unavailable or quota exceeded â silent 33 } 34 } 35 36 function readCurrentUTMs() { 37 const params = new URLSearchParams(window.location.search); 38 const out = {}; 39 utmParams.forEach(function (key) { 40 if (!params.has(key)) return; 41 const value = params.get(key); 42 if (value) out[key] = value; 43 }); 44 return out; 45 } 46 47 const storedUTMs = readStoredUTMs(); 48 const currentUTMs = readCurrentUTMs(); 49 const utmsToApply = Object.assign({}, storedUTMs, currentUTMs); 50 51 if (Object.keys(currentUTMs).length > 0) { 52 writeStoredUTMs(utmsToApply); 53 } 54 55 // Blackthorn links 56 document.querySelectorAll('a[href*="blackthorn.io"]').forEach(function (link) { 57 try { 58 const url = new URL(link.getAttribute("href"), window.location.href); 59 Object.keys(utmsToApply).forEach(function (key) { 60 url.searchParams.set(key, utmsToApply[key]); 61 }); 62 link.href = url.toString(); 63 } catch (e) { 64 console.error("iab-utm-persist: error modifying Blackthorn link:", e); 65 } 66 }); 67 68 // HubSpot embedded forms â same-origin iframe contentDocument access 69 // HubSpot renders forms inside iframes via srcdoc/document.write (no src 70 // attribute). Iframes are same-origin so we can reach in and populate 71 // hidden utm_* fields. Overwrite-always: utmsToApply already has URL 72 // values merged over stored, so overwriting can't clobber a fresh URL 73 // UTM, but it does override stale HubSpot returning-visitor defaults. 74 function applyToIframeForm(iframe) { 75 try { 76 const doc = iframe.contentDocument; 77 if (!doc) return; 78 const form = doc.querySelector("form.hs-form"); 79 if (!form) return; 80 Object.keys(utmsToApply).forEach(function (key) { 81 const input = form.querySelector('input[name="' + key + '"]'); 82 if (!input) return; 83 if (input.value === utmsToApply[key]) return; 84 input.value = utmsToApply[key]; 85 input.dispatchEvent(new Event("input", { bubbles: true })); 86 input.dispatchEvent(new Event("change", { bubbles: true })); 87 }); 88 } catch (e) { 89 // contentDocument may be cross-origin or not yet ready â silent 90 } 91 } 92 93 function watchHubSpotIframe(iframe) { 94 if (iframe.dataset.utmWatched) return; 95 iframe.dataset.utmWatched = "1"; 96 97 let innerObserver = null; 98 function attachInnerObserver() { 99 if (innerObserver) { 100 try { innerObserver.disconnect(); } catch (e) {} 101 innerObserver = null; 102 } 103 try { 104 const doc = iframe.contentDocument; 105 if (!doc) return; 106 innerObserver = new MutationObserver(function () { applyToIframeForm(iframe); }); 107 innerObserver.observe(doc, { childList: true, subtree: true }); 108 } catch (e) { 109 // contentDocument not accessible yet 110 } 111 } 112 113 applyToIframeForm(iframe); 114 attachInnerObserver(); 115 iframe.addEventListener("load", function () { 116 applyToIframeForm(iframe); 117 attachInnerObserver(); 118 }); 119 } 120 121 function scanForHubSpotIframes(root) { 122 if (!root) return; 123 if (root.matches && root.matches("iframe.hs-form-iframe")) { 124 watchHubSpotIframe(root); 125 return; 126 } 127 if (root.querySelectorAll) { 128 root.querySelectorAll("iframe.hs-form-iframe").forEach(watchHubSpotIframe); 129 } 130 } 131 132 scanForHubSpotIframes(document.body); 133 134 const observer = new MutationObserver(function (mutations) { 135 mutations.forEach(function (mutation) { 136 if (mutation.type !== "childList") return;
137 mutation.addedNodes.forEach(function (node) { 138 if (node.nodeType !== 1) return; 139 scanForHubSpotIframes(node); 140 }); 141 }); 142 }); 143 observer.observe(document.body, { childList: true, subtree: true }); 144});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.