1/** 2 * Click to load videos for FI_GAGAR::ContentElements Video elements. 3 * 4 * The storefront renders a preview image inside a real <button>. Nothing is requested from 5 * the video service until the visitor presses it, which keeps the visitor's data out of the 6 * video service (GDPR) and keeps the page light. 7 * 8 * One delegated listener handles every video on the page, so a page with many videos costs 9 * no more than a page with one. 10 */ 11(function () { 12 'use strict'; 13 14 var FACADE_SELECTOR = 'button.VilkasContentElementVideoFacade'; 15 var IFRAME_ALLOW = 'accelerometer; autoplay; clipboard-write; encrypted-media; ' + 16 'gyroscope; picture-in-picture; web-share'; 17 18 // WCAG 2.2.2: the visitor asked to watch a video, so stop rotating the slider it sits in. 19 function stopSurroundingSlider(node) { 20 var slider = node.closest('.VilkasContentElementImageSlider'); 21 if (slider && slider.swiper && slider.swiper.autoplay) { 22 slider.swiper.autoplay.stop(); 23 } 24 } 25 26 function announce(message) { 27 if (!message) { 28 return; 29 } 30 var existing = document.getElementById('VilkasContentElementVideoAnnouncer'); 31 if (existing) { 32 existing.remove(); 33 } 34 var announcer = document.createElement('div'); 35 announcer.id = 'VilkasContentElementVideoAnnouncer'; 36 announcer.className = 'VilkasSrOnly'; 37 announcer.setAttribute('role', 'status'); 38 announcer.setAttribute('aria-live', 'polite'); 39 announcer.setAttribute('aria-atomic', 'true'); 40 announcer.textContent = message; 41 document.body.appendChild(announcer); 42 } 43 44 function loadVideo(button) { 45 var wrapper = button.parentNode; 46 var announcement = button.getAttribute('data-vilkas-video-announcement'); 47 48 // A video file is already sitting behind the button with preload none, so there is 49 // nothing to inject: take the button away and play what is there. 50 var file = wrapper.querySelector('video'); 51 if (file) { 52 stopSurroundingSlider(button); 53 button.remove(); 54 var playing = file.play(); 55 if (playing && playing.catch) { 56 playing.catch(function () { /* the browser declined, controls are still there */ }); 57 } 58 if (file.focus) { 59 file.focus(); 60 } 61 announce(announcement); 62 return; 63 } 64 65 var source = button.getAttribute('data-vilkas-video-src'); 66 if (!source) { 67 return; 68 } 69 70 // The click is the user gesture that permits playback, so autoplay is safe to add 71 // here. It is deliberately not stored in the address, so that nothing autoplays 72 // when click to load is switched off. 73 var separator = source.indexOf('?') === -1 ? '?' : '&'; 74 var iframe = document.createElement('iframe'); 75 iframe.className = 'VilkasContentElementVideoPlayer'; 76 iframe.setAttribute('src', source + separator + 'autoplay=1'); 77 iframe.setAttribute('title', button.getAttribute('data-vilkas-video-title') || ''); 78 iframe.setAttribute('loading', 'eager'); 79 iframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin'); 80 iframe.setAttribute('allow', IFRAME_ALLOW); 81 iframe.setAttribute('allowfullscreen', 'allowfullscreen'); 82 83 stopSurroundingSlider(button); 84 button.remove(); 85 wrapper.appendChild(iframe); 86 // Keyboard users must not lose their place in the page. 87 iframe.focus(); 88 announce(announcement); 89 } 90 91 document.addEventListener('click', function (event) { 92 if (!event.target || !event.target.closest) { 93 return; 94 } 95 var button = event.target.closest(FACADE_SELECTOR); 96 if (!button) { 97 return; 98 } 99 event.preventDefault(); 100 loadVideo(button); 101 }); 102 103 // The play event does not bubble, so it has to be caught in the capture phase. 104 document.addEventListener('play', function (event) { 105 var video = event.target; 106 if (!video || !video.matches || !video.matches('video.VilkasContentElementVideoPlayer')) { 107 return; 108 } 109 stopSurroundingSlider(video); 110 }, true); 111 112 /** 113 * Video files on a slide of an image slider. 114 * 115 * These are not given the autoplay attribute, because that would start every slide's 116 * video while the slider is still out of sight. Instead the video of the slide the 117 * visitor is actually looking at is started here, and the slider is held still until 118 * the video ends. Only the merchant's own video files get this treatment: a video 119 * service is never contacted before the visitor clicks its play button. 120 */ 121 var AUTOPLAY_SLIDE_SELECTOR = 'video[data-vilkas-video-autoplay-in-slider]'; 122 123 function slideOf(video) { 124 return video.closest('.swiper-slide'); 125 } 126 127
128 function syncSliderVideos(slider, isVisible) { 129 var videos = slider.querySelectorAll(AUTOPLAY_SLIDE_SELECTOR); 130 Array.prototype.forEach.call(videos, function (video) { 131 var slide = slideOf(video); 132 var isActive = isVisible && slide && slide.classList.contains('swiper-slide-active'); 133 if (isActive) { 134 // Muted is required, otherwise the browser refuses to start it. 135 video.muted = true; 136 var started = video.play(); 137 if (started && started.catch) { 138 started.catch(function () { /* the browser declined, leave the poster up */ }); 139 } 140 } else if (!video.paused) { 141 video.pause(); 142 video.currentTime = 0; 143 } 144 }); 145 } 146 147 function initSlider(slider) { 148 if (slider.dataset.vilkasVideoSliderBound) { 149 return; 150 } 151 if (!slider.querySelector(AUTOPLAY_SLIDE_SELECTOR)) { 152 return; 153 } 154 slider.dataset.vilkasVideoSliderBound = '1'; 155 156 var onScreen = true; 157 if (typeof IntersectionObserver === 'function') { 158 onScreen = false; 159 new IntersectionObserver(function (entries) { 160 entries.forEach(function (entry) { 161 onScreen = entry.isIntersecting; 162 syncSliderVideos(slider, onScreen); 163 }); 164 }, {threshold: 0.25}).observe(slider); 165 } 166 167 // Swiper is created by an inline script that may run after this file, so hooking 168 // swiper.on() here would silently do nothing. The active slide is marked with a 169 // class either way, so watch the class instead of the Swiper API. 170 var pending = null; 171 new MutationObserver(function () { 172 if (pending) { 173 return; 174 } 175 pending = setTimeout(function () { 176 pending = null; 177 syncSliderVideos(slider, onScreen); 178 }, 50); 179 }).observe(slider, {subtree: true, attributes: true, attributeFilter: ['class']}); 180 181 // Let the slider move on once the video has had its turn. 182 slider.addEventListener('ended', function (event) { 183 if (!event.target.matches || !event.target.matches(AUTOPLAY_SLIDE_SELECTOR)) { 184 return; 185 } 186 if (slider.swiper && slider.swiper.autoplay && slider.swiper.params.autoplay) { 187 slider.swiper.autoplay.start(); 188 } 189 }, true); 190 191 syncSliderVideos(slider, onScreen); 192 } 193 194 function initAllSliders() { 195 var sliders = document.querySelectorAll('.VilkasContentElementImageSlider'); 196 Array.prototype.forEach.call(sliders, initSlider); 197 } 198 199 if (document.readyState === 'loading') { 200 document.addEventListener('DOMContentLoaded', initAllSliders); 201 } else { 202 initAllSliders(); 203 } 204 // Swiper is initialised from an inline script per slider, which may run after this file. 205 window.addEventListener('load', initAllSliders); 206}());
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.