PageSourceSearch

https://butterfly-monitoring.net/modules/contrib/youtube_cookies/js/youtube-cookies.js?tm6m8c

js butterfly-monitoring.net collected 2026-10-01 12:00:08 UTC 19,408 bytes, 663 lines download raw bytes

1/**
2 * @file
3 * Do not let videos be played until user accept cookies.
4 */
5
6/**
7 * Manage YouTube cookies on the page played videos.
8 *
9 * @param {string} action
10 *   Block YouTube videos until accept cookies / use youtube-nocookie domain.
11 * @param {string} thumbnailLabel
12 *   Message for the button that open the consent popup.
13 * @param {string} popupMessage
14 *   Message that explains the cookies tracking.
15 * @param {string} manageButton
16 *   Allow managing cookies.
17 * @param {string} okButton
18 *   Accept YouTube's tracking.
19 * @param {string} exitButton
20 *   Remove the button (only appears in popup).
21 * @param {string} thumbnailMarkup
22 *   HTML markup for the YouTube cookies thumbnail.
23 *
24 * @constructor
25 */
26function YoutubeCookies(
27  action,
28  thumbnailLabel,
29  popupMessage,
30  manageButton,
31  okButton,
32  exitButton,
33  thumbnailMarkup
34) {
35  this.checkYoutubeNoCookie = action !== "no_cookies_domain";
36  this.action = action;
37  this.youtubeCookiesAccepted = false;
38  this.manageCookiesCallback = null;
39  this.acceptedCookiesCallback = null;
40  this.thumbnailLabel = thumbnailLabel;
41  this.popupMessage = popupMessage;
42  this.thumbnailMarkup = thumbnailMarkup;
43  this.showThirdPartyButton = true;
44  this.manageButton = manageButton;
45  this.okButton = okButton;
46  this.exitButton = exitButton;
47  this.currentVideo = null;
48  this.interceptVideo = true;
49  this.setup();
50}
51
52const youtubeCookiesDisabled = new Event("youtubeCookiesDisabled");
53const youtubeCookiesEnabled = new Event("youtubeCookiesEnabled");
54
55/**
56 * Decide whether the manage cookies button should be shown.
57 *
58 * @param {boolean} value
59 *   If false, the 'Manage cookies' button won't appear.
60 */
61YoutubeCookies.prototype.setShowThirdPartyButton = value => {
62  this.showThirdPartyButton = value;
63};
64
65/**
66 * Callback that allow third parties show the manage cookies form.
67 *
68 * @param {function} callback
69 *   Callback that show the form.
70 */
71YoutubeCookies.prototype.setManageCookiesCallback = function (callback) {
72  this.manageCookiesCallback = callback;
73};
74
75/**
76 * Check that cookies are accepted.
77 *
78 * It allows to use a custom callback to determine that.
79 *
80 * @return {boolean|*}
81 *   TRUE when the cookies are accepted.
82 */
83YoutubeCookies.prototype.areCookiesAccepted = () => {
84  if (this.acceptedCookiesCallback !== null) {
85    return this.acceptedCookiesCallback();
86  }
87  return this.youtubeCookiesAccepted;
88};
89
90/**
91 * Set up the YouTube videos.
92 *
93 * Show / hide videos whether the
94 * cookies have been accepted.
95 *
96 * @param {boolean} youtubeCookiesAccepted
97 *   True or false according the provider value.
98 */
99YoutubeCookies.prototype.setAcceptedCookies = youtubeCookiesAccepted => {
100  if (youtubeCookiesAccepted) {
101    document
102      .querySelectorAll("iframe[data-yc-iframe-processed]")
103      .forEach(element => {
104        Drupal.youtubeCookies.destroyYoutuebeCookiesIframe(element.parentNode);
105      });
106    Drupal.youtubeCookies.removePopup();
107    if (Drupal.youtubeCookies.currentVideo !== null) {
108      // Propagate the click event to the oembed-lazy original button.
109      if (Drupal.youtubeCookies.isOembedLazyload(Drupal.youtubeCookies.currentVideo)) {
110        Drupal.youtubeCookies.currentVideo.querySelector(".oembed-lazyload__button").click();
111      }
112      Drupal.youtubeCookies.interceptVideo = true;
113      Drupal.youtubeCookies.currentVideo = null;
114    }
115    document.dispatchEvent(youtubeCookiesDisabled);
116  } else {
117    Drupal.youtubeCookies.processYoutubeIframes(document);
118    document.dispatchEvent(youtubeCookiesEnabled);
119  }
120  Drupal.youtubeCookies.youtubeCookiesAccepted = youtubeCookiesAccepted;
121};
122
123/**
124 * Set the callback that will be called when the cookies are not accepted.
125 *
126 * @param {function} callback
127 *   Funcgtion to execute.
128 */
129YoutubeCookies.prototype.onNonAcceptedCookies = callback => {
130  this.onNonAcceptedCookiesCallback = callback;
131};
132
133/**
134 * Set up the cookies' management.
135 */
136YoutubeCookies.prototype.setup = function() {
137  document.addEventListener(
138    "DOMContentLoaded",
139    this.onContentLoaded.bind(this)
140  );
141};
142
143/**
144 * Adds the YouTube cookies popup.
145 *
146 * @param {object} element
147 *   Element that will have the popup.
148 * @param {boolean} showExitButton
149 *   Weather to show the exit button or not.
150 *
151 * @return {HTMLDivElement}
152 *   Popup HTML
153 */
154YoutubeCookies.prototype.addPopup = function(element, showExitButton) {
155  const popupContainer = document.createElement("div");
156  popupContainer.classList.add("youtube-cookies-popup");
157  const popupContainerBox = document.createElement("div");
158  popupContainerBox.classList.add("youtube-cookies-popup-box");
159  const popupContainerBoxInfo = document.createElement("div");
160  popupContainerBoxInfo.classList.add("youtube-cookies-popup-box-info");
161  popupContainerBoxInfo.insertAdjacentHTML("beforeend", this.popupMessage);
162  const popupContainerBoxButtons = document.createElement("div");
163  popupContainerBoxButtons.classList.add("youtube-cookies-popup-box-buttons");
164  if (this.showThirdPartyButton) {
165    popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.manageButton);
166  }
167  popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.okButton);
168  if (showExitButton) {
169    popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.exitButton);
170  }
171  popupContainerBoxInfo.appendChild(popupContainerBoxButtons);
172  popupContainerBox.appendChild(popupContainerBoxInfo);
173  popupContainer.appendChild(popupContainerBox);
174  element.append(popupContainer);
175  popupContainer
176    .querySelector(".youtube-cookies-button--accept")
177    .addEventListener("click", this.onAcceptClick.bind(this));
178  if (showExitButton) {
179    popupContainer
180      .querySelector(".youtube-cookies-button--reject")
181      .addEventListener("click", this.onRejectClick.bind(this));
182  }
183  const manageCookiesButton = popupContainer.querySelector(
184    ".youtube-cookies-button--manage"
185  );
186  if (manageCookiesButton != null) {
187    manageCookiesButton.addEventListener(
188      "click",
189      this.onManageCookiesClick.bind(this)
190    );
191  }
192  return popupContainer;
193};
194
195/**
196 * Actions to do when the fake/yc thumbnail is clicked.
197 *
198 * @param {object} event
199 *   Event.
200 */
201YoutubeCookies.prototype.onThumbnailClick = function(event) {
202  if (!this.interceptVideo) {
203    // @TODO: Check if this condition is needed.
204    return;
205  }
206
207  event.preventDefault();
208  this.addPopup(document.body, true);
209  this.currentVideo = event.currentTarget.parentNode;
210  this.interceptVideo = false;
211};
212
213/**
214 * Remove the fake thumbnail added by yc.
215 *
216 * @param {object} iframeWrapper
217 *   The iframe container.
218 */
219YoutubeCookies.prototype.removeFakeThumbnail = function(iframeWrapper) {
220  iframeWrapper.querySelector("[data-yc-fake-button]").remove();
221  if (this.isOembedLazyload(iframeWrapper)) {
222    const button = iframeWrapper.querySelector(
223      "[data-yc-original-oembed-button]"
224    );
225    button.style.display = "";
226    button.removeAttribute("data-yc-original-oembed-button");
227  }
228};
229
230/**
231 * Dispatch event to allow other modules to add
232 * their specific behaviours when the thumbnail
233 * button is clicked.
234 *
235 * @param {object} button
236 *   Element that received the click.
237 */
238YoutubeCookies.prototype.eventDispatch = function(button) {
239  const iframeWrapper = button.currentTarget.parentNode;
240  const event = new Event("youtube-cookies-thumbnail-click");
241  iframeWrapper.querySelector("iframe").dispatchEvent(event);
242};
243
244/**
245 * Remove the popup.
246 */
247YoutubeCookies.prototype.removePopup = function() {
248  const popup = document.querySelector(".youtube-cookies-popup");
249  if (popup !== null) {
250    popup.remove();
251  }
252};
253
254/**
255 * When manage cookies is clicked show the manage cookies button.
256 */
257YoutubeCookies.prototype.onManageCookiesClick = function() {
258  if (this.manageCookiesCallback !== null) {
259    this.manageCookiesCallback();
260  }
261  this.interceptVideo = true;
262};
263
264/**
265 * When cookies are accepted videos can be played.
266 *
267 * It only works in the current page , after refreshing
268 * consent will be needed again. Consent will only stop
269 * after managing third party cookies.
270 *
271 * @param {object} event
272 *   Event.
273 */
274YoutubeCookies.prototype.onAcceptClick = function(event) {
275  this.removePopup();
276
277  if (this.currentVideo !== null) {
278    this.destroyYoutuebeCookiesIframe(this.currentVideo);
279
280    // Propagate the click event to the oembed-lazy original button.
281    if (this.isOembedLazyload(this.currentVideo)) {
282      this.currentVideo.querySelector(".oembed-lazyload__button").click();
283    }
284    this.interceptVideo = true;
285    this.currentVideo = null;
286  }
287};
288
289/**
290 * When cookies are rejected the popup is removed.
291 *
292 * @param {object} event
293 *   Event.
294 */
295YoutubeCookies.prototype.onRejectClick = function(event) {
296  this.removePopup();
297  this.currentVideo = null;
298  this.interceptVideo = true;
299};
300
301/**
302 * Add a wrapper div to the YouTube iframe to easily add the thumbnail.
303 *
304 * @param {object} iframe
305 *   Iframe.
306 */
307YoutubeCookies.prototype.setIframeWrapper = iframe => {
308  const wrapper = document.createElement("div");
309  wrapper.classList.add("youtube-cookies__iframe-container");
310  iframe.parentNode.insertBefore(wrapper, iframe);
311  wrapper.appendChild(iframe);
312};
313
314/**
315 * Remove a wrapper div to the YouTube iframe to easily add the thumbnail.
316 *
317 * @param {object} iframe
318 *   Iframe.
319 */
320YoutubeCookies.prototype.removeIframeWrapper = iframe => {
321  const wrapper = iframe.parentNode;
322  wrapper.after(iframe);
323  wrapper.remove();
324};
325
326/**
327 * Replace a YouTube iframe with a YouTube thumbnail.
328 *
329 * If tracking is accepted the video will be loaded.
330 *
331 * @param {object} iframe
332 *   Iframe.
333 * @param {string} iframeSrc
334 *   A YouTube URL.
335 */
336YoutubeCookies.prototype.setYoutubeThumbnail = function(iframe, iframeSrc) {
337  const videoId = this.getYoutubeIdFromUrl(iframeSrc);
338  const button = document.createElement("button");
339  button.classList.add("youtube-cookies__thumbnail");
340  button.addEventListener("click", this.onThumbnailClick.bind(this));
341  button.setAttribute("data-yc-fake-button", "1");
342  button.setAttribute("aria-label", this.thumbnailLabel);
343  button.innerHTML = this.thumbnailMarkup.replace(/videoId/g, videoId);
344  iframe.parentNode.prepend(button);
345};
346
347/**
348 * Check that an url is from YouTube.
349 *
350 * @param {string} source
351 *   YouTube url.
352 *
353 * @return {boolean}
354 *   True if URL is from YouTube.
355 */
356YoutubeCookies.prototype.isYoutubeSource = function(source) {
357  if (source.length === 0) {
358    return false;
359  }
360
361  const regExps = [/youtu\.be/, /youtube\.com/];
362  if (this.checkYoutubeNoCookie) {
363    regExps.push(/youtube-nocookie\.com/);
364  }
365
366  for (const i in regExps) {
367    if (source.match(regExps[i])) {
368      return true;
369    }
370  }
371  return false;
372};
373
374/**
375 * Check that an iframe is oembed-lazyload.
376 *
377 * @param {object} element
378 *   YouTube url.
379 *
380 * @return {boolean}
381 *   True if URL is oembed-lazyload.
382 */
383YoutubeCookies.prototype.isOembedLazyload = function(element) {
384  return !!(
385    element.classList.contains("oembed-lazyload") ||
386    element.classList.contains("oembed-lazyload__iframe")
387  );
388};
389
390/**
391 * Stop an iframe.
392 *
393 * @param {object} iframe
394 *   Iframe object.
395 */
396YoutubeCookies.prototype.stopIframe = function(iframe) {
397  const iframeSrc = iframe.src;
398
399  if (iframeSrc.length > 0 && this.isYoutubeSource(iframeSrc)) {
400    iframe.setAttribute("data-src", iframeSrc);
401    iframe.src = '';
402  }
403};
404
405/**
406 * Given a youtube video URL get its ID.
407 *
408 * @param {string} url
409 *   Youtube video URL.
410 *
411 * @return {null|*}
412 *   Video id, if exists.
413 */
414YoutubeCookies.prototype.getYoutubeIdFromUrl = function(url) {
415  const youtubeIdRegexp = "[^\\s&?/#]{11}";
416  let videoIdMatches = [];
417  if (url.match(`(youtube\.com\/embed\/)(${youtubeIdRegexp})`)) {
418    videoIdMatches = url.match(`(youtube\.com\/embed\/)(${youtubeIdRegexp})`);
419  }
420  if (url.match(`(youtu\.be\/)(${youtubeIdRegexp})`)) {
421    videoIdMatches = url.match(
422      `(youtube.com/embed)|(youtu.be/)(${youtubeIdRegexp})`
423    );
424  } else if (url.match(`(youtube.com/watch%3Fv%3D)(${youtubeIdRegexp})`)) {
425    videoIdMatches = url.match(
426      `(youtube.com/watch%3Fv%3D)(${youtubeIdRegexp})`
427    );
428  }
429  if (videoIdMatches !== null && videoIdMatches.length > 0) {
430    return videoIdMatches[videoIdMatches.length - 1];
431  }
432  return null;
433};
434
435/**
436 * Replace an element YouTube URL by youtube-nocookie alternative.
437 *
438 * @param {object} element
439 *   Element.
440 * @param {string} url
441 *   Video URL.
442 */
443YoutubeCookies.prototype.setYoutubeNoCookiesSrc = function(element, url) {
444  const attribute =
445    typeof element.src === "string" && element.src.length > 0
446      ? "src"
447      : "data-src";
448  const videoId = this.getYoutubeIdFromUrl(url);
449  if (videoId != null) {
450    element.setAttribute("data-original-src", url);
451    element.setAttribute("data-original-src-attribute", attribute);
452    // @TODO: Improve, the domain replace should change only the domain not the
453    //  entire URL. This changes the formatter default behaviour and must not.
454    element.setAttribute(
455      attribute,
456      `https://www.youtube-nocookie.com/embed/${videoId}?enablejsapi=1`
457    );
458  }
459};
460
461/**
462 * Set the YouTube original URL to the iframe.
463 *
464 * Only works with videos that haven't been played yet.
465 *
466 * @param {object} element
467 *   Iframe.
468 */
469YoutubeCookies.prototype.setYoutubeOriginalUrl = function(element) {
470  const attribute = element.getAttribute("data-original-src-attribute");
471  element.setAttribute(attribute, element.getAttribute("data-original-src"));
472  element.removeAttribute("data-original-src-attribute");
473  element.removeAttribute("data-original-src");
474};
475
476/**
477 * Set up the iframes so cookies can be managed.
478 *
479 * @param {object} event
480 *   Event objetc.
481 */
482YoutubeCookies.prototype.onContentLoaded = function(event) {
483  if (!this.youtubeCookiesAccepted) {
484    this.processYoutubeIframes(event.target);
485  }
486};
487
488/**
489 * Process YouTube iframes.
490 *
491 * @param {object} container
492 *   Element that contains the iframe.
493 */
494YoutubeCookies.prototype.processYoutubeIframes = function(container) {
495  if (this.youtubeCookiesAccepted) {
496    return;
497  }
498
499  let newButton;
500  const iframes = container.getElementsByTagName("iframe");
501  for (const iframeIndex in iframes) {
502    const iframe = iframes[iframeIndex];
503    if (typeof iframe !== "object") {
504      return;
505    }
506
507    if (iframe.hasAttribute("data-yc-iframe-processed")) {
508      continue;
509    }
510
511    this.stopIframe(iframe);
512
513    const iframeSrc = iframe.hasAttribute("data-src")
514      ? iframe.getAttribute("data-src")
515      : iframe.src;
516    if (
517      typeof iframeSrc === "string" &&
518      !this.isYoutubeSource(iframeSrc)
519    ) {
520      continue;
521    }
522
523    const iframeWrapper = iframe.parentNode;
524    if (this.isOembedLazyload(iframeWrapper)) {
525      iframeWrapper.setAttribute(
526        "data-original-strategy",
527        iframeWrapper.getAttribute("data-strategy")
528      );
529      iframeWrapper.removeAttribute("data-strategy");
530    }
531
532    switch (this.action) {
533      case "no_cookies_domain":
534        this.setYoutubeNoCookiesSrc(iframe, iframeSrc);
535        break;
536
537      case "popup":
538        if (this.isOembedLazyload(iframeWrapper)) {
539          const originalButton = iframeWrapper.querySelector("button");
540          if (typeof originalButton === "object") {
541            iframe.parentNode.insertAdjacentHTML(
542              "afterbegin",
543              originalButton.outerHTML
544            );
545            originalButton.setAttribute("data-yc-original-oembed-button", 1);
546            originalButton.style.display = "none";
547
548            // @TODO: Review, for the scope of this module launch an event on
549            // thumbnail click is trivial. At this point the modules do not
550            // perform a relevant action.
551            originalButton.addEventListener(
552              "click",
553              this.eventDispatch.bind(this)
554            );
555
556            newButton = iframe.parentNode.querySelector(
557              "button:not([data-yc-original-oembed-button])"
558            );
559
560            newButton.addEventListener("click", this.onThumbnailClick.bind(this));
561            newButton.setAttribute("data-yc-fake-button", "1");
562          }
563        } else {
564          // Set up the wrapper.
565          if (iframe.classList.contains("youtube-cookies__iframe--wysiwyg")) {
566            this.setIframeWrapper(iframe);
567          }
568          if (iframe.classList.contains("youtube-cookies__iframe--oembed")) {
569            iframe.parentNode.classList.add("youtube-cookies__iframe-container");
570          }
571          // Add the thumbnails.
572          this.setYoutubeThumbnail(iframe, iframeSrc);
573        }
574        break;
575    }
576
577    iframe.setAttribute("data-yc-iframe-index", iframeIndex);
578    iframe.setAttribute("data-yc-iframe-processed", "1");
579    iframe.dispatchEvent(youtubeCookiesEnabled);
580  }
581};
582
583/**
584 * Set the YouTube iframe/container to his initial state.
585 *
586 * @param {object} iframeWrapper
587 *    Iframe container object.
588 */
589YoutubeCookies.prototype.destroyYoutuebeCookiesIframe = function(
590  iframeWrapper
591) {
592  const iframe = iframeWrapper.querySelector("iframe");
593
594  switch (this.action) {
595    case "no_cookies_domain":
596      this.setYoutubeOriginalUrl(iframe);
597      break;
598
599    case "popup":
600      this.removeFakeThumbnail(iframeWrapper);
601      if (this.isOembedLazyload(iframeWrapper)) {
602        iframeWrapper.setAttribute(
603          "data-strategy",
604          iframeWrapper.getAttribute("data-original-strategy")
605        );
606        iframeWrapper.removeAttribute("data-original-strategy");
607      } else {
608        if (iframe.classList.contains("youtube-cookies__iframe--wysiwyg")) {
609          this.removeIframeWrapper(iframe);
610        }
611        if (iframe.classList.contains("youtube-cookies__iframe--oembed")) {
612          iframe.parentNode.classList.remove("youtube-cookies__iframe-container");
613        }
614        iframe.setAttribute("src", iframe.getAttribute("data-src"));
615        iframe.removeAttribute("data-src");
616      }
617      break;
618  }
619  iframe.removeAttribute("data-yc-iframe-processed");
620  iframe.removeAttribute("data-yc-iframe-index");
621  iframe.dispatchEvent(youtubeCookiesDisabled);
622};
623
624/**
625 * Set the callback that checks cookies have been accepted.
626 *
627 * @param {function} callback
628 *   Callback that determine if cookies have been accepted.
629 */
630YoutubeCookies.prototype.setAcceptedCookiesCallback = function(callback) {
631  this.acceptedCookiesCallback = callback;
632};
633
634/**
635 * Initialize YouTube cookies.
636 */
637(function(Drupal, drupalSettings) {
638  if (typeof drupalSettings.youtubeCookies === "object") {
639    Drupal.youtubeCookies = new YoutubeCookies(
640      drupalSettings.youtubeCookies.action,
641      drupalSettings.youtubeCookies.thumbnailLabel,
642      drupalSettings.youtubeCookies.popupMessage,
643      drupalSettings.youtubeCookies.manageButton,
644      drupalSettings.youtubeCookies.okButton,
645      drupalSettings.youtubeCookies.exitButton,
646      drupalSettings.youtubeCookies.thumbnailMarkup
647    );
648  }
649
650  Drupal.behaviors.youtubeCookies = {
651    attach() {
652      // Force attachment of oembed lazyload behaviour before removing the oembed attributes.
653      if (
654        typeof Drupal.behaviors.oembedLazyload === "object" &&
655        typeof Drupal.behaviors.oembedLazyload.attach === "function"
656      ) {
657        Drupal.behaviors.oembedLazyload.attach(document);
658      }
659      Drupal.youtubeCookies.processYoutubeIframes(document);
660      document.dispatchEvent(youtubeCookiesEnabled);
661    }
662  };
663})(Drupal, drupalSettings);

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.