CTTE: Thread references through markup.h
[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/unicode/CharacterNames.h>
44
45 namespace WebCore {
46
47 using namespace HTMLNames;
48
49 void MarkupAccumulator::appendCharactersReplacingEntities(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask)
50 {
51     DEFINE_STATIC_LOCAL(const String, ampReference, (ASCIILiteral("&amp;")));
52     DEFINE_STATIC_LOCAL(const String, ltReference, (ASCIILiteral("&lt;")));
53     DEFINE_STATIC_LOCAL(const String, gtReference, (ASCIILiteral("&gt;")));
54     DEFINE_STATIC_LOCAL(const String, quotReference, (ASCIILiteral("&quot;")));
55     DEFINE_STATIC_LOCAL(const String, nbspReference, (ASCIILiteral("&nbsp;")));
56
57     static const EntityDescription entityMaps[] = {
58         { '&', ampReference, EntityAmp },
59         { '<', ltReference, EntityLt },
60         { '>', gtReference, EntityGt },
61         { '"', quotReference, EntityQuot },
62         { noBreakSpace, nbspReference, EntityNbsp },
63     };
64
65     if (!(offset + length))
66         return;
67
68     ASSERT(offset + length <= source.length());
69
70     if (source.is8Bit()) {
71         const LChar* text = source.characters8() + offset;
72
73         size_t positionAfterLastEntity = 0;
74         for (size_t i = 0; i < length; ++i) {
75             for (size_t entityIndex = 0; entityIndex < WTF_ARRAY_LENGTH(entityMaps); ++entityIndex) {
76                 if (text[i] == entityMaps[entityIndex].entity && entityMaps[entityIndex].mask & entityMask) {
77                     result.append(text + positionAfterLastEntity, i - positionAfterLastEntity);
78                     result.append(entityMaps[entityIndex].reference);
79                     positionAfterLastEntity = i + 1;
80                     break;
81                 }
82             }
83         }
84         result.append(text + positionAfterLastEntity, length - positionAfterLastEntity);
85     } else {
86         const UChar* text = source.characters16() + offset;
87
88         size_t positionAfterLastEntity = 0;
89         for (size_t i = 0; i < length; ++i) {
90             for (size_t entityIndex = 0; entityIndex < WTF_ARRAY_LENGTH(entityMaps); ++entityIndex) {
91                 if (text[i] == entityMaps[entityIndex].entity && entityMaps[entityIndex].mask & entityMask) {
92                     result.append(text + positionAfterLastEntity, i - positionAfterLastEntity);
93                     result.append(entityMaps[entityIndex].reference);
94                     positionAfterLastEntity = i + 1;
95                     break;
96                 }
97             }
98         }
99         result.append(text + positionAfterLastEntity, length - positionAfterLastEntity);
100     }
101 }
102
103 MarkupAccumulator::MarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs resolveUrlsMethod, const Range* range, EFragmentSerialization fragmentSerialization)
104     : m_nodes(nodes)
105     , m_range(range)
106     , m_resolveURLsMethod(resolveUrlsMethod)
107     , m_fragmentSerialization(fragmentSerialization)
108     , m_prefixLevel(0)
109 {
110 }
111
112 MarkupAccumulator::~MarkupAccumulator()
113 {
114 }
115
116 String MarkupAccumulator::serializeNodes(Node& targetNode, Node* nodeToSkip, EChildrenOnly childrenOnly, Vector<QualifiedName>* tagNamesToSkip)
117 {
118     serializeNodesWithNamespaces(targetNode, nodeToSkip, childrenOnly, 0, tagNamesToSkip);
119     return m_markup.toString();
120 }
121
122 void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, Node* nodeToSkip, EChildrenOnly childrenOnly, const Namespaces* namespaces, Vector<QualifiedName>* tagNamesToSkip)
123 {
124     if (&targetNode == nodeToSkip)
125         return;
126     
127     if (tagNamesToSkip && targetNode.isElementNode()) {
128         for (size_t i = 0; i < tagNamesToSkip->size(); ++i) {
129             if (targetNode.hasTagName(tagNamesToSkip->at(i)))
130                 return;
131         }
132     }
133
134     Namespaces namespaceHash;
135     if (namespaces)
136         namespaceHash = *namespaces;
137     else if (inXMLFragmentSerialization()) {
138         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
139         namespaceHash.set(xmlAtom.impl(), XMLNames::xmlNamespaceURI.impl());
140         namespaceHash.set(XMLNames::xmlNamespaceURI.impl(), xmlAtom.impl());
141     }
142
143     if (!childrenOnly)
144         appendStartTag(targetNode, &namespaceHash);
145
146     if (!(targetNode.document().isHTMLDocument() && elementCannotHaveEndTag(targetNode))) {
147 #if ENABLE(TEMPLATE_ELEMENT)
148         Node* current = targetNode.hasTagName(templateTag) ? toHTMLTemplateElement(targetNode).content()->firstChild() : targetNode.firstChild();
149 #else
150         Node* current = targetNode.firstChild();
151 #endif
152         for ( ; current; current = current->nextSibling())
153             serializeNodesWithNamespaces(*current, nodeToSkip, IncludeNode, &namespaceHash, tagNamesToSkip);
154     }
155
156     if (!childrenOnly)
157         appendEndTag(targetNode);
158 }
159
160 String MarkupAccumulator::resolveURLIfNeeded(const Element& element, const String& urlString) const
161 {
162     switch (m_resolveURLsMethod) {
163     case ResolveAllURLs:
164         return element.document().completeURL(urlString).string();
165
166     case ResolveNonLocalURLs:
167         if (!element.document().url().isLocalFile())
168             return element.document().completeURL(urlString).string();
169         break;
170
171     case DoNotResolveURLs:
172         break;
173     }
174     return urlString;
175 }
176
177 void MarkupAccumulator::appendString(const String& string)
178 {
179     m_markup.append(string);
180 }
181
182 void MarkupAccumulator::appendStartTag(const Node& node, Namespaces* namespaces)
183 {
184     appendStartMarkup(m_markup, node, namespaces);
185     if (m_nodes)
186         m_nodes->append(const_cast<Node*>(&node));
187 }
188
189 void MarkupAccumulator::appendEndTag(const Node& node)
190 {
191     appendEndMarkup(m_markup, node);
192 }
193
194 size_t MarkupAccumulator::totalLength(const Vector<String>& strings)
195 {
196     size_t length = 0;
197     for (size_t i = 0; i < strings.size(); ++i)
198         length += strings[i].length();
199     return length;
200 }
201
202 void MarkupAccumulator::concatenateMarkup(StringBuilder& result)
203 {
204     result.append(m_markup);
205 }
206
207 void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool documentIsHTML)
208 {
209     appendCharactersReplacingEntities(result, attribute, 0, attribute.length(),
210         documentIsHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue);
211 }
212
213 void MarkupAccumulator::appendCustomAttributes(StringBuilder&, const Element&, Namespaces*)
214 {
215 }
216
217 void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, const Element& element, const Attribute& attribute)
218 {
219     ASSERT(element.isURLAttribute(attribute));
220     const String resolvedURLString = resolveURLIfNeeded(element, attribute.value());
221     UChar quoteChar = '"';
222     String strippedURLString = resolvedURLString.stripWhiteSpace();
223     if (protocolIsJavaScript(strippedURLString)) {
224         // minimal escaping for javascript urls
225         if (strippedURLString.contains('"')) {
226             if (strippedURLString.contains('\''))
227                 strippedURLString.replaceWithLiteral('"', "&quot;");
228             else
229                 quoteChar = '\'';
230         }
231         result.append(quoteChar);
232         result.append(strippedURLString);
233         result.append(quoteChar);
234         return;
235     }
236
237     // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
238     result.append(quoteChar);
239     appendAttributeValue(result, resolvedURLString, false);
240     result.append(quoteChar);
241 }
242
243 void MarkupAccumulator::appendNodeValue(StringBuilder& result, const Node& node, const Range* range, EntityMask entityMask)
244 {
245     const String str = node.nodeValue();
246     unsigned length = str.length();
247     unsigned start = 0;
248
249     if (range) {
250         if (&node == range->endContainer())
251             length = range->endOffset();
252         if (&node == range->startContainer()) {
253             start = range->startOffset();
254             length -= start;
255         }
256     }
257
258     appendCharactersReplacingEntities(result, str, start, length, entityMask);
259 }
260
261 bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element)
262 {
263     // Don't add namespace attribute if it is already defined for this elem.
264     const AtomicString& prefix = element.prefix();
265     if (prefix.isEmpty())
266         return !element.hasAttribute(xmlnsAtom);
267
268     DEFINE_STATIC_LOCAL(String, xmlnsWithColon, (ASCIILiteral("xmlns:")));
269     return !element.hasAttribute(xmlnsWithColon + prefix);
270 }
271
272 bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces)
273 {
274     namespaces.checkConsistency();
275
276     // Don't add namespace attributes twice
277     // HTML Parser will create xmlns attributes without namespace for HTML elements, allow those as well.
278     if (attribute.name().localName() == xmlnsAtom && (attribute.namespaceURI().isEmpty() || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI)) {
279         namespaces.set(emptyAtom.impl(), attribute.value().impl());
280         return false;
281     }
282
283     QualifiedName xmlnsPrefixAttr(xmlnsAtom, attribute.localName(), XMLNSNames::xmlnsNamespaceURI);
284     if (attribute.name() == xmlnsPrefixAttr) {
285         namespaces.set(attribute.localName().impl(), attribute.value().impl());
286         namespaces.set(attribute.value().impl(), attribute.localName().impl());
287         return false;
288     }
289
290     return true;
291 }
292
293 void MarkupAccumulator::appendNamespace(StringBuilder& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces& namespaces, bool allowEmptyDefaultNS)
294 {
295     namespaces.checkConsistency();
296     if (namespaceURI.isEmpty()) {
297         // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-xhtml-syntax.html#xml-fragment-serialization-algorithm
298         if (allowEmptyDefaultNS && namespaces.get(emptyAtom.impl())) {
299             result.append(' ');
300             result.append(xmlnsAtom.string());
301             result.appendLiteral("=\"\"");
302         }
303         return;
304     }
305
306     // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key
307     AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl();
308     AtomicStringImpl* foundNS = namespaces.get(pre);
309     if (foundNS != namespaceURI.impl()) {
310         namespaces.set(pre, namespaceURI.impl());
311         // Add namespace to prefix pair so we can do constraint checking later.
312         if (inXMLFragmentSerialization() && !prefix.isEmpty())
313             namespaces.set(namespaceURI.impl(), pre);
314         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
315         if (namespaceURI.impl() == XMLNames::xmlNamespaceURI.impl())
316             return;
317         result.append(' ');
318         result.append(xmlnsAtom.string());
319         if (!prefix.isEmpty()) {
320             result.append(':');
321             result.append(prefix);
322         }
323
324         result.append('=');
325         result.append('"');
326         appendAttributeValue(result, namespaceURI, false);
327         result.append('"');
328     }
329 }
330
331 EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const
332 {
333     const QualifiedName* parentName = 0;
334     if (text.parentElement())
335         parentName = &text.parentElement()->tagQName();
336
337     if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag))
338         return EntityMaskInCDATA;
339
340     return text.document().isHTMLDocument() ? EntityMaskInHTMLPCDATA : EntityMaskInPCDATA;
341 }
342
343 void MarkupAccumulator::appendText(StringBuilder& result, const Text& text)
344 {
345     appendNodeValue(result, text, m_range, entityMaskForText(text));
346 }
347
348 static void appendComment(StringBuilder& result, const String& comment)
349 {
350     // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
351     result.appendLiteral("<!--");
352     result.append(comment);
353     result.appendLiteral("-->");
354 }
355
356 void MarkupAccumulator::appendXMLDeclaration(StringBuilder& result, const Document& document)
357 {
358     if (!document.hasXMLDeclaration())
359         return;
360
361     result.appendLiteral("<?xml version=\"");
362     result.append(document.xmlVersion());
363     const String& encoding = document.xmlEncoding();
364     if (!encoding.isEmpty()) {
365         result.appendLiteral("\" encoding=\"");
366         result.append(encoding);
367     }
368     if (document.xmlStandaloneStatus() != Document::StandaloneUnspecified) {
369         result.appendLiteral("\" standalone=\"");
370         if (document.xmlStandalone())
371             result.appendLiteral("yes");
372         else
373             result.appendLiteral("no");
374     }
375
376     result.appendLiteral("\"?>");
377 }
378
379 void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType& documentType)
380 {
381     if (documentType.name().isEmpty())
382         return;
383
384     result.appendLiteral("<!DOCTYPE ");
385     result.append(documentType.name());
386     if (!documentType.publicId().isEmpty()) {
387         result.appendLiteral(" PUBLIC \"");
388         result.append(documentType.publicId());
389         result.append('"');
390         if (!documentType.systemId().isEmpty()) {
391             result.append(' ');
392             result.append('"');
393             result.append(documentType.systemId());
394             result.append('"');
395         }
396     } else if (!documentType.systemId().isEmpty()) {
397         result.appendLiteral(" SYSTEM \"");
398         result.append(documentType.systemId());
399         result.append('"');
400     }
401     if (!documentType.internalSubset().isEmpty()) {
402         result.append(' ');
403         result.append('[');
404         result.append(documentType.internalSubset());
405         result.append(']');
406     }
407     result.append('>');
408 }
409
410 void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data)
411 {
412     // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
413     result.append('<');
414     result.append('?');
415     result.append(target);
416     result.append(' ');
417     result.append(data);
418     result.append('?');
419     result.append('>');
420 }
421
422 void MarkupAccumulator::appendElement(StringBuilder& result, const Element& element, Namespaces* namespaces)
423 {
424     appendOpenTag(result, element, namespaces);
425
426     if (element.hasAttributes()) {
427         unsigned length = element.attributeCount();
428         for (unsigned int i = 0; i < length; i++)
429             appendAttribute(result, element, element.attributeAt(i), 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.append("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 documentIsHTML = element.document().isHTMLDocument();
493
494     result.append(' ');
495
496     QualifiedName prefixedName = attribute.name();
497     if (documentIsHTML && !attributeIsInSerializedNamespace(attribute))
498         result.append(attribute.name().localName());
499     else {
500         if (!attribute.namespaceURI().isEmpty()) {
501             AtomicStringImpl* foundNS = namespaces && attribute.prefix().impl() ? namespaces->get(attribute.prefix().impl()) : 0;
502             bool prefixIsAlreadyMappedToOtherNS = foundNS && foundNS != attribute.namespaceURI().impl();
503             if (attribute.prefix().isEmpty() || !foundNS || prefixIsAlreadyMappedToOtherNS) {
504                 if (AtomicStringImpl* prefix = namespaces ? namespaces->get(attribute.namespaceURI().impl()) : 0)
505                     prefixedName.setPrefix(AtomicString(prefix));
506                 else {
507                     bool shouldBeDeclaredUsingAppendNamespace = !attribute.prefix().isEmpty() && !foundNS;
508                     if (!shouldBeDeclaredUsingAppendNamespace && attribute.localName() != xmlnsAtom && namespaces)
509                         generateUniquePrefix(prefixedName, *namespaces);
510                 }
511             }
512         }
513         result.append(prefixedName.toString());
514     }
515
516     result.append('=');
517
518     if (element.isURLAttribute(attribute))
519         appendQuotedURLAttributeValue(result, element, attribute);
520     else {
521         result.append('"');
522         appendAttributeValue(result, attribute.value(), documentIsHTML);
523         result.append('"');
524     }
525
526     if ((inXMLFragmentSerialization() || !documentIsHTML) && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces))
527         appendNamespace(result, prefixedName.prefix(), prefixedName.namespaceURI(), *namespaces);
528 }
529
530 void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section)
531 {
532     // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
533     result.appendLiteral("<![CDATA[");
534     result.append(section);
535     result.appendLiteral("]]>");
536 }
537
538 void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node& node, Namespaces* namespaces)
539 {
540     if (namespaces)
541         namespaces->checkConsistency();
542
543     switch (node.nodeType()) {
544     case Node::TEXT_NODE:
545         appendText(result, toText(node));
546         break;
547     case Node::COMMENT_NODE:
548         appendComment(result, toComment(node).data());
549         break;
550     case Node::DOCUMENT_NODE:
551         appendXMLDeclaration(result, toDocument(node));
552         break;
553     case Node::DOCUMENT_FRAGMENT_NODE:
554         break;
555     case Node::DOCUMENT_TYPE_NODE:
556         appendDocumentType(result, toDocumentType(node));
557         break;
558     case Node::PROCESSING_INSTRUCTION_NODE:
559         appendProcessingInstruction(result, toProcessingInstruction(node).target(), toProcessingInstruction(node).data());
560         break;
561     case Node::ELEMENT_NODE:
562         appendElement(result, toElement(node), namespaces);
563         break;
564     case Node::CDATA_SECTION_NODE:
565         appendCDATASection(result, toCDATASection(node).data());
566         break;
567     case Node::ATTRIBUTE_NODE:
568     case Node::ENTITY_NODE:
569     case Node::ENTITY_REFERENCE_NODE:
570     case Node::NOTATION_NODE:
571     case Node::XPATH_NAMESPACE_NODE:
572         ASSERT_NOT_REACHED();
573         break;
574     }
575 }
576
577 // Rules of self-closure
578 // 1. No elements in HTML documents use the self-closing syntax.
579 // 2. Elements w/ children never self-close because they use a separate end tag.
580 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
581 // 4. Other elements self-close.
582 bool MarkupAccumulator::shouldSelfClose(const Node& node)
583 {
584     if (!inXMLFragmentSerialization() && node.document().isHTMLDocument())
585         return false;
586     if (node.hasChildNodes())
587         return false;
588     if (node.isHTMLElement() && !elementCannotHaveEndTag(node))
589         return false;
590     return true;
591 }
592
593 bool MarkupAccumulator::elementCannotHaveEndTag(const Node& node)
594 {
595     if (!node.isHTMLElement())
596         return false;
597
598     // FIXME: ieForbidsInsertHTML may not be the right function to call here
599     // ieForbidsInsertHTML is used to disallow setting innerHTML/outerHTML
600     // or createContextualFragment.  It does not necessarily align with
601     // which elements should be serialized w/o end tags.
602     return toHTMLElement(node).ieForbidsInsertHTML();
603 }
604
605 void MarkupAccumulator::appendEndMarkup(StringBuilder& result, const Node& node)
606 {
607     if (!node.isElementNode() || shouldSelfClose(node) || (!node.hasChildNodes() && elementCannotHaveEndTag(node)))
608         return;
609
610     result.append('<');
611     result.append('/');
612     result.append(toElement(node).nodeNamePreservingCase());
613     result.append('>');
614 }
615
616 }