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.