Bug 1398605, keep nsContentList objects alive for awhile when generating state keys so that new objects don't need to be created all the time, r=ehsan
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Thu, 14 Sep 2017 21:56:58 +0300
changeset 665134 90dd92f271f5cc3209b7231a6d2f96ee32c8ae7d
parent 665019 97fbaf813dc6aaf4c1cb59d38268f4fe91950c54
child 665135 bb57f4702727f87e3bf578e3938075c095b3b0bd
push id79943
push userbmo:tchiovoloni@mozilla.com
push dateThu, 14 Sep 2017 23:59:48 +0000
reviewersehsan
bugs1398605
milestone57.0a1
Bug 1398605, keep nsContentList objects alive for awhile when generating state keys so that new objects don't need to be created all the time, r=ehsan
dom/base/nsContentUtils.cpp
dom/html/nsHTMLDocument.cpp
dom/html/nsHTMLDocument.h
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3049,43 +3049,20 @@ nsContentUtils::GenerateStateKey(nsICont
   nsCOMPtr<nsIHTMLDocument> htmlDocument =
     do_QueryInterface(aContent->GetUncomposedDoc());
 
   KeyAppendInt(partID, aKey);  // first append a partID
   bool generatedUniqueKey = false;
 
   if (htmlDocument) {
     nsHTMLDocument* htmlDoc = static_cast<nsHTMLDocument*> (htmlDocument.get());
-    // Flush our content model so it'll be up to date
-    // If this becomes unnecessary and the following line is removed,
-    // please also remove the corresponding flush operation from
-    // nsHtml5TreeBuilderCppSupplement.h. (Look for "See bug 497861." there.)
-    aContent->GetUncomposedDoc()->FlushPendingNotifications(FlushType::Content);
-
-    RefPtr<nsContentList> htmlForms = htmlDoc->GetExistingForms();
-    if (!htmlForms) {
-      // If the document doesn't have an existing forms content list, create a
-      // new one, but avoid creating a live list since we only need to use the
-      // list here and it doesn't need to listen to mutation events.
-
-      // Please keep this in sync with nsHTMLDocument::GetForms().
-      htmlForms = new nsContentList(aDocument, kNameSpaceID_XHTML,
-                                    nsGkAtoms::form, nsGkAtoms::form,
-                                    /* aDeep = */ true,
-                                    /* aLiveList = */ false);
-    }
-    RefPtr<nsContentList> htmlFormControls =
-      new nsContentList(aDocument,
-                        nsHTMLDocument::MatchFormControls,
-                        nullptr, nullptr,
-                        /* aDeep = */ true,
-                        /* aMatchAtom = */ nullptr,
-                        /* aMatchNameSpaceId = */ kNameSpaceID_None,
-                        /* aFuncMayDependOnAttr = */ true,
-                        /* aLiveList = */ false);
+    RefPtr<nsContentList> htmlForms;
+    RefPtr<nsContentList> htmlFormControls;
+    htmlDoc->GetFormsAndFormControls(getter_AddRefs(htmlForms),
+                                     getter_AddRefs(htmlFormControls));
 
     // If we have a form control and can calculate form information, use that
     // as the key - it is more reliable than just recording position in the
     // DOM.
     // XXXbz Is it, really?  We have bugs on this, I think...
     // Important to have a unique key, and tag/type/name may not be.
     //
     // If the control has a form, the format of the key is:
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -169,16 +169,17 @@ NS_NewHTMLDocument(nsIDocument** aInstan
   doc->SetLoadedAsData(aLoadedAsData);
   doc.forget(aInstancePtrResult);
 
   return NS_OK;
 }
 
 nsHTMLDocument::nsHTMLDocument()
   : nsDocument("text/html")
+  , mContentListHolder(nullptr)
   , mNumForms(0)
   , mWriteLevel(0)
   , mLoadFlags(0)
   , mTooDeepWriteRecursion(false)
   , mDisableDocWrite(false)
   , mWarnedWidthHeight(false)
   , mContentEditableCount(0)
   , mEditingState(EditingState::eOff)
@@ -3749,8 +3750,55 @@ nsHTMLDocument::WillIgnoreCharsetOverrid
                                       nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                       &isResource);
     if (NS_FAILED(rv) || isResource) {
       return true;
     }
   }
   return false;
 }
+
+void
+nsHTMLDocument::GetFormsAndFormControls(nsContentList** aFormList,
+                                        nsContentList** aFormControlList)
+{
+  RefPtr<ContentListHolder> holder = mContentListHolder;
+  if (!holder) {
+    // Flush our content model so it'll be up to date
+    // If this becomes unnecessary and the following line is removed,
+    // please also remove the corresponding flush operation from
+    // nsHtml5TreeBuilderCppSupplement.h. (Look for "See bug 497861." there.)
+    //XXXsmaug nsHtml5TreeBuilderCppSupplement doesn't seem to have such flush
+    //         anymore.
+    FlushPendingNotifications(FlushType::Content);
+
+    RefPtr<nsContentList> htmlForms = GetExistingForms();
+    if (!htmlForms) {
+      // If the document doesn't have an existing forms content list, create a
+      // new one which will be released soon by ContentListHolder.
+      // Please keep this in sync with nsHTMLDocument::GetForms().
+      htmlForms = new nsContentList(this, kNameSpaceID_XHTML,
+                                    nsGkAtoms::form, nsGkAtoms::form,
+                                    /* aDeep = */ true,
+                                    /* aLiveList = */ true);
+    }
+
+    RefPtr<nsContentList> htmlFormControls =
+      new nsContentList(this,
+                        nsHTMLDocument::MatchFormControls,
+                        nullptr, nullptr,
+                        /* aDeep = */ true,
+                        /* aMatchAtom = */ nullptr,
+                        /* aMatchNameSpaceId = */ kNameSpaceID_None,
+                        /* aFuncMayDependOnAttr = */ true,
+                        /* aLiveList = */ true);
+
+    holder = new ContentListHolder(this, htmlForms, htmlFormControls);
+    RefPtr<ContentListHolder> runnable = holder;
+    if (NS_SUCCEEDED(Dispatch(TaskCategory::GarbageCollection,
+                              runnable.forget()))) {
+      mContentListHolder = holder;
+    }
+  }
+
+  NS_ADDREF(*aFormList = holder->mFormList);
+  NS_ADDREF(*aFormControlList = holder->mFormControlList);
+}
\ No newline at end of file
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -12,17 +12,17 @@
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLCollection.h"
 #include "nsIScriptElement.h"
 #include "nsTArray.h"
 
 #include "PLDHashTable.h"
 #include "nsIHttpChannel.h"
 #include "nsHTMLStyleSheet.h"
-
+#include "nsThreadUtils.h"
 #include "nsICommandManager.h"
 #include "mozilla/dom/HTMLSharedElement.h"
 #include "mozilla/dom/BindingDeclarations.h"
 
 class nsIURI;
 class nsIDocShell;
 class nsICachingChannel;
 class nsIWyciwygChannel;
@@ -261,16 +261,18 @@ public:
     return nsIDocument::GetLocation();
   }
 
   virtual nsHTMLDocument* AsHTMLDocument() override { return this; }
 
   static bool MatchFormControls(Element* aElement, int32_t aNamespaceID,
                                 nsIAtom* aAtom, void* aData);
 
+  void GetFormsAndFormControls(nsContentList** aFormList,
+                               nsContentList** aFormControlList);
 protected:
   ~nsHTMLDocument();
 
   nsresult GetBodySize(int32_t* aWidth,
                        int32_t* aHeight);
 
   nsIContent *MatchId(nsIContent *aContent, const nsAString& aId);
 
@@ -307,16 +309,47 @@ protected:
 
   /**
    * Like IsEditingOn(), but will flush as needed first.
    */
   bool IsEditingOnAfterFlush();
 
   void *GenerateParserKey(void);
 
+  // A helper class to keep nsContentList objects alive for a short period of
+  // time. Note, when the final Release is called on an nsContentList object, it
+  // removes itself from MutationObserver list.
+  class ContentListHolder : public mozilla::Runnable
+  {
+  public:
+    ContentListHolder(nsHTMLDocument* aDocument,
+                      nsContentList* aFormList,
+                      nsContentList* aFormControlList)
+      : mozilla::Runnable("ContentListHolder")
+      , mDocument(aDocument)
+      , mFormList(aFormList)
+      , mFormControlList(aFormControlList)
+    {
+    }
+
+    ~ContentListHolder()
+    {
+      MOZ_ASSERT(!mDocument->mContentListHolder ||
+                 mDocument->mContentListHolder == this);
+      mDocument->mContentListHolder = nullptr;
+    }
+
+    RefPtr<nsHTMLDocument> mDocument;
+    RefPtr<nsContentList> mFormList;
+    RefPtr<nsContentList> mFormControlList;
+  };
+
+  friend class ContentListHolder;
+  ContentListHolder* mContentListHolder;
+
   RefPtr<nsContentList> mImages;
   RefPtr<nsEmptyContentList> mApplets;
   RefPtr<nsContentList> mEmbeds;
   RefPtr<nsContentList> mLinks;
   RefPtr<nsContentList> mAnchors;
   RefPtr<nsContentList> mScripts;
   RefPtr<nsContentList> mForms;