3dc18a67513850b1b8e2c2fb2a96ce34716d4996
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / 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 "core/editing/MarkupAccumulator.h"
29
30 #include "core/HTMLNames.h"
31 #include "core/XLinkNames.h"
32 #include "core/XMLNSNames.h"
33 #include "core/XMLNames.h"
34 #include "core/dom/CDATASection.h"
35 #include "core/dom/Comment.h"
36 #include "core/dom/Document.h"
37 #include "core/dom/DocumentFragment.h"
38 #include "core/dom/DocumentType.h"
39 #include "core/dom/ProcessingInstruction.h"
40 #include "core/editing/Editor.h"
41 #include "core/html/HTMLElement.h"
42 #include "core/html/HTMLTemplateElement.h"
43 #include "platform/weborigin/KURL.h"
44 #include "wtf/unicode/CharacterNames.h"
45
46 namespace blink {
47
48 using namespace HTMLNames;
49
50 struct EntityDescription {
51     UChar entity;
52     const CString& reference;
53     EntityMask mask;
54 };
55
56 template <typename CharType>
57 static inline void appendCharactersReplacingEntitiesInternal(StringBuilder& result, CharType* text, unsigned length, const EntityDescription entityMaps[], unsigned entityMapsCount, EntityMask entityMask)
58 {
59     unsigned positionAfterLastEntity = 0;
60     for (unsigned i = 0; i < length; ++i) {
61         for (unsigned entityIndex = 0; entityIndex < entityMapsCount; ++entityIndex) {
62             if (text[i] == entityMaps[entityIndex].entity && entityMaps[entityIndex].mask & entityMask) {
63                 result.append(text + positionAfterLastEntity, i - positionAfterLastEntity);
64                 const CString& replacement = entityMaps[entityIndex].reference;
65                 result.append(replacement.data(), replacement.length());
66                 positionAfterLastEntity = i + 1;
67                 break;
68             }
69         }
70     }
71     result.append(text + positionAfterLastEntity, length - positionAfterLastEntity);
72 }
73
74 void MarkupAccumulator::appendCharactersReplacingEntities(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask)
75 {
76     DEFINE_STATIC_LOCAL(const CString, ampReference, ("&amp;"));
77     DEFINE_STATIC_LOCAL(const CString, ltReference, ("&lt;"));
78     DEFINE_STATIC_LOCAL(const CString, gtReference, ("&gt;"));
79     DEFINE_STATIC_LOCAL(const CString, quotReference, ("&quot;"));
80     DEFINE_STATIC_LOCAL(const CString, nbspReference, ("&nbsp;"));
81
82     static const EntityDescription entityMaps[] = {
83         { '&', ampReference, EntityAmp },
84         { '<', ltReference, EntityLt },
85         { '>', gtReference, EntityGt },
86         { '"', quotReference, EntityQuot },
87         { noBreakSpace, nbspReference, EntityNbsp },
88     };
89
90     if (!(offset + length))
91         return;
92
93     ASSERT(offset + length <= source.length());
94     if (source.is8Bit())
95         appendCharactersReplacingEntitiesInternal(result, source.characters8() + offset, length, entityMaps, WTF_ARRAY_LENGTH(entityMaps), entityMask);
96     else
97         appendCharactersReplacingEntitiesInternal(result, source.characters16() + offset, length, entityMaps, WTF_ARRAY_LENGTH(entityMaps), entityMask);
98 }
99
100 MarkupAccumulator::MarkupAccumulator(WillBeHeapVector<RawPtrWillBeMember<Node>>* nodes, EAbsoluteURLs resolveUrlsMethod, const Range* range, SerializationType serializationType)
101     : m_nodes(nodes)
102     , m_range(range)
103     , m_resolveURLsMethod(resolveUrlsMethod)
104     , m_serializationType(serializationType)
105 {
106 }
107
108 MarkupAccumulator::~MarkupAccumulator()
109 {
110 }
111
112 String MarkupAccumulator::serializeNodes(Node& targetNode, EChildrenOnly childrenOnly, Vector<QualifiedName>* tagNamesToSkip)
113 {
114     Namespaces* namespaces = nullptr;
115     Namespaces namespaceHash;
116     if (!serializeAsHTMLDocument(targetNode)) {
117         // Add pre-bound namespaces for XML fragments.
118         namespaceHash.set(xmlAtom, XMLNames::xmlNamespaceURI);
119         namespaces = &namespaceHash;
120     }
121
122     serializeNodesWithNamespaces(targetNode, childrenOnly, namespaces, tagNamesToSkip);
123     return m_markup.toString();
124 }
125
126 void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, EChildrenOnly childrenOnly, const Namespaces* namespaces, Vector<QualifiedName>* tagNamesToSkip)
127 {
128     if (tagNamesToSkip && targetNode.isElementNode()) {
129         for (const auto& tag : *tagNamesToSkip) {
130             if (toElement(targetNode).hasTagName(tag))
131                 return;
132         }
133     }
134
135     Namespaces namespaceHash;
136     if (namespaces)
137         namespaceHash = *namespaces;
138
139     if (!childrenOnly)
140         appendStartTag(targetNode, &namespaceHash);
141
142     if (!(serializeAsHTMLDocument(targetNode) && elementCannotHaveEndTag(targetNode))) {
143         Node* current = isHTMLTemplateElement(targetNode) ? toHTMLTemplateElement(targetNode).content()->firstChild() : targetNode.firstChild();
144         for ( ; current; current = current->nextSibling())
145             serializeNodesWithNamespaces(*current, IncludeNode, &namespaceHash, tagNamesToSkip);
146     }
147
148     if (!childrenOnly && targetNode.isElementNode())
149         appendEndTag(toElement(targetNode));
150 }
151
152 String MarkupAccumulator::resolveURLIfNeeded(const Element& element, const String& urlString) const
153 {
154     switch (m_resolveURLsMethod) {
155     case ResolveAllURLs:
156         return element.document().completeURL(urlString).string();
157
158     case ResolveNonLocalURLs:
159         if (!element.document().url().isLocalFile())
160             return element.document().completeURL(urlString).string();
161         break;
162
163     case DoNotResolveURLs:
164         break;
165     }
166     return urlString;
167 }
168
169 void MarkupAccumulator::appendString(const String& string)
170 {
171     m_markup.append(string);
172 }
173
174 void MarkupAccumulator::appendStartTag(Node& node, Namespaces* namespaces)
175 {
176     appendStartMarkup(m_markup, node, namespaces);
177     if (m_nodes)
178         m_nodes->append(&node);
179 }
180
181 void MarkupAccumulator::appendEndTag(const Element& element)
182 {
183     appendEndMarkup(m_markup, element);
184 }
185
186 size_t MarkupAccumulator::totalLength(const Vector<String>& strings)
187 {
188     size_t length = 0;
189     for (const auto& string : strings)
190         length += string.length();
191     return length;
192 }
193
194 void MarkupAccumulator::concatenateMarkup(StringBuilder& result)
195 {
196     result.append(m_markup);
197 }
198
199 void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool documentIsHTML)
200 {
201     appendCharactersReplacingEntities(result, attribute, 0, attribute.length(),
202         documentIsHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue);
203 }
204
205 void MarkupAccumulator::appendCustomAttributes(StringBuilder&, const Element&, Namespaces*)
206 {
207 }
208
209 void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, const Element& element, const Attribute& attribute)
210 {
211     ASSERT(element.isURLAttribute(attribute));
212     const String resolvedURLString = resolveURLIfNeeded(element, attribute.value());
213     UChar quoteChar = '"';
214     String strippedURLString = resolvedURLString.stripWhiteSpace();
215     if (protocolIsJavaScript(strippedURLString)) {
216         // minimal escaping for javascript urls
217         if (strippedURLString.contains('"')) {
218             if (strippedURLString.contains('\''))
219                 strippedURLString.replaceWithLiteral('"', "&quot;");
220             else
221                 quoteChar = '\'';
222         }
223         result.append(quoteChar);
224         result.append(strippedURLString);
225         result.append(quoteChar);
226         return;
227     }
228
229     // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
230     result.append(quoteChar);
231     appendAttributeValue(result, resolvedURLString, false);
232     result.append(quoteChar);
233 }
234
235 bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element, Namespaces& namespaces)
236 {
237     // Don't add namespace attribute if it is already defined for this elem.
238     const AtomicString& prefix = element.prefix();
239     if (prefix.isEmpty()) {
240         if (element.hasAttribute(xmlnsAtom)) {
241             namespaces.set(emptyAtom, element.namespaceURI());
242             return false;
243         }
244         return true;
245     }
246
247     return !element.hasAttribute(WTF::xmlnsWithColon + prefix);
248 }
249
250 bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, const Element& element)
251 {
252     // xmlns and xmlns:prefix attributes should be handled by another branch in appendAttribute.
253     ASSERT(attribute.namespaceURI() != XMLNSNames::xmlnsNamespaceURI);
254
255     // Attributes are in the null namespace by default.
256     if (!attribute.namespaceURI())
257         return false;
258
259     // Attributes without a prefix will need one generated for them, and an xmlns attribute for that prefix.
260     if (!attribute.prefix())
261         return true;
262
263     return !element.hasAttribute(WTF::xmlnsWithColon + attribute.prefix());
264 }
265
266 void MarkupAccumulator::appendNamespace(StringBuilder& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces& namespaces)
267 {
268     if (namespaceURI.isEmpty())
269         return;
270
271     const AtomicString& lookupKey = (!prefix) ? emptyAtom : prefix;
272     AtomicString foundURI = namespaces.get(lookupKey);
273     if (foundURI != namespaceURI) {
274         namespaces.set(lookupKey, namespaceURI);
275         result.append(' ');
276         result.append(xmlnsAtom.string());
277         if (!prefix.isEmpty()) {
278             result.append(':');
279             result.append(prefix);
280         }
281
282         result.appendLiteral("=\"");
283         appendAttributeValue(result, namespaceURI, false);
284         result.append('"');
285     }
286 }
287
288 EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const
289 {
290     if (!serializeAsHTMLDocument(text))
291         return EntityMaskInPCDATA;
292
293     const QualifiedName* parentName = nullptr;
294     if (text.parentElement())
295         parentName = &(text.parentElement())->tagQName();
296
297     if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag))
298         return EntityMaskInCDATA;
299     return EntityMaskInHTMLPCDATA;
300 }
301
302 void MarkupAccumulator::appendText(StringBuilder& result, Text& text)
303 {
304     const String& str = text.data();
305     unsigned length = str.length();
306     unsigned start = 0;
307
308     if (m_range) {
309         if (text == m_range->endContainer())
310             length = m_range->endOffset();
311         if (text == m_range->startContainer()) {
312             start = m_range->startOffset();
313             length -= start;
314         }
315     }
316     appendCharactersReplacingEntities(result, str, start, length, entityMaskForText(text));
317 }
318
319 void MarkupAccumulator::appendComment(StringBuilder& result, const String& comment)
320 {
321     // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
322     result.appendLiteral("<!--");
323     result.append(comment);
324     result.appendLiteral("-->");
325 }
326
327 void MarkupAccumulator::appendXMLDeclaration(StringBuilder& result, const Document& document)
328 {
329     if (!document.hasXMLDeclaration())
330         return;
331
332     result.appendLiteral("<?xml version=\"");
333     result.append(document.xmlVersion());
334     const String& encoding = document.xmlEncoding();
335     if (!encoding.isEmpty()) {
336         result.appendLiteral("\" encoding=\"");
337         result.append(encoding);
338     }
339     if (document.xmlStandaloneStatus() != Document::StandaloneUnspecified) {
340         result.appendLiteral("\" standalone=\"");
341         if (document.xmlStandalone())
342             result.appendLiteral("yes");
343         else
344             result.appendLiteral("no");
345     }
346
347     result.appendLiteral("\"?>");
348 }
349
350 void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType& n)
351 {
352     if (n.name().isEmpty())
353         return;
354
355     result.appendLiteral("<!DOCTYPE ");
356     result.append(n.name());
357     if (!n.publicId().isEmpty()) {
358         result.appendLiteral(" PUBLIC \"");
359         result.append(n.publicId());
360         result.append('"');
361         if (!n.systemId().isEmpty()) {
362             result.appendLiteral(" \"");
363             result.append(n.systemId());
364             result.append('"');
365         }
366     } else if (!n.systemId().isEmpty()) {
367         result.appendLiteral(" SYSTEM \"");
368         result.append(n.systemId());
369         result.append('"');
370     }
371     result.append('>');
372 }
373
374 void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data)
375 {
376     // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
377     result.appendLiteral("<?");
378     result.append(target);
379     result.append(' ');
380     result.append(data);
381     result.appendLiteral("?>");
382 }
383
384 void MarkupAccumulator::appendElement(StringBuilder& result, Element& element, Namespaces* namespaces)
385 {
386     appendOpenTag(result, element, namespaces);
387
388     AttributeCollection attributes = element.attributes();
389     for (const auto& attribute : attributes)
390         appendAttribute(result, element, attribute, namespaces);
391
392     // Give an opportunity to subclasses to add their own attributes.
393     appendCustomAttributes(result, element, namespaces);
394
395     appendCloseTag(result, element);
396 }
397
398 void MarkupAccumulator::appendOpenTag(StringBuilder& result, const Element& element, Namespaces* namespaces)
399 {
400     result.append('<');
401     result.append(element.tagQName().toString());
402     if (!serializeAsHTMLDocument(element) && namespaces && shouldAddNamespaceElement(element, *namespaces))
403         appendNamespace(result, element.prefix(), element.namespaceURI(), *namespaces);
404 }
405
406 void MarkupAccumulator::appendCloseTag(StringBuilder& result, const Element& element)
407 {
408     if (shouldSelfClose(element)) {
409         if (element.isHTMLElement())
410             result.append(' '); // XHTML 1.0 <-> HTML compatibility.
411         result.append('/');
412     }
413     result.append('>');
414 }
415
416 static inline bool attributeIsInSerializedNamespace(const Attribute& attribute)
417 {
418     return attribute.namespaceURI() == XMLNames::xmlNamespaceURI
419         || attribute.namespaceURI() == XLinkNames::xlinkNamespaceURI
420         || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI;
421 }
422
423 void MarkupAccumulator::appendAttribute(StringBuilder& result, const Element& element, const Attribute& attribute, Namespaces* namespaces)
424 {
425     bool documentIsHTML = serializeAsHTMLDocument(element);
426
427     QualifiedName prefixedName = attribute.name();
428     if (documentIsHTML && !attributeIsInSerializedNamespace(attribute)) {
429         result.append(' ');
430         result.append(attribute.name().localName());
431     } else {
432         if (attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI) {
433             if (!attribute.prefix() && attribute.localName() != xmlnsAtom)
434                 prefixedName.setPrefix(xmlnsAtom);
435             if (namespaces) { // Account for the namespace attribute we're about to append.
436                 const AtomicString& lookupKey = (!attribute.prefix()) ? emptyAtom : attribute.localName();
437                 namespaces->set(lookupKey, attribute.value());
438             }
439         } else if (attribute.namespaceURI() == XMLNames::xmlNamespaceURI) {
440             if (!attribute.prefix())
441                 prefixedName.setPrefix(xmlAtom);
442         } else {
443             if (attribute.namespaceURI() == XLinkNames::xlinkNamespaceURI) {
444                 if (!attribute.prefix())
445                     prefixedName.setPrefix(xlinkAtom);
446             }
447
448             if (namespaces && shouldAddNamespaceAttribute(attribute, element)) {
449                 if (!prefixedName.prefix()) {
450                     // This behavior is in process of being standardized. See crbug.com/248044 and https://www.w3.org/Bugs/Public/show_bug.cgi?id=24208
451                     String prefixPrefix("ns", 2);
452                     for (unsigned i = attribute.namespaceURI().impl()->existingHash(); ; ++i) {
453                         AtomicString newPrefix(String(prefixPrefix + String::number(i)));
454                         AtomicString foundURI = namespaces->get(newPrefix);
455                         if (foundURI == attribute.namespaceURI() || foundURI == nullAtom) {
456                             // We already generated a prefix for this namespace.
457                             prefixedName.setPrefix(newPrefix);
458                             break;
459                         }
460                     }
461                 }
462                 ASSERT(prefixedName.prefix());
463                 appendNamespace(result, prefixedName.prefix(), attribute.namespaceURI(), *namespaces);
464             }
465         }
466         result.append(' ');
467         result.append(prefixedName.toString());
468     }
469
470     result.append('=');
471
472     if (element.isURLAttribute(attribute)) {
473         appendQuotedURLAttributeValue(result, element, attribute);
474     } else {
475         result.append('"');
476         appendAttributeValue(result, attribute.value(), documentIsHTML);
477         result.append('"');
478     }
479 }
480
481 void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section)
482 {
483     // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
484     result.appendLiteral("<![CDATA[");
485     result.append(section);
486     result.appendLiteral("]]>");
487 }
488
489 void MarkupAccumulator::appendStartMarkup(StringBuilder& result, Node& node, Namespaces* namespaces)
490 {
491     switch (node.nodeType()) {
492     case Node::TEXT_NODE:
493         appendText(result, toText(node));
494         break;
495     case Node::COMMENT_NODE:
496         appendComment(result, toComment(node).data());
497         break;
498     case Node::DOCUMENT_NODE:
499         appendXMLDeclaration(result, toDocument(node));
500         break;
501     case Node::DOCUMENT_FRAGMENT_NODE:
502         break;
503     case Node::DOCUMENT_TYPE_NODE:
504         appendDocumentType(result, toDocumentType(node));
505         break;
506     case Node::PROCESSING_INSTRUCTION_NODE:
507         appendProcessingInstruction(result, toProcessingInstruction(node).target(), toProcessingInstruction(node).data());
508         break;
509     case Node::ELEMENT_NODE:
510         appendElement(result, toElement(node), namespaces);
511         break;
512     case Node::CDATA_SECTION_NODE:
513         appendCDATASection(result, toCDATASection(node).data());
514         break;
515     case Node::ATTRIBUTE_NODE:
516         ASSERT_NOT_REACHED();
517         break;
518     }
519 }
520
521 // Rules of self-closure
522 // 1. No elements in HTML documents use the self-closing syntax.
523 // 2. Elements w/ children never self-close because they use a separate end tag.
524 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
525 // 4. Other elements self-close.
526 bool MarkupAccumulator::shouldSelfClose(const Element& element)
527 {
528     if (serializeAsHTMLDocument(element))
529         return false;
530     if (element.hasChildren())
531         return false;
532     if (element.isHTMLElement() && !elementCannotHaveEndTag(element))
533         return false;
534     return true;
535 }
536
537 bool MarkupAccumulator::elementCannotHaveEndTag(const Node& node)
538 {
539     if (!node.isHTMLElement())
540         return false;
541
542     // FIXME: ieForbidsInsertHTML may not be the right function to call here
543     // ieForbidsInsertHTML is used to disallow setting innerHTML/outerHTML
544     // or createContextualFragment.  It does not necessarily align with
545     // which elements should be serialized w/o end tags.
546     return toHTMLElement(node).ieForbidsInsertHTML();
547 }
548
549 void MarkupAccumulator::appendEndMarkup(StringBuilder& result, const Element& element)
550 {
551     if (shouldSelfClose(element) || (!element.hasChildren() && elementCannotHaveEndTag(element)))
552         return;
553
554     result.appendLiteral("</");
555     result.append(element.tagQName().toString());
556     result.append('>');
557 }
558
559 bool MarkupAccumulator::serializeAsHTMLDocument(const Node& node) const
560 {
561     if (m_serializationType == ForcedXML)
562         return false;
563     return node.document().isHTMLDocument();
564 }
565
566 }