1/** 2 * Vendor signup-form / flipbook embed activation across Blazor enhanced navigation. 3 * 4 * The curated vendor embeds (see docs/features/vendor-embed/implementation.md) are expanded 5 * server-side by InlineVendorEmbedExpander / EmbeddedContentBlock into the vendor's trusted 6 * markup, which for the script-loader vendors (Constant Contact, Paperform, FlipGorilla) 7 * includes <script> elements. 8 * 9 * Two distinct things break under Blazor enhanced navigation, and both are handled here: 10 * 11 * 1. FIRST VISIT â enhanced navigation patches the new page's markup into the existing DOM 12 * programmatically, and per the HTML spec a <script> inserted that way never executes (its 13 * "already started" flag is set). The embed container renders but the widget never boots. 14 * Fixed by replacing each inert script with a fresh, executable clone. 15 * 16 * 2. RETURN VISIT â a vendor whose runtime has already loaded may ignore a re-injected loader 17 * (Constant Contact guards re-initialisation via SignUpFormWidget._instances), while enhanced 18 * navigation has supplied a fresh, empty container. Such a vendor declares `reboot` to ask its 19 * runtime to re-scan. A vendor that re-scans on its own needs nothing here. 20 * 21 * A full page load needs neither path: the parser executes the scripts normally. 22 */ 23window.vendorEmbedInterop = (function () { 24 const ACTIVATED_ATTR = 'data-vendor-embed-activated'; 25 26 /** 27 * The script-loader vendors from EmbedVendorCatalog. Iframe-only vendors (GovDelivery, 28 * Adobe InDesign, Facebook) are absent deliberately: an <iframe> patched into the DOM 29 * loads normally and needs no activation. 30 * 31 * Each vendor declares `isRendered(container)`, because "has the vendor filled this in yet?" 32 * is vendor-specific â FlipGorilla ships a fallback link inside its own target, so a generic 33 * "no child elements" test would treat every flipbook as already rendered and never activate 34 * it. Keep these predicates matched to EmbedVendorCatalog's markup. 35 * 36 * `reboot` is optional and only for vendors that need waking on a return visit. A vendor 37 * without one falls through to script activation, which stays correct on a first visit. 38 */ 39 const VENDORS = [ 40 { 41 key: 'constant-contact', 42 container: '.ctct-embed', 43 // The widget replaces the empty form div's contents with the rendered form. 44 isRendered: container => { 45 const target = container.querySelector('.ctct-inline-form'); 46 return !target || target.children.length > 0; 47 }, 48 isLoaded: () => !!window.SignUpFormWidget, 49 // Re-scans the DOM for unrendered forms. Verified against the live widget. 50 reboot: () => window.SignUpFormWidget.main(), 51 }, 52 { 53 key: 'paperform', 54 container: '.paperform-embed', 55 // The embed script mounts into the [data-paperform-id] div, adding children to it. 56 isRendered: container => { 57 const target = container.querySelector('[data-paperform-id]'); 58 return !target || target.children.length > 0; 59 }, 60 isLoaded: () => !!window.Paperform, 61 // Deliberate no-op rather than `reboot: null`. __embed.min.js ends with an 62 // unconditional `In(), setInterval(In, 50), window.Paperform = {popup: ...}`: it 63 // installs a document-wide poller that mounts any [data-paperform-id] it has not seen 64 // (guarded per element by an internal _pfd flag), and it has no re-init guard of its 65 // own. So a return visit self-heals via that existing poller and needs no call here â 66 // but it must NOT fall through to script activation, which would re-execute the whole 67 // 36 KB bundle and stack another permanent 50 ms interval on every return visit. 68 // The runtime exposes only `popup`; there is no reload()/re-scan entry point to call. 69 selfHeals: true, 70 reboot: () => {}, 71 }, 72 { 73 key: 'flipgorilla', 74 container: '.flipgorilla-embed', 75 // The catalog emits a fallback <a> inside the target as the graceful-degradation 76 // path, so an unrendered flipbook already has one child. fg.js adds its own nodes 77 // on top, so "still only the fallback anchor" means unrendered. 78 isRendered: container => { 79 const target = container.querySelector('[name="flipgorilla"]'); 80 if (!target) return true; 81 const children = target.children; 82 return !(children.length === 0 83 || (children.length === 1 && children[0].tagName === 'A')); 84 }, 85 // fg.js exposes no documented re-scan entry point, so a return visit falls through 86 // to script activation below. 87 reboot: null, 88 }, 89 ]; 90 91 /** 92 * Replace an inert script with an executable clone. Resolves once an external script has 93 * loaded (or failed) so the next one in document order can run after it. 94 */ 95 function activateScript(script) { 96 const replacement = document.createElement('script'); 97 98 for (const attribute of Array.from(script.attributes)) { 99 replacement.setAttribute(attribute.name, attribute.value); 100 } 101 replacement.setAttribute(ACTIVATED_ATTR, ''); 102 103 if (!script.src) { 104 replacement.textContent = script.textContent; 105 script.replaceWith(replacement); 106 return Promise.resolve(); 107 } 108 109 // `async`/`defer` are meaningless for a dynamically inserted script and only defeat the 110 // ordering we enforce below, so drop them and sequence the loads ourselves. 111 replacement.removeAttribute('async'); 112 replacement.removeAttribute('defer'); 113 114 const loaded = new Promise(resolve => { 115 replacement.addEventListener('load', () => resolve(), { once: true }); 116 replacement.addEventListener('error', () => resolve(), { once: true }); 117 }); 118 119 script.replaceWith(replacement); 120 return loaded; 121 } 122 123 async function activateContainer(container, vendor) { 124 // Return visit: the runtime is already loaded, so re-running the loader is at best a 125 // no-op and at worst harmful (Paperform would re-execute its bundle and stack another 126 // polling interval). Hand off to the vendor instead â either an explicit re-scan 127 // (Constant Contact) or a deliberate no-op for a runtime that self-heals (Paperform). 128 if (vendor.reboot && vendor.isLoaded?.()) { 129 try { 130 vendor.reboot(); 131 } catch {
132 // A vendor internal changing shape must never break page navigation. 133 } 134 return; 135 } 136 137 const scripts = Array.from(container.querySelectorAll('script')) 138 .filter(script => !script.hasAttribute(ACTIVATED_ATTR)); 139 140 for (const script of scripts) { 141 // Deliberate redundancy: activateScript's clone also carries the marker (it copies 142 // every attribute, and this one is set before the copy), and `replaceWith` is 143 // synchronous, so the isConnected skip below already covers the interleavings. Marking 144 // here too is belt-and-braces against a future edit reordering those steps â either 145 // site alone is sufficient today; removing both breaks the marker test. 146 script.setAttribute(ACTIVATED_ATTR, ''); 147 148 // If an overlapping pass detached this script while we were awaiting an earlier one, 149 // its load event can never fire â skip it rather than awaiting a promise that would 150 // never settle and leave the caller hanging. 151 if (!script.isConnected) continue; 152 153 await activateScript(script); 154 } 155 } 156 157 async function activateEmbeds(root) { 158 const scope = root || document; 159 160 for (const vendor of VENDORS) { 161 for (const container of Array.from(scope.querySelectorAll(vendor.container))) { 162 // Skip embeds the vendor has already rendered, so repeated enhancedload events 163 // never re-boot a working widget. 164 if (vendor.isRendered(container)) continue; 165 await activateContainer(container, vendor); 166 } 167 } 168 } 169 170 return { activateEmbeds }; 171})(); 172 173(function () { 174 function activate() { 175 window.vendorEmbedInterop.activateEmbeds(document); 176 } 177 178 // Only enhanced navigation leaves embeds inert; a full load is handled by the parser. 179 window.Blazor?.addEventListener?.('enhancedload', activate); 180})();
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.