1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * nsIContentSerializer implementation that can be used with an
9  * nsIDocumentEncoder to convert an XML DOM to an XML string that
10  * could be parsed into more or less the original DOM.
11  */
12 
13 #include "nsXMLContentSerializer.h"
14 
15 #include "nsGkAtoms.h"
16 #include "nsIContent.h"
17 #include "nsIContentInlines.h"
18 #include "mozilla/dom/Document.h"
19 #include "nsIDocumentEncoder.h"
20 #include "nsElementTable.h"
21 #include "nsNameSpaceManager.h"
22 #include "nsTextFragment.h"
23 #include "nsString.h"
24 #include "mozilla/Sprintf.h"
25 #include "nsUnicharUtils.h"
26 #include "nsCRT.h"
27 #include "nsContentUtils.h"
28 #include "nsAttrName.h"
29 #include "mozilla/dom/Comment.h"
30 #include "mozilla/dom/CustomElementRegistry.h"
31 #include "mozilla/dom/DocumentType.h"
32 #include "mozilla/dom/Element.h"
33 #include "mozilla/dom/ProcessingInstruction.h"
34 #include "mozilla/intl/LineBreaker.h"
35 #include "nsParserConstants.h"
36 #include "mozilla/Encoding.h"
37 
38 using namespace mozilla;
39 using namespace mozilla::dom;
40 
41 #define kXMLNS "xmlns"
42 
43 // to be readable, we assume that an indented line contains
44 // at least this number of characters (arbitrary value here).
45 // This is a limit for the indentation.
46 #define MIN_INDENTED_LINE_LENGTH 15
47 
48 // the string used to indent.
49 #define INDENT_STRING "  "
50 #define INDENT_STRING_LENGTH 2
51 
NS_NewXMLContentSerializer(nsIContentSerializer ** aSerializer)52 nsresult NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer) {
53   RefPtr<nsXMLContentSerializer> it = new nsXMLContentSerializer();
54   it.forget(aSerializer);
55   return NS_OK;
56 }
57 
nsXMLContentSerializer()58 nsXMLContentSerializer::nsXMLContentSerializer()
59     : mPrefixIndex(0),
60       mColPos(0),
61       mIndentOverflow(0),
62       mIsIndentationAddedOnCurrentLine(false),
63       mInAttribute(false),
64       mAddNewlineForRootNode(false),
65       mAddSpace(false),
66       mMayIgnoreLineBreakSequence(false),
67       mBodyOnly(false),
68       mInBody(0) {}
69 
70 nsXMLContentSerializer::~nsXMLContentSerializer() = default;
71 
NS_IMPL_ISUPPORTS(nsXMLContentSerializer,nsIContentSerializer)72 NS_IMPL_ISUPPORTS(nsXMLContentSerializer, nsIContentSerializer)
73 
74 NS_IMETHODIMP
75 nsXMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
76                              const Encoding* aEncoding, bool aIsCopying,
77                              bool aRewriteEncodingDeclaration,
78                              bool* aNeedsPreformatScanning,
79                              nsAString& aOutput) {
80   *aNeedsPreformatScanning = false;
81   mPrefixIndex = 0;
82   mColPos = 0;
83   mIndentOverflow = 0;
84   mIsIndentationAddedOnCurrentLine = false;
85   mInAttribute = false;
86   mAddNewlineForRootNode = false;
87   mAddSpace = false;
88   mMayIgnoreLineBreakSequence = false;
89   mBodyOnly = false;
90   mInBody = 0;
91 
92   if (aEncoding) {
93     aEncoding->Name(mCharset);
94   }
95   mFlags = aFlags;
96 
97   // Set the line break character:
98   if ((mFlags & nsIDocumentEncoder::OutputCRLineBreak) &&
99       (mFlags & nsIDocumentEncoder::OutputLFLineBreak)) {  // Windows
100     mLineBreak.AssignLiteral("\r\n");
101   } else if (mFlags & nsIDocumentEncoder::OutputCRLineBreak) {  // Mac
102     mLineBreak.Assign('\r');
103   } else if (mFlags & nsIDocumentEncoder::OutputLFLineBreak) {  // Unix/DOM
104     mLineBreak.Assign('\n');
105   } else {
106     mLineBreak.AssignLiteral(NS_LINEBREAK);  // Platform/default
107   }
108 
109   mDoRaw = !!(mFlags & nsIDocumentEncoder::OutputRaw);
110 
111   mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted && !mDoRaw);
112 
113   mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap && !mDoRaw);
114 
115   mAllowLineBreaking =
116       !(mFlags & nsIDocumentEncoder::OutputDisallowLineBreaking);
117 
118   if (!aWrapColumn) {
119     mMaxColumn = 72;
120   } else {
121     mMaxColumn = aWrapColumn;
122   }
123 
124   mOutput = &aOutput;
125   mPreLevel = 0;
126   mIsIndentationAddedOnCurrentLine = false;
127   return NS_OK;
128 }
129 
AppendTextData(nsIContent * aNode,int32_t aStartOffset,int32_t aEndOffset,nsAString & aStr,bool aTranslateEntities)130 nsresult nsXMLContentSerializer::AppendTextData(nsIContent* aNode,
131                                                 int32_t aStartOffset,
132                                                 int32_t aEndOffset,
133                                                 nsAString& aStr,
134                                                 bool aTranslateEntities) {
135   nsIContent* content = aNode;
136   const nsTextFragment* frag;
137   if (!content || !(frag = content->GetText())) {
138     return NS_ERROR_FAILURE;
139   }
140 
141   int32_t fragLength = frag->GetLength();
142   int32_t endoffset =
143       (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
144   int32_t length = endoffset - aStartOffset;
145 
146   NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
147   NS_ASSERTION(aStartOffset <= endoffset,
148                "A start offset is beyond the end of the text fragment!");
149 
150   if (length <= 0) {
151     // XXX Zero is a legal value, maybe non-zero values should be an
152     // error.
153     return NS_OK;
154   }
155 
156   if (frag->Is2b()) {
157     const char16_t* strStart = frag->Get2b() + aStartOffset;
158     if (aTranslateEntities) {
159       NS_ENSURE_TRUE(AppendAndTranslateEntities(
160                          Substring(strStart, strStart + length), aStr),
161                      NS_ERROR_OUT_OF_MEMORY);
162     } else {
163       NS_ENSURE_TRUE(aStr.Append(Substring(strStart, strStart + length),
164                                  mozilla::fallible),
165                      NS_ERROR_OUT_OF_MEMORY);
166     }
167   } else {
168     nsAutoString utf16;
169     if (!CopyASCIItoUTF16(Span(frag->Get1b() + aStartOffset, length), utf16,
170                           mozilla::fallible_t())) {
171       return NS_ERROR_OUT_OF_MEMORY;
172     }
173     if (aTranslateEntities) {
174       NS_ENSURE_TRUE(AppendAndTranslateEntities(utf16, aStr),
175                      NS_ERROR_OUT_OF_MEMORY);
176     } else {
177       NS_ENSURE_TRUE(aStr.Append(utf16, mozilla::fallible),
178                      NS_ERROR_OUT_OF_MEMORY);
179     }
180   }
181 
182   return NS_OK;
183 }
184 
185 NS_IMETHODIMP
AppendText(nsIContent * aText,int32_t aStartOffset,int32_t aEndOffset)186 nsXMLContentSerializer::AppendText(nsIContent* aText, int32_t aStartOffset,
187                                    int32_t aEndOffset) {
188   NS_ENSURE_ARG(aText);
189   NS_ENSURE_STATE(mOutput);
190 
191   nsAutoString data;
192   nsresult rv;
193 
194   rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
195   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
196 
197   if (mDoRaw || PreLevel() > 0) {
198     NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
199                    NS_ERROR_OUT_OF_MEMORY);
200   } else if (mDoFormat) {
201     NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, *mOutput),
202                    NS_ERROR_OUT_OF_MEMORY);
203   } else if (mDoWrap) {
204     NS_ENSURE_TRUE(AppendToStringWrapped(data, *mOutput),
205                    NS_ERROR_OUT_OF_MEMORY);
206   } else {
207     NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
208                    NS_ERROR_OUT_OF_MEMORY);
209   }
210 
211   return NS_OK;
212 }
213 
214 NS_IMETHODIMP
AppendCDATASection(nsIContent * aCDATASection,int32_t aStartOffset,int32_t aEndOffset)215 nsXMLContentSerializer::AppendCDATASection(nsIContent* aCDATASection,
216                                            int32_t aStartOffset,
217                                            int32_t aEndOffset) {
218   NS_ENSURE_ARG(aCDATASection);
219   NS_ENSURE_STATE(mOutput);
220 
221   nsresult rv;
222 
223   constexpr auto cdata = u"<![CDATA["_ns;
224 
225   if (mDoRaw || PreLevel() > 0) {
226     NS_ENSURE_TRUE(AppendToString(cdata, *mOutput), NS_ERROR_OUT_OF_MEMORY);
227   } else if (mDoFormat) {
228     NS_ENSURE_TRUE(AppendToStringFormatedWrapped(cdata, *mOutput),
229                    NS_ERROR_OUT_OF_MEMORY);
230   } else if (mDoWrap) {
231     NS_ENSURE_TRUE(AppendToStringWrapped(cdata, *mOutput),
232                    NS_ERROR_OUT_OF_MEMORY);
233   } else {
234     NS_ENSURE_TRUE(AppendToString(cdata, *mOutput), NS_ERROR_OUT_OF_MEMORY);
235   }
236 
237   nsAutoString data;
238   rv = AppendTextData(aCDATASection, aStartOffset, aEndOffset, data, false);
239   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
240 
241   NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
242                  NS_ERROR_OUT_OF_MEMORY);
243 
244   NS_ENSURE_TRUE(AppendToString(u"]]>"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
245 
246   return NS_OK;
247 }
248 
249 NS_IMETHODIMP
AppendProcessingInstruction(ProcessingInstruction * aPI,int32_t aStartOffset,int32_t aEndOffset)250 nsXMLContentSerializer::AppendProcessingInstruction(ProcessingInstruction* aPI,
251                                                     int32_t aStartOffset,
252                                                     int32_t aEndOffset) {
253   NS_ENSURE_STATE(mOutput);
254 
255   nsAutoString target, data, start;
256 
257   NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
258 
259   aPI->GetTarget(target);
260 
261   aPI->GetData(data);
262 
263   NS_ENSURE_TRUE(start.AppendLiteral("<?", mozilla::fallible),
264                  NS_ERROR_OUT_OF_MEMORY);
265   NS_ENSURE_TRUE(start.Append(target, mozilla::fallible),
266                  NS_ERROR_OUT_OF_MEMORY);
267 
268   if (mDoRaw || PreLevel() > 0) {
269     NS_ENSURE_TRUE(AppendToString(start, *mOutput), NS_ERROR_OUT_OF_MEMORY);
270   } else if (mDoFormat) {
271     if (mAddSpace) {
272       NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
273     }
274     NS_ENSURE_TRUE(AppendToStringFormatedWrapped(start, *mOutput),
275                    NS_ERROR_OUT_OF_MEMORY);
276   } else if (mDoWrap) {
277     NS_ENSURE_TRUE(AppendToStringWrapped(start, *mOutput),
278                    NS_ERROR_OUT_OF_MEMORY);
279   } else {
280     NS_ENSURE_TRUE(AppendToString(start, *mOutput), NS_ERROR_OUT_OF_MEMORY);
281   }
282 
283   if (!data.IsEmpty()) {
284     NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
285                    NS_ERROR_OUT_OF_MEMORY);
286     NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
287                    NS_ERROR_OUT_OF_MEMORY);
288   }
289   NS_ENSURE_TRUE(AppendToString(u"?>"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
290 
291   MaybeFlagNewlineForRootNode(aPI);
292 
293   return NS_OK;
294 }
295 
296 NS_IMETHODIMP
AppendComment(Comment * aComment,int32_t aStartOffset,int32_t aEndOffset)297 nsXMLContentSerializer::AppendComment(Comment* aComment, int32_t aStartOffset,
298                                       int32_t aEndOffset) {
299   NS_ENSURE_STATE(mOutput);
300 
301   nsAutoString data;
302   aComment->GetData(data);
303 
304   int32_t dataLength = data.Length();
305   if (aStartOffset || (aEndOffset != -1 && aEndOffset < dataLength)) {
306     int32_t length =
307         (aEndOffset == -1) ? dataLength : std::min(aEndOffset, dataLength);
308     length -= aStartOffset;
309 
310     nsAutoString frag;
311     if (length > 0) {
312       data.Mid(frag, aStartOffset, length);
313     }
314     data.Assign(frag);
315   }
316 
317   NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
318 
319   constexpr auto startComment = u"<!--"_ns;
320 
321   if (mDoRaw || PreLevel() > 0) {
322     NS_ENSURE_TRUE(AppendToString(startComment, *mOutput),
323                    NS_ERROR_OUT_OF_MEMORY);
324   } else if (mDoFormat) {
325     if (mAddSpace) {
326       NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
327     }
328     NS_ENSURE_TRUE(AppendToStringFormatedWrapped(startComment, *mOutput),
329                    NS_ERROR_OUT_OF_MEMORY);
330   } else if (mDoWrap) {
331     NS_ENSURE_TRUE(AppendToStringWrapped(startComment, *mOutput),
332                    NS_ERROR_OUT_OF_MEMORY);
333   } else {
334     NS_ENSURE_TRUE(AppendToString(startComment, *mOutput),
335                    NS_ERROR_OUT_OF_MEMORY);
336   }
337 
338   // Even if mDoformat, we don't format the content because it
339   // could have been preformated by the author
340   NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
341                  NS_ERROR_OUT_OF_MEMORY);
342   NS_ENSURE_TRUE(AppendToString(u"-->"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
343 
344   MaybeFlagNewlineForRootNode(aComment);
345 
346   return NS_OK;
347 }
348 
349 NS_IMETHODIMP
AppendDoctype(DocumentType * aDocType)350 nsXMLContentSerializer::AppendDoctype(DocumentType* aDocType) {
351   NS_ENSURE_STATE(mOutput);
352 
353   nsAutoString name, publicId, systemId;
354   aDocType->GetName(name);
355   aDocType->GetPublicId(publicId);
356   aDocType->GetSystemId(systemId);
357 
358   NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
359 
360   NS_ENSURE_TRUE(AppendToString(u"<!DOCTYPE "_ns, *mOutput),
361                  NS_ERROR_OUT_OF_MEMORY);
362   NS_ENSURE_TRUE(AppendToString(name, *mOutput), NS_ERROR_OUT_OF_MEMORY);
363 
364   char16_t quote;
365   if (!publicId.IsEmpty()) {
366     NS_ENSURE_TRUE(AppendToString(u" PUBLIC "_ns, *mOutput),
367                    NS_ERROR_OUT_OF_MEMORY);
368     if (publicId.FindChar(char16_t('"')) == -1) {
369       quote = char16_t('"');
370     } else {
371       quote = char16_t('\'');
372     }
373     NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
374     NS_ENSURE_TRUE(AppendToString(publicId, *mOutput), NS_ERROR_OUT_OF_MEMORY);
375     NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
376 
377     if (!systemId.IsEmpty()) {
378       NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
379                      NS_ERROR_OUT_OF_MEMORY);
380       if (systemId.FindChar(char16_t('"')) == -1) {
381         quote = char16_t('"');
382       } else {
383         quote = char16_t('\'');
384       }
385       NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
386       NS_ENSURE_TRUE(AppendToString(systemId, *mOutput),
387                      NS_ERROR_OUT_OF_MEMORY);
388       NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
389     }
390   } else if (!systemId.IsEmpty()) {
391     if (systemId.FindChar(char16_t('"')) == -1) {
392       quote = char16_t('"');
393     } else {
394       quote = char16_t('\'');
395     }
396     NS_ENSURE_TRUE(AppendToString(u" SYSTEM "_ns, *mOutput),
397                    NS_ERROR_OUT_OF_MEMORY);
398     NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
399     NS_ENSURE_TRUE(AppendToString(systemId, *mOutput), NS_ERROR_OUT_OF_MEMORY);
400     NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
401   }
402 
403   NS_ENSURE_TRUE(AppendToString(kGreaterThan, *mOutput),
404                  NS_ERROR_OUT_OF_MEMORY);
405   MaybeFlagNewlineForRootNode(aDocType);
406 
407   return NS_OK;
408 }
409 
PushNameSpaceDecl(const nsAString & aPrefix,const nsAString & aURI,nsIContent * aOwner)410 nsresult nsXMLContentSerializer::PushNameSpaceDecl(const nsAString& aPrefix,
411                                                    const nsAString& aURI,
412                                                    nsIContent* aOwner) {
413   NameSpaceDecl* decl = mNameSpaceStack.AppendElement();
414   if (!decl) return NS_ERROR_OUT_OF_MEMORY;
415 
416   decl->mPrefix.Assign(aPrefix);
417   decl->mURI.Assign(aURI);
418   // Don't addref - this weak reference will be removed when
419   // we pop the stack
420   decl->mOwner = aOwner;
421   return NS_OK;
422 }
423 
PopNameSpaceDeclsFor(nsIContent * aOwner)424 void nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIContent* aOwner) {
425   int32_t index, count;
426 
427   count = mNameSpaceStack.Length();
428   for (index = count - 1; index >= 0; index--) {
429     if (mNameSpaceStack[index].mOwner != aOwner) {
430       break;
431     }
432     mNameSpaceStack.RemoveLastElement();
433   }
434 }
435 
ConfirmPrefix(nsAString & aPrefix,const nsAString & aURI,nsIContent * aElement,bool aIsAttribute)436 bool nsXMLContentSerializer::ConfirmPrefix(nsAString& aPrefix,
437                                            const nsAString& aURI,
438                                            nsIContent* aElement,
439                                            bool aIsAttribute) {
440   if (aPrefix.EqualsLiteral(kXMLNS)) {
441     return false;
442   }
443 
444   if (aURI.EqualsLiteral("http://www.w3.org/XML/1998/namespace")) {
445     // The prefix must be xml for this namespace. We don't need to declare it,
446     // so always just set the prefix to xml.
447     aPrefix.AssignLiteral("xml");
448 
449     return false;
450   }
451 
452   bool mustHavePrefix;
453   if (aIsAttribute) {
454     if (aURI.IsEmpty()) {
455       // Attribute in the null namespace.  This just shouldn't have a prefix.
456       // And there's no need to push any namespace decls
457       aPrefix.Truncate();
458       return false;
459     }
460 
461     // Attribute not in the null namespace -- must have a prefix
462     mustHavePrefix = true;
463   } else {
464     // Not an attribute, so doesn't _have_ to have a prefix
465     mustHavePrefix = false;
466   }
467 
468   // Keep track of the closest prefix that's bound to aURI and whether we've
469   // found such a thing.  closestURIMatch holds the prefix, and uriMatch
470   // indicates whether we actually have one.
471   nsAutoString closestURIMatch;
472   bool uriMatch = false;
473 
474   // Also keep track of whether we've seen aPrefix already.  If we have, that
475   // means that it's already bound to a URI different from aURI, so even if we
476   // later (so in a more outer scope) see it bound to aURI we can't reuse it.
477   bool haveSeenOurPrefix = false;
478 
479   int32_t count = mNameSpaceStack.Length();
480   int32_t index = count - 1;
481   while (index >= 0) {
482     NameSpaceDecl& decl = mNameSpaceStack.ElementAt(index);
483     // Check if we've found a prefix match
484     if (aPrefix.Equals(decl.mPrefix)) {
485       // If the URIs match and aPrefix is not bound to any other URI, we can
486       // use aPrefix
487       if (!haveSeenOurPrefix && aURI.Equals(decl.mURI)) {
488         // Just use our uriMatch stuff.  That will deal with an empty aPrefix
489         // the right way.  We can break out of the loop now, though.
490         uriMatch = true;
491         closestURIMatch = aPrefix;
492         break;
493       }
494 
495       haveSeenOurPrefix = true;
496 
497       // If they don't, and either:
498       // 1) We have a prefix (so we'd be redeclaring this prefix to point to a
499       //    different namespace) or
500       // 2) We're looking at an existing default namespace decl on aElement (so
501       //    we can't create a new default namespace decl for this URI)
502       // then generate a new prefix.  Note that we do NOT generate new prefixes
503       // if we happen to have aPrefix == decl->mPrefix == "" and mismatching
504       // URIs when |decl| doesn't have aElement as its owner.  In that case we
505       // can simply push the new namespace URI as the default namespace for
506       // aElement.
507       if (!aPrefix.IsEmpty() || decl.mOwner == aElement) {
508         NS_ASSERTION(!aURI.IsEmpty(),
509                      "Not allowed to add a xmlns attribute with an empty "
510                      "namespace name unless it declares the default "
511                      "namespace.");
512 
513         GenerateNewPrefix(aPrefix);
514         // Now we need to validate our new prefix/uri combination; check it
515         // against the full namespace stack again.  Note that just restarting
516         // the while loop is ok, since we haven't changed aURI, so the
517         // closestURIMatch and uriMatch state is not affected.
518         index = count - 1;
519         haveSeenOurPrefix = false;
520         continue;
521       }
522     }
523 
524     // If we've found a URI match, then record the first one
525     if (!uriMatch && aURI.Equals(decl.mURI)) {
526       // Need to check that decl->mPrefix is not declared anywhere closer to
527       // us.  If it is, we can't use it.
528       bool prefixOK = true;
529       int32_t index2;
530       for (index2 = count - 1; index2 > index && prefixOK; --index2) {
531         prefixOK = (mNameSpaceStack[index2].mPrefix != decl.mPrefix);
532       }
533 
534       if (prefixOK) {
535         uriMatch = true;
536         closestURIMatch.Assign(decl.mPrefix);
537       }
538     }
539 
540     --index;
541   }
542 
543   // At this point the following invariants hold:
544   // 1) The prefix in closestURIMatch is mapped to aURI in our scope if
545   //    uriMatch is set.
546   // 2) There is nothing on the namespace stack that has aPrefix as the prefix
547   //    and a _different_ URI, except for the case aPrefix.IsEmpty (and
548   //    possible default namespaces on ancestors)
549 
550   // So if uriMatch is set it's OK to use the closestURIMatch prefix.  The one
551   // exception is when closestURIMatch is actually empty (default namespace
552   // decl) and we must have a prefix.
553   if (uriMatch && (!mustHavePrefix || !closestURIMatch.IsEmpty())) {
554     aPrefix.Assign(closestURIMatch);
555     return false;
556   }
557 
558   if (aPrefix.IsEmpty()) {
559     // At this point, aPrefix is empty (which means we never had a prefix to
560     // start with).  If we must have a prefix, just generate a new prefix and
561     // then send it back through the namespace stack checks to make sure it's
562     // OK.
563     if (mustHavePrefix) {
564       GenerateNewPrefix(aPrefix);
565       return ConfirmPrefix(aPrefix, aURI, aElement, aIsAttribute);
566     }
567 
568     // One final special case.  If aPrefix is empty and we never saw an empty
569     // prefix (default namespace decl) on the namespace stack and we're in the
570     // null namespace there is no reason to output an |xmlns=""| here.  It just
571     // makes the output less readable.
572     if (!haveSeenOurPrefix && aURI.IsEmpty()) {
573       return false;
574     }
575   }
576 
577   // Now just set aURI as the new default namespace URI.  Indicate that we need
578   // to create a namespace decl for the final prefix
579   return true;
580 }
581 
GenerateNewPrefix(nsAString & aPrefix)582 void nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix) {
583   aPrefix.Assign('a');
584   aPrefix.AppendInt(mPrefixIndex++);
585 }
586 
SerializeAttr(const nsAString & aPrefix,const nsAString & aName,const nsAString & aValue,nsAString & aStr,bool aDoEscapeEntities)587 bool nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix,
588                                            const nsAString& aName,
589                                            const nsAString& aValue,
590                                            nsAString& aStr,
591                                            bool aDoEscapeEntities) {
592   nsAutoString attrString_;
593   // For innerHTML we can do faster appending without
594   // temporary strings.
595   bool rawAppend = mDoRaw && aDoEscapeEntities;
596   nsAString& attrString = (rawAppend) ? aStr : attrString_;
597 
598   NS_ENSURE_TRUE(attrString.Append(char16_t(' '), mozilla::fallible), false);
599   if (!aPrefix.IsEmpty()) {
600     NS_ENSURE_TRUE(attrString.Append(aPrefix, mozilla::fallible), false);
601     NS_ENSURE_TRUE(attrString.Append(char16_t(':'), mozilla::fallible), false);
602   }
603   NS_ENSURE_TRUE(attrString.Append(aName, mozilla::fallible), false);
604 
605   if (aDoEscapeEntities) {
606     // if problem characters are turned into character entity references
607     // then there will be no problem with the value delimiter characters
608     NS_ENSURE_TRUE(attrString.AppendLiteral("=\"", mozilla::fallible), false);
609 
610     mInAttribute = true;
611     bool result = AppendAndTranslateEntities(aValue, attrString);
612     mInAttribute = false;
613     NS_ENSURE_TRUE(result, false);
614 
615     NS_ENSURE_TRUE(attrString.Append(char16_t('"'), mozilla::fallible), false);
616     if (rawAppend) {
617       return true;
618     }
619   } else {
620     // Depending on whether the attribute value contains quotes or apostrophes
621     // we need to select the delimiter character and escape characters using
622     // character entity references, ignoring the value of aDoEscapeEntities.
623     // See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2.2 for
624     // the standard on character entity references in values.  We also have to
625     // make sure to escape any '&' characters.
626 
627     bool bIncludesSingle = false;
628     bool bIncludesDouble = false;
629     nsAString::const_iterator iCurr, iEnd;
630     aValue.BeginReading(iCurr);
631     aValue.EndReading(iEnd);
632     for (; iCurr != iEnd; ++iCurr) {
633       if (*iCurr == char16_t('\'')) {
634         bIncludesSingle = true;
635         if (bIncludesDouble) {
636           break;
637         }
638       } else if (*iCurr == char16_t('"')) {
639         bIncludesDouble = true;
640         if (bIncludesSingle) {
641           break;
642         }
643       }
644     }
645 
646     // Delimiter and escaping is according to the following table
647     //    bIncludesDouble   bIncludesSingle   Delimiter    Escape Double Quote
648     //    FALSE             FALSE             "            FALSE
649     //    FALSE             TRUE              "            FALSE
650     //    TRUE              FALSE             '            FALSE
651     //    TRUE              TRUE              "            TRUE
652     char16_t cDelimiter =
653         (bIncludesDouble && !bIncludesSingle) ? char16_t('\'') : char16_t('"');
654     NS_ENSURE_TRUE(attrString.Append(char16_t('='), mozilla::fallible), false);
655     NS_ENSURE_TRUE(attrString.Append(cDelimiter, mozilla::fallible), false);
656     nsAutoString sValue(aValue);
657     NS_ENSURE_TRUE(
658         sValue.ReplaceSubstring(u"&"_ns, u"&amp;"_ns, mozilla::fallible),
659         false);
660     if (bIncludesDouble && bIncludesSingle) {
661       NS_ENSURE_TRUE(
662           sValue.ReplaceSubstring(u"\""_ns, u"&quot;"_ns, mozilla::fallible),
663           false);
664     }
665     NS_ENSURE_TRUE(attrString.Append(sValue, mozilla::fallible), false);
666     NS_ENSURE_TRUE(attrString.Append(cDelimiter, mozilla::fallible), false);
667   }
668   if (mDoRaw || PreLevel() > 0) {
669     NS_ENSURE_TRUE(AppendToStringConvertLF(attrString, aStr), false);
670   } else if (mDoFormat) {
671     NS_ENSURE_TRUE(AppendToStringFormatedWrapped(attrString, aStr), false);
672   } else if (mDoWrap) {
673     NS_ENSURE_TRUE(AppendToStringWrapped(attrString, aStr), false);
674   } else {
675     NS_ENSURE_TRUE(AppendToStringConvertLF(attrString, aStr), false);
676   }
677 
678   return true;
679 }
680 
ScanNamespaceDeclarations(Element * aElement,Element * aOriginalElement,const nsAString & aTagNamespaceURI)681 uint32_t nsXMLContentSerializer::ScanNamespaceDeclarations(
682     Element* aElement, Element* aOriginalElement,
683     const nsAString& aTagNamespaceURI) {
684   uint32_t index, count;
685   nsAutoString uriStr, valueStr;
686 
687   count = aElement->GetAttrCount();
688 
689   // First scan for namespace declarations, pushing each on the stack
690   uint32_t skipAttr = count;
691   for (index = 0; index < count; index++) {
692     const BorrowedAttrInfo info = aElement->GetAttrInfoAt(index);
693     const nsAttrName* name = info.mName;
694 
695     int32_t namespaceID = name->NamespaceID();
696     nsAtom* attrName = name->LocalName();
697 
698     if (namespaceID == kNameSpaceID_XMLNS ||
699         // Also push on the stack attrs named "xmlns" in the null
700         // namespace... because once we serialize those out they'll look like
701         // namespace decls.  :(
702         // XXXbz what if we have both "xmlns" in the null namespace and "xmlns"
703         // in the xmlns namespace?
704         (namespaceID == kNameSpaceID_None && attrName == nsGkAtoms::xmlns)) {
705       info.mValue->ToString(uriStr);
706 
707       if (!name->GetPrefix()) {
708         if (aTagNamespaceURI.IsEmpty() && !uriStr.IsEmpty()) {
709           // If the element is in no namespace we need to add a xmlns
710           // attribute to declare that. That xmlns attribute must not have a
711           // prefix (see http://www.w3.org/TR/REC-xml-names/#dt-prefix), ie it
712           // must declare the default namespace. We just found an xmlns
713           // attribute that declares the default namespace to something
714           // non-empty. We're going to ignore this attribute, for children we
715           // will detect that we need to add it again and attributes aren't
716           // affected by the default namespace.
717           skipAttr = index;
718         } else {
719           // Default NS attribute does not have prefix (and the name is "xmlns")
720           PushNameSpaceDecl(u""_ns, uriStr, aOriginalElement);
721         }
722       } else {
723         PushNameSpaceDecl(nsDependentAtomString(attrName), uriStr,
724                           aOriginalElement);
725       }
726     }
727   }
728   return skipAttr;
729 }
730 
IsJavaScript(nsIContent * aContent,nsAtom * aAttrNameAtom,int32_t aAttrNamespaceID,const nsAString & aValueString)731 bool nsXMLContentSerializer::IsJavaScript(nsIContent* aContent,
732                                           nsAtom* aAttrNameAtom,
733                                           int32_t aAttrNamespaceID,
734                                           const nsAString& aValueString) {
735   bool isHtml = aContent->IsHTMLElement();
736   bool isXul = aContent->IsXULElement();
737   bool isSvg = aContent->IsSVGElement();
738 
739   if (aAttrNamespaceID == kNameSpaceID_None && (isHtml || isXul || isSvg) &&
740       (aAttrNameAtom == nsGkAtoms::href || aAttrNameAtom == nsGkAtoms::src)) {
741     static const char kJavaScript[] = "javascript";
742     int32_t pos = aValueString.FindChar(':');
743     if (pos < (int32_t)(sizeof kJavaScript - 1)) return false;
744     nsAutoString scheme(Substring(aValueString, 0, pos));
745     scheme.StripWhitespace();
746     if ((scheme.Length() == (sizeof kJavaScript - 1)) &&
747         scheme.EqualsIgnoreCase(kJavaScript))
748       return true;
749     else
750       return false;
751   }
752 
753   return aContent->IsEventAttributeName(aAttrNameAtom);
754 }
755 
SerializeAttributes(Element * aElement,Element * aOriginalElement,nsAString & aTagPrefix,const nsAString & aTagNamespaceURI,nsAtom * aTagName,nsAString & aStr,uint32_t aSkipAttr,bool aAddNSAttr)756 bool nsXMLContentSerializer::SerializeAttributes(
757     Element* aElement, Element* aOriginalElement, nsAString& aTagPrefix,
758     const nsAString& aTagNamespaceURI, nsAtom* aTagName, nsAString& aStr,
759     uint32_t aSkipAttr, bool aAddNSAttr) {
760   nsAutoString prefixStr, uriStr, valueStr;
761   nsAutoString xmlnsStr;
762   xmlnsStr.AssignLiteral(kXMLNS);
763   uint32_t index, count;
764 
765   MaybeSerializeIsValue(aElement, aStr);
766 
767   // If we had to add a new namespace declaration, serialize
768   // and push it on the namespace stack
769   if (aAddNSAttr) {
770     if (aTagPrefix.IsEmpty()) {
771       // Serialize default namespace decl
772       NS_ENSURE_TRUE(
773           SerializeAttr(u""_ns, xmlnsStr, aTagNamespaceURI, aStr, true), false);
774     } else {
775       // Serialize namespace decl
776       NS_ENSURE_TRUE(
777           SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true),
778           false);
779     }
780     PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement);
781   }
782 
783   count = aElement->GetAttrCount();
784 
785   // Now serialize each of the attributes
786   // XXX Unfortunately we need a namespace manager to get
787   // attribute URIs.
788   for (index = 0; index < count; index++) {
789     if (aSkipAttr == index) {
790       continue;
791     }
792 
793     const nsAttrName* name = aElement->GetAttrNameAt(index);
794     int32_t namespaceID = name->NamespaceID();
795     nsAtom* attrName = name->LocalName();
796     nsAtom* attrPrefix = name->GetPrefix();
797 
798     // Filter out any attribute starting with [-|_]moz
799     nsDependentAtomString attrNameStr(attrName);
800     if (StringBeginsWith(attrNameStr, u"_moz"_ns) ||
801         StringBeginsWith(attrNameStr, u"-moz"_ns)) {
802       continue;
803     }
804 
805     if (attrPrefix) {
806       attrPrefix->ToString(prefixStr);
807     } else {
808       prefixStr.Truncate();
809     }
810 
811     bool addNSAttr = false;
812     if (kNameSpaceID_XMLNS != namespaceID) {
813       nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr);
814       addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true);
815     }
816 
817     aElement->GetAttr(namespaceID, attrName, valueStr);
818 
819     nsDependentAtomString nameStr(attrName);
820     bool isJS = IsJavaScript(aElement, attrName, namespaceID, valueStr);
821 
822     NS_ENSURE_TRUE(SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS),
823                    false);
824 
825     if (addNSAttr) {
826       NS_ASSERTION(!prefixStr.IsEmpty(),
827                    "Namespaced attributes must have a prefix");
828       NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true),
829                      false);
830       PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement);
831     }
832   }
833 
834   return true;
835 }
836 
837 NS_IMETHODIMP
AppendElementStart(Element * aElement,Element * aOriginalElement)838 nsXMLContentSerializer::AppendElementStart(Element* aElement,
839                                            Element* aOriginalElement) {
840   NS_ENSURE_ARG(aElement);
841   NS_ENSURE_STATE(mOutput);
842 
843   bool forceFormat = false;
844   nsresult rv = NS_OK;
845   if (!CheckElementStart(aElement, forceFormat, *mOutput, rv)) {
846     // When we go to AppendElementEnd for this element, we're going to
847     // MaybeLeaveFromPreContent().  So make sure to MaybeEnterInPreContent()
848     // now, so our PreLevel() doesn't get confused.
849     MaybeEnterInPreContent(aElement);
850     return rv;
851   }
852 
853   NS_ENSURE_SUCCESS(rv, rv);
854 
855   nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
856   aElement->NodeInfo()->GetPrefix(tagPrefix);
857   aElement->NodeInfo()->GetName(tagLocalName);
858   aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
859 
860   uint32_t skipAttr =
861       ScanNamespaceDeclarations(aElement, aOriginalElement, tagNamespaceURI);
862 
863   nsAtom* name = aElement->NodeInfo()->NameAtom();
864   bool lineBreakBeforeOpen =
865       LineBreakBeforeOpen(aElement->GetNameSpaceID(), name);
866 
867   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
868     if (mColPos && lineBreakBeforeOpen) {
869       NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
870     } else {
871       NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
872                      NS_ERROR_OUT_OF_MEMORY);
873     }
874     if (!mColPos) {
875       NS_ENSURE_TRUE(AppendIndentation(*mOutput), NS_ERROR_OUT_OF_MEMORY);
876     } else if (mAddSpace) {
877       NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
878                      NS_ERROR_OUT_OF_MEMORY);
879       mAddSpace = false;
880     }
881   } else if (mAddSpace) {
882     NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
883                    NS_ERROR_OUT_OF_MEMORY);
884     mAddSpace = false;
885   } else {
886     NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
887                    NS_ERROR_OUT_OF_MEMORY);
888   }
889 
890   // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode
891   // wasn't called
892   mAddNewlineForRootNode = false;
893 
894   bool addNSAttr;
895   addNSAttr =
896       ConfirmPrefix(tagPrefix, tagNamespaceURI, aOriginalElement, false);
897 
898   // Serialize the qualified name of the element
899   NS_ENSURE_TRUE(AppendToString(kLessThan, *mOutput), NS_ERROR_OUT_OF_MEMORY);
900   if (!tagPrefix.IsEmpty()) {
901     NS_ENSURE_TRUE(AppendToString(tagPrefix, *mOutput), NS_ERROR_OUT_OF_MEMORY);
902     NS_ENSURE_TRUE(AppendToString(u":"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
903   }
904   NS_ENSURE_TRUE(AppendToString(tagLocalName, *mOutput),
905                  NS_ERROR_OUT_OF_MEMORY);
906 
907   MaybeEnterInPreContent(aElement);
908 
909   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
910     NS_ENSURE_TRUE(IncrIndentation(name), NS_ERROR_OUT_OF_MEMORY);
911   }
912 
913   NS_ENSURE_TRUE(
914       SerializeAttributes(aElement, aOriginalElement, tagPrefix,
915                           tagNamespaceURI, name, *mOutput, skipAttr, addNSAttr),
916       NS_ERROR_OUT_OF_MEMORY);
917 
918   NS_ENSURE_TRUE(AppendEndOfElementStart(aElement, aOriginalElement, *mOutput),
919                  NS_ERROR_OUT_OF_MEMORY);
920 
921   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
922       LineBreakAfterOpen(aElement->GetNameSpaceID(), name)) {
923     NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
924   }
925 
926   NS_ENSURE_TRUE(AfterElementStart(aElement, aOriginalElement, *mOutput),
927                  NS_ERROR_OUT_OF_MEMORY);
928 
929   return NS_OK;
930 }
931 
932 // aElement is the actual element we're outputting.  aOriginalElement is the one
933 // in the original DOM, which is the one we have to test for kids.
ElementNeedsSeparateEndTag(Element * aElement,Element * aOriginalElement)934 static bool ElementNeedsSeparateEndTag(Element* aElement,
935                                        Element* aOriginalElement) {
936   if (aOriginalElement->GetChildCount()) {
937     // We have kids, so we need a separate end tag.  This needs to be checked on
938     // aOriginalElement because that's the one that's actually in the DOM and
939     // might have kids.
940     return true;
941   }
942 
943   if (!aElement->IsHTMLElement()) {
944     // Empty non-HTML elements can just skip a separate end tag.
945     return false;
946   }
947 
948   // HTML container tags should have a separate end tag even if empty, per spec.
949   // See
950   // https://w3c.github.io/DOM-Parsing/#dfn-concept-xml-serialization-algorithm
951   nsAtom* localName = aElement->NodeInfo()->NameAtom();
952   bool isHTMLContainer = nsHTMLElement::IsContainer(
953       nsHTMLTags::CaseSensitiveAtomTagToId(localName));
954   return isHTMLContainer;
955 }
956 
AppendEndOfElementStart(Element * aElement,Element * aOriginalElement,nsAString & aStr)957 bool nsXMLContentSerializer::AppendEndOfElementStart(Element* aElement,
958                                                      Element* aOriginalElement,
959                                                      nsAString& aStr) {
960   if (ElementNeedsSeparateEndTag(aElement, aOriginalElement)) {
961     return AppendToString(kGreaterThan, aStr);
962   }
963 
964   // We don't need a separate end tag.  For HTML elements (which at this point
965   // must be non-containers), append a space before the '/', per spec.  See
966   // https://w3c.github.io/DOM-Parsing/#dfn-concept-xml-serialization-algorithm
967   if (aOriginalElement->IsHTMLElement()) {
968     if (!AppendToString(kSpace, aStr)) {
969       return false;
970     }
971   }
972 
973   return AppendToString(u"/>"_ns, aStr);
974 }
975 
976 NS_IMETHODIMP
AppendElementEnd(Element * aElement,Element * aOriginalElement)977 nsXMLContentSerializer::AppendElementEnd(Element* aElement,
978                                          Element* aOriginalElement) {
979   NS_ENSURE_ARG(aElement);
980   NS_ENSURE_STATE(mOutput);
981 
982   nsIContent* content = aElement;
983 
984   bool forceFormat = false, outputElementEnd;
985   outputElementEnd =
986       CheckElementEnd(aElement, aOriginalElement, forceFormat, *mOutput);
987 
988   nsAtom* name = content->NodeInfo()->NameAtom();
989 
990   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
991     DecrIndentation(name);
992   }
993 
994   if (!outputElementEnd) {
995     // Keep this in sync with the cleanup at the end of this method.
996     PopNameSpaceDeclsFor(aElement);
997     MaybeLeaveFromPreContent(content);
998     MaybeFlagNewlineForRootNode(aElement);
999     AfterElementEnd(content, *mOutput);
1000     return NS_OK;
1001   }
1002 
1003   nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
1004 
1005   aElement->NodeInfo()->GetPrefix(tagPrefix);
1006   aElement->NodeInfo()->GetName(tagLocalName);
1007   aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
1008 
1009 #ifdef DEBUG
1010   bool debugNeedToPushNamespace =
1011 #endif
1012       ConfirmPrefix(tagPrefix, tagNamespaceURI, aElement, false);
1013   NS_ASSERTION(!debugNeedToPushNamespace,
1014                "Can't push namespaces in closing tag!");
1015 
1016   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
1017     bool lineBreakBeforeClose =
1018         LineBreakBeforeClose(content->GetNameSpaceID(), name);
1019 
1020     if (mColPos && lineBreakBeforeClose) {
1021       NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
1022     }
1023     if (!mColPos) {
1024       NS_ENSURE_TRUE(AppendIndentation(*mOutput), NS_ERROR_OUT_OF_MEMORY);
1025     } else if (mAddSpace) {
1026       NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
1027                      NS_ERROR_OUT_OF_MEMORY);
1028       mAddSpace = false;
1029     }
1030   } else if (mAddSpace) {
1031     NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
1032                    NS_ERROR_OUT_OF_MEMORY);
1033     mAddSpace = false;
1034   }
1035 
1036   NS_ENSURE_TRUE(AppendToString(kEndTag, *mOutput), NS_ERROR_OUT_OF_MEMORY);
1037   if (!tagPrefix.IsEmpty()) {
1038     NS_ENSURE_TRUE(AppendToString(tagPrefix, *mOutput), NS_ERROR_OUT_OF_MEMORY);
1039     NS_ENSURE_TRUE(AppendToString(u":"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
1040   }
1041   NS_ENSURE_TRUE(AppendToString(tagLocalName, *mOutput),
1042                  NS_ERROR_OUT_OF_MEMORY);
1043   NS_ENSURE_TRUE(AppendToString(kGreaterThan, *mOutput),
1044                  NS_ERROR_OUT_OF_MEMORY);
1045 
1046   // Keep what follows in sync with the cleanup in the !outputElementEnd case.
1047   PopNameSpaceDeclsFor(aElement);
1048 
1049   MaybeLeaveFromPreContent(content);
1050 
1051   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
1052       LineBreakAfterClose(content->GetNameSpaceID(), name)) {
1053     NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
1054   } else {
1055     MaybeFlagNewlineForRootNode(aElement);
1056   }
1057 
1058   AfterElementEnd(content, *mOutput);
1059 
1060   return NS_OK;
1061 }
1062 
1063 NS_IMETHODIMP
Finish()1064 nsXMLContentSerializer::Finish() {
1065   NS_ENSURE_STATE(mOutput);
1066 
1067   mOutput = nullptr;
1068 
1069   return NS_OK;
1070 }
1071 
1072 NS_IMETHODIMP
GetOutputLength(uint32_t & aLength) const1073 nsXMLContentSerializer::GetOutputLength(uint32_t& aLength) const {
1074   NS_ENSURE_STATE(mOutput);
1075 
1076   aLength = mOutput->Length();
1077 
1078   return NS_OK;
1079 }
1080 
1081 NS_IMETHODIMP
AppendDocumentStart(Document * aDocument)1082 nsXMLContentSerializer::AppendDocumentStart(Document* aDocument) {
1083   NS_ENSURE_ARG_POINTER(aDocument);
1084   NS_ENSURE_STATE(mOutput);
1085 
1086   nsAutoString version, encoding, standalone;
1087   aDocument->GetXMLDeclaration(version, encoding, standalone);
1088 
1089   if (version.IsEmpty())
1090     return NS_OK;  // A declaration must have version, or there is no decl
1091 
1092   constexpr auto endQuote = u"\""_ns;
1093 
1094   *mOutput += u"<?xml version=\""_ns + version + endQuote;
1095 
1096   if (!mCharset.IsEmpty()) {
1097     *mOutput +=
1098         u" encoding=\""_ns + NS_ConvertASCIItoUTF16(mCharset) + endQuote;
1099   }
1100   // Otherwise just don't output an encoding attr.  Not that we expect
1101   // mCharset to ever be empty.
1102 #ifdef DEBUG
1103   else {
1104     NS_WARNING("Empty mCharset?  How come?");
1105   }
1106 #endif
1107 
1108   if (!standalone.IsEmpty()) {
1109     *mOutput += u" standalone=\""_ns + standalone + endQuote;
1110   }
1111 
1112   NS_ENSURE_TRUE(mOutput->AppendLiteral("?>", mozilla::fallible),
1113                  NS_ERROR_OUT_OF_MEMORY);
1114   mAddNewlineForRootNode = true;
1115 
1116   return NS_OK;
1117 }
1118 
CheckElementStart(Element *,bool & aForceFormat,nsAString & aStr,nsresult & aResult)1119 bool nsXMLContentSerializer::CheckElementStart(Element*, bool& aForceFormat,
1120                                                nsAString& aStr,
1121                                                nsresult& aResult) {
1122   aResult = NS_OK;
1123   aForceFormat = false;
1124   return true;
1125 }
1126 
CheckElementEnd(Element * aElement,Element * aOriginalElement,bool & aForceFormat,nsAString & aStr)1127 bool nsXMLContentSerializer::CheckElementEnd(Element* aElement,
1128                                              Element* aOriginalElement,
1129                                              bool& aForceFormat,
1130                                              nsAString& aStr) {
1131   // We don't output a separate end tag for empty element
1132   aForceFormat = false;
1133   return ElementNeedsSeparateEndTag(aElement, aOriginalElement);
1134 }
1135 
AppendToString(const char16_t aChar,nsAString & aOutputStr)1136 bool nsXMLContentSerializer::AppendToString(const char16_t aChar,
1137                                             nsAString& aOutputStr) {
1138   if (mBodyOnly && !mInBody) {
1139     return true;
1140   }
1141   mColPos += 1;
1142   return aOutputStr.Append(aChar, mozilla::fallible);
1143 }
1144 
AppendToString(const nsAString & aStr,nsAString & aOutputStr)1145 bool nsXMLContentSerializer::AppendToString(const nsAString& aStr,
1146                                             nsAString& aOutputStr) {
1147   if (mBodyOnly && !mInBody) {
1148     return true;
1149   }
1150   mColPos += aStr.Length();
1151   return aOutputStr.Append(aStr, mozilla::fallible);
1152 }
1153 
1154 #define _ 0
1155 
1156 // This table indexes into kEntityStrings[].
1157 const uint8_t nsXMLContentSerializer::kEntities[] = {
1158     // clang-format off
1159   _, _, _, _, _, _, _, _, _, _,
1160   _, _, _, _, _, _, _, _, _, _,
1161   _, _, _, _, _, _, _, _, _, _,
1162   _, _, _, _, _, _, _, _, 2, _,
1163   _, _, _, _, _, _, _, _, _, _,
1164   _, _, _, _, _, _, _, _, _, _,
1165   3, _, 4
1166     // clang-format on
1167 };
1168 
1169 // This table indexes into kEntityStrings[].
1170 const uint8_t nsXMLContentSerializer::kAttrEntities[] = {
1171     // clang-format off
1172   _, _, _, _, _, _, _, _, _, 5,
1173   6, _, _, 7, _, _, _, _, _, _,
1174   _, _, _, _, _, _, _, _, _, _,
1175   _, _, _, _, 1, _, _, _, 2, _,
1176   _, _, _, _, _, _, _, _, _, _,
1177   _, _, _, _, _, _, _, _, _, _,
1178   3, _, 4
1179     // clang-format on
1180 };
1181 
1182 #undef _
1183 
1184 const char* const nsXMLContentSerializer::kEntityStrings[] = {
1185     /* 0 */ nullptr,
1186     /* 1 */ "&quot;",
1187     /* 2 */ "&amp;",
1188     /* 3 */ "&lt;",
1189     /* 4 */ "&gt;",
1190     /* 5 */ "&#9;",
1191     /* 6 */ "&#xA;",
1192     /* 7 */ "&#xD;",
1193 };
1194 
AppendAndTranslateEntities(const nsAString & aStr,nsAString & aOutputStr)1195 bool nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
1196                                                         nsAString& aOutputStr) {
1197   if (mInAttribute) {
1198     return AppendAndTranslateEntities<kGTVal>(aStr, aOutputStr, kAttrEntities,
1199                                               kEntityStrings);
1200   }
1201 
1202   return AppendAndTranslateEntities<kGTVal>(aStr, aOutputStr, kEntities,
1203                                             kEntityStrings);
1204 }
1205 
1206 /* static */
AppendAndTranslateEntities(const nsAString & aStr,nsAString & aOutputStr,const uint8_t aEntityTable[],uint16_t aMaxTableIndex,const char * const aStringTable[])1207 bool nsXMLContentSerializer::AppendAndTranslateEntities(
1208     const nsAString& aStr, nsAString& aOutputStr, const uint8_t aEntityTable[],
1209     uint16_t aMaxTableIndex, const char* const aStringTable[]) {
1210   nsReadingIterator<char16_t> done_reading;
1211   aStr.EndReading(done_reading);
1212 
1213   // for each chunk of |aString|...
1214   uint32_t advanceLength = 0;
1215   nsReadingIterator<char16_t> iter;
1216 
1217   for (aStr.BeginReading(iter); iter != done_reading;
1218        iter.advance(int32_t(advanceLength))) {
1219     uint32_t fragmentLength = done_reading - iter;
1220     const char16_t* c = iter.get();
1221     const char16_t* fragmentStart = c;
1222     const char16_t* fragmentEnd = c + fragmentLength;
1223     const char* entityText = nullptr;
1224 
1225     advanceLength = 0;
1226     // for each character in this chunk, check if it
1227     // needs to be replaced
1228     for (; c < fragmentEnd; c++, advanceLength++) {
1229       char16_t val = *c;
1230       if ((val <= aMaxTableIndex) && aEntityTable[val]) {
1231         entityText = aStringTable[aEntityTable[val]];
1232         break;
1233       }
1234     }
1235 
1236     NS_ENSURE_TRUE(
1237         aOutputStr.Append(fragmentStart, advanceLength, mozilla::fallible),
1238         false);
1239     if (entityText) {
1240       NS_ENSURE_TRUE(AppendASCIItoUTF16(mozilla::MakeStringSpan(entityText),
1241                                         aOutputStr, mozilla::fallible),
1242                      false);
1243       advanceLength++;
1244     }
1245   }
1246 
1247   return true;
1248 }
1249 
MaybeAddNewlineForRootNode(nsAString & aStr)1250 bool nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString& aStr) {
1251   if (mAddNewlineForRootNode) {
1252     return AppendNewLineToString(aStr);
1253   }
1254 
1255   return true;
1256 }
1257 
MaybeFlagNewlineForRootNode(nsINode * aNode)1258 void nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode* aNode) {
1259   nsINode* parent = aNode->GetParentNode();
1260   if (parent) {
1261     mAddNewlineForRootNode = parent->IsDocument();
1262   }
1263 }
1264 
MaybeEnterInPreContent(nsIContent * aNode)1265 void nsXMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode) {
1266   // support of the xml:space attribute
1267   nsAutoString space;
1268   if (ShouldMaintainPreLevel() && aNode->IsElement() &&
1269       aNode->AsElement()->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space) &&
1270       space.EqualsLiteral("preserve")) {
1271     ++PreLevel();
1272   }
1273 }
1274 
MaybeLeaveFromPreContent(nsIContent * aNode)1275 void nsXMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode) {
1276   // support of the xml:space attribute
1277   nsAutoString space;
1278   if (ShouldMaintainPreLevel() && aNode->IsElement() &&
1279       aNode->AsElement()->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space) &&
1280       space.EqualsLiteral("preserve")) {
1281     --PreLevel();
1282   }
1283 }
1284 
AppendNewLineToString(nsAString & aStr)1285 bool nsXMLContentSerializer::AppendNewLineToString(nsAString& aStr) {
1286   bool result = AppendToString(mLineBreak, aStr);
1287   mMayIgnoreLineBreakSequence = true;
1288   mColPos = 0;
1289   mAddSpace = false;
1290   mIsIndentationAddedOnCurrentLine = false;
1291   return result;
1292 }
1293 
AppendIndentation(nsAString & aStr)1294 bool nsXMLContentSerializer::AppendIndentation(nsAString& aStr) {
1295   mIsIndentationAddedOnCurrentLine = true;
1296   bool result = AppendToString(mIndent, aStr);
1297   mAddSpace = false;
1298   mMayIgnoreLineBreakSequence = false;
1299   return result;
1300 }
1301 
IncrIndentation(nsAtom * aName)1302 bool nsXMLContentSerializer::IncrIndentation(nsAtom* aName) {
1303   // we want to keep the source readable
1304   if (mDoWrap &&
1305       mIndent.Length() >= uint32_t(mMaxColumn) - MIN_INDENTED_LINE_LENGTH) {
1306     ++mIndentOverflow;
1307   } else {
1308     return mIndent.AppendLiteral(INDENT_STRING, mozilla::fallible);
1309   }
1310 
1311   return true;
1312 }
1313 
DecrIndentation(nsAtom * aName)1314 void nsXMLContentSerializer::DecrIndentation(nsAtom* aName) {
1315   if (mIndentOverflow)
1316     --mIndentOverflow;
1317   else
1318     mIndent.Cut(0, INDENT_STRING_LENGTH);
1319 }
1320 
LineBreakBeforeOpen(int32_t aNamespaceID,nsAtom * aName)1321 bool nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID,
1322                                                  nsAtom* aName) {
1323   return mAddSpace;
1324 }
1325 
LineBreakAfterOpen(int32_t aNamespaceID,nsAtom * aName)1326 bool nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID,
1327                                                 nsAtom* aName) {
1328   return false;
1329 }
1330 
LineBreakBeforeClose(int32_t aNamespaceID,nsAtom * aName)1331 bool nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID,
1332                                                   nsAtom* aName) {
1333   return mAddSpace;
1334 }
1335 
LineBreakAfterClose(int32_t aNamespaceID,nsAtom * aName)1336 bool nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID,
1337                                                  nsAtom* aName) {
1338   return false;
1339 }
1340 
AppendToStringConvertLF(const nsAString & aStr,nsAString & aOutputStr)1341 bool nsXMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr,
1342                                                      nsAString& aOutputStr) {
1343   if (mBodyOnly && !mInBody) {
1344     return true;
1345   }
1346 
1347   if (mDoRaw) {
1348     NS_ENSURE_TRUE(AppendToString(aStr, aOutputStr), false);
1349   } else {
1350     // Convert line-endings to mLineBreak
1351     uint32_t start = 0;
1352     uint32_t theLen = aStr.Length();
1353     while (start < theLen) {
1354       int32_t eol = aStr.FindChar('\n', start);
1355       if (eol == kNotFound) {
1356         nsDependentSubstring dataSubstring(aStr, start, theLen - start);
1357         NS_ENSURE_TRUE(AppendToString(dataSubstring, aOutputStr), false);
1358         start = theLen;
1359         // if there was a line break before this substring
1360         // AppendNewLineToString was called, so we should reverse
1361         // this flag
1362         mMayIgnoreLineBreakSequence = false;
1363       } else {
1364         nsDependentSubstring dataSubstring(aStr, start, eol - start);
1365         NS_ENSURE_TRUE(AppendToString(dataSubstring, aOutputStr), false);
1366         NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1367         start = eol + 1;
1368       }
1369     }
1370   }
1371 
1372   return true;
1373 }
1374 
AppendFormatedWrapped_WhitespaceSequence(nsAString::const_char_iterator & aPos,const nsAString::const_char_iterator aEnd,const nsAString::const_char_iterator aSequenceStart,bool & aMayIgnoreStartOfLineWhitespaceSequence,nsAString & aOutputStr)1375 bool nsXMLContentSerializer::AppendFormatedWrapped_WhitespaceSequence(
1376     nsAString::const_char_iterator& aPos,
1377     const nsAString::const_char_iterator aEnd,
1378     const nsAString::const_char_iterator aSequenceStart,
1379     bool& aMayIgnoreStartOfLineWhitespaceSequence, nsAString& aOutputStr) {
1380   // Handle the complete sequence of whitespace.
1381   // Continue to iterate until we find the first non-whitespace char.
1382   // Updates "aPos" to point to the first unhandled char.
1383   // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1384   // as well as the other "global" state flags.
1385 
1386   bool sawBlankOrTab = false;
1387   bool leaveLoop = false;
1388 
1389   do {
1390     switch (*aPos) {
1391       case ' ':
1392       case '\t':
1393         sawBlankOrTab = true;
1394         [[fallthrough]];
1395       case '\n':
1396         ++aPos;
1397         // do not increase mColPos,
1398         // because we will reduce the whitespace to a single char
1399         break;
1400       default:
1401         leaveLoop = true;
1402         break;
1403     }
1404   } while (!leaveLoop && aPos < aEnd);
1405 
1406   if (mAddSpace) {
1407     // if we had previously been asked to add space,
1408     // our situation has not changed
1409   } else if (!sawBlankOrTab && mMayIgnoreLineBreakSequence) {
1410     // nothing to do in the case where line breaks have already been added
1411     // before the call of AppendToStringWrapped
1412     // and only if we found line break in the sequence
1413     mMayIgnoreLineBreakSequence = false;
1414   } else if (aMayIgnoreStartOfLineWhitespaceSequence) {
1415     // nothing to do
1416     aMayIgnoreStartOfLineWhitespaceSequence = false;
1417   } else {
1418     if (sawBlankOrTab) {
1419       if (mDoWrap && mColPos + 1 >= mMaxColumn) {
1420         // no much sense in delaying, we only have one slot left,
1421         // let's write a break now
1422         bool result = aOutputStr.Append(mLineBreak, mozilla::fallible);
1423         mColPos = 0;
1424         mIsIndentationAddedOnCurrentLine = false;
1425         mMayIgnoreLineBreakSequence = true;
1426         NS_ENSURE_TRUE(result, false);
1427       } else {
1428         // do not write out yet, we may write out either a space or a linebreak
1429         // let's delay writing it out until we know more
1430         mAddSpace = true;
1431         ++mColPos;  // eat a slot of available space
1432       }
1433     } else {
1434       // Asian text usually does not contain spaces, therefore we should not
1435       // transform a linebreak into a space.
1436       // Since we only saw linebreaks, but no spaces or tabs,
1437       // let's write a linebreak now.
1438       NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1439     }
1440   }
1441 
1442   return true;
1443 }
1444 
AppendWrapped_NonWhitespaceSequence(nsAString::const_char_iterator & aPos,const nsAString::const_char_iterator aEnd,const nsAString::const_char_iterator aSequenceStart,bool & aMayIgnoreStartOfLineWhitespaceSequence,bool & aSequenceStartAfterAWhiteSpace,nsAString & aOutputStr)1445 bool nsXMLContentSerializer::AppendWrapped_NonWhitespaceSequence(
1446     nsAString::const_char_iterator& aPos,
1447     const nsAString::const_char_iterator aEnd,
1448     const nsAString::const_char_iterator aSequenceStart,
1449     bool& aMayIgnoreStartOfLineWhitespaceSequence,
1450     bool& aSequenceStartAfterAWhiteSpace, nsAString& aOutputStr) {
1451   mMayIgnoreLineBreakSequence = false;
1452   aMayIgnoreStartOfLineWhitespaceSequence = false;
1453 
1454   // Handle the complete sequence of non-whitespace in this block
1455   // Iterate until we find the first whitespace char or an aEnd condition
1456   // Updates "aPos" to point to the first unhandled char.
1457   // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1458   // as well as the other "global" state flags.
1459 
1460   bool thisSequenceStartsAtBeginningOfLine = !mColPos;
1461   bool onceAgainBecauseWeAddedBreakInFront = false;
1462   bool foundWhitespaceInLoop;
1463   uint32_t length, colPos;
1464 
1465   do {
1466     if (mColPos) {
1467       colPos = mColPos;
1468     } else {
1469       if (mDoFormat && !mDoRaw && !PreLevel() &&
1470           !onceAgainBecauseWeAddedBreakInFront) {
1471         colPos = mIndent.Length();
1472       } else
1473         colPos = 0;
1474     }
1475     foundWhitespaceInLoop = false;
1476     length = 0;
1477     // we iterate until the next whitespace character
1478     // or until we reach the maximum of character per line
1479     // or until the end of the string to add.
1480     do {
1481       if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1482         foundWhitespaceInLoop = true;
1483         break;
1484       }
1485 
1486       ++aPos;
1487       ++length;
1488     } while ((!mDoWrap || colPos + length < mMaxColumn) && aPos < aEnd);
1489 
1490     // in the case we don't reached the end of the string, but we reached the
1491     // maxcolumn, we see if there is a whitespace after the maxcolumn if yes,
1492     // then we can append directly the string instead of appending a new line
1493     // etc.
1494     if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1495       foundWhitespaceInLoop = true;
1496     }
1497 
1498     if (aPos == aEnd || foundWhitespaceInLoop) {
1499       // there is enough room for the complete block we found
1500       if (mDoFormat && !mColPos) {
1501         NS_ENSURE_TRUE(AppendIndentation(aOutputStr), false);
1502       } else if (mAddSpace) {
1503         bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
1504         mAddSpace = false;
1505         NS_ENSURE_TRUE(result, false);
1506       }
1507 
1508       mColPos += length;
1509       NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, aPos - aSequenceStart,
1510                                        mozilla::fallible),
1511                      false);
1512 
1513       // We have not yet reached the max column, we will continue to
1514       // fill the current line in the next outer loop iteration
1515       // (this one in AppendToStringWrapped)
1516       // make sure we return in this outer loop
1517       onceAgainBecauseWeAddedBreakInFront = false;
1518     } else {  // we reach the max column
1519       if (!thisSequenceStartsAtBeginningOfLine &&
1520           (mAddSpace || (!mDoFormat && aSequenceStartAfterAWhiteSpace))) {
1521         // when !mDoFormat, mAddSpace is not used, mAddSpace is always false
1522         // so, in the case where mDoWrap && !mDoFormat, if we want to enter in
1523         // this condition...
1524 
1525         // We can avoid to wrap. We try to add the whole block
1526         // in an empty new line
1527 
1528         NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1529         aPos = aSequenceStart;
1530         thisSequenceStartsAtBeginningOfLine = true;
1531         onceAgainBecauseWeAddedBreakInFront = true;
1532       } else {
1533         // we must wrap
1534         onceAgainBecauseWeAddedBreakInFront = false;
1535         bool foundWrapPosition = false;
1536         int32_t wrapPosition = 0;
1537 
1538         if (mAllowLineBreaking) {
1539           mozilla::intl::LineBreaker* lineBreaker =
1540               nsContentUtils::LineBreaker();
1541 
1542           wrapPosition =
1543               lineBreaker->Prev(aSequenceStart, (aEnd - aSequenceStart),
1544                                 (aPos - aSequenceStart) + 1);
1545           if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
1546             foundWrapPosition = true;
1547           } else {
1548             wrapPosition =
1549                 lineBreaker->Next(aSequenceStart, (aEnd - aSequenceStart),
1550                                   (aPos - aSequenceStart));
1551             if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
1552               foundWrapPosition = true;
1553             }
1554           }
1555         }
1556 
1557         if (foundWrapPosition) {
1558           if (!mColPos && mDoFormat) {
1559             NS_ENSURE_TRUE(AppendIndentation(aOutputStr), false);
1560           } else if (mAddSpace) {
1561             bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
1562             mAddSpace = false;
1563             NS_ENSURE_TRUE(result, false);
1564           }
1565           NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, wrapPosition,
1566                                            mozilla::fallible),
1567                          false);
1568 
1569           NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1570           aPos = aSequenceStart + wrapPosition;
1571           aMayIgnoreStartOfLineWhitespaceSequence = true;
1572         } else {
1573           // try some simple fallback logic
1574           // go forward up to the next whitespace position,
1575           // in the worst case this will be all the rest of the data
1576 
1577           // we update the mColPos variable with the length of
1578           // the part already parsed.
1579           mColPos += length;
1580 
1581           // now try to find the next whitespace
1582           do {
1583             if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1584               break;
1585             }
1586 
1587             ++aPos;
1588             ++mColPos;
1589           } while (aPos < aEnd);
1590 
1591           if (mAddSpace) {
1592             bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
1593             mAddSpace = false;
1594             NS_ENSURE_TRUE(result, false);
1595           }
1596           NS_ENSURE_TRUE(
1597               aOutputStr.Append(aSequenceStart, aPos - aSequenceStart,
1598                                 mozilla::fallible),
1599               false);
1600         }
1601       }
1602       aSequenceStartAfterAWhiteSpace = false;
1603     }
1604   } while (onceAgainBecauseWeAddedBreakInFront);
1605 
1606   return true;
1607 }
1608 
AppendToStringFormatedWrapped(const nsAString & aStr,nsAString & aOutputStr)1609 bool nsXMLContentSerializer::AppendToStringFormatedWrapped(
1610     const nsAString& aStr, nsAString& aOutputStr) {
1611   if (mBodyOnly && !mInBody) {
1612     return true;
1613   }
1614 
1615   nsAString::const_char_iterator pos, end, sequenceStart;
1616 
1617   aStr.BeginReading(pos);
1618   aStr.EndReading(end);
1619 
1620   bool sequenceStartAfterAWhitespace = false;
1621   if (pos < end) {
1622     nsAString::const_char_iterator end2;
1623     aOutputStr.EndReading(end2);
1624     --end2;
1625     if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1626       sequenceStartAfterAWhitespace = true;
1627     }
1628   }
1629 
1630   // if the current line already has text on it, such as a tag,
1631   // leading whitespace is significant
1632   bool mayIgnoreStartOfLineWhitespaceSequence =
1633       (!mColPos ||
1634        (mIsIndentationAddedOnCurrentLine && sequenceStartAfterAWhitespace &&
1635         uint32_t(mColPos) == mIndent.Length()));
1636 
1637   while (pos < end) {
1638     sequenceStart = pos;
1639 
1640     // if beginning of a whitespace sequence
1641     if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1642       NS_ENSURE_TRUE(AppendFormatedWrapped_WhitespaceSequence(
1643                          pos, end, sequenceStart,
1644                          mayIgnoreStartOfLineWhitespaceSequence, aOutputStr),
1645                      false);
1646     } else {  // any other non-whitespace char
1647       NS_ENSURE_TRUE(
1648           AppendWrapped_NonWhitespaceSequence(
1649               pos, end, sequenceStart, mayIgnoreStartOfLineWhitespaceSequence,
1650               sequenceStartAfterAWhitespace, aOutputStr),
1651           false);
1652     }
1653   }
1654 
1655   return true;
1656 }
1657 
AppendWrapped_WhitespaceSequence(nsAString::const_char_iterator & aPos,const nsAString::const_char_iterator aEnd,const nsAString::const_char_iterator aSequenceStart,nsAString & aOutputStr)1658 bool nsXMLContentSerializer::AppendWrapped_WhitespaceSequence(
1659     nsAString::const_char_iterator& aPos,
1660     const nsAString::const_char_iterator aEnd,
1661     const nsAString::const_char_iterator aSequenceStart,
1662     nsAString& aOutputStr) {
1663   // Handle the complete sequence of whitespace.
1664   // Continue to iterate until we find the first non-whitespace char.
1665   // Updates "aPos" to point to the first unhandled char.
1666   mAddSpace = false;
1667   mIsIndentationAddedOnCurrentLine = false;
1668 
1669   bool leaveLoop = false;
1670   nsAString::const_char_iterator lastPos = aPos;
1671 
1672   do {
1673     switch (*aPos) {
1674       case ' ':
1675       case '\t':
1676         // if there are too many spaces on a line, we wrap
1677         if (mColPos >= mMaxColumn) {
1678           if (lastPos != aPos) {
1679             NS_ENSURE_TRUE(
1680                 aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible),
1681                 false);
1682           }
1683           NS_ENSURE_TRUE(AppendToString(mLineBreak, aOutputStr), false);
1684           mColPos = 0;
1685           lastPos = aPos;
1686         }
1687 
1688         ++mColPos;
1689         ++aPos;
1690         break;
1691       case '\n':
1692         if (lastPos != aPos) {
1693           NS_ENSURE_TRUE(
1694               aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible),
1695               false);
1696         }
1697         NS_ENSURE_TRUE(AppendToString(mLineBreak, aOutputStr), false);
1698         mColPos = 0;
1699         ++aPos;
1700         lastPos = aPos;
1701         break;
1702       default:
1703         leaveLoop = true;
1704         break;
1705     }
1706   } while (!leaveLoop && aPos < aEnd);
1707 
1708   if (lastPos != aPos) {
1709     NS_ENSURE_TRUE(
1710         aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible), false);
1711   }
1712 
1713   return true;
1714 }
1715 
AppendToStringWrapped(const nsAString & aStr,nsAString & aOutputStr)1716 bool nsXMLContentSerializer::AppendToStringWrapped(const nsAString& aStr,
1717                                                    nsAString& aOutputStr) {
1718   if (mBodyOnly && !mInBody) {
1719     return true;
1720   }
1721 
1722   nsAString::const_char_iterator pos, end, sequenceStart;
1723 
1724   aStr.BeginReading(pos);
1725   aStr.EndReading(end);
1726 
1727   // not used in this case, but needed by AppendWrapped_NonWhitespaceSequence
1728   bool mayIgnoreStartOfLineWhitespaceSequence = false;
1729   mMayIgnoreLineBreakSequence = false;
1730 
1731   bool sequenceStartAfterAWhitespace = false;
1732   if (pos < end && !aOutputStr.IsEmpty()) {
1733     nsAString::const_char_iterator end2;
1734     aOutputStr.EndReading(end2);
1735     --end2;
1736     if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1737       sequenceStartAfterAWhitespace = true;
1738     }
1739   }
1740 
1741   while (pos < end) {
1742     sequenceStart = pos;
1743 
1744     // if beginning of a whitespace sequence
1745     if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1746       sequenceStartAfterAWhitespace = true;
1747       NS_ENSURE_TRUE(
1748           AppendWrapped_WhitespaceSequence(pos, end, sequenceStart, aOutputStr),
1749           false);
1750     } else {  // any other non-whitespace char
1751       NS_ENSURE_TRUE(
1752           AppendWrapped_NonWhitespaceSequence(
1753               pos, end, sequenceStart, mayIgnoreStartOfLineWhitespaceSequence,
1754               sequenceStartAfterAWhitespace, aOutputStr),
1755           false);
1756     }
1757   }
1758 
1759   return true;
1760 }
1761 
ShouldMaintainPreLevel() const1762 bool nsXMLContentSerializer::ShouldMaintainPreLevel() const {
1763   // Only attempt to maintain the pre level for consumers who care about it.
1764   return !mDoRaw || (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre);
1765 }
1766 
MaybeSerializeIsValue(Element * aElement,nsAString & aStr)1767 bool nsXMLContentSerializer::MaybeSerializeIsValue(Element* aElement,
1768                                                    nsAString& aStr) {
1769   CustomElementData* ceData = aElement->GetCustomElementData();
1770   if (ceData) {
1771     nsAtom* isAttr = ceData->GetIs(aElement);
1772     if (isAttr && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
1773       NS_ENSURE_TRUE(aStr.AppendLiteral(" is=\"", mozilla::fallible), false);
1774       NS_ENSURE_TRUE(
1775           aStr.Append(nsDependentAtomString(isAttr), mozilla::fallible), false);
1776       NS_ENSURE_TRUE(aStr.AppendLiteral("\"", mozilla::fallible), false);
1777     }
1778   }
1779 
1780   return true;
1781 }
1782