PageSourceSearch

https://dons.restosducoeur.org/libs.iraiser.eu/libs/payment/frame/1.8/IRaiserFrame.js

js restosducoeur.org collected 2026-09-24 08:31:58 UTC 34,994 bytes, 739 lines download raw bytes

1if (typeof IRaiserFrame === 'undefined') {
2    var IRaiserFrame = {
3
4        currentScript: document.currentScript,
5
6        // Iframe title attribute translations, keyed by the same ISO codes as allowedLangs.
7        // Used for screen reader accessibility, the iframe content itself is localized by DF.
8        TITLES: {
9            donation: {
10                ar_AR: 'نموذج التبرع',
11                bg_BG: 'Формуляр за дарение',
12                cs_CZ: 'Dárcovský formulář',
13                da_DK: 'Donationsformular',
14                de_DE: 'Spendenformular',
15                el_GR: 'Φόρμα δωρεάς',
16                en_EN: 'Donation form',
17                en_HK: 'Donation form',
18                en_ME: 'Donation form',
19                es_ES: 'Formulario de donación',
20                et_EE: 'Annetusvorm',
21                fi_FI: 'Lahjoituslomake',
22                fr_FR: 'Formulaire de don',
23                hr_HR: 'Obrazac za donaciju',
24                hu_HU: 'Adományozási űrlap',
25                id_ID: 'Formulir donasi',
26                it_IT: 'Modulo di donazione',
27                ja_JP: '寄付フォーム',
28                ka_GE: 'შემოწირულობის ფორმა',
29                kk_KZ: 'Қайырымдылық пішіні',
30                ko_KR: '기부 양식',
31                nl_NL: 'Donatieformulier',
32                no_NO: 'Donasjonsskjema',
33                pl_PL: 'Formularz darowizny',
34                pt_PT: 'Formulário de doação',
35                ro_RO: 'Formular de donație',
36                ru_RU: 'Форма пожертвования',
37                se_SE: 'Donationsformulär',
38                sk_SK: 'Darovací formulár',
39                sl_SI: 'Obrazec za donacijo',
40                sr_RS: 'Образац за донацију',
41                tr_TR: 'Bağış formu',
42                uk_UA: 'Форма пожертви',
43                vi_VN: 'Mẫu quyên góp',
44                zh_CN: '捐款表单',
45                zh_TW: '捐款表單',
46            },
47            presentation: {
48                ar_AR: 'عرض الحملة',
49                bg_BG: 'Представяне на кампанията',
50                cs_CZ: 'Představení kampaně',
51                da_DK: 'Kampagnepræsentation',
52                de_DE: 'Kampagnenpräsentation',
53                el_GR: 'Παρουσίαση εκστρατείας',
54                en_EN: 'Campaign presentation',
55                en_HK: 'Campaign presentation',
56                en_ME: 'Campaign presentation',
57                es_ES: 'Presentación de la campaña',
58                et_EE: 'Kampaania tutvustus',
59                fi_FI: 'Kampanjan esittely',
60                fr_FR: 'Présentation de la campagne',
61                hr_HR: 'Predstavljanje kampanje',
62                hu_HU: 'A kampány bemutatása',
63                id_ID: 'Presentasi kampanye',
64                it_IT: 'Presentazione della campagna',
65                ja_JP: 'キャンペーン紹介',
66                ka_GE: 'კამპანიის პრეზენტაცია',
67                kk_KZ: 'Науқан таныстырылымы',
68                ko_KR: '캠페인 소개',
69                nl_NL: 'Campagnepresentatie',
70                no_NO: 'Presentasjon av kampanjen',
71                pl_PL: 'Prezentacja kampanii',
72                pt_PT: 'Apresentação da campanha',
73                ro_RO: 'Prezentarea campaniei',
74                ru_RU: 'Презентация кампании',
75                se_SE: 'Kampanjpresentation',
76                sk_SK: 'Predstavenie kampane',
77                sl_SI: 'Predstavitev kampanje',
78                sr_RS: 'Представљање кампање',
79                tr_TR: 'Kampanya sunumu',
80                uk_UA: 'Презентація кампанії',
81                vi_VN: 'Giới thiệu chiến dịch',
82                zh_CN: '活动介绍',
83                zh_TW: '活動介紹',
84            },
85        },
86
87        getIframeTitle(kind, src) {
88            let lang = 'en_EN';
89            try {
90                lang = new URL(src).searchParams.get('lang') || 'en_EN';
91            } catch (e) { /* fall back to en_EN */ }
92            return IRaiserFrame.TITLES[kind][lang] || IRaiserFrame.TITLES[kind].en_EN;
93        },
94
95        // Ajout d'une fonction pour rechercher les ancres dans les href
96        processAnchors() {
97            document.querySelectorAll('a[href*="#iraiser_popin"], a[href*="#iraiser_native"]').forEach(element => {
98                let href = element.getAttribute('href');
99
100                // Vérification de l'ancre et ajout de la classe appropriée
101                const isPopin = href.includes('#iraiser_popin');
102                if (isPopin) {
103                    element.classList.add('iraiser_popin');
104                    href = href.replace('#iraiser_popin', '');
105                } else if (href.includes('#iraiser_native')) {
106                    element.classList.add('iraiser_native');
107                    href = href.replace('#iraiser_native', '');
108                }
109
110                if(href.includes('#scroll')){
111                    element.setAttribute('data-scroll', 'true');
112                    href = href.replace('#scroll', '');
113                }
114
115                // #noleftside is popin-only (native has no left iframe)
116                if(isPopin && href.includes('#noleftside')){
117                    element.setAttribute('data-noleftside', 'true');
118                    href = href.replace('#noleftside', '');
119                }
120
121                // #step2: open the donation form directly on step 2 (requires amount & frequency query params)
122                if(href.includes('#step2')){
123                    element.setAttribute('data-step', '2');
124                    href = href.replace('#step2', '');
125                }
126
127                // Mise à jour du href sans l'ancre
128                element.setAttribute('href', href);
129            });
130        },
131        
132        // Adds a query parameter to the URL
133        addQueryParam(url, param, value) {
134            const urlObj = new URL(url);
135            urlObj.searchParams.set(param, value);
136            return urlObj.toString();
137        },
138
139        // Corrects iframe src URL by modifying path and adding necessary parameters
140        correctIframeSrc(url, type, busId, element) {
141            const urlObj = new URL(url);
142            const originUrlObj = new URL(url);
143            urlObj.search = '';
144
145            urlObj.searchParams.set('private_window', type);
146            urlObj.searchParams.set('reserved_donation_embed_form', type);
147            urlObj.searchParams.set('_cv', 1);
148
149            // Retrieve the language from the current page and add it as a query parameter
150            const pageLang = document.documentElement.lang.substring(0, 2).toLowerCase() || 'en';
151            let isoLang = originUrlObj.searchParams.get('lang');
152            if (!isoLang) {
153                // Construct the 4-character ISO code with underscore, handling special cases
154                switch (pageLang) {
155                    case 'en':
156                        isoLang = 'en_EN';
157                        break;
158                    case 'zh':
159                        isoLang = (document.documentElement.dataset.region === 'TW') ? 'zh_TW' : 'zh_CN';
160                        break;
161                    case 'et':
162                        isoLang = 'et_EE';
163                        break;
164                    case 'sl':
165                        isoLang = 'sl_SI';
166                        break;
167                    case 'sr':
168                        isoLang = 'sr_RS';
169                        break;
170                    case 'uk':
171                        isoLang = 'uk_UA';
172                        break;
173                    case 'el':
174                        isoLang = 'el_GR';
175                        break;
176                    default:
177                        isoLang = `${pageLang}_${pageLang.toUpperCase()}`;
178                        break;
179                }
180            }
181
182            const allowedLangs = [
183                'ar_AR', 'bg_BG', 'cs_CZ', 'da_DK', 'de_DE', 'el_GR', 'en_EN', 'en_HK', 'en_ME', 'es_ES', 'et_EE', 
184                'fi_FI', 'fr_FR', 'hr_HR', 'hu_HU', 'id_ID', 'it_IT', 'ja_JP', 'ka_GE', 'kk_KZ', 'ko_KR', 'nl_NL', 
185                'no_NO', 'pl_PL', 'pt_PT', 'ro_RO', 'ru_RU', 'se_SE', 'sk_SK', 'sl_SI', 'sr_RS', 'tr_TR', 'uk_UA', 
186                'vi_VN', 'zh_CN', 'zh_TW'
187            ];
188            
189            // If isoLang is not in the allowed list, default to 'en_EN'
190            if (!allowedLangs.includes(isoLang)) {
191                isoLang = 'en_EN';
192            }
193            
194            urlObj.searchParams.set('lang', isoLang);
195            // Add existing parameters from the URL if they start with 'utm_' or 'reserved_' and are not visitor identifiers
196            const identifierPatterns = [
197                /^(\d{8,}|[a-fA-F0-9]{16,})$/, // Numeric or hex identifiers
198                /^G-[A-Z0-9]{10}$/, // Google Analytics G-XXXXXXXXXX
199                /^fb\.\d+$/, // Facebook Analytics (fb.<number>)
200                /^ABTastyID$/, // AB Tasty identifier pattern
201                /^GA\d+\.\d+\.\d+$/, // Google Analytics specific identifier pattern
202            ];
203
204            const allowedKeys = [
205                'cid', 'country', 'amount', 'currency', 'frequency', 'free_amount', 'once_grid', 'regular_grid', 'affectation', 'once_grid[]', 'regular_grid[]'
206                // utm_*, reserved_*
207            ];
208
209            originUrlObj.searchParams.forEach((value, key) => {
210                if ((key.startsWith('utm_') || key.startsWith('reserved_')|| allowedKeys.includes(key))
211                    && !identifierPatterns.some(pattern => pattern.test(value))) {
212                    if (key.endsWith('[]')) {
213                        urlObj.searchParams.append(key, value);
214                    } else {
215                        urlObj.searchParams.set(key, value);
216                    }
217                }
218            });
219
220            // Set query parameters if element has corresponding data- attributes
221            allowedKeys.forEach((key) => {
222                let dataAttr = `data-${key}`;
223                if (element.hasAttribute(dataAttr)) {
224                    urlObj.searchParams.set(key, element.getAttribute(dataAttr));
225                }
226            });
227
228            const scriptUrl = IRaiserFrame.currentScript.src;
229            const version = scriptUrl.split('/').slice(-2, -1)[0];
230            const versionParam = 'private_embed_form_version=' + version;
231            const busParam = 'private_embed_form_bus=' + busId;
232
233            // Add 'iframe' and 'private_embed_form_bus' and 'private_embed_form_version' to the hash if not already present
234            if (urlObj.hash) {
235                if (!urlObj.hash.includes('iframe')) {
236                    urlObj.hash += '&iframe';
237                }
238                if (!urlObj.hash.includes('private_embed_form_bus')) {
239                    urlObj.hash += '&' + busParam;
240                }
241                if (!urlObj.hash.includes('private_embed_form_version')) {
242                    urlObj.hash += '&' + versionParam;
243                }
244            } else {
245                urlObj.hash = `#iframe&${busParam}&${versionParam}`;
246            }
247
248            // #step2 / data-step="2": embed.js renders the form directly on step 2 (no step-1 flash)
249            if (element.getAttribute('data-step') === '2' && !urlObj.hash.includes('private_embed_form_step')) {
250                urlObj.hash += '&private_embed_form_step=2';
251            }
252
253            return urlObj.toString();
254        },
255
256        // Transforms an element into an iframe if it is not already one
257        transformToIframe(element, type='native') {
258            const iframe = document.createElement('iframe');
259            iframe.classList.add('iraiser');
260
261            // Assign a random data-bus ID if not present
262            if (!iframe.getAttribute('data-bus')) {
263                iframe.setAttribute('data-bus', 'bus_' + Math.floor(Math.random() * 10 ** 16).toString());
264            }
265
266            // Copy attributes from the original element to the iframe
267            Array.from(element.attributes).forEach(attr => {
268                iframe.setAttribute(attr.name, attr.value);
269            });
270
271            // We MUST set the `allow` attribute before the src below, for the payment Permissions
272            // Policy to take effect: it's evaluated at load time, so setting it after has no effect
273            // until a reload. Otherwise Apple/Google Pay (PaymentRequest) get blocked on native
274            // iframes ("payment is not allowed").
275            iframe.setAttribute('allow', 'payment *');
276
277            // Set iframe source using corrected src or original element's src/href
278            const src = element.getAttribute('src') || element.getAttribute('href');
279            let correctedSrc;
280            try {
281                correctedSrc = IRaiserFrame.correctIframeSrc(src, type, iframe.getAttribute('data-bus'), element);
282            } catch (e) {
283                console.warn('IRaiserFrame: invalid or missing src/href, skipping element', element, e);
284                return null;
285            }
286            iframe.setAttribute('src', correctedSrc);
287
288            // Ensure a title is set for accessibility (screen readers)
289            if (!iframe.hasAttribute('title')) {
290                iframe.setAttribute('title', IRaiserFrame.getIframeTitle('donation', iframe.getAttribute('src')));
291            }
292
293            // Replace original element with the new iframe
294            element.parentNode.replaceChild(iframe, element);
295
296            // Allow the parent page to communicate dynamically the amounts to the iframe
297            // Add listener to detect changes to data- attributes
298            const observer = new MutationObserver((mutations) => {
299                mutations.forEach((mutation) => {
300                    const element = mutation.target;
301                    if (mutation.type === 'attributes' && mutation.attributeName.startsWith('data-') && !mutation.attributeName.startsWith('data-donation')) {
302                        const attributeName = mutation.attributeName;
303                        const attributeValue = element.getAttribute(attributeName);
304                        if (attributeValue) {
305                            if (['data-amount', 'data-frequency', 'data-currency'].includes(attributeName)) {
306                                // Collect all relevant values if they exist
307                                const amount = element.getAttribute('data-amount');
308                                const frequency = element.getAttribute('data-frequency');
309                                const currency = element.getAttribute('data-currency');
310
311                                let message = {};
312                                if (amount) message['amount'] = amount;
313                                if (frequency) message['frequency'] = frequency;
314                                if (currency) message['currency'] = currency;
315
316                                element.contentWindow.postMessage({'set_donation':message}, '*');
317                            } else {
318                                element.contentWindow.postMessage(
319                                    { [`set_${attributeName.replace('data-', '')}`]: attributeValue },
320                                    '*'
321                                );
322                            }
323                        }
324                    }
325                });
326            });
327
328            // Observe the iframe for changes to attributes starting with data-donation_
329            observer.observe(iframe, {
330                attributes: true,
331            });
332
333            return iframe;
334        },
335
336        setupNativeIframe(element, type='native') {
337            const iframe = IRaiserFrame.transformToIframe(element, type);
338            if (!iframe) {
339                return null;
340            }
341
342            iframe.style.margin = '0 auto';
343            iframe.style.display = 'block';
344            iframe.style.maxWidth = 'min(100%, 100vw, 580px)';
345            iframe.style.height = '550px';
346
347            if (!iframe.hasAttribute('width') && type=='native') {
348                iframe.setAttribute('width', '480px');
349            }
350
351            iframe.setAttribute('scrolling', 'no');
352            iframe.setAttribute('frameborder', '0');
353            iframe.setAttribute('marginheight', '0');
354            iframe.setAttribute('marginwidth', '0');
355
356            return iframe;
357        },
358
359        // Configures all iframes with the .iraiser_native class
360        setupNativeIframes() {
361            const iframes = document.querySelectorAll('.iraiser_native');
362
363            iframes.forEach((element) => {
364                try {
365                    IRaiserFrame.setupNativeIframe(element);
366                } catch (e) {
367                    console.warn('IRaiserFrame: failed to set up native iframe, skipping element', element, e);
368                }
369            });
370        },        
371
372        setupPopinIframes() {
373            document.querySelectorAll('.iraiser_popin').forEach(element => {
374                element.addEventListener('click', function (event) {
375                    if (!(this.getAttribute('data-scroll') === 'true')) {
376                        event.preventDefault(); // Prevent default behaviour (scroll to the top at popin opening) except if data-scroll is true
377                    }
378                    let href = this.getAttribute('data-href') || this.getAttribute('href');
379                    if (!href || href === '#') return;
380
381                    this.setAttribute('href', '#');
382                    this.setAttribute('data-href', href);
383
384                    if (!this.getAttribute('data-bus')) {
385                        this.setAttribute('data-bus', 'bus_' + Math.floor(Math.random() * 10 ** 16).toString());
386                    }
387
388                    const hideLeftside = this.getAttribute('data-noleftside') === 'true';
389
390                    let leftSrc, rightSrc;
391                    try {
392                        if (!hideLeftside) {
393                            leftSrc = IRaiserFrame.correctIframeSrc(href, 'head', this.getAttribute('data-bus'), this);
394                        }
395                        rightSrc = IRaiserFrame.correctIframeSrc(href, 'popin', this.getAttribute('data-bus'), this);
396                    } catch (e) {
397                        console.warn('IRaiserFrame: invalid popin href, aborting', href, e);
398                        return;
399                    }
400
401                    let checkElement = document.getElementById('iraiser-modal-overlay');
402                    if (checkElement) {
403                        checkElement.remove();
404                    }
405
406                    // Modal overlay creation
407                    const modalOverlay = document.createElement('div');
408                    modalOverlay.id = 'iraiser-modal-overlay';
409                    modalOverlay.classList.add('iraiser');
410
411                    // Modal content creation
412                    const modalContent = document.createElement('div');
413                    modalContent.classList.add('iraiser', 'modal-content');
414                    if (hideLeftside) {
415                        modalContent.classList.add('no-leftside');
416                    }
417
418                    // Close button creation
419                    const closeButton = document.createElement('div');
420                    closeButton.classList.add('iraiser', 'close-button');
421
422                    const closeSvg = `
423                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
424                            <circle cx="12" cy="12" r="12" fill="white" opacity="0.8"/>
425                            <line x1="6" y1="6" x2="18" y2="18" stroke="black" stroke-width="2"/>
426                            <line x1="6" y1="18" x2="18" y2="6" stroke="black" stroke-width="2"/>
427                        </svg>`;
428                    closeButton.innerHTML = closeSvg;
429
430                    // Iframe container creation
431                    const iframeContainer = document.createElement('div');
432                    iframeContainer.classList.add('iraiser', 'iframe-container');
433                    iframeContainer.style.zIndex = '1001';
434
435                    // Left iframe creation with corrected ID (skipped when data-noleftside="true")
436                    if (!hideLeftside) {
437                        const iframeLeft = document.createElement('iframe');
438                        iframeLeft.id = 'iraiser-iframe-left';
439                        iframeLeft.classList.add('iraiser');
440                        iframeLeft.style.border = 'none';
441                        iframeLeft.src = leftSrc;
442                        iframeLeft.setAttribute('title', IRaiserFrame.getIframeTitle('presentation', iframeLeft.src));
443                        iframeContainer.appendChild(iframeLeft);
444                    }
445
446                    // Right iframe creation with corrected ID
447                    const iframeRight = document.createElement('iframe');
448                    iframeRight.id = 'iraiser-iframe-right';
449                    iframeRight.classList.add('iraiser', 'iraiser_popin');
450                    iframeRight.setAttribute('allow', 'payment *');
451                    iframeRight.setAttribute('frameborder', '0');
452                    iframeRight.setAttribute('marginheight', '0');
453                    iframeRight.setAttribute('marginwidth', '0');
454                    iframeRight.src = rightSrc;
455                    iframeRight.setAttribute('title', IRaiserFrame.getIframeTitle('donation', iframeRight.src));
456                    iframeRight.style.border = 'none';
457                    iframeContainer.appendChild(iframeRight);
458
459                    modalContent.appendChild(closeButton);
460                    modalContent.appendChild(iframeContainer);
461                    modalOverlay.appendChild(modalContent);
462                    document.body.appendChild(modalOverlay);
463
464                    // Modal close functionality
465                    closeButton.addEventListener('click', function () {
466                        modalOverlay.remove();
467                        const styleElement = document.getElementById('hide-scrollbar-style');
468                        if (styleElement) styleElement.remove();
469                    });
470
471                    // Inject CSS styles as a <style> element
472                    const style = document.createElement('style');
473                    style.id = 'iraiser-dynamic-styles';
474                    style.textContent = `
475                        /* Modal overlay styling */
476                        #iraiser-modal-overlay.iraiser {
477                            display: flex;
478                            position: fixed;
479                            top: 0;
480                            left: 0;
481                            width: 100%;
482                            height: 100%;
483                            z-index: 99999999999999;
484                            background-color: rgba(0, 0, 0, 0.7);
485                            overflow-y: auto;
486                            overflow-x: hidden;
487                        }
488                        
489                        /* Modal content styling */
490                        .iraiser .modal-content {
491                            display: flex;
492                            flex-direction: row;
493                            align-items: center;
494                            position: relative;
495                            width: 100%;
496                            height: 100%;
497                            box-sizing: border-box;
498                            background-color: transparent;
499                        }
500                    
501                        /* Close button styling */
502                        .iraiser .close-button {
503                            position: absolute;
504                            top: 20px;
505                            right: 30px;
506                            cursor: pointer;
507                            z-index: 1002;
508                        }
509                    
510                        /* Iframe container styling */
511                        .iraiser .iframe-container {
512                            display: flex;
513                            gap: 20px;
514                            z-index: 1001;
515                        }
516                    
517                        /* Iframe styling for large screens */
518                        @media (min-width: 930px) {
519                            .iraiser .modal-content {
520                                justify-content: center;
521                                padding-top: calc((100vh - 700px) / 2);
522                                align-items: flex-start;
523                            }
524                            .iraiser .iframe-container {
525                                flex-direction: row;
526                                gap: 20px;
527                            }
528                            .iraiser iframe {
529                                border-radius: 12px;
530                            }
531                            .iraiser iframe#iraiser-iframe-left {
532                                width: 500px;
533                                height: 640px;
534                                min-height: min(98vh,640px);
535                                max-height: calc(47vh + 340px);
536                            }
537                            .iraiser iframe#iraiser-iframe-right {
538                                width: 380px;
539                                height: 640px;
540                                max-height: calc(47vh + 340px);
541                            }
542                        }
543                    
544                        /* Iframe styling for medium screens */
545                        @media (max-width: 929px) and (min-width: 500px) {
546                            .iraiser .iframe-container {
547                                flex-direction: column;
548                                gap: 0;
549                            }
550                            .iraiser iframe {
551                                width: 500px;
552                                max-width: 500px;
553                                height: 640px;
554                            }
555                            .iraiser iframe#iraiser-iframe-left {
556                                border-radius: 12px 12px 0 0;
557                            }
558                            .iraiser iframe#iraiser-iframe-right {
559                                border-radius: 0 0 12px 12px;
560                            }
561                        }
562                    
563                        /* Iframe styling for small screens */
564                        @media (max-width: 929px) {
565                            .iraiser .modal-content{
566                                align-items: center;
567                                flex-direction: column;
568                                justify-content: flex-start;
569                            }
570                            
571                            .iraiser iframe {
572                                width: 100vw;
573                                max-width: 500px;
574                                height: 640px;
575                            }
576                            .iraiser .iframe-container {
577                                flex-direction: column;
578                                gap: 0;
579                            }
580                            #iraiser-iframe-left{
581                                max-height:200px;
582                            }
583                        }
584        
585                        html:has(#iraiser-modal-overlay) {
586                            overflow: hidden;
587                        }
588
589                        /* When the left side is hidden, center the donation iframe alone */
590                        .iraiser .modal-content.no-leftside .iframe-container {
591                            gap: 0;
592                            justify-content: center;
593                        }
594                        @media (min-width: 930px) {
595                            .iraiser .modal-content.no-leftside iframe#iraiser-iframe-right {
596                                width: 480px;
597                                border-radius: 12px;
598                            }
599                        }
600                        @media (max-width: 929px) and (min-width: 500px) {
601                            .iraiser .modal-content.no-leftside iframe#iraiser-iframe-right {
602                                border-radius: 12px;
603                            }
604                        }
605                    `;
606                    document.head.appendChild(style);
607                });
608            });
609        },
610
611        // Fonction pour scroller jusqu'à p pixels en dessous de l'iframe dans la page parente
612        scrollToPixelsBelowIframe(iframe, p) {
613            var reference = window;
614
615            const iframeRect = iframe.getBoundingClientRect();
616            const absoluteTop = iframeRect.top + reference.scrollY;
617            const targetScrollPosition = absoluteTop + p - 80; // au cas ou un menu de 80px serait présent en overlay
618
619            reference.scrollTo({
620                top: targetScrollPosition,
621                behavior: 'smooth',
622            });
623        },
624
625        // Fonction pour vérifier si le pixel situé à p pixels du haut de l'iframe appartient à l'iframe
626        isPixelBelongingToIframe(iframe, p) {
627            const iframeRect = iframe.getBoundingClientRect();
628            const pixelX = iframeRect.left + 1;
629            const pixelY = iframeRect.top + p;
630            const elementAtPixel = document.elementFromPoint(pixel
630X, pixelY);
631
632            return elementAtPixel === iframe || iframe.contains(elementAtPixel);
633        },
634
635        // Fonction pour scroller si l'iframe est recouverte
636        scrollToIframeIfNotVisible(iframe, p = 0) {
637
638            if (iframe.classList.contains('iraiser_popin')) {
639                return; // don't scroll in parent if popin
640            }
641
642            if (!IRaiserFrame.isPixelBelongingToIframe(iframe, p)) {
643                IRaiserFrame.scrollToPixelsBelowIframe(iframe, p);
644            }
645        },
646
647        beforeunload: false,
648
649        // Handles incoming messages from iframes
650        handleIframeMessage(event) {
651            let iframe = null;
652            const iframes = document.getElementsByTagName('iframe');
653
654            for (let i = 0; i < iframes.length; i++) {
655                if (iframes[i].contentWindow === event.source) {
656                    iframe = iframes[i];
657                    break;
658                }
659            }
660            if (iframe) {
661                const data = event.data;
662
663                if (data) {
664                    if (!!data.height && !isNaN(data.height)) {
665                        iframe.style.height = Math.max(data.height+1, 200) + 'px';
666                    }
667
668                    if(data.interaction_init){
669                        IRaiserFrame.interactionInit = true;
670                    }
671
672                    if (!!data.current_step && IRaiserFrame.interactionInit) {
673                        if (!IRaiserFrame.beforeUnloadFlag) {
674                            IRaiserFrame.beforeUnloadFlag = true;
675                            window.addEventListener('beforeunload', (e) => {
676                                let message;
677                                if (iframe.hasAttribute('data-message')) {
678                                    message = iframe.getAttribute('data-message');
679                                } else {
680                                    const translations = {
681                                        fr: "Êtes-vous sûrs de vouloir abandonner votre don ? Pour revenir à l'étape précédente, cliquez sur la flèche située en haut à gauche du formulaire de don. Cliquez sur 'annuler' pour poursuivre votre don et sur 'confirmer' pour quitter ou changer de page.",
682                                        en: "Are you sure you want to abandon your donation? To return to the previous step, click the arrow at the top left of the donation form. Click 'cancel' to continue your donation and 'confirm' to leave or change the page.",
683                                        it: "Sei sicuro di voler abbandonare la tua donazione? Per tornare alla fase precedente, fai clic sulla freccia in alto a sinistra del modulo di donazione. Clicca su 'annulla' per continuare la tua donazione e su 'conferma' per lasciare o cambiare pagina.",
684                                        nl: "Weet u zeker dat u uw donatie wilt annuleren? Om terug te keren naar de vorige stap, klikt u op de pijl linksboven in het donatieformulier. Klik op 'annuleren' om door te gaan met uw donatie en op 'bevestigen' om de pagina te verlaten of te wijzigen."
685                                    };
686                                    const lang = document.documentElement.lang || 'en';
687                                    message = translations[lang] || translations['fr'];
688                                }
689                                (e || window.event).returnValue = message;
690                                return message;
691                            });
692                        }
693                        iframe.setAttribute('data-donation_step', data.current_step);
694                        let closestParent = iframe.closest('.iraiser.modal-content');
695                        if(closestParent){
696                            closestParent.setAttribute('data-donation_step', data.current_step);
697                        }
698                    }
699
700                    if (!!data.amount && !!data.frequency && !!data.currency) {
701                        iframe.setAttribute('data-donation_amount', data.amount);
702                        iframe.setAttribute('data-donation_frequency', data.frequency);
703                        iframe.setAttribute('data-donation_currency', data.currency);
704                    }
705
706                    if (!!data.scroll && IRaiserFrame.interactionInit) {
707                        IRaiserFrame.scrollToIframeIfNotVisible(iframe, data.scroll);
708                    }
709
710                    if(!!data.ack){
711                        if(data.ack.cid) {
712                            iframe.setAttribute('data-cid',data.ack.cid);
713                        }
714                        if(data.ack.lang) {
715                            iframe.setAttribute('data-lang',data.ack.lang);
716                        }
717                    }
718                }
719            }
720        },
721
722        ensureMessageListener() {
723            window.addEventListener('message', IRaiserFrame.handleIframeMessage);
724        },
725
726
727    // Initialization function
728        init() {
729            IRaiserFrame.processAnchors(); 
730            IRaiserFrame.setupNativeIframes();
731            IRaiserFrame.setupPopinIframes();
732            IRaiserFrame.ensureMessageListener();
733        },
734    };
735
736    window.addEventListener('load', function() {
737        IRaiserFrame.init();
738    });
739}

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.