Bug 564991. Part 27: Make plugin geometry changes asynchronous and make them happen as close to the final paint as possible. r=matspal,sr=vlad
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 16 Jul 2010 09:08:08 +1200
changeset 47756 fb34f3f684dfa1650d4991d185b21c0d83a4f139
parent 47755 751e74792a2fbee99e0968fc9413f9897f531fa2
child 47757 41143b03b5a141fcd77e4aed43bb7a29af2e9d17
push idunknown
push userunknown
push dateunknown
reviewersmatspal, vlad
bugs564991
milestone2.0b2pre
Bug 564991. Part 27: Make plugin geometry changes asynchronous and make them happen as close to the final paint as possible. r=matspal,sr=vlad
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/base/nsPresShell.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsObjectFrame.cpp
view/public/nsIViewObserver.h
view/src/nsViewManager.cpp
view/src/nsViewManager.h
widget/public/nsGUIEvent.h
widget/src/gtk2/nsWindow.cpp
widget/src/windows/nsWindow.cpp
widget/src/windows/nsWindow.h
widget/src/windows/nsWindowGfx.cpp
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -7975,17 +7975,17 @@ nsCSSFrameConstructor::ProcessRestyledFr
 
   if (didInvalidate && !didReflow) {
     // RepaintFrame changes can indicate changes in opacity etc which
     // can require plugin clipping to change. If we requested a reflow,
     // we don't need to do this since the reflow will do it for us.
     nsIFrame* rootFrame = mPresShell->FrameManager()->GetRootFrame();
     nsRootPresContext* rootPC = presContext->GetRootPresContext();
     if (rootPC) {
-      rootPC->UpdatePluginGeometry(rootFrame);
+      rootPC->RequestUpdatePluginGeometry(rootFrame);
     }
   }
 
   // cleanup references and verify the style tree.  Note that the latter needs
   // to happen once we've processed the whole list, since until then the tree
   // is not in fact in a consistent state.
   index = count;
   while (0 <= --index) {
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2360,20 +2360,22 @@ nsPresContext::CheckForInterrupt(nsIFram
 #endif /* NOISY_INTERRUPTIBLE_REFLOW */
     mShell->FrameNeedsToContinueReflow(aFrame);
   }
   return mHasPendingInterrupt;
 }
 
 nsRootPresContext::nsRootPresContext(nsIDocument* aDocument,
                                      nsPresContextType aType)
-  : nsPresContext(aDocument, aType)
+  : nsPresContext(aDocument, aType),
+    mUpdatePluginGeometryForFrame(nsnull),
+    mNeedsToUpdatePluginGeometry(PR_FALSE)
 {
   mRegisteredPlugins.Init();
-}  
+}
 
 nsRootPresContext::~nsRootPresContext()
 {
   NS_ASSERTION(mRegisteredPlugins.Count() == 0,
                "All plugins should have been unregistered");
 }
 
 void
@@ -2520,33 +2522,107 @@ nsRootPresContext::GetPluginGeometryUpda
     list.DeleteAll();
   }
 
   // Plugins that we didn't find in the display list are not visible
   closure.mAffectedPlugins.EnumerateEntries(PluginHideEnumerator, &closure);
 }
 
 void
-nsRootPresContext::UpdatePluginGeometry(nsIFrame* aChangedSubtree)
+nsRootPresContext::UpdatePluginGeometry()
 {
+  if (!mNeedsToUpdatePluginGeometry)
+    return;
+  mNeedsToUpdatePluginGeometry = PR_FALSE;
+
+  nsIFrame* f = mUpdatePluginGeometryForFrame;
+  if (f) {
+    mUpdatePluginGeometryForFrame->PresContext()->
+      SetContainsUpdatePluginGeometryFrame(PR_FALSE);
+    mUpdatePluginGeometryForFrame = nsnull;
+  } else {
+    f = FrameManager()->GetRootFrame();
+  }
+
   nsTArray<nsIWidget::Configuration> configurations;
-  GetPluginGeometryUpdates(aChangedSubtree, &configurations);
+  GetPluginGeometryUpdates(f, &configurations);
   if (configurations.IsEmpty())
     return;
   nsIWidget* widget = FrameManager()->GetRootFrame()->GetNearestWidget();
   NS_ASSERTION(widget, "Plugins must have a parent window");
   widget->ConfigureChildren(configurations);
   DidApplyPluginGeometryUpdates();
 }
 
+void
+nsRootPresContext::ForcePluginGeometryUpdate()
+{
+  // Force synchronous paint
+  nsIPresShell* shell = GetPresShell();
+  if (!shell)
+    return;
+  nsIFrame* rootFrame = shell->GetRootFrame();
+  if (!rootFrame)
+    return;
+  nsCOMPtr<nsIWidget> widget = rootFrame->GetNearestWidget();
+  if (!widget)
+    return;
+  // Force synchronous paint of a single pixel, just to force plugin
+  // updates to be flushed. Doing plugin updates during paint is the best
+  // way to ensure that plugin updates are in sync with our content.
+  widget->Invalidate(nsIntRect(0,0,1,1), PR_TRUE);
+
+  // Update plugin geometry just in case that invalidate didn't work
+  // (e.g. if none of the widget is visible, it might not have processed
+  // a paint event). Normally this won't need to do anything.
+  UpdatePluginGeometry();
+}
+
+void
+nsRootPresContext::RequestUpdatePluginGeometry(nsIFrame* aFrame)
+{
+  if (mRegisteredPlugins.Count() == 0)
+    return;
+
+  if (!mNeedsToUpdatePluginGeometry) {
+    // Dispatch a Gecko event to ensure plugin geometry gets updated
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(this, &nsRootPresContext::ForcePluginGeometryUpdate);
+    NS_DispatchToMainThread(event);
+  }
+
+  mNeedsToUpdatePluginGeometry = PR_TRUE;
+  if (aFrame == mUpdatePluginGeometryForFrame)
+    return;
+  if (!mUpdatePluginGeometryForFrame) {
+    mUpdatePluginGeometryForFrame = aFrame;
+    mUpdatePluginGeometryForFrame->PresContext()->
+      SetContainsUpdatePluginGeometryFrame(PR_TRUE);
+  } else {
+    mUpdatePluginGeometryForFrame->PresContext()->
+      SetContainsUpdatePluginGeometryFrame(PR_FALSE);
+    mUpdatePluginGeometryForFrame = nsnull;
+  }
+}
+
 static PLDHashOperator
 PluginDidSetGeometryEnumerator(nsPtrHashKey<nsObjectFrame>* aEntry, void* userArg)
 {
   nsObjectFrame* f = aEntry->GetKey();
   f->DidSetWidgetGeometry();
   return PL_DHASH_NEXT;
 }
 
 void
 nsRootPresContext::DidApplyPluginGeometryUpdates()
 {
   mRegisteredPlugins.EnumerateEntries(PluginDidSetGeometryEnumerator, nsnull);
 }
+
+void
+nsRootPresContext::RootForgetUpdatePluginGeometryFrame(nsIFrame* aFrame)
+{
+  if (aFrame == mUpdatePluginGeometryForFrame) {
+    mUpdatePluginGeometryForFrame->PresContext()->
+      SetContainsUpdatePluginGeometryFrame(PR_FALSE);
+    mUpdatePluginGeometryForFrame = nsnull;
+  }
+}
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -955,16 +955,27 @@ public:
     NS_PRECONDITION(aContent, "Don't do that");
     if (GetPresShell() &&
         GetPresShell()->GetDocument() == aContent->GetCurrentDoc()) {
       return aContent->GetPrimaryFrame();
     }
     return nsnull;
   }
 
+  void NotifyDestroyingFrame(nsIFrame* aFrame)
+  {
+    PropertyTable()->DeleteAllFor(aFrame);
+  }
+  inline void ForgetUpdatePluginGeometryFrame(nsIFrame* aFrame);
+
+  void SetContainsUpdatePluginGeometryFrame(PRBool aValue)
+  {
+    mContainsUpdatePluginGeometryFrame = aValue;
+  }
+
   PRBool MayHaveFixedBackgroundFrames() { return mMayHaveFixedBackgroundFrames; }
   void SetHasFixedBackgroundFrame() { mMayHaveFixedBackgroundFrames = PR_TRUE; }
 
   PRUint32 EstimateMemoryUsed() {
     PRUint32 result = 0;
 
     result += sizeof(nsPresContext);
     result += GetBidiMemoryUsed();
@@ -1142,16 +1153,18 @@ protected:
   // the document rather than to change the document's dimensions
   unsigned              mSupressResizeReflow : 1;
 
   unsigned              mIsVisual : 1;
 
   unsigned              mProcessingRestyles : 1;
   unsigned              mProcessingAnimationStyleChange : 1;
 
+  unsigned              mContainsUpdatePluginGeometryFrame : 1;
+
   // Cache whether we are chrome or not because it is expensive.  
   // mIsChromeIsCached tells us if mIsChrome is valid or we need to get the
   // value the slow way.
   mutable unsigned      mIsChromeIsCached : 1;
   mutable unsigned      mIsChrome : 1;
 
 #ifdef DEBUG
   PRBool                mInitialized;
@@ -1203,20 +1216,19 @@ public:
    * region). If the plugin was not already registered, this does
    * nothing.
    */
   void UnregisterPluginForGeometryUpdates(nsObjectFrame* aPlugin);
 
   /**
    * Iterate through all plugins that are registered for geometry updates
    * and update their position and clip region to match the current frame
-   * tree. Only frames at or under aChangedRoot can have changed their
-   * geometry.
+   * tree.
    */
-  void UpdatePluginGeometry(nsIFrame* aChangedRoot);
+  void UpdatePluginGeometry();
 
   /**
    * Iterate through all plugins that are registered for geometry updates
    * and compute their position and clip region according to the
    * current frame tree. Only frames at or under aChangedRoot can have
    * changed their geometry. The computed positions and clip regions are
    * appended to aConfigurations.
    */
@@ -1227,20 +1239,54 @@ public:
    * When all geometry updates have been applied, call this function
    * in case the nsObjectFrames have work to do after the widgets
    * have been updated.
    */
   void DidApplyPluginGeometryUpdates();
 
   virtual PRBool IsRoot() { return PR_TRUE; }
 
+  /**
+   * This method is called off an event to force the plugin geometry to
+   * be updated. First we try to paint, since updating plugin geometry
+   * during paint is best for keeping plugins in sync with content.
+   * But we also force geometry updates in case painting doesn't work.
+   */
+  void ForcePluginGeometryUpdate();
+
+  /**
+   * Call this after reflow and scrolling to ensure that the geometry
+   * of any windowed plugins is updated. aFrame is the root of the
+   * frame subtree whose geometry has changed.
+   */
+  void RequestUpdatePluginGeometry(nsIFrame* aFrame);
+
+  /**
+   * Call this when a frame is being destroyed and
+   * mContainsUpdatePluginGeometryFrame is set in the frame's prescontext.
+   */
+  void RootForgetUpdatePluginGeometryFrame(nsIFrame* aFrame);
+
 private:
   nsTHashtable<nsPtrHashKey<nsObjectFrame> > mRegisteredPlugins;
+  nsIFrame* mUpdatePluginGeometryForFrame;
+  PRPackedBool mNeedsToUpdatePluginGeometry;
 };
 
+inline void
+nsPresContext::ForgetUpdatePluginGeometryFrame(nsIFrame* aFrame)
+{
+  if (mContainsUpdatePluginGeometryFrame) {
+    nsRootPresContext* rootPC = GetRootPresContext();
+    if (rootPC) {
+      rootPC->RootForgetUpdatePluginGeometryFrame(aFrame);
+    }
+  }
+}
+
 #ifdef DEBUG
 
 struct nsAutoLayoutPhase {
   nsAutoLayoutPhase(nsPresContext* aPresContext, nsLayoutPhase aPhase)
     : mPresContext(aPresContext), mPhase(aPhase), mCount(0)
   {
     Enter();
   }
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -812,29 +812,31 @@ public:
   virtual already_AddRefed<nsPIDOMWindow> GetRootWindow();
 
   //nsIViewObserver interface
 
   NS_IMETHOD Paint(nsIView* aDisplayRoot,
                    nsIView* aViewToPaint,
                    nsIWidget* aWidget,
                    const nsRegion& aDirtyRegion,
-                   PRBool aPaintDefaultBackground);
+                   PRBool aPaintDefaultBackground,
+                   PRBool aWillSendDidPaint);
   NS_IMETHOD HandleEvent(nsIView*        aView,
                          nsGUIEvent*     aEvent,
                          nsEventStatus*  aEventStatus);
   virtual NS_HIDDEN_(nsresult) HandleDOMEventWithTarget(nsIContent* aTargetContent,
                                                         nsEvent* aEvent,
                                                         nsEventStatus* aStatus);
   virtual NS_HIDDEN_(nsresult) HandleDOMEventWithTarget(nsIContent* aTargetContent,
                                                         nsIDOMEvent* aEvent,
                                                         nsEventStatus* aStatus);
   NS_IMETHOD ResizeReflow(nsIView *aView, nscoord aWidth, nscoord aHeight);
   NS_IMETHOD_(PRBool) IsVisible();
-  NS_IMETHOD_(void) WillPaint();
+  NS_IMETHOD_(void) WillPaint(PRBool aWillSendDidPaint);
+  NS_IMETHOD_(void) DidPaint();
   NS_IMETHOD_(void) DispatchSynthMouseMove(nsGUIEvent *aEvent,
                                            PRBool aFlushOnHoverChange);
   NS_IMETHOD_(void) ClearMouseCapture(nsIView* aView);
 
   // caret handling
   virtual NS_HIDDEN_(already_AddRefed<nsCaret>) GetCaret() const;
   virtual NS_HIDDEN_(void) MaybeInvalidateCaretPosition();
   NS_IMETHOD SetCaretEnabled(PRBool aInEnable);
@@ -2848,33 +2850,35 @@ PresShell::SetIgnoreFrameDestruction(PRB
   mIgnoreFrameDestruction = aIgnore;
 }
 
 void
 PresShell::NotifyDestroyingFrame(nsIFrame* aFrame)
 {
   NS_TIME_FUNCTION_MIN(1.0);
 
+  mPresContext->ForgetUpdatePluginGeometryFrame(aFrame);
+
   if (!mIgnoreFrameDestruction) {
     mPresContext->StopImagesFor(aFrame);
 
     mFrameConstructor->NotifyDestroyingFrame(aFrame);
 
     for (PRInt32 idx = mDirtyRoots.Length(); idx; ) {
       --idx;
       if (mDirtyRoots[idx] == aFrame) {
         mDirtyRoots.RemoveElementAt(idx);
       }
     }
 
     // Notify the frame manager
     FrameManager()->NotifyDestroyingFrame(aFrame);
 
     // Remove frame properties
-    mPresContext->PropertyTable()->DeleteAllFor(aFrame);
+    mPresContext->NotifyDestroyingFrame(aFrame);
 
     if (aFrame == mCurrentEventFrame) {
       mCurrentEventContent = aFrame->GetContent();
       mCurrentEventFrame = nsnull;
     }
 
   #ifdef NS_DEBUG
     if (aFrame == mDrawEventTargetFrame) {
@@ -4543,17 +4547,17 @@ PresShell::UnsuppressAndInvalidate()
     rootFrame->Invalidate(rect);
 
     if (mCaretEnabled && mCaret) {
       mCaret->CheckCaretDrawingState();
     }
 
     nsRootPresContext* rootPC = mPresContext->GetRootPresContext();
     if (rootPC) {
-      rootPC->UpdatePluginGeometry(rootFrame);
+      rootPC->RequestUpdatePluginGeometry(rootFrame);
     }
   }
 
   // now that painting is unsuppressed, focus may be set on the document
   nsPIDOMWindow *win = mDocument->GetWindow();
   if (win)
     win->SetReadyForFocus();
 
@@ -4813,16 +4817,26 @@ PresShell::FlushPendingNotifications(moz
       if (ProcessReflowCommands(aType < Flush_Layout) && mContentToScrollTo) {
         // We didn't get interrupted.  Go ahead and scroll to our content
         DoScrollContentIntoView(mContentToScrollTo, mContentScrollVPosition,
                                 mContentScrollHPosition);
         mContentToScrollTo = nsnull;
       }
     }
 
+    if (aType >= Flush_Layout) {
+      // Flush plugin geometry. Don't flush plugin geometry for
+      // interruptible layouts, since WillPaint does an interruptible
+      // layout.
+      nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+      if (rootPresContext) {
+        rootPresContext->UpdatePluginGeometry();
+      }
+    }
+
     PRUint32 updateFlags = NS_VMREFRESH_NO_SYNC;
     if (aType >= Flush_Display) {
       // Flushing paints, so perform the invalidates and drawing
       // immediately
       updateFlags = NS_VMREFRESH_IMMEDIATE;
     }
     else if (aType < Flush_InterruptibleLayout) {
       // Not flushing reflows, so do deferred invalidates.  This will keep us
@@ -5825,17 +5839,18 @@ static void DrawThebesLayer(ThebesLayer*
   }
 }
 
 NS_IMETHODIMP
 PresShell::Paint(nsIView*        aDisplayRoot,
                  nsIView*        aViewToPaint,
                  nsIWidget*      aWidgetToPaint,
                  const nsRegion& aDirtyRegion,
-                 PRBool          aPaintDefaultBackground)
+                 PRBool          aPaintDefaultBackground,
+                 PRBool          aWillSendDidPaint)
 {
 #ifdef NS_FUNCTION_TIMER
   NS_TIME_FUNCTION_DECLARE_DOCURL;
   const nsRect& bounds__ = aDirtyRegion.GetBounds();
   NS_TIME_FUNCTION_MIN_FMT(1.0, "%s (line %d) (document: %s, dirty rect: (<%f, %f>, <%f, %f>)",
                            MOZ_FUNCTION_NAME, __LINE__, docURL__.get(),
                            NSCoordToFloat(bounds__.x),
                            NSCoordToFloat(bounds__.y),
@@ -7080,31 +7095,53 @@ PresShell::IsVisible()
   if (!bw)
     return PR_FALSE;
   PRBool res = PR_TRUE;
   bw->GetVisibility(&res);
   return res;
 }
 
 NS_IMETHODIMP_(void)
-PresShell::WillPaint()
-{
-  // Don't bother reflowing if some viewmanager in our tree is painting while
-  // we still have painting suppressed.
+PresShell::WillPaint(PRBool aWillSendDidPaint)
+{
+  // Don't bother doing anything if some viewmanager in our tree is
+  // painting while we still have painting suppressed.
   if (mPaintingSuppressed) {
     return;
   }
-  
+
+  if (!aWillSendDidPaint) {
+    nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+    if (!rootPresContext) {
+      return;
+    }
+    if (rootPresContext == mPresContext) {
+      rootPresContext->UpdatePluginGeometry();
+    }
+  }
+
   // 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.
   FlushPendingNotifications(Flush_InterruptibleLayout);
 }
 
+NS_IMETHODIMP_(void)
+PresShell::DidPaint()
+{
+  nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+  if (!rootPresContext) {
+    return;
+  }
+  if (rootPresContext == mPresContext) {
+    rootPresContext->UpdatePluginGeometry();
+  }
+}
+
 nsresult
 PresShell::GetAgentStyleSheets(nsCOMArray<nsIStyleSheet>& aSheets)
 {
   aSheets.Clear();
   PRInt32 sheetCount = mStyleSet->SheetCount(nsStyleSet::eAgentSheet);
 
   for (PRInt32 i = 0; i < sheetCount; ++i) {
     nsIStyleSheet *sheet = mStyleSet->StyleSheetAt(nsStyleSet::eAgentSheet, i);
@@ -7489,17 +7526,17 @@ PresShell::DoReflow(nsIFrame* target, PR
     // should be suppressed now. We don't want to do extra reflow work
     // before our reflow event happens.
     mSuppressInterruptibleReflows = PR_TRUE;
     MaybeScheduleReflow();
   }
 
   nsRootPresContext* rootPC = mPresContext->GetRootPresContext();
   if (rootPC) {
-    rootPC->UpdatePluginGeometry(target);
+    rootPC->RequestUpdatePluginGeometry(target);
   }
 
   return !interrupted;
 }
 
 #ifdef DEBUG
 void
 PresShell::DoVerifyReflow()
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1622,42 +1622,23 @@ void nsGfxScrollFrameInner::MarkActive()
       gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
     }
     gScrollFrameActivityTracker->AddObject(this);
   }
 }
 
 void nsGfxScrollFrameInner::ScrollVisual(nsIntPoint aPixDelta)
 {
-  nsRootPresContext* rootPresContext =
-    mOuter->PresContext()->GetRootPresContext();
+  nsRootPresContext* rootPresContext = mOuter->PresContext()->GetRootPresContext();
   if (!rootPresContext) {
     return;
   }
 
-  nsPoint offsetToView;
-  nsPoint offsetToWidget;
-  nsIWidget* nearestWidget =
-    mOuter->GetClosestView(&offsetToView)->GetNearestWidget(&offsetToWidget);
-  nsPoint nearestWidgetOffset = offsetToView + offsetToWidget;
-
-  nsTArray<nsIWidget::Configuration> configurations;
-  // Only update plugin configurations if we're going to scroll the
-  // root widget. Otherwise we must be in a popup or some other situation
-  // where we don't actually support windows plugins.
-  if (rootPresContext->FrameManager()->GetRootFrame()->GetNearestWidget() ==
-        nearestWidget) {
-    rootPresContext->GetPluginGeometryUpdates(mOuter, &configurations);
-  }
-
-  // Just invalidate the frame and adjust child widgets
-  // Recall that our widget's origin is at our bounds' top-left
-  if (nearestWidget) {
-    nearestWidget->ConfigureChildren(configurations);
-  }
+  rootPresContext->RequestUpdatePluginGeometry(mOuter);
+
   AdjustViewsAndWidgets(mScrolledFrame, PR_FALSE);
   // We need to call this after fixing up the widget and view positions
   // to be consistent with the view and frame hierarchy.
   PRUint32 flags = nsIFrame::INVALIDATE_REASON_SCROLL_REPAINT;
   nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(mOuter);
   if (IsScrollingActive() && CanScrollWithBlitting(mOuter, displayRoot)) {
     flags |= nsIFrame::INVALIDATE_NO_THEBES_LAYERS;
   }
--- a/layout/generic/nsObjectFrame.cpp
+++ b/layout/generic/nsObjectFrame.cpp
@@ -764,17 +764,17 @@ nsObjectFrame::CreateWidget(nscoord aWid
       // the Thebes layer containing the plugin is updated.
       Invalidate(GetContentRect() - GetPosition());
 #endif
     }
   }
 
   if (mWidget) {
     rpc->RegisterPluginForGeometryUpdates(this);
-    rpc->UpdatePluginGeometry(this);
+    rpc->RequestUpdatePluginGeometry(this);
 
     // Here we set the background color for this widget because some plugins will use 
     // the child window background color when painting. If it's not set, it may default to gray
     // Sometimes, a frame doesn't have a background color or is transparent. In this
     // case, walk up the frame tree until we do find a frame with a background color
     for (nsIFrame* frame = this; frame; frame = frame->GetParent()) {
       nscolor bgcolor =
         frame->GetVisitedDependentColor(eCSSProperty_background_color);
--- a/view/public/nsIViewObserver.h
+++ b/view/public/nsIViewObserver.h
@@ -42,18 +42,18 @@
 #include "nsEvent.h"
 #include "nsColor.h"
 #include "nsRect.h"
 
 class nsIRenderingContext;
 class nsGUIEvent;
 
 #define NS_IVIEWOBSERVER_IID  \
-  { 0xac43a985, 0xcae6, 0x499d, \
-    { 0xae, 0x8f, 0x9c, 0x92, 0xec, 0x6f, 0x2c, 0x47 } }
+  { 0x8e69db48, 0x9d01, 0x4c0a, \
+    { 0xb9, 0xea, 0xa4, 0x4b, 0xc5, 0x89, 0xc8, 0x63 } }
 
 class nsIViewObserver : public nsISupports
 {
 public:
   
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IVIEWOBSERVER_IID)
 
   /* called when the observer needs to paint. This paints the entire
@@ -70,17 +70,18 @@ public:
    * and any associated widget.) The name illustrates the expected behavior,
    * which is to paint some default background color over the dirty region.
    * @return error status
    */
   NS_IMETHOD Paint(nsIView*        aDisplayRoot,
                    nsIView*        aViewToPaint,
                    nsIWidget*      aWidgetToPaint,
                    const nsRegion& aDirtyRegion,
-                   PRBool          aPaintDefaultBackground) = 0;
+                   PRBool          aPaintDefaultBackground,
+                   PRBool          aWillSendDidPaint) = 0;
 
   /* called when the observer needs to handle an event
    * @param aView  - where to start processing the event; the root view,
    * or the view that's currently capturing this sort of event; must be a view
    * for this presshell
    * @param aEvent - event notification
    * @param aEventStatus - out parameter for event handling
    *                       status
@@ -106,17 +107,24 @@ public:
    */
   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;
+  NS_IMETHOD_(void) WillPaint(PRBool aWillSendDidPaint) = 0;
+
+  /**
+   * Notify the observer that we finished painting.  This
+   * gives the observer a chance to make some last-minute invalidates
+   * and geometry changes if it wants to.
+   */
+  NS_IMETHOD_(void) DidPaint() = 0;
 
   /**
    * Dispatch the given synthesized mouse move event, and if
    * aFlushOnHoverChange is true, flush layout if :hover changes cause
    * any restyles.
    */
   NS_IMETHOD_(void) DispatchSynthMouseMove(nsGUIEvent *aEvent,
                                            PRBool aFlushOnHoverChange) = 0;
--- a/view/src/nsViewManager.cpp
+++ b/view/src/nsViewManager.cpp
@@ -434,17 +434,17 @@ void nsViewManager::RenderViews(nsView *
   // (Bug 485275)
   nsViewManager* displayRootVM = displayRoot->GetViewManager();
   if (displayRootVM && displayRootVM != this) {
     displayRootVM->RenderViews(aView, aWidget, aRegion);
     return;
   }
 
   if (mObserver) {
-    mObserver->Paint(displayRoot, aView, aWidget, aRegion, PR_FALSE);
+    mObserver->Paint(displayRoot, aView, aWidget, aRegion, PR_FALSE, PR_FALSE);
   }
 }
 
 void nsViewManager::ProcessPendingUpdates(nsView* aView, PRBool aDoInvalidate)
 {
   NS_ASSERTION(IsRootVM(), "Updates will be missed");
 
   // Protect against a null-view.
@@ -801,17 +801,17 @@ NS_IMETHODIMP nsViewManager::DispatchEve
                 // event would fire and could end up being after some timer
                 // events, leading to frame dropping in DHTML).  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....
                 UpdateViewBatch batch(this);
-                rootVM->CallWillPaintOnObservers();
+                rootVM->CallWillPaintOnObservers(event->willSendDidPaint);
                 batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC);
 
                 // Get the view pointer again since the code above might have
                 // destroyed it (bug 378273).
                 view = nsView::GetViewFor(aEvent->widget);
               }
             }
             // Make sure to sync up any widget geometry changes we
@@ -825,17 +825,18 @@ NS_IMETHODIMP nsViewManager::DispatchEve
                       event->region, NS_VMREFRESH_DOUBLE_BUFFER);
             }
           }
         } else if (aEvent->message == NS_PAINT) {
           // since we got an NS_PAINT event, we need to
           // draw something so we don't get blank areas,
           // unless there's no widget or it's transparent.
           nsRegion rgn = ConvertDeviceRegionToAppRegion(event->region, mContext);
-          mObserver->Paint(aView, aView, event->widget, rgn, PR_TRUE);
+          mObserver->Paint(aView, aView, event->widget, rgn, PR_TRUE,
+                           event->willSendDidPaint);
 
           // Clients like the editor can trigger multiple
           // reflows during what the user perceives as a single
           // edit operation, so it disables view manager
           // refreshing until the edit operation is complete
           // so that users don't see the intermediate steps.
           // 
           // Unfortunately some of these reflows can trigger
@@ -857,16 +858,22 @@ NS_IMETHODIMP nsViewManager::DispatchEve
           // alternate patch.)
 
           UpdateView(aView, rgn.GetBounds(), NS_VMREFRESH_NO_SYNC);
         }
 
         break;
       }
 
+    case NS_DID_PAINT: {
+      nsRefPtr<nsViewManager> rootVM = RootViewManager();
+      rootVM->CallDidPaintOnObservers();
+      break;
+    }
+
     case NS_CREATE:
     case NS_DESTROY:
     case NS_SETZLEVEL:
     case NS_MOVE:
       /* Don't pass these events through. Passing them through
          causes performance problems on pages with lots of views/frames 
          @see bug 112861 */
       *aStatus = nsEventStatus_eConsumeNoDefault;
@@ -1515,67 +1522,85 @@ nsViewManager::IsPainting(PRBool& aIsPai
 {
   aIsPainting = IsPainting();
   return NS_OK;
 }
 
 void
 nsViewManager::FlushPendingInvalidates()
 {
-  NS_ASSERTION(IsRootVM(), "Must be root VM for this to be called!\n");
+  NS_ASSERTION(IsRootVM(), "Must be root VM for this to be called!");
   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
   // view 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.
   ++mUpdateBatchCnt;
-  CallWillPaintOnObservers();
+  CallWillPaintOnObservers(PR_FALSE);
   --mUpdateBatchCnt;
   
   if (mHasPendingUpdates) {
     ProcessPendingUpdates(mRootView, PR_TRUE);
     mHasPendingUpdates = PR_FALSE;
   }
 }
 
 void
-nsViewManager::CallWillPaintOnObservers()
+nsViewManager::CallWillPaintOnObservers(PRBool aWillSendDidPaint)
 {
-  NS_PRECONDITION(IsRootVM(), "Must be root VM for this to be called!\n");
+  NS_PRECONDITION(IsRootVM(), "Must be root VM for this to be called!");
   NS_PRECONDITION(mUpdateBatchCnt > 0, "Must be in an update batch!");
 
 #ifdef DEBUG
   PRInt32 savedUpdateBatchCnt = mUpdateBatchCnt;
 #endif
   PRInt32 index;
   for (index = 0; index < mVMCount; index++) {
     nsViewManager* vm = (nsViewManager*)gViewManagers->ElementAt(index);
     if (vm->RootViewManager() == this) {
       // One of our kids.
       nsCOMPtr<nsIViewObserver> obs = vm->GetViewObserver();
       if (obs) {
-        obs->WillPaint();
+        obs->WillPaint(aWillSendDidPaint);
         NS_ASSERTION(mUpdateBatchCnt == savedUpdateBatchCnt,
                      "Observer did not end view batch?");
       }
     }
   }
 }
 
 void
+nsViewManager::CallDidPaintOnObservers()
+{
+  NS_PRECONDITION(IsRootVM(), "Must be root VM for this to be called!");
+
+  PRInt32 index;
+  for (index = 0; index < mVMCount; index++) {
+    nsViewManager* vm = (nsViewManager*)gViewManagers->ElementAt(index);
+    if (vm->RootViewManager() == this) {
+      // One of our kids.
+      nsCOMPtr<nsIViewObserver> obs = vm->GetViewObserver();
+      if (obs) {
+        obs->DidPaint();
+      }
+    }
+  }
+}
+
+void
 nsViewManager::ProcessInvalidateEvent()
 {
   NS_ASSERTION(IsRootVM(),
                "Incorrectly targeted invalidate event");
   // If we're in the middle of an update batch, just repost the event,
   // to be processed when the batch ends.
   PRBool processEvent = (mUpdateBatchCnt == 0);
   if (processEvent) {
--- a/view/src/nsViewManager.h
+++ b/view/src/nsViewManager.h
@@ -167,17 +167,18 @@ protected:
 
 private:
 
   void FlushPendingInvalidates();
   void ProcessPendingUpdates(nsView *aView, PRBool aDoInvalidate);
   /**
    * Call WillPaint() on all view observers under this vm root.
    */
-  void CallWillPaintOnObservers();
+  void CallWillPaintOnObservers(PRBool aWillSendDidPaint);
+  void CallDidPaintOnObservers();
   void ReparentChildWidgets(nsIView* aView, nsIWidget *aNewWidget);
   void ReparentWidgets(nsIView* aView, nsIView *aParent);
   void UpdateWidgetArea(nsView *aWidgetView, nsIWidget* aWidget,
                         const nsRegion &aDamagedRegion,
                         nsView* aIgnoreWidgetView);
 
   void UpdateViews(nsView *aView, PRUint32 aUpdateFlags);
 
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -168,16 +168,19 @@ class nsHashKey;
 // Widget size mode was changed
 #define NS_SIZEMODE                     (NS_WINDOW_START + 4)
 // Widget got activated
 #define NS_ACTIVATE                     (NS_WINDOW_START + 7)
 // Widget got deactivated
 #define NS_DEACTIVATE                   (NS_WINDOW_START + 8)
 // top-level window z-level change request
 #define NS_SETZLEVEL                    (NS_WINDOW_START + 9)
+// Widget was repainted (dispatched when it's safe to move widgets, but
+// only on some platforms (including GTK2 and Windows))
+#define NS_DID_PAINT                   (NS_WINDOW_START + 28)
 // Widget will need to be painted
 #define NS_WILL_PAINT                   (NS_WINDOW_START + 29)
 // Widget needs to be repainted
 #define NS_PAINT                        (NS_WINDOW_START + 30)
 // Key is pressed within a window
 #define NS_KEY_PRESS                    (NS_WINDOW_START + 31)
 // Key is released within a window
 #define NS_KEY_UP                       (NS_WINDOW_START + 32)
@@ -633,22 +636,24 @@ public:
 /**
  * Window repaint event
  */
 
 class nsPaintEvent : public nsGUIEvent
 {
 public:
   nsPaintEvent(PRBool isTrusted, PRUint32 msg, nsIWidget *w)
-    : nsGUIEvent(isTrusted, msg, w, NS_PAINT_EVENT)
+    : nsGUIEvent(isTrusted, msg, w, NS_PAINT_EVENT),
+      willSendDidPaint(PR_FALSE)
   {
   }
 
   // area that needs repainting
   nsIntRegion region;
+  PRPackedBool willSendDidPaint;
 };
 
 /**
  * Scrollbar event
  */
 
 class nsScrollbarEvent : public nsGUIEvent
 {
--- a/widget/src/gtk2/nsWindow.cpp
+++ b/widget/src/gtk2/nsWindow.cpp
@@ -2318,16 +2318,17 @@ nsWindow::OnExposeEvent(GtkWidget *aWidg
 
     // Windows that are not visible will be painted after they become visible.
     if (!mGdkWindow || mIsFullyObscured || !mHasMappedToplevel)
         return FALSE;
 
     nsPaintEvent event(PR_TRUE, NS_PAINT, this);
     event.refPoint.x = aEvent->area.x;
     event.refPoint.y = aEvent->area.y;
+    event.willSendDidPaint = PR_TRUE;
 
     GdkRectangle *rects;
     gint nrects;
     gdk_region_get_rectangles(aEvent->region, &rects, &nrects);
     if (NS_UNLIKELY(!rects)) // OOM
         return FALSE;
 
     if (nrects > MAX_RECTS_IN_REGION) {
@@ -2570,16 +2571,27 @@ nsWindow::OnExposeEvent(GtkWidget *aWidg
         // if we had to allocate a local pixmap, free it here
         if (bufferPixmap && bufferPixmap != gBufferPixmap)
             g_object_unref(G_OBJECT(bufferPixmap));
     }
 #endif // MOZ_X11
 
     g_free(rects);
 
+    nsPaintEvent didPaintEvent(PR_TRUE, NS_DID_PAINT, this);
+    DispatchEvent(&didPaintEvent, status);
+
+    // Synchronously flush any new dirty areas
+    GdkRegion* dirtyArea = gdk_window_get_update_area(mGdkWindow);
+    if (dirtyArea) {
+        gdk_window_invalidate_region(mGdkWindow, dirtyArea, PR_FALSE);
+        gdk_region_destroy(dirtyArea);
+        gdk_window_process_updates(mGdkWindow, PR_FALSE);
+    }
+
     // check the return value!
     return TRUE;
 }
 
 gboolean
 nsWindow::OnConfigureEvent(GtkWidget *aWidget, GdkEventConfigure *aEvent)
 {
     LOG(("configure event [%p] %d %d %d %d\n", (void *)this,
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -4628,23 +4628,23 @@ PRBool nsWindow::ProcessMessage(UINT msg
 
     case WM_DESTROY:
       // clean up.
       OnDestroy();
       result = PR_TRUE;
       break;
 
     case WM_PAINT:
-      *aRetValue = (int) OnPaint();
+      *aRetValue = (int) OnPaint(NULL, 0);
       result = PR_TRUE;
       break;
 
 #ifndef WINCE
     case WM_PRINTCLIENT:
-      result = OnPaint((HDC) wParam);
+      result = OnPaint((HDC) wParam, 0);
       break;
 #endif
 
     case WM_HOTKEY:
       result = OnHotKey(wParam, lParam);
       break;
 
     case WM_SYSCHAR:
--- a/widget/src/windows/nsWindow.h
+++ b/widget/src/windows/nsWindow.h
@@ -347,17 +347,17 @@ protected:
                                     PRUint32 aFlags = 0,
                                     const MSG *aMsg = nsnull,
                                     PRBool *aEventDispatched = nsnull);
   virtual PRBool          OnScroll(UINT aMsg, WPARAM aWParam, LPARAM aLParam);
   PRBool                  OnGesture(WPARAM wParam, LPARAM lParam);
   PRBool                  OnHotKey(WPARAM wParam, LPARAM lParam);
   BOOL                    OnInputLangChange(HKL aHKL);
   void                    OnSettingsChange(WPARAM wParam, LPARAM lParam);
-  virtual PRBool          OnPaint(HDC aDC = nsnull);
+  PRBool                  OnPaint(HDC aDC, PRUint32 aNestingLevel);
   void                    OnWindowPosChanged(WINDOWPOS *wp, PRBool& aResult);
 #if defined(CAIRO_HAS_DDRAW_SURFACE)
   PRBool                  OnPaintImageDDraw16();
 #endif // defined(CAIRO_HAS_DDRAW_SURFACE)
   PRBool                  OnMouseWheel(UINT msg, WPARAM wParam, LPARAM lParam, 
                                        PRBool& result, PRBool& getWheelInfo,
                                        LRESULT *aRetValue);
 #if !defined(WINCE)
--- a/widget/src/windows/nsWindowGfx.cpp
+++ b/widget/src/windows/nsWindowGfx.cpp
@@ -295,17 +295,17 @@ EnsureSharedSurfaceSize(gfxIntSize size)
     sSharedSurfaceSize = size;
     sSharedSurfaceData = nsnull;
     sSharedSurfaceData = (PRUint8 *)malloc(WORDSSIZE(sSharedSurfaceSize) * 4);
   }
 
   return (sSharedSurfaceData != nsnull);
 }
 
-PRBool nsWindow::OnPaint(HDC aDC)
+PRBool nsWindow::OnPaint(HDC aDC, PRUint32 aNestingLevel)
 {
 #ifdef MOZ_IPC
   // We never have reentrant paint events, except when we're running our RPC
   // windows event spin loop. If we don't trap for this, we'll try to paint,
   // but view manager will refuse to paint the surface, resulting is black
   // flashes on the plugin rendering surface.
   if (mozilla::ipc::RPCChannel::IsSpinLoopActive() && mPainting)
     return PR_FALSE;
@@ -343,16 +343,17 @@ PRBool nsWindow::OnPaint(HDC aDC)
   // windows event spin loop. If we don't trap for this, we'll try to paint,
   // but view manager will refuse to paint the surface, resulting is black
   // flashes on the plugin rendering surface.
   if (mozilla::ipc::RPCChannel::IsSpinLoopActive() && mPainting)
     return PR_FALSE;
 #endif
 
   nsPaintEvent willPaintEvent(PR_TRUE, NS_WILL_PAINT, this);
+  willPaintEvent.willSendDidPaint = PR_TRUE;
   DispatchWindowEvent(&willPaintEvent);
 
 #ifdef CAIRO_HAS_DDRAW_SURFACE
   if (IsRenderMode(gfxWindowsPlatform::RENDER_IMAGE_DDRAW16)) {
     return OnPaintImageDDraw16();
   }
 #endif
 
@@ -400,16 +401,17 @@ PRBool nsWindow::OnPaint(HDC aDC)
   InitEvent(event);
 
 #ifdef MOZ_XUL
   PRBool forceRepaint = aDC || (eTransparencyTransparent == mTransparencyMode);
 #else
   PRBool forceRepaint = NULL != aDC;
 #endif
   event.region = GetRegionToPaint(forceRepaint, ps, hDC);
+  event.willSendDidPaint = PR_TRUE;
 
   if (!event.region.IsEmpty() && mEventCallback)
   {
     // Should probably pass in a real region here, using GetRandomRgn
     // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/clipping_4q0e.asp
 
 #ifdef WIDGET_DEBUG_OUTPUT
     debug_DumpPaintEvent(stdout,
@@ -734,16 +736,23 @@ DDRAW_FAILED:
     }
     ::ReleaseDC(mWnd, debugPaintFlashDC);
     ::DeleteObject(debugPaintFlashRegion);
   }
 #endif // WIDGET_DEBUG_OUTPUT && !WINCE
 
   mPainting = PR_FALSE;
 
+  nsPaintEvent didPaintEvent(PR_TRUE, NS_DID_PAINT, this);
+  DispatchWindowEvent(&didPaintEvent);
+
+  if (aNestingLevel == 0 && ::GetUpdateRect(mWnd, NULL, PR_FALSE)) {
+    OnPaint(aDC, 1);
+  }
+
   return result;
 }
 
 nsresult nsWindowGfx::CreateIcon(imgIContainer *aContainer,
                                   PRBool aIsCursor,
                                   PRUint32 aHotspotX,
                                   PRUint32 aHotspotY,
                                   HICON *aIcon) {