From dbf8868c428b45e23679ae3e206889519f836ec2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Enrique=20Alc=C3=A1ntara?= <ealcantara@gitlab.com>
Date: Tue, 12 Jul 2022 18:25:17 +0000
Subject: [PATCH] Skip unknown tags in the Content Editor

Skip HTML tags that are not explicitly
supported in the Content Editor. Also skip
all the children of these unsupported tags
---
 .../content_editor/extensions/sourcemap.js    |  10 +
 .../services/hast_to_prosemirror_converter.js |   6 +-
 .../services/markdown_serializer.js           |  12 +-
 .../services/remark_markdown_deserializer.js  |   5 +
 .../services/serialization_helpers.js         |  10 +-
 glfm_specification/example_snapshots/html.yml | 103 ++-
 .../example_snapshots/prosemirror_json.yml    | 705 +++++++++++++++---
 .../remark_markdown_processing_spec.js        |  76 ++
 .../services/markdown_serializer_spec.js      |  18 +-
 9 files changed, 750 insertions(+), 195 deletions(-)

diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js
index 87118074462a2..618f17b1c5e7b 100644
--- a/app/assets/javascripts/content_editor/extensions/sourcemap.js
+++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js
@@ -9,6 +9,7 @@ import FootnoteDefinition from './footnote_definition';
 import Heading from './heading';
 import HardBreak from './hard_break';
 import HorizontalRule from './horizontal_rule';
+import HTMLNodes from './html_nodes';
 import Image from './image';
 import Italic from './italic';
 import Link from './link';
@@ -51,13 +52,22 @@ export default Extension.create({
           TableCell.name,
           TableHeader.name,
           TableRow.name,
+          ...HTMLNodes.map((htmlNode) => htmlNode.name),
         ],
         attributes: {
+          /**
+           * The reason to add a function that returns an empty
+           * string in these attributes is indicate that these
+           * attributes shouldn’t be rendered in the ProseMirror
+           * view.
+           */
           sourceMarkdown: {
             default: null,
+            renderHTML: () => '',
           },
           sourceMapKey: {
             default: null,
+            renderHTML: () => '',
           },
         },
       },
diff --git a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js
index 2c462cdde91b3..6859ebc2c5a18 100644
--- a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js
+++ b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js
@@ -570,11 +570,7 @@ export const createProseMirrorDocFromMdastTree = ({
     const factory = findFactory(hastNode, ancestors, proseMirrorNodeFactories);
 
     if (!factory) {
-      throw new Error(
-        `Hast node of type "${
-          hastNode.tagName || hastNode.type
-        }" not supported by this converter. Please, provide an specification.`,
-      );
+      return SKIP;
     }
 
     const parent = findParent(ancestors, factory.parent);
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index c7e05072927a5..c1c7af6b1afb6 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -170,12 +170,6 @@ const defaultSerializerConfig = {
     [HardBreak.name]: preserveUnchanged(renderHardBreak),
     [Heading.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.heading),
     [HorizontalRule.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.horizontal_rule),
-    ...HTMLNodes.reduce((serializers, htmlNode) => {
-      return {
-        ...serializers,
-        [htmlNode.name]: (state, node) => renderHTMLNode(htmlNode.options.tagName)(state, node),
-      };
-    }, {}),
     [Image.name]: preserveUnchanged(renderImage),
     [ListItem.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.list_item),
     [OrderedList.name]: preserveUnchanged(renderOrderedList),
@@ -202,6 +196,12 @@ const defaultSerializerConfig = {
     [Text.name]: defaultMarkdownSerializer.nodes.text,
     [Video.name]: renderPlayable,
     [WordBreak.name]: (state) => state.write('<wbr>'),
+    ...HTMLNodes.reduce((serializers, htmlNode) => {
+      return {
+        ...serializers,
+        [htmlNode.name]: (state, node) => renderHTMLNode(htmlNode.options.tagName)(state, node),
+      };
+    }, {}),
   },
 };
 
diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
index da10c684b0b6a..8c99dc157e6d2 100644
--- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
+++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
@@ -88,6 +88,11 @@ const factorySpecs = {
     selector: (hastNode, ancestors) =>
       hastNode.tagName === 'input' && isTaskItem(ancestors[ancestors.length - 1]),
   },
+  div: {
+    type: 'block',
+    selector: 'div',
+    wrapInParagraph: true,
+  },
   table: {
     type: 'block',
     selector: 'table',
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index 88f5192af771b..7d5e718b41cfa 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -5,6 +5,8 @@ const defaultAttrs = {
   th: { colspan: 1, rowspan: 1, colwidth: null },
 };
 
+const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey'];
+
 const ignoreAttrs = {
   dd: ['isTerm'],
   dt: ['isTerm'],
@@ -101,13 +103,17 @@ function htmlEncode(str = '') {
     .replace(/"/g, '&#34;');
 }
 
+const shouldIgnoreAttr = (tagName, attrKey, attrValue) =>
+  ignoreAttrs[tagName]?.includes(attrKey) ||
+  defaultIgnoreAttrs.includes(attrKey) ||
+  defaultAttrs[tagName]?.[attrKey] === attrValue;
+
 export function openTag(tagName, attrs) {
   let str = `<${tagName}`;
 
   str += Object.entries(attrs || {})
     .map(([key, value]) => {
-      if ((ignoreAttrs[tagName] || []).includes(key) || defaultAttrs[tagName]?.[key] === value)
-        return '';
+      if (shouldIgnoreAttr(tagName, key, value)) return '';
 
       return ` ${key}="${htmlEncode(value?.toString())}"`;
     })
diff --git a/glfm_specification/example_snapshots/html.yml b/glfm_specification/example_snapshots/html.yml
index 701f7ccef0e90..6a9acdd03a4a7 100644
--- a/glfm_specification/example_snapshots/html.yml
+++ b/glfm_specification/example_snapshots/html.yml
@@ -1527,8 +1527,9 @@
              <a></a>
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div><p>
+      *hello*
+             </p></div>
 04_06__leaf_blocks__html_blocks__004:
   canonical: |
     </div>
@@ -1549,8 +1550,7 @@
     <p data-sourcepos="3:1-3:10"><em>Markdown</em></p>
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div><p><em>Markdown</em></p></div>
 04_06__leaf_blocks__html_blocks__006:
   canonical: |
     <div id="foo"
@@ -1560,8 +1560,7 @@
     <div>
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div></div>
 04_06__leaf_blocks__html_blocks__007:
   canonical: |
     <div id="foo" class="bar
@@ -1571,8 +1570,7 @@
     <div>
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div></div>
 04_06__leaf_blocks__html_blocks__008:
   canonical: |
     <div>
@@ -1584,8 +1582,9 @@
     <p data-sourcepos="4:1-4:5"><em>bar</em></p>
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div><p>
+    *foo*
+    </p><p><em>bar</em></p></div>
 04_06__leaf_blocks__html_blocks__009:
   canonical: |
     <div id="foo"
@@ -1616,8 +1615,7 @@
   static: |-
     <div><a href="bar">*foo*</a></div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div><p><a target="_blank" rel="noopener noreferrer nofollow" href="bar">*foo*</a></p></div>
 04_06__leaf_blocks__html_blocks__013:
   canonical: |
     <table><tr><td>
@@ -1643,8 +1641,7 @@
     int x = 33;
     ```
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div></div>
 04_06__leaf_blocks__html_blocks__015:
   canonical: |
     <a href="foo">
@@ -1667,8 +1664,7 @@
 
     *bar*
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "warning" not supported by this converter. Please, provide an specification.
+    <p></p>
 04_06__leaf_blocks__html_blocks__017:
   canonical: |
     <i class="foo">
@@ -1761,8 +1757,7 @@
 
     <p data-sourcepos="6:1-6:4" dir="auto">okay</p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "script" not supported by this converter. Please, provide an specification.
+    <p>okay</p>
 04_06__leaf_blocks__html_blocks__024:
   canonical: |
     <style
@@ -1780,8 +1775,7 @@
 
     <p data-sourcepos="7:1-7:4" dir="auto">okay</p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "style" not supported by this converter. Please, provide an specification.
+    <p>okay</p>
 04_06__leaf_blocks__html_blocks__025:
   canonical: |
     <style
@@ -1793,8 +1787,7 @@
 
     foo
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "style" not supported by this converter. Please, provide an specification.
+    <p></p>
 04_06__leaf_blocks__html_blocks__026:
   canonical: |
     <blockquote>
@@ -1811,8 +1804,9 @@
     </div>
     </blockquote>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <blockquote multiline="false"><div><p>
+    foo
+    </p></div></blockquote>
 04_06__leaf_blocks__html_blocks__027:
   canonical: |
     <ul>
@@ -1831,8 +1825,7 @@
     </li>
     </ul>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <ul bullet="*"><li><p></p><div></div></li><li><p>foo</p></li></ul>
 04_06__leaf_blocks__html_blocks__028:
   canonical: |
     <style>p{color:red;}</style>
@@ -1841,8 +1834,7 @@
     p{color:red;}
     <p data-sourcepos="2:1-2:5" dir="auto"><em>foo</em></p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "style" not supported by this converter. Please, provide an specification.
+    <p><em>foo</em></p>
 04_06__leaf_blocks__html_blocks__029:
   canonical: |
     <!-- foo -->*bar*
@@ -1861,8 +1853,7 @@
   static: |-
     1. *bar*
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "script" not supported by this converter. Please, provide an specification.
+    <p>1. *bar*</p>
 04_06__leaf_blocks__html_blocks__031:
   canonical: |
     <!-- Foo
@@ -1955,8 +1946,7 @@
     </div>
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div><pre class="content-editor-code-block undefined code highlight"><code>&lt;div&gt;</code></pre></div>
 04_06__leaf_blocks__html_blocks__037:
   canonical: |
     <p>Foo</p>
@@ -1969,8 +1959,7 @@
     bar
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <p>Foo</p>
 04_06__leaf_blocks__html_blocks__038:
   canonical: |
     <div>
@@ -1983,8 +1972,9 @@
     </div>
     *foo*
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div><p>
+    bar
+    </p></div>
 04_06__leaf_blocks__html_blocks__039:
   canonical: |
     <p>Foo
@@ -2008,8 +1998,7 @@
     <p data-sourcepos="3:1-3:18"><em>Emphasized</em> text.</p>
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div><p><em>Emphasized</em> text.</p></div>
 04_06__leaf_blocks__html_blocks__041:
   canonical: |
     <div>
@@ -2020,8 +2009,9 @@
     *Emphasized* text.
     </div>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "div" not supported by this converter. Please, provide an specification.
+    <div><p>
+    *Emphasized* text.
+    </p></div>
 04_06__leaf_blocks__html_blocks__042:
   canonical: |
     <table>
@@ -2157,8 +2147,7 @@
     <p data-sourcepos="1:1-1:17" dir="auto">[foo]: (baz)</p>
     <p data-sourcepos="3:1-3:5" dir="auto">[foo]</p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "bar" not supported by this converter. Please, provide an specification.
+    <p>[foo]: </p>
 04_07__leaf_blocks__link_reference_definitions__011:
   canonical: |
     <p><a href="/url%5Cbar*baz" title="foo&quot;bar\baz">foo</a></p>
@@ -6096,8 +6085,7 @@
   static: |-
     <p data-sourcepos="1:1-2:5" dir="auto">[link]()</p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "foo" not supported by this converter. Please, provide an specification.
+    <p>[link](</p>
 06_07__inlines__links__009:
   canonical: |
     <p><a href="b)c">a</a></p>
@@ -6336,8 +6324,7 @@
   static: |-
     <p data-sourcepos="1:1-1:24" dir="auto">[foo </p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "bar" not supported by this converter. Please, provide an specification.
+    <p>[foo </p>
 06_07__inlines__links__041:
   canonical: |
     <p>[foo<code>](/uri)</code></p>
@@ -6422,8 +6409,7 @@
   static: |-
     <p data-sourcepos="1:1-1:24" dir="auto">[foo </p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "bar" not supported by this converter. Please, provide an specification.
+    <p>[foo </p>
 06_07__inlines__links__053:
   canonical: |
     <p>[foo<code>][ref]</code></p>
@@ -7085,16 +7071,14 @@
   static: |-
     <p data-sourcepos="1:1-1:13" dir="auto"><a></a></p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "bab" not supported by this converter. Please, provide an specification.
+    <p></p>
 06_11__inlines__raw_html__002:
   canonical: |
     <p><a/><b2/></p>
   static: |-
     <p data-sourcepos="1:1-1:9" dir="auto"><a></a></p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "b2" not supported by this converter. Please, provide an specification.
+    <p></p>
 06_11__inlines__raw_html__003:
   canonical: |
     <p><a  /><b2
@@ -7102,8 +7086,7 @@
   static: |-
     <p data-sourcepos="1:1-2:12" dir="auto"><a></a></p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "b2" not supported by this converter. Please, provide an specification.
+    <p></p>
 06_11__inlines__raw_html__004:
   canonical: |
     <p><a foo="bar" bam = 'baz <em>"</em>'
@@ -7118,8 +7101,7 @@
   static: |-
     <p data-sourcepos="1:1-1:38" dir="auto">Foo </p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "responsive-image" not supported by this converter. Please, provide an specification.
+    <p>Foo </p>
 06_11__inlines__raw_html__006:
   canonical: |
     <p>&lt;33&gt; &lt;__&gt;</p>
@@ -7260,8 +7242,7 @@
       &lt;xmp&gt; is disallowed.  &lt;XMP&gt; is also disallowed.
     &lt;/blockquote&gt;</strong></p>
   wysiwyg: |-
-    Error - check implementation:
-    Hast node of type "title" not supported by this converter. Please, provide an specification.
+    <p></p>
 06_13__inlines__hard_line_breaks__001:
   canonical: |
     <p>foo<br />
@@ -7434,11 +7415,11 @@
 07_01__gitlab_specific_markdown__footnotes__001:
   canonical: ""
   static: |-
-    <p data-sourcepos="1:1-1:27" dir="auto">footnote reference tag <sup class="footnote-ref"><a href="#fn-1-4281" id="fnref-1-4281" data-footnote-ref>1</a></sup></p>
+    <p data-sourcepos="1:1-1:27" dir="auto">footnote reference tag <sup class="footnote-ref"><a href="#fn-1-794" id="fnref-1-794" data-footnote-ref>1</a></sup></p>
     <section data-footnotes class="footnotes">
     <ol>
-    <li id="fn-1-4281">
-    <p data-sourcepos="3:7-3:19">footnote text <a href="#fnref-1-4281" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
+    <li id="fn-1-794">
+    <p data-sourcepos="3:7-3:19">footnote text <a href="#fnref-1-794" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
     </li>
     </ol>
     </section>
diff --git a/glfm_specification/example_snapshots/prosemirror_json.yml b/glfm_specification/example_snapshots/prosemirror_json.yml
index a221350775d6b..85d499ef552b5 100644
--- a/glfm_specification/example_snapshots/prosemirror_json.yml
+++ b/glfm_specification/example_snapshots/prosemirror_json.yml
@@ -2941,8 +2941,25 @@
     ]
   }
 04_06__leaf_blocks__html_blocks__003: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div",
+        "content": [
+          {
+            "type": "paragraph",
+            "content": [
+              {
+                "type": "text",
+                "text": "\n  *hello*\n         "
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__004: |-
   {
     "type": "doc",
@@ -2959,17 +2976,82 @@
     ]
   }
 04_06__leaf_blocks__html_blocks__005: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div",
+        "content": [
+          {
+            "type": "paragraph",
+            "content": [
+              {
+                "type": "text",
+                "marks": [
+                  {
+                    "type": "italic"
+                  }
+                ],
+                "text": "Markdown"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__006: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div"
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__007: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div"
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__008: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div",
+        "content": [
+          {
+            "type": "paragraph",
+            "content": [
+              {
+                "type": "text",
+                "text": "\n*foo*\n"
+              }
+            ]
+          },
+          {
+            "type": "paragraph",
+            "content": [
+              {
+                "type": "text",
+                "marks": [
+                  {
+                    "type": "italic"
+                  }
+                ],
+                "text": "bar"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__009: |-
   {
     "type": "doc",
@@ -2998,8 +3080,37 @@
     ]
   }
 04_06__leaf_blocks__html_blocks__012: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div",
+        "content": [
+          {
+            "type": "paragraph",
+            "content": [
+              {
+                "type": "text",
+                "marks": [
+                  {
+                    "type": "link",
+                    "attrs": {
+                      "href": "bar",
+                      "target": "_blank",
+                      "class": null,
+                      "title": null,
+                      "canonicalSrc": null
+                    }
+                  }
+                ],
+                "text": "*foo*"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__013: |-
   {
     "type": "doc",
@@ -3039,8 +3150,23 @@
     ]
   }
 04_06__leaf_blocks__html_blocks__014: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div"
+      },
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "\n``` c\nint x = 33;\n```"
+          }
+        ]
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__015: |-
   {
     "type": "doc",
@@ -3069,8 +3195,14 @@
     ]
   }
 04_06__leaf_blocks__html_blocks__016: |-
-  Error - check implementation:
-  Hast node of type "warning" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph"
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__017: |-
   {
     "type": "doc",
@@ -3126,10 +3258,259 @@
       }
     ]
   }
-04_06__leaf_blocks__html_blocks__020: |-
-  Error - check implementation:
-  Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
-04_06__leaf_blocks__html_blocks__021: |-
+04_06__leaf_blocks__html_blocks__020: |-
+  Error - check implementation:
+  Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
+04_06__leaf_blocks__html_blocks__021: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "marks": [
+              {
+                "type": "italic"
+              },
+              {
+                "type": "strike"
+              }
+            ],
+            "text": "foo"
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__022: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "codeBlock",
+        "attrs": {
+          "language": null,
+          "class": "code highlight"
+        },
+        "content": [
+          {
+            "type": "text",
+            "text": "\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags"
+          }
+        ]
+      },
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "okay"
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__023: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "okay"
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__024: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "okay"
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__025: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph"
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__026: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "blockquote",
+        "attrs": {
+          "multiline": false
+        },
+        "content": [
+          {
+            "type": "div",
+            "content": [
+              {
+                "type": "paragraph",
+                "content": [
+                  {
+                    "type": "text",
+                    "text": "\nfoo\n"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "bar"
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__027: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "bulletList",
+        "attrs": {
+          "bullet": "*"
+        },
+        "content": [
+          {
+            "type": "listItem",
+            "content": [
+              {
+                "type": "paragraph"
+              },
+              {
+                "type": "div"
+              }
+            ]
+          },
+          {
+            "type": "listItem",
+            "content": [
+              {
+                "type": "paragraph",
+                "content": [
+                  {
+                    "type": "text",
+                    "text": "foo"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__028: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "marks": [
+              {
+                "type": "italic"
+              }
+            ],
+            "text": "foo"
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__029: |-
+  Error - check implementation:
+  Cannot read properties of undefined (reading 'wrapper')
+04_06__leaf_blocks__html_blocks__030: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "1. *bar*"
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__031: |-
+  Error - check implementation:
+  Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
+04_06__leaf_blocks__html_blocks__032: |-
+  Error - check implementation:
+  Cannot read properties of undefined (reading 'wrapper')
+04_06__leaf_blocks__html_blocks__033: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph"
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__034: |-
+  Error - check implementation:
+  Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
+04_06__leaf_blocks__html_blocks__035: |-
+  Error - check implementation:
+  Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
+04_06__leaf_blocks__html_blocks__036: |-
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div",
+        "content": [
+          {
+            "type": "codeBlock",
+            "attrs": {
+              "language": null,
+              "class": "code highlight"
+            },
+            "content": [
+              {
+                "type": "text",
+                "text": "<div>"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+04_06__leaf_blocks__html_blocks__037: |-
   {
     "type": "doc",
     "content": [
@@ -3138,34 +3519,41 @@
         "content": [
           {
             "type": "text",
-            "marks": [
-              {
-                "type": "italic"
-              },
+            "text": "Foo"
+          }
+        ]
+      },
+      {
+        "type": "div",
+        "content": [
+          {
+            "type": "paragraph",
+            "content": [
               {
-                "type": "strike"
+                "type": "text",
+                "text": "\nbar\n"
               }
-            ],
-            "text": "foo"
+            ]
           }
         ]
       }
     ]
   }
-04_06__leaf_blocks__html_blocks__022: |-
+04_06__leaf_blocks__html_blocks__038: |-
   {
     "type": "doc",
     "content": [
       {
-        "type": "codeBlock",
-        "attrs": {
-          "language": null,
-          "class": "code highlight"
-        },
+        "type": "div",
         "content": [
           {
-            "type": "text",
-            "text": "\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags"
+            "type": "paragraph",
+            "content": [
+              {
+                "type": "text",
+                "text": "\nbar\n"
+              }
+            ]
           }
         ]
       },
@@ -3174,66 +3562,12 @@
         "content": [
           {
             "type": "text",
-            "text": "okay"
+            "text": "\n*foo*"
           }
         ]
       }
     ]
   }
-04_06__leaf_blocks__html_blocks__023: |-
-  Error - check implementation:
-  Hast node of type "script" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__024: |-
-  Error - check implementation:
-  Hast node of type "style" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__025: |-
-  Error - check implementation:
-  Hast node of type "style" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__026: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__027: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__028: |-
-  Error - check implementation:
-  Hast node of type "style" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__029: |-
-  Error - check implementation:
-  Cannot read properties of undefined (reading 'wrapper')
-04_06__leaf_blocks__html_blocks__030: |-
-  Error - check implementation:
-  Hast node of type "script" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__031: |-
-  Error - check implementation:
-  Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
-04_06__leaf_blocks__html_blocks__032: |-
-  Error - check implementation:
-  Cannot read properties of undefined (reading 'wrapper')
-04_06__leaf_blocks__html_blocks__033: |-
-  {
-    "type": "doc",
-    "content": [
-      {
-        "type": "paragraph"
-      }
-    ]
-  }
-04_06__leaf_blocks__html_blocks__034: |-
-  Error - check implementation:
-  Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
-04_06__leaf_blocks__html_blocks__035: |-
-  Error - check implementation:
-  Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
-04_06__leaf_blocks__html_blocks__036: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__037: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
-04_06__leaf_blocks__html_blocks__038: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
 04_06__leaf_blocks__html_blocks__039: |-
   {
     "type": "doc",
@@ -3266,11 +3600,54 @@
     ]
   }
 04_06__leaf_blocks__html_blocks__040: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div",
+        "content": [
+          {
+            "type": "paragraph",
+            "content": [
+              {
+                "type": "text",
+                "marks": [
+                  {
+                    "type": "italic"
+                  }
+                ],
+                "text": "Emphasized"
+              },
+              {
+                "type": "text",
+                "text": " text."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__041: |-
-  Error - check implementation:
-  Hast node of type "div" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "div",
+        "content": [
+          {
+            "type": "paragraph",
+            "content": [
+              {
+                "type": "text",
+                "text": "\n*Emphasized* text.\n"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
 04_06__leaf_blocks__html_blocks__042: |-
   {
     "type": "doc",
@@ -3586,8 +3963,29 @@
     ]
   }
 04_07__leaf_blocks__link_reference_definitions__010: |-
-  Error - check implementation:
-  Hast node of type "bar" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "[foo]: "
+          }
+        ]
+      },
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "[foo]"
+          }
+        ]
+      }
+    ]
+  }
 04_07__leaf_blocks__link_reference_definitions__011: |-
   {
     "type": "doc",
@@ -13715,8 +14113,20 @@
     ]
   }
 06_07__inlines__links__008: |-
-  Error - check implementation:
-  Hast node of type "foo" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "[link]("
+          }
+        ]
+      }
+    ]
+  }
 06_07__inlines__links__009: |-
   {
     "type": "doc",
@@ -14573,8 +14983,20 @@
     ]
   }
 06_07__inlines__links__040: |-
-  Error - check implementation:
-  Hast node of type "bar" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "[foo "
+          }
+        ]
+      }
+    ]
+  }
 06_07__inlines__links__041: |-
   {
     "type": "doc",
@@ -14938,8 +15360,20 @@
     ]
   }
 06_07__inlines__links__052: |-
-  Error - check implementation:
-  Hast node of type "bar" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "[foo "
+          }
+        ]
+      }
+    ]
+  }
 06_07__inlines__links__053: |-
   {
     "type": "doc",
@@ -17446,14 +17880,32 @@
     ]
   }
 06_11__inlines__raw_html__001: |-
-  Error - check implementation:
-  Hast node of type "bab" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph"
+      }
+    ]
+  }
 06_11__inlines__raw_html__002: |-
-  Error - check implementation:
-  Hast node of type "b2" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph"
+      }
+    ]
+  }
 06_11__inlines__raw_html__003: |-
-  Error - check implementation:
-  Hast node of type "b2" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph"
+      }
+    ]
+  }
 06_11__inlines__raw_html__004: |-
   {
     "type": "doc",
@@ -17464,8 +17916,20 @@
     ]
   }
 06_11__inlines__raw_html__005: |-
-  Error - check implementation:
-  Hast node of type "responsive-image" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph",
+        "content": [
+          {
+            "type": "text",
+            "text": "Foo "
+          }
+        ]
+      }
+    ]
+  }
 06_11__inlines__raw_html__006: |-
   {
     "type": "doc",
@@ -17662,8 +18126,25 @@
     ]
   }
 06_12__inlines__disallowed_raw_html_extension__001: |-
-  Error - check implementation:
-  Hast node of type "title" not supported by this converter. Please, provide an specification.
+  {
+    "type": "doc",
+    "content": [
+      {
+        "type": "paragraph"
+      },
+      {
+        "type": "blockquote",
+        "attrs": {
+          "multiline": false
+        },
+        "content": [
+          {
+            "type": "paragraph"
+          }
+        ]
+      }
+    ]
+  }
 06_13__inlines__hard_line_breaks__001: |-
   {
     "type": "doc",
diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js
index e969e3627ca9c..d1690c4f6c11b 100644
--- a/spec/frontend/content_editor/remark_markdown_processing_spec.js
+++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js
@@ -6,6 +6,7 @@ import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight
 import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
 import FootnoteReference from '~/content_editor/extensions/footnote_reference';
 import HardBreak from '~/content_editor/extensions/hard_break';
+import HTMLNodes from '~/content_editor/extensions/html_nodes';
 import Heading from '~/content_editor/extensions/heading';
 import HorizontalRule from '~/content_editor/extensions/horizontal_rule';
 import Image from '~/content_editor/extensions/image';
@@ -52,6 +53,7 @@ const tiptapEditor = createTestEditor({
     TableCell,
     TaskList,
     TaskItem,
+    ...HTMLNodes,
   ],
 });
 
@@ -64,6 +66,7 @@ const {
     bulletList,
     code,
     codeBlock,
+    div,
     footnoteDefinition,
     footnoteReference,
     hardBreak,
@@ -108,6 +111,13 @@ const {
     tableRow: { nodeType: TableRow.name },
     taskItem: { nodeType: TaskItem.name },
     taskList: { nodeType: TaskList.name },
+    ...HTMLNodes.reduce(
+      (builders, htmlNode) => ({
+        ...builders,
+        [htmlNode.name]: { nodeType: htmlNode.name },
+      }),
+      {},
+    ),
   },
 });
 
@@ -915,6 +925,12 @@ Paragraph
         paragraph(source('Paragraph'), 'Paragraph'),
       ),
     },
+    {
+      markdown: `
+<div>div</div>
+`,
+      expectedDoc: doc(div(source('<div>div</div>'), paragraph(source('div'), 'div'))),
+    },
   ];
 
   const runOnly = examples.find((example) => example.only === true);
@@ -928,4 +944,64 @@ Paragraph
     expect(document.toJSON()).toEqual(expectedDoc.toJSON());
     expect(serialize(document)).toEqual(trimmed);
   });
+
+  /**
+   * DISCLAIMER: THIS IS A SECURITY ORIENTED TEST THAT ENSURES
+   * THE CLIENT-SIDE PARSER IGNORES DANGEROUS TAGS THAT ARE NOT
+   * EXPLICITELY SUPPORTED.
+   *
+   * PLEASE CONSIDER THIS INFORMATION WHILE MODIFYING THESE TESTS
+   */
+  it.each([
+    {
+      markdown: `
+<script>
+alert("Hello world")
+</script>
+    `,
+      expectedHtml: '<p></p>',
+    },
+    {
+      markdown: `
+<foo>Hello</foo>
+      `,
+      expectedHtml: '<p></p>',
+    },
+    {
+      markdown: `
+<h1 class="heading-with-class">Header</h1>
+      `,
+      expectedHtml: '<h1>Header</h1>',
+    },
+    {
+      markdown: `
+<a id="link-id">Header</a> and other text
+      `,
+      expectedHtml:
+        '<p><a target="_blank" rel="noopener noreferrer nofollow">Header</a> and other text</p>',
+    },
+    {
+      markdown: `
+<style>
+body {
+  display: none;
+}
+</style>
+      `,
+      expectedHtml: '<p></p>',
+    },
+    {
+      markdown: '<div style="transform">div</div>',
+      expectedHtml: '<div><p>div</p></div>',
+    },
+  ])(
+    'removes unknown tags and unsupported attributes from HTML output',
+    async ({ markdown, expectedHtml }) => {
+      const document = await deserialize(markdown);
+
+      tiptapEditor.commands.setContent(document.toJSON());
+
+      expect(tiptapEditor.getHTML()).toEqual(expectedHtml);
+    },
+  );
 });
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index 156095dad14fc..68806f9c26add 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -57,8 +57,6 @@ const tiptapEditor = createTestEditor({
     HardBreak,
     Heading,
     HorizontalRule,
-    ...HTMLMarks,
-    ...HTMLNodes,
     Image,
     InlineDiff,
     Italic,
@@ -73,6 +71,8 @@ const tiptapEditor = createTestEditor({
     TableRow,
     TaskItem,
     TaskList,
+    ...HTMLMarks,
+    ...HTMLNodes,
   ],
 });
 
@@ -122,13 +122,6 @@ const {
     codeBlock: { nodeType: CodeBlockHighlight.name },
     details: { nodeType: Details.name },
     detailsContent: { nodeType: DetailsContent.name },
-    ...HTMLNodes.reduce(
-      (builders, htmlNode) => ({
-        ...builders,
-        [htmlNode.name]: { nodeType: htmlNode.name },
-      }),
-      {},
-    ),
     descriptionItem: { nodeType: DescriptionItem.name },
     descriptionList: { nodeType: DescriptionList.name },
     emoji: { markType: Emoji.name },
@@ -153,6 +146,13 @@ const {
     tableRow: { nodeType: TableRow.name },
     taskItem: { nodeType: TaskItem.name },
     taskList: { nodeType: TaskList.name },
+    ...HTMLNodes.reduce(
+      (builders, htmlNode) => ({
+        ...builders,
+        [htmlNode.name]: { nodeType: htmlNode.name },
+      }),
+      {},
+    ),
   },
 });
 
-- 
GitLab