Bug 800859. Part 3: Dispatch MozAfterPaint after we've done the COMPOSITE step for the invalidations recorded in the event. r=mattwoodrow
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 19 Oct 2012 17:50:34 +1300
changeset 111229 74f3a93b928335df8e3b03bc1a6a317a8336ee8b
parent 111228 1bf5c18ae382f29e7442aae37dac48a815eb6425
child 111230 bf04510e09eed0b900cce06e6765bbea0fdf1373
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersmattwoodrow
bugs800859
milestone19.0a1
Bug 800859. Part 3: Dispatch MozAfterPaint after we've done the COMPOSITE step for the invalidations recorded in the event. r=mattwoodrow
content/events/src/nsDOMNotifyPaintEvent.cpp
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/base/nsPresShell.cpp
--- a/content/events/src/nsDOMNotifyPaintEvent.cpp
+++ b/content/events/src/nsDOMNotifyPaintEvent.cpp
@@ -17,17 +17,17 @@ nsDOMNotifyPaintEvent::nsDOMNotifyPaintE
                                              uint32_t aEventType,
                                              nsInvalidateRequestList* aInvalidateRequests)
 : nsDOMEvent(aPresContext, aEvent)
 {
   if (mEvent) {
     mEvent->message = aEventType;
   }
   if (aInvalidateRequests) {
-    mInvalidateRequests.SwapElements(aInvalidateRequests->mRequests);
+    mInvalidateRequests.MoveElementsFrom(aInvalidateRequests->mRequests);
   }
 }
 
 DOMCI_DATA(NotifyPaintEvent, nsDOMNotifyPaintEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMNotifyPaintEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNotifyPaintEvent)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(NotifyPaintEvent)
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -129,24 +129,25 @@ nsPresContext::MakeColorPref(const nsStr
   return nsRuleNode::ComputeColor(value, this, nullptr, color)
     ? color
     : NS_RGB(0, 0, 0);
 }
 
 bool
 nsPresContext::IsDOMPaintEventPending() 
 {
-  if (!mInvalidateRequests.mRequests.IsEmpty()) {
-    return true;    
+  if (mFireAfterPaintEvents) {
+    return true;
   }
   if (GetDisplayRootPresContext()->GetRootPresContext()->mRefreshDriver->ViewManagerFlushIsPending()) {
     // Since we're promising that there will be a MozAfterPaint event
     // fired, we record an empty invalidation in case display list
     // invalidation doesn't invalidate anything further.
     NotifyInvalidation(nsRect(0, 0, 0, 0), 0);
+    NS_ASSERTION(mFireAfterPaintEvents, "Why aren't we planning to fire the event?");
     return true;
   }
   return false;
 }
 
 int
 nsPresContext::PrefChangedCallback(const char* aPrefName, void* instance_data)
 {
@@ -2028,17 +2029,17 @@ nsPresContext::EnsureSafeToHandOutCSSRul
 
   NS_ABORT_IF_FALSE(res == nsCSSStyleSheet::eUniqueInner_ClonedInner,
                     "unexpected result");
   RebuildAllStyleData(nsChangeHint(0));
   return true;
 }
 
 void
-nsPresContext::FireDOMPaintEvent()
+nsPresContext::FireDOMPaintEvent(nsInvalidateRequestList* aList)
 {
   nsPIDOMWindow* ourWindow = mDocument->GetWindow();
   if (!ourWindow)
     return;
 
   nsCOMPtr<nsIDOMEventTarget> dispatchTarget = do_QueryInterface(ourWindow);
   nsCOMPtr<nsIDOMEventTarget> eventTarget = dispatchTarget;
   if (!IsChrome() && !mSendAfterPaintToContent) {
@@ -2053,19 +2054,17 @@ nsPresContext::FireDOMPaintEvent()
   }
   // Events sent to the window get propagated to the chrome event handler
   // automatically.
   nsCOMPtr<nsIDOMEvent> event;
   // This will empty our list in case dispatching the event causes more damage
   // (hopefully it won't, or we're likely to get an infinite loop! At least
   // it won't be blocking app execution though).
   NS_NewDOMNotifyPaintEvent(getter_AddRefs(event), this, nullptr,
-                            NS_AFTERPAINT,
-                            &mInvalidateRequests);
-  mAllInvalidated = false;
+                            NS_AFTERPAINT, aList);
   if (!event) {
     return;
   }
 
   // Even if we're not telling the window about the event (so eventTarget is
   // the chrome event handler, not the window), the window is still
   // logically the event target.
   event->SetTarget(eventTarget);
@@ -2194,17 +2193,17 @@ nsPresContext::NotifyInvalidation(const 
   if (!pc) {
     nsRootPresContext* rpc = GetRootPresContext();
     if (rpc) {
       rpc->EnsureEventualDidPaintEvent();
     }
   }
 
   nsInvalidateRequestList::Request* request =
-    mInvalidateRequests.mRequests.AppendElement();
+    mInvalidateRequestsSinceLastPaint.mRequests.AppendElement();
   if (!request)
     return;
 
   request->mRect = aRect;
   request->mFlags = aFlags;
 }
 
 /* static */ void 
@@ -2226,46 +2225,91 @@ nsPresContext::NotifySubDocInvalidation(
     //PresContext coordinate space is relative to the start of our visible
     // region. Is this really true? This feels like the wrong way to get the right
     // answer.
     rect.MoveBy(-topLeft);
     data->mPresContext->NotifyInvalidation(rect, 0);
   }
 }
 
+struct NotifyDidPaintSubdocumentCallbackClosure {
+  uint32_t mFlags;
+  bool mNeedsAnotherDidPaintNotification;
+};
 static bool
 NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData)
 {
+  NotifyDidPaintSubdocumentCallbackClosure* closure =
+    static_cast<NotifyDidPaintSubdocumentCallbackClosure*>(aData);
   nsIPresShell* shell = aDocument->GetShell();
   if (shell) {
     nsPresContext* pc = shell->GetPresContext();
     if (pc) {
-      pc->NotifyDidPaintForSubtree();
+      pc->NotifyDidPaintForSubtree(closure->mFlags);
+      if (pc->IsDOMPaintEventPending()) {
+        closure->mNeedsAnotherDidPaintNotification = true;
+      }
     }
   }
   return true;
 }
 
+class DelayedFireDOMPaintEvent : public nsRunnable {
+public:
+  DelayedFireDOMPaintEvent(nsPresContext* aPresContext,
+                           nsInvalidateRequestList* aList)
+    : mPresContext(aPresContext)
+  {
+    mList.TakeFrom(aList);
+  }
+  NS_IMETHOD Run()
+  {
+    mPresContext->FireDOMPaintEvent(&mList);
+    return NS_OK;
+  }
+
+  nsRefPtr<nsPresContext> mPresContext;
+  nsInvalidateRequestList mList;
+};
+
 void
-nsPresContext::NotifyDidPaintForSubtree()
+nsPresContext::NotifyDidPaintForSubtree(uint32_t aFlags)
 {
   if (IsRoot()) {
-    if (!mFireAfterPaintEvents)
-      return;
-
     static_cast<nsRootPresContext*>(this)->CancelDidPaintTimer();
   }
 
-  mFireAfterPaintEvents = false;
-
-  nsCOMPtr<nsIRunnable> ev =
-    NS_NewRunnableMethod(this, &nsPresContext::FireDOMPaintEvent);
-  nsContentUtils::AddScriptRunner(ev);
-
-  mDocument->EnumerateSubDocuments(NotifyDidPaintSubdocumentCallback, nullptr);
+  if (!mFireAfterPaintEvents) {
+    return;
+  }
+
+  if (aFlags & nsIPresShell::PAINT_LAYERS) {
+    mUndeliveredInvalidateRequestsBeforeLastPaint.TakeFrom(
+        &mInvalidateRequestsSinceLastPaint);
+    mAllInvalidated = false;
+  }
+  if (aFlags & nsIPresShell::PAINT_COMPOSITE) {
+    nsCOMPtr<nsIRunnable> ev =
+      new DelayedFireDOMPaintEvent(this, &mUndeliveredInvalidateRequestsBeforeLastPaint);
+    nsContentUtils::AddScriptRunner(ev);
+  }
+
+  NotifyDidPaintSubdocumentCallbackClosure closure = { aFlags, false };
+  mDocument->EnumerateSubDocuments(NotifyDidPaintSubdocumentCallback, &closure);
+
+  if (!closure.mNeedsAnotherDidPaintNotification &&
+      mInvalidateRequestsSinceLastPaint.IsEmpty() &&
+      mUndeliveredInvalidateRequestsBeforeLastPaint.IsEmpty()) {
+    // Nothing more to do for the moment.
+    mFireAfterPaintEvents = false;
+  } else {
+    if (IsRoot()) {
+      static_cast<nsRootPresContext*>(this)->EnsureEventualDidPaintEvent();
+    }
+  }
 }
 
 bool
 nsPresContext::HasCachedStyleData()
 {
   return mShell && mShell->StyleSet()->HasCachedStyleData();
 }
 
@@ -2720,17 +2764,20 @@ nsRootPresContext::ApplyPluginGeometryUp
   mRegisteredPlugins.EnumerateEntries(PluginDidSetGeometryEnumerator, nullptr);
 }
 
 static void
 NotifyDidPaintForSubtreeCallback(nsITimer *aTimer, void *aClosure)
 {
   nsPresContext* presContext = (nsPresContext*)aClosure;
   nsAutoScriptBlocker blockScripts;
-  presContext->NotifyDidPaintForSubtree();
+  // This is a fallback if we don't get paint events for some reason
+  // so we'll just pretend both layer painting and compositing happened.
+  presContext->NotifyDidPaintForSubtree(
+      nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_COMPOSITE);
 }
 
 void
 nsRootPresContext::EnsureEventualDidPaintEvent()
 {
   if (mNotifyDidPaintTimer)
     return;
   mNotifyDidPaintTimer = do_CreateInstance("@mozilla.org/timer;1");
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -105,16 +105,22 @@ enum nsLayoutPhase {
 
 class nsInvalidateRequestList {
 public:
   struct Request {
     nsRect   mRect;
     uint32_t mFlags;
   };
 
+  void TakeFrom(nsInvalidateRequestList* aList)
+  {
+    mRequests.MoveElementsFrom(aList->mRequests);
+  }
+  bool IsEmpty() { return mRequests.IsEmpty(); }
+
   nsTArray<Request> mRequests;
 };
 
 /**
  * Layer UserData for ContainerLayers that want to be notified
  * of local invalidations of them and their descendant layers.
  * Pass a callback to ComputeDifferences to have these called.
  */
@@ -823,26 +829,28 @@ public:
   // and, if necessary, synchronously rebuilding all style data.
   // Returns true on success and false on failure (not safe).
   bool EnsureSafeToHandOutCSSRules();
 
   void NotifyInvalidation(uint32_t aFlags);
   void NotifyInvalidation(const nsRect& aRect, uint32_t aFlags);
   // aRect is in device pixels
   void NotifyInvalidation(const nsIntRect& aRect, uint32_t aFlags);
-  void NotifyDidPaintForSubtree();
-  void FireDOMPaintEvent();
+  // aFlags are nsIPresShell::PAINT_ flags
+  void NotifyDidPaintForSubtree(uint32_t aFlags);
+  void FireDOMPaintEvent(nsInvalidateRequestList* aList);
 
   // Callback for catching invalidations in ContainerLayers
   // Passed to LayerProperties::ComputeDifference
   static void NotifySubDocInvalidation(mozilla::layers::ContainerLayer* aContainer,
                                        const nsIntRegion& aRegion);
   bool IsDOMPaintEventPending();
   void ClearMozAfterPaintEvents() {
-    mInvalidateRequests.mRequests.Clear();
+    mInvalidateRequestsSinceLastPaint.mRequests.Clear();
+    mUndeliveredInvalidateRequestsBeforeLastPaint.mRequests.Clear();
     mAllInvalidated = false;
   }
 
   bool IsProcessingRestyles() const {
     return mProcessingRestyles;
   }
 
   void SetProcessingRestyles(bool aProcessing) {
@@ -1133,17 +1141,18 @@ protected:
 
   nsCOMPtr<nsITheme> mTheme;
   nsCOMPtr<nsILanguageAtomService> mLangService;
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
   nsCOMPtr<nsITimer>    mPrefChangedTimer;
 
   FramePropertyTable    mPropertyTable;
 
-  nsInvalidateRequestList mInvalidateRequests;
+  nsInvalidateRequestList mInvalidateRequestsSinceLastPaint;
+  nsInvalidateRequestList mUndeliveredInvalidateRequestsBeforeLastPaint;
 
   // container for per-context fonts (downloadable, SVG, etc.)
   nsUserFontSet*        mUserFontSet;
 
   nsRect                mVisibleArea;
   nsSize                mPageSize;
   float                 mPageScale;
   float                 mPPScale;
@@ -1195,16 +1204,18 @@ protected:
   unsigned              mPrefBidiDirection : 1;
   unsigned              mPrefScrollbarSide : 2;
   unsigned              mPendingSysColorChanged : 1;
   unsigned              mPendingThemeChanged : 1;
   unsigned              mPendingUIResolutionChanged : 1;
   unsigned              mPendingMediaFeatureValuesChanged : 1;
   unsigned              mPrefChangePendingNeedsReflow : 1;
   unsigned              mMayHaveFixedBackgroundFrames : 1;
+  // True if the requests in mInvalidateRequestsSinceLastPaint cover the
+  // entire viewport
   unsigned              mAllInvalidated : 1;
 
   // Are we currently drawing an SVG glyph?
   unsigned              mIsGlyph : 1;
 
   // Does the associated document use viewport units?
   unsigned              mUsesViewportUnits : 1;
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5211,41 +5211,44 @@ PresShell::ProcessSynthMouseMoveEvent(bo
   if (!aFromScroll) {
     mSynthMouseMoveEvent.Forget();
   }
 }
 
 class nsAutoNotifyDidPaint
 {
 public:
-  nsAutoNotifyDidPaint(bool aWillSendDidPaint)
-    : mWillSendDidPaint(aWillSendDidPaint)
+  nsAutoNotifyDidPaint(PresShell* aShell, uint32_t aFlags)
+    : mShell(aShell), mFlags(aFlags)
   {
   }
   ~nsAutoNotifyDidPaint()
   {
-    if (!mWillSendDidPaint && nsContentUtils::XPConnect()) {
+    mShell->GetPresContext()->NotifyDidPaintForSubtree(mFlags);
+    if (!(mFlags & nsIPresShell::PAINT_WILL_SEND_DID_PAINT) &&
+        nsContentUtils::XPConnect()) {
       nsContentUtils::XPConnect()->NotifyDidPaint();
     }
   }
 
 private:
-  bool mWillSendDidPaint;
+  PresShell* mShell;
+  uint32_t mFlags;
 };
 
 void
 PresShell::Paint(nsIView*        aViewToPaint,
                  const nsRegion& aDirtyRegion,
                  uint32_t        aFlags)
 {
   SAMPLE_LABEL("Paint", "PresShell::Paint");
   NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell");
   NS_ASSERTION(aViewToPaint, "null view");
 
-  nsAutoNotifyDidPaint notifyDidPaint((aFlags & PAINT_WILL_SEND_DID_PAINT) != 0);
+  nsAutoNotifyDidPaint notifyDidPaint(this, aFlags);
 
   nsPresContext* presContext = GetPresContext();
   AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint);
 
   nsIFrame* frame = aViewToPaint->GetFrame();
 
   bool isRetainingManager;
   LayerManager* layerManager =
@@ -5304,17 +5307,16 @@ PresShell::Paint(nsIView*        aViewTo
             aViewToPaint->GetViewManager()->InvalidateViewNoSuppression(aViewToPaint, rect);
             presContext->NotifyInvalidation(invalid, 0);
           }
         } else {
           aViewToPaint->GetViewManager()->InvalidateView(aViewToPaint);
         }
 
         frame->UpdatePaintCountForPaintedPresShells();
-        presContext->NotifyDidPaintForSubtree();
         return;
       }
     }
     frame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE);
   } else {
     layerManager->BeginTransaction();
   }
   if (frame) {
@@ -5333,38 +5335,31 @@ PresShell::Paint(nsIView*        aViewTo
     // The layer system can trigger invalidates during painting
     // (see FrameLayerBuilder).
     frame->BeginDeferringInvalidatesForDisplayRoot(aDirtyRegion);
 
     // We can paint directly into the widget using its layer manager.
     nsLayoutUtils::PaintFrame(nullptr, frame, aDirtyRegion, bgcolor, flags);
 
     frame->EndDeferringInvalidatesForDisplayRoot();
-    if (aFlags & PAINT_LAYERS) {
-      presContext->NotifyDidPaintForSubtree();
-    }
     return;
   }
 
   nsRefPtr<ColorLayer> root = layerManager->CreateColorLayer();
   if (root) {
     nsPresContext* pc = GetPresContext();
     nsIntRect bounds =
       pc->GetVisibleArea().ToOutsidePixels(pc->AppUnitsPerDevPixel());
     bgcolor = NS_ComposeColors(bgcolor, mCanvasBackgroundColor);
     root->SetColor(bgcolor);
     root->SetVisibleRegion(bounds);
     layerManager->SetRoot(root);
   }
   layerManager->EndTransaction(NULL, NULL, (aFlags & PAINT_COMPOSITE) ?
     LayerManager::END_DEFAULT : LayerManager::END_NO_COMPOSITE);
-
-  if (aFlags & PAINT_LAYERS) {
-    presContext->NotifyDidPaintForSubtree();
-  }
 }
 
 // static
 void
 nsIPresShell::SetCapturingContent(nsIContent* aContent, uint8_t aFlags)
 {
   // If capture was set for pointer lock, don't unlock unless we are coming
   // out of pointer lock explicitly.