Bug 726334, bug 726332, don't keep stuff alive too long in formfillcontroller, r=gavin, a=lsblakk
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Tue, 17 Apr 2012 10:47:07 +0300
changeset 81802 ddec781cfc636c5e6676a19b66fa288bef12db4e
parent 81801 e18487de75f6ffb9d8eeb73a3099be7667124966
child 81803 665a4cfb95cf9e6828d1a7a08c7c5e80c6b4aa12
push id127
push useropettay@mozilla.com
push dateTue, 17 Apr 2012 09:34:10 +0000
reviewersgavin, lsblakk
bugs726334, 726332
milestone10.0.4esrpre
Bug 726334, bug 726332, don't keep stuff alive too long in formfillcontroller, r=gavin, a=lsblakk
toolkit/components/satchel/nsFormFillController.cpp
toolkit/components/satchel/nsFormFillController.h
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -75,33 +75,57 @@
 NS_IMPL_ISUPPORTS5(nsFormFillController,
                    nsIFormFillController,
                    nsIAutoCompleteInput,
                    nsIAutoCompleteSearch,
                    nsIDOMEventListener,
                    nsIMutationObserver)
 
 nsFormFillController::nsFormFillController() :
+  mFocusedInput(nsnull),
+  mFocusedInputNode(nsnull),
+  mListNode(nsnull),
   mTimeout(50),
   mMinResultsForPopup(1),
   mMaxRows(0),
   mDisableAutoComplete(false),
   mCompleteDefaultIndex(false),
   mCompleteSelectedIndex(false),
   mForceComplete(false),
   mSuppressOnInput(false)
 {
   mController = do_GetService("@mozilla.org/autocomplete/controller;1");
   mDocShells = do_CreateInstance("@mozilla.org/supports-array;1");
   mPopups = do_CreateInstance("@mozilla.org/supports-array;1");
   mPwmgrInputs.Init();
 }
 
+struct PwmgrInputsEnumData
+{
+  PwmgrInputsEnumData(nsFormFillController* aFFC, nsIDocument* aDoc)
+  : mFFC(aFFC), mDoc(aDoc) {}
+
+  nsFormFillController* mFFC;
+  nsCOMPtr<nsIDocument> mDoc;
+};
+
 nsFormFillController::~nsFormFillController()
 {
+  if (mListNode) {
+    mListNode->RemoveMutationObserver(this);
+    mListNode = nsnull;
+  }
+  if (mFocusedInputNode) {
+    MaybeRemoveMutationObserver(mFocusedInputNode);
+    mFocusedInputNode = nsnull;
+    mFocusedInput = nsnull;
+  }
+  PwmgrInputsEnumData ed(this, nsnull);
+  mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed);
+
   // Remove ourselves as a focus listener from all cached docShells
   PRUint32 count;
   mDocShells->Count(&count);
   for (PRUint32 i = 0; i < count; ++i) {
     nsCOMPtr<nsIDocShell> docShell;
     mDocShells->GetElementAt(i, getter_AddRefs(docShell));
     nsCOMPtr<nsIDOMWindow> domWindow = GetWindowForDocShell(docShell);
     RemoveWindowListeners(domWindow);
@@ -113,45 +137,53 @@ nsFormFillController::~nsFormFillControl
 //
 
 void
 nsFormFillController::AttributeChanged(nsIDocument* aDocument,
                                        mozilla::dom::Element* aElement,
                                        PRInt32 aNameSpaceID,
                                        nsIAtom* aAttribute, PRInt32 aModType)
 {
-  RevalidateDataList();
+  if (mListNode && mListNode->Contains(aElement)) {
+    RevalidateDataList();
+  }
 }
 
 void
 nsFormFillController::ContentAppended(nsIDocument* aDocument,
                                       nsIContent* aContainer,
                                       nsIContent* aChild,
                                       PRInt32 aIndexInContainer)
 {
-  RevalidateDataList();
+  if (mListNode && mListNode->Contains(aContainer)) {
+    RevalidateDataList();
+  }
 }
 
 void
 nsFormFillController::ContentInserted(nsIDocument* aDocument,
                                       nsIContent* aContainer,
                                       nsIContent* aChild,
                                       PRInt32 aIndexInContainer)
 {
-  RevalidateDataList();
+  if (mListNode && mListNode->Contains(aContainer)) {
+    RevalidateDataList();
+  }
 }
 
 void
 nsFormFillController::ContentRemoved(nsIDocument* aDocument,
                                      nsIContent* aContainer,
                                      nsIContent* aChild,
                                      PRInt32 aIndexInContainer,
                                      nsIContent* aPreviousSibling)
 {
-  RevalidateDataList();
+  if (mListNode && mListNode->Contains(aContainer)) {
+    RevalidateDataList();
+  }
 }
 
 void
 nsFormFillController::CharacterDataWillChange(nsIDocument* aDocument,
                                               nsIContent* aContent,
                                               CharacterDataChangeInfo* aInfo)
 {
 }
@@ -174,16 +206,35 @@ nsFormFillController::AttributeWillChang
 void
 nsFormFillController::ParentChainChanged(nsIContent* aContent)
 {
 }
 
 void
 nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode)
 {
+  mPwmgrInputs.Remove(aNode);
+  if (aNode == mListNode) {
+    mListNode = nsnull;
+    RevalidateDataList();
+  } else if (aNode == mFocusedInputNode) {
+    mFocusedInputNode = nsnull;
+    mFocusedInput = nsnull;
+  }
+}
+
+void
+nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode)
+{
+  // Nodes being tracked in mPwmgrInputs will have their observers removed when
+  // they stop being tracked. 
+  bool dummy;
+  if (!mPwmgrInputs.Get(aNode, &dummy)) {
+    aNode->RemoveMutationObserver(this);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////
 //// nsIFormFillController
 
 NS_IMETHODIMP
 nsFormFillController::AttachToBrowser(nsIDocShell *aDocShell, nsIAutoCompletePopup *aPopup)
 {
@@ -223,17 +274,20 @@ nsFormFillController::MarkAsLoginManager
 {
   /*
    * The Login Manager can supply autocomplete results for username fields,
    * when a user has multiple logins stored for a site. It uses this
    * interface to indicate that the form manager shouldn't handle the
    * autocomplete. The form manager also checks for this tag when saving
    * form history (so it doesn't save usernames).
    */
-  mPwmgrInputs.Put(aInput, 1);
+  nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+  NS_ENSURE_STATE(node);
+  mPwmgrInputs.Put(node, true);
+  node->AddMutationObserverUnlessExists(this);
 
   if (!mLoginManager)
     mLoginManager = do_GetService("@mozilla.org/login-manager;1");
 
   return NS_OK;
 }
 
 
@@ -558,18 +612,18 @@ NS_IMETHODIMP
 nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam,
                                   nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener)
 {
   nsresult rv;
   nsCOMPtr<nsIAutoCompleteResult> result;
 
   // If the login manager has indicated it's responsible for this field, let it
   // handle the autocomplete. Otherwise, handle with form history.
-  PRInt32 dummy;
-  if (mPwmgrInputs.Get(mFocusedInput, &dummy)) {
+  bool dummy;
+  if (mPwmgrInputs.Get(mFocusedInputNode, &dummy)) {
     // XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
     // satchel manage the field?
     rv = mLoginManager->AutoCompleteSearch(aSearchString,
                                          aPreviousResult,
                                          mFocusedInput,
                                          getter_AddRefs(result));
   } else {
     nsCOMPtr<nsIAutoCompleteResult> formHistoryResult;
@@ -600,18 +654,25 @@ nsFormFillController::StartSearch(const 
                                                    mFocusedInput,
                                                    getter_AddRefs(result));
 
     if (mFocusedInput) {
       nsCOMPtr<nsIDOMHTMLElement> list;
       mFocusedInput->GetList(getter_AddRefs(list));
 
       nsCOMPtr<nsINode> node = do_QueryInterface(list);
-      if(node) {
-        node->AddMutationObserverUnlessExists(this);
+      if (mListNode != node) {
+        if (mListNode) {
+          mListNode->RemoveMutationObserver(this);
+          mListNode = nsnull;
+        }
+        if (node) {
+          node->AddMutationObserverUnlessExists(this);
+          mListNode = node;
+        }
       }
     }
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   aListener->OnSearchResult(this, result);
 
   return NS_OK;
@@ -638,16 +699,19 @@ public:
 private:
   nsCOMPtr<nsIAutoCompleteObserver> mObserver;
   nsCOMPtr<nsIAutoCompleteSearch> mSearch;
   nsCOMPtr<nsIAutoCompleteResult> mResult;
 };
 
 void nsFormFillController::RevalidateDataList()
 {
+  if (!mLastListener) {
+    return;
+  }
   nsresult rv;
   nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
     do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
 
   nsCOMPtr<nsIAutoCompleteResult> result;
 
   rv = inputListAutoComplete->AutoCompleteSearch(mLastSearchResult,
                                                  mLastSearchString,
@@ -719,57 +783,61 @@ nsFormFillController::HandleEvent(nsIDOM
 
     if (mFocusedInput) {
       nsCOMPtr<nsIDOMDocument> inputDoc;
       mFocusedInput->GetOwnerDocument(getter_AddRefs(inputDoc));
       if (domDoc == inputDoc)
         StopControllingInput();
     }
 
-    mPwmgrInputs.Enumerate(RemoveForDOMDocumentEnumerator, domDoc);
+    nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+    PwmgrInputsEnumData ed(this, doc);
+    mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed);
   }
 
   return NS_OK;
 }
 
 
 /* static */ PLDHashOperator
-nsFormFillController::RemoveForDOMDocumentEnumerator(nsISupports* aKey,
-                                                  PRInt32& aEntry,
+nsFormFillController::RemoveForDocumentEnumerator(const nsINode* aKey,
+                                                  bool& aEntry,
                                                   void* aUserData)
 {
-  nsIDOMDocument* domDoc = static_cast<nsIDOMDocument*>(aUserData);
-  nsCOMPtr<nsIDOMHTMLInputElement> element = do_QueryInterface(aKey);
-  nsCOMPtr<nsIDOMDocument> elementDoc;
-  element->GetOwnerDocument(getter_AddRefs(elementDoc));
-  if (elementDoc == domDoc)
+  PwmgrInputsEnumData* ed = static_cast<PwmgrInputsEnumData*>(aUserData);
+  if (aKey && (!ed->mDoc || aKey->OwnerDoc() == ed->mDoc)) {
+    // mFocusedInputNode's observer is tracked separately, don't remove it here.
+    if (aKey != ed->mFFC->mFocusedInputNode) {
+      const_cast<nsINode*>(aKey)->RemoveMutationObserver(ed->mFFC);
+    }
     return PL_DHASH_REMOVE;
-
+  }
   return PL_DHASH_NEXT;
 }
 
 nsresult
 nsFormFillController::Focus(nsIDOMEvent* aEvent)
 {
   nsCOMPtr<nsIDOMEventTarget> target;
   aEvent->GetTarget(getter_AddRefs(target));
 
   nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(target);
-  if (!input)
+  nsCOMPtr<nsINode> inputNode = do_QueryInterface(input); 
+  if (!inputNode)
     return NS_OK;
 
   bool isReadOnly = false;
   input->GetReadOnly(&isReadOnly);
 
   nsAutoString autocomplete;
   input->GetAttribute(NS_LITERAL_STRING("autocomplete"), autocomplete);
 
-  PRInt32 dummy;
+  bool dummy;
   bool isPwmgrInput = false;
-  if (mPwmgrInputs.Get(input, &dummy))
+  if (mPwmgrInputs.Get(inputNode, &dummy))
       isPwmgrInput = true;
 
   nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(input);
   if (formControl && formControl->IsSingleLineTextControl(true) &&
       !isReadOnly || isPwmgrInput) {
     StartControllingInput(input);
   }
 
@@ -961,17 +1029,19 @@ nsFormFillController::RemoveWindowListen
 {
   if (!aWindow)
     return;
 
   StopControllingInput();
 
   nsCOMPtr<nsIDOMDocument> domDoc;
   aWindow->GetDocument(getter_AddRefs(domDoc));
-  mPwmgrInputs.Enumerate(RemoveForDOMDocumentEnumerator, domDoc);
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+  PwmgrInputsEnumData ed(this, doc);
+  mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed);
 
   nsCOMPtr<nsPIDOMWindow> privateDOMWindow(do_QueryInterface(aWindow));
   nsIDOMEventTarget* target = nsnull;
   if (privateDOMWindow)
     target = privateDOMWindow->GetChromeEventHandler();
 
   if (!target)
     return;
@@ -1020,47 +1090,62 @@ nsFormFillController::StartControllingIn
   nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(aInput);
   PRInt32 index = GetIndexOfDocShell(docShell);
   if (index < 0)
     return;
 
   // Cache the popup for the focused docShell
   mPopups->GetElementAt(index, getter_AddRefs(mFocusedPopup));
 
+  nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+  if (!node) {
+    return;
+  }
+
   AddKeyListener(aInput);
+  
+  node->AddMutationObserverUnlessExists(this);
+  mFocusedInputNode = node;
   mFocusedInput = aInput;
 
+  nsCOMPtr<nsIDOMHTMLElement> list;
+  mFocusedInput->GetList(getter_AddRefs(list));
+  nsCOMPtr<nsINode> listNode = do_QueryInterface(list);
+  if (listNode) {
+    listNode->AddMutationObserverUnlessExists(this);
+    mListNode = listNode;
+  }
+
   // Now we are the autocomplete controller's bitch
   mController->SetInput(this);
 }
 
 void
 nsFormFillController::StopControllingInput()
 {
   RemoveKeyListener();
 
-  if(mFocusedInput) {
-    nsCOMPtr<nsIDOMHTMLElement> list;
-    mFocusedInput->GetList(getter_AddRefs(list));
-
-    nsCOMPtr<nsINode> node = do_QueryInterface(list);
-    if (node) {
-      node->RemoveMutationObserver(this);
-    }
+  if (mListNode) {
+    mListNode->RemoveMutationObserver(this);
+    mListNode = nsnull;
   }
 
   // Reset the controller's input, but not if it has been switched
   // to another input already, which might happen if the user switches
   // focus by clicking another autocomplete textbox
   nsCOMPtr<nsIAutoCompleteInput> input;
   mController->GetInput(getter_AddRefs(input));
   if (input == this)
     mController->SetInput(nsnull);
 
-  mFocusedInput = nsnull;
+  if (mFocusedInputNode) {
+    MaybeRemoveMutationObserver(mFocusedInputNode);
+    mFocusedInputNode = nsnull;
+    mFocusedInput = nsnull;
+  }
   mFocusedPopup = nsnull;
 }
 
 nsIDocShell *
 nsFormFillController::GetDocShellForInput(nsIDOMHTMLInputElement *aInput)
 {
   nsCOMPtr<nsIDOMDocument> domDoc;
   aInput->GetOwnerDocument(getter_AddRefs(domDoc));
--- a/toolkit/components/satchel/nsFormFillController.h
+++ b/toolkit/components/satchel/nsFormFillController.h
@@ -56,16 +56,17 @@
 #include "nsIMutationObserver.h"
 
 // X.h defines KeyPress
 #ifdef KeyPress
 #undef KeyPress
 #endif
 
 class nsFormHistory;
+class nsINode;
 
 class nsFormFillController : public nsIFormFillController,
                              public nsIAutoCompleteInput,
                              public nsIAutoCompleteSearch,
                              public nsIDOMEventListener,
                              public nsIMutationObserver
 {
 public:
@@ -95,37 +96,41 @@ protected:
 
   void RevalidateDataList();
   bool RowMatch(nsFormHistory *aHistory, PRUint32 aIndex, const nsAString &aInputName, const nsAString &aInputValue);
 
   inline nsIDocShell *GetDocShellForInput(nsIDOMHTMLInputElement *aInput);
   inline nsIDOMWindow *GetWindowForDocShell(nsIDocShell *aDocShell);
   inline PRInt32 GetIndexOfDocShell(nsIDocShell *aDocShell);
 
-  static PLDHashOperator RemoveForDOMDocumentEnumerator(nsISupports* aKey,
-                                                        PRInt32& aEntry,
-                                                        void* aUserData);
+  void MaybeRemoveMutationObserver(nsINode* aNode);
+
+  static PLDHashOperator RemoveForDocumentEnumerator(const nsINode* aKey,
+                                                     bool& aEntry,
+                                                     void* aUserData);
   bool IsEventTrusted(nsIDOMEvent *aEvent);
   bool IsInputAutoCompleteOff();
   // members //////////////////////////////////////////
 
   nsCOMPtr<nsIAutoCompleteController> mController;
   nsCOMPtr<nsILoginManager> mLoginManager;
-  nsCOMPtr<nsIDOMHTMLInputElement> mFocusedInput;
+  nsIDOMHTMLInputElement* mFocusedInput;
+  nsINode* mFocusedInputNode;
+  nsINode* mListNode;
   nsCOMPtr<nsIAutoCompletePopup> mFocusedPopup;
 
   nsCOMPtr<nsISupportsArray> mDocShells;
   nsCOMPtr<nsISupportsArray> mPopups;
 
   //these are used to dynamically update the autocomplete
   nsCOMPtr<nsIAutoCompleteResult> mLastSearchResult;
   nsCOMPtr<nsIAutoCompleteObserver> mLastListener;
   nsString mLastSearchString;
 
-  nsDataHashtable<nsISupportsHashKey,PRInt32> mPwmgrInputs;
+  nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mPwmgrInputs;
 
   PRUint32 mTimeout;
   PRUint32 mMinResultsForPopup;
   PRUint32 mMaxRows;
   bool mDisableAutoComplete;
   bool mCompleteDefaultIndex;
   bool mCompleteSelectedIndex;
   bool mForceComplete;