Bug 574089 - Optimize ::GetInnerHTML, r=jst
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sat, 26 Jun 2010 23:39:56 +0300
changeset 46309 32d027dfd9bebd7dfc0d2f4a9640e63a796a5a44
parent 46308 0247a1f85bcae66d111992b424a4d833c2d70403
child 46310 22734aee737812f5d56f89c8595901e87329ca94
push idunknown
push userunknown
push dateunknown
reviewersjst
bugs574089
milestone1.9.3a6pre
Bug 574089 - Optimize ::GetInnerHTML, r=jst
content/base/public/nsIDocument.h
content/base/public/nsIDocumentEncoder.idl
content/base/src/nsDocument.cpp
content/base/src/nsDocumentEncoder.cpp
content/base/src/nsXHTMLContentSerializer.cpp
content/base/src/nsXMLContentSerializer.cpp
content/html/content/src/nsGenericHTMLElement.cpp
content/html/document/src/nsHTMLDocument.cpp
xpcom/string/public/nsStringBuffer.h
xpcom/string/src/nsSubstring.cpp
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -58,16 +58,17 @@
 #include "nsIObserver.h"
 #include "nsGkAtoms.h"
 #include "nsAutoPtr.h"
 #include "nsPIDOMWindow.h"
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #endif // MOZ_SMIL
 #include "nsIScriptGlobalObject.h"
+#include "nsIDocumentEncoder.h"
 
 class nsIContent;
 class nsPresContext;
 class nsIPresShell;
 class nsIDocShell;
 class nsStyleSet;
 class nsIStyleSheet;
 class nsIStyleRule;
@@ -111,20 +112,19 @@ class Loader;
 
 namespace dom {
 class Link;
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 
-// fbcd570b-dbfa-479b-9bd0-02312129c044
 #define NS_IDOCUMENT_IID      \
-{ 0xfbcd570b, 0xdbfa, 0x479b, \
-  { 0x9b, 0xd0, 0x02, 0x31, 0x21, 0x29, 0xc0, 0x44 } }
+{ 0x1d8bd3d4, 0x6f6d, 0x49fe, \
+  { 0xaf, 0xda, 0xc9, 0x4a, 0xef, 0x8f, 0xcf, 0x1f } }
 
 // Flag for AddStyleSheet().
 #define NS_STYLESHEET_FROM_CATALOG                (1 << 0)
 
 // Document states
 
 // RTL locale: specific to the XUL localedir attribute
 #define NS_DOCUMENT_STATE_RTL_LOCALE              (1 << 0)
@@ -1110,16 +1110,26 @@ public:
   virtual already_AddRefed<nsIParser> GetFragmentParser() {
     return nsnull;
   }
 
   virtual void SetFragmentParser(nsIParser* aParser) {
     // Do nothing.
   }
 
+  already_AddRefed<nsIDocumentEncoder> GetCachedEncoder()
+  {
+    return mCachedEncoder.forget();
+  }
+
+  void SetCachedEncoder(nsIDocumentEncoder* aEncoder)
+  {
+    mCachedEncoder = aEncoder;
+  }
+
   // In case of failure, the document really can't initialize the frame loader.
   virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) = 0;
   // In case of failure, the caller must handle the error, for example by
   // finalizing frame loader asynchronously.
   virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader) = 0;
   // Removes the frame loader of aShell from the initialization list.
   virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) = 0;
   //  Returns true if the frame loader of aShell is in the finalization list.
@@ -1417,16 +1427,27 @@ protected:
   virtual void MutationEventDispatched(nsINode* aTarget) = 0;
   friend class mozAutoSubtreeModified;
 
   virtual Element* GetNameSpaceElement()
   {
     return GetRootElement();
   }
 
+  void SetContentTypeInternal(const nsACString& aType)
+  {
+    mCachedEncoder = nsnull;
+    mContentType = aType;
+  }
+
+  nsCString GetContentTypeInternal() const
+  {
+    return mContentType;
+  }
+
   nsCOMPtr<nsIURI> mDocumentURI;
   nsCOMPtr<nsIURI> mDocumentBaseURI;
 
   nsWeakPtr mDocumentLoadGroup;
 
   nsWeakPtr mDocumentContainer;
 
   nsCString mCharacterSet;
@@ -1525,17 +1546,19 @@ protected:
   // document.
   nsCOMPtr<nsIDocument> mOriginalDocument;
 
   // The bidi options for this document.  What this bitfield means is
   // defined in nsBidiUtils.h
   PRUint32 mBidiOptions;
 
   nsCString mContentLanguage;
+private:
   nsCString mContentType;
+protected:
 
   // The document's security info
   nsCOMPtr<nsISupports> mSecurityInfo;
 
   // if this document is part of a multipart document,
   // the ID can be used to distinguish it from the other parts.
   PRUint32 mPartID;
   
@@ -1555,16 +1578,18 @@ protected:
 
   PRUint32 mEventsSuppressed;
 
   nsString mPendingStateObject;
 
   // Weak reference to mScriptGlobalObject QI:d to nsPIDOMWindow,
   // updated on every set of mSecriptGlobalObject.
   nsPIDOMWindow *mWindow;
+
+  nsCOMPtr<nsIDocumentEncoder> mCachedEncoder;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)
 
 /**
  * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified
  * event is dispatched, if necessary, when the outermost mozAutoSubtreeModified
  * object is deleted.
--- a/content/base/public/nsIDocumentEncoder.idl
+++ b/content/base/public/nsIDocumentEncoder.idl
@@ -39,16 +39,23 @@
 #include "nsISupports.idl"
 
 interface nsIDOMDocument;
 interface nsIDOMRange;
 interface nsISelection;
 interface nsIDOMNode;
 interface nsIOutputStream;
 
+%{ C++
+class nsINode;
+class nsIDocument;
+%}
+[ptr] native nsINodePtr(nsINode);
+[ptr] native nsIDocumentPtr(nsIDocument);
+
 [scriptable, uuid(c0da5b87-0ba7-4d7c-8cb3-fcb02af4253d)]
 interface nsIDocumentEncoderNodeFixup : nsISupports
 {
   /**
    * Create a fixed up version of a node. This method is called before
    * each node in a document is about to be persisted. The implementor
    * may return a new node with fixed up attributes or null. If null is
    * returned the node should be used as-is.
@@ -56,17 +63,17 @@ interface nsIDocumentEncoderNodeFixup : 
    * @param [OUT] aSerializeCloneKids True if the document encoder should
    * apply recursive serialization to the children of the fixed up node
    * instead of the children of the original node.
    * @return The resulting fixed up node.
    */
   nsIDOMNode fixupNode(in nsIDOMNode aNode, out boolean aSerializeCloneKids);
 };
 
-[scriptable, uuid(794a81f6-bde6-4f76-9f5e-0ea0911a2d9f)]
+[scriptable, uuid(7222bdf1-c2b9-41f1-a40a-a3d65283a95b)]
 interface nsIDocumentEncoder : nsISupports
 {
   // Output methods flag bits. There are a frightening number of these,
   // because everyone wants something a little bit different
    
 
   /** 
    * Output only the selection (as opposed to the whole document).
@@ -233,16 +240,19 @@ interface nsIDocumentEncoder : nsISuppor
    * Initialize with a pointer to the document and the mime type.
    * @param aDocument Document to encode.
    * @param aMimeType MimeType to use. May also be set by SetMimeType.
    * @param aFlags Flags to use while encoding. May also be set by SetFlags.
    */
   void init(in nsIDOMDocument aDocument,
             in AString aMimeType,
             in unsigned long aFlags);
+  [noscript] void nativeInit(in nsIDocumentPtr aDocument,
+                             in AString aMimeType,
+                             in unsigned long aFlags);
 
   /**
    *  If the selection is set to a non-null value, then the
    *  selection is used for encoding, otherwise the entire
    *  document is encoded.
    * @param aSelection The selection to encode.
    */
   void setSelection(in nsISelection aSelection);
@@ -265,16 +275,17 @@ interface nsIDocumentEncoder : nsISuppor
 
   /**
    *  If the container is set to a non-null value, then its
    *  child nodes are used for encoding, otherwise the entire
    *  document or range or selection or node is encoded.
    *  @param aContainer The node which child nodes will be encoded.
    */
   void setContainerNode(in nsIDOMNode aContainer);
+  [noscript] void setNativeContainerNode(in nsINodePtr aContainer);
 
   /**
    *  Documents typically have an intrinsic character set,
    *  but if no intrinsic value is found, the platform character set
    *  is used. This function overrides both the intrinisc and platform
    *  charset.
    *  @param aCharset Overrides the both the intrinsic or platform
    *  character set when encoding the document.
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1388,17 +1388,17 @@ nsDOMImplementation::Init(nsIURI* aDocum
 // ==================================================================
 
   // NOTE! nsDocument::operator new() zeroes out all members, so don't
   // bother initializing members to 0.
 
 nsDocument::nsDocument(const char* aContentType)
   : nsIDocument()
 {
-  mContentType = aContentType;
+  SetContentTypeInternal(nsDependentCString(aContentType));
   
 #ifdef PR_LOGGING
   if (!gDocumentLeakPRLog)
     gDocumentLeakPRLog = PR_NewLogModule("DocumentLeak");
 
   if (gDocumentLeakPRLog)
     PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG,
            ("DOCUMENT %p created", this));
@@ -1683,16 +1683,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mStyleAttrStyleSheet, nsIStyleSheet)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptEventManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mXPathEvaluatorTearoff)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mLayoutHistoryState)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnloadBlocker)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstBaseNodeWithHref)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDOMImplementation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOriginalDocument)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCachedEncoder)
 
   // Traverse all our nsCOMArrays.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mStyleSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCatalogSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mPreloadingImages)
 
 #ifdef MOZ_SMIL
   // Traverse animation components
@@ -1726,16 +1727,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   }
   tmp->mFirstChild = nsnull;
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCachedRootElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDisplayDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstBaseNodeWithHref)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDOMImplementation)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOriginalDocument)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCachedEncoder)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_USERDATA
 
   tmp->mParentDocument = nsnull;
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mPreloadingImages)
 
   // nsDocument has a pretty complex destructor, so we're going to
@@ -1942,17 +1944,17 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
 
     // XXXbz what does "just fine" mean exactly?  And given that there
     // is no nsDocShell::SetDocument, what is this talking about?
   }
 
   mLastModified.Truncate();
   // XXXbz I guess we're assuming that the caller will either pass in
   // a channel with a useful type or call SetContentType?
-  mContentType.Truncate();
+  SetContentTypeInternal(EmptyCString());
   mContentLanguage.Truncate();
   mBaseTarget.Truncate();
   mReferrer.Truncate();
 
   mXMLDeclarationBits = 0;
 
   // Now get our new principal
   if (aPrincipal) {
@@ -2150,17 +2152,17 @@ nsDocument::StartDocumentLoad(const char
   nsCAutoString contentType;
   if (NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
     // XXX this is only necessary for viewsource:
     nsACString::const_iterator start, end, semicolon;
     contentType.BeginReading(start);
     contentType.EndReading(end);
     semicolon = start;
     FindCharInReadable(';', semicolon, end);
-    mContentType = Substring(start, semicolon);
+    SetContentTypeInternal(Substring(start, semicolon));
   }
 
   RetrieveRelevantHeaders(aChannel);
 
   mChannel = aChannel;
   
   nsresult rv = InitCSP();
   NS_ENSURE_SUCCESS(rv, rv);
@@ -2431,29 +2433,29 @@ nsDocument::SetApplicationCache(nsIAppli
   mApplicationCache = aApplicationCache;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocument::GetContentType(nsAString& aContentType)
 {
-  CopyUTF8toUTF16(mContentType, aContentType);
+  CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
 
   return NS_OK;
 }
 
 void
 nsDocument::SetContentType(const nsAString& aContentType)
 {
-  NS_ASSERTION(mContentType.IsEmpty() ||
-               mContentType.Equals(NS_ConvertUTF16toUTF8(aContentType)),
+  NS_ASSERTION(GetContentTypeInternal().IsEmpty() ||
+               GetContentTypeInternal().Equals(NS_ConvertUTF16toUTF8(aContentType)),
                "Do you really want to change the content-type?");
 
-  CopyUTF16toUTF8(aContentType, mContentType);
+  SetContentTypeInternal(NS_ConvertUTF16toUTF8(aContentType));
 }
 
 /* Return true if the document is in the focused top-level window, and is an
  * ancestor of the focused DOMWindow. */
 NS_IMETHODIMP
 nsDocument::HasFocus(PRBool* aResult)
 {
   *aResult = PR_FALSE;
@@ -7300,17 +7302,17 @@ nsDocument::CloneDocHelper(nsDocument* c
   // Misc state
 
   // State from nsIDocument
   clone->mCharacterSet = mCharacterSet;
   clone->mCharacterSetSource = mCharacterSetSource;
   clone->mCompatMode = mCompatMode;
   clone->mBidiOptions = mBidiOptions;
   clone->mContentLanguage = mContentLanguage;
-  clone->mContentType = mContentType;
+  clone->SetContentTypeInternal(GetContentTypeInternal());
   clone->mSecurityInfo = mSecurityInfo;
 
   // State from nsDocument
   clone->mIsRegularHTML = mIsRegularHTML;
   clone->mXMLDeclarationBits = mXMLDeclarationBits;
   clone->mBaseTarget = mBaseTarget;
   return NS_OK;
 }
--- a/content/base/src/nsDocumentEncoder.cpp
+++ b/content/base/src/nsDocumentEncoder.cpp
@@ -76,32 +76,33 @@
 #include "nsISelection.h"
 #include "nsISelectionPrivate.h"
 #include "nsITransferable.h" // for kUnicodeMime
 #include "nsContentUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsReadableUtils.h"
 #include "nsTArray.h"
 #include "nsIFrame.h"
+#include "nsStringBuffer.h"
 
 nsresult NS_NewDomSelection(nsISelection **aDomSelection);
 
 enum nsRangeIterationDirection {
   kDirectionOut = -1,
   kDirectionIn = 1
 };
 
 class nsDocumentEncoder : public nsIDocumentEncoder
 {
 public:
   nsDocumentEncoder();
   virtual ~nsDocumentEncoder();
 
-  NS_DECL_ISUPPORTS
-
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder)
   NS_DECL_NSIDOCUMENTENCODER
 
 protected:
   void Initialize();
   nsresult SerializeNodeStart(nsINode* aNode, PRInt32 aStartOffset,
                               PRInt32 aEndOffset, nsAString& aStr,
                               nsINode* aOriginalNode = nsnull);
   nsresult SerializeToStringRecursive(nsINode* aNode,
@@ -170,61 +171,97 @@ protected:
   nsAutoTArray<nsINode*, 8>    mCommonAncestors;
   nsAutoTArray<nsIContent*, 8> mStartNodes;
   nsAutoTArray<PRInt32, 8>     mStartOffsets;
   nsAutoTArray<nsIContent*, 8> mEndNodes;
   nsAutoTArray<PRInt32, 8>     mEndOffsets;
   PRPackedBool      mHaltRangeHint;  
   PRPackedBool      mIsCopying;  // Set to PR_TRUE only while copying
   PRPackedBool      mNodeIsContainer;
+  nsStringBuffer*   mCachedBuffer;
 };
 
-NS_IMPL_ADDREF(nsDocumentEncoder)
-NS_IMPL_RELEASE(nsDocumentEncoder)
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder)
 
-NS_INTERFACE_MAP_BEGIN(nsDocumentEncoder)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocumentEncoder)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocumentEncoder)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder)
    NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder)
    NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-nsDocumentEncoder::nsDocumentEncoder()
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocumentEncoder)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSelection)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRange)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNode)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCommonParent)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDocumentEncoder)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSelection)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRange)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNode)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCommonParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+nsDocumentEncoder::nsDocumentEncoder() : mCachedBuffer(nsnull)
 {
   Initialize();
   mMimeType.AssignLiteral("text/plain");
 
 }
 
 void nsDocumentEncoder::Initialize()
 {
   mFlags = 0;
   mWrapColumn = 72;
   mStartDepth = 0;
   mEndDepth = 0;
   mStartRootIndex = 0;
   mEndRootIndex = 0;
   mHaltRangeHint = PR_FALSE;
   mNodeIsContainer = PR_FALSE;
+  mSerializer = nsnull;
 }
 
 nsDocumentEncoder::~nsDocumentEncoder()
 {
+  if (mCachedBuffer) {
+    mCachedBuffer->Release();
+  }
 }
 
 NS_IMETHODIMP
 nsDocumentEncoder::Init(nsIDOMDocument* aDocument,
                         const nsAString& aMimeType,
                         PRUint32 aFlags)
 {
   if (!aDocument)
     return NS_ERROR_INVALID_ARG;
 
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
+  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+  return NativeInit(doc, aMimeType, aFlags);
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::NativeInit(nsIDocument* aDocument,
+                              const nsAString& aMimeType,
+                              PRUint32 aFlags)
+{
+  if (!aDocument)
+    return NS_ERROR_INVALID_ARG;
+
   Initialize();
 
-  mDocument = do_QueryInterface(aDocument);
-  NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
+  mDocument = aDocument;
 
   mMimeType = aMimeType;
 
   mFlags = aFlags;
   mIsCopying = PR_FALSE;
 
   return NS_OK;
 }
@@ -262,16 +299,24 @@ NS_IMETHODIMP
 nsDocumentEncoder::SetContainerNode(nsIDOMNode *aContainer)
 {
   mNodeIsContainer = PR_TRUE;
   mNode = do_QueryInterface(aContainer);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDocumentEncoder::SetNativeContainerNode(nsINode* aContainer)
+{
+  mNodeIsContainer = PR_TRUE;
+  mNode = aContainer;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocumentEncoder::SetCharset(const nsACString& aCharset)
 {
   mCharset = aCharset;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocumentEncoder::GetMimeType(nsAString& aMimeType)
@@ -923,21 +968,36 @@ nsDocumentEncoder::SerializeRangeToStrin
 NS_IMETHODIMP
 nsDocumentEncoder::EncodeToString(nsAString& aOutputString)
 {
   if (!mDocument)
     return NS_ERROR_NOT_INITIALIZED;
 
   aOutputString.Truncate();
 
-  nsCAutoString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX);
-  AppendUTF16toUTF8(mMimeType, progId);
+  nsString output;
+  static const size_t bufferSize = 2048;
+  if (!mCachedBuffer) {
+    mCachedBuffer = nsStringBuffer::Alloc(bufferSize);
+  }
+  NS_ASSERTION(!mCachedBuffer->IsReadonly(),
+               "DocumentEncoder shouldn't keep reference to non-readonly buffer!");
+  static_cast<PRUnichar*>(mCachedBuffer->Data())[0] = PRUnichar(0);
+  mCachedBuffer->ToString(0, output, PR_TRUE);
+  // output owns the buffer now!
+  mCachedBuffer = nsnull;
+  
 
-  mSerializer = do_CreateInstance(progId.get());
-  NS_ENSURE_TRUE(mSerializer, NS_ERROR_NOT_IMPLEMENTED);
+  if (!mSerializer) {
+    nsCAutoString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX);
+    AppendUTF16toUTF8(mMimeType, progId);
+
+    mSerializer = do_CreateInstance(progId.get());
+    NS_ENSURE_TRUE(mSerializer, NS_ERROR_NOT_IMPLEMENTED);
+  }
 
   nsresult rv = NS_OK;
 
   nsCOMPtr<nsIAtom> charsetAtom;
   if (!mCharset.IsEmpty()) {
     if (!mCharsetConverterManager) {
       mCharsetConverterManager = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -961,57 +1021,69 @@ nsDocumentEncoder::EncodeToString(nsAStr
       // Bug 236546: newlines not added when copying table cells into clipboard
       // Each selected cell shows up as a range containing a row with a single cell
       // get the row, compare it to previous row and emit </tr><tr> as needed
       range->GetStartContainer(getter_AddRefs(node));
       NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
       if (node != prevNode) {
         if (prevNode) {
           nsCOMPtr<nsINode> p = do_QueryInterface(prevNode);
-          rv = SerializeNodeEnd(p, aOutputString);
+          rv = SerializeNodeEnd(p, output);
           NS_ENSURE_SUCCESS(rv, rv);
           prevNode = nsnull;
         }
         nsCOMPtr<nsIContent> content = do_QueryInterface(node);
         if (content && content->Tag() == nsGkAtoms::tr) {
           nsCOMPtr<nsINode> n = do_QueryInterface(node);
-          rv = SerializeNodeStart(n, 0, -1, aOutputString);
+          rv = SerializeNodeStart(n, 0, -1, output);
           NS_ENSURE_SUCCESS(rv, rv);
           prevNode = node;
         }
       }
 
       nsCOMPtr<nsIRange> r = do_QueryInterface(range);
-      rv = SerializeRangeToString(r, aOutputString);
+      rv = SerializeRangeToString(r, output);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     if (prevNode) {
       nsCOMPtr<nsINode> p = do_QueryInterface(prevNode);
-      rv = SerializeNodeEnd(p, aOutputString);
+      rv = SerializeNodeEnd(p, output);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     mSelection = nsnull;
   } else if (mRange) {
-      rv = SerializeRangeToString(mRange, aOutputString);
+      rv = SerializeRangeToString(mRange, output);
 
       mRange = nsnull;
   } else if (mNode) {
-    rv = SerializeToStringRecursive(mNode, aOutputString, mNodeIsContainer);
+    rv = SerializeToStringRecursive(mNode, output, mNodeIsContainer);
     mNode = nsnull;
   } else {
-    rv = mSerializer->AppendDocumentStart(mDocument, aOutputString);
+    rv = mSerializer->AppendDocumentStart(mDocument, output);
 
     if (NS_SUCCEEDED(rv)) {
-      rv = SerializeToStringRecursive(mDocument, aOutputString, PR_FALSE);
+      rv = SerializeToStringRecursive(mDocument, output, PR_FALSE);
     }
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mSerializer->Flush(aOutputString);
+  rv = mSerializer->Flush(output);
+ 
+  if (NS_SUCCEEDED(rv)) {
+    aOutputString.Append(output.get(), output.Length());
+  }
+  mCachedBuffer = nsStringBuffer::FromString(output);
+  // Try to cache the buffer.
+  if (mCachedBuffer && mCachedBuffer->StorageSize() == bufferSize &&
+      !mCachedBuffer->IsReadonly()) {
+    mCachedBuffer->AddRef();
+  } else {
+    mCachedBuffer = nsnull;
+  }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocumentEncoder::EncodeToStream(nsIOutputStream* aStream)
 {
   nsresult rv = NS_OK;
--- a/content/base/src/nsXHTMLContentSerializer.cpp
+++ b/content/base/src/nsXHTMLContentSerializer.cpp
@@ -96,16 +96,18 @@ nsXHTMLContentSerializer::~nsXHTMLConten
   NS_ASSERTION(mOLStateStack.IsEmpty(), "Expected OL State stack to be empty");
 }
 
 NS_IMETHODIMP
 nsXHTMLContentSerializer::Init(PRUint32 aFlags, PRUint32 aWrapColumn,
                               const char* aCharSet, PRBool aIsCopying,
                               PRBool aRewriteEncodingDeclaration)
 {
+  mInBody = 0;
+
   // 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;
   }
 
--- a/content/base/src/nsXMLContentSerializer.cpp
+++ b/content/base/src/nsXMLContentSerializer.cpp
@@ -106,16 +106,25 @@ nsXMLContentSerializer::~nsXMLContentSer
 
 NS_IMPL_ISUPPORTS1(nsXMLContentSerializer, nsIContentSerializer)
 
 NS_IMETHODIMP 
 nsXMLContentSerializer::Init(PRUint32 aFlags, PRUint32 aWrapColumn,
                              const char* aCharSet, PRBool aIsCopying,
                              PRBool aRewriteEncodingDeclaration)
 {
+  mPrefixIndex = 0;
+  mColPos = 0;
+  mIndentOverflow = 0;
+  mIsIndentationAddedOnCurrentLine = PR_FALSE;
+  mInAttribute = PR_FALSE;
+  mAddNewlineForRootNode = PR_FALSE;
+  mAddSpace = PR_FALSE;
+  mMayIgnoreLineBreakSequence = PR_FALSE;
+
   mCharset = aCharSet;
   mFlags = aFlags;
 
   // Set the line break character:
   if ((mFlags & nsIDocumentEncoder::OutputCRLineBreak)
       && (mFlags & nsIDocumentEncoder::OutputLFLineBreak)) { // Windows
     mLineBreak.AssignLiteral("\r\n");
   }
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -650,58 +650,60 @@ nsGenericHTMLElement::GetOffsetParent(ns
   return NS_OK;
 }
 
 nsresult
 nsGenericHTMLElement::GetInnerHTML(nsAString& aInnerHTML)
 {
   aInnerHTML.Truncate();
 
-  nsCOMPtr<nsIDocument> doc = GetOwnerDoc();
+  nsIDocument* doc = GetOwnerDoc();
   if (!doc) {
     return NS_OK; // We rely on the document for doing HTML conversion
   }
 
-  nsCOMPtr<nsIDOMNode> thisNode(do_QueryInterface(static_cast<nsIContent *>
-                                                             (this)));
   nsresult rv = NS_OK;
 
   nsAutoString contentType;
   if (IsInHTMLDocument()) {
     contentType.AssignLiteral("text/html");
   } else {
     doc->GetContentType(contentType);
   }
-  
-  nsCOMPtr<nsIDocumentEncoder> docEncoder;
-  docEncoder =
-    do_CreateInstance(PromiseFlatCString(
+
+  nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder();
+  if (!docEncoder) {
+    docEncoder =
+      do_CreateInstance(PromiseFlatCString(
         nsDependentCString(NS_DOC_ENCODER_CONTRACTID_BASE) +
         NS_ConvertUTF16toUTF8(contentType)
       ).get());
+  }
   if (!(docEncoder || doc->IsHTML())) {
     // This could be some type for which we create a synthetic document.  Try
     // again as XML
     contentType.AssignLiteral("application/xml");
     docEncoder = do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "application/xml");
   }
 
   NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
 
-  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
-  rv = docEncoder->Init(domDoc, contentType,
-                        nsIDocumentEncoder::OutputEncodeBasicEntities |
-                        // Output DOM-standard newlines
-                        nsIDocumentEncoder::OutputLFLineBreak |
-                        // Don't do linebreaking that's not present in the source
-                        nsIDocumentEncoder::OutputRaw);
+  rv = docEncoder->NativeInit(doc, contentType,
+                              nsIDocumentEncoder::OutputEncodeBasicEntities |
+                              // Output DOM-standard newlines
+                              nsIDocumentEncoder::OutputLFLineBreak |
+                              // Don't do linebreaking that's not present in
+                              // the source
+                              nsIDocumentEncoder::OutputRaw);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  docEncoder->SetContainerNode(thisNode);
-  return docEncoder->EncodeToString(aInnerHTML);
+  docEncoder->SetNativeContainerNode(this);
+  rv = docEncoder->EncodeToString(aInnerHTML);
+  doc->SetCachedEncoder(docEncoder);
+  return rv;
 }
 
 nsresult
 nsGenericHTMLElement::SetInnerHTML(const nsAString& aInnerHTML)
 {
   nsIDocument* doc = GetOwnerDoc();
   NS_ENSURE_STATE(doc);
 
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -337,17 +337,17 @@ nsHTMLDocument::ResetToURI(nsIURI *aURI,
   NS_ASSERTION(!mWyciwygChannel,
                "nsHTMLDocument::Reset() - Wyciwyg Channel  still exists!");
 
   mWyciwygChannel = nsnull;
 
   // Make the content type default to "text/html", we are a HTML
   // document, after all. Once we start getting data, this may be
   // changed.
-  mContentType = "text/html";
+  SetContentTypeInternal(nsDependentCString("text/html"));
 }
 
 nsStyleSet::sheetType
 nsHTMLDocument::GetAttrSheetType()
 {
   if (IsHTML()) {
     return nsStyleSet::eHTMLPresHintSheet;
   }
@@ -2030,17 +2030,17 @@ nsHTMLDocument::OpenCommon(const nsACStr
   if (loadAsHtml5) {
     mParser = nsHtml5Module::NewHtml5Parser();
     rv = NS_OK;
   } else {
     mParser = do_CreateInstance(kCParserCID, &rv);  
   }
 
   // This will be propagated to the parser when someone actually calls write()
-  mContentType = aContentType;
+  SetContentTypeInternal(aContentType);
 
   mWriteState = eDocumentOpened;
 
   if (NS_SUCCEEDED(rv)) {
     if (loadAsHtml5) {
       nsHtml5Module::Initialize(mParser, this, uri, shell, channel);
     } else {
       nsCOMPtr<nsIHTMLContentSink> sink;
@@ -2129,17 +2129,17 @@ nsHTMLDocument::Close()
 
   if (mParser && mWriteState == eDocumentOpened) {
     mPendingScripts.RemoveElement(GenerateParserKey());
 
     mWriteState = mPendingScripts.IsEmpty() ? eDocumentClosed : ePendingClose;
 
     ++mWriteLevel;
     rv = mParser->Parse(EmptyString(), mParser->GetRootContextKey(),
-                        mContentType, PR_TRUE);
+                        GetContentTypeInternal(), PR_TRUE);
     --mWriteLevel;
 
     // XXX Make sure that all the document.written content is
     // reflowed.  We should remove this call once we change
     // nsHTMLDocument::OpenCommon() so that it completely destroys the
     // earlier document's content and frame hierarchy.  Right now, it
     // re-uses the earlier document's root content object and
     // corresponding frame objects.  These re-used frame objects think
@@ -2229,21 +2229,21 @@ nsHTMLDocument::WriteCommon(const nsAStr
   ++mWriteLevel;
 
   // This could be done with less code, but for performance reasons it
   // makes sense to have the code for two separate Parse() calls here
   // since the concatenation of strings costs more than we like. And
   // why pay that price when we don't need to?
   if (aNewlineTerminate) {
     rv = mParser->Parse(aText + new_line,
-                        key, mContentType,
+                        key, GetContentTypeInternal(),
                         (mWriteState == eNotWriting || (mWriteLevel > 1)));
   } else {
     rv = mParser->Parse(aText,
-                        key, mContentType,
+                        key, GetContentTypeInternal(),
                         (mWriteState == eNotWriting || (mWriteLevel > 1)));
   }
 
   --mWriteLevel;
 
   mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
 
   return rv;
--- a/xpcom/string/public/nsStringBuffer.h
+++ b/xpcom/string/public/nsStringBuffer.h
@@ -160,13 +160,15 @@ class nsStringBuffer
        * storage units) of the string contained in the string buffer since the
        * length of the string may be less than its storage size.  The string
        * must have a null terminator at the offset specified by |len|.
        *
        * NOTE: storage size is measured in bytes even for wide strings;
        *       however, string length is always measured in storage units
        *       (2-byte units for wide strings).
        */
-      NS_COM void ToString(PRUint32 len, nsAString &str);
-      NS_COM void ToString(PRUint32 len, nsACString &str);
+      NS_COM void ToString(PRUint32 len, nsAString &str,
+                           PRBool aMoveOwnership = PR_FALSE);
+      NS_COM void ToString(PRUint32 len, nsACString &str,
+                           PRBool aMoveOwnership = PR_FALSE);
   };
 
 #endif /* !defined(nsStringBuffer_h__ */
--- a/xpcom/string/src/nsSubstring.cpp
+++ b/xpcom/string/src/nsSubstring.cpp
@@ -259,44 +259,50 @@ nsStringBuffer::FromString(const nsACStr
 
     if (!(accessor->flags() & nsCSubstring::F_SHARED))
       return nsnull;
 
     return FromData(accessor->data());
   }
 
 void
-nsStringBuffer::ToString(PRUint32 len, nsAString &str)
+nsStringBuffer::ToString(PRUint32 len, nsAString &str,
+                         PRBool aMoveOwnership)
   {
     PRUnichar* data = static_cast<PRUnichar*>(Data());
 
     nsAStringAccessor* accessor = static_cast<nsAStringAccessor*>(&str);
     NS_ASSERTION(data[len] == PRUnichar(0), "data should be null terminated");
 
     // preserve class flags
     PRUint32 flags = accessor->flags();
     flags = (flags & 0xFFFF0000) | nsSubstring::F_SHARED | nsSubstring::F_TERMINATED;
 
-    AddRef();
+    if (!aMoveOwnership) {
+      AddRef();
+    }
     accessor->set(data, len, flags);
   }
 
 void
-nsStringBuffer::ToString(PRUint32 len, nsACString &str)
+nsStringBuffer::ToString(PRUint32 len, nsACString &str,
+                         PRBool aMoveOwnership)
   {
     char* data = static_cast<char*>(Data());
 
     nsACStringAccessor* accessor = static_cast<nsACStringAccessor*>(&str);
     NS_ASSERTION(data[len] == char(0), "data should be null terminated");
 
     // preserve class flags
     PRUint32 flags = accessor->flags();
     flags = (flags & 0xFFFF0000) | nsCSubstring::F_SHARED | nsCSubstring::F_TERMINATED;
 
-    AddRef();
+    if (!aMoveOwnership) {
+      AddRef();
+    }
     accessor->set(data, len, flags);
   }
 
 // ---------------------------------------------------------------------------
 
 
   // define nsSubstring
 #include "string-template-def-unichar.h"