From e262097029d63ea426e85456b86b73648cd0ec2e Mon Sep 17 00:00:00 2001
From: SkyZeroZx <73321943+SkyZeroZx@users.noreply.github.com>
Date: Thu, 12 Nov 2026 13:02:25 -0500
Subject: [PATCH] fix: escape raw text wherever a parser can read it as foreign
content
A parser assigns namespaces from the markup, not from our tree, so the text of
an HTML raw text element is escaped wherever a parser re-reading the output may
process its start tag in SVG or MathML content, and stays raw everywhere else,
including at HTML integration points.
The HTML loop of serializeChildren() stays as it was. svg and math subtrees go
through serializeForeignContent(), which tracks top-down how a parser reads
them: integration points only count in their own namespace, and template
contents and fallback raw text are covered. Raw text stays escaped once the
parser can leave the tree inside svg or math: when an end tag closes a
same-named foreign ancestor of an integration point, when a breakout tag pops a
nested svg or math element, when raw text is ended early by its own end tag,
after a void element, which is not void in foreign content, and at a table
start tag in an integration point of an svg or math element foster parented by
a table, where the table insertion mode pops past the integration point.
A style, script, xmp or plaintext element that may be read as foreign content
gets all of its markup escaped instead of the end tags that match it, since
those are real end tags of nested elements when it is read as foreign content.
Fixes angular/angular#69900
---
lib/NodeUtils.js | 377 ++++++++++++++++++++++++++++++++++++++++++++++-
test/xss.js | 364 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 735 insertions(+), 6 deletions(-)
diff --git a/lib/NodeUtils.js b/lib/NodeUtils.js
index a80490b..af01180 100644
--- a/lib/NodeUtils.js
+++ b/lib/NodeUtils.js
@@ -123,6 +123,16 @@ function escapeAttr(s) {
});
}
+function serializeForeignRawText(element) {
+ var s = '';
+ for (var child = element.firstChild; child; child = child.nextSibling) {
+ s += child.nodeType === 3 /*TEXT_NODE*/ ||
+ child.nodeType === 4 /*CDATA_SECTION_NODE*/ ?
+ escape(child.data) : serializeOne(child, element);
+ }
+ return s;
+}
+
function attrname(a) {
var ns = a.namespaceURI;
if (!ns)
@@ -291,6 +301,255 @@ function escapeProcessingInstructionContent(rawContent) {
: rawContent;
}
+// The contexts in which a parser that re-reads our output may process a start
+// tag. A parser assigns namespaces from the markup, not from our tree, and an
+// element is resolved knowing only its ancestors, so it is resolved against
+// every context it may be parsed in. Raw text stays raw only when none of them
+// is foreign content.
+// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction
+var HTML_CONTEXT = 1; // HTML rules, including at HTML integration points
+var MATHML_TEXT_CONTEXT = 2; // in mi, mo, mn, ms or mtext
+var SVG_CONTEXT = 4;
+var MATHML_CONTEXT = 8;
+var ANNOTATION_XML_CONTEXT = 16; // in annotation-xml without an HTML encoding
+var HTML_RULES = HTML_CONTEXT | MATHML_TEXT_CONTEXT;
+var FOREIGN_CONTEXTS = SVG_CONTEXT | MATHML_CONTEXT | ANNOTATION_XML_CONTEXT;
+// Where the element is rather than how it is parsed: inside an svg or math
+// element, inside one that sits in an integration point of another, inside
+// the content of a MathML text integration point (which a parser can pop back
+// to, and then reads mglyph and malignmark as MathML), and inside the HTML
+// content of any integration point (where the integration point itself can be
+// the current node, so an end tag is matched against its foreign ancestors).
+var IN_FOREIGN_CONTENT = 32;
+var IN_NESTED_FOREIGN_CONTENT = 64;
+var IN_MATHML_TEXT_CONTENT = 128;
+var IN_INTEGRATION_POINT_CONTENT = 256;
+// Where a parser may be in a table insertion mode inside foreign content, and
+// inside an svg or math element that it foster parents there, whose
+// integration points it then parses in that insertion mode.
+var IN_TABLE_CONTENT = 512;
+var IN_FOSTER_PARENTED_FOREIGN_CONTENT = 1024;
+var INHERITED_FLAGS = IN_FOREIGN_CONTENT | IN_NESTED_FOREIGN_CONTENT |
+ IN_MATHML_TEXT_CONTENT | IN_TABLE_CONTENT |
+ IN_FOSTER_PARENTED_FOREIGN_CONTENT;
+
+// Start tags that pop the parser out of foreign content.
+// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign
+var foreignContentBreakoutTags = {
+ b: true, big: true, blockquote: true, body: true, br: true, center: true,
+ code: true, dd: true, div: true, dl: true, dt: true, em: true, embed: true,
+ h1: true, h2: true, h3: true, h4: true, h5: true, h6: true, head: true,
+ hr: true, i: true, img: true, li: true, listing: true, menu: true,
+ meta: true, nobr: true, ol: true, p: true, pre: true, ruby: true, s: true,
+ small: true, span: true, strong: true, strike: true, sub: true, sup: true,
+ table: true, tt: true, u: true, ul: true, var: true
+};
+
+// Elements whose content a parser reads as text up to their own end tag, which
+// a same-named element in it therefore ends early. The other raw text elements
+// get that end tag escaped in all of their content.
+var textEndedByOwnEndTag = {
+ iframe: true, noembed: true, noframes: true, noscript: true, textarea: true,
+ title: true
+};
+
+// Elements whose content a parser reads in a table insertion mode (true) or
+// in one where a table start tag is not special (false). In a table insertion
+// mode, a table start tag pops elements up to the table, whatever integration
+// points are on the way, and so the table too. The other table parts keep the
+// insertion mode as it is, or are ignored outside of a table.
+var tableInsertionModes = {
+ table: true, td: false, th: false, caption: false, template: false
+};
+
+// `serializedTagName()` in ASCII lowercase, as a parser reads it. Reads
+// `_localName` directly: `localName` is a megamorphic getter here.
+function lowerCaseTagName(element) {
+ var ns = element.namespaceURI;
+ var name = ns === NAMESPACE.HTML || ns === NAMESPACE.SVG ||
+ ns === NAMESPACE.MATHML ? element._localName : element.tagName;
+ for (var i = 0; i < name.length; i++) {
+ var c = name.charCodeAt(i);
+ if (c >= 65 && c <= 90) return utils.toASCIILowerCase(name);
+ }
+ return name;
+}
+
+// ASCII case-insensitive `svg` or `math`, without allocating.
+function isSvgOrMathName(name) {
+ if (name.length === 3) {
+ return (name.charCodeAt(0) | 32) === 115 &&
+ (name.charCodeAt(1) | 32) === 118 && (name.charCodeAt(2) | 32) === 103;
+ }
+ if (name.length === 4) {
+ return (name.charCodeAt(0) | 32) === 109 &&
+ (name.charCodeAt(1) | 32) === 97 && (name.charCodeAt(2) | 32) === 116 &&
+ (name.charCodeAt(3) | 32) === 104;
+ }
+ return false;
+}
+
+function attributeValue(element, name) {
+ for (var i = 0; i < element._numattrs; i++) {
+ var attribute = element._attr(i);
+ if (utils.toASCIILowerCase(attrname(attribute)) === name)
+ return attribute.value;
+ }
+ return null;
+}
+
+function breaksOutOfForeignContent(element, name) {
+ return foreignContentBreakoutTags[name] === true || (name === 'font' &&
+ (attributeValue(element, 'color') !== null ||
+ attributeValue(element, 'face') !== null ||
+ attributeValue(element, 'size') !== null));
+}
+
+function mathmlChildContexts(element, name) {
+ if (name === 'mi' || name === 'mo' || name === 'mn' || name === 'ms' ||
+ name === 'mtext') {
+ return MATHML_TEXT_CONTEXT | IN_MATHML_TEXT_CONTENT |
+ IN_INTEGRATION_POINT_CONTENT;
+ }
+ if (name === 'annotation-xml') {
+ var encoding = attributeValue(element, 'encoding');
+ encoding = encoding && utils.toASCIILowerCase(encoding);
+ return encoding === 'text/html' || encoding === 'application/xhtml+xml' ?
+ HTML_CONTEXT | IN_INTEGRATION_POINT_CONTENT : ANNOTATION_XML_CONTEXT;
+ }
+ return MATHML_CONTEXT;
+}
+
+// The contexts in which the children of `element` are parsed, when `element`
+// itself is parsed in one of `contexts`. `name` is `lowerCaseTagName(element)`
+// if the caller already has it.
+function childContexts(element, contexts, name) {
+ // Fast path: in HTML, only `svg` and `math` change the context.
+ if (contexts === HTML_CONTEXT && !isSvgOrMathName(element._localName))
+ return HTML_CONTEXT;
+
+ if (name === undefined) name = lowerCaseTagName(element);
+ var result = contexts & INHERITED_FLAGS;
+ if (contexts & FOREIGN_CONTEXTS) {
+ result |= IN_FOREIGN_CONTENT;
+ if (breaksOutOfForeignContent(element, name)) {
+ contexts |= HTML_CONTEXT;
+ } else {
+ if (contexts & SVG_CONTEXT) {
+ result |= name === 'foreignobject' || name === 'desc' ||
+ name === 'title' ? HTML_CONTEXT | IN_INTEGRATION_POINT_CONTENT :
+ SVG_CONTEXT;
+ }
+ if (contexts & MATHML_CONTEXT)
+ result |= mathmlChildContexts(element, name);
+ if (contexts & ANNOTATION_XML_CONTEXT) {
+ result |= name === 'svg' ? SVG_CONTEXT :
+ mathmlChildContexts(element, name);
+ }
+ // An earlier sibling may have popped the parser out of foreign content.
+ contexts |= HTML_RULES;
+ }
+ }
+
+ var mathmlGlyph = name === 'mglyph' || name === 'malignmark';
+ if (contexts & MATHML_TEXT_CONTEXT && mathmlGlyph)
+ result |= MATHML_CONTEXT;
+ if (contexts & HTML_CONTEXT ||
+ (contexts & MATHML_TEXT_CONTEXT && !mathmlGlyph)) {
+ if (name === 'svg' || name === 'math') {
+ result |= (name === 'svg' ? SVG_CONTEXT : MATHML_CONTEXT) |
+ IN_FOREIGN_CONTENT;
+ if (contexts & IN_FOREIGN_CONTENT) result |= IN_NESTED_FOREIGN_CONTENT;
+ if (contexts & IN_TABLE_CONTENT)
+ result |= IN_FOSTER_PARENTED_FOREIGN_CONTENT;
+ } else {
+ result |= HTML_CONTEXT | (contexts & IN_INTEGRATION_POINT_CONTENT);
+ // Only an element that is surely HTML leaves a table insertion mode: as
+ // foreign content, it would leave the insertion mode as it is.
+ var tableInsertionMode = tableInsertionModes[name];
+ if (tableInsertionMode === true)
+ result |= IN_TABLE_CONTENT;
+ else if (tableInsertionMode === false && !(contexts & FOREIGN_CONTEXTS))
+ result &= ~IN_TABLE_CONTENT;
+ // The parser can pop back to a MathML text integration point first.
+ if (mathmlGlyph && contexts & IN_MATHML_TEXT_CONTENT)
+ result |= MATHML_CONTEXT;
+ }
+ }
+ return result;
+}
+
+// Whether a parser may stop following our tree at `element`'s start tag
+// inside foreign content, so that an end tag we serialize later closes a
+// foreign element around it. In the HTML content of an integration point the
+// parser pops, ignores or ends elements early on its own, and then matches
+// their end tags against the foreign elements around the integration point,
+// which `foreignNames` counts by name. It also pops past an integration point
+// of a foster parented svg or math element at a table start tag. At a
+// breakout tag inside an svg or math element nested in an integration point,
+// it pops that element back to it.
+function misparses(element, contexts, name, foreignNames) {
+ if (contexts & IN_INTEGRATION_POINT_CONTENT && contexts & HTML_RULES) {
+ if (foreignNames[name] > 0 && name !== 'svg' && name !== 'math')
+ return true;
+ if (name === 'table' && contexts & IN_TABLE_CONTENT &&
+ contexts & IN_FOSTER_PARENTED_FOREIGN_CONTENT)
+ return true;
+ }
+ // A void element has no end tag, but in foreign content it is not void, so
+ // it stays open around what we serialize after it.
+ if (contexts & FOREIGN_CONTEXTS && element.namespaceURI === NAMESPACE.HTML &&
+ emptyElements[element._localName] === true &&
+ !breaksOutOfForeignContent(element, name))
+ return true;
+ return (contexts & IN_NESTED_FOREIGN_CONTENT) !== 0 &&
+ (contexts & FOREIGN_CONTEXTS) !== 0 &&
+ breaksOutOfForeignContent(element, name);
+}
+
+// The contexts in which `kid` is parsed as a child of `parent`. Counts the
+// names of the foreign elements around `kid` into `foreignNames`, if given.
+function childContextsOf(parent, kid, foreignNames) {
+ if (parent.nodeType === 0)
+ parent = kid.parentNode;
+
+ var ancestors = [];
+ var contexts = 0;
+ for (var node = parent; node;) {
+ if (node.nodeType === 1 /*ELEMENT_NODE*/) {
+ ancestors.push(node);
+ node = node.parentNode;
+ } else if (node.nodeType === 11 /*DOCUMENT_FRAGMENT_NODE*/ && node._host) {
+ node = node._host;
+ } else {
+ if (node.nodeType === 9 /*DOCUMENT_NODE*/) contexts = HTML_CONTEXT;
+ break;
+ }
+ }
+
+ // A detached subtree is parsed in whatever namespace its root ends up in.
+ var root = ancestors.length ? ancestors[ancestors.length - 1] : kid;
+ if (contexts === 0) {
+ contexts = root.namespaceURI === NAMESPACE.SVG ? SVG_CONTEXT :
+ root.namespaceURI === NAMESPACE.MATHML ? MATHML_CONTEXT : HTML_CONTEXT;
+ }
+ for (var i = ancestors.length - 1; i >= 0; i--) {
+ var name = lowerCaseTagName(ancestors[i]);
+ if (foreignNames !== undefined && contexts & FOREIGN_CONTEXTS)
+ foreignNames[name] = (foreignNames[name] || 0) + 1;
+ contexts = childContexts(ancestors[i], contexts, name);
+ }
+ if (contexts & FOREIGN_CONTEXTS) contexts |= IN_FOREIGN_CONTENT;
+ return contexts;
+}
+
+// Raw text of an HTML raw-text element inside foreign content is parsed as
+// normal text, so it has to be escaped when serialized.
+function isRawTextElement(kid) {
+ return kid.namespaceURI === NAMESPACE.HTML && !kid._innerHTML &&
+ hasRawContent[kid.localName.toUpperCase()] === true;
+}
+
function serializeChildren(parent, serialize) {
if (parent._innerHTML) return parent._innerHTML;
var kid = parent.firstChild;
@@ -300,6 +559,10 @@ function serializeChildren(parent, serialize) {
if (templateSerialize === undefined) {
templateSerialize = require('./htmlelts').elements.HTMLTemplateElement.prototype.serialize;
}
+ var foreignNames = Object.create(null);
+ var contexts = childContextsOf(parent, kid, foreignNames);
+ if (contexts !== HTML_CONTEXT)
+ return serializeForeignContent(parent, serialize, contexts, foreignNames);
var stack = [];
var s = '';
while (true) {
@@ -313,13 +576,98 @@ function serializeChildren(parent, serialize) {
}
var kidSerialize = kid.nodeType === 1 ? kid.serialize : undefined;
+ if (kid.nodeType === 1 &&
+ (kidSerialize === serialize ||
+ (kidSerialize === templateSerialize && kid.content.serialize === serialize))) {
+ var content = kidSerialize === templateSerialize ? kid.content : kid;
+ var tagName = content._innerHTML || content.firstChild === null ?
+ undefined : serializedTagName(kid);
+ // Outside of foreign content, only `svg` and `math` change how a parser
+ // reads the children.
+ if (tagName !== undefined && isSvgOrMathName(tagName)) {
+ s += serializeOne(kid, parent, serializeForeignContent(kid, serialize,
+ childContexts(kid, HTML_CONTEXT), Object.create(null)));
+ kid = kid.nextSibling;
+ continue;
+ }
+ if (tagName !== undefined &&
+ !(kid.namespaceURI === NAMESPACE.HTML && emptyElements[tagName])) {
+ stack.push({kid: kid, parent: parent, output: s});
+ s = '';
+ parent = content;
+ kid = content.firstChild;
+ continue;
+ }
+ s += serializeOne(kid, parent, content._innerHTML || '');
+ }
+ else {
+ s += serializeOne(kid, parent);
+ }
+ kid = kid.nextSibling;
+ }
+}
+
+// Serializes the children of `parent` like `serializeChildren()`, when a
+// parser re-reading our output parses them in `contexts`, inside foreign
+// content. The contexts are kept up to date top-down, so that raw text needs
+// no ancestor walk of its own, and `foreignNames` counts the foreign elements
+// open around the children by name.
+function serializeForeignContent(parent, serialize, contexts, foreignNames) {
+ var kid = parent.firstChild;
+ var stack = [];
+ var s = '';
+ // Whether the text children of `parent` are escaped as foreign raw text.
+ var escapeText = false;
+ // Set once the parser may stop following the tree, for the rest of it.
+ var misparsed = false;
+ // The outermost element around the children of `parent` that a parser may
+ // read as `textEndedByOwnEndTag` text.
+ var rawTextName;
+ while (true) {
+ if (kid === null) {
+ if (stack.length === 0) return s;
+ var frame = stack.pop();
+ s = frame.output +
+ serializeOne(frame.kid, frame.parent, s, frame.foreign);
+ if (frame.foreignName !== undefined) foreignNames[frame.foreignName]--;
+ contexts = frame.contexts;
+ escapeText = frame.escapeText;
+ rawTextName = frame.rawTextName;
+ parent = frame.parent;
+ kid = frame.kid.nextSibling;
+ continue;
+ }
+
+ var kidSerialize = undefined;
+ var kidName = undefined;
+ if (kid.nodeType === 1) {
+ kidSerialize = kid.serialize;
+ kidName = lowerCaseTagName(kid);
+ // A same-named element ends that text early, which makes the end tags
+ // of what follows in it stray.
+ if (!misparsed) {
+ misparsed = kidName === rawTextName ||
+ misparses(kid, contexts, kidName, foreignNames);
+ }
+ }
if (kid.nodeType === 1 &&
(kidSerialize === serialize ||
(kidSerialize === templateSerialize && kid.content.serialize === serialize))) {
var content = kidSerialize === templateSerialize ? kid.content : kid;
if (!content._innerHTML && content.firstChild !== null &&
!(kid.namespaceURI === NAMESPACE.HTML && emptyElements[serializedTagName(kid)])) {
- stack.push({kid: kid, parent: parent, output: s});
+ var foreignName = contexts & FOREIGN_CONTEXTS ? kidName : undefined;
+ var foreign = misparsed || (contexts & FOREIGN_CONTEXTS) !== 0;
+ stack.push({kid: kid, parent: parent, output: s, contexts: contexts,
+ escapeText: escapeText, foreignName: foreignName,
+ rawTextName: rawTextName, foreign: foreign});
+ if (foreignName !== undefined)
+ foreignNames[foreignName] = (foreignNames[foreignName] || 0) + 1;
+ if (rawTextName === undefined && contexts & HTML_RULES &&
+ textEndedByOwnEndTag[kidName] === true)
+ rawTextName = kidName;
+ escapeText = content === kid && foreign && isRawTextElement(kid);
+ contexts = childContexts(kid, contexts, kidName);
s = '';
parent = content;
kid = content.firstChild;
@@ -327,6 +675,10 @@ function serializeChildren(parent, serialize) {
}
s += serializeOne(kid, parent, content._innerHTML || '');
}
+ else if (escapeText && (kid.nodeType === 3 /*TEXT_NODE*/ ||
+ kid.nodeType === 4 /*CDATA_SECTION_NODE*/)) {
+ s += escape(kid.data);
+ }
else {
s += serializeOne(kid, parent);
}
@@ -334,7 +686,10 @@ function serializeChildren(parent, serialize) {
}
}
-function serializeOne(kid, parent, children) {
+// `children` and `foreign` are passed by the serialization loops, which have
+// already serialized (and, for foreign raw text, escaped) the children and
+// know whether a parser may read `kid` as foreign content.
+function serializeOne(kid, parent, children, foreign) {
var s = '';
switch(kid.nodeType) {
case 1: //ELEMENT_NODE
@@ -352,16 +707,26 @@ function serializeOne(kid, parent, children) {
s += '>';
if (!(html && emptyElements[tagname])) {
- var ss = children === undefined ? kid.serialize() : children;
+ var upperTag = tagname.toUpperCase();
+ var ss = children;
+ if (ss === undefined) {
+ foreign = hasRawContent[upperTag] === true && !kid._innerHTML &&
+ (childContextsOf(parent, kid) & FOREIGN_CONTEXTS) !== 0;
+ ss = foreign && html ? serializeForeignRawText(kid) : kid.serialize();
+ }
// If an element can have raw content, this content may
// potentially require escaping to avoid XSS.
- var upperTag = tagname.toUpperCase();
if (hasRawContent[upperTag] && !hasRawContentFallback[upperTag] && ss.includes('')) {
- ss = escapeMatchingClosingTag(ss, tagname);
+ var escaped = escapeMatchingClosingTag(ss, tagname);
const fallbackTags = fallbackRawContentTags(parent);
for (const fallbackTag of fallbackTags) {
- ss = escapeMatchingClosingTag(ss, fallbackTag);
+ escaped = escapeMatchingClosingTag(escaped, fallbackTag);
}
+ // Read as foreign content, the end tags that this escapes are real
+ // ones of nested elements; read as raw text, they would end it. Its
+ // text is escaped already, so escape all of its markup instead.
+ ss = foreign === true && escaped !== ss ?
+ ss.replace(/*/';
+ const document = domino.createDocument('');
+ const svg = document.createElementNS(SVG, 'svg');
+ const group = document.createElementNS(SVG, 'g');
+ const wrapper = document.createElement('g');
+ const style = document.createElement('style');
+ const marker = document.createElement('span');
+ style.textContent = payload;
+ marker.textContent = 'kept';
+ style.appendChild(marker);
+ wrapper.appendChild(style);
+ svg.appendChild(group);
+ document.body.appendChild(svg);
+ document.body.appendChild(wrapper);
+ document.serialize().should.containEql('';
+ document.body.serialize().should.containEql(serialized);
+ style.outerHTML.should.equal(serialized);
+ return document;
+}
+
+exports.rawContentIsEscapedOnlyWhereParsedAsForeignContent = function () {
+ const escaped = [
+ [[SVG, 'svg']],
+ [[SVG, 'svg'], [HTML_ELEMENT, 'g']],
+ [[SVG, 'svg'], [SVG, 'foreignObject'], [HTML_ELEMENT, 'svg']],
+ [[SVG, 'svg'], [HTML_ELEMENT, 'div'], [SVG, 'svg']],
+ [[MATHML, 'math'], [HTML_ELEMENT, 'mtext'], [HTML_ELEMENT, 'mglyph']],
+ [[MATHML, 'math'],
+ [MATHML, 'annotation-xml', { encoding: 'application/xml' }]],
+ // A parser only reads an integration point in its own namespace.
+ [[MATHML, 'math'], [SVG, 'foreignObject']],
+ [[MATHML, 'math'], [HTML_ELEMENT, 'title']],
+ [[SVG, 'svg'], [MATHML, 'mtext']],
+ [[SVG, 'svg'],
+ [HTML_ELEMENT, 'annotation-xml', { encoding: 'text/html' }]],
+ // Template contents are serialized in place.
+ [[SVG, 'svg'], [HTML_ELEMENT, 'template']],
+ [[MATHML, 'math'], [HTML_ELEMENT, 'template']],
+ ];
+ const raw = [
+ [[SVG, 'svg'], [SVG, 'foreignObject'], [HTML_ELEMENT, 'div']],
+ [[SVG, 'svg'], [SVG, 'foreignObject'], [SVG, 'a']],
+ [[MATHML, 'math'], [HTML_ELEMENT, 'mtext']],
+ [[MATHML, 'math'],
+ [MATHML, 'annotation-xml', { encoding: 'text/html' }]],
+ // `div` pops a parser out of foreign content.
+ [[MATHML, 'math'],
+ [MATHML, 'annotation-xml', { encoding: 'application/xml' }],
+ [HTML_ELEMENT, 'div']],
+ ];
+ for (const ancestors of escaped) {
+ assertStyleSerialization(ancestors, serializedStyleCss);
+ }
+ for (const ancestors of raw) {
+ assertStyleSerialization(ancestors, styleCss);
+ }
+};
+
+exports.rawContentIsEscapedOnceAParserMayLeaveTheTree = async function () {
+ const icon = el(SVG, 'svg', [el(SVG, 'g', [el(HTML_ELEMENT, 'span')])]);
+ const serialized = '';
+ const trees = [
+ // The pops a parser out of the icon back to the ,
+ // and the stray of the icon then closes the