From ff739d54dd395a25dd23283dd85d6804720c3481 Mon Sep 17 00:00:00 2001
From: Andreas Fernandez <a.fernandez@scripting-base.de>
Date: Wed, 5 Jul 2023 11:31:57 +0200
Subject: [PATCH] [BUGFIX] Use event delegation for frontend links

TYPO3 ships some default handling for encrypted email links and popup
windows. Unfortunately, only links that were available on initial
document load were handled, links that were added after couldn't get
handled.

To solve the issue, the events are not directly bound to such links
anymore, but handled by an event delegation approach.

Resolves: #101228
Releases: main, 12.4, 11.5
Change-Id: I8ec5d5736d9f9cfe8496cf69cf1128f1af4c0eb8
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79782
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: core-ci <typo3@b13.com>
---
 .../Public/JavaScript/default_frontend.js     | 75 ++++++++-----------
 1 file changed, 32 insertions(+), 43 deletions(-)

diff --git a/typo3/sysext/frontend/Resources/Public/JavaScript/default_frontend.js b/typo3/sysext/frontend/Resources/Public/JavaScript/default_frontend.js
index 06e2a58b91b8..1e360c88d769 100644
--- a/typo3/sysext/frontend/Resources/Public/JavaScript/default_frontend.js
+++ b/typo3/sysext/frontend/Resources/Public/JavaScript/default_frontend.js
@@ -1,23 +1,4 @@
 (function() {
-  /**
-   * @param {Function} callback
-   */
-  function ready(callback) {
-    if (document.readyState === 'complete') {
-      callback.call(null);
-      return;
-    }
-    var clearListeners = function() {
-      window.removeEventListener('load', delegate);
-      document.removeEventListener('DOMContentLoaded', delegate);
-    };
-    var delegate = function() {
-      clearListeners();
-      callback.call(null);
-    };
-    window.addEventListener('load', delegate);
-    document.addEventListener('DOMContentLoaded', delegate);
-  }
   /**
    * Decoding helper function
    *
@@ -76,29 +57,37 @@
     return windowRef;
   }
 
-  ready(function() {
-    var mailtoElements = document.querySelectorAll('a[data-mailto-token][data-mailto-vector]');
-    // `Array.from` for IE compatibility
-    Array.from(mailtoElements).forEach(function(element) {
-        element.addEventListener('click', function(evt) {
-          evt.preventDefault();
-          var dataset = evt.currentTarget.dataset;
-          var value = dataset.mailtoToken;
-          var offset = parseInt(dataset.mailtoVector, 10) * -1;
-          document.location.href = decryptString(value, offset);
-        });
-      });
-    var openElements = document.querySelectorAll('a[data-window-url]');
-    // `Array.from` for IE compatibility
-    Array.from(openElements).forEach(function(element) {
-        element.addEventListener('click', function(evt) {
-          evt.preventDefault();
-          var dataset = evt.currentTarget.dataset;
-          var url = dataset.windowUrl;
-          var target = dataset.windowTarget || null;
-          var features = dataset.windowFeatures || null;
-          windowOpen(url, target, features);
-        });
-      });
+  /**
+   * Delegates event handling to elements
+   *
+   * @param {string} event
+   * @param {string} selector
+   * @param {function} callback
+   */
+  function delegateEvent(event, selector, callback) {
+    document.addEventListener(event, function(evt) {
+      for (var targetElement = evt.target; targetElement && targetElement !== document; targetElement = targetElement.parentNode) {
+        if (targetElement.matches(selector)) {
+          callback.call(targetElement, evt, targetElement);
+        }
+      }
+    });
+  }
+
+  delegateEvent('click', 'a[data-mailto-token][data-mailto-vector]', function(evt, evtTarget) {
+    evt.preventDefault();
+    var dataset = evtTarget.dataset;
+    var value = dataset.mailtoToken;
+    var offset = parseInt(dataset.mailtoVector, 10) * -1;
+    document.location.href = decryptString(value, offset);
+  });
+
+  delegateEvent('click', 'a[data-window-url]', function(evt, evtTarget) {
+    evt.preventDefault();
+    var dataset = evtTarget.dataset;
+    var url = dataset.windowUrl;
+    var target = dataset.windowTarget || null;
+    var features = dataset.windowFeatures || null;
+    windowOpen(url, target, features);
   });
 })();
-- 
GitLab