Bug 1576899: change `nsIContentSerializer` to take output string as argument in `nsIContentSerializer::Init`. r=hsivonen
authorMirko Brodesser <mbrodesser@mozilla.com>
Wed, 28 Aug 2019 11:54:49 +0000
changeset 554162 10716060af24e9de0ea140d3d4f8922f19379634
parent 554161 86b52938406b2649dfe660015019c4483dcd6526
child 554163 366ce74393fc40912937e21ff5601c0641404625
push id2165
push userffxbld-merge
push dateMon, 14 Oct 2019 16:30:58 +0000
treeherdermozilla-release@0eae18af659f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershsivonen
bugs1576899
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1576899: change `nsIContentSerializer` to take output string as argument in `nsIContentSerializer::Init`. r=hsivonen Before, all `nsIContentSerializer::Append`* methods took an output string. The state of `nsPlainTextSerializer` depended on the string pointing to the same object. Now, it's ensured that the same output string is used between `nsIContentSerializer::Init` and `nsIContentSerializer::Finish`. Moreover, `nsHTMLCopyEncoder::EncodeToStringWithContext` re-used `mSerializer` without initializing it again. This was error-prone, because after serializing with `mSerializer`, it's internal state might have changed to an undesirable one (e.g. `nsPlainTextSerializer::mWrapColumn` could've been modified). Hence, it is now initialized again before serializing the common ancestors. Differential Revision: https://phabricator.services.mozilla.com/D43613
dom/base/nsDocumentEncoder.cpp
dom/base/nsHTMLContentSerializer.cpp
dom/base/nsHTMLContentSerializer.h
dom/base/nsIContentSerializer.h
dom/base/nsPlainTextSerializer.cpp
dom/base/nsPlainTextSerializer.h
dom/base/nsXHTMLContentSerializer.cpp
dom/base/nsXHTMLContentSerializer.h
dom/base/nsXMLContentSerializer.cpp
dom/base/nsXMLContentSerializer.h
--- a/dom/base/nsDocumentEncoder.cpp
+++ b/dom/base/nsDocumentEncoder.cpp
@@ -56,72 +56,72 @@ enum nsRangeIterationDirection { kDirect
 
 class TextStreamer {
  public:
   /**
    * @param aStream Will be kept alive by the TextStreamer.
    * @param aUnicodeEncoder Needs to be non-nullptr.
    */
   TextStreamer(nsIOutputStream& aStream, UniquePtr<Encoder> aUnicodeEncoder,
-               bool aIsPlainText);
+               bool aIsPlainText, nsAString& aOutputBuffer);
 
   /**
-   * @param aString Will be truncated if aString is written to stream.
+   * String will be truncated if it is written to stream.
    */
-  nsresult FlushIfStringLongEnough(nsAString& aString);
+  nsresult FlushIfStringLongEnough();
 
   /**
-   * @param aString Will be truncated.
+   * String will be truncated.
    */
-  nsresult ForceFlush(nsAString& aString);
+  nsresult ForceFlush();
 
  private:
   const static uint32_t kMaxLengthBeforeFlush = 1024;
 
   const static uint32_t kEncoderBufferSizeInBytes = 4096;
 
-  nsresult EncodeAndWrite(const nsAString& aString);
+  nsresult EncodeAndWrite();
 
-  nsresult EncodeAndWriteAndTruncate(nsAString& aString);
+  nsresult EncodeAndWriteAndTruncate();
 
   const nsCOMPtr<nsIOutputStream> mStream;
   const UniquePtr<Encoder> mUnicodeEncoder;
   const bool mIsPlainText;
+  nsAString& mOutputBuffer;
 };
 
 TextStreamer::TextStreamer(nsIOutputStream& aStream,
                            UniquePtr<Encoder> aUnicodeEncoder,
-                           bool aIsPlainText)
+                           bool aIsPlainText, nsAString& aOutputBuffer)
     : mStream{&aStream},
       mUnicodeEncoder(std::move(aUnicodeEncoder)),
-      mIsPlainText(aIsPlainText) {
+      mIsPlainText(aIsPlainText),
+      mOutputBuffer(aOutputBuffer) {
   MOZ_ASSERT(mUnicodeEncoder);
 }
 
-nsresult TextStreamer::FlushIfStringLongEnough(nsAString& aString) {
+nsresult TextStreamer::FlushIfStringLongEnough() {
   nsresult rv = NS_OK;
 
-  if (aString.Length() > kMaxLengthBeforeFlush) {
-    rv = EncodeAndWriteAndTruncate(aString);
+  if (mOutputBuffer.Length() > kMaxLengthBeforeFlush) {
+    rv = EncodeAndWriteAndTruncate();
   }
 
   return rv;
 }
 
-nsresult TextStreamer::ForceFlush(nsAString& aString) {
-  return EncodeAndWriteAndTruncate(aString);
-}
+nsresult TextStreamer::ForceFlush() { return EncodeAndWriteAndTruncate(); }
 
-nsresult TextStreamer::EncodeAndWrite(const nsAString& aString) {
-  if (aString.IsEmpty()) {
+nsresult TextStreamer::EncodeAndWrite() {
+  if (mOutputBuffer.IsEmpty()) {
     return NS_OK;
   }
 
   uint8_t buffer[kEncoderBufferSizeInBytes];
-  auto src = MakeSpan(aString);
+  auto src = MakeSpan(mOutputBuffer);
   auto bufferSpan = MakeSpan(buffer);
   // Reserve space for terminator
   auto dst = bufferSpan.To(bufferSpan.Length() - 1);
   for (;;) {
     uint32_t result;
     size_t read;
     size_t written;
     bool hadErrors;
@@ -151,19 +151,19 @@ nsresult TextStreamer::EncodeAndWrite(co
       return rv;
     }
     if (result == kInputEmpty) {
       return NS_OK;
     }
   }
 }
 
-nsresult TextStreamer::EncodeAndWriteAndTruncate(nsAString& aString) {
-  const nsresult rv = EncodeAndWrite(aString);
-  aString.Truncate();
+nsresult TextStreamer::EncodeAndWriteAndTruncate() {
+  const nsresult rv = EncodeAndWrite();
+  mOutputBuffer.Truncate();
   return rv;
 }
 
 /**
  * The scope may be limited to either a selection, range, or node.
  */
 class EncodingScope {
  public:
@@ -216,44 +216,41 @@ class nsDocumentEncoder : public nsIDocu
   virtual ~nsDocumentEncoder();
 
   void Initialize(bool aClearCachedSerializer = true);
 
   /**
    * @param aMaxLength As described at
    * `nsIDocumentEncodder.encodeToStringWithMaxLength`.
    */
-  nsresult SerializeDependingOnScope(nsAString& aOutput, uint32_t aMaxLength);
+  nsresult SerializeDependingOnScope(uint32_t aMaxLength);
 
-  nsresult SerializeSelection(nsAString& aOutput);
+  nsresult SerializeSelection();
 
-  nsresult SerializeNode(nsAString& aOutput);
+  nsresult SerializeNode();
 
   /**
    * @param aMaxLength As described at
    * `nsIDocumentEncodder.encodeToStringWithMaxLength`.
    */
-  nsresult SerializeWholeDocument(nsAString& aOutput, uint32_t aMaxLength);
+  nsresult SerializeWholeDocument(uint32_t aMaxLength);
 
   nsresult SerializeNodeStart(nsINode& aOriginalNode, int32_t aStartOffset,
-                              int32_t aEndOffset, nsAString& aStr,
+                              int32_t aEndOffset,
                               nsINode* aFixupNode = nullptr);
-  nsresult SerializeToStringRecursive(nsINode* aNode, nsAString& aStr,
-                                      bool aDontSerializeRoot,
+  nsresult SerializeToStringRecursive(nsINode* aNode, bool aDontSerializeRoot,
                                       uint32_t aMaxLength = 0);
-  nsresult SerializeNodeEnd(nsINode& aOriginalNode, nsAString& aStr,
+  nsresult SerializeNodeEnd(nsINode& aOriginalNode,
                             nsINode* aFixupNode = nullptr);
   // This serializes the content of aNode.
-  nsresult SerializeToStringIterative(nsINode* aNode, nsAString& aStr);
-  nsresult SerializeRangeToString(nsRange* aRange, nsAString& aOutputString);
-  nsresult SerializeRangeNodes(nsRange* aRange, nsINode* aNode,
-                               nsAString& aString, int32_t aDepth);
-  nsresult SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
-                                      nsAString& aString);
-  nsresult SerializeRangeContextEnd(nsAString& aString);
+  nsresult SerializeToStringIterative(nsINode* aNode);
+  nsresult SerializeRangeToString(nsRange* aRange);
+  nsresult SerializeRangeNodes(nsRange* aRange, nsINode* aNode, int32_t aDepth);
+  nsresult SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray);
+  nsresult SerializeRangeContextEnd();
 
   virtual int32_t GetImmediateContextCount(
       const nsTArray<nsINode*>& aAncestorArray) {
     return -1;
   }
 
   bool IsInvisibleNodeAndShouldBeSkipped(nsINode& aNode) const {
     if (mFlags & SkipInvisibleContent) {
@@ -387,35 +384,34 @@ void nsDocumentEncoder::Initialize(bool 
 static bool ParentIsTR(nsIContent* aContent) {
   mozilla::dom::Element* parent = aContent->GetParentElement();
   if (!parent) {
     return false;
   }
   return parent->IsHTMLElement(nsGkAtoms::tr);
 }
 
-nsresult nsDocumentEncoder::SerializeDependingOnScope(nsAString& aOutput,
-                                                      uint32_t aMaxLength) {
+nsresult nsDocumentEncoder::SerializeDependingOnScope(uint32_t aMaxLength) {
   nsresult rv = NS_OK;
   if (mEncodingScope.mSelection) {
-    rv = SerializeSelection(aOutput);
+    rv = SerializeSelection();
   } else if (nsRange* range = mEncodingScope.mRange) {
-    rv = SerializeRangeToString(range, aOutput);
+    rv = SerializeRangeToString(range);
   } else if (mEncodingScope.mNode) {
-    rv = SerializeNode(aOutput);
+    rv = SerializeNode();
   } else {
-    rv = SerializeWholeDocument(aOutput, aMaxLength);
+    rv = SerializeWholeDocument(aMaxLength);
   }
 
   mEncodingScope = {};
 
   return rv;
 }
 
-nsresult nsDocumentEncoder::SerializeSelection(nsAString& aOutput) {
+nsresult nsDocumentEncoder::SerializeSelection() {
   NS_ENSURE_TRUE(mEncodingScope.mSelection, NS_ERROR_FAILURE);
 
   nsresult rv = NS_OK;
   Selection* selection = mEncodingScope.mSelection;
   uint32_t count = selection->RangeCount();
 
   nsCOMPtr<nsINode> node;
   nsCOMPtr<nsINode> prevNode;
@@ -429,92 +425,91 @@ nsresult nsDocumentEncoder::SerializeSel
     // needed Bug 137450: Problem copying/pasting a table from a web page to
     // Excel. Each separate block of <tr></tr> produced above will be wrapped
     // by the immediate context. This assumes that you can't select cells that
     // are multiple selections from two tables simultaneously.
     node = range->GetStartContainer();
     NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
     if (node != prevNode) {
       if (prevNode) {
-        rv = SerializeNodeEnd(*prevNode, aOutput);
+        rv = SerializeNodeEnd(*prevNode);
         NS_ENSURE_SUCCESS(rv, rv);
       }
       nsCOMPtr<nsIContent> content = do_QueryInterface(node);
       if (content && content->IsHTMLElement(nsGkAtoms::tr) &&
           !ParentIsTR(content)) {
         if (!prevNode) {
           // Went from a non-<tr> to a <tr>
           mCommonAncestors.Clear();
           nsContentUtils::GetAncestors(node->GetParentNode(), mCommonAncestors);
-          rv = SerializeRangeContextStart(mCommonAncestors, aOutput);
+          rv = SerializeRangeContextStart(mCommonAncestors);
           NS_ENSURE_SUCCESS(rv, rv);
           // Don't let SerializeRangeToString serialize the context again
           mDisableContextSerialize = true;
         }
 
-        rv = SerializeNodeStart(*node, 0, -1, aOutput);
+        rv = SerializeNodeStart(*node, 0, -1);
         NS_ENSURE_SUCCESS(rv, rv);
         prevNode = node;
       } else if (prevNode) {
         // Went from a <tr> to a non-<tr>
         mDisableContextSerialize = false;
-        rv = SerializeRangeContextEnd(aOutput);
+        rv = SerializeRangeContextEnd();
         NS_ENSURE_SUCCESS(rv, rv);
         prevNode = nullptr;
       }
     }
 
-    rv = SerializeRangeToString(range, aOutput);
+    rv = SerializeRangeToString(range);
     NS_ENSURE_SUCCESS(rv, rv);
     if (i == 0) {
       firstRangeStartDepth = mContextInfoDepth.mStart;
     }
   }
   mContextInfoDepth.mStart = firstRangeStartDepth;
 
   if (prevNode) {
-    rv = SerializeNodeEnd(*prevNode, aOutput);
+    rv = SerializeNodeEnd(*prevNode);
     NS_ENSURE_SUCCESS(rv, rv);
     mDisableContextSerialize = false;
-    rv = SerializeRangeContextEnd(aOutput);
+    rv = SerializeRangeContextEnd();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Just to be safe
   mDisableContextSerialize = false;
 
   return rv;
 }
 
-nsresult nsDocumentEncoder::SerializeNode(nsAString& aOutput) {
+nsresult nsDocumentEncoder::SerializeNode() {
   NS_ENSURE_TRUE(mEncodingScope.mNode, NS_ERROR_FAILURE);
 
   nsresult rv = NS_OK;
   nsINode* node = mEncodingScope.mNode;
   const bool nodeIsContainer = mEncodingScope.mNodeIsContainer;
   if (!mNodeFixup && !(mFlags & SkipInvisibleContent) && !mTextStreamer &&
       nodeIsContainer) {
-    rv = SerializeToStringIterative(node, aOutput);
+    rv = SerializeToStringIterative(node);
   } else {
-    rv = SerializeToStringRecursive(node, aOutput, nodeIsContainer);
+    rv = SerializeToStringRecursive(node, nodeIsContainer);
   }
 
   return rv;
 }
 
-nsresult nsDocumentEncoder::SerializeWholeDocument(nsAString& aOutput,
-                                                   uint32_t aMaxLength) {
+nsresult nsDocumentEncoder::SerializeWholeDocument(uint32_t aMaxLength) {
   NS_ENSURE_FALSE(mEncodingScope.mSelection, NS_ERROR_FAILURE);
   NS_ENSURE_FALSE(mEncodingScope.mRange, NS_ERROR_FAILURE);
   NS_ENSURE_FALSE(mEncodingScope.mNode, NS_ERROR_FAILURE);
 
-  nsresult rv = mSerializer->AppendDocumentStart(mDocument, aOutput);
+  nsresult rv = mSerializer->AppendDocumentStart(mDocument);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = SerializeToStringRecursive(mDocument, aOutput, false, aMaxLength);
+  rv = SerializeToStringRecursive(mDocument, false, aMaxLength);
   return rv;
 }
 
 nsDocumentEncoder::~nsDocumentEncoder() {
   if (mCachedBuffer) {
     mCachedBuffer->Release();
   }
 }
@@ -627,17 +622,16 @@ class FixupNodeDeterminer {
   nsIDocumentEncoderNodeFixup* mNodeFixup;
   nsCOMPtr<nsINode> mFixupNode;
   nsINode& mOriginalNode;
 };
 
 nsresult nsDocumentEncoder::SerializeNodeStart(nsINode& aOriginalNode,
                                                int32_t aStartOffset,
                                                int32_t aEndOffset,
-                                               nsAString& aStr,
                                                nsINode* aFixupNode) {
   if (mNeedsPreformatScanning) {
     if (aOriginalNode.IsElement()) {
       mSerializer->ScanElementForPreformat(aOriginalNode.AsElement());
     } else if (aOriginalNode.IsText()) {
       const nsCOMPtr<nsINode> parent = aOriginalNode.GetParent();
       if (parent && parent->IsElement()) {
         mSerializer->ScanElementForPreformat(parent->AsElement());
@@ -657,53 +651,51 @@ nsresult nsDocumentEncoder::SerializeNod
 
   if (node->IsElement()) {
     if ((mFlags & (nsIDocumentEncoder::OutputPreformatted |
                    nsIDocumentEncoder::OutputDropInvisibleBreak)) &&
         nsLayoutUtils::IsInvisibleBreak(node)) {
       return rv;
     }
     rv = mSerializer->AppendElementStart(node->AsElement(),
-                                         aOriginalNode.AsElement(), aStr);
+                                         aOriginalNode.AsElement());
     return rv;
   }
 
   switch (node->NodeType()) {
     case nsINode::TEXT_NODE: {
       rv = mSerializer->AppendText(static_cast<nsIContent*>(node), aStartOffset,
-                                   aEndOffset, aStr);
+                                   aEndOffset);
       break;
     }
     case nsINode::CDATA_SECTION_NODE: {
       rv = mSerializer->AppendCDATASection(static_cast<nsIContent*>(node),
-                                           aStartOffset, aEndOffset, aStr);
+                                           aStartOffset, aEndOffset);
       break;
     }
     case nsINode::PROCESSING_INSTRUCTION_NODE: {
       rv = mSerializer->AppendProcessingInstruction(
-          static_cast<ProcessingInstruction*>(node), aStartOffset, aEndOffset,
-          aStr);
+          static_cast<ProcessingInstruction*>(node), aStartOffset, aEndOffset);
       break;
     }
     case nsINode::COMMENT_NODE: {
       rv = mSerializer->AppendComment(static_cast<Comment*>(node), aStartOffset,
-                                      aEndOffset, aStr);
+                                      aEndOffset);
       break;
     }
     case nsINode::DOCUMENT_TYPE_NODE: {
-      rv = mSerializer->AppendDoctype(static_cast<DocumentType*>(node), aStr);
+      rv = mSerializer->AppendDoctype(static_cast<DocumentType*>(node));
       break;
     }
   }
 
   return rv;
 }
 
 nsresult nsDocumentEncoder::SerializeNodeEnd(nsINode& aOriginalNode,
-                                             nsAString& aStr,
                                              nsINode* aFixupNode) {
   if (mNeedsPreformatScanning) {
     if (aOriginalNode.IsElement()) {
       mSerializer->ForgetElementForPreformat(aOriginalNode.AsElement());
     } else if (aOriginalNode.IsText()) {
       const nsCOMPtr<nsINode> parent = aOriginalNode.GetParent();
       if (parent && parent->IsElement()) {
         mSerializer->ForgetElementForPreformat(parent->AsElement());
@@ -718,27 +710,30 @@ nsresult nsDocumentEncoder::SerializeNod
   nsresult rv = NS_OK;
 
   FixupNodeDeterminer fixupNodeDeterminer{mNodeFixup, aFixupNode,
                                           aOriginalNode};
   nsINode* node = &fixupNodeDeterminer.GetFixupNodeFallBackToOriginalNode();
 
   if (node->IsElement()) {
     rv = mSerializer->AppendElementEnd(node->AsElement(),
-                                       aOriginalNode.AsElement(), aStr);
+                                       aOriginalNode.AsElement());
   }
 
   return rv;
 }
 
 nsresult nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode,
-                                                       nsAString& aStr,
                                                        bool aDontSerializeRoot,
                                                        uint32_t aMaxLength) {
-  if (aMaxLength > 0 && aStr.Length() >= aMaxLength) {
+  uint32_t outputLength{0};
+  nsresult rv = mSerializer->GetOutputLength(outputLength);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aMaxLength > 0 && outputLength >= aMaxLength) {
     return NS_OK;
   }
 
   NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
 
   if (IsInvisibleNodeAndShouldBeSkipped(*aNode)) {
     return NS_OK;
   }
@@ -752,62 +747,59 @@ nsresult nsDocumentEncoder::SerializeToS
       if (nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame()) {
         if (!frame->IsSelectable(nullptr)) {
           aDontSerializeRoot = true;
         }
       }
     }
   }
 
-  nsresult rv = NS_OK;
-
   if (!aDontSerializeRoot) {
     int32_t endOffset = -1;
     if (aMaxLength > 0) {
-      MOZ_ASSERT(aMaxLength >= aStr.Length());
-      endOffset = aMaxLength - aStr.Length();
+      MOZ_ASSERT(aMaxLength >= outputLength);
+      endOffset = aMaxLength - outputLength;
     }
-    rv = SerializeNodeStart(*aNode, 0, endOffset, aStr, maybeFixedNode);
+    rv = SerializeNodeStart(*aNode, 0, endOffset, maybeFixedNode);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsINode* node = fixupNodeDeterminer.IsSerializationOfFixupChildrenNeeded()
                       ? maybeFixedNode
                       : aNode;
 
   for (nsINode* child = nsNodeUtils::GetFirstChildOfTemplateOrNode(node); child;
        child = child->GetNextSibling()) {
-    rv = SerializeToStringRecursive(child, aStr, false, aMaxLength);
+    rv = SerializeToStringRecursive(child, false, aMaxLength);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (!aDontSerializeRoot) {
-    rv = SerializeNodeEnd(*aNode, aStr, maybeFixedNode);
+    rv = SerializeNodeEnd(*aNode, maybeFixedNode);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (mTextStreamer) {
-    rv = mTextStreamer->FlushIfStringLongEnough(aStr);
+    rv = mTextStreamer->FlushIfStringLongEnough();
   }
 
   return rv;
 }
 
-nsresult nsDocumentEncoder::SerializeToStringIterative(nsINode* aNode,
-                                                       nsAString& aStr) {
+nsresult nsDocumentEncoder::SerializeToStringIterative(nsINode* aNode) {
   nsresult rv;
 
   nsINode* node = nsNodeUtils::GetFirstChildOfTemplateOrNode(aNode);
   while (node) {
     nsINode* current = node;
-    rv = SerializeNodeStart(*current, 0, -1, aStr, current);
+    rv = SerializeNodeStart(*current, 0, -1, current);
     NS_ENSURE_SUCCESS(rv, rv);
     node = nsNodeUtils::GetFirstChildOfTemplateOrNode(current);
     while (!node && current && current != aNode) {
-      rv = SerializeNodeEnd(*current, aStr);
+      rv = SerializeNodeEnd(*current);
       NS_ENSURE_SUCCESS(rv, rv);
       // Check if we have siblings.
       node = current->GetNextSibling();
       if (!node) {
         // Perhaps parent node has siblings.
         current = current->GetParentNode();
 
         // Handle template element. If the parent is a template's content,
@@ -824,17 +816,16 @@ nsresult nsDocumentEncoder::SerializeToS
 
   return NS_OK;
 }
 
 static bool IsTextNode(nsINode* aNode) { return aNode && aNode->IsText(); }
 
 nsresult nsDocumentEncoder::SerializeRangeNodes(nsRange* const aRange,
                                                 nsINode* const aNode,
-                                                nsAString& aString,
                                                 const int32_t aDepth) {
   nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
   NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
 
   if (IsInvisibleNodeAndShouldBeSkipped(*aNode)) {
     return NS_OK;
   }
 
@@ -855,50 +846,50 @@ nsresult nsDocumentEncoder::SerializeRan
     if (end >= 0 && (uint32_t)end <= endContainerPath.Length()) {
       endNode = endContainerPath[end];
     }
   }
 
   if (startNode != content && endNode != content) {
     // node is completely contained in range.  Serialize the whole subtree
     // rooted by this node.
-    rv = SerializeToStringRecursive(aNode, aString, false);
+    rv = SerializeToStringRecursive(aNode, false);
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
     // due to implementation it is impossible for text node to be both start and
     // end of range.  We would have handled that case without getting here.
     // XXXsmaug What does this all mean?
     if (IsTextNode(aNode)) {
       if (startNode == content) {
         int32_t startOffset = aRange->StartOffset();
-        rv = SerializeNodeStart(*aNode, startOffset, -1, aString);
+        rv = SerializeNodeStart(*aNode, startOffset, -1);
         NS_ENSURE_SUCCESS(rv, rv);
       } else {
         int32_t endOffset = aRange->EndOffset();
-        rv = SerializeNodeStart(*aNode, 0, endOffset, aString);
+        rv = SerializeNodeStart(*aNode, 0, endOffset);
         NS_ENSURE_SUCCESS(rv, rv);
       }
-      rv = SerializeNodeEnd(*aNode, aString);
+      rv = SerializeNodeEnd(*aNode);
       NS_ENSURE_SUCCESS(rv, rv);
     } else {
       if (aNode != mCommonAncestorOfRange) {
         if (IncludeInContext(aNode)) {
           // halt the incrementing of mContextInfoDepth.  This is
           // so paste client will include this node in paste.
           mHaltRangeHint = true;
         }
         if ((startNode == content) && !mHaltRangeHint) {
           ++mContextInfoDepth.mStart;
         }
         if ((endNode == content) && !mHaltRangeHint) {
           ++mContextInfoDepth.mEnd;
         }
 
         // serialize the start of this node
-        rv = SerializeNodeStart(*aNode, 0, -1, aString);
+        rv = SerializeNodeStart(*aNode, 0, -1);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       const auto& startContainerOffsets =
           mRangeBoundaryPathsAndOffsets.mStartContainerOffsets;
       const auto& endContainerOffsets =
           mRangeBoundaryPathsAndOffsets.mEndContainerOffsets;
       // do some calculations that will tell us which children of this
@@ -936,38 +927,38 @@ nsresult nsDocumentEncoder::SerializeRan
         for (; j < startOffset && childAsNode; ++j) {
           childAsNode = childAsNode->GetNextSibling();
         }
 
         MOZ_ASSERT(j == startOffset);
 
         for (; childAsNode && j < endOffset; ++j) {
           if ((j == startOffset) || (j == endOffset - 1)) {
-            rv = SerializeRangeNodes(aRange, childAsNode, aString, aDepth + 1);
+            rv = SerializeRangeNodes(aRange, childAsNode, aDepth + 1);
           } else {
-            rv = SerializeToStringRecursive(childAsNode, aString, false);
+            rv = SerializeToStringRecursive(childAsNode, false);
           }
 
           NS_ENSURE_SUCCESS(rv, rv);
           childAsNode = childAsNode->GetNextSibling();
         }
       }
 
       // serialize the end of this node
       if (aNode != mCommonAncestorOfRange) {
-        rv = SerializeNodeEnd(*aNode, aString);
+        rv = SerializeNodeEnd(*aNode);
         NS_ENSURE_SUCCESS(rv, rv);
       }
     }
   }
   return NS_OK;
 }
 
 nsresult nsDocumentEncoder::SerializeRangeContextStart(
-    const nsTArray<nsINode*>& aAncestorArray, nsAString& aString) {
+    const nsTArray<nsINode*>& aAncestorArray) {
   if (mDisableContextSerialize) {
     return NS_OK;
   }
 
   AutoTArray<nsINode*, 8>* serializedContext = mRangeContexts.AppendElement();
 
   int32_t i = aAncestorArray.Length(), j;
   nsresult rv = NS_OK;
@@ -977,47 +968,46 @@ nsresult nsDocumentEncoder::SerializeRan
 
   while (i > 0) {
     nsINode* node = aAncestorArray.ElementAt(--i);
 
     if (!node) break;
 
     // Either a general inclusion or as immediate context
     if (IncludeInContext(node) || i < j) {
-      rv = SerializeNodeStart(*node, 0, -1, aString);
+      rv = SerializeNodeStart(*node, 0, -1);
       serializedContext->AppendElement(node);
       if (NS_FAILED(rv)) break;
     }
   }
 
   return rv;
 }
 
-nsresult nsDocumentEncoder::SerializeRangeContextEnd(nsAString& aString) {
+nsresult nsDocumentEncoder::SerializeRangeContextEnd() {
   if (mDisableContextSerialize) {
     return NS_OK;
   }
 
   MOZ_RELEASE_ASSERT(!mRangeContexts.IsEmpty(),
                      "Tried to end context without starting one.");
   AutoTArray<nsINode*, 8>& serializedContext = mRangeContexts.LastElement();
 
   nsresult rv = NS_OK;
   for (nsINode* node : Reversed(serializedContext)) {
-    rv = SerializeNodeEnd(*node, aString);
+    rv = SerializeNodeEnd(*node);
 
     if (NS_FAILED(rv)) break;
   }
 
   mRangeContexts.RemoveLastElement();
   return rv;
 }
 
-nsresult nsDocumentEncoder::SerializeRangeToString(nsRange* aRange,
-                                                   nsAString& aOutputString) {
+nsresult nsDocumentEncoder::SerializeRangeToString(nsRange* aRange) {
   if (!aRange || aRange->Collapsed()) return NS_OK;
 
   mCommonAncestorOfRange = aRange->GetCommonAncestor();
 
   if (!mCommonAncestorOfRange) {
     return NS_OK;
   }
 
@@ -1048,41 +1038,40 @@ nsresult nsDocumentEncoder::SerializeRan
 
   nsCOMPtr<nsIContent> commonContent =
       do_QueryInterface(mCommonAncestorOfRange);
   mStartRootIndex = startContainerPath.IndexOf(commonContent);
   mEndRootIndex = endContainerPath.IndexOf(commonContent);
 
   nsresult rv = NS_OK;
 
-  rv = SerializeRangeContextStart(mCommonAncestors, aOutputString);
+  rv = SerializeRangeContextStart(mCommonAncestors);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (startContainer == endContainer && IsTextNode(startContainer)) {
     if (mFlags & SkipInvisibleContent) {
       // Check that the parent is visible if we don't a frame.
       // IsInvisibleNodeAndShouldBeSkipped() will do it when there's a frame.
       nsCOMPtr<nsIContent> content = do_QueryInterface(startContainer);
       if (content && !content->GetPrimaryFrame()) {
         nsIContent* parent = content->GetParent();
         if (!parent || IsInvisibleNodeAndShouldBeSkipped(*parent)) {
           return NS_OK;
         }
       }
     }
-    rv = SerializeNodeStart(*startContainer, startOffset, endOffset,
-                            aOutputString);
+    rv = SerializeNodeStart(*startContainer, startOffset, endOffset);
     NS_ENSURE_SUCCESS(rv, rv);
-    rv = SerializeNodeEnd(*startContainer, aOutputString);
+    rv = SerializeNodeEnd(*startContainer);
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
-    rv = SerializeRangeNodes(aRange, mCommonAncestorOfRange, aOutputString, 0);
+    rv = SerializeRangeNodes(aRange, mCommonAncestorOfRange, 0);
     NS_ENSURE_SUCCESS(rv, rv);
   }
-  rv = SerializeRangeContextEnd(aOutputString);
+  rv = SerializeRangeContextEnd();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return rv;
 }
 
 void nsDocumentEncoder::ReleaseDocumentReferenceAndInitialize(
     bool aClearCachedSerializer) {
   mDocument = nullptr;
@@ -1132,22 +1121,23 @@ nsDocumentEncoder::EncodeToStringWithMax
   }
 
   nsresult rv = NS_OK;
 
   bool rewriteEncodingDeclaration =
       !mEncodingScope.IsLimited() &&
       !(mFlags & OutputDontRewriteEncodingDeclaration);
   mSerializer->Init(mFlags, mWrapColumn, mEncoding, mIsCopying,
-                    rewriteEncodingDeclaration, &mNeedsPreformatScanning);
+                    rewriteEncodingDeclaration, &mNeedsPreformatScanning,
+                    output);
 
-  rv = SerializeDependingOnScope(output, aMaxLength);
+  rv = SerializeDependingOnScope(aMaxLength);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mSerializer->Flush(output);
+  rv = mSerializer->FlushAndFinish();
 
   mCachedBuffer = nsStringBuffer::FromString(output);
   // We have to be careful how we set aOutputString, because we don't
   // want it to end up sharing mCachedBuffer if we plan to reuse it.
   bool setOutput = false;
   // Try to cache the buffer.
   if (mCachedBuffer) {
     if ((mCachedBuffer->StorageSize() == kStringBufferSizeInBytes) &&
@@ -1178,24 +1168,24 @@ nsDocumentEncoder::EncodeToStream(nsIOut
   nsresult rv = NS_OK;
 
   if (!mDocument) return NS_ERROR_NOT_INITIALIZED;
 
   if (!mEncoding) {
     return NS_ERROR_UCONV_NOCONV;
   }
 
+  nsAutoString buf;
   const bool isPlainText = mMimeType.LowerCaseEqualsLiteral(kTextMime);
-  mTextStreamer.emplace(*aStream, mEncoding->NewEncoder(), isPlainText);
-  nsAutoString buf;
+  mTextStreamer.emplace(*aStream, mEncoding->NewEncoder(), isPlainText, buf);
 
   rv = EncodeToString(buf);
 
   // Force a flush of the last chunk of data.
-  rv = mTextStreamer->ForceFlush(buf);
+  rv = mTextStreamer->ForceFlush();
   NS_ENSURE_SUCCESS(rv, rv);
 
   mTextStreamer.reset();
 
   return rv;
 }
 
 NS_IMETHODIMP
@@ -1411,16 +1401,19 @@ nsHTMLCopyEncoder::EncodeToStringWithCon
   // now encode common ancestors into aContextString.  Note that the common
   // ancestors will be for the last range in the selection in the case of
   // multirange selections. encoding ancestors every range in a multirange
   // selection in a way that could be understood by the paste code would be a
   // lot more work to do.  As a practical matter, selections are single range,
   // and the ones that aren't are table cell selections where all the cells are
   // in the same table.
 
+  mSerializer->Init(mFlags, mWrapColumn, mEncoding, mIsCopying, false,
+                    &mNeedsPreformatScanning, aContextString);
+
   // leaf of ancestors might be text node.  If so discard it.
   int32_t count = mCommonAncestors.Length();
   int32_t i;
   nsCOMPtr<nsINode> node;
   if (count > 0) node = mCommonAncestors.ElementAt(0);
 
   if (node && IsTextNode(node)) {
     mCommonAncestors.RemoveElementAt(0);
@@ -1431,26 +1424,28 @@ nsHTMLCopyEncoder::EncodeToStringWithCon
       --mContextInfoDepth.mEnd;
     }
     count--;
   }
 
   i = count;
   while (i > 0) {
     node = mCommonAncestors.ElementAt(--i);
-    rv = SerializeNodeStart(*node, 0, -1, aContextString);
+    rv = SerializeNodeStart(*node, 0, -1);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   // i = 0; guaranteed by above
   while (i < count) {
     node = mCommonAncestors.ElementAt(i++);
-    rv = SerializeNodeEnd(*node, aContextString);
+    rv = SerializeNodeEnd(*node);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  mSerializer->Finish();
+
   // encode range info : the start and end depth of the selection, where the
   // depth is distance down in the parent hierarchy.  Later we will need to add
   // leading/trailing whitespace info to this.
   nsAutoString infoString;
   infoString.AppendInt(mContextInfoDepth.mStart);
   infoString.Append(char16_t(','));
   infoString.AppendInt(mContextInfoDepth.mEnd);
   aInfoString = infoString;
--- a/dom/base/nsHTMLContentSerializer.cpp
+++ b/dom/base/nsHTMLContentSerializer.cpp
@@ -42,18 +42,17 @@ nsresult NS_NewHTMLContentSerializer(nsI
   return NS_OK;
 }
 
 nsHTMLContentSerializer::nsHTMLContentSerializer() { mIsHTMLSerializer = true; }
 
 nsHTMLContentSerializer::~nsHTMLContentSerializer() {}
 
 NS_IMETHODIMP
-nsHTMLContentSerializer::AppendDocumentStart(Document* aDocument,
-                                             nsAString& aStr) {
+nsHTMLContentSerializer::AppendDocumentStart(Document* aDocument) {
   return NS_OK;
 }
 
 bool nsHTMLContentSerializer::SerializeHTMLAttributes(
     Element* aElement, Element* aOriginalElement, nsAString& aTagPrefix,
     const nsAString& aTagNamespaceURI, nsAtom* aTagName, int32_t aNamespace,
     nsAString& aStr) {
   MaybeSerializeIsValue(aElement, aStr);
@@ -134,64 +133,66 @@ bool nsHTMLContentSerializer::SerializeH
                    false);
   }
 
   return true;
 }
 
 NS_IMETHODIMP
 nsHTMLContentSerializer::AppendElementStart(Element* aElement,
-                                            Element* aOriginalElement,
-                                            nsAString& aStr) {
+                                            Element* aOriginalElement) {
   NS_ENSURE_ARG(aElement);
+  NS_ENSURE_STATE(mOutput);
 
   bool forceFormat = false;
   nsresult rv = NS_OK;
-  if (!CheckElementStart(aElement, forceFormat, aStr, rv)) {
+  if (!CheckElementStart(aElement, forceFormat, *mOutput, rv)) {
     // When we go to AppendElementEnd for this element, we're going to
     // MaybeLeaveFromPreContent().  So make sure to MaybeEnterInPreContent()
     // now, so our PreLevel() doesn't get confused.
     MaybeEnterInPreContent(aElement);
     return rv;
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAtom* name = aElement->NodeInfo()->NameAtom();
   int32_t ns = aElement->GetNameSpaceID();
 
   bool lineBreakBeforeOpen = LineBreakBeforeOpen(ns, name);
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
     if (mColPos && lineBreakBeforeOpen) {
-      NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     } else {
-      NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
+                     NS_ERROR_OUT_OF_MEMORY);
     }
     if (!mColPos) {
-      NS_ENSURE_TRUE(AppendIndentation(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendIndentation(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     } else if (mAddSpace) {
-      bool result = AppendToString(char16_t(' '), aStr);
+      bool result = AppendToString(char16_t(' '), *mOutput);
       mAddSpace = false;
       NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
     }
   } else if (mAddSpace) {
-    bool result = AppendToString(char16_t(' '), aStr);
+    bool result = AppendToString(char16_t(' '), *mOutput);
     mAddSpace = false;
     NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
   } else {
-    NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   }
   // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode
   // wasn't called
   mAddNewlineForRootNode = false;
 
-  NS_ENSURE_TRUE(AppendToString(kLessThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(kLessThan, *mOutput), NS_ERROR_OUT_OF_MEMORY);
 
-  NS_ENSURE_TRUE(AppendToString(nsDependentAtomString(name), aStr),
+  NS_ENSURE_TRUE(AppendToString(nsDependentAtomString(name), *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
 
   MaybeEnterInPreContent(aElement);
 
   // for block elements, we increase the indentation
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel())
     NS_ENSURE_TRUE(IncrIndentation(name), NS_ERROR_OUT_OF_MEMORY);
 
@@ -218,53 +219,54 @@ nsHTMLContentSerializer::AppendElementSt
     }
     mOLStateStack.AppendElement(olState(startAttrVal, true));
   }
 
   if (mIsCopying && name == nsGkAtoms::li && ns == kNameSpaceID_XHTML) {
     mIsFirstChildOfOL = IsFirstChildOfOL(aOriginalElement);
     if (mIsFirstChildOfOL) {
       // If OL is parent of this LI, serialize attributes in different manner.
-      NS_ENSURE_TRUE(SerializeLIValueAttribute(aElement, aStr),
+      NS_ENSURE_TRUE(SerializeLIValueAttribute(aElement, *mOutput),
                      NS_ERROR_OUT_OF_MEMORY);
     }
   }
 
   // Even LI passed above have to go through this
   // for serializing attributes other than "value".
   nsAutoString dummyPrefix;
   NS_ENSURE_TRUE(
       SerializeHTMLAttributes(aElement, aOriginalElement, dummyPrefix,
-                              EmptyString(), name, ns, aStr),
+                              EmptyString(), name, ns, *mOutput),
       NS_ERROR_OUT_OF_MEMORY);
 
-  NS_ENSURE_TRUE(AppendToString(kGreaterThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(kGreaterThan, *mOutput),
+                 NS_ERROR_OUT_OF_MEMORY);
 
   if (ns == kNameSpaceID_XHTML &&
       (name == nsGkAtoms::script || name == nsGkAtoms::style ||
        name == nsGkAtoms::noscript || name == nsGkAtoms::noframes)) {
     ++mDisableEntityEncoding;
   }
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
       LineBreakAfterOpen(ns, name)) {
-    NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
   }
 
-  NS_ENSURE_TRUE(AfterElementStart(aElement, aOriginalElement, aStr),
+  NS_ENSURE_TRUE(AfterElementStart(aElement, aOriginalElement, *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLContentSerializer::AppendElementEnd(Element* aElement,
-                                          Element* aOriginalElement,
-                                          nsAString& aStr) {
+                                          Element* aOriginalElement) {
   NS_ENSURE_ARG(aElement);
+  NS_ENSURE_STATE(mOutput);
 
   nsAtom* name = aElement->NodeInfo()->NameAtom();
   int32_t ns = aElement->GetNameSpaceID();
 
   if (ns == kNameSpaceID_XHTML &&
       (name == nsGkAtoms::script || name == nsGkAtoms::style ||
        name == nsGkAtoms::noscript || name == nsGkAtoms::noframes)) {
     --mDisableEntityEncoding;
@@ -306,42 +308,43 @@ nsHTMLContentSerializer::AppendElementEn
       return NS_OK;
     }
   }
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
     bool lineBreakBeforeClose = LineBreakBeforeClose(ns, name);
 
     if (mColPos && lineBreakBeforeClose) {
-      NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     }
     if (!mColPos) {
-      NS_ENSURE_TRUE(AppendIndentation(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendIndentation(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     } else if (mAddSpace) {
-      bool result = AppendToString(char16_t(' '), aStr);
+      bool result = AppendToString(char16_t(' '), *mOutput);
       mAddSpace = false;
       NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
     }
   } else if (mAddSpace) {
-    bool result = AppendToString(char16_t(' '), aStr);
+    bool result = AppendToString(char16_t(' '), *mOutput);
     mAddSpace = false;
     NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
   }
 
-  NS_ENSURE_TRUE(AppendToString(kEndTag, aStr), NS_ERROR_OUT_OF_MEMORY);
-  NS_ENSURE_TRUE(AppendToString(nsDependentAtomString(name), aStr),
+  NS_ENSURE_TRUE(AppendToString(kEndTag, *mOutput), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(nsDependentAtomString(name), *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
-  NS_ENSURE_TRUE(AppendToString(kGreaterThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(kGreaterThan, *mOutput),
+                 NS_ERROR_OUT_OF_MEMORY);
 
   // Keep this cleanup in sync with the IsContainer() early return above.
   MaybeLeaveFromPreContent(aElement);
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
       LineBreakAfterClose(ns, name)) {
-    NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
   } else {
     MaybeFlagNewlineForRootNode(aElement);
   }
 
   if (name == nsGkAtoms::body && ns == kNameSpaceID_XHTML) {
     --mInBody;
   }
 
--- a/dom/base/nsHTMLContentSerializer.h
+++ b/dom/base/nsHTMLContentSerializer.h
@@ -19,26 +19,24 @@
 
 class nsAtom;
 
 class nsHTMLContentSerializer final : public nsXHTMLContentSerializer {
  public:
   nsHTMLContentSerializer();
   virtual ~nsHTMLContentSerializer();
 
-  NS_IMETHOD AppendElementStart(mozilla::dom::Element* aElement,
-                                mozilla::dom::Element* aOriginalElement,
-                                nsAString& aStr) override;
+  NS_IMETHOD AppendElementStart(
+      mozilla::dom::Element* aElement,
+      mozilla::dom::Element* aOriginalElement) override;
 
   NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement,
-                              mozilla::dom::Element* aOriginalElement,
-                              nsAString& aStr) override;
+                              mozilla::dom::Element* aOriginalElement) override;
 
-  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument,
-                                 nsAString& aStr) override;
+  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument) override;
 
  protected:
   MOZ_MUST_USE
   virtual bool SerializeHTMLAttributes(mozilla::dom::Element* aContent,
                                        mozilla::dom::Element* aOriginalElement,
                                        nsAString& aTagPrefix,
                                        const nsAString& aTagNamespaceURI,
                                        nsAtom* aTagName, int32_t aNamespace,
--- a/dom/base/nsIContentSerializer.h
+++ b/dom/base/nsIContentSerializer.h
@@ -29,54 +29,61 @@ class ProcessingInstruction;
       0x93, 0xdf, 0xb6, 0xfa, 0xb5, 0xd5, 0x46, 0x88 \
     }                                                \
   }
 
 class nsIContentSerializer : public nsISupports {
  public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENTSERIALIZER_IID)
 
+  /**
+   * @param aOutput The `Append*` methods will append to this string. The
+   *        reference to it will be dropped with `Finish`.
+   */
   NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
                   const mozilla::Encoding* aEncoding, bool aIsCopying,
-                  bool aIsWholeDocument, bool* aNeedsPerformatScanning) = 0;
+                  bool aIsWholeDocument, bool* aNeedsPerformatScanning,
+                  nsAString& aOutput) = 0;
 
   NS_IMETHOD AppendText(nsIContent* aText, int32_t aStartOffset,
-                        int32_t aEndOffset, nsAString& aStr) = 0;
+                        int32_t aEndOffset) = 0;
 
   NS_IMETHOD AppendCDATASection(nsIContent* aCDATASection, int32_t aStartOffset,
-                                int32_t aEndOffset, nsAString& aStr) = 0;
+                                int32_t aEndOffset) = 0;
 
   NS_IMETHOD AppendProcessingInstruction(
       mozilla::dom::ProcessingInstruction* aPI, int32_t aStartOffset,
-      int32_t aEndOffset, nsAString& aStr) = 0;
+      int32_t aEndOffset) = 0;
 
   NS_IMETHOD AppendComment(mozilla::dom::Comment* aComment,
-                           int32_t aStartOffset, int32_t aEndOffset,
-                           nsAString& aStr) = 0;
+                           int32_t aStartOffset, int32_t aEndOffset) = 0;
 
-  NS_IMETHOD AppendDoctype(mozilla::dom::DocumentType* aDoctype,
-                           nsAString& aStr) = 0;
+  NS_IMETHOD AppendDoctype(mozilla::dom::DocumentType* aDoctype) = 0;
 
   NS_IMETHOD AppendElementStart(mozilla::dom::Element* aElement,
-                                mozilla::dom::Element* aOriginalElement,
-                                nsAString& aStr) = 0;
+                                mozilla::dom::Element* aOriginalElement) = 0;
 
   NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement,
-                              mozilla::dom::Element* aOriginalElement,
-                              nsAString& aStr) = 0;
+                              mozilla::dom::Element* aOriginalElement) = 0;
+
+  NS_IMETHOD FlushAndFinish() = 0;
 
-  NS_IMETHOD Flush(nsAString& aStr) = 0;
+  /**
+   * Drops the reference to the output buffer.
+   */
+  NS_IMETHOD Finish() = 0;
+
+  NS_IMETHOD GetOutputLength(uint32_t& aLength) const = 0;
 
   /**
    * Append any items in the beginning of the document that won't be
    * serialized by other methods. XML declaration is the most likely
    * thing this method can produce.
    */
-  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument,
-                                 nsAString& aStr) = 0;
+  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument) = 0;
 
   // If Init() sets *aNeedsPerformatScanning to true, then these methods are
   // called when elements are started and ended, before AppendElementStart
   // and AppendElementEnd, respectively.  They are supposed to be used to
   // allow the implementer to keep track of whether the element is
   // preformatted.
   NS_IMETHOD ScanElementForPreformat(mozilla::dom::Element* aElement) = 0;
   NS_IMETHOD ForgetElementForPreformat(mozilla::dom::Element* aElement) = 0;
--- a/dom/base/nsPlainTextSerializer.cpp
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -121,17 +121,17 @@ void nsPlainTextSerializer::CurrentLineC
 }
 
 nsPlainTextSerializer::nsPlainTextSerializer()
     : mCurrentLineContent{kNoFlags},
       mFloatingLines(-1),
       mLineBreakDue(false),
       kSpace(NS_LITERAL_STRING(" "))  // Init of "constant"
 {
-  mOutputString = nullptr;
+  mOutput = nullptr;
   mHeadLevel = 0;
   mAtFirstColumn = true;
   mIndent = 0;
   mCiteQuoteLevel = 0;
   mHasWrittenCiteBlockquote = false;
   mSpanLevel = 0;
   for (int32_t i = 0; i <= 6; i++) {
     mHeaderCounter[i] = 0;
@@ -195,32 +195,32 @@ void nsPlainTextSerializer::Settings::In
   // XXX We should let the caller decide whether to do this or not
   mFlags &= ~nsIDocumentEncoder::OutputNoFramesContent;
 }
 
 NS_IMETHODIMP
 nsPlainTextSerializer::Init(const uint32_t aFlags, uint32_t aWrapColumn,
                             const Encoding* aEncoding, bool aIsCopying,
                             bool aIsWholeDocument,
-                            bool* aNeedsPreformatScanning) {
+                            bool* aNeedsPreformatScanning, nsAString& aOutput) {
 #ifdef DEBUG
   // Check if the major control flags are set correctly.
   if (aFlags & nsIDocumentEncoder::OutputFormatFlowed) {
     NS_ASSERTION(aFlags & nsIDocumentEncoder::OutputFormatted,
                  "If you want format=flowed, you must combine it with "
                  "nsIDocumentEncoder::OutputFormatted");
   }
 
   if (aFlags & nsIDocumentEncoder::OutputFormatted) {
     NS_ASSERTION(
         !(aFlags & nsIDocumentEncoder::OutputPreformatted),
         "Can't do formatted and preformatted output at the same time!");
   }
 #endif
-
+  mOutput = &aOutput;
   *aNeedsPreformatScanning = true;
   mSettings.Init(aFlags);
   mWrapColumn = aWrapColumn;
 
   // Only create a linebreaker if we will handle wrapping.
   if (MayWrap() && MayBreakLines()) {
     mLineBreaker = nsContentUtils::LineBreaker();
   }
@@ -285,17 +285,19 @@ static bool IsDisplayNone(Element* aElem
 
 static bool IsIgnorableScriptOrStyle(Element* aElement) {
   return aElement->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::style) &&
          IsDisplayNone(aElement);
 }
 
 NS_IMETHODIMP
 nsPlainTextSerializer::AppendText(nsIContent* aText, int32_t aStartOffset,
-                                  int32_t aEndOffset, nsAString& aStr) {
+                                  int32_t aEndOffset) {
+  NS_ENSURE_STATE(mOutput);
+
   if (mIgnoreAboveIndex != (uint32_t)kNotFound) {
     return NS_OK;
   }
 
   NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
   if (aStartOffset < 0) return NS_ERROR_INVALID_ARG;
 
   NS_ENSURE_ARG(aText);
@@ -328,18 +330,16 @@ nsPlainTextSerializer::AppendText(nsICon
     CopyASCIItoUTF16(Substring(data + aStartOffset, data + endoffset), textstr);
   }
 
   // Mask the text if the text node is in a password field.
   if (content->HasFlag(NS_MAYBE_MASKED)) {
     EditorUtils::MaskString(textstr, content->AsText(), 0, aStartOffset);
   }
 
-  mOutputString = &aStr;
-
   // We have to split the string across newlines
   // to match parser behavior
   int32_t start = 0;
   int32_t offset = textstr.FindCharInSet("\n\r");
   while (offset != kNotFound) {
     if (offset > start) {
       // Pass in the line
       DoAddText(false, Substring(textstr, start, offset - start));
@@ -356,26 +356,24 @@ nsPlainTextSerializer::AppendText(nsICon
   if (start < length) {
     if (start) {
       DoAddText(false, Substring(textstr, start, length - start));
     } else {
       DoAddText(false, textstr);
     }
   }
 
-  mOutputString = nullptr;
-
   return rv;
 }
 
 NS_IMETHODIMP
 nsPlainTextSerializer::AppendCDATASection(nsIContent* aCDATASection,
                                           int32_t aStartOffset,
-                                          int32_t aEndOffset, nsAString& aStr) {
-  return AppendText(aCDATASection, aStartOffset, aEndOffset, aStr);
+                                          int32_t aEndOffset) {
+  return AppendText(aCDATASection, aStartOffset, aEndOffset);
 }
 
 NS_IMETHODIMP
 nsPlainTextSerializer::ScanElementForPreformat(Element* aElement) {
   mPreformatStack.push(IsElementPreformatted(aElement));
   return NS_OK;
 }
 
@@ -384,87 +382,96 @@ nsPlainTextSerializer::ForgetElementForP
   MOZ_RELEASE_ASSERT(!mPreformatStack.empty(),
                      "Tried to pop without previous push.");
   mPreformatStack.pop();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPlainTextSerializer::AppendElementStart(Element* aElement,
-                                          Element* aOriginalElement,
-                                          nsAString& aStr) {
+                                          Element* aOriginalElement) {
   NS_ENSURE_ARG(aElement);
+  NS_ENSURE_STATE(mOutput);
 
   mElement = aElement;
 
   nsresult rv;
   nsAtom* id = GetIdForContent(mElement);
 
   bool isContainer = !FragmentOrElement::IsHTMLVoid(id);
 
-  mOutputString = &aStr;
-
   if (isContainer) {
     rv = DoOpenContainer(id);
   } else {
     rv = DoAddLeaf(id);
   }
 
   mElement = nullptr;
-  mOutputString = nullptr;
 
   if (id == nsGkAtoms::head) {
     ++mHeadLevel;
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsPlainTextSerializer::AppendElementEnd(Element* aElement,
-                                        Element* aOriginalElement,
-                                        nsAString& aStr) {
+                                        Element* aOriginalElement) {
   NS_ENSURE_ARG(aElement);
+  NS_ENSURE_STATE(mOutput);
 
   mElement = aElement;
 
   nsresult rv;
   nsAtom* id = GetIdForContent(mElement);
 
   bool isContainer = !FragmentOrElement::IsHTMLVoid(id);
 
-  mOutputString = &aStr;
-
   rv = NS_OK;
   if (isContainer) {
     rv = DoCloseContainer(id);
   }
 
   mElement = nullptr;
-  mOutputString = nullptr;
 
   if (id == nsGkAtoms::head) {
     NS_ASSERTION(mHeadLevel != 0, "mHeadLevel being decremented below 0");
     --mHeadLevel;
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
-nsPlainTextSerializer::Flush(nsAString& aStr) {
-  mOutputString = &aStr;
+nsPlainTextSerializer::FlushAndFinish() {
   FlushLine();
-  mOutputString = nullptr;
+  return Finish();
+}
+
+NS_IMETHODIMP
+nsPlainTextSerializer::Finish() {
+  NS_ENSURE_STATE(mOutput);
+
+  mOutput = nullptr;
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsPlainTextSerializer::AppendDocumentStart(Document* aDocument,
-                                           nsAString& aStr) {
+nsPlainTextSerializer::GetOutputLength(uint32_t& aLength) const {
+  NS_ENSURE_STATE(mOutput);
+
+  aLength = mOutput->Length();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPlainTextSerializer::AppendDocumentStart(Document* aDocument) {
   return NS_OK;
 }
 
 nsresult nsPlainTextSerializer::DoOpenContainer(nsAtom* aTag) {
   if (IsIgnorableRubyAnnotation(aTag)) {
     // Ignorable ruby annotation shouldn't be replaced by a placeholder
     // character, neither any of its descendants.
     mIgnoredChildNodeLevel++;
@@ -1129,17 +1136,19 @@ void nsPlainTextSerializer::FlushLine() 
     Output(mCurrentLineContent.mValue);
     mAtFirstColumn = false;
     mCurrentLineContent.mValue.Truncate();
     mCurrentLineContent.mWidth = 0;
   }
 }
 
 void nsPlainTextSerializer::Output(nsString& aString) {
-  mOutputString->Append(aString);
+  MOZ_ASSERT(mOutput);
+
+  mOutput->Append(aString);
 }
 
 static bool IsSpaceStuffable(const char16_t* s) {
   if (s[0] == '>' || s[0] == ' ' || s[0] == kNBSP ||
       NS_strncmp(s, u"From ", 5) == 0)
     return true;
   else
     return false;
--- a/dom/base/nsPlainTextSerializer.h
+++ b/dom/base/nsPlainTextSerializer.h
@@ -1,9 +1,10 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * nsIContentSerializer implementation that can be used with an
  * nsIDocumentEncoder to convert a DOM into plaintext in a nice way
@@ -39,47 +40,48 @@ class nsPlainTextSerializer final : publ
   nsPlainTextSerializer();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsPlainTextSerializer)
 
   // nsIContentSerializer
   NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
                   const mozilla::Encoding* aEncoding, bool aIsCopying,
-                  bool aIsWholeDocument,
-                  bool* aNeedsPreformatScanning) override;
+                  bool aIsWholeDocument, bool* aNeedsPreformatScanning,
+                  nsAString& aOutput) override;
 
   NS_IMETHOD AppendText(nsIContent* aText, int32_t aStartOffset,
-                        int32_t aEndOffset, nsAString& aStr) override;
+                        int32_t aEndOffset) override;
   NS_IMETHOD AppendCDATASection(nsIContent* aCDATASection, int32_t aStartOffset,
-                                int32_t aEndOffset, nsAString& aStr) override;
+                                int32_t aEndOffset) override;
   NS_IMETHOD AppendProcessingInstruction(
       mozilla::dom::ProcessingInstruction* aPI, int32_t aStartOffset,
-      int32_t aEndOffset, nsAString& aStr) override {
+      int32_t aEndOffset) override {
     return NS_OK;
   }
   NS_IMETHOD AppendComment(mozilla::dom::Comment* aComment,
-                           int32_t aStartOffset, int32_t aEndOffset,
-                           nsAString& aStr) override {
+                           int32_t aStartOffset, int32_t aEndOffset) override {
     return NS_OK;
   }
-  NS_IMETHOD AppendDoctype(mozilla::dom::DocumentType* aDoctype,
-                           nsAString& aStr) override {
+  NS_IMETHOD AppendDoctype(mozilla::dom::DocumentType* aDoctype) override {
     return NS_OK;
   }
-  NS_IMETHOD AppendElementStart(mozilla::dom::Element* aElement,
-                                mozilla::dom::Element* aOriginalElement,
-                                nsAString& aStr) override;
+  NS_IMETHOD AppendElementStart(
+      mozilla::dom::Element* aElement,
+      mozilla::dom::Element* aOriginalElement) override;
   NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement,
-                              mozilla::dom::Element* aOriginalElement,
-                              nsAString& aStr) override;
-  NS_IMETHOD Flush(nsAString& aStr) override;
+                              mozilla::dom::Element* aOriginalElement) override;
+
+  NS_IMETHOD FlushAndFinish() override;
 
-  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument,
-                                 nsAString& aStr) override;
+  NS_IMETHOD Finish() override;
+
+  NS_IMETHOD GetOutputLength(uint32_t& aLength) const override;
+
+  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument) override;
 
   NS_IMETHOD ScanElementForPreformat(mozilla::dom::Element* aElement) override;
   NS_IMETHOD ForgetElementForPreformat(
       mozilla::dom::Element* aElement) override;
 
  private:
   ~nsPlainTextSerializer();
 
@@ -264,18 +266,18 @@ class nsPlainTextSerializer final : publ
   RefPtr<mozilla::dom::Element> mElement;
 
   // For handling table rows
   AutoTArray<bool, 8> mHasWrittenCellsForRow;
 
   // Values gotten in OpenContainer that is (also) needed in CloseContainer
   AutoTArray<bool, 8> mIsInCiteBlockquote;
 
-  // The output data
-  nsAString* mOutputString;
+  // Non-owning.
+  nsAString* mOutput;
 
   // The tag stack: the stack of tags we're operating on, so we can nest.
   // The stack only ever points to static atoms, so they don't need to be
   // refcounted.
   nsAtom** mTagStack;
   uint32_t mTagStackIndex;
 
   // The stack indicating whether the elements we've been operating on are
--- a/dom/base/nsXHTMLContentSerializer.cpp
+++ b/dom/base/nsXHTMLContentSerializer.cpp
@@ -55,29 +55,30 @@ nsXHTMLContentSerializer::nsXHTMLContent
 nsXHTMLContentSerializer::~nsXHTMLContentSerializer() {
   NS_ASSERTION(mOLStateStack.IsEmpty(), "Expected OL State stack to be empty");
 }
 
 NS_IMETHODIMP
 nsXHTMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
                                const Encoding* aEncoding, bool aIsCopying,
                                bool aRewriteEncodingDeclaration,
-                               bool* aNeedsPreformatScanning) {
+                               bool* aNeedsPreformatScanning,
+                               nsAString& aOutput) {
   // The previous version of the HTML serializer did implicit wrapping
   // when there is no flags, so we keep wrapping in order to keep
   // compatibility with the existing calling code
   // XXXLJ perhaps should we remove this default settings later ?
   if (aFlags & nsIDocumentEncoder::OutputFormatted) {
     aFlags = aFlags | nsIDocumentEncoder::OutputWrap;
   }
 
   nsresult rv;
   rv = nsXMLContentSerializer::Init(aFlags, aWrapColumn, aEncoding, aIsCopying,
                                     aRewriteEncodingDeclaration,
-                                    aNeedsPreformatScanning);
+                                    aNeedsPreformatScanning, aOutput);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mRewriteEncodingDeclaration = aRewriteEncodingDeclaration;
   mIsCopying = aIsCopying;
   mIsFirstChildOfOL = false;
   mInBody = 0;
   mDisableEntityEncoding = 0;
   mBodyOnly = (mFlags & nsIDocumentEncoder::OutputBodyOnly) ? true : false;
@@ -104,42 +105,45 @@ bool nsXHTMLContentSerializer::HasLongLi
     if (int32_t(eol - start) > kLongLineLen) rv = true;
     start = eol + 1;
   }
   return rv;
 }
 
 NS_IMETHODIMP
 nsXHTMLContentSerializer::AppendText(nsIContent* aText, int32_t aStartOffset,
-                                     int32_t aEndOffset, nsAString& aStr) {
+                                     int32_t aEndOffset) {
   NS_ENSURE_ARG(aText);
+  NS_ENSURE_STATE(mOutput);
 
   nsAutoString data;
   nsresult rv;
 
   rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
 
   if (mDoRaw || PreLevel() > 0) {
-    NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoFormat) {
-    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, aStr),
+    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoWrap) {
-    NS_ENSURE_TRUE(AppendToStringWrapped(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToStringWrapped(data, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   } else {
     int32_t lastNewlineOffset = kNotFound;
     if (HasLongLines(data, lastNewlineOffset)) {
       // We have long lines, rewrap
       mDoWrap = true;
-      bool result = AppendToStringWrapped(data, aStr);
+      bool result = AppendToStringWrapped(data, *mOutput);
       mDoWrap = false;
       NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
     } else {
-      NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr),
+      NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
                      NS_ERROR_OUT_OF_MEMORY);
     }
   }
 
   return NS_OK;
 }
 
 bool nsXHTMLContentSerializer::SerializeAttributes(
@@ -370,20 +374,20 @@ void nsXHTMLContentSerializer::AfterElem
   // this method is not called by nsHTMLContentSerializer
   // so we don't have to check HTML element, just XHTML
   if (aContent->IsHTMLElement(nsGkAtoms::body)) {
     --mInBody;
   }
 }
 
 NS_IMETHODIMP
-nsXHTMLContentSerializer::AppendDocumentStart(Document* aDocument,
-                                              nsAString& aStr) {
-  if (!mBodyOnly)
-    return nsXMLContentSerializer::AppendDocumentStart(aDocument, aStr);
+nsXHTMLContentSerializer::AppendDocumentStart(Document* aDocument) {
+  if (!mBodyOnly) {
+    return nsXMLContentSerializer::AppendDocumentStart(aDocument);
+  }
 
   return NS_OK;
 }
 
 bool nsXHTMLContentSerializer::CheckElementStart(Element* aElement,
                                                  bool& aForceFormat,
                                                  nsAString& aStr,
                                                  nsresult& aResult) {
--- a/dom/base/nsXHTMLContentSerializer.h
+++ b/dom/base/nsXHTMLContentSerializer.h
@@ -28,23 +28,22 @@ class Encoding;
 class nsXHTMLContentSerializer : public nsXMLContentSerializer {
  public:
   nsXHTMLContentSerializer();
   virtual ~nsXHTMLContentSerializer();
 
   NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
                   const mozilla::Encoding* aEncoding, bool aIsCopying,
                   bool aRewriteEncodingDeclaration,
-                  bool* aNeedsPreformatScanning) override;
+                  bool* aNeedsPreformatScanning, nsAString& aOutput) override;
 
   NS_IMETHOD AppendText(nsIContent* aText, int32_t aStartOffset,
-                        int32_t aEndOffset, nsAString& aStr) override;
+                        int32_t aEndOffset) override;
 
-  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument,
-                                 nsAString& aStr) override;
+  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument) override;
 
  protected:
   virtual bool CheckElementStart(mozilla::dom::Element* aElement,
                                  bool& aForceFormat, nsAString& aStr,
                                  nsresult& aResult) override;
 
   MOZ_MUST_USE
   virtual bool AfterElementStart(nsIContent* aContent,
--- a/dom/base/nsXMLContentSerializer.cpp
+++ b/dom/base/nsXMLContentSerializer.cpp
@@ -70,17 +70,18 @@ nsXMLContentSerializer::nsXMLContentSeri
 nsXMLContentSerializer::~nsXMLContentSerializer() {}
 
 NS_IMPL_ISUPPORTS(nsXMLContentSerializer, nsIContentSerializer)
 
 NS_IMETHODIMP
 nsXMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
                              const Encoding* aEncoding, bool aIsCopying,
                              bool aRewriteEncodingDeclaration,
-                             bool* aNeedsPreformatScanning) {
+                             bool* aNeedsPreformatScanning,
+                             nsAString& aOutput) {
   *aNeedsPreformatScanning = false;
   mPrefixIndex = 0;
   mColPos = 0;
   mIndentOverflow = 0;
   mIsIndentationAddedOnCurrentLine = false;
   mInAttribute = false;
   mAddNewlineForRootNode = false;
   mAddSpace = false;
@@ -115,16 +116,17 @@ nsXMLContentSerializer::Init(uint32_t aF
       !(mFlags & nsIDocumentEncoder::OutputDisallowLineBreaking);
 
   if (!aWrapColumn) {
     mMaxColumn = 72;
   } else {
     mMaxColumn = aWrapColumn;
   }
 
+  mOutput = &aOutput;
   mPreLevel = 0;
   mIsIndentationAddedOnCurrentLine = false;
   return NS_OK;
 }
 
 nsresult nsXMLContentSerializer::AppendTextData(nsIContent* aNode,
                                                 int32_t aStartOffset,
                                                 int32_t aEndOffset,
@@ -177,217 +179,237 @@ nsresult nsXMLContentSerializer::AppendT
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXMLContentSerializer::AppendText(nsIContent* aText, int32_t aStartOffset,
-                                   int32_t aEndOffset, nsAString& aStr) {
+                                   int32_t aEndOffset) {
   NS_ENSURE_ARG(aText);
+  NS_ENSURE_STATE(mOutput);
 
   nsAutoString data;
   nsresult rv;
 
   rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
 
   if (mDoRaw || PreLevel() > 0) {
-    NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoFormat) {
-    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, aStr),
+    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoWrap) {
-    NS_ENSURE_TRUE(AppendToStringWrapped(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToStringWrapped(data, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   } else {
-    NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXMLContentSerializer::AppendCDATASection(nsIContent* aCDATASection,
                                            int32_t aStartOffset,
-                                           int32_t aEndOffset,
-                                           nsAString& aStr) {
+                                           int32_t aEndOffset) {
   NS_ENSURE_ARG(aCDATASection);
+  NS_ENSURE_STATE(mOutput);
+
   nsresult rv;
 
   NS_NAMED_LITERAL_STRING(cdata, "<![CDATA[");
 
   if (mDoRaw || PreLevel() > 0) {
-    NS_ENSURE_TRUE(AppendToString(cdata, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(cdata, *mOutput), NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoFormat) {
-    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(cdata, aStr),
+    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(cdata, *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoWrap) {
-    NS_ENSURE_TRUE(AppendToStringWrapped(cdata, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToStringWrapped(cdata, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   } else {
-    NS_ENSURE_TRUE(AppendToString(cdata, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(cdata, *mOutput), NS_ERROR_OUT_OF_MEMORY);
   }
 
   nsAutoString data;
   rv = AppendTextData(aCDATASection, aStartOffset, aEndOffset, data, false);
   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
 
-  NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
+                 NS_ERROR_OUT_OF_MEMORY);
 
-  NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("]]>"), aStr),
+  NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("]]>"), *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXMLContentSerializer::AppendProcessingInstruction(ProcessingInstruction* aPI,
                                                     int32_t aStartOffset,
-                                                    int32_t aEndOffset,
-                                                    nsAString& aStr) {
+                                                    int32_t aEndOffset) {
+  NS_ENSURE_STATE(mOutput);
+
   nsAutoString target, data, start;
 
-  NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
 
   aPI->GetTarget(target);
 
   aPI->GetData(data);
 
   NS_ENSURE_TRUE(start.AppendLiteral("<?", mozilla::fallible),
                  NS_ERROR_OUT_OF_MEMORY);
   NS_ENSURE_TRUE(start.Append(target, mozilla::fallible),
                  NS_ERROR_OUT_OF_MEMORY);
 
   if (mDoRaw || PreLevel() > 0) {
-    NS_ENSURE_TRUE(AppendToString(start, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(start, *mOutput), NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoFormat) {
     if (mAddSpace) {
-      NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     }
-    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(start, aStr),
+    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(start, *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoWrap) {
-    NS_ENSURE_TRUE(AppendToStringWrapped(start, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToStringWrapped(start, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   } else {
-    NS_ENSURE_TRUE(AppendToString(start, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(start, *mOutput), NS_ERROR_OUT_OF_MEMORY);
   }
 
   if (!data.IsEmpty()) {
-    NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   }
-  NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("?>"), aStr),
+  NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("?>"), *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
 
   MaybeFlagNewlineForRootNode(aPI);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXMLContentSerializer::AppendComment(Comment* aComment, int32_t aStartOffset,
-                                      int32_t aEndOffset, nsAString& aStr) {
+                                      int32_t aEndOffset) {
+  NS_ENSURE_STATE(mOutput);
+
   nsAutoString data;
   aComment->GetData(data);
 
   int32_t dataLength = data.Length();
   if (aStartOffset || (aEndOffset != -1 && aEndOffset < dataLength)) {
     int32_t length =
         (aEndOffset == -1) ? dataLength : std::min(aEndOffset, dataLength);
     length -= aStartOffset;
 
     nsAutoString frag;
     if (length > 0) {
       data.Mid(frag, aStartOffset, length);
     }
     data.Assign(frag);
   }
 
-  NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
 
   NS_NAMED_LITERAL_STRING(startComment, "<!--");
 
   if (mDoRaw || PreLevel() > 0) {
-    NS_ENSURE_TRUE(AppendToString(startComment, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(startComment, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoFormat) {
     if (mAddSpace) {
-      NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     }
-    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(startComment, aStr),
+    NS_ENSURE_TRUE(AppendToStringFormatedWrapped(startComment, *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
   } else if (mDoWrap) {
-    NS_ENSURE_TRUE(AppendToStringWrapped(startComment, aStr),
+    NS_ENSURE_TRUE(AppendToStringWrapped(startComment, *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
   } else {
-    NS_ENSURE_TRUE(AppendToString(startComment, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(startComment, *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   }
 
   // Even if mDoformat, we don't format the content because it
   // could have been preformated by the author
-  NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
-  NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("-->"), aStr),
+  NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
+                 NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("-->"), *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
 
   MaybeFlagNewlineForRootNode(aComment);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXMLContentSerializer::AppendDoctype(DocumentType* aDocType, nsAString& aStr) {
+nsXMLContentSerializer::AppendDoctype(DocumentType* aDocType) {
+  NS_ENSURE_STATE(mOutput);
+
   nsAutoString name, publicId, systemId;
   aDocType->GetName(name);
   aDocType->GetPublicId(publicId);
   aDocType->GetSystemId(systemId);
 
-  NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
 
-  NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("<!DOCTYPE "), aStr),
+  NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("<!DOCTYPE "), *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
-  NS_ENSURE_TRUE(AppendToString(name, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(name, *mOutput), NS_ERROR_OUT_OF_MEMORY);
 
   char16_t quote;
   if (!publicId.IsEmpty()) {
-    NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" PUBLIC "), aStr),
+    NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" PUBLIC "), *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
     if (publicId.FindChar(char16_t('"')) == -1) {
       quote = char16_t('"');
     } else {
       quote = char16_t('\'');
     }
-    NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_TRUE(AppendToString(publicId, aStr), NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(publicId, *mOutput), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
 
     if (!systemId.IsEmpty()) {
-      NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr),
+      NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
                      NS_ERROR_OUT_OF_MEMORY);
       if (systemId.FindChar(char16_t('"')) == -1) {
         quote = char16_t('"');
       } else {
         quote = char16_t('\'');
       }
-      NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
-      NS_ENSURE_TRUE(AppendToString(systemId, aStr), NS_ERROR_OUT_OF_MEMORY);
-      NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendToString(systemId, *mOutput),
+                     NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
     }
   } else if (!systemId.IsEmpty()) {
     if (systemId.FindChar(char16_t('"')) == -1) {
       quote = char16_t('"');
     } else {
       quote = char16_t('\'');
     }
-    NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" SYSTEM "), aStr),
+    NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" SYSTEM "), *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_TRUE(AppendToString(systemId, aStr), NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(systemId, *mOutput), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(quote, *mOutput), NS_ERROR_OUT_OF_MEMORY);
   }
 
-  NS_ENSURE_TRUE(AppendToString(kGreaterThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(kGreaterThan, *mOutput),
+                 NS_ERROR_OUT_OF_MEMORY);
   MaybeFlagNewlineForRootNode(aDocType);
 
   return NS_OK;
 }
 
 nsresult nsXMLContentSerializer::PushNameSpaceDecl(const nsAString& aPrefix,
                                                    const nsAString& aURI,
                                                    nsIContent* aOwner) {
@@ -815,23 +837,23 @@ bool nsXMLContentSerializer::SerializeAt
     }
   }
 
   return true;
 }
 
 NS_IMETHODIMP
 nsXMLContentSerializer::AppendElementStart(Element* aElement,
-                                           Element* aOriginalElement,
-                                           nsAString& aStr) {
+                                           Element* aOriginalElement) {
   NS_ENSURE_ARG(aElement);
+  NS_ENSURE_STATE(mOutput);
 
   bool forceFormat = false;
   nsresult rv = NS_OK;
-  if (!CheckElementStart(aElement, forceFormat, aStr, rv)) {
+  if (!CheckElementStart(aElement, forceFormat, *mOutput, rv)) {
     // When we go to AppendElementEnd for this element, we're going to
     // MaybeLeaveFromPreContent().  So make sure to MaybeEnterInPreContent()
     // now, so our PreLevel() doesn't get confused.
     MaybeEnterInPreContent(aElement);
     return rv;
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
@@ -845,71 +867,75 @@ nsXMLContentSerializer::AppendElementSta
       ScanNamespaceDeclarations(aElement, aOriginalElement, tagNamespaceURI);
 
   nsAtom* name = aElement->NodeInfo()->NameAtom();
   bool lineBreakBeforeOpen =
       LineBreakBeforeOpen(aElement->GetNameSpaceID(), name);
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
     if (mColPos && lineBreakBeforeOpen) {
-      NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     } else {
-      NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
+                     NS_ERROR_OUT_OF_MEMORY);
     }
     if (!mColPos) {
-      NS_ENSURE_TRUE(AppendIndentation(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendIndentation(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     } else if (mAddSpace) {
-      NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr),
+      NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
                      NS_ERROR_OUT_OF_MEMORY);
       mAddSpace = false;
     }
   } else if (mAddSpace) {
-    NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
     mAddSpace = false;
   } else {
-    NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
   }
 
   // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode
   // wasn't called
   mAddNewlineForRootNode = false;
 
   bool addNSAttr;
   addNSAttr =
       ConfirmPrefix(tagPrefix, tagNamespaceURI, aOriginalElement, false);
 
   // Serialize the qualified name of the element
-  NS_ENSURE_TRUE(AppendToString(kLessThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(kLessThan, *mOutput), NS_ERROR_OUT_OF_MEMORY);
   if (!tagPrefix.IsEmpty()) {
-    NS_ENSURE_TRUE(AppendToString(tagPrefix, aStr), NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(":"), aStr),
+    NS_ENSURE_TRUE(AppendToString(tagPrefix, *mOutput), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(":"), *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
   }
-  NS_ENSURE_TRUE(AppendToString(tagLocalName, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(tagLocalName, *mOutput),
+                 NS_ERROR_OUT_OF_MEMORY);
 
   MaybeEnterInPreContent(aElement);
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
     NS_ENSURE_TRUE(IncrIndentation(name), NS_ERROR_OUT_OF_MEMORY);
   }
 
   NS_ENSURE_TRUE(
       SerializeAttributes(aElement, aOriginalElement, tagPrefix,
-                          tagNamespaceURI, name, aStr, skipAttr, addNSAttr),
+                          tagNamespaceURI, name, *mOutput, skipAttr, addNSAttr),
       NS_ERROR_OUT_OF_MEMORY);
 
-  NS_ENSURE_TRUE(AppendEndOfElementStart(aElement, aOriginalElement, aStr),
+  NS_ENSURE_TRUE(AppendEndOfElementStart(aElement, aOriginalElement, *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
       LineBreakAfterOpen(aElement->GetNameSpaceID(), name)) {
-    NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
   }
 
-  NS_ENSURE_TRUE(AfterElementStart(aElement, aOriginalElement, aStr),
+  NS_ENSURE_TRUE(AfterElementStart(aElement, aOriginalElement, *mOutput),
                  NS_ERROR_OUT_OF_MEMORY);
 
   return NS_OK;
 }
 
 // aElement is the actual element we're outputting.  aOriginalElement is the one
 // in the original DOM, which is the one we have to test for kids.
 static bool ElementNeedsSeparateEndTag(Element* aElement,
@@ -951,38 +977,38 @@ bool nsXMLContentSerializer::AppendEndOf
     }
   }
 
   return AppendToString(NS_LITERAL_STRING("/>"), aStr);
 }
 
 NS_IMETHODIMP
 nsXMLContentSerializer::AppendElementEnd(Element* aElement,
-                                         Element* aOriginalElement,
-                                         nsAString& aStr) {
+                                         Element* aOriginalElement) {
   NS_ENSURE_ARG(aElement);
+  NS_ENSURE_STATE(mOutput);
 
   nsIContent* content = aElement;
 
   bool forceFormat = false, outputElementEnd;
   outputElementEnd =
-      CheckElementEnd(aElement, aOriginalElement, forceFormat, aStr);
+      CheckElementEnd(aElement, aOriginalElement, forceFormat, *mOutput);
 
   nsAtom* name = content->NodeInfo()->NameAtom();
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
     DecrIndentation(name);
   }
 
   if (!outputElementEnd) {
     // Keep this in sync with the cleanup at the end of this method.
     PopNameSpaceDeclsFor(aElement);
     MaybeLeaveFromPreContent(content);
     MaybeFlagNewlineForRootNode(aElement);
-    AfterElementEnd(content, aStr);
+    AfterElementEnd(content, *mOutput);
     return NS_OK;
   }
 
   nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
 
   aElement->NodeInfo()->GetPrefix(tagPrefix);
   aElement->NodeInfo()->GetName(tagLocalName);
   aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
@@ -994,88 +1020,109 @@ nsXMLContentSerializer::AppendElementEnd
   NS_ASSERTION(!debugNeedToPushNamespace,
                "Can't push namespaces in closing tag!");
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
     bool lineBreakBeforeClose =
         LineBreakBeforeClose(content->GetNameSpaceID(), name);
 
     if (mColPos && lineBreakBeforeClose) {
-      NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     }
     if (!mColPos) {
-      NS_ENSURE_TRUE(AppendIndentation(aStr), NS_ERROR_OUT_OF_MEMORY);
+      NS_ENSURE_TRUE(AppendIndentation(*mOutput), NS_ERROR_OUT_OF_MEMORY);
     } else if (mAddSpace) {
-      NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr),
+      NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
                      NS_ERROR_OUT_OF_MEMORY);
       mAddSpace = false;
     }
   } else if (mAddSpace) {
-    NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
+                   NS_ERROR_OUT_OF_MEMORY);
     mAddSpace = false;
   }
 
-  NS_ENSURE_TRUE(AppendToString(kEndTag, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(kEndTag, *mOutput), NS_ERROR_OUT_OF_MEMORY);
   if (!tagPrefix.IsEmpty()) {
-    NS_ENSURE_TRUE(AppendToString(tagPrefix, aStr), NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(":"), aStr),
+    NS_ENSURE_TRUE(AppendToString(tagPrefix, *mOutput), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(":"), *mOutput),
                    NS_ERROR_OUT_OF_MEMORY);
   }
-  NS_ENSURE_TRUE(AppendToString(tagLocalName, aStr), NS_ERROR_OUT_OF_MEMORY);
-  NS_ENSURE_TRUE(AppendToString(kGreaterThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(tagLocalName, *mOutput),
+                 NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(AppendToString(kGreaterThan, *mOutput),
+                 NS_ERROR_OUT_OF_MEMORY);
 
   // Keep what follows in sync with the cleanup in the !outputElementEnd case.
   PopNameSpaceDeclsFor(aElement);
 
   MaybeLeaveFromPreContent(content);
 
   if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
       LineBreakAfterClose(content->GetNameSpaceID(), name)) {
-    NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
   } else {
     MaybeFlagNewlineForRootNode(aElement);
   }
 
-  AfterElementEnd(content, aStr);
+  AfterElementEnd(content, *mOutput);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXMLContentSerializer::AppendDocumentStart(Document* aDocument,
-                                            nsAString& aStr) {
+nsXMLContentSerializer::Finish() {
+  NS_ENSURE_STATE(mOutput);
+
+  mOutput = nullptr;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::GetOutputLength(uint32_t& aLength) const {
+  NS_ENSURE_STATE(mOutput);
+
+  aLength = mOutput->Length();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendDocumentStart(Document* aDocument) {
   NS_ENSURE_ARG_POINTER(aDocument);
+  NS_ENSURE_STATE(mOutput);
 
   nsAutoString version, encoding, standalone;
   aDocument->GetXMLDeclaration(version, encoding, standalone);
 
   if (version.IsEmpty())
     return NS_OK;  // A declaration must have version, or there is no decl
 
   NS_NAMED_LITERAL_STRING(endQuote, "\"");
 
-  aStr += NS_LITERAL_STRING("<?xml version=\"") + version + endQuote;
+  *mOutput += NS_LITERAL_STRING("<?xml version=\"") + version + endQuote;
 
   if (!mCharset.IsEmpty()) {
-    aStr += NS_LITERAL_STRING(" encoding=\"") +
-            NS_ConvertASCIItoUTF16(mCharset) + endQuote;
+    *mOutput += NS_LITERAL_STRING(" encoding=\"") +
+                NS_ConvertASCIItoUTF16(mCharset) + endQuote;
   }
   // Otherwise just don't output an encoding attr.  Not that we expect
   // mCharset to ever be empty.
 #ifdef DEBUG
   else {
     NS_WARNING("Empty mCharset?  How come?");
   }
 #endif
 
   if (!standalone.IsEmpty()) {
-    aStr += NS_LITERAL_STRING(" standalone=\"") + standalone + endQuote;
+    *mOutput += NS_LITERAL_STRING(" standalone=\"") + standalone + endQuote;
   }
 
-  NS_ENSURE_TRUE(aStr.AppendLiteral("?>", mozilla::fallible),
+  NS_ENSURE_TRUE(mOutput->AppendLiteral("?>", mozilla::fallible),
                  NS_ERROR_OUT_OF_MEMORY);
   mAddNewlineForRootNode = true;
 
   return NS_OK;
 }
 
 bool nsXMLContentSerializer::CheckElementStart(Element*, bool& aForceFormat,
                                                nsAString& aStr,
--- a/dom/base/nsXMLContentSerializer.h
+++ b/dom/base/nsXMLContentSerializer.h
@@ -34,47 +34,47 @@ class nsXMLContentSerializer : public ns
  public:
   nsXMLContentSerializer();
 
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
                   const mozilla::Encoding* aEncoding, bool aIsCopying,
                   bool aRewriteEncodingDeclaration,
-                  bool* aNeedsPreformatScanning) override;
+                  bool* aNeedsPreformatScanning, nsAString& aOutput) override;
 
   NS_IMETHOD AppendText(nsIContent* aText, int32_t aStartOffset,
-                        int32_t aEndOffset, nsAString& aStr) override;
+                        int32_t aEndOffset) override;
 
   NS_IMETHOD AppendCDATASection(nsIContent* aCDATASection, int32_t aStartOffset,
-                                int32_t aEndOffset, nsAString& aStr) override;
+                                int32_t aEndOffset) override;
 
   NS_IMETHOD AppendProcessingInstruction(
       mozilla::dom::ProcessingInstruction* aPI, int32_t aStartOffset,
-      int32_t aEndOffset, nsAString& aStr) override;
+      int32_t aEndOffset) override;
 
   NS_IMETHOD AppendComment(mozilla::dom::Comment* aComment,
-                           int32_t aStartOffset, int32_t aEndOffset,
-                           nsAString& aStr) override;
+                           int32_t aStartOffset, int32_t aEndOffset) override;
+
+  NS_IMETHOD AppendDoctype(mozilla::dom::DocumentType* aDoctype) override;
 
-  NS_IMETHOD AppendDoctype(mozilla::dom::DocumentType* aDoctype,
-                           nsAString& aStr) override;
-
-  NS_IMETHOD AppendElementStart(mozilla::dom::Element* aElement,
-                                mozilla::dom::Element* aOriginalElement,
-                                nsAString& aStr) override;
+  NS_IMETHOD AppendElementStart(
+      mozilla::dom::Element* aElement,
+      mozilla::dom::Element* aOriginalElement) override;
 
   NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement,
-                              mozilla::dom::Element* aOriginalElement,
-                              nsAString& aStr) override;
+                              mozilla::dom::Element* aOriginalElement) override;
+
+  NS_IMETHOD FlushAndFinish() override { return NS_OK; }
 
-  NS_IMETHOD Flush(nsAString& aStr) override { return NS_OK; }
+  NS_IMETHOD Finish() override;
 
-  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument,
-                                 nsAString& aStr) override;
+  NS_IMETHOD GetOutputLength(uint32_t& aLength) const override;
+
+  NS_IMETHOD AppendDocumentStart(mozilla::dom::Document* aDocument) override;
 
   NS_IMETHOD ScanElementForPreformat(mozilla::dom::Element* aElement) override {
     return NS_OK;
   }
   NS_IMETHOD ForgetElementForPreformat(
       mozilla::dom::Element* aElement) override {
     return NS_OK;
   }
@@ -434,16 +434,19 @@ class nsXMLContentSerializer : public ns
   // says that if the next string to add contains a newline character at the
   // begining, then this newline character should be ignored, because a
   // such character has already been added into the output string
   bool mMayIgnoreLineBreakSequence;
 
   bool mBodyOnly;
   int32_t mInBody;
 
+  // Non-owning.
+  nsAString* mOutput;
+
  private:
   // number of nested elements which have preformated content
   MOZ_INIT_OUTSIDE_CTOR int32_t mPreLevel;
 };
 
 nsresult NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer);
 
 #endif