Use "= default" to denote default constructor or destructor
[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() = default;
129
130 String MarkupAccumulator::serializeNodes(Node& targetNode, EChildrenOnly childrenOnly, Vector<QualifiedName>* tagNamesToSkip)
131 {
132     serializeNodesWithNamespaces(targetNode, childrenOnly, 0, tagNamesToSkip);
133     return m_markup.toString();
134 }
135
136 void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, EChildrenOnly childrenOnly, const Namespaces* namespaces, Vector<QualifiedName>* tagNamesToSkip)
137 {
138     if (tagNamesToSkip && is<Element>(targetNode)) {
139         for (auto& name : *tagNamesToSkip) {
140             if (downcast<Element>(targetNode).hasTagName(name))
141                 return;
142         }
143     }
144
145     Namespaces namespaceHash;
146     if (namespaces)
147         namespaceHash = *namespaces;
148     else if (inXMLFragmentSerialization()) {
149         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
150         namespaceHash.set(xmlAtom().impl(), XMLNames::xmlNamespaceURI.impl());
151         namespaceHash.set(XMLNames::xmlNamespaceURI.impl(), xmlAtom().impl());
152     }
153
154     if (!childrenOnly)
155         appendStartTag(targetNode, &namespaceHash);
156
157     if (targetNode.document().isHTMLDocument() && elementCannotHaveEndTag(targetNode))
158         return;
159
160     Node* current = targetNode.hasTagName(templateTag) ? downcast<HTMLTemplateElement>(targetNode).content().firstChild() : targetNode.firstChild();
161     for ( ; current; current = current->nextSibling())
162         serializeNodesWithNamespaces(*current, IncludeNode, &namespaceHash, tagNamesToSkip);
163
164     if (!childrenOnly)
165         appendEndTag(targetNode);
166 }
167
168 String MarkupAccumulator::resolveURLIfNeeded(const Element& element, const String& urlString) const
169 {
170     switch (m_resolveURLsMethod) {
171     case ResolveAllURLs:
172         return element.document().completeURL(urlString).string();
173
174     case ResolveNonLocalURLs:
175         if (!element.document().url().isLocalFile())
176             return element.document().completeURL(urlString).string();
177         break;
178
179     case DoNotResolveURLs:
180         break;
181     }
182     return urlString;
183 }
184
185 void MarkupAccumulator::appendString(const String& string)
186 {
187     m_markup.append(string);
188 }
189
190 void MarkupAccumulator::appendStartTag(const Node& node, Namespaces* namespaces)
191 {
192     appendStartMarkup(m_markup, node, namespaces);
193     if (m_nodes)
194         m_nodes->append(const_cast<Node*>(&node));
195 }
196
197 void MarkupAccumulator::appendEndTag(const Element& element)
198 {
199     appendEndMarkup(m_markup, element);
200 }
201
202 size_t MarkupAccumulator::totalLength(const Vector<String>& strings)
203 {
204     size_t length = 0;
205     for (auto& string : strings)
206         length += string.length();
207     return length;
208 }
209
210 void MarkupAccumulator::concatenateMarkup(StringBuilder& result)
211 {
212     result.append(m_markup);
213 }
214
215 void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool isSerializingHTML)
216 {
217     appendCharactersReplacingEntities(result, attribute, 0, attribute.length(),
218         isSerializingHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue);
219 }
220
221 void MarkupAccumulator::appendCustomAttributes(StringBuilder&, const Element&, Namespaces*)
222 {
223 }
224
225 void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, const Element& element, const Attribute& attribute)
226 {
227     ASSERT(element.isURLAttribute(attribute));
228     const String resolvedURLString = resolveURLIfNeeded(element, attribute.value());
229     UChar quoteChar = '"';
230     String strippedURLString = resolvedURLString.stripWhiteSpace();
231     if (protocolIsJavaScript(strippedURLString)) {
232         // minimal escaping for javascript urls
233         if (strippedURLString.contains('"')) {
234             if (strippedURLString.contains('\''))
235                 strippedURLString.replaceWithLiteral('"', "&quot;");
236             else
237                 quoteChar = '\'';
238         }
239         result.append(quoteChar);
240         result.append(strippedURLString);
241         result.append(quoteChar);
242         return;
243     }
244
245     // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
246     result.append(quoteChar);
247     appendAttributeValue(result, resolvedURLString, false);
248     result.append(quoteChar);
249 }
250
251 bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element)
252 {
253     // Don't add namespace attribute if it is already defined for this elem.
254     const AtomicString& prefix = element.prefix();
255     if (prefix.isEmpty())
256         return !element.hasAttribute(xmlnsAtom());
257
258     static NeverDestroyed<String> xmlnsWithColon(MAKE_STATIC_STRING_IMPL("xmlns:"));
259     return !element.hasAttribute(xmlnsWithColon.get() + prefix);
260 }
261
262 bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces)
263 {
264     namespaces.checkConsistency();
265
266     // Don't add namespace attributes twice
267     // HTML Parser will create xmlns attributes without namespace for HTML elements, allow those as well.
268     if (attribute.name().localName() == xmlnsAtom() && (attribute.namespaceURI().isEmpty() || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI)) {
269         namespaces.set(emptyAtom().impl(), attribute.value().impl());
270         return false;
271     }
272
273     QualifiedName xmlnsPrefixAttr(xmlnsAtom(), attribute.localName(), XMLNSNames::xmlnsNamespaceURI);
274     if (attribute.name() == xmlnsPrefixAttr) {
275         namespaces.set(attribute.localName().impl(), attribute.value().impl());
276         namespaces.set(attribute.value().impl(), attribute.localName().impl());
277         return false;
278     }
279
280     return true;
281 }
282
283 void MarkupAccumulator::appendNamespace(StringBuilder& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces& namespaces, bool allowEmptyDefaultNS)
284 {
285     namespaces.checkConsistency();
286     if (namespaceURI.isEmpty()) {
287         // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-xhtml-syntax.html#xml-fragment-serialization-algorithm
288         if (allowEmptyDefaultNS && namespaces.get(emptyAtom().impl())) {
289             result.append(' ');
290             result.append(xmlnsAtom().string());
291             result.appendLiteral("=\"\"");
292         }
293         return;
294     }
295
296     // Use emptyAtom()s's impl() for both null and empty strings since the HashMap can't handle 0 as a key
297     AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom().impl() : prefix.impl();
298     AtomicStringImpl* foundNS = namespaces.get(pre);
299     if (foundNS != namespaceURI.impl()) {
300         namespaces.set(pre, namespaceURI.impl());
301         // Add namespace to prefix pair so we can do constraint checking later.
302         if (inXMLFragmentSerialization() && !prefix.isEmpty())
303             namespaces.set(namespaceURI.impl(), pre);
304         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
305         if (namespaceURI.impl() == XMLNames::xmlNamespaceURI.impl())
306             return;
307         result.append(' ');
308         result.append(xmlnsAtom().string());
309         if (!prefix.isEmpty()) {
310             result.append(':');
311             result.append(prefix);
312         }
313
314         result.append('=');
315         result.append('"');
316         appendAttributeValue(result, namespaceURI, false);
317         result.append('"');
318     }
319 }
320
321 EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const
322 {
323     if (!text.document().isHTMLDocument())
324         return EntityMaskInPCDATA;
325
326     const QualifiedName* parentName = nullptr;
327     if (text.parentElement())
328         parentName = &text.parentElement()->tagQName();
329
330     if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag))
331         return EntityMaskInCDATA;
332     return EntityMaskInHTMLPCDATA;
333 }
334
335 void MarkupAccumulator::appendText(StringBuilder& result, const Text& text)
336 {
337     const String& textData = text.data();
338     unsigned start = 0;
339     unsigned length = textData.length();
340
341     if (m_range) {
342         if (&text == &m_range->endContainer())
343             length = m_range->endOffset();
344         if (&text == &m_range->startContainer()) {
345             start = m_range->startOffset();
346             length -= start;
347         }
348     }
349
350     appendCharactersReplacingEntities(result, textData, start, length, entityMaskForText(text));
351 }
352
353 static void appendComment(StringBuilder& result, const String& comment)
354 {
355     // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
356     result.appendLiteral("<!--");
357     result.append(comment);
358     result.appendLiteral("-->");
359 }
360
361 void MarkupAccumulator::appendXMLDeclaration(StringBuilder& result, const Document& document)
362 {
363     if (!document.hasXMLDeclaration())
364         return;
365
366     result.appendLiteral("<?xml version=\"");
367     result.append(document.xmlVersion());
368     const String& encoding = document.xmlEncoding();
369     if (!encoding.isEmpty()) {
370         result.appendLiteral("\" encoding=\"");
371         result.append(encoding);
372     }
373     if (document.xmlStandaloneStatus() != Document::StandaloneUnspecified) {
374         result.appendLiteral("\" standalone=\"");
375         if (document.xmlStandalone())
376             result.appendLiteral("yes");
377         else
378             result.appendLiteral("no");
379     }
380
381     result.appendLiteral("\"?>");
382 }
383
384 void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType& documentType)
385 {
386     if (documentType.name().isEmpty())
387         return;
388
389     result.appendLiteral("<!DOCTYPE ");
390     result.append(documentType.name());
391     if (!documentType.publicId().isEmpty()) {
392         result.appendLiteral(" PUBLIC \"");
393         result.append(documentType.publicId());
394         result.append('"');
395         if (!documentType.systemId().isEmpty()) {
396             result.append(' ');
397             result.append('"');
398             result.append(documentType.systemId());
399             result.append('"');
400         }
401     } else if (!documentType.systemId().isEmpty()) {
402         result.appendLiteral(" SYSTEM \"");
403         result.append(documentType.systemId());
404         result.append('"');
405     }
406     result.append('>');
407 }
408
409 void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data)
410 {
411     // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
412     result.append('<');
413     result.append('?');
414     result.append(target);
415     result.append(' ');
416     result.append(data);
417     result.append('?');
418     result.append('>');
419 }
420
421 void MarkupAccumulator::appendElement(StringBuilder& result, const Element& element, Namespaces* namespaces)
422 {
423     appendOpenTag(result, element, namespaces);
424
425     if (element.hasAttributes()) {
426         for (const Attribute& attribute : element.attributesIterator())
427             appendAttribute(result, element, attribute, namespaces);
428     }
429
430     // Give an opportunity to subclasses to add their own attributes.
431     appendCustomAttributes(result, element, namespaces);
432
433     appendCloseTag(result, element);
434 }
435
436 void MarkupAccumulator::appendOpenTag(StringBuilder& result, const Element& element, Namespaces* namespaces)
437 {
438     result.append('<');
439     if (inXMLFragmentSerialization() && namespaces && element.prefix().isEmpty()) {
440         // According to http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo we now should create
441         // a default namespace declaration to make this namespace well-formed. However, http://www.w3.org/TR/xml-names11/#xmlReserved states
442         // "The prefix xml MUST NOT be declared as the default namespace.", so use the xml prefix explicitly.
443         if (element.namespaceURI() == XMLNames::xmlNamespaceURI) {
444             result.append(xmlAtom());
445             result.append(':');
446         }
447     }
448     result.append(element.nodeNamePreservingCase());
449     if ((inXMLFragmentSerialization() || !element.document().isHTMLDocument()) && namespaces && shouldAddNamespaceElement(element))
450         appendNamespace(result, element.prefix(), element.namespaceURI(), *namespaces, inXMLFragmentSerialization());
451 }
452
453 void MarkupAccumulator::appendCloseTag(StringBuilder& result, const Element& element)
454 {
455     if (shouldSelfClose(element)) {
456         if (element.isHTMLElement())
457             result.append(' '); // XHTML 1.0 <-> HTML compatibility.
458         result.append('/');
459     }
460     result.append('>');
461 }
462
463 static inline bool attributeIsInSerializedNamespace(const Attribute& attribute)
464 {
465     return attribute.namespaceURI() == XMLNames::xmlNamespaceURI
466         || attribute.namespaceURI() == XLinkNames::xlinkNamespaceURI
467         || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI;
468 }
469
470 void MarkupAccumulator::generateUniquePrefix(QualifiedName& prefixedName, const Namespaces& namespaces)
471 {
472     // http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo
473     // Find a prefix following the pattern "NS" + index (starting at 1) and make sure this
474     // prefix is not declared in the current scope.
475     StringBuilder builder;
476     do {
477         builder.clear();
478         builder.appendLiteral("NS");
479         builder.appendNumber(++m_prefixLevel);
480         const AtomicString& name = builder.toAtomicString();
481         if (!namespaces.get(name.impl())) {
482             prefixedName.setPrefix(name);
483             return;
484         }
485     } while (true);
486 }
487
488 void MarkupAccumulator::appendAttribute(StringBuilder& result, const Element& element, const Attribute& attribute, Namespaces* namespaces)
489 {
490     bool isSerializingHTML = element.document().isHTMLDocument() && !inXMLFragmentSerialization();
491
492     result.append(' ');
493
494     QualifiedName prefixedName = attribute.name();
495     if (isSerializingHTML && !attributeIsInSerializedNamespace(attribute))
496         result.append(attribute.name().localName());
497     else {
498         if (!attribute.namespaceURI().isEmpty()) {
499             if (attribute.namespaceURI() == XMLNames::xmlNamespaceURI) {
500                 // Always use xml as prefix if the namespace is the XML namespace.
501                 prefixedName.setPrefix(xmlAtom());
502             } else {
503                 AtomicStringImpl* foundNS = namespaces && attribute.prefix().impl() ? namespaces->get(attribute.prefix().impl()) : 0;
504                 bool prefixIsAlreadyMappedToOtherNS = foundNS && foundNS != attribute.namespaceURI().impl();
505                 if (attribute.prefix().isEmpty() || !foundNS || prefixIsAlreadyMappedToOtherNS) {
506                     if (AtomicStringImpl* prefix = namespaces ? namespaces->get(attribute.namespaceURI().impl()) : 0)
507                         prefixedName.setPrefix(AtomicString(prefix));
508                     else {
509                         bool shouldBeDeclaredUsingAppendNamespace = !attribute.prefix().isEmpty() && !foundNS;
510                         if (!shouldBeDeclaredUsingAppendNamespace && attribute.localName() != xmlnsAtom() && namespaces)
511                             generateUniquePrefix(prefixedName, *namespaces);
512                     }
513                 }
514             }
515         }
516         result.append(prefixedName.toString());
517     }
518
519     result.append('=');
520
521     if (element.isURLAttribute(attribute))
522         appendQuotedURLAttributeValue(result, element, attribute);
523     else {
524         result.append('"');
525         appendAttributeValue(result, attribute.value(), isSerializingHTML);
526         result.append('"');
527     }
528
529     if (!isSerializingHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces))
530         appendNamespace(result, prefixedName.prefix(), prefixedName.namespaceURI(), *namespaces);
531 }
532
533 void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section)
534 {
535     // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
536     result.appendLiteral("<![CDATA[");
537     result.append(section);
538     result.appendLiteral("]]>");
539 }
540
541 void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node& node, Namespaces* namespaces)
542 {
543     if (namespaces)
544         namespaces->checkConsistency();
545
546     switch (node.nodeType()) {
547     case Node::TEXT_NODE:
548         appendText(result, downcast<Text>(node));
549         break;
550     case Node::COMMENT_NODE:
551         appendComment(result, downcast<Comment>(node).data());
552         break;
553     case Node::DOCUMENT_NODE:
554         appendXMLDeclaration(result, downcast<Document>(node));
555         break;
556     case Node::DOCUMENT_FRAGMENT_NODE:
557         break;
558     case Node::DOCUMENT_TYPE_NODE:
559         appendDocumentType(result, downcast<DocumentType>(node));
560         break;
561     case Node::PROCESSING_INSTRUCTION_NODE:
562         appendProcessingInstruction(result, downcast<ProcessingInstruction>(node).target(), downcast<ProcessingInstruction>(node).data());
563         break;
564     case Node::ELEMENT_NODE:
565         appendElement(result, downcast<Element>(node), namespaces);
566         break;
567     case Node::CDATA_SECTION_NODE:
568         appendCDATASection(result, downcast<CDATASection>(node).data());
569         break;
570     case Node::ATTRIBUTE_NODE:
571         ASSERT_NOT_REACHED();
572         break;
573     }
574 }
575
576 // Rules of self-closure
577 // 1. No elements in HTML documents use the self-closing syntax.
578 // 2. Elements w/ children never self-close because they use a separate end tag.
579 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
580 // 4. Other elements self-close.
581 bool MarkupAccumulator::shouldSelfClose(const Element& element)
582 {
583     if (!inXMLFragmentSerialization() && element.document().isHTMLDocument())
584         return false;
585     if (element.hasChildNodes())
586         return false;
587     if (element.isHTMLElement() && !elementCannotHaveEndTag(element))
588         return false;
589     return true;
590 }
591
592 bool MarkupAccumulator::elementCannotHaveEndTag(const Node& node)
593 {
594     if (!is<HTMLElement>(node))
595         return false;
596
597     // From https://html.spec.whatwg.org/#serialising-html-fragments:
598     // If current node is an area, base, basefont, bgsound, br, col, embed, frame, hr, img,
599     // input, keygen, link, meta, param, source, track or wbr element, then continue on to
600     // the next child node at this point.
601     static const HTMLQualifiedName* tags[] = { &areaTag, &baseTag, &basefontTag, &bgsoundTag, &brTag, &colTag, &embedTag,
602         &frameTag, &hrTag, &imgTag, &inputTag, &keygenTag, &linkTag, &metaTag, &paramTag, &sourceTag, &trackTag, &wbrTag };
603     auto& element = downcast<HTMLElement>(node);
604     for (auto* tag : tags) {
605         if (element.hasTagName(*tag))
606             return true;
607     }
608     return false;
609 }
610
611 void MarkupAccumulator::appendEndMarkup(StringBuilder& result, const Element& element)
612 {
613     if (shouldSelfClose(element) || (!element.hasChildNodes() && elementCannotHaveEndTag(element)))
614         return;
615
616     result.append('<');
617     result.append('/');
618     result.append(element.nodeNamePreservingCase());
619     result.append('>');
620 }
621
622 }