Web Inspector: HTML Formatter - better handling for HTML specific tag cases (<p>...
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Sep 2019 18:37:07 +0000 (18:37 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Sep 2019 18:37:07 +0000 (18:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=201757
<rdar://problem/55409987>

Reviewed by Devin Rousso.

Source/WebInspectorUI:

* UserInterface/Workers/Formatter/HTMLFormatter.js:
(HTMLFormatter.prototype._after):
Handle a closing tag with different text than the opening tag.

* UserInterface/Workers/Formatter/HTMLTreeBuilderFormatter.js:
(HTMLTreeBuilderFormatter.prototype._pushParserNodeTopLevel):
(HTMLTreeBuilderFormatter.prototype._pushParserNodeStack):
(HTMLTreeBuilderFormatter.prototype._implicitlyCloseHTMLNodesForOpenTag):
(HTMLTreeBuilderFormatter.prototype._implicitlyCloseTagNamesInsideParentTagNames):
(HTMLTreeBuilderFormatter.prototype._indexOfStackNodeMatchingTagNames):
Generalize the implicit closing a bit. Allow open tags to implicitly
close certain other open tags in the stack.

LayoutTests:

* inspector/formatting/formatting-html-expected.txt:
* inspector/formatting/formatting-html.html:
* inspector/formatting/resources/html-tests/auto-close-normal-expected.html: Renamed from LayoutTests/inspector/formatting/resources/html-tests/auto-close-expected.html.
* inspector/formatting/resources/html-tests/auto-close-normal.html: Renamed from LayoutTests/inspector/formatting/resources/html-tests/auto-close.html.
* inspector/formatting/resources/html-tests/auto-close-special-expected.html: Added.
* inspector/formatting/resources/html-tests/auto-close-special.html: Added.
* inspector/formatting/resources/html-tests/list-expected.html:
* inspector/formatting/resources/html-tests/list.html:
* inspector/formatting/resources/html-tests/not-well-formed-1-expected.html:
* inspector/formatting/resources/html-tests/not-well-formed-1.html:
* inspector/formatting/resources/html-tests/p-expected.html: Added.
* inspector/formatting/resources/html-tests/p.html: Added.
* inspector/formatting/resources/html-tests/table-expected.html: Added.
* inspector/formatting/resources/html-tests/table.html: Added.
* inspector/formatting/resources/html-tests/tag-case-expected.html: Added.
* inspector/formatting/resources/html-tests/tag-case.html: Added.
Tests for HTML specialties.

* inspector/formatting/formatting-xml-expected.txt:
* inspector/formatting/formatting-xml.html:
* inspector/formatting/resources/xml-tests/tag-case-expected.xml: Added.
* inspector/formatting/resources/xml-tests/tag-case.xml: Added.
* inspector/formatting/resources/xml-tests/valid-html-invalid-xml-expected.xml:
* inspector/formatting/resources/xml-tests/valid-html-invalid-xml.xml:
XML is case-sensitive. Ensure XML doesn't get more of the HTML specialties.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@249969 268f45cc-cd09-0410-ab3c-d52691b4dbfc

26 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/formatting/formatting-html-expected.txt
LayoutTests/inspector/formatting/formatting-html.html
LayoutTests/inspector/formatting/formatting-xml-expected.txt
LayoutTests/inspector/formatting/formatting-xml.html
LayoutTests/inspector/formatting/resources/html-tests/auto-close-normal-expected.html [moved from LayoutTests/inspector/formatting/resources/html-tests/auto-close-expected.html with 100% similarity]
LayoutTests/inspector/formatting/resources/html-tests/auto-close-normal.html [moved from LayoutTests/inspector/formatting/resources/html-tests/auto-close.html with 100% similarity]
LayoutTests/inspector/formatting/resources/html-tests/auto-close-special-expected.html [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/html-tests/auto-close-special.html [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/html-tests/list-expected.html
LayoutTests/inspector/formatting/resources/html-tests/list.html
LayoutTests/inspector/formatting/resources/html-tests/not-well-formed-1-expected.html
LayoutTests/inspector/formatting/resources/html-tests/not-well-formed-1.html
LayoutTests/inspector/formatting/resources/html-tests/p-expected.html [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/html-tests/p.html [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/html-tests/table-expected.html [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/html-tests/table.html [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/html-tests/tag-case-expected.html [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/html-tests/tag-case.html [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/xml-tests/tag-case-expected.xml [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/xml-tests/tag-case.xml [new file with mode: 0644]
LayoutTests/inspector/formatting/resources/xml-tests/valid-html-invalid-xml-expected.xml
LayoutTests/inspector/formatting/resources/xml-tests/valid-html-invalid-xml.xml
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Workers/Formatter/HTMLFormatter.js
Source/WebInspectorUI/UserInterface/Workers/Formatter/HTMLTreeBuilderFormatter.js

index 7927b67..8039e21 100644 (file)
@@ -1,3 +1,37 @@
+2019-09-17  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: HTML Formatter - better handling for HTML specific tag cases (<p>/<li>)
+        https://bugs.webkit.org/show_bug.cgi?id=201757
+        <rdar://problem/55409987>
+
+        Reviewed by Devin Rousso.
+
+        * inspector/formatting/formatting-html-expected.txt:
+        * inspector/formatting/formatting-html.html:
+        * inspector/formatting/resources/html-tests/auto-close-normal-expected.html: Renamed from LayoutTests/inspector/formatting/resources/html-tests/auto-close-expected.html.
+        * inspector/formatting/resources/html-tests/auto-close-normal.html: Renamed from LayoutTests/inspector/formatting/resources/html-tests/auto-close.html.
+        * inspector/formatting/resources/html-tests/auto-close-special-expected.html: Added.
+        * inspector/formatting/resources/html-tests/auto-close-special.html: Added.
+        * inspector/formatting/resources/html-tests/list-expected.html:
+        * inspector/formatting/resources/html-tests/list.html:
+        * inspector/formatting/resources/html-tests/not-well-formed-1-expected.html:
+        * inspector/formatting/resources/html-tests/not-well-formed-1.html:
+        * inspector/formatting/resources/html-tests/p-expected.html: Added.
+        * inspector/formatting/resources/html-tests/p.html: Added.
+        * inspector/formatting/resources/html-tests/table-expected.html: Added.
+        * inspector/formatting/resources/html-tests/table.html: Added.
+        * inspector/formatting/resources/html-tests/tag-case-expected.html: Added.
+        * inspector/formatting/resources/html-tests/tag-case.html: Added.
+        Tests for HTML specialties.
+
+        * inspector/formatting/formatting-xml-expected.txt:
+        * inspector/formatting/formatting-xml.html:
+        * inspector/formatting/resources/xml-tests/tag-case-expected.xml: Added.
+        * inspector/formatting/resources/xml-tests/tag-case.xml: Added.
+        * inspector/formatting/resources/xml-tests/valid-html-invalid-xml-expected.xml:
+        * inspector/formatting/resources/xml-tests/valid-html-invalid-xml.xml:
+        XML is case-sensitive. Ensure XML doesn't get more of the HTML specialties.
+
 2019-09-17  Antti Koivisto  <antti@apple.com>
 
         TextIterator should convert tabs to spaces
index 399259f..0d43742 100644 (file)
@@ -4,7 +4,8 @@ Test HTML formatting.
 == Running test suite: HTMLFormatter
 -- Running test case: HTMLFormatter
 PASS: attributes.html
-PASS: auto-close.html
+PASS: auto-close-normal.html
+PASS: auto-close-special.html
 PASS: basic-1.html
 PASS: basic-2.html
 PASS: comments.html
@@ -23,5 +24,8 @@ PASS: list.html
 PASS: not-well-formed-1.html
 PASS: not-well-formed-2.html
 PASS: not-well-formed-3.html
+PASS: p.html
 PASS: self-closing.html
+PASS: table.html
+PASS: tag-case.html
 
index 0aac530..1fc1de1 100644 (file)
@@ -10,7 +10,8 @@ function test()
 
     addFormattingTests(suite, "text/html", [
         "resources/html-tests/attributes.html",
-        "resources/html-tests/auto-close.html",
+        "resources/html-tests/auto-close-normal.html",
+        "resources/html-tests/auto-close-special.html",
         "resources/html-tests/basic-1.html",
         "resources/html-tests/basic-2.html",
         "resources/html-tests/comments.html",
@@ -29,7 +30,10 @@ function test()
         "resources/html-tests/not-well-formed-1.html",
         "resources/html-tests/not-well-formed-2.html",
         "resources/html-tests/not-well-formed-3.html",
+        "resources/html-tests/p.html",
         "resources/html-tests/self-closing.html",
+        "resources/html-tests/table.html",
+        "resources/html-tests/tag-case.html",
     ]);
 
     suite.runTestCasesAndFinish();
index f06e748..b382b2d 100644 (file)
@@ -6,6 +6,7 @@ Test XML formatting.
 PASS: atom.xml
 PASS: basic.xml
 PASS: rss.xml
+PASS: tag-case.xml
 PASS: valid-html-invalid-xml.xml
 PASS: xslt.xml
 
index 855f044..7704bfe 100644 (file)
@@ -12,6 +12,7 @@ function test()
         "resources/xml-tests/atom.xml",
         "resources/xml-tests/basic.xml",
         "resources/xml-tests/rss.xml",
+        "resources/xml-tests/tag-case.xml",
         "resources/xml-tests/valid-html-invalid-xml.xml",
         "resources/xml-tests/xslt.xml",
     ]);
diff --git a/LayoutTests/inspector/formatting/resources/html-tests/auto-close-special-expected.html b/LayoutTests/inspector/formatting/resources/html-tests/auto-close-special-expected.html
new file mode 100644 (file)
index 0000000..34e935e
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- body closes head -->
+<head>
+    <title>Title
+<body>
+
+    <!-- option and optgroup -->
+    <select>
+        <option>1
+        <option>2
+            
+        <optgroup label="Group 1">
+            <option>1
+            <option>2
+                
+        <optgroup label="Group 2">
+            <option>1
+            <option>2
+    </select>
diff --git a/LayoutTests/inspector/formatting/resources/html-tests/auto-close-special.html b/LayoutTests/inspector/formatting/resources/html-tests/auto-close-special.html
new file mode 100644 (file)
index 0000000..777008f
--- /dev/null
@@ -0,0 +1,12 @@
+<!-- body closes head -->
+<head><title>Title
+<body>
+    
+<!-- option and optgroup -->
+<select>
+    <option>1<option>2
+    <optgroup label="Group 1">
+        <option>1<option>2
+    <optgroup label="Group 2">
+        <option>1<option>2
+</select>
index adc8ce4..380411b 100644 (file)
 </ol>
 
 <ul>
+    <li>One
+    <li>Two
+    <li>Three
+</ul>
+<ol>
+    <li>One
+    <li>Two
+    <li>Three
+</ol>
+
+<!-- Nested -->
+<ol>
     <li>
-        One
-        <li>
-            Two
-            <li>Three
+        <ul>
+            <li>1
+            <li>2
+        </ul>
+    <li>Test
+</ol>
+<ul>
+    <li>
+        <ol>
+            <li>1
+            <li>2
+        </ol>
+    <li>Test
 </ul>
 <ol>
     <li>
-        One
-        <li>
-            Two
-            <li>Three
+        <ol>
+            <li>1
+            <li>2
+        </ol>
+    <li>Test
 </ol>
+<ul>
+    <li>
+        <ul>
+            <li>1
+            <li>2
+        </ul>
+    <li>Test
+</ul>
index 1e90797..fef1858 100644 (file)
@@ -3,3 +3,9 @@
 
 <ul><li>One<li>Two<li>Three</ul>
 <ol><li>One<li>Two<li>Three</ol>
+
+<!-- Nested -->
+<ol><li><ul><li>1<li>2</ul><li>Test</ol>
+<ul><li><ol><li>1<li>2</ol><li>Test</ul>
+<ol><li><ol><li>1<li>2</ol><li>Test</ol>
+<ul><li><ul><li>1<li>2</ul><li>Test</ul>
diff --git a/LayoutTests/inspector/formatting/resources/html-tests/p-expected.html b/LayoutTests/inspector/formatting/resources/html-tests/p-expected.html
new file mode 100644 (file)
index 0000000..2be0d65
--- /dev/null
@@ -0,0 +1,18 @@
+<p>1
+<p>2
+<p>3
+
+<div>
+    <p>1
+    <p>2
+    <p>3
+</div>
+
+<div>
+    <section>
+        <p>1
+        <p>2
+        <section>
+            <p>3
+            <p>4
+</div>
diff --git a/LayoutTests/inspector/formatting/resources/html-tests/p.html b/LayoutTests/inspector/formatting/resources/html-tests/p.html
new file mode 100644 (file)
index 0000000..698e4bb
--- /dev/null
@@ -0,0 +1,5 @@
+<p>1<p>2<p>3
+
+<div><p>1<p>2<p>3</div>
+
+<div><section><p>1<p>2<section><p>3<p>4</div>
diff --git a/LayoutTests/inspector/formatting/resources/html-tests/table-expected.html b/LayoutTests/inspector/formatting/resources/html-tests/table-expected.html
new file mode 100644 (file)
index 0000000..2b90efb
--- /dev/null
@@ -0,0 +1,47 @@
+<table>
+    <tr>
+        <th>1
+        <td>2
+        <td>3
+            
+    <tr>
+        <td>1
+        <th>2
+        <td>3
+            
+    <tr>
+        <td>1
+        <td>2
+        <th>3
+            
+    <tr>
+        <th>1
+        <th>2
+        <th>3
+</table>
+
+<table>
+    <td>1
+    <td>2
+    <tr>
+        <td>3
+        <td>4
+</table>
+<table>
+    <th>1
+    <th>2
+    <tr>
+        <th>3
+        <th>4
+</table>
+
+<table>
+    <thead>Head
+    <tbody>Body
+    <tfoot>Foot
+</table>
+<table>
+    <tr>
+        <td>1
+    <tfoot>Text
+</table>
diff --git a/LayoutTests/inspector/formatting/resources/html-tests/table.html b/LayoutTests/inspector/formatting/resources/html-tests/table.html
new file mode 100644 (file)
index 0000000..2b8445c
--- /dev/null
@@ -0,0 +1,12 @@
+<table>
+    <tr><th>1<td>2<td>3
+    <tr><td>1<th>2<td>3
+    <tr><td>1<td>2<th>3
+    <tr><th>1<th>2<th>3
+</table>
+
+<table><td>1<td>2<tr><td>3<td>4</table>
+<table><th>1<th>2<tr><th>3<th>4</table>
+
+<table><thead>Head<tbody>Body<tfoot>Foot</table>
+<table><tr><td>1<tfoot>Text</table>
diff --git a/LayoutTests/inspector/formatting/resources/html-tests/tag-case-expected.html b/LayoutTests/inspector/formatting/resources/html-tests/tag-case-expected.html
new file mode 100644 (file)
index 0000000..c08c95f
--- /dev/null
@@ -0,0 +1,2 @@
+<div>text</DIV>
+<DIV>text</div>
diff --git a/LayoutTests/inspector/formatting/resources/html-tests/tag-case.html b/LayoutTests/inspector/formatting/resources/html-tests/tag-case.html
new file mode 100644 (file)
index 0000000..c08c95f
--- /dev/null
@@ -0,0 +1,2 @@
+<div>text</DIV>
+<DIV>text</div>
diff --git a/LayoutTests/inspector/formatting/resources/xml-tests/tag-case-expected.xml b/LayoutTests/inspector/formatting/resources/xml-tests/tag-case-expected.xml
new file mode 100644 (file)
index 0000000..df2feab
--- /dev/null
@@ -0,0 +1,4 @@
+<div>
+    text</DIV>
+    <DIV>text
+</div>
diff --git a/LayoutTests/inspector/formatting/resources/xml-tests/tag-case.xml b/LayoutTests/inspector/formatting/resources/xml-tests/tag-case.xml
new file mode 100644 (file)
index 0000000..c08c95f
--- /dev/null
@@ -0,0 +1,2 @@
+<div>text</DIV>
+<DIV>text</div>
index cff4fcc..3c0192c 100644 (file)
@@ -1,4 +1,11 @@
 <outer>
     <img src="1">
         <img src="2">
+            <ol>
+                <li>
+                    One
+                    <li>
+                        Two
+                        <li>Three
+            </ol>
 </outer>
index 0da6931..c9f872c 100644 (file)
@@ -1,3 +1,24 @@
+2019-09-17  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: HTML Formatter - better handling for HTML specific tag cases (<p>/<li>)
+        https://bugs.webkit.org/show_bug.cgi?id=201757
+        <rdar://problem/55409987>
+
+        Reviewed by Devin Rousso.
+
+        * UserInterface/Workers/Formatter/HTMLFormatter.js:
+        (HTMLFormatter.prototype._after):
+        Handle a closing tag with different text than the opening tag.
+
+        * UserInterface/Workers/Formatter/HTMLTreeBuilderFormatter.js:
+        (HTMLTreeBuilderFormatter.prototype._pushParserNodeTopLevel):
+        (HTMLTreeBuilderFormatter.prototype._pushParserNodeStack):
+        (HTMLTreeBuilderFormatter.prototype._implicitlyCloseHTMLNodesForOpenTag):
+        (HTMLTreeBuilderFormatter.prototype._implicitlyCloseTagNamesInsideParentTagNames):
+        (HTMLTreeBuilderFormatter.prototype._indexOfStackNodeMatchingTagNames):
+        Generalize the implicit closing a bit. Allow open tags to implicitly
+        close certain other open tags in the stack.
+
 2019-09-13  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: HTML Formatter - XML mode
index db28d7d..bbd53ad 100644 (file)
@@ -289,8 +289,9 @@ HTMLFormatter = class HTMLFormatter
                 this._builder.appendNewline();
             }
             if (!node.implicitClose) {
+                console.assert(node.closeTagName);
                 console.assert(node.closeTagPos);
-                this._builder.appendToken("</" + node.name + ">", node.closeTagPos);
+                this._builder.appendToken("</" + node.closeTagName + ">", node.closeTagPos);
             }
             this._builder.appendNewline();
             return;
index c93b191..8b36b4c 100644 (file)
@@ -24,9 +24,9 @@
  */
 
 // This tree builder attempts to match input text to output DOM node.
-// This therefore doesn't do HTML5 tree construction like auto-closing
+// This therefore doesn't do HTML5 tree construction like implicitly-closing
 // specific HTML parent nodes depending on being in a particular node,
-// it only does basic auto-closing. In general this tries to be a
+// it only does basic implicitly-closing. In general this tries to be a
 // whitespace reformatter for input text and not generate the ultimate
 // html tree that a browser would generate.
 //
@@ -51,7 +51,7 @@ HTMLTreeBuilderFormatter = class HTMLTreeBuilderFormatter
 
     pushParserNode(parserNode)
     {
-        let containerNode = this._stackOfOpenElements[this._stackOfOpenElements.length - 1];
+        let containerNode = this._stackOfOpenElements.lastValue;
         if (!containerNode)
             this._pushParserNodeTopLevel(parserNode);
         else
@@ -90,34 +90,34 @@ HTMLTreeBuilderFormatter = class HTMLTreeBuilderFormatter
     {
         if (parserNode.type === HTMLParser.NodeType.OpenTag) {
             let node = this._buildDOMNodeFromOpenTag(parserNode);
-            containerNode.children.push(node);
+            let childrenArray = containerNode.children;
+            if (!this._isXML) {
+                this._implicitlyCloseHTMLNodesForOpenTag(parserNode, node);
+                containerNode = this._stackOfOpenElements.lastValue;
+                childrenArray = containerNode ? containerNode.children : this._dom;
+            }
+            childrenArray.push(node);
             if (!this._isEmptyNode(parserNode, node))
                 this._stackOfOpenElements.push(node);
             return;
         }
 
         if (parserNode.type === HTMLParser.NodeType.CloseTag) {
-            let found = false;
-            let nodesToPop = 0;
-            for (let i = this._stackOfOpenElements.length - 1; i >= 0; --i) {
-                nodesToPop++;
-                let stackNode = this._stackOfOpenElements[i];
-                if (stackNode.name === parserNode.name) {
-                    found = true;
-                    break;
-                }
-            }
+            let tagName = this._isXML ? parserNode.name : parserNode.name.toLowerCase();
+            let matchingOpenTagIndex = this._indexOfStackNodeMatchingTagNames([tagName]);
 
-            // Found a matching tag, auto-close nodes.
-            if (found) {
-                console.assert(nodesToPop > 0);
+            // Found a matching tag, implicitly-close nodes.
+            if (matchingOpenTagIndex !== -1) {
+                let nodesToPop = this._stackOfOpenElements.length - matchingOpenTagIndex;
                 for (let i = 0; i < nodesToPop - 1; ++i) {
-                    let autoClosingNode = this._stackOfOpenElements.pop();
-                    autoClosingNode.implicitClose = true;
+                    let implicitlyClosingNode = this._stackOfOpenElements.pop();
+                    implicitlyClosingNode.implicitClose = true;
+                }
+                let implicitlyClosingNode = this._stackOfOpenElements.pop();
+                if (parserNode.pos) {
+                    implicitlyClosingNode.closeTagPos = parserNode.pos;
+                    implicitlyClosingNode.closeTagName = parserNode.name;
                 }
-                let autoClosingNode = this._stackOfOpenElements.pop();
-                if (parserNode.pos)
-                    autoClosingNode.closeTagPos = parserNode.pos;
                 return;
             }
 
@@ -132,6 +132,150 @@ HTMLTreeBuilderFormatter = class HTMLTreeBuilderFormatter
         containerNode.children.push(node);
     }
 
+    _implicitlyCloseHTMLNodesForOpenTag(parserNode, node)
+    {
+        if (parserNode.closed)
+            return;
+
+        switch (node.lowercaseName) {
+        // <body> closes <head>.
+        case "body":
+            this._implicitlyCloseTagNamesInsideParentTagNames(["head"]);
+            break;
+
+        // Inside <select>.
+        case "option":
+            this._implicitlyCloseTagNamesInsideParentTagNames(["option"], ["select"]);
+            break;
+        case "optgroup": {
+            let didClose = this._implicitlyCloseTagNamesInsideParentTagNames(["optgroup"], ["select"]);;
+            if (!didClose)
+                this._implicitlyCloseTagNamesInsideParentTagNames(["option"], ["select"]);
+            break;
+        }
+
+        // Inside <ol>/<ul>.
+        case "li":
+            this._implicitlyCloseTagNamesInsideParentTagNames(["li"], ["ol", "ul"]);
+            break;
+
+        // Inside <dl>.
+        case "dd":
+        case "dt":
+            this._implicitlyCloseTagNamesInsideParentTagNames(["dd", "dt"], ["dl"]);
+            break;
+
+        // Inside <table>.
+        case "tr": {
+            let didClose = this._implicitlyCloseTagNamesInsideParentTagNames(["tr"], ["table"]);
+            if (!didClose)
+                this._implicitlyCloseTagNamesInsideParentTagNames(["td", "th"], ["table"]);
+            break;
+        }
+        case "td":
+        case "th":
+            this._implicitlyCloseTagNamesInsideParentTagNames(["td", "th"], ["table"]);
+            break;
+        case "tbody": {
+            let didClose = this._implicitlyCloseTagNamesInsideParentTagNames(["thead"], ["table"]);
+            if (!didClose)
+                didClose = this._implicitlyCloseTagNamesInsideParentTagNames(["tr"], ["table"]);
+            break;
+        }
+        case "tfoot": {
+            let didClose = this._implicitlyCloseTagNamesInsideParentTagNames(["tbody"], ["table"]);
+            if (!didClose)
+                didClose = this._implicitlyCloseTagNamesInsideParentTagNames(["tr"], ["table"]);
+            break;
+        }
+        case "colgroup":
+            this._implicitlyCloseTagNamesInsideParentTagNames(["colgroup"], ["table"]);
+            break;
+
+        // Nodes that implicitly close a <p>. Normally this is only in <body> but we simplify to always.
+        // https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody
+        case "address":
+        case "article":
+        case "aside":
+        case "blockquote":
+        case "center":
+        case "details":
+        case "dialog":
+        case "dir":
+        case "div":
+        case "dl":
+        case "fieldset":
+        case "figcaption":
+        case "figure":
+        case "footer":
+        case "form":
+        case "h1":
+        case "h2":
+        case "h3":
+        case "h4":
+        case "h5":
+        case "h6":
+        case "header":
+        case "hgroup":
+        case "hr":
+        case "listing":
+        case "main":
+        case "menu":
+        case "nav":
+        case "ol":
+        case "p":
+        case "plaintext":
+        case "pre":
+        case "section":
+        case "summary":
+        case "table":
+        case "ul":
+        case "xmp":
+            this._implicitlyCloseTagNamesInsideParentTagNames(["p"]);
+            break;
+        }
+    }
+
+    _implicitlyCloseTagNamesInsideParentTagNames(tagNames, containerScopeTagNames)
+    {
+        console.assert(!this._isXML, "Implicitly closing only happens in HTML. Also, names are compared case insensitively which would be invalid for XML.");
+
+        let existingOpenTagIndex = this._indexOfStackNodeMatchingTagNames(tagNames);
+        if (existingOpenTagIndex === -1)
+            return false;
+
+        // Disallow impliticly closing beyond the container tag boundary.
+        if (containerScopeTagNames) {
+            for (let i = existingOpenTagIndex + 1; i < this._stackOfOpenElements.length; ++i) {
+                let stackNode = this._stackOfOpenElements[i];
+                let name = stackNode.lowercaseName;
+                if (containerScopeTagNames.includes(name))
+                    return false;
+            }
+        }
+
+        // Implicitly close tags.
+        let nodesToPop = this._stackOfOpenElements.length - existingOpenTagIndex;
+        for (let i = 0; i < nodesToPop; ++i) {
+            let implicitlyClosingNode = this._stackOfOpenElements.pop();
+            implicitlyClosingNode.implicitClose = true;
+        }
+
+        return true;
+    }
+
+    _indexOfStackNodeMatchingTagNames(tagNames)
+    {
+        for (let i = this._stackOfOpenElements.length - 1; i >= 0; --i) {
+            let stackNode = this._stackOfOpenElements[i];
+            let name = this._isXML ? stackNode.name : stackNode.lowercaseName;
+            if (tagNames.includes(name))
+                return i;
+        }
+
+        return -1;
+    }
+
     _isEmptyNode(parserNode, node)
     {
         if (parserNode.closed)