Fix handling of non-Element nodes in DomUtil#insertHtml()
authorTim Düsterhus <duesterhus@woltlab.com>
Tue, 20 Apr 2021 13:52:49 +0000 (15:52 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Tue, 20 Apr 2021 13:57:11 +0000 (15:57 +0200)
The function was rewritten to make use of simple appendChild() / insertBefore()
calls in combination with a DocumentFragment vs. inserting all children within
the given `html` manually.

This fixes the Twitter Embed functionality if user consent is required.

ts/WoltLabSuite/Core/Dom/Util.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js

index ae6c35a0a0aa548e7daa5403e84ceefdf84e6613..a630f6cb0cfe9833dee720cabc6e5e6d0ce6ff87 100644 (file)
@@ -206,7 +206,14 @@ const DomUtil = {
   setInnerHtml(element: Element, innerHtml: string): void {
     element.innerHTML = innerHtml;
 
-    const scripts = element.querySelectorAll<HTMLScriptElement>("script");
+    let container: Node & ParentNode;
+    if (element instanceof HTMLTemplateElement) {
+      container = element.content;
+    } else {
+      container = element;
+    }
+
+    const scripts = container.querySelectorAll<HTMLScriptElement>("script");
     for (let i = 0, length = scripts.length; i < length; i++) {
       const script = scripts[i];
       const newScript = document.createElement("script");
@@ -216,7 +223,7 @@ const DomUtil = {
         newScript.textContent = script.textContent;
       }
 
-      element.appendChild(newScript);
+      container.appendChild(newScript);
       script.remove();
     }
   },
@@ -228,25 +235,25 @@ const DomUtil = {
    * @param insertMethod
    */
   insertHtml(html: string, referenceElement: Element, insertMethod: string): void {
-    const element = document.createElement("div");
+    const element = document.createElement("template");
     this.setInnerHtml(element, html);
 
-    if (!element.childNodes.length) {
-      return;
-    }
-
-    let node = element.childNodes[0] as Element;
+    const fragment = document.importNode(element.content, true);
     switch (insertMethod) {
       case "append":
-        referenceElement.appendChild(node);
+        referenceElement.appendChild(fragment);
         break;
 
       case "after":
-        this.insertAfter(node, referenceElement);
+        if (referenceElement.parentNode === null) {
+          throw new Error("The reference element has no parent, but the insert position was set to 'after'.");
+        }
+
+        referenceElement.parentNode.insertBefore(fragment, referenceElement.nextSibling);
         break;
 
       case "prepend":
-        this.prepend(node, referenceElement);
+        referenceElement.insertBefore(fragment, referenceElement.firstChild);
         break;
 
       case "before":
@@ -254,20 +261,12 @@ const DomUtil = {
           throw new Error("The reference element has no parent, but the insert position was set to 'before'.");
         }
 
-        referenceElement.parentNode.insertBefore(node, referenceElement);
+        referenceElement.parentNode.insertBefore(fragment, referenceElement);
         break;
 
       default:
         throw new Error("Unknown insert method '" + insertMethod + "'.");
     }
-
-    let tmp;
-    while (element.childNodes.length) {
-      tmp = element.childNodes[0];
-
-      this.insertAfter(tmp, node);
-      node = tmp;
-    }
   },
 
   /**
index 7c58937aa82d9e5dcdca2df00596b3ce95a59ea8..6f07819176d7348a51c535aa35f45e9477ed792a 100644 (file)
@@ -174,7 +174,14 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo
          */
         setInnerHtml(element, innerHtml) {
             element.innerHTML = innerHtml;
-            const scripts = element.querySelectorAll("script");
+            let container;
+            if (element instanceof HTMLTemplateElement) {
+                container = element.content;
+            }
+            else {
+                container = element;
+            }
+            const scripts = container.querySelectorAll("script");
             for (let i = 0, length = scripts.length; i < length; i++) {
                 const script = scripts[i];
                 const newScript = document.createElement("script");
@@ -184,7 +191,7 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo
                 else {
                     newScript.textContent = script.textContent;
                 }
-                element.appendChild(newScript);
+                container.appendChild(newScript);
                 script.remove();
             }
         },
@@ -195,37 +202,31 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo
          * @param insertMethod
          */
         insertHtml(html, referenceElement, insertMethod) {
-            const element = document.createElement("div");
+            const element = document.createElement("template");
             this.setInnerHtml(element, html);
-            if (!element.childNodes.length) {
-                return;
-            }
-            let node = element.childNodes[0];
+            const fragment = document.importNode(element.content, true);
             switch (insertMethod) {
                 case "append":
-                    referenceElement.appendChild(node);
+                    referenceElement.appendChild(fragment);
                     break;
                 case "after":
-                    this.insertAfter(node, referenceElement);
+                    if (referenceElement.parentNode === null) {
+                        throw new Error("The reference element has no parent, but the insert position was set to 'after'.");
+                    }
+                    referenceElement.parentNode.insertBefore(fragment, referenceElement.nextSibling);
                     break;
                 case "prepend":
-                    this.prepend(node, referenceElement);
+                    referenceElement.insertBefore(fragment, referenceElement.firstChild);
                     break;
                 case "before":
                     if (referenceElement.parentNode === null) {
                         throw new Error("The reference element has no parent, but the insert position was set to 'before'.");
                     }
-                    referenceElement.parentNode.insertBefore(node, referenceElement);
+                    referenceElement.parentNode.insertBefore(fragment, referenceElement);
                     break;
                 default:
                     throw new Error("Unknown insert method '" + insertMethod + "'.");
             }
-            let tmp;
-            while (element.childNodes.length) {
-                tmp = element.childNodes[0];
-                this.insertAfter(tmp, node);
-                node = tmp;
-            }
         },
         /**
          * Returns true if `element` contains the `child` element.