NeverDestroyed<String>(ASCIILiteral(...)) is not thread safe.
[WebKit-https.git] / Source / WebCore / editing / MarkupAccumulator.cpp
1 /*
2  * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "MarkupAccumulator.h"
29
30 #include "CDATASection.h"
31 #include "Comment.h"
32 #include "DocumentFragment.h"
33 #include "DocumentType.h"
34 #include "Editor.h"
35 #include "HTMLElement.h"
36 #include "HTMLNames.h"
37 #include "HTMLTemplateElement.h"
38 #include "URL.h"
39 #include "ProcessingInstruction.h"
40 #include "XLinkNames.h"
41 #include "XMLNSNames.h"
42 #include "XMLNames.h"
43 #include <wtf/NeverDestroyed.h>
44 #include <wtf/unicode/CharacterNames.h>
45
46 namespace WebCore {
47
48 using namespace HTMLNames;
49
50 struct EntityDescription {
51     const char* characters;
52     unsigned char length;
53     unsigned char mask;
54 };
55
56 static const EntityDescription entitySubstitutionList[] = {
57     { "", 0 , 0 },
58     { "&amp;", 5 , EntityAmp },
59     { "&lt;", 4, EntityLt },
60     { "&gt;", 4, EntityGt },
61     { "&quot;", 6, EntityQuot },
62     { "&nbsp;", 6, EntityNbsp },
63 };
64
65 enum EntitySubstitutionIndex {
66     EntitySubstitutionNullIndex = 0,
67     EntitySubstitutionAmpIndex = 1,
68     EntitySubstitutionLtIndex = 2,
69     EntitySubstitutionGtIndex = 3,
70     EntitySubstitutionQuotIndex = 4,
71     EntitySubstitutionNbspIndex = 5,
72 };
73
74 static const unsigned maximumEscapedentityCharacter = noBreakSpace;
75 static const uint8_t entityMap[maximumEscapedentityCharacter + 1] = {
76     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
77     EntitySubstitutionQuotIndex, // '"'.
78     0, 0, 0,
79     EntitySubstitutionAmpIndex, // '&'.
80     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
81     EntitySubstitutionLtIndex, // '<'.
82     0,
83     EntitySubstitutionGtIndex, // '>'.
84     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
85     EntitySubstitutionNbspIndex // noBreakSpace.
86 };
87
88 template<typename CharacterType>
89 static inline void appendCharactersReplacingEntitiesInternal(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask)
90 {
91     const CharacterType* text = source.characters<CharacterType>() + offset;
92
93     size_t positionAfterLastEntity = 0;
94     for (size_t i = 0; i < length; ++i) {
95         CharacterType character = text[i];
96         uint8_t substitution = character < WTF_ARRAY_LENGTH(entityMap) ? entityMap[character] : static_cast<uint8_t>(EntitySubstitutionNullIndex);
97         if (UNLIKELY(substitution != EntitySubstitutionNullIndex) && entitySubstitutionList[substitution].mask & entityMask) {
98             result.append(text + positionAfterLastEntity, i - positionAfterLastEntity);
99             result.append(entitySubstitutionList[substitution].characters, entitySubstitutionList[substitution].length);
100             positionAfterLastEntity = i + 1;
101         }
102     }
103     result.append(text + positionAfterLastEntity, length - positionAfterLastEntity);
104 }
105
106 void MarkupAccumulator::appendCharactersReplacingEntities(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask)
107 {
108     if (!(offset + length))
109         return;
110
111     ASSERT(offset + length <= source.length());
112
113     if (source.is8Bit())
114         appendCharactersReplacingEntitiesInternal<LChar>(result, source, offset, length, entityMask);
115     else
116         appendCharactersReplacingEntitiesInternal<UChar>(result, source, offset, length, entityMask);
117 }
118
119 MarkupAccumulator::MarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs resolveUrlsMethod, const Range* range, EFragmentSerialization fragmentSerialization)
120     : m_nodes(nodes)
121     , m_range(range)
122     , m_resolveURLsMethod(resolveUrlsMethod)
123     , m_fragmentSerialization(fragmentSerialization)
124     , m_prefixLevel(0)
125 {
126 }
127
128 MarkupAccumulator::~MarkupAccumulator()
129 {
130 }
131
132 String MarkupAccumulator::serializeNodes(Node& targetNode, EChildrenOnly childrenOnly, Vector<QualifiedName>* tagNamesToSkip)
133 {
134     serializeNodesWithNamespaces(targetNode, childrenOnly, 0, tagNamesToSkip);
135     return m_markup.toString();
136 }
137
138 void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, EChildrenOnly childrenOnly, const Namespaces* namespaces, Vector<QualifiedName>* tagNamesToSkip)
139 {
140     if (tagNamesToSkip && is<Element>(targetNode)) {
141         for (auto& name : *tagNamesToSkip) {
142             if (downcast<Element>(targetNode).hasTagName(name))
143                 return;
144         }
145     }
146
147     Namespaces namespaceHash;
148     if (namespaces)
149         namespaceHash = *namespaces;
150     else if (inXMLFragmentSerialization()) {
151         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
152         namespaceHash.set(xmlAtom.impl(), XMLNames::xmlNamespaceURI.impl());
153         namespaceHash.set(XMLNames::xmlNamespaceURI.impl(), xmlAtom.impl());
154     }
155
156     if (!childrenOnly)
157         appendStartTag(targetNode, &namespaceHash);
158
159     if (targetNode.document().isHTMLDocument() && elementCannotHaveEndTag(targetNode))
160         return;
161
162     Node* current = targetNode.hasTagName(templateTag) ? downcast<HTMLTemplateElement>(targetNode).content().firstChild() : targetNode.firstChild();
163     for ( ; current; current = current->nextSibling())
164         serializeNodesWithNamespaces(*current, IncludeNode, &namespaceHash, tagNamesToSkip);
165
166     if (!childrenOnly)
167         appendEndTag(targetNode);
168 }
169
170 String MarkupAccumulator::resolveURLIfNeeded(const Element& element, const String& urlString) const
171 {
172     switch (m_resolveURLsMethod) {
173     case ResolveAllURLs:
174         return element.document().completeURL(urlString).string();
175
176     case ResolveNonLocalURLs:
177         if (!element.document().url().isLocalFile())
178             return element.document().completeURL(urlString).string();
179         break;
180
181     case DoNotResolveURLs:
182         break;
183     }
184     return urlString;
185 }
186
187 void MarkupAccumulator::appendString(const String& string)
188 {
189     m_markup.append(string);
190 }
191
192 void MarkupAccumulator::appendStartTag(const Node& node, Namespaces* namespaces)
193 {
194     appendStartMarkup(m_markup, node, namespaces);
195     if (m_nodes)
196         m_nodes->append(const_cast<Node*>(&node));
197 }
198
199 void MarkupAccumulator::appendEndTag(const Element& element)
200 {
201     appendEndMarkup(m_markup, element);
202 }
203
204 size_t MarkupAccumulator::totalLength(const Vector<String>& strings)
205 {
206     size_t length = 0;
207     for (auto& string : strings)
208         length += string.length();
209     return length;
210 }
211
212 void MarkupAccumulator::concatenateMarkup(StringBuilder& result)
213 {
214     result.append(m_markup);
215 }
216
217 void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool isSerializingHTML)
218 {
219     appendCharactersReplacingEntities(result, attribute, 0, attribute.length(),
220         isSerializingHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue);
221 }
222
223 void MarkupAccumulator::appendCustomAttributes(StringBuilder&, const Element&, Namespaces*)
224 {
225 }
226
227 void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, const Element& element, const Attribute& attribute)
228 {
229     ASSERT(element.isURLAttribute(attribute));
230     const String resolvedURLString = resolveURLIfNeeded(element, attribute.value());
231     UChar quoteChar = '"';
232     String strippedURLString = resolvedURLString.stripWhiteSpace();
233     if (protocolIsJavaScript(strippedURLString)) {
234         // minimal escaping for javascript urls
235         if (strippedURLString.contains('"')) {
236             if (strippedURLString.contains('\''))
237                 strippedURLString.replaceWithLiteral('"', "&quot;");
238             else
239                 quoteChar = '\'';
240         }
241         result.append(quoteChar);
242         result.append(strippedURLString);
243         result.append(quoteChar);
244         return;
245     }
246
247     // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
248     result.append(quoteChar);
249     appendAttributeValue(result, resolvedURLString, false);
250     result.append(quoteChar);
251 }
252
253 bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element)
254 {
255     // Don't add namespace attribute if it is already defined for this elem.
256     const AtomicString& prefix = element.prefix();
257     if (prefix.isEmpty())
258         return !element.hasAttribute(xmlnsAtom);
259
260     static NeverDestroyed<String> xmlnsWithColon(MAKE_STATIC_STRING_IMPL("xmlns:"));
261     return !element.hasAttribute(xmlnsWithColon.get() + prefix);
262 }
263
264 bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces)
265 {
266     namespaces.checkConsistency();
267
268     // Don't add namespace attributes twice
269     // HTML Parser will create xmlns attributes without namespace for HTML elements, allow those as well.
270     if (attribute.name().localName() == xmlnsAtom && (attribute.namespaceURI().isEmpty() || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI)) {
271         namespaces.set(emptyAtom.impl(), attribute.value().impl());
272         return false;
273     }
274
275     QualifiedName xmlnsPrefixAttr(xmlnsAtom, attribute.localName(), XMLNSNames::xmlnsNamespaceURI);
276     if (attribute.name() == xmlnsPrefixAttr) {
277         namespaces.set(attribute.localName().impl(), attribute.value().impl());
278         namespaces.set(attribute.value().impl(), attribute.localName().impl());
279         return false;
280     }
281
282     return true;
283 }
284
285 void MarkupAccumulator::appendNamespace(StringBuilder& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces& namespaces, bool allowEmptyDefaultNS)
286 {
287     namespaces.checkConsistency();
288     if (namespaceURI.isEmpty()) {
289         // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-xhtml-syntax.html#xml-fragment-serialization-algorithm
290         if (allowEmptyDefaultNS && namespaces.get(emptyAtom.impl())) {
291             result.append(' ');
292             result.append(xmlnsAtom.string());
293             result.appendLiteral("=\"\"");
294         }
295         return;
296     }
297
298     // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key
299     AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl();
300     AtomicStringImpl* foundNS = namespaces.get(pre);
301     if (foundNS != namespaceURI.impl()) {
302         namespaces.set(pre, namespaceURI.impl());
303         // Add namespace to prefix pair so we can do constraint checking later.
304         if (inXMLFragmentSerialization() && !prefix.isEmpty())
305             namespaces.set(namespaceURI.impl(), pre);
306         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
307         if (namespaceURI.impl() == XMLNames::xmlNamespaceURI.impl())
308             return;
309         result.append(' ');
310         result.append(xmlnsAtom.string());
311         if (!prefix.isEmpty()) {
312             result.append(':');
313             result.append(prefix);
314         }
315
316         result.append('=');
317         result.append('"');
318         appendAttributeValue(result, namespaceURI, false);
319         result.append('"');
320     }
321 }
322
323 EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const
324 {
325     if (!text.document().isHTMLDocument())
326         return EntityMaskInPCDATA;
327
328     const QualifiedName* parentName = nullptr;
329     if (text.parentElement())
330         parentName = &text.parentElement()->tagQName();
331
332     if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag))
333         return EntityMaskInCDATA;
334     return EntityMaskInHTMLPCDATA;
335 }
336
337 void MarkupAccumulator::appendText(StringBuilder& result, const Text& text)
338 {
339     const String& textData = text.data();
340     unsigned start = 0;
341     unsigned length = textData.length();
342
343     if (m_range) {
344         if (&text == &m_range->endContainer())
345             length = m_range->endOffset();
346         if (&text == &m_range->startContainer()) {
347             start = m_range->startOffset();
348             length -= start;
349         }
350     }
351
352     appendCharactersReplacingEntities(result, textData, start, length, entityMaskForText(text));
353 }
354
355 static void appendComment(StringBuilder& result, const String& comment)
356 {
357     // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
358     result.appendLiteral("<!--");
359     result.append(comment);
360     result.appendLiteral("-->");
361 }
362
363 void MarkupAccumulator::appendXMLDeclaration(StringBuilder& result, const Document& document)
364 {
365     if (!document.hasXMLDeclaration())
366         return;
367
368     result.appendLiteral("<?xml version=\"");
369     result.append(document.xmlVersion());
370     const String& encoding = document.xmlEncoding();
371     if (!encoding.isEmpty()) {
372         result.appendLiteral("\" encoding=\"");
373         result.append(encoding);
374     }
375     if (document.xmlStandaloneStatus() != Document::StandaloneUnspecified) {
376         result.appendLiteral("\" standalone=\"");
377         if (document.xmlStandalone())
378             result.appendLiteral("yes");
379         else
380             result.appendLiteral("no");
381     }
382
383     result.appendLiteral("\"?>");
384 }
385
386 void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType& documentType)
387 {
388     if (documentType.name().isEmpty())
389         return;
390
391     result.appendLiteral("<!DOCTYPE ");
392     result.append(documentType.name());
393     if (!documentType.publicId().isEmpty()) {
394         result.appendLiteral(" PUBLIC \"");
395         result.append(documentType.publicId());
396         result.append('"');
397         if (!documentType.systemId().isEmpty()) {
398             result.append(' ');
399             result.append('"');
400             result.append(documentType.systemId());
401             result.append('"');
402         }
403     } else if (!documentType.systemId().isEmpty()) {
404         result.appendLiteral(" SYSTEM \"");
405         result.append(documentType.systemId());
406         result.append('"');
407     }
408     result.append('>');
409 }
410
411 void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data)
412 {
413     // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
414     result.append('<');
415     result.append('?');
416     result.append(target);
417     result.append(' ');
418     result.append(data);
419     result.append('?');
420     result.append('>');
421 }
422
423 void MarkupAccumulator::appendElement(StringBuilder& result, const Element& element, Namespaces* namespaces)
424 {
425     appendOpenTag(result, element, namespaces);
426
427     if (element.hasAttributes()) {
428         for (const Attribute& attribute : element.attributesIterator())
429             appendAttribute(result, element, attribute, namespaces);
430     }
431
432     // Give an opportunity to subclasses to add their own attributes.
433     appendCustomAttributes(result, element, namespaces);
434
435     appendCloseTag(result, element);
436 }
437
438 void MarkupAccumulator::appendOpenTag(StringBuilder& result, const Element& element, Namespaces* namespaces)
439 {
440     result.append('<');
441     if (inXMLFragmentSerialization() && namespaces && element.prefix().isEmpty()) {
442         // According to http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo we now should create
443         // a default namespace declaration to make this namespace well-formed. However, http://www.w3.org/TR/xml-names11/#xmlReserved states
444         // "The prefix xml MUST NOT be declared as the default namespace.", so use the xml prefix explicitly.
445         if (element.namespaceURI() == XMLNames::xmlNamespaceURI) {
446             result.append(xmlAtom);
447             result.append(':');
448         }
449     }
450     result.append(element.nodeNamePreservingCase());
451     if ((inXMLFragmentSerialization() || !element.document().isHTMLDocument()) && namespaces && shouldAddNamespaceElement(element))
452         appendNamespace(result, element.prefix(), element.namespaceURI(), *namespaces, inXMLFragmentSerialization());
453 }
454
455 void MarkupAccumulator::appendCloseTag(StringBuilder& result, const Element& element)
456 {
457     if (shouldSelfClose(element)) {
458         if (element.isHTMLElement())
459             result.append(' '); // XHTML 1.0 <-> HTML compatibility.
460         result.append('/');
461     }
462     result.append('>');
463 }
464
465 static inline bool attributeIsInSerializedNamespace(const Attribute& attribute)
466 {
467     return attribute.namespaceURI() == XMLNames::xmlNamespaceURI
468         || attribute.namespaceURI() == XLinkNames::xlinkNamespaceURI
469         || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI;
470 }
471
472 void MarkupAccumulator::generateUniquePrefix(QualifiedName& prefixedName, const Namespaces& namespaces)
473 {
474     // http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo
475     // Find a prefix following the pattern "NS" + index (starting at 1) and make sure this
476     // prefix is not declared in the current scope.
477     StringBuilder builder;
478     do {
479         builder.clear();
480         builder.appendLiteral("NS");
481         builder.appendNumber(++m_prefixLevel);
482         const AtomicString& name = builder.toAtomicString();
483         if (!namespaces.get(name.impl())) {
484             prefixedName.setPrefix(name);
485             return;
486         }
487     } while (true);
488 }
489
490 void MarkupAccumulator::appendAttribute(StringBuilder& result, const Element& element, const Attribute& attribute, Namespaces* namespaces)
491 {
492     bool isSerializingHTML = element.document().isHTMLDocument() && !inXMLFragmentSerialization();
493
494     result.append(' ');
495
496     QualifiedName prefixedName = attribute.name();
497     if (isSerializingHTML && !attributeIsInSerializedNamespace(attribute))
498         result.append(attribute.name().localName());
499     else {
500         if (!attribute.namespaceURI().isEmpty()) {
501             if (attribute.namespaceURI() == XMLNames::xmlNamespaceURI) {
502                 // Always use xml as prefix if the namespace is the XML namespace.
503                 prefixedName.setPrefix(xmlAtom);
504             } else {
505                 AtomicStringImpl* foundNS = namespaces && attribute.prefix().impl() ? namespaces->get(attribute.prefix().impl()) : 0;
506                 bool prefixIsAlreadyMappedToOtherNS = foundNS && foundNS != attribute.namespaceURI().impl();
507                 if (attribute.prefix().isEmpty() || !foundNS || prefixIsAlreadyMappedToOtherNS) {
508                     if (AtomicStringImpl* prefix = namespaces ? namespaces->get(attribute.namespaceURI().impl()) : 0)
509                         prefixedName.setPrefix(AtomicString(prefix));
510                     else {
511                         bool shouldBeDeclaredUsingAppendNamespace = !attribute.prefix().isEmpty() && !foundNS;
512                         if (!shouldBeDeclaredUsingAppendNamespace && attribute.localName() != xmlnsAtom && namespaces)
513                             generateUniquePrefix(prefixedName, *namespaces);
514                     }
515                 }
516             }
517         }
518         result.append(prefixedName.toString());
519     }
520
521     result.append('=');
522
523     if (element.isURLAttribute(attribute))
524         appendQuotedURLAttributeValue(result, element, attribute);
525     else {
526         result.append('"');
527         appendAttributeValue(result, attribute.value(), isSerializingHTML);
528         result.append('"');
529     }
530
531     if (!isSerializingHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces))
532         appendNamespace(result, prefixedName.prefix(), prefixedName.namespaceURI(), *namespaces);
533 }
534
535 void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section)
536 {
537     // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
538     result.appendLiteral("<![CDATA[");
539     result.append(section);
540     result.appendLiteral("]]>");
541 }
542
543 void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node& node, Namespaces* namespaces)
544 {
545     if (namespaces)
546         namespaces->checkConsistency();
547
548     switch (node.nodeType()) {
549     case Node::TEXT_NODE:
550         appendText(result, downcast<Text>(node));
551         break;
552     case Node::COMMENT_NODE:
553         appendComment(result, downcast<Comment>(node).data());
554         break;
555     case Node::DOCUMENT_NODE:
556         appendXMLDeclaration(result, downcast<Document>(node));
557         break;
558     case Node::DOCUMENT_FRAGMENT_NODE:
559         break;
560     case Node::DOCUMENT_TYPE_NODE:
561         appendDocumentType(result, downcast<DocumentType>(node));
562         break;
563     case Node::PROCESSING_INSTRUCTION_NODE:
564         appendProcessingInstruction(result, downcast<ProcessingInstruction>(node).target(), downcast<ProcessingInstruction>(node).data());
565         break;
566     case Node::ELEMENT_NODE:
567         appendElement(result, downcast<Element>(node), namespaces);
568         break;
569     case Node::CDATA_SECTION_NODE:
570         appendCDATASection(result, downcast<CDATASection>(node).data());
571         break;
572     case Node::ATTRIBUTE_NODE:
573         ASSERT_NOT_REACHED();
574         break;
575     }
576 }
577
578 // Rules of self-closure
579 // 1. No elements in HTML documents use the self-closing syntax.
580 // 2. Elements w/ children never self-close because they use a separate end tag.
581 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
582 // 4. Other elements self-close.
583 bool MarkupAccumulator::shouldSelfClose(const Element& element)
584 {
585     if (!inXMLFragmentSerialization() && element.document().isHTMLDocument())
586         return false;
587     if (element.hasChildNodes())
588         return false;
589     if (element.isHTMLElement() && !elementCannotHaveEndTag(element))
590         return false;
591     return true;
592 }
593
594 bool MarkupAccumulator::elementCannotHaveEndTag(const Node& node)
595 {
596     if (!is<HTMLElement>(node))
597         return false;
598
599     // From https://html.spec.whatwg.org/#serialising-html-fragments:
600     // If current node is an area, base, basefont, bgsound, br, col, embed, frame, hr, img,
601     // input, keygen, link, meta, param, source, track or wbr element, then continue on to
602     // the next child node at this point.
603     static const HTMLQualifiedName* tags[] = { &areaTag, &baseTag, &basefontTag, &bgsoundTag, &brTag, &colTag, &embedTag,
604         &frameTag, &hrTag, &imgTag, &inputTag, &keygenTag, &linkTag, &metaTag, &paramTag, &sourceTag, &trackTag, &wbrTag };
605     auto& element = downcast<HTMLElement>(node);
606     for (auto* tag : tags) {
607         if (element.hasTagName(*tag))
608             return true;
609     }
610     return false;
611 }
612
613 void MarkupAccumulator::appendEndMarkup(StringBuilder& result, const Element& element)
614 {
615     if (shouldSelfClose(element) || (!element.hasChildNodes() && elementCannotHaveEndTag(element)))
616         return;
617
618     result.append('<');
619     result.append('/');
620     result.append(element.nodeNamePreservingCase());
621     result.append('>');
622 }
623
624 }