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
--- 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;