Bug 399852 - Crash [@ nsCSSFrameConstructor::FindFrameWithContent] with position: fixed, focusing and contenteditable, patch by Chris Pearce, r+sr=roc, a=blocking1.9+
authormartijn.martijn@gmail.com
Wed, 09 Jan 2008 13:44:59 -0800
changeset 10097 5c4bd278743bffd7b9c079a05dd19f9a71641948
parent 10096 c87d748b173f8e789b3f509ee073104a3fce710f
child 10098 f7d6e832403f693bcd439472c8d63b97fd19aa8a
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblocking1.9
bugs399852
milestone1.9b3pre
Bug 399852 - Crash [@ nsCSSFrameConstructor::FindFrameWithContent] with position: fixed, focusing and contenteditable, patch by Chris Pearce, r+sr=roc, a=blocking1.9+
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
view/public/nsIViewManager.h
view/src/nsView.cpp
view/src/nsViewManager.cpp
view/src/nsViewManager.h
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -61,16 +61,17 @@
 #include "nsTableColFrame.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLTableColElement.h"
 #include "nsIDOMHTMLTableCaptionElem.h"
 #include "nsHTMLParts.h"
 #include "nsIPresShell.h"
 #include "nsStyleSet.h"
 #include "nsIViewManager.h"
+#include "nsViewManager.h"
 #include "nsIEventStateManager.h"
 #include "nsIScrollableView.h"
 #include "nsStyleConsts.h"
 #include "nsTableOuterFrame.h"
 #include "nsIDOMXULElement.h"
 #include "nsHTMLContainerFrame.h"
 #include "nsINameSpaceManager.h"
 #include "nsIDOMHTMLSelectElement.h"
@@ -374,16 +375,36 @@ static PRInt32 FFWC_recursions=0;
 static PRInt32 FFWC_nextInFlows=0;
 static PRInt32 FFWC_slowSearchForText=0;
 #endif
 
 static nsresult
 DeletingFrameSubtree(nsFrameManager* aFrameManager,
                      nsIFrame*       aFrame);
 
+void nsFocusEventSuppressor::Suppress(nsIPresShell *aPresShell)
+{
+  NS_ASSERTION(aPresShell, "Need non-null nsIPresShell!");
+  NS_ASSERTION(!mViewManager, "Suppress before a pending UnSuppress()");
+  nsFrameManager *frameManager = aPresShell->FrameManager();
+  mViewManager = frameManager->GetPresContext()->GetViewManager();
+  if (mViewManager) {
+    mOldSuppressState = mViewManager->GetSuppressFocusEvents();
+    mViewManager->SetSuppressFocusEvents(PR_TRUE);
+  }
+}
+
+void nsFocusEventSuppressor::Unsuppress()
+{
+  if (mViewManager) {
+    mViewManager->SetSuppressFocusEvents(mOldSuppressState);
+    mViewManager = nsnull;
+  }
+}
+
 #ifdef  MOZ_SVG
 
 static nsIFrame *
 SVG_GetFirstNonAAncestorFrame(nsIFrame *aParentFrame)
 {
   for (nsIFrame *ancestorFrame = aParentFrame; ancestorFrame != nsnull;
        ancestorFrame = ancestorFrame->GetParent()) {
     if (ancestorFrame->GetType() != nsGkAtoms::svgAFrame) {
@@ -10233,24 +10254,34 @@ nsCSSFrameConstructor::AttributeChanged(
                                                                   aStateMask);
 
   PostRestyleEvent(aContent, rshint, hint);
 
   return result;
 }
 
 void
+nsCSSFrameConstructor::BeginUpdate() {
+  if (!mUpdateCount) {
+    mFocusSuppressor.Suppress(mPresShell);
+  }
+  ++mUpdateCount;
+}
+
+void
 nsCSSFrameConstructor::EndUpdate()
 {
   if (mUpdateCount == 1) {
     // This is the end of our last update.  Before we decrement
     // mUpdateCount, recalc quotes and counters as needed.
 
     RecalcQuotesAndCounters();
     NS_ASSERTION(mUpdateCount == 1, "Odd update count");
+
+    mFocusSuppressor.Unsuppress();
   }
 
   --mUpdateCount;
 }
 
 void
 nsCSSFrameConstructor::RecalcQuotesAndCounters()
 {
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -47,16 +47,17 @@
 #include "nsILayoutHistoryState.h"
 #include "nsIXBLService.h"
 #include "nsQuoteList.h"
 #include "nsCounterManager.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsThreadUtils.h"
 #include "nsPageContentFrame.h"
+#include "nsIViewManager.h"
 
 class nsIDocument;
 struct nsFrameItems;
 struct nsAbsoluteItems;
 class nsStyleContext;
 struct nsStyleContent;
 struct nsStyleDisplay;
 class nsIPresShell;
@@ -68,16 +69,30 @@ class nsStyleChangeList;
 class nsIFrame;
 
 struct nsFindFrameHint
 {
   nsIFrame *mPrimaryFrameForPrevSibling;  // weak ref to the primary frame for the content for which we need a frame
   nsFindFrameHint() : mPrimaryFrameForPrevSibling(nsnull) { }
 };
 
+// Class which makes an nsIPresShell's ViewManager supress
+// focus/blur events. This prevents the frame tree from being changed
+// by focus handlers etc while *we* are trying to change it.
+// Fix for bug 399852.
+class nsFocusEventSuppressor
+{
+public:
+  void Suppress(nsIPresShell *aPresShell);
+  void Unsuppress();
+private:
+  nsCOMPtr<nsIViewManager> mViewManager;
+  PRBool mOldSuppressState;
+};
+
 typedef void (PR_CALLBACK nsLazyFrameConstructionCallback)
              (nsIContent* aContent, nsIFrame* aFrame, void* aArg);
 
 class nsFrameConstructorState;
 class nsFrameConstructorSaveState;
   
 class nsCSSFrameConstructor
 {
@@ -143,30 +158,33 @@ public:
   void NotifyDestroyingFrame(nsIFrame* aFrame);
 
   nsresult AttributeChanged(nsIContent* aContent,
                             PRInt32     aNameSpaceID,
                             nsIAtom*    aAttribute,
                             PRInt32     aModType,
                             PRUint32    aStateMask);
 
-  void BeginUpdate() { ++mUpdateCount; }
+  void BeginUpdate();
   void EndUpdate();
   void RecalcQuotesAndCounters();
 
   void WillDestroyFrameTree();
 
   // Note: It's the caller's responsibility to make sure to wrap a
   // ProcessRestyledFrames call in a view update batch.
   // This function does not call ProcessAttachedQueue() on the binding manager.
   // If the caller wants that to happen synchronously, it needs to handle that
   // itself.
   nsresult ProcessRestyledFrames(nsStyleChangeList& aRestyleArray);
 
 private:
+
+  nsFocusEventSuppressor mFocusSuppressor;
+
   // Note: It's the caller's responsibility to make sure to wrap a
   // ProcessOneRestyle call in a view update batch.
   // This function does not call ProcessAttachedQueue() on the binding manager.
   // If the caller wants that to happen synchronously, it needs to handle that
   // itself.
   void ProcessOneRestyle(nsIContent* aContent, nsReStyleHint aRestyleHint,
                          nsChangeHint aChangeHint);
 
--- a/view/public/nsIViewManager.h
+++ b/view/public/nsIViewManager.h
@@ -435,16 +435,28 @@ public:
                                nsRectVisibility *aRectVisibility)=0;
 
   /**
    * Dispatch a mouse move event based on the most recent mouse
    * position.  This is used when the contents of the page moved
    * (aFromScroll is false) or scrolled (aFromScroll is true).
    */
   NS_IMETHOD SynthesizeMouseMove(PRBool aFromScroll)=0;
+
+  /**
+   * Toggles global suppression of focus/blur events. When suppression
+   * is on, focus/blur events will not be sent to their target widgets/views.
+   * Note that when called with aSuppress as false, blur/focus events are
+   * fired to reset the focus. This can run arbitrary code, and could
+   * even destroy the view manager.
+   */
+  virtual void SetSuppressFocusEvents(PRBool aSuppress)=0;
+
+  virtual PRBool GetSuppressFocusEvents()=0;
+
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIViewManager, NS_IVIEWMANAGER_IID)
 
 // Paint timing mode flags
 
 // intermediate: do no special timing processing; repaint when the
 // toolkit issues an expose event (which will happen *before* PLEvent
--- a/view/src/nsView.cpp
+++ b/view/src/nsView.cpp
@@ -195,16 +195,23 @@ void nsView::DropMouseGrabbing() {
     mViewManager->GrabMouseEvents(GetParent(), boolResult);
   }
 }
 
 nsView::~nsView()
 {
   MOZ_COUNT_DTOR(nsView);
 
+  if (this == nsViewManager::GetViewFocusedBeforeSuppression()) {
+    nsViewManager::SetViewFocusedBeforeSuppression(nsnull);
+  }
+  if (this == nsViewManager::GetCurrentlyFocusedView()) {
+    nsViewManager::SetCurrentlyFocusedView(nsnull);
+  }
+
   while (GetFirstChild())
   {
     nsView* child = GetFirstChild();
     if (child->GetViewManager() == mViewManager) {
       child->Destroy();
     } else {
       // just unhook it. Someone else will want to destroy this.
       RemoveChild(child);
--- a/view/src/nsViewManager.cpp
+++ b/view/src/nsViewManager.cpp
@@ -930,16 +930,73 @@ void nsViewManager::UpdateViews(nsView *
   // update all children as well.
   nsView* childView = aView->GetFirstChild();
   while (nsnull != childView)  {
     UpdateViews(childView, aUpdateFlags);
     childView = childView->GetNextSibling();
   }
 }
 
+PRBool nsViewManager::sSuppressFocusEvents = PR_FALSE;
+nsView *nsViewManager::sCurrentlyFocusView = nsnull;
+nsView *nsViewManager::sViewFocusedBeforeSuppression = nsnull;
+
+// Enables/disables focus/blur event suppression. When suppression
+// is disabled, we "reboot" the focus by sending a blur to what was
+// focused before suppression began, and by sending a focus event to
+// what should be currently focused. The suppression should be enabled
+// when we're messing with the frame tree, so focus/blur handlers
+// don't mess with stuff while we're trying too. See Bug 399852.
+void nsViewManager::SetSuppressFocusEvents(PRBool aSuppress)
+{
+  if (sSuppressFocusEvents && !aSuppress) {
+    // We're turning off suppression, synthesize LOSTFOCUS/GOTFOCUS.
+    if (GetCurrentlyFocusedView() != GetViewFocusedBeforeSuppression()) {
+
+      // Turn off suppresion before we send blur/focus events.
+      sSuppressFocusEvents = aSuppress;
+    
+      nsIWidget *widget = nsnull;
+      nsEventStatus status;
+
+      // Backup what is focused before we send the blur. If the
+      // blur causes a focus change, keep that new focus change,
+      // don't overwrite with the old "currently focused view".
+      nsIView *currentFocusBeforeBlur = GetCurrentlyFocusedView();
+
+      // Send NS_LOSTFOCUS to widget that was focused before
+      // focus/blur suppression.
+      if (GetViewFocusedBeforeSuppression()) {
+        widget = GetViewFocusedBeforeSuppression()->GetWidget();
+        if (widget) {
+          nsGUIEvent event(PR_TRUE, NS_LOSTFOCUS, widget);
+          widget->DispatchEvent(&event, status);
+        }
+      }
+
+      // Send NS_GOTFOCUS to the widget that we think should be focused.
+      if (GetCurrentlyFocusedView() &&
+          currentFocusBeforeBlur == GetCurrentlyFocusedView())
+      {
+        widget = GetCurrentlyFocusedView()->GetWidget();
+        if (widget) {
+          nsGUIEvent event(PR_TRUE, NS_GOTFOCUS, widget);
+          widget->DispatchEvent(&event, status); 
+        }
+      }
+    }
+
+  } else if (!sSuppressFocusEvents && aSuppress) {
+    // We're turning on focus/blur suppression, remember what had
+    // the focus.
+    SetViewFocusedBeforeSuppression(GetCurrentlyFocusedView());
+    sSuppressFocusEvents = aSuppress;
+  }
+}
+
 NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aStatus)
 {
   *aStatus = nsEventStatus_eIgnore;
 
   switch(aEvent->message)
     {
     case NS_SIZE:
       {
@@ -1133,16 +1190,22 @@ NS_IMETHODIMP nsViewManager::DispatchEve
         if (obs) {
           obs->HandleEvent(view, aEvent, aStatus);
         }
       }
       break; 
 
     default:
       {
+        if (aEvent->message == NS_GOTFOCUS)
+          SetCurrentlyFocusedView(nsView::GetViewFor(aEvent->widget));
+        if ((aEvent->message == NS_GOTFOCUS || aEvent->message == NS_LOSTFOCUS) &&
+             nsViewManager::GetSuppressFocusEvents())
+          break;
+
         if ((NS_IS_MOUSE_EVENT(aEvent) &&
              // Ignore moves that we synthesize.
              static_cast<nsMouseEvent*>(aEvent)->reason ==
                nsMouseEvent::eReal &&
              // Ignore mouse exit and enter (we'll get moves if the user
              // is really moving the mouse) since we get them when we
              // create and destroy widgets.
              aEvent->message != NS_MOUSE_EXIT &&
--- a/view/src/nsViewManager.h
+++ b/view/src/nsViewManager.h
@@ -196,20 +196,52 @@ public:
                                nsRectVisibility *aRectVisibility);
 
   NS_IMETHOD SynthesizeMouseMove(PRBool aFromScroll);
   void ProcessSynthMouseMoveEvent(PRBool aFromScroll);
 
   /* Update the cached RootViewManager pointer on this view manager. */
   void InvalidateHierarchy();
 
+  virtual void SetSuppressFocusEvents(PRBool aSuppress);
+
+  virtual PRBool GetSuppressFocusEvents()
+  {
+    return sSuppressFocusEvents;
+  }
+
+  static void SetCurrentlyFocusedView(nsView *aView)
+  {
+    sCurrentlyFocusView = aView;
+  }
+  
+  static nsView* GetCurrentlyFocusedView()
+  {
+    return sCurrentlyFocusView;
+  }
+
+  static void SetViewFocusedBeforeSuppression(nsView *aView)
+  {
+    sViewFocusedBeforeSuppression = aView;
+  }
+
+  static nsView* GetViewFocusedBeforeSuppression()
+  {
+    return sViewFocusedBeforeSuppression;
+  }
+
 protected:
   virtual ~nsViewManager();
 
 private:
+
+  static nsView *sCurrentlyFocusView;
+  static nsView *sViewFocusedBeforeSuppression;
+  static PRBool sSuppressFocusEvents;
+
   void FlushPendingInvalidates();
   void ProcessPendingUpdates(nsView *aView, PRBool aDoInvalidate);
   void ReparentChildWidgets(nsIView* aView, nsIWidget *aNewWidget);
   void ReparentWidgets(nsIView* aView, nsIView *aParent);
   already_AddRefed<nsIRenderingContext> CreateRenderingContext(nsView &aView);
   void UpdateWidgetArea(nsView *aWidgetView, const nsRegion &aDamagedRegion,
                         nsView* aIgnoreWidgetView);