diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp index d4fa567095b..0eba014f2cb 100644 --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -471,10 +471,6 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext, GenerateDragGesture(aPresContext, (nsGUIEvent*)aEvent); UpdateCursor(aPresContext, aEvent, mCurrentTarget, aStatus); GenerateMouseEnterExit(aPresContext, (nsGUIEvent*)aEvent); - // Flush reflows and invalidates to eliminate flicker when both a reflow - // and visual change occur in an event callback. See bug #36849 - // XXXbz eeeew. Why not fix viewmanager to flush reflows before painting?? - FlushPendingEvents(aPresContext); break; case NS_MOUSE_EXIT: GenerateMouseEnterExit(aPresContext, (nsGUIEvent*)aEvent); @@ -1514,10 +1510,11 @@ nsEventStateManager::GenerateDragGesture(nsPresContext* aPresContext, StopTrackingDragGesture(); } - } - // Now flush all pending notifications. - FlushPendingEvents(aPresContext); + // Now flush all pending notifications, for better responsiveness + // while dragging. + FlushPendingEvents(aPresContext); + } } // GenerateDragGesture nsresult @@ -2838,7 +2835,7 @@ nsEventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext, //reset mCurretTargetContent to what it was mCurrentTargetContent = targetBeforeEvent; - // Now flush all pending notifications. + // Now flush all pending notifications, for better responsiveness. FlushPendingEvents(aPresContext); } @@ -4429,12 +4426,7 @@ nsEventStateManager::FlushPendingEvents(nsPresContext* aPresContext) NS_PRECONDITION(nsnull != aPresContext, "nsnull ptr"); nsIPresShell *shell = aPresContext->GetPresShell(); if (shell) { - // This is not flushing _Display because of the mess that is bug 36849 - shell->FlushPendingNotifications(Flush_Layout); - nsIViewManager* viewManager = shell->GetViewManager(); - if (viewManager) { - viewManager->FlushPendingInvalidates(); - } + shell->FlushPendingNotifications(Flush_Display); } } diff --git a/editor/libeditor/base/nsEditor.cpp b/editor/libeditor/base/nsEditor.cpp index 0bc68aed234..4f90ee9604a 100644 --- a/editor/libeditor/base/nsEditor.cpp +++ b/editor/libeditor/base/nsEditor.cpp @@ -4381,8 +4381,10 @@ nsresult nsEditor::EndUpdateViewBatch() { PRUint32 updateFlag = NS_VMREFRESH_IMMEDIATE; + // If we're doing async updates, use NS_VMREFRESH_DEFERRED here, so that + // the reflows we caused will get processed before the invalidates. if (flags & nsIPlaintextEditor::eEditorUseAsyncUpdatesMask) - updateFlag = NS_VMREFRESH_NO_SYNC; + updateFlag = NS_VMREFRESH_DEFERRED; mViewManager->EndUpdateViewBatch(updateFlag); } diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index f0fd53f2473..8a4ab340b8d 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -39,6 +39,11 @@ #include "nsIPresShell.h" #include "nsILinkHandler.h" #include "nsIDocShellTreeItem.h" +#include "nsIDocShell.h" +#include "nsIContentViewer.h" +#include "nsIDocumentViewer.h" +#include "nsPIDOMWindow.h" +#include "nsIFocusController.h" #include "nsStyleSet.h" #include "nsImageLoader.h" #include "nsIContent.h" @@ -1229,6 +1234,42 @@ nsPresContext::SetPrintSettings(nsIPrintSettings *aPrintSettings) mPrintSettings = aPrintSettings; } +PRBool +nsPresContext::EnsureVisible(PRBool aUnsuppressFocus) +{ + nsCOMPtr docShell(do_QueryReferent(mContainer)); + if (docShell) { + nsCOMPtr cv; + docShell->GetContentViewer(getter_AddRefs(cv)); + // Make sure this is the content viewer we belong with + nsCOMPtr docV(do_QueryInterface(cv)); + if (docV) { + nsCOMPtr currentPresContext; + docV->GetPresContext(getter_AddRefs(currentPresContext)); + if (currentPresContext == this) { + // OK, this is us. We want to call Show() on the content viewer. But + // first, we need to suppress focus changes; otherwise the focus will + // get sent to the wrong place (toplevel window). + nsCOMPtr privWindow = do_GetInterface(docShell); + // XXXbz privWindow should never really be null! + nsIFocusController* fc = + privWindow ? privWindow->GetRootFocusController() : nsnull; + if (fc) { + fc->SetSuppressFocus(PR_TRUE, + "nsPresContext::EnsureVisible Suppression"); + } + cv->Show(); + if (fc && aUnsuppressFocus) { + fc->SetSuppressFocus(PR_FALSE, + "nsPresContext::EnsureVisible Suppression"); + } + return PR_TRUE; + } + } + } + return PR_FALSE; +} + nsresult NS_NewPresContext(nsPresContext::nsPresContextType aType, nsPresContext** aInstancePtrResult) diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index 5f79377aeae..3d0cfb37681 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -587,6 +587,16 @@ public: /* Accessor for table of frame properties */ nsPropertyTable* PropertyTable() { return &mPropertyTable; } + /* Helper function that ensures that this prescontext is shown in its + docshell if it's the most recent prescontext for the docshell. Returns + whether the prescontext is now being shown. + + @param aUnsuppressFocus If this is false, then focus will not be + unsuppressed when PR_TRUE is returned. It's the caller's responsibility + to unsuppress focus in that case. + */ + NS_HIDDEN_(PRBool) EnsureVisible(PRBool aUnsuppressFocus); + #ifdef MOZ_REFLOW_PERF NS_HIDDEN_(void) CountReflows(const char * aName, PRUint32 aType, nsIFrame * aFrame); diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 77c4806b0ec..748425ab6c8 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -188,7 +188,6 @@ // Content viewer interfaces #include "nsIContentViewer.h" -#include "nsIDocumentViewer.h" #ifdef IBMBIDI #include "nsIBidiKeyboard.h" @@ -1222,6 +1221,7 @@ public: nsEventStatus* aStatus); NS_IMETHOD ResizeReflow(nsIView *aView, nscoord aWidth, nscoord aHeight); NS_IMETHOD_(PRBool) IsVisible(); + NS_IMETHOD_(void) WillPaint(); // caret handling NS_IMETHOD GetCaret(nsICaret **aOutCaret); @@ -1333,7 +1333,7 @@ protected: nsresult AddDummyLayoutRequest(void); nsresult RemoveDummyLayoutRequest(void); - void WillCauseReflow() {} + void WillCauseReflow() { ++mChangeNestCount; } nsresult DidCauseReflow(); void DidDoReflow(); nsresult ProcessReflowCommands(PRBool aInterruptible); @@ -1393,6 +1393,11 @@ protected: 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 mCurrentEventContent; nsVoidArray mCurrentEventFrameStack; @@ -4640,38 +4645,11 @@ PresShell::IsPaintingSuppressed(PRBool* aResult) void PresShell::UnsuppressAndInvalidate() { - nsCOMPtr ourWindow = do_QueryInterface(mDocument->GetScriptGlobalObject()); - nsIFocusController *focusController = nsnull; - if (ourWindow) - focusController = ourWindow->GetRootFocusController(); - if (focusController) - // Suppress focus. The act of tearing down the old content viewer - // causes us to blur incorrectly. - focusController->SetSuppressFocus(PR_TRUE, "PresShell suppression on Web page loads"); - - nsCOMPtr container = mPresContext->GetContainer(); - if (container) { - nsCOMPtr cvc(do_QueryInterface(container)); - if (cvc) { - nsCOMPtr cv; - cvc->GetContentViewer(getter_AddRefs(cv)); - if (cv) { - nsCOMPtr kungFuDeathGrip(this); - cv->Show(); - // Calling |Show| may destroy us. Not sure why yet, but it's - // a smoketest blocker. - if (mIsDestroying) { - if (focusController) { - // Unsuppress focus now that we're exiting this code, - // otherwise we're stuck in focus suppression, which hoses most of Mozilla - focusController->SetSuppressFocus(PR_FALSE, "PresShell suppression on Web page loads"); - } - return; - } - } - } + if (!mPresContext->EnsureVisible(PR_FALSE)) { + // No point; we're about to be torn down anyway. + return; } - + mPaintingSuppressed = PR_FALSE; nsIFrame* rootFrame = FrameManager()->GetRootFrame(); if (rootFrame) { @@ -4680,6 +4658,13 @@ PresShell::UnsuppressAndInvalidate() rootFrame->Invalidate(rect, PR_FALSE); } + // This makes sure to get the same thing that nsPresContext::EnsureVisible() + // got. + nsCOMPtr container = mPresContext->GetContainer(); + nsCOMPtr ourWindow = do_GetInterface(container); + nsIFocusController* focusController = + ourWindow ? ourWindow->GetRootFocusController() : nsnull; + if (ourWindow) CheckForFocus(ourWindow, focusController, mDocument); @@ -4928,8 +4913,8 @@ PresShell::IsSafeToFlush(PRBool& aIsSafeToFlush) { aIsSafeToFlush = PR_TRUE; - if (mIsReflowing) { - // Not safe if we are reflowing + if (mIsReflowing || mChangeNestCount) { + // Not safe if we are reflowing or in the middle of frame construction aIsSafeToFlush = PR_FALSE; } else { // Not safe if we are painting @@ -5588,7 +5573,7 @@ PresShell::HandleEvent(nsIView *aView, NS_ASSERTION(aView, "null view"); aHandled = PR_TRUE; - if (mIsDestroying || mIsReflowing) { + if (mIsDestroying || mIsReflowing || mChangeNestCount) { return NS_OK; } @@ -6018,6 +6003,26 @@ PresShell::IsVisible() return res; } +NS_IMETHODIMP_(void) +PresShell::WillPaint() +{ + // Don't reenter reflow and don't reflow during frame construction + if (mIsReflowing || mChangeNestCount) { + return; + } + + // Process reflows, if we have them, to reduce flicker due to invalidates and + // reflow being interspersed. Note that we _do_ allow this to be + // interruptible; if we can't do all the reflows it's better to flicker a bit + // than to freeze up. + // XXXbz this update batch may not be strictly necessary, but it's good form. + // XXXbz should we be flushing out style changes here? Probably not, I'd say. + NS_ASSERTION(mViewManager, "Something weird is going on"); + mViewManager->BeginUpdateViewBatch(); + ProcessReflowCommands(PR_TRUE); + mViewManager->EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC); +} + nsresult PresShell::GetAgentStyleSheets(nsCOMArray& aSheets) { @@ -6150,12 +6155,15 @@ PresShell::PostReflowEvent() nsresult PresShell::DidCauseReflow() { - // We may have had more reflow commands appended to the queue during - // our reflow. Make sure these get processed at some point. - if (!gAsyncReflowDuringDocLoad && mDocumentLoading) { - FlushPendingNotifications(Flush_Layout); - } else { - PostReflowEvent(); + NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()"); + if (--mChangeNestCount == 0) { + // We may have had more reflow commands appended to the queue during + // our reflow. Make sure these get processed at some point. + if (!gAsyncReflowDuringDocLoad && mDocumentLoading) { + FlushPendingNotifications(Flush_Layout); + } else { + PostReflowEvent(); + } } return NS_OK; diff --git a/layout/generic/nsObjectFrame.cpp b/layout/generic/nsObjectFrame.cpp index 377a2c4e9af..f05f9a7f33c 100644 --- a/layout/generic/nsObjectFrame.cpp +++ b/layout/generic/nsObjectFrame.cpp @@ -104,15 +104,11 @@ #include "nsIPluginWidget.h" #include "nsGUIEvent.h" #include "nsIRenderingContext.h" -#include "nsIContentViewer.h" -#include "nsIDocumentViewer.h" -#include "nsIDocShell.h" #include "npapi.h" #include "nsGfxCIID.h" #include "nsUnicharUtils.h" #include "nsTransform2D.h" #include "nsIImageLoadingContent.h" -#include "nsIFocusController.h" #include "nsPIDOMWindow.h" #include "nsContentUtils.h" #include "nsIStringBundle.h" @@ -4104,39 +4100,7 @@ NS_IMETHODIMP nsPluginInstanceOwner::Init(nsPresContext* aPresContext, nsObjectF // a page is reloaded. Shutdown happens usually when the last instance // is destroyed. Here we make sure the plugin instance in the old // document is destroyed before we try to create the new one. - - nsCOMPtr container = aPresContext->GetContainer(); - if (container) { - // We need to suppress the focus controller so that destroying the old - // content viewer doesn't transfer focus to the toplevel window. - - nsCOMPtr privWindow = do_GetInterface(container); - nsIFocusController *fc = nsnull; - if (privWindow) { - fc = privWindow->GetRootFocusController(); - if (fc) - fc->SetSuppressFocus(PR_TRUE, "PluginInstanceOwner::Init Suppression"); - } - - nsCOMPtr docShell = do_QueryInterface(container); - if (docShell) { - nsCOMPtr cv; - docShell->GetContentViewer(getter_AddRefs(cv)); - // Make sure that we're in the presentation that the current - // content viewer knows about - nsCOMPtr docV(do_QueryInterface(cv)); - if (docV) { - nsCOMPtr currentPresContext; - docV->GetPresContext(getter_AddRefs(currentPresContext)); - if (currentPresContext == aPresContext) { - cv->Show(); - } - } - } - - if (fc) - fc->SetSuppressFocus(PR_FALSE, "PluginInstanceOwner::Init Suppression"); - } + aPresContext->EnsureVisible(PR_TRUE); // register context menu listener mCXMenuListener = new nsPluginDOMContextMenuListener(); diff --git a/view/public/nsIViewManager.h b/view/public/nsIViewManager.h index 819d5feee2a..d41f3073f5d 100644 --- a/view/public/nsIViewManager.h +++ b/view/public/nsIViewManager.h @@ -499,14 +499,6 @@ public: */ NS_IMETHOD IsPainting(PRBool& aIsPainting)=0; - - /** - * Flush pending invalidates which have been queued up - * between DisableRefresh and EnableRefresh calls. - */ - NS_IMETHOD FlushPendingInvalidates()=0; - - /** * Set the default background color that the view manager should use * to paint otherwise unowned areas. If the color isn't known, just set diff --git a/view/public/nsIViewObserver.h b/view/public/nsIViewObserver.h index 0c1c8a42c37..ba5604842cd 100644 --- a/view/public/nsIViewObserver.h +++ b/view/public/nsIViewObserver.h @@ -91,6 +91,13 @@ public: * of having the view trees linked. */ NS_IMETHOD_(PRBool) IsVisible() = 0; + + /** + * Notify the observer that we're about to start painting. This + * gives the observer a chance to make some last-minute invalidates + * and geometry changes if it wants to. + */ + NS_IMETHOD_(void) WillPaint() = 0; }; #endif diff --git a/view/src/nsView.cpp b/view/src/nsView.cpp index bbf13bbb26c..5e10d005f05 100644 --- a/view/src/nsView.cpp +++ b/view/src/nsView.cpp @@ -362,10 +362,11 @@ void nsView::SetPositionIgnoringChildWidgets(nscoord aX, nscoord aY) void nsView::ResetWidgetBounds(PRBool aRecurse, PRBool aMoveOnly, PRBool aInvalidateChangedSize) { if (mWindow) { - // If our view manager has refresh disabled, then - // do nothing; the view manager will set our position when - // refresh is reenabled. + // If our view manager has refresh disabled, then do nothing; the view + // manager will set our position when refresh is reenabled. Just let it + // know that it has pending updates. if (!mViewManager->IsRefreshEnabled()) { + mViewManager->PostPendingUpdate(); return; } diff --git a/view/src/nsViewManager.cpp b/view/src/nsViewManager.cpp index da8b2ab8b4a..2d525225976 100644 --- a/view/src/nsViewManager.cpp +++ b/view/src/nsViewManager.cpp @@ -62,6 +62,7 @@ #include "nsInt64.h" #include "nsScrollPortView.h" #include "nsHashtable.h" +#include "nsCOMArray.h" static NS_DEFINE_IID(kBlenderCID, NS_BLENDER_CID); static NS_DEFINE_IID(kRegionCID, NS_REGION_CID); @@ -427,8 +428,7 @@ static PRBool IsViewVisible(nsView *aView) // Find out if the root view is visible by asking the view observer // (this won't be needed anymore if we link view trees across chrome / // content boundaries in DocumentViewerImpl::MakeWindow). - nsCOMPtr vo; - aView->GetViewManager()->GetViewObserver(*getter_AddRefs(vo)); + nsIViewObserver* vo = aView->GetViewManager()->GetViewObserver(); return vo && vo->IsVisible(); } @@ -482,7 +482,7 @@ nsViewManager::nsViewManager() // assumed to be cleared here. mDefaultBackgroundColor = NS_RGBA(0, 0, 0, 0); mAllowDoubleBuffering = PR_TRUE; - mHasPendingInvalidates = PR_FALSE; + mHasPendingUpdates = PR_FALSE; mRecursiveRefreshPending = PR_FALSE; mUpdateBatchFlags = 0; } @@ -1696,7 +1696,7 @@ nsViewManager::UpdateWidgetArea(nsView *aWidgetView, const nsRegion &aDamagedReg // Don't let dirtyRegion grow beyond 8 rects dirtyRegion->SimplifyOutward(8); nsViewManager* rootVM = RootViewManager(); - rootVM->mHasPendingInvalidates = PR_TRUE; + rootVM->mHasPendingUpdates = PR_TRUE; rootVM->IncrementUpdateCount(); return; // this should only happen at the top level, and this result @@ -1940,6 +1940,22 @@ NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aS UpdateView(view, NS_VMREFRESH_NO_SYNC); } else { //NS_ASSERTION(IsViewVisible(view), "painting an invisible view"); + + // Just notify our own view observer that we're about to paint + // XXXbz do we need to notify other view observers for viewmanagers + // in our tree? + nsIViewObserver* observer = GetViewObserver(); + if (observer) { + // Do an update view batch, and make sure we don't process those + // invalidates right now. Note that the observer may try to + // reenter this code from inside WillPaint() by trying to do a + // synchronous paint, but since refresh will be disabled it won't + // be able to do the paint. We should really sort out the rules + // on our synch painting api.... + BeginUpdateViewBatch(); + observer->WillPaint(); + EndUpdateViewBatch(NS_VMREFRESH_DEFERRED); + } Refresh(view, event->renderingContext, region, NS_VMREFRESH_DOUBLE_BUFFER); } @@ -2007,9 +2023,11 @@ NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aS case NS_SYSCOLORCHANGED: { + // Hold a refcount to the observer. The continued existence of the observer will + // delay deletion of this view hierarchy should the event want to cause its + // destruction in, say, some JavaScript event handler. nsView *view = nsView::GetViewFor(aEvent->widget); - nsCOMPtr obs; - GetViewObserver(*getter_AddRefs(obs)); + nsCOMPtr obs = GetViewObserver(); if (obs) { PRBool handled; obs->HandleEvent(view, aEvent, aStatus, PR_TRUE, handled); @@ -2344,8 +2362,7 @@ nsEventStatus nsViewManager::HandleEvent(nsView* aView, nsGUIEvent* aEvent, PRBo // Hold a refcount to the observer. The continued existence of the observer will // delay deletion of this view hierarchy should the event want to cause its // destruction in, say, some JavaScript event handler. - nsCOMPtr obs; - GetViewObserver(*getter_AddRefs(obs)); + nsCOMPtr obs = GetViewObserver(); // accessibility events and key events are dispatched directly to the focused view if (aEvent->eventStructType == NS_ACCESSIBLE_EVENT @@ -2361,7 +2378,7 @@ nsEventStatus nsViewManager::HandleEvent(nsView* aView, nsGUIEvent* aEvent, PRBo } nsAutoVoidArray targetViews; - nsAutoVoidArray heldRefCountsToOtherVMs; + nsCOMArray heldRefCountsToOtherVMs; // In fact, we only need to take this expensive path when the event is a mouse event ... riiiight? PLArenaPool displayArena; @@ -2377,10 +2394,9 @@ nsEventStatus nsViewManager::HandleEvent(nsView* aView, nsGUIEvent* aEvent, PRBo nsView* v = element->mView; nsViewManager* vVM = v->GetViewManager(); if (vVM != this) { - nsIViewObserver* vobs = nsnull; - vVM->GetViewObserver(vobs); - if (nsnull != vobs) { - heldRefCountsToOtherVMs.AppendElement(vobs); + nsIViewObserver* vobs = vVM->GetViewObserver(); + if (vobs) { + heldRefCountsToOtherVMs.AppendObject(vobs); } } } @@ -2406,8 +2422,10 @@ nsEventStatus nsViewManager::HandleEvent(nsView* aView, nsGUIEvent* aEvent, PRBo obs->HandleEvent(v, aEvent, &status, i == targetViews.Count() - 1, handled); } } else { - nsCOMPtr vobs; - vVM->GetViewObserver(*getter_AddRefs(vobs)); + // Hold a refcount to the observer. The continued existence of the observer will + // delay deletion of this view hierarchy should the event want to cause its + // destruction in, say, some JavaScript event handler. + nsCOMPtr vobs = GetViewObserver(); if (vobs) { vobs->HandleEvent(v, aEvent, &status, i == targetViews.Count() - 1, handled); } @@ -2427,12 +2445,6 @@ nsEventStatus nsViewManager::HandleEvent(nsView* aView, nsGUIEvent* aEvent, PRBo PL_FreeArenaPool(&displayArena); PL_FinishArenaPool(&displayArena); - // release death grips - for (i = 0; i < heldRefCountsToOtherVMs.Count(); i++) { - nsIViewObserver* element = NS_STATIC_CAST(nsIViewObserver*, heldRefCountsToOtherVMs.ElementAt(i)); - NS_RELEASE(element); - } - return status; } @@ -3294,14 +3306,12 @@ NS_IMETHODIMP nsViewManager::EnableRefresh(PRUint32 aUpdateFlags) // nested batching can combine IMMEDIATE with DEFERRED. Favour // IMMEDIATE over DEFERRED and DEFERRED over NO_SYNC. if (aUpdateFlags & NS_VMREFRESH_IMMEDIATE) { - ProcessPendingUpdates(mRootView); - mHasPendingInvalidates = PR_FALSE; + FlushPendingInvalidates(); Composite(); } else if (aUpdateFlags & NS_VMREFRESH_DEFERRED) { PostInvalidateEvent(); } else { // NO_SYNC - ProcessPendingUpdates(mRootView); - mHasPendingInvalidates = PR_FALSE; + FlushPendingInvalidates(); } return NS_OK; @@ -4146,18 +4156,53 @@ nsViewManager::IsPainting(PRBool& aIsPainting) return NS_OK; } -NS_IMETHODIMP +void nsViewManager::FlushPendingInvalidates() { - if (!IsRootVM()) { - return RootViewManager()->FlushPendingInvalidates(); - } + NS_ASSERTION(IsRootVM(), "Must be root VM for this to be called!\n"); + NS_ASSERTION(mUpdateBatchCnt == 0, "Must not be in an update batch!"); + // XXXbz this is probably not quite OK yet, if callers can explicitly + // DisableRefresh while we have an event posted. + // NS_ASSERTION(mRefreshEnabled, "How did we get here?"); + + // Let all the view observers of all viewmanagers in this tree know that + // we're about to "paint" (this lets them get in their invalidates now so + // we don't go through two invalidate-processing cycles). + NS_ASSERTION(gViewManagers, "Better have a viewmanagers array!"); + + // Disable refresh while we notify our view observers, so that if they do + // vie w update batches we don't reenter this code and so that we batch + // all of them together. We don't use + // BeginUpdateViewBatch/EndUpdateViewBatch, since that would reenter this + // exact code, but we want the effect of a single big update batch. + PRBool refreshEnabled = mRefreshEnabled; + mRefreshEnabled = PR_FALSE; + ++mUpdateBatchCnt; - if (mHasPendingInvalidates) { - ProcessPendingUpdates(mRootView); - mHasPendingInvalidates = PR_FALSE; + PRInt32 index; + for (index = 0; index < mVMCount; index++) { + nsViewManager* vm = (nsViewManager*)gViewManagers->ElementAt(index); + if (vm->RootViewManager() == this) { + // One of our kids + nsIViewObserver* observer = vm->GetViewObserver(); + if (observer) { + observer->WillPaint(); + NS_ASSERTION(mUpdateBatchCnt == 1, "Observer did not end view batch?"); + } + } + } + + --mUpdateBatchCnt; + // Someone could have called EnableRefresh on us from inside WillPaint(). + // Only reset the old mRefreshEnabled value if the current value is false. + if (!mRefreshEnabled) { + mRefreshEnabled = refreshEnabled; + } + + if (mHasPendingUpdates) { + ProcessPendingUpdates(mRootView); + mHasPendingUpdates = PR_FALSE; } - return NS_OK; } void diff --git a/view/src/nsViewManager.h b/view/src/nsViewManager.h index d2a2dd59817..73ff2b2d4a9 100644 --- a/view/src/nsViewManager.h +++ b/view/src/nsViewManager.h @@ -240,7 +240,6 @@ public: NS_IMETHOD AllowDoubleBuffering(PRBool aDoubleBuffer); NS_IMETHOD IsPainting(PRBool& aIsPainting); - NS_IMETHOD FlushPendingInvalidates(); NS_IMETHOD SetDefaultBackgroundColor(nscolor aColor); NS_IMETHOD GetDefaultBackgroundColor(nscolor* aColor); NS_IMETHOD GetLastUserEventTime(PRUint32& aTime); @@ -268,9 +267,10 @@ public: protected: virtual ~nsViewManager(); - void ProcessPendingUpdates(nsView *aView); private: + void FlushPendingInvalidates(); + void ProcessPendingUpdates(nsView *aView); void ReparentChildWidgets(nsIView* aView, nsIWidget *aNewWidget); void ReparentWidgets(nsIView* aView, nsIView *aParent); already_AddRefed CreateRenderingContext(nsView &aView); @@ -471,6 +471,11 @@ public: // NOT in nsIViewManager, so private to the view module PRBool IsRefreshEnabled() { return RootViewManager()->mRefreshEnabled; } + nsIViewObserver* GetViewObserver() { return mObserver; } + + // Call this when you need to let the viewmanager know that it now has + // pending updates. + void PostPendingUpdate() { RootViewManager()->mHasPendingUpdates = PR_TRUE; } private: nsIDeviceContext *mContext; float mTwipsToPixels; @@ -512,7 +517,7 @@ private: // Use IsPainting() and SetPainting() to access mPainting. PRPackedBool mPainting; PRPackedBool mRecursiveRefreshPending; - PRPackedBool mHasPendingInvalidates; + PRPackedBool mHasPendingUpdates; //from here to public should be static and locked... MMP static PRInt32 mVMCount; //number of viewmanagers