PageSourceSearch

https://www2.gov.bm/js/vendor-embed-interop.3a3uszm43f.js

js www2.gov.bm collected 2026-09-29 03:21:50 UTC 8,816 bytes, 180 lines download raw bytes

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.