Bug 482578, r=bz, sr=sicking, a=samuel.sidler
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Tue, 14 Jul 2009 13:32:43 +0300
changeset 26057 f223409207c0
parent 26056 a1a6b17a22da
child 26058 6b3d980f794a
push id1765
push useropettay@mozilla.com
push date2009-07-14 10:33 +0000
reviewersbz, sicking, samuel
bugs482578
milestone1.9.1.1pre
Bug 482578, r=bz, sr=sicking, a=samuel.sidler
layout/base/nsPresShell.cpp
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -756,16 +756,17 @@ FrameArena::FreeFrame(size_t aSize, void
 struct nsCallbackEventRequest
 {
   nsIReflowCallback* callback;
   nsCallbackEventRequest* next;
 };
 
 // ----------------------------------------------------------------------------
 class nsPresShellEventCB;
+class nsAutoCauseReflowNotifier;
 
 class PresShell : public nsIPresShell, public nsIViewObserver,
                   public nsStubDocumentObserver,
                   public nsISelectionController, public nsIObserver,
                   public nsSupportsWeakReference
 {
 public:
   PresShell();
@@ -1012,16 +1013,23 @@ public:
 protected:
   virtual ~PresShell();
 
   void HandlePostedReflowCallbacks();
   void CancelPostedReflowCallbacks();
 
   void UnsuppressAndInvalidate();
 
+  void WillCauseReflow() {
+    nsContentUtils::AddScriptBlocker();
+    ++mChangeNestCount;
+  }
+  nsresult DidCauseReflow();
+  friend class nsAutoCauseReflowNotifier;
+
   void     WillDoReflow();
   void     DidDoReflow();
   nsresult ProcessReflowCommands(PRBool aInterruptible);
   void     ClearReflowEventStatus();
   void     PostReflowEvent();
   
   void DoReflow(nsIFrame* aFrame);
 #ifdef DEBUG
@@ -1125,16 +1133,21 @@ protected:
   // reflow roots that need to be reflowed, as both a queue and a hashtable
   nsVoidArray mDirtyRoots;
 
   PRPackedBool mDocumentLoading;
   PRPackedBool mIsReflowing;
 
   PRPackedBool mIgnoreFrameDestruction;
   PRPackedBool mHaveShutDown;
+
+  // This is used to protect ourselves from triggering reflow while in the
+  // middle of frame construction and the like... it really shouldn't be
+  // needed, one hopes, but it is for now.
+  PRUint32  mChangeNestCount;
   
   nsIFrame*   mCurrentEventFrame;
   nsCOMPtr<nsIContent> mCurrentEventContent;
   nsVoidArray mCurrentEventFrameStack;
   nsCOMArray<nsIContent> mCurrentEventContentStack;
 
   nsCOMPtr<nsIContent>          mLastAnchorScrolledTo;
   nscoord                       mLastAnchorScrollPositionY;
@@ -1319,16 +1332,39 @@ private:
   nsCOMPtr<nsITimer> mResizeEventTimer;
 
   typedef void (*nsPluginEnumCallback)(PresShell*, nsIContent*);
   void EnumeratePlugins(nsIDOMDocument *aDocument,
                         const nsString &aPluginTag,
                         nsPluginEnumCallback aCallback);
 };
 
+class nsAutoCauseReflowNotifier
+{
+public:
+  nsAutoCauseReflowNotifier(PresShell* aShell)
+    : mShell(aShell)
+  {
+    mShell->WillCauseReflow();
+  }
+  ~nsAutoCauseReflowNotifier()
+  {
+    // This check should not be needed. Currently the only place that seem
+    // to need it is the code that deals with bug 337586.
+    if (!mShell->mHaveShutDown) {
+      mShell->DidCauseReflow();
+    }
+    else {
+      nsContentUtils::RemoveScriptBlocker();
+    }
+  }
+
+  PresShell* mShell;
+};
+
 class NS_STACK_CLASS nsPresShellEventCB : public nsDispatchingCallback
 {
 public:
   nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {}
 
   virtual void HandleEvent(nsEventChainPostVisitor& aVisitor)
   {
     if (aVisitor.mPresContext && aVisitor.mEvent->eventStructType != NS_EVENT) {
@@ -2618,17 +2654,17 @@ PresShell::InitialReflow(nscoord aWidth,
   
   if (root) {
     MOZ_TIMER_DEBUGLOG(("Reset and start: Frame Creation: PresShell::InitialReflow(), this=%p\n",
                         (void*)this));
     MOZ_TIMER_RESET(mFrameCreationWatch);
     MOZ_TIMER_START(mFrameCreationWatch);
 
     {
-      nsAutoScriptBlocker scriptBlocker;
+      nsAutoCauseReflowNotifier reflowNotifier(this);
       mFrameConstructor->BeginUpdate();
 
       if (!rootFrame) {
         // Have style sheet processor construct a frame for the
         // precursors to the root content object's frame
         mFrameConstructor->ConstructRootFrame(root, &rootFrame);
         FrameManager()->SetRootFrame(rootFrame);
       }
@@ -2770,17 +2806,17 @@ PresShell::ResizeReflow(nscoord aWidth, 
       mFrameConstructor->ProcessPendingRestyles();
     }
 
     if (!mIsDestroying) {
       // XXX Do a full invalidate at the beginning so that invalidates along
       // the way don't have region accumulation issues?
 
       {
-        nsAutoScriptBlocker scriptBlocker;
+        nsAutoCauseReflowNotifier crNotifier(this);
         WillDoReflow();
 
         // Kick off a top-down reflow
         AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
         mIsReflowing = PR_TRUE;
 
         mDirtyRoots.RemoveElement(rootFrame);
         DoReflow(rootFrame);
@@ -3305,29 +3341,32 @@ PresShell::RestoreRootScrollPosition()
   // Restore frame state for the root scroll frame
   nsCOMPtr<nsILayoutHistoryState> historyState =
     mDocument->GetLayoutHistoryState();
   // Make sure we don't reenter reflow via the sync paint that happens while
   // we're scrolling to our restored position.  Entering reflow for the
   // scrollable frame will cause it to reenter ScrollToRestoredPosition(), and
   // it'll get all confused.
   nsAutoScriptBlocker scriptBlocker;
+  ++mChangeNestCount;
 
   if (historyState) {
     nsIFrame* scrollFrame = GetRootScrollFrame();
     if (scrollFrame) {
       nsIScrollableFrame* scrollableFrame;
       CallQueryInterface(scrollFrame, &scrollableFrame);
       if (scrollableFrame) {
         FrameManager()->RestoreFrameStateFor(scrollFrame, historyState,
                                              nsIStatefulFrame::eDocumentScrollState);
         scrollableFrame->ScrollToRestoredPosition();
       }
     }
   }
+
+  --mChangeNestCount;
 }
 
 void
 PresShell::BeginLoad(nsIDocument *aDocument)
 {  
 #ifdef MOZ_PERF_METRICS
   // Reset style resolution stopwatch maintained by style set
   MOZ_TIMER_DEBUGLOG(("Reset: Style Resolution: PresShell::BeginLoad(), this=%p\n", (void*)this));
@@ -3619,17 +3658,20 @@ PresShell::RecreateFramesFor(nsIContent*
   // start messing with the frame model; otherwise we can get content doubling.
   mDocument->FlushPendingNotifications(Flush_ContentAndNotify);
 
   nsAutoScriptBlocker scriptBlocker;
 
   nsStyleChangeList changeList;
   changeList.AppendChange(nsnull, aContent, nsChangeHint_ReconstructFrame);
 
+  // Mark ourselves as not safe to flush while we're doing frame construction.
+  ++mChangeNestCount;
   nsresult rv = mFrameConstructor->ProcessRestyledFrames(changeList);
+  --mChangeNestCount;
   
   batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC);
 #ifdef ACCESSIBILITY
   InvalidateAccessibleSubtree(aContent);
 #endif
   return rv;
 }
 
@@ -4719,31 +4761,32 @@ PresShell::HandlePostedReflowCallbacks()
 
    if (shouldFlush)
      FlushPendingNotifications(Flush_Layout);
 }
 
 NS_IMETHODIMP 
 PresShell::IsSafeToFlush(PRBool& aIsSafeToFlush)
 {
-  aIsSafeToFlush = nsContentUtils::IsSafeToRunScript();
-#ifdef DEBUG
   // Not safe if we are reflowing or in the middle of frame construction
-  PRBool isSafeToFlush = !mIsReflowing;
-  // Not safe if we are painting
-  nsIViewManager* viewManager = GetViewManager();
-  if (viewManager) {
-    PRBool isPainting = PR_FALSE;
-    viewManager->IsPainting(isPainting);
-    if (isPainting) {
-      isSafeToFlush = PR_FALSE;
-    }
-  }
-  NS_ASSERTION(!aIsSafeToFlush || isSafeToFlush, "Missing a script blocker!");
-#endif
+  aIsSafeToFlush = !mIsReflowing &&
+                   !mChangeNestCount;
+
+  if (aIsSafeToFlush) {
+    // Not safe if we are painting
+    nsIViewManager* viewManager = GetViewManager();
+    if (viewManager) {
+      PRBool isPainting = PR_FALSE;
+      viewManager->IsPainting(isPainting);
+      if (isPainting) {
+        aIsSafeToFlush = PR_FALSE;
+      }
+    }
+  }
+
   return NS_OK;
 }
 
 
 NS_IMETHODIMP 
 PresShell::FlushPendingNotifications(mozFlushType aType)
 {
   return DoFlushPendingNotifications(aType, PR_FALSE);
@@ -4752,16 +4795,17 @@ PresShell::FlushPendingNotifications(moz
 nsresult
 PresShell::DoFlushPendingNotifications(mozFlushType aType,
                                        PRBool aInterruptibleReflow)
 {
   NS_ASSERTION(aType >= Flush_Frames, "Why did we get called?");
   
   PRBool isSafeToFlush;
   IsSafeToFlush(isSafeToFlush);
+  isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
 
   NS_ASSERTION(!isSafeToFlush || mViewManager, "Must have view manager");
   // Make sure the view manager stays alive while batching view updates.
   nsCOMPtr<nsIViewManager> viewManagerDeathGrip = mViewManager;
   if (isSafeToFlush && mViewManager) {
     // Processing pending notifications can kill us, and some callers only
     // hold weak refs when calling FlushPendingNotifications().  :(
     nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
@@ -4848,17 +4892,17 @@ PresShell::IsReflowLocked(PRBool* aIsRef
 void
 PresShell::CharacterDataChanged(nsIDocument *aDocument,
                                 nsIContent*  aContent,
                                 CharacterDataChangeInfo* aInfo)
 {
   NS_PRECONDITION(!mIsDocumentGone, "Unexpected CharacterDataChanged");
   NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
 
-  nsAutoScriptBlocker scriptBlocker;
+  nsAutoCauseReflowNotifier crNotifier(this);
 
   if (mCaret) {
     // Invalidate the caret's current location before we call into the frame
     // constructor. It is important to do this now, and not wait until the
     // resulting reflow, because this call causes continuation frames of the
     // text frame the caret is in to forget what part of the content they
     // refer to, making it hard for them to return the correct continuation
     // frame to the caret.
@@ -4890,17 +4934,17 @@ PresShell::ContentStatesChanged(nsIDocum
                                 nsIContent* aContent1,
                                 nsIContent* aContent2,
                                 PRInt32 aStateMask)
 {
   NS_PRECONDITION(!mIsDocumentGone, "Unexpected ContentStatesChanged");
   NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
 
   if (mDidInitialReflow) {
-    nsAutoScriptBlocker scriptBlocker;
+    nsAutoCauseReflowNotifier crNotifier(this);
     mFrameConstructor->ContentStatesChanged(aContent1, aContent2, aStateMask);
     VERIFY_STYLE_TREE;
   }
 }
 
 
 void
 PresShell::AttributeChanged(nsIDocument* aDocument,
@@ -4912,17 +4956,17 @@ PresShell::AttributeChanged(nsIDocument*
 {
   NS_PRECONDITION(!mIsDocumentGone, "Unexpected AttributeChanged");
   NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
 
   // XXXwaterson it might be more elegant to wait until after the
   // initial reflow to begin observing the document. That would
   // squelch any other inappropriate notifications as well.
   if (mDidInitialReflow) {
-    nsAutoScriptBlocker scriptBlocker;
+    nsAutoCauseReflowNotifier crNotifier(this);
     mFrameConstructor->AttributeChanged(aContent, aNameSpaceID,
                                         aAttribute, aModType, aStateMask);
     VERIFY_STYLE_TREE;
   }
 }
 
 void
 PresShell::ContentAppended(nsIDocument *aDocument,
@@ -4932,17 +4976,17 @@ PresShell::ContentAppended(nsIDocument *
   NS_PRECONDITION(!mIsDocumentGone, "Unexpected ContentAppended");
   NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
   NS_PRECONDITION(aContainer, "must have container");
   
   if (!mDidInitialReflow) {
     return;
   }
   
-  nsAutoScriptBlocker scriptBlocker;
+  nsAutoCauseReflowNotifier crNotifier(this);
   MOZ_TIMER_DEBUGLOG(("Start: Frame Creation: PresShell::ContentAppended(), this=%p\n", this));
   MOZ_TIMER_START(mFrameCreationWatch);
 
   // Call this here so it only happens for real content mutations and
   // not cases when the frame constructor calls its own methods to force
   // frame reconstruction.
   mFrameConstructor->RestyleForAppend(aContainer, aNewIndexInContainer);
 
@@ -4961,17 +5005,17 @@ PresShell::ContentInserted(nsIDocument* 
 {
   NS_PRECONDITION(!mIsDocumentGone, "Unexpected ContentInserted");
   NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
 
   if (!mDidInitialReflow) {
     return;
   }
   
-  nsAutoScriptBlocker scriptBlocker;
+  nsAutoCauseReflowNotifier crNotifier(this);
 
   // Call this here so it only happens for real content mutations and
   // not cases when the frame constructor calls its own methods to force
   // frame reconstruction.
   if (aContainer)
     mFrameConstructor->RestyleForInsertOrChange(aContainer, aChild);
 
   mFrameConstructor->ContentInserted(aContainer, aChild,
@@ -4992,17 +5036,17 @@ PresShell::ContentRemoved(nsIDocument *a
   if (mCaret) {
     mCaret->InvalidateOutsideCaret();
   }
 
   // Notify the ESM that the content has been removed, so that
   // it can clean up any state related to the content.
   mPresContext->EventStateManager()->ContentRemoved(aChild);
 
-  nsAutoScriptBlocker scriptBlocker;
+  nsAutoCauseReflowNotifier crNotifier(this);
 
   // Call this here so it only happens for real content mutations and
   // not cases when the frame constructor calls its own methods to force
   // frame reconstruction.
   if (aContainer)
     mFrameConstructor->RestyleForRemove(aContainer, aChild, aIndexInContainer);
 
   PRBool didReconstruct;
@@ -5013,17 +5057,17 @@ PresShell::ContentRemoved(nsIDocument *a
 }
 
 nsresult
 PresShell::ReconstructFrames(void)
 {
   if (!mPresContext || !mPresContext->IsDynamic()) {
     return NS_OK;
   }
-  nsAutoScriptBlocker scriptBlocker;
+  nsAutoCauseReflowNotifier crNotifier(this);
   mFrameConstructor->BeginUpdate();
   nsresult rv = mFrameConstructor->ReconstructDocElementHierarchy();
   VERIFY_STYLE_TREE;
   mFrameConstructor->EndUpdate();
 
   return rv;
 }
 
@@ -6567,16 +6611,26 @@ PresShell::PostReflowEvent()
 #ifdef DEBUG
     if (VERIFY_REFLOW_NOISY_RC & gVerifyReflowFlags) {
       printf("\n*** PresShell::PostReflowEvent(), this=%p, event=%p\n", (void*)this, (void*)ev);
     }
 #endif    
   }
 }
 
+nsresult
+PresShell::DidCauseReflow()
+{
+  NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()");
+  --mChangeNestCount;
+  nsContentUtils::RemoveScriptBlocker();
+
+  return NS_OK;
+}
+
 void
 PresShell::WillDoReflow()
 {
   // We just reflowed, tell the caret that its frame might have moved.
   // XXXbz that comment makes no sense
   if (mCaret) {
     mCaret->InvalidateOutsideCaret();
     mCaret->UpdateCaretPosition();
@@ -6911,17 +6965,19 @@ PresShell::Observe(nsISupports* aSubject
       // frames (hack!).
       nsStyleChangeList changeList;
       WalkFramesThroughPlaceholders(mPresContext, rootFrame,
                                     ReframeImageBoxes, &changeList);
       // Mark ourselves as not safe to flush while we're doing frame
       // construction.
       {
         nsAutoScriptBlocker scriptBlocker;
+        ++mChangeNestCount;
         mFrameConstructor->ProcessRestyledFrames(changeList);
+        --mChangeNestCount;
       }
 
       batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC);
 #ifdef ACCESSIBILITY
       InvalidateAccessibleSubtree(nsnull);
 #endif
     }
     return NS_OK;
@@ -7398,17 +7454,17 @@ PresShell::VerifyIncrementalReflow()
   nsCOMPtr<nsIPresShell> sh;
   rv = mDocument->CreateShell(cx, vm, newSet, getter_AddRefs(sh));
   NS_ENSURE_SUCCESS(rv, PR_FALSE);
   newSet.forget();
   // Note that after we create the shell, we must make sure to destroy it
   sh->SetVerifyReflowEnable(PR_FALSE); // turn off verify reflow while we're reflowing the test frame tree
   vm->SetViewObserver((nsIViewObserver *)((PresShell*)sh.get()));
   {
-    nsAutoScriptBlocker scriptBlocker;
+    nsAutoCauseReflowNotifier crNotifier(this);
     sh->InitialReflow(r.width, r.height);
   }
   mDocument->BindingManager()->ProcessAttachedQueue();
   sh->FlushPendingNotifications(Flush_Layout);
   sh->SetVerifyReflowEnable(PR_TRUE);  // turn on verify reflow again now that we're done reflowing the test frame tree
   // Force the non-primary presshell to unsuppress; it doesn't want to normally
   // because it thinks it's hidden
   ((PresShell*)sh.get())->mPaintingSuppressed = PR_FALSE;