diff --git a/src/Parser.events.spec.ts b/src/Parser.events.spec.ts index 40c4c85e0..17d761e8e 100644 --- a/src/Parser.events.spec.ts +++ b/src/Parser.events.spec.ts @@ -215,6 +215,28 @@ describe("Events", () => { recognizeSelfClosing: true, })); + it("Self-closing SVG integration point acknowledges the slash", () => + runTest("x")); + + it("Self-closing SVG title acknowledges the slash", () => + runTest("x")); + + it("Self-closing SVG desc acknowledges the slash", () => + runTest("x")); + + it("Self-closing MathML integration point acknowledges the slash", () => + runTest("x")); + + it("Self-closing MathML annotation-xml acknowledges the slash", () => + runTest("x")); + + it("Self-closing HTML descendant of an integration point ignores the slash", () => + /* + * `title` here is HTML (inside foreignObject's HTML context), not a + * foreign integration point, so its slash must not be acknowledged. + */ + runTest("x</foreignObject></svg>")); + it("HTML image alias", () => runTest("<image></image>")); it("SVG image is not aliased", () => runTest("<svg><image></image></svg>")); diff --git a/src/Parser.ts b/src/Parser.ts index 33050243b..174a231ff 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -338,6 +338,25 @@ export class Parser implements Callbacks { return this.foreignContext[0] !== ForeignContext.None; } + /** + * Whether the element currently being opened is a foreign (SVG/MathML) + * element. Foreign elements acknowledge the self-closing flag even in HTML + * mode. + * + * HTML integration points (e.g. `<foreignObject>`, or `<title>` inside + * `<svg>`) switch their *children* back to the HTML context, so + * `isInForeignContext()` reports `false` for them even though the element + * itself is foreign. For those, check the enclosing context instead. + */ + private currentTagIsForeign(): boolean { + return ( + this.isInForeignContext() || + (this.htmlMode && + htmlIntegrationElements.has(this.tagname) && + this.foreignContext[1] !== ForeignContext.None) + ); + } + /** * Checks if the current tag is a void element. Override this if you want * to specify your own additional void elements. @@ -503,7 +522,7 @@ export class Parser implements Callbacks { */ onselfclosingtag(endIndex: number): void { this.endIndex = endIndex; - if (this.recognizeSelfClosing || this.isInForeignContext()) { + if (this.recognizeSelfClosing || this.currentTagIsForeign()) { this.closeCurrentTag(false); // Set `startIndex` for next node diff --git a/src/__snapshots__/Parser.events.spec.ts.snap b/src/__snapshots__/Parser.events.spec.ts.snap index 686fcafcc..30d563f9e 100644 --- a/src/__snapshots__/Parser.events.spec.ts.snap +++ b/src/__snapshots__/Parser.events.spec.ts.snap @@ -2767,6 +2767,435 @@ exports[`Events > Scripts ending with < 1`] = ` ] `; +exports[`Events > Self-closing HTML descendant of an integration point ignores the slash 1`] = ` +[ + { + "$event": "opentagname", + "data": [ + "svg", + ], + "endIndex": 4, + "startIndex": 0, + }, + { + "$event": "opentag", + "data": [ + "svg", + {}, + false, + ], + "endIndex": 4, + "startIndex": 0, + }, + { + "$event": "opentagname", + "data": [ + "foreignObject", + ], + "endIndex": 19, + "startIndex": 5, + }, + { + "$event": "opentag", + "data": [ + "foreignObject", + {}, + false, + ], + "endIndex": 19, + "startIndex": 5, + }, + { + "$event": "opentagname", + "data": [ + "title", + ], + "endIndex": 26, + "startIndex": 20, + }, + { + "$event": "opentag", + "data": [ + "title", + {}, + false, + ], + "endIndex": 27, + "startIndex": 20, + }, + { + "$event": "text", + "data": [ + "x</foreignObject></svg>", + ], + "endIndex": 50, + "startIndex": 28, + }, + { + "$event": "closetag", + "data": [ + "title", + true, + ], + "endIndex": 51, + "startIndex": 51, + }, + { + "$event": "closetag", + "data": [ + "foreignObject", + true, + ], + "endIndex": 51, + "startIndex": 51, + }, + { + "$event": "closetag", + "data": [ + "svg", + true, + ], + "endIndex": 51, + "startIndex": 51, + }, +] +`; + +exports[`Events > Self-closing MathML annotation-xml acknowledges the slash 1`] = ` +[ + { + "$event": "opentagname", + "data": [ + "math", + ], + "endIndex": 5, + "startIndex": 0, + }, + { + "$event": "opentag", + "data": [ + "math", + {}, + false, + ], + "endIndex": 5, + "startIndex": 0, + }, + { + "$event": "opentagname", + "data": [ + "annotation-xml", + ], + "endIndex": 21, + "startIndex": 6, + }, + { + "$event": "opentag", + "data": [ + "annotation-xml", + {}, + false, + ], + "endIndex": 22, + "startIndex": 6, + }, + { + "$event": "closetag", + "data": [ + "annotation-xml", + true, + ], + "endIndex": 22, + "startIndex": 6, + }, + { + "$event": "text", + "data": [ + "x", + ], + "endIndex": 23, + "startIndex": 23, + }, + { + "$event": "closetag", + "data": [ + "math", + false, + ], + "endIndex": 30, + "startIndex": 24, + }, +] +`; + +exports[`Events > Self-closing MathML integration point acknowledges the slash 1`] = ` +[ + { + "$event": "opentagname", + "data": [ + "math", + ], + "endIndex": 5, + "startIndex": 0, + }, + { + "$event": "opentag", + "data": [ + "math", + {}, + false, + ], + "endIndex": 5, + "startIndex": 0, + }, + { + "$event": "opentagname", + "data": [ + "mi", + ], + "endIndex": 9, + "startIndex": 6, + }, + { + "$event": "opentag", + "data": [ + "mi", + {}, + false, + ], + "endIndex": 10, + "startIndex": 6, + }, + { + "$event": "closetag", + "data": [ + "mi", + true, + ], + "endIndex": 10, + "startIndex": 6, + }, + { + "$event": "text", + "data": [ + "x", + ], + "endIndex": 11, + "startIndex": 11, + }, + { + "$event": "closetag", + "data": [ + "math", + false, + ], + "endIndex": 18, + "startIndex": 12, + }, +] +`; + +exports[`Events > Self-closing SVG desc acknowledges the slash 1`] = ` +[ + { + "$event": "opentagname", + "data": [ + "svg", + ], + "endIndex": 4, + "startIndex": 0, + }, + { + "$event": "opentag", + "data": [ + "svg", + {}, + false, + ], + "endIndex": 4, + "startIndex": 0, + }, + { + "$event": "opentagname", + "data": [ + "desc", + ], + "endIndex": 10, + "startIndex": 5, + }, + { + "$event": "opentag", + "data": [ + "desc", + {}, + false, + ], + "endIndex": 11, + "startIndex": 5, + }, + { + "$event": "closetag", + "data": [ + "desc", + true, + ], + "endIndex": 11, + "startIndex": 5, + }, + { + "$event": "text", + "data": [ + "x", + ], + "endIndex": 12, + "startIndex": 12, + }, + { + "$event": "closetag", + "data": [ + "svg", + false, + ], + "endIndex": 18, + "startIndex": 13, + }, +] +`; + +exports[`Events > Self-closing SVG integration point acknowledges the slash 1`] = ` +[ + { + "$event": "opentagname", + "data": [ + "svg", + ], + "endIndex": 4, + "startIndex": 0, + }, + { + "$event": "opentag", + "data": [ + "svg", + {}, + false, + ], + "endIndex": 4, + "startIndex": 0, + }, + { + "$event": "opentagname", + "data": [ + "foreignObject", + ], + "endIndex": 19, + "startIndex": 5, + }, + { + "$event": "opentag", + "data": [ + "foreignObject", + {}, + false, + ], + "endIndex": 20, + "startIndex": 5, + }, + { + "$event": "closetag", + "data": [ + "foreignObject", + true, + ], + "endIndex": 20, + "startIndex": 5, + }, + { + "$event": "text", + "data": [ + "x", + ], + "endIndex": 21, + "startIndex": 21, + }, + { + "$event": "closetag", + "data": [ + "svg", + false, + ], + "endIndex": 27, + "startIndex": 22, + }, +] +`; + +exports[`Events > Self-closing SVG title acknowledges the slash 1`] = ` +[ + { + "$event": "opentagname", + "data": [ + "svg", + ], + "endIndex": 4, + "startIndex": 0, + }, + { + "$event": "opentag", + "data": [ + "svg", + {}, + false, + ], + "endIndex": 4, + "startIndex": 0, + }, + { + "$event": "opentagname", + "data": [ + "title", + ], + "endIndex": 11, + "startIndex": 5, + }, + { + "$event": "opentag", + "data": [ + "title", + {}, + false, + ], + "endIndex": 12, + "startIndex": 5, + }, + { + "$event": "closetag", + "data": [ + "title", + true, + ], + "endIndex": 12, + "startIndex": 5, + }, + { + "$event": "text", + "data": [ + "x", + ], + "endIndex": 13, + "startIndex": 13, + }, + { + "$event": "closetag", + "data": [ + "svg", + false, + ], + "endIndex": 19, + "startIndex": 14, + }, +] +`; + exports[`Events > Self-closing foreign element with recognizeSelfClosing 1`] = ` [ {