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