Bug 655084 - Remove nsWeakFrame's from editor scripts and the nsTextInputListener to improve perf on pages with lots of text widgets. r=ehsan
authorRandell Jesup <rjesup@wgate.com>
Sat, 21 May 2011 04:25:27 -0400
changeset 69872 52f72d71dc59210b22c1f782a74dc19ae478baa6
parent 69871 35f142a55f503ad28c9b3cbcca862c21a9da29ed
child 69873 107bbdaf84c072fd3a37b8ca1daac8100e52e301
push id20127
push userrjesup@wgate.com
push dateSun, 22 May 2011 06:52:51 +0000
treeherdermozilla-central@52f72d71dc59 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs655084
milestone6.0a1
first release with
nightly linux32
52f72d71dc59 / 6.0a1 / 20110522030613 / files
nightly linux64
52f72d71dc59 / 6.0a1 / 20110522030613 / files
nightly mac
52f72d71dc59 / 6.0a1 / 20110522030613 / files
nightly win32
52f72d71dc59 / 6.0a1 / 20110522030613 / files
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
Bug 655084 - Remove nsWeakFrame's from editor scripts and the nsTextInputListener to improve perf on pages with lots of text widgets. r=ehsan
content/html/content/src/nsTextEditorState.cpp
content/html/content/src/nsTextEditorState.h
layout/forms/nsTextControlFrame.cpp
layout/forms/nsTextControlFrame.h
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -79,37 +79,41 @@ struct SelectionState {
   PRInt32 mEnd;
 };
 
 class RestoreSelectionState : public nsRunnable {
 public:
   RestoreSelectionState(nsTextEditorState *aState, nsTextControlFrame *aFrame,
                         PRInt32 aStart, PRInt32 aEnd)
     : mFrame(aFrame),
-      mWeakFrame(aFrame),
       mStart(aStart),
       mEnd(aEnd),
       mTextEditorState(aState)
   {
   }
 
   NS_IMETHOD Run() {
-    if (mWeakFrame.IsAlive()) {
+    if (mFrame) {
       // SetSelectionRange leads to Selection::AddRange which flushes Layout -
       // need to block script to avoid nested PrepareEditor calls (bug 642800).
       nsAutoScriptBlocker scriptBlocker;
       mFrame->SetSelectionRange(mStart, mEnd);
       mTextEditorState->HideSelectionIfBlurred();
     }
+    mTextEditorState->FinishedRestoringSelection();
     return NS_OK;
   }
 
+  // Let the text editor tell us we're no longer relevant - avoids use of nsWeakFrame
+  void Revoke() {
+    mFrame = nsnull;
+  }
+
 private:
   nsTextControlFrame* mFrame;
-  nsWeakFrame mWeakFrame;
   PRInt32 mStart;
   PRInt32 mEnd;
   nsTextEditorState* mTextEditorState;
 };
 
 /*static*/
 PRBool
 nsITextControlElement::GetWrapPropertyEnum(nsIContent* aContent,
@@ -640,17 +644,17 @@ public:
 protected:
 
   nsresult  UpdateTextInputCommands(const nsAString& commandsToUpdate);
 
   NS_HIDDEN_(nsINativeKeyBindings*) GetKeyBindings();
 
 protected:
 
-  nsWeakFrame mFrame;
+  nsIFrame* mFrame;
 
   nsITextControlElement* const mTxtCtrlElement;
 
   PRPackedBool    mSelectionWasCollapsed;
   /**
    * Whether we had undo items or not the last time we got EditAction()
    * notification (when this state changes we update undo and redo menus)
    */
@@ -668,17 +672,18 @@ protected:
 };
 
 
 /*
  * nsTextInputListener implementation
  */
 
 nsTextInputListener::nsTextInputListener(nsITextControlElement* aTxtCtrlElement)
-: mTxtCtrlElement(aTxtCtrlElement)
+: mFrame(nsnull)
+, mTxtCtrlElement(aTxtCtrlElement)
 , mSelectionWasCollapsed(PR_TRUE)
 , mHadUndoItems(PR_FALSE)
 , mHadRedoItems(PR_FALSE)
 , mSettingValue(PR_FALSE)
 {
 }
 
 nsTextInputListener::~nsTextInputListener() 
@@ -698,17 +703,19 @@ NS_INTERFACE_MAP_BEGIN(nsTextInputListen
 NS_INTERFACE_MAP_END
 
 // BEGIN nsIDOMSelectionListener
 
 NS_IMETHODIMP
 nsTextInputListener::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* aSel, PRInt16 aReason)
 {
   PRBool collapsed;
-  if (!mFrame.IsAlive() || !aDoc || !aSel || NS_FAILED(aSel->GetIsCollapsed(&collapsed)))
+  nsWeakFrame weakFrame = mFrame;
+
+  if (!aDoc || !aSel || NS_FAILED(aSel->GetIsCollapsed(&collapsed)))
     return NS_OK;
 
   // Fire the select event
   // The specs don't exactly say when we should fire the select event.
   // IE: Whenever you add/remove a character to/from the selection. Also
   //     each time for select all. Also if you get to the end of the text 
   //     field you will get new event for each keypress or a continuous 
   //     stream of events if you use the mouse. IE will fire select event 
@@ -743,17 +750,17 @@ nsTextInputListener::NotifySelectionChan
   }
 
   // if the collapsed state did not change, don't fire notifications
   if (collapsed == mSelectionWasCollapsed)
     return NS_OK;
   
   mSelectionWasCollapsed = collapsed;
 
-  if (!mFrame.IsAlive() || !nsContentUtils::IsFocusedContent(mFrame->GetContent()))
+  if (!weakFrame.IsAlive() || !nsContentUtils::IsFocusedContent(mFrame->GetContent()))
     return NS_OK;
 
   return UpdateTextInputCommands(NS_LITERAL_STRING("select"));
 }
 
 // END nsIDOMSelectionListener
 
 // BEGIN nsIDOMKeyListener
@@ -794,17 +801,16 @@ DoCommandCallback(const char *aCommand, 
     controller->DoCommand(aCommand);
   }
 }
 
 
 NS_IMETHODIMP
 nsTextInputListener::KeyDown(nsIDOMEvent *aDOMEvent)
 {
-  NS_ENSURE_STATE(mFrame.IsAlive());
   nsCOMPtr<nsIDOMKeyEvent> keyEvent(do_QueryInterface(aDOMEvent));
   NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
 
   nsNativeKeyEvent nativeEvent;
   nsINativeKeyBindings *bindings = GetKeyBindings();
   if (bindings &&
       nsContentUtils::DOMEventToNativeKeyEvent(keyEvent, &nativeEvent, PR_FALSE)) {
     if (bindings->KeyDown(nativeEvent, DoCommandCallback, mFrame)) {
@@ -813,17 +819,16 @@ nsTextInputListener::KeyDown(nsIDOMEvent
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextInputListener::KeyPress(nsIDOMEvent *aDOMEvent)
 {
-  NS_ENSURE_STATE(mFrame.IsAlive());
   nsCOMPtr<nsIDOMKeyEvent> keyEvent(do_QueryInterface(aDOMEvent));
   NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
 
   nsNativeKeyEvent nativeEvent;
   nsINativeKeyBindings *bindings = GetKeyBindings();
   if (bindings &&
       nsContentUtils::DOMEventToNativeKeyEvent(keyEvent, &nativeEvent, PR_TRUE)) {
     if (bindings->KeyPress(nativeEvent, DoCommandCallback, mFrame)) {
@@ -832,17 +837,16 @@ nsTextInputListener::KeyPress(nsIDOMEven
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextInputListener::KeyUp(nsIDOMEvent *aDOMEvent)
 {
-  NS_ENSURE_STATE(mFrame.IsAlive());
   nsCOMPtr<nsIDOMKeyEvent> keyEvent(do_QueryInterface(aDOMEvent));
   NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
 
   nsNativeKeyEvent nativeEvent;
   nsINativeKeyBindings *bindings = GetKeyBindings();
   if (bindings &&
       nsContentUtils::DOMEventToNativeKeyEvent(keyEvent, &nativeEvent, PR_FALSE)) {
     if (bindings->KeyUp(nativeEvent, DoCommandCallback, mFrame)) {
@@ -854,18 +858,19 @@ nsTextInputListener::KeyUp(nsIDOMEvent *
 }
 // END nsIDOMKeyListener
 
 // BEGIN nsIEditorObserver
 
 NS_IMETHODIMP
 nsTextInputListener::EditAction()
 {
-  NS_ENSURE_STATE(mFrame.IsAlive());
-  nsITextControlFrame* frameBase = do_QueryFrame(mFrame.GetFrame());
+  nsWeakFrame weakFrame = mFrame;
+
+  nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
   nsTextControlFrame* frame = static_cast<nsTextControlFrame*> (frameBase);
   NS_ASSERTION(frame, "Where is our frame?");
   //
   // Update the undo / redo menus
   //
   nsCOMPtr<nsIEditor> editor;
   frame->GetEditor(getter_AddRefs(editor));
 
@@ -882,17 +887,17 @@ nsTextInputListener::EditAction()
       (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) {
     // Modify the menu if undo or redo items are different
     UpdateTextInputCommands(NS_LITERAL_STRING("undo"));
 
     mHadUndoItems = numUndoItems != 0;
     mHadRedoItems = numRedoItems != 0;
   }
 
-  if (!mFrame.IsAlive()) {
+  if (!weakFrame.IsAlive()) {
     return NS_OK;
   }
 
   // Make sure we know we were changed (do NOT set this to false if there are
   // no undo items; JS could change the value and we'd still need to save it)
   frame->SetValueChanged(PR_TRUE);
 
   if (!mSettingValue) {
@@ -911,18 +916,16 @@ nsTextInputListener::EditAction()
 }
 
 // END nsIEditorObserver
 
 
 nsresult
 nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate)
 {
-  NS_ENSURE_STATE(mFrame.IsAlive());
-
   nsIContent* content = mFrame->GetContent();
   NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
   
   nsCOMPtr<nsIDocument> doc = content->GetDocument();
   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
   nsPIDOMWindow *domWindow = doc->GetWindow();
   NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
@@ -962,16 +965,17 @@ nsTextInputListener::GetKeyBindings()
 }
 
 // END nsTextInputListener
 
 // nsTextEditorState
 
 nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement)
   : mTextCtrlElement(aOwningElement),
+    mRestoringSelection(nsnull),
     mBoundFrame(nsnull),
     mTextListener(nsnull),
     mEditorInitialized(PR_FALSE),
     mInitializing(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsTextEditorState);
 }
 
@@ -1380,18 +1384,23 @@ nsTextEditorState::PrepareEditor(const n
     mEditorInitialized = PR_TRUE;
   }
 
   if (mTextListener)
     newEditor->AddEditorObserver(mTextListener);
 
   // Restore our selection after being bound to a new frame
   if (mSelState) {
-    nsContentUtils::AddScriptRunner(new RestoreSelectionState(this, mBoundFrame, mSelState->mStart, mSelState->mEnd));
-    mSelState = nsnull;
+    if (mRestoringSelection) // paranoia
+      mRestoringSelection->Revoke();
+    mRestoringSelection = new RestoreSelectionState(this, mBoundFrame, mSelState->mStart, mSelState->mEnd);
+    if (mRestoringSelection) {
+      nsContentUtils::AddScriptRunner(mRestoringSelection);
+      mSelState = nsnull;
+    }
   }
 
   return rv;
 }
 
 void
 nsTextEditorState::DestroyEditor()
 {
@@ -1414,16 +1423,21 @@ nsTextEditorState::UnbindFromFrame(nsTex
   NS_ASSERTION(!aFrame || aFrame == mBoundFrame, "Unbinding from the wrong frame");
   NS_ENSURE_TRUE(!aFrame || aFrame == mBoundFrame, );
 
   // We need to start storing the value outside of the editor if we're not
   // going to use it anymore, so retrieve it for now.
   nsAutoString value;
   GetValue(value, PR_TRUE);
 
+  if (mRestoringSelection) {
+    mRestoringSelection->Revoke();
+    mRestoringSelection = nsnull;
+  }
+
   // Save our selection state if needed.
   // Note that nsTextControlFrame::GetSelectionRange attempts to initialize the
   // editor before grabbing the range, and because this is not an acceptable
   // side effect for unbinding from a text control frame, we need to call
   // GetSelectionRange before calling DestroyEditor, and only if
   // mEditorInitialized indicates that we actually have an editor available.
   if (mEditorInitialized) {
     mSelState = new SelectionState();
--- a/content/html/content/src/nsTextEditorState.h
+++ b/content/html/content/src/nsTextEditorState.h
@@ -135,16 +135,18 @@ struct SelectionState;
  *     the nsIPlaintextEditor interface, and is internally managed by the editor as the
  *     native anonymous content tree attached to the control's frame.
  *
  *   * If the text editor state object is unbound from the control's frame, the value is
  *     transferred to the mValue member variable, and will be managed there until a new
  *     frame is bound to the text editor state object.
  */
 
+class RestoreSelectionState;
+
 class nsTextEditorState {
 public:
   explicit nsTextEditorState(nsITextControlElement* aOwningElement);
   ~nsTextEditorState();
 
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsTextEditorState)
   NS_INLINE_DECL_REFCOUNTING(nsTextEditorState)
 
@@ -208,28 +210,32 @@ public:
   /* called to free up native keybinding services */
   static NS_HIDDEN_(void) ShutDown();
 
   void ClearValueCache() { mCachedValue.Truncate(); }
 
   void HideSelectionIfBlurred();
 
 private:
+  friend class RestoreSelectionState;
+
   // not copy constructible
   nsTextEditorState(const nsTextEditorState&);
   // not assignable
   void operator= (const nsTextEditorState&);
 
   nsresult CreateRootNode();
 
   void ValueWasChanged(PRBool aNotify);
 
   void DestroyEditor();
   void Clear();
 
+  void FinishedRestoringSelection() { mRestoringSelection = nsnull; }
+
   class InitializationGuard {
   public:
     explicit InitializationGuard(nsTextEditorState& aState) :
       mState(aState),
       mGuardSet(PR_FALSE)
     {
       if (!mState.mInitializing) {
         mGuardSet = PR_TRUE;
@@ -248,16 +254,17 @@ private:
     nsTextEditorState& mState;
     PRBool mGuardSet;
   };
   friend class InitializationGuard;
 
   nsITextControlElement* const mTextCtrlElement;
   nsRefPtr<nsTextInputSelectionImpl> mSelCon;
   nsAutoPtr<SelectionState> mSelState;
+  RestoreSelectionState* mRestoringSelection;
   nsCOMPtr<nsIEditor> mEditor;
   nsCOMPtr<nsIContent> mRootNode;
   nsCOMPtr<nsIContent> mPlaceholderDiv;
   nsTextControlFrame* mBoundFrame;
   nsTextInputListener* mTextListener;
   nsAutoPtr<nsCString> mValue;
   nsRefPtr<nsAnonDivObserver> mMutationObserver;
   mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control.
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -196,16 +196,22 @@ nsTextControlFrame::~nsTextControlFrame(
 {
 }
 
 void
 nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   mScrollEvent.Revoke();
 
+  EditorInitializer* initializer = (EditorInitializer*) Properties().Get(TextControlInitializer());
+  if (initializer) {
+    initializer->Revoke();
+    Properties().Delete(TextControlInitializer());
+  }
+
   // Unbind the text editor state object from the frame.  The editor will live
   // on, but things like controllers will be released.
   nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
   NS_ASSERTION(txtCtrl, "Content not a text control element");
   txtCtrl->UnbindFromFrame(this);
 
   nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), PR_FALSE);
 
@@ -440,20 +446,27 @@ nsTextControlFrame::CreateAnonymousConte
       // so are input text controls with spellcheck=true
       element->GetSpellcheck(&initEagerly);
     }
   }
 
   if (initEagerly) {
     NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                  "Someone forgot a script blocker?");
-
-    if (!nsContentUtils::AddScriptRunner(new EditorInitializer(this))) {
+    EditorInitializer* initializer = (EditorInitializer*) Properties().Get(TextControlInitializer());
+    if (initializer) {
+      initializer->Revoke();
+    }
+    initializer = new EditorInitializer(this);
+    if (!nsContentUtils::AddScriptRunner(initializer)) {
+      initializer->Revoke(); // paranoia
+      delete initializer;
       return NS_ERROR_OUT_OF_MEMORY;
     }
+    Properties().Set(TextControlInitializer(),initializer);
   }
 
   return NS_OK;
 }
 
 void
 nsTextControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
                                              PRUint32 aFilter)
--- a/layout/forms/nsTextControlFrame.h
+++ b/layout/forms/nsTextControlFrame.h
@@ -175,16 +175,21 @@ public:
   NS_IMETHOD AttributeChanged(PRInt32         aNameSpaceID,
                               nsIAtom*        aAttribute,
                               PRInt32         aModType);
 
   nsresult GetText(nsString& aText);
 
   NS_DECL_QUERYFRAME
 
+  // Temp reference to scriptrunner
+  // We could make these auto-Revoking via the "delete" entry for safety
+  NS_DECLARE_FRAME_PROPERTY(TextControlInitializer, nsnull)
+
+
 public: //for methods who access nsTextControlFrame directly
   void FireOnInput(PRBool aTrusted);
   void SetValueChanged(PRBool aValueChanged);
   /** Called when the frame is focused, to remember the value for onChange. */
   nsresult InitFocusedValue();
 
   void SetFireChangeEventState(PRBool aNewState)
   {
@@ -281,33 +286,37 @@ public: //for methods who access nsTextC
 protected:
   class EditorInitializer;
   friend class EditorInitializer;
   friend class nsTextEditorState; // needs access to UpdateValueDisplay
 
   class EditorInitializer : public nsRunnable {
   public:
     EditorInitializer(nsTextControlFrame* aFrame) :
-      mWeakFrame(aFrame),
       mFrame(aFrame) {}
 
     NS_IMETHOD Run() {
-      if (mWeakFrame) {
+      if (mFrame) {
         nsCOMPtr<nsIPresShell> shell =
-          mWeakFrame.GetFrame()->PresContext()->GetPresShell();
+          mFrame->PresContext()->GetPresShell();
         PRBool observes = shell->ObservesNativeAnonMutationsForPrint();
         shell->ObserveNativeAnonMutationsForPrint(PR_TRUE);
         mFrame->EnsureEditorInitialized();
         shell->ObserveNativeAnonMutationsForPrint(observes);
+        mFrame->FinishedInitializer();
       }
       return NS_OK;
     }
 
+    // avoids use of nsWeakFrame
+    void Revoke() {
+      mFrame = nsnull;
+    }
+
   private:
-    nsWeakFrame mWeakFrame;
     nsTextControlFrame* mFrame;
   };
 
   class ScrollOnFocusEvent;
   friend class ScrollOnFocusEvent;
 
   class ScrollOnFocusEvent : public nsRunnable {
   public:
@@ -383,16 +392,20 @@ private:
   PRBool GetNotifyOnInput() const { return mNotifyOnInput; }
   void SetNotifyOnInput(PRBool val) { mNotifyOnInput = val; }
 
   /**
    * Return the root DOM element, and implicitly initialize the editor if needed.
    */
   nsresult GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement);
 
+  void FinishedInitializer() {
+    Properties().Delete(TextControlInitializer());
+  }
+
 private:
   // these packed bools could instead use the high order bits on mState, saving 4 bytes 
   PRPackedBool mUseEditor;
   PRPackedBool mIsProcessing;
   PRPackedBool mNotifyOnInput;//default this to off to stop any notifications until setup is complete
   // Calls to SetValue will be treated as user values (i.e. trigger onChange
   // eventually) when mFireChangeEventState==true, this is used by nsFileControlFrame.
   PRPackedBool mFireChangeEventState;