Bug 974197 - Fire MozAfterPaint after the compositor has composited the frame. r=roc
☠☠ backed out by 279e3bc8979d ☠ ☠
authorMatt Woodrow <mwoodrow@mozilla.com>
Fri, 07 Mar 2014 16:24:32 +1300
changeset 177825 c817078789778d716d7ca363a4e3c1c6df170b68
parent 177824 fb3f9e19fa94b84ac08b8d308f720cad8e810719
child 177826 22feaebc0b2c5b355405d0692ab83921a9e0a156
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersroc
bugs974197
milestone31.0a1
Bug 974197 - Fire MozAfterPaint after the compositor has composited the frame. r=roc
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/client/ClientLayerManager.h
gfx/layers/ipc/CompositorChild.cpp
gfx/layers/ipc/CompositorChild.h
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
gfx/layers/ipc/PCompositor.ipdl
layout/base/nsPresShell.cpp
layout/reftests/forms/input/range/stepDown-unthemed.html
layout/reftests/forms/input/range/stepDown.html
layout/reftests/forms/input/range/stepUp-unthemed.html
layout/reftests/forms/input/range/stepUp.html
layout/reftests/printing/745025-1.html
view/public/nsView.h
view/src/nsView.cpp
widget/nsIWidgetListener.h
widget/xpwidgets/nsIWidgetListener.cpp
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -68,16 +68,17 @@
 #include "StructuredCloneUtils.h"
 #include "nsViewportInfo.h"
 #include "JavaScriptChild.h"
 #include "APZCCallbackHelper.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
 #include "mozilla/gfx/Matrix.h"
 #include "UnitTransforms.h"
+#include "ClientLayerManager.h"
 
 #include "nsColorPickerProxy.h"
 
 #ifdef DEBUG
 #include "PCOMContentPermissionRequestChild.h"
 #endif /* DEBUG */
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
@@ -100,16 +101,19 @@ static const CSSSize kDefaultViewportSiz
 
 static const char BROWSER_ZOOM_TO_RECT[] = "browser-zoom-to-rect";
 static const char BEFORE_FIRST_PAINT[] = "before-first-paint";
 
 static bool sCpowsEnabled = false;
 static int32_t sActiveDurationMs = 10;
 static bool sActiveDurationMsSet = false;
 
+typedef nsDataHashtable<nsUint64HashKey, TabChild*> TabChildMap;
+static TabChildMap* sTabChildren;
+
 TabChildBase::TabChildBase()
   : mOldViewportWidth(0.0f)
   , mContentDocumentIsDisplayed(false)
   , mTabChildGlobal(nullptr)
   , mInnerSize(0, 0)
 {
 }
 
@@ -664,16 +668,17 @@ TabChild::Create(ContentChild* aManager,
 }
 
 
 TabChild::TabChild(ContentChild* aManager, const TabContext& aContext, uint32_t aChromeFlags)
   : TabContext(aContext)
   , mRemoteFrame(nullptr)
   , mManager(aManager)
   , mChromeFlags(aChromeFlags)
+  , mLayersId(0)
   , mOuterRect(0, 0, 0, 0)
   , mActivePointerId(-1)
   , mTapHoldTimer(nullptr)
   , mAppPackageFileDescriptorRecved(false)
   , mLastBackgroundColor(NS_RGB(255, 255, 255))
   , mDidFakeShow(false)
   , mNotified(false)
   , mTriedBrowserInit(false)
@@ -1288,16 +1293,27 @@ TabChild::DestroyWindow()
     if (mWidget) {
         mWidget->Destroy();
     }
 
     if (mRemoteFrame) {
         mRemoteFrame->Destroy();
         mRemoteFrame = nullptr;
     }
+
+
+    if (mLayersId != 0) {
+      MOZ_ASSERT(sTabChildren);
+      sTabChildren->Remove(mLayersId);
+      if (!sTabChildren->Count()) {
+        delete sTabChildren;
+        sTabChildren = nullptr;
+      }
+      mLayersId = 0;
+    }
 }
 
 bool
 TabChild::UseDirectCompositor()
 {
     return !!CompositorChild::Get();
 }
 
@@ -2382,16 +2398,23 @@ TabChild::InitRenderingState()
         mWidget->GetLayerManager(shadowManager, mTextureFactoryIdentifier.mParentBackend)
                ->AsShadowForwarder();
     NS_ABORT_IF_FALSE(lf && lf->HasShadowManager(),
                       "PuppetWidget should have shadow manager");
     lf->IdentifyTextureHost(mTextureFactoryIdentifier);
     ImageBridgeChild::IdentifyCompositorTextureHost(mTextureFactoryIdentifier);
 
     mRemoteFrame = remoteFrame;
+    if (id != 0) {
+      if (!sTabChildren) {
+        sTabChildren = new TabChildMap;
+      }
+      sTabChildren->Put(id, this);
+      mLayersId = id;
+    }
 
     nsCOMPtr<nsIObserverService> observerService =
         mozilla::services::GetObserverService();
 
     if (observerService) {
         observerService->AddObserver(this,
                                      BROWSER_ZOOM_TO_RECT,
                                      false);
@@ -2578,16 +2601,36 @@ TabChild::GetFrom(nsIPresShell* aPresShe
   nsIDocument* doc = aPresShell->GetDocument();
   if (!doc) {
       return nullptr;
   }
   nsCOMPtr<nsIDocShell> docShell(doc->GetDocShell());
   return GetFrom(docShell);
 }
 
+TabChild*
+TabChild::GetFrom(uint64_t aLayersId)
+{
+  if (!sTabChildren) {
+    return nullptr;
+  }
+  return sTabChildren->Get(aLayersId);
+}
+
+void
+TabChild::DidComposite()
+{
+  MOZ_ASSERT(mWidget);
+  MOZ_ASSERT(mWidget->GetLayerManager());
+  MOZ_ASSERT(mWidget->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT);
+
+  ClientLayerManager *manager = static_cast<ClientLayerManager*>(mWidget->GetLayerManager());
+  manager->DidComposite();
+}
+
 NS_IMETHODIMP
 TabChild::OnShowTooltip(int32_t aXCoords, int32_t aYCoords, const char16_t *aTipText)
 {
     nsString str(aTipText);
     SendShowTooltip(aXCoords, aYCoords, str);
     return NS_OK;
 }
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -425,16 +425,19 @@ public:
     static inline TabChild*
     GetFrom(nsIDocShell* aDocShell)
     {
       nsCOMPtr<nsITabChild> tc = do_GetInterface(aDocShell);
       return static_cast<TabChild*>(tc.get());
     }
 
     static TabChild* GetFrom(nsIPresShell* aPresShell);
+    static TabChild* GetFrom(uint64_t aLayersId);
+
+    void DidComposite();
 
     static inline TabChild*
     GetFrom(nsIDOMWindow* aWindow)
     {
       nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
       nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webNav);
       return GetFrom(docShell);
     }
@@ -509,16 +512,17 @@ private:
 
     TextureFactoryIdentifier mTextureFactoryIdentifier;
     nsCOMPtr<nsIWebNavigation> mWebNav;
     nsCOMPtr<nsIWidget> mWidget;
     nsCOMPtr<nsIURI> mLastURI;
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<ContentChild> mManager;
     uint32_t mChromeFlags;
+    uint64_t mLayersId;
     nsIntRect mOuterRect;
     // When we're tracking a possible tap gesture, this is the "down"
     // point of the touchstart.
     LayoutDevicePoint mGestureDownPoint;
     // The touch identifier of the active gesture.
     int32_t mActivePointerId;
     // A timer task that fires if the tap-hold timeout is exceeded by
     // the touch we're tracking.  That is, if touchend or a touchmove
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -19,16 +19,17 @@
 #include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/layers/PLayerChild.h"  // for PLayerChild
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/TextureClientPool.h" // for TextureClientPool
 #include "mozilla/layers/SimpleTextureClientPool.h" // for SimpleTextureClientPool
 #include "nsAString.h"
 #include "nsIWidget.h"                  // for nsIWidget
+#include "nsIWidgetListener.h"
 #include "nsTArray.h"                   // for AutoInfallibleTArray
 #include "nsXULAppAPI.h"                // for XRE_GetProcessType, etc
 #include "TiledLayerBuffer.h"
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #endif
 
 using namespace mozilla::dom;
@@ -266,16 +267,31 @@ ClientLayerManager::GetRemoteRenderer()
 void
 ClientLayerManager::Composite()
 {
   if (LayerTransactionChild* manager = mForwarder->GetShadowManager()) {
     manager->SendForceComposite();
   }
 }
 
+void
+ClientLayerManager::DidComposite()
+{
+  MOZ_ASSERT(mWidget);
+  nsIWidgetListener *listener = mWidget->GetWidgetListener();
+  if (listener) {
+    listener->DidCompositeWindow();
+  } else {
+    listener = mWidget->GetAttachedWidgetListener();
+    if (listener) {
+      listener->DidCompositeWindow();
+    }
+  }
+}
+
 void 
 ClientLayerManager::MakeSnapshotIfRequired()
 {
   if (!mShadowTarget) {
     return;
   }
   if (mWidget) {
     if (CompositorChild* remoteRenderer = GetRemoteRenderer()) {
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -158,16 +158,18 @@ public:
   void SetNeedsComposite(bool aNeedsComposite)
   {
     mNeedsComposite = aNeedsComposite;
   }
   bool NeedsComposite() const { return mNeedsComposite; }
 
   virtual void Composite() MOZ_OVERRIDE;
 
+  virtual void DidComposite();
+
 protected:
   enum TransactionPhase {
     PHASE_NONE, PHASE_CONSTRUCTION, PHASE_DRAWING, PHASE_FORWARD
   };
   TransactionPhase mPhase;
 
 private:
   /**
--- a/gfx/layers/ipc/CompositorChild.cpp
+++ b/gfx/layers/ipc/CompositorChild.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/layers/PLayerTransactionChild.h"
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsIObserver.h"                // for nsIObserver
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl
 #include "nsXULAppAPI.h"                // for XRE_GetIOMessageLoop, etc
 #include "FrameLayerBuilder.h"
+#include "mozilla/dom/TabChild.h"
 
 using mozilla::layers::LayerTransactionChild;
 
 namespace mozilla {
 namespace layers {
 
 /*static*/ CompositorChild* CompositorChild::sCompositor;
 
@@ -115,16 +116,31 @@ CompositorChild::DeallocPLayerTransactio
 
 bool
 CompositorChild::RecvInvalidateAll()
 {
   FrameLayerBuilder::InvalidateAllLayers(mLayerManager);
   return true;
 }
 
+bool
+CompositorChild::RecvDidComposite(const uint64_t& aId)
+{
+  if (mLayerManager) {
+    MOZ_ASSERT(aId == 0);
+    mLayerManager->DidComposite();
+  } else if (aId != 0) {
+    dom::TabChild *child = dom::TabChild::GetFrom(aId);
+    if (child) {
+      child->DidComposite();
+    }
+  }
+  return true;
+}
+
 void
 CompositorChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_ASSERT(sCompositor == this);
 
 #ifdef MOZ_B2G
   // Due to poor lifetime management of gralloc (and possibly shmems) we will
   // crash at some point in the future when we get destroyed due to abnormal
--- a/gfx/layers/ipc/CompositorChild.h
+++ b/gfx/layers/ipc/CompositorChild.h
@@ -51,16 +51,18 @@ public:
   Create(Transport* aTransport, ProcessId aOtherProcess);
 
   static CompositorChild* Get();
 
   static bool ChildProcessHasCompositor() { return sCompositor != nullptr; }
 
   virtual bool RecvInvalidateAll() MOZ_OVERRIDE;
 
+  virtual bool RecvDidComposite(const uint64_t& aId) MOZ_OVERRIDE;
+
 private:
   // Private destructor, to discourage deletion outside of Release():
   virtual ~CompositorChild();
 
   virtual PLayerTransactionChild*
     AllocPLayerTransactionChild(const nsTArray<LayersBackend>& aBackendHints,
                                 const uint64_t& aId,
                                 TextureFactoryIdentifier* aTextureFactoryIdentifier,
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -45,16 +45,17 @@
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "nsXULAppAPI.h"                // for XRE_GetIOMessageLoop
 #ifdef XP_WIN
 #include "mozilla/layers/CompositorD3D11.h"
 #include "mozilla/layers/CompositorD3D9.h"
 #endif
 #include "GeckoProfiler.h"
 #include "mozilla/ipc/ProtocolTypes.h"
+#include "mozilla/unused.h"
 
 using namespace base;
 using namespace mozilla;
 using namespace mozilla::ipc;
 using namespace mozilla::gfx;
 using namespace std;
 
 namespace mozilla {
@@ -183,16 +184,17 @@ CompositorParent::CompositorParent(nsIWi
   , mIsTesting(false)
   , mPaused(false)
   , mUseExternalSurfaceSize(aUseExternalSurfaceSize)
   , mEGLSurfaceSize(aSurfaceWidth, aSurfaceHeight)
   , mPauseCompositionMonitor("PauseCompositionMonitor")
   , mResumeCompositionMonitor("ResumeCompositionMonitor")
   , mOverrideComposeReadiness(false)
   , mForceCompositionTask(nullptr)
+  , mWantDidCompositeEvent(false)
 {
   NS_ABORT_IF_FALSE(sCompositorThread != nullptr || sCompositorThreadID,
                     "The compositor thread must be Initialized before instanciating a COmpositorParent.");
   MOZ_COUNT_CTOR(CompositorParent);
   mCompositorID = 0;
   // FIXME: This holds on the the fact that right now the only thing that
   // can destroy this instance is initialized on the compositor thread after
   // this task has been processed.
@@ -517,16 +519,18 @@ CompositorParent::NotifyShadowTreeTransa
     AutoResolveRefLayers resolve(mCompositionManager);
     mApzcTreeManager->UpdatePanZoomControllerTree(this, mLayerManager->GetRoot(), aIsFirstPaint, aId);
 
     mLayerManager->NotifyShadowTreeTransaction();
   }
   if (aScheduleComposite) {
     ScheduleComposition();
   }
+
+  mWantDidCompositeEvent = true;
 }
 
 // Used when layout.frame_rate is -1. Needs to be kept in sync with
 // DEFAULT_FRAME_RATE in nsRefreshDriver.cpp.
 static const int32_t kDefaultFrameRate = 60;
 
 static int32_t
 CalculateCompositionFrameRate()
@@ -646,16 +650,21 @@ CompositorParent::CompositeToTarget(Draw
   if (gDumpCompositorTree) {
     printf_stderr("Painting --- compositing layer tree:\n");
     mLayerManager->Dump();
   }
 #endif
   mLayerManager->SetDebugOverlayWantsNextFrame(false);
   mLayerManager->EndEmptyTransaction();
 
+  if (!aTarget && mWantDidCompositeEvent) {
+    DidComposite();
+    mWantDidCompositeEvent = false;
+  }
+
   if (mLayerManager->DebugOverlayWantsNextFrame()) {
     ScheduleComposition();
   }
 
 #ifdef COMPOSITOR_PERFORMANCE_WARNING
   TimeDuration executionTime = TimeStamp::Now() - mLastCompose;
   TimeDuration frameBudget = TimeDuration::FromMilliseconds(15);
   int32_t frameRate = CalculateCompositionFrameRate();
@@ -674,16 +683,30 @@ CompositorParent::CompositeToTarget(Draw
     // Special full-tilt composite mode for performance testing
     ScheduleComposition();
   }
 
   profiler_tracing("Paint", "Composite", TRACING_INTERVAL_END);
 }
 
 void
+CompositorParent::DidComposite()
+{
+  unused << SendDidComposite(0);
+
+  for (LayerTreeMap::iterator it = sIndirectLayerTrees.begin();
+       it != sIndirectLayerTrees.end(); it++) {
+    LayerTreeState* lts = &it->second;
+    if (lts->mParent == this && lts->mCrossProcessParent) {
+      unused << lts->mCrossProcessParent->SendDidComposite(it->first);
+    }
+  }
+}
+
+void
 CompositorParent::ForceComposeToTarget(DrawTarget* aTarget)
 {
   PROFILER_LABEL("CompositorParent", "ForceComposeToTarget");
   AutoRestore<bool> override(mOverrideComposeReadiness);
   mOverrideComposeReadiness = true;
 
   CompositeToTarget(aTarget);
 }
@@ -761,16 +784,17 @@ CompositorParent::ShadowLayersUpdated(La
       bool requestNextFrame =
         mCompositionManager->TransformShadowTree(mTestTime);
       if (!requestNextFrame) {
         CancelCurrentCompositeTask();
       }
     }
   }
   mLayerManager->NotifyShadowTreeTransaction();
+  mWantDidCompositeEvent = true;
 }
 
 void
 CompositorParent::ForceComposite(LayerTransactionParent* aLayerTree)
 {
   ScheduleComposition();
 }
 
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -295,16 +295,18 @@ private:
   static CompositorParent* RemoveCompositor(uint64_t id);
 
    /**
    * Return true if current state allows compositing, that is
    * finishing a layers transaction.
    */
   bool CanComposite();
 
+  void DidComposite();
+
   nsRefPtr<LayerManagerComposite> mLayerManager;
   nsRefPtr<Compositor> mCompositor;
   RefPtr<AsyncCompositionManager> mCompositionManager;
   nsIWidget* mWidget;
   CancelableTask *mCurrentCompositeTask;
   TimeStamp mLastCompose;
   TimeStamp mTestTime;
   bool mIsTesting;
@@ -323,15 +325,17 @@ private:
   uint64_t mCompositorID;
   uint64_t mRootLayerTreeID;
 
   bool mOverrideComposeReadiness;
   CancelableTask* mForceCompositionTask;
 
   nsRefPtr<APZCTreeManager> mApzcTreeManager;
 
+  bool mWantDidCompositeEvent;
+
   DISALLOW_EVIL_CONSTRUCTORS(CompositorParent);
 };
 
 } // layers
 } // mozilla
 
 #endif // mozilla_layers_CompositorParent_h
--- a/gfx/layers/ipc/PCompositor.ipdl
+++ b/gfx/layers/ipc/PCompositor.ipdl
@@ -35,16 +35,21 @@ intr protocol PCompositor
 {
   // A Compositor manages a single Layer Manager (PLayerTransaction)
   manages PLayerTransaction;
 
 child:
   // The child should invalidate everything so that the whole window is redrawn.
   async InvalidateAll();
 
+  // The compositor completed a layers transaction. id is the layers id
+  // of the child layer tree that was composited (or 0 when notifying
+  // the root layer tree).
+  async DidComposite(uint64_t id);
+
 parent:
 
   // The child is about to be destroyed, so perform any necessary cleanup.
   sync WillStop();
 
   // Clean up in preparation for own destruction.
   sync Stop();
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5819,21 +5819,17 @@ PresShell::Paint(nsView*        aViewToP
   nsIFrame* frame = aViewToPaint->GetFrame();
 
   bool isRetainingManager;
   LayerManager* layerManager =
     aViewToPaint->GetWidget()->GetLayerManager(&isRetainingManager);
   NS_ASSERTION(layerManager, "Must be in paint event");
   bool shouldInvalidate = layerManager->NeedsWidgetInvalidation();
 
-  uint32_t didPaintFlags = aFlags;
-  if (!shouldInvalidate) {
-    didPaintFlags |= PAINT_COMPOSITE;
-  }
-  nsAutoNotifyDidPaint notifyDidPaint(this, didPaintFlags);
+  nsAutoNotifyDidPaint notifyDidPaint(this, aFlags);
   AutoUpdateHitRegion updateHitRegion(this, frame);
 
   // Whether or not we should set first paint when painting is
   // suppressed is debatable. For now we'll do it because
   // B2G relies on first paint to configure the viewport and
   // we only want to do that when we have real content to paint.
   // See Bug 798245
   if (mIsFirstPaint && !mPaintingSuppressed) {
--- a/layout/reftests/forms/input/range/stepDown-unthemed.html
+++ b/layout/reftests/forms/input/range/stepDown-unthemed.html
@@ -4,14 +4,13 @@
              should be moved to the appropriate position -->
   <script type="text/javascript">
     function setValue()
     {
       document.getElementById('i').stepDown();
       document.documentElement.className = '';
     }
     document.addEventListener("MozReftestInvalidate", setValue);
-    setTimeout(setValue, 2000); // useful when not running under reftest suite
   </script>
   <body>
     <input type=range id='i' value=100 step=25 style='-moz-appearance:none'>
   </body>
 </html>
--- a/layout/reftests/forms/input/range/stepDown.html
+++ b/layout/reftests/forms/input/range/stepDown.html
@@ -4,14 +4,13 @@
              should be moved to the appropriate position -->
   <script type="text/javascript">
     function setValue()
     {
       document.getElementById('i').stepDown();
       document.documentElement.className = '';
     }
     document.addEventListener("MozReftestInvalidate", setValue);
-    setTimeout(setValue, 2000); // useful when not running under reftest suite
   </script>
   <body>
     <input type=range id='i' value=100 step=25>
   </body>
 </html>
--- a/layout/reftests/forms/input/range/stepUp-unthemed.html
+++ b/layout/reftests/forms/input/range/stepUp-unthemed.html
@@ -4,14 +4,13 @@
              should be moved to the appropriate position -->
   <script type="text/javascript">
     function setValue()
     {
       document.getElementById('i').stepUp();
       document.documentElement.className = '';
     }
     document.addEventListener("MozReftestInvalidate", setValue);
-    setTimeout(setValue, 2000); // useful when not running under reftest suite
   </script>
   <body>
     <input type=range id='i' value=50 step=25 style='-moz-appearance:none'>
   </body>
 </html>
--- a/layout/reftests/forms/input/range/stepUp.html
+++ b/layout/reftests/forms/input/range/stepUp.html
@@ -4,14 +4,13 @@
              should be moved to the appropriate position -->
   <script type="text/javascript">
     function setValue()
     {
       document.getElementById('i').stepUp();
       document.documentElement.className = '';
     }
     document.addEventListener("MozReftestInvalidate", setValue);
-    setTimeout(setValue, 2000); // useful when not running under reftest suite
   </script>
   <body>
     <input type=range id='i' value=50 step=25>
   </body>
 </html>
--- a/layout/reftests/printing/745025-1.html
+++ b/layout/reftests/printing/745025-1.html
@@ -7,16 +7,16 @@
       canvas.mozPrintCallback = function(obj) {
         var ctx = obj.context;
         ctx.fillStyle = 'rgb(255, 0, 0)';
         ctx.fillRect(0, 0, 10, 10);
         obj.done();
       };
       setTimeout(function() {
         document.documentElement.className = "reftest-print" 
-      }, 0);
+      }, 100);
     };
   </script>
 </head>
 <body>
   <canvas id="canvas" width="10px" height="10px"></canvas>
 </body>
 </html>
--- a/view/public/nsView.h
+++ b/view/public/nsView.h
@@ -365,16 +365,17 @@ public:
   virtual nsIPresShell* GetPresShell() MOZ_OVERRIDE;
   virtual nsView* GetView() MOZ_OVERRIDE { return this; }
   virtual bool WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y) MOZ_OVERRIDE;
   virtual bool WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight) MOZ_OVERRIDE;
   virtual bool RequestWindowClose(nsIWidget* aWidget) MOZ_OVERRIDE;
   virtual void WillPaintWindow(nsIWidget* aWidget) MOZ_OVERRIDE;
   virtual bool PaintWindow(nsIWidget* aWidget, nsIntRegion aRegion) MOZ_OVERRIDE;
   virtual void DidPaintWindow() MOZ_OVERRIDE;
+  virtual void DidCompositeWindow() MOZ_OVERRIDE;
   virtual void RequestRepaint() MOZ_OVERRIDE;
   virtual nsEventStatus HandleEvent(mozilla::WidgetGUIEvent* aEvent,
                                     bool aUseAttachedEvents) MOZ_OVERRIDE;
 
   virtual ~nsView();
 
   nsPoint GetOffsetTo(const nsView* aOther, const int32_t aAPD) const;
   nsIWidget* GetNearestWidget(nsPoint* aOffset, const int32_t aAPD) const;
--- a/view/src/nsView.cpp
+++ b/view/src/nsView.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/Likely.h"
 #include "mozilla/Poison.h"
 #include "nsIWidget.h"
 #include "nsViewManager.h"
 #include "nsIFrame.h"
 #include "nsPresArena.h"
 #include "nsXULPopupManager.h"
 #include "nsIWidgetListener.h"
+#include "nsContentUtils.h" // for nsAutoScriptBlocker
 
 using namespace mozilla;
 
 nsView::nsView(nsViewManager* aViewManager, nsViewVisibility aVisibility)
 {
   MOZ_COUNT_CTOR(nsView);
 
   mVis = aVisibility;
@@ -1052,16 +1053,26 @@ nsView::PaintWindow(nsIWidget* aWidget, 
 void
 nsView::DidPaintWindow()
 {
   nsRefPtr<nsViewManager> vm = mViewManager;
   vm->DidPaintWindow();
 }
 
 void
+nsView::DidCompositeWindow()
+{
+  nsIPresShell* presShell = mViewManager->GetPresShell();
+  if (presShell) {
+    nsAutoScriptBlocker scriptBlocker;
+    presShell->GetPresContext()->GetDisplayRootPresContext()->GetRootPresContext()->NotifyDidPaintForSubtree(nsIPresShell::PAINT_COMPOSITE);
+  }
+}
+
+void
 nsView::RequestRepaint()
 {
   nsIPresShell* presShell = mViewManager->GetPresShell();
   if (presShell) {
     presShell->ScheduleViewManagerFlush();
   }
 }
 
--- a/widget/nsIWidgetListener.h
+++ b/widget/nsIWidgetListener.h
@@ -125,16 +125,18 @@ public:
   /**
    * Indicates that a paint occurred.
    * This is called at a time when it is OK to change the geometry of
    * this widget or of other widgets.
    * Must be called after every call to PaintWindow.
    */
   virtual void DidPaintWindow();
 
+  virtual void DidCompositeWindow();
+
   /**
    * Request that layout schedules a repaint on the next refresh driver tick.
    */
   virtual void RequestRepaint();
 
   /**
    * Handle an event.
    */
--- a/widget/xpwidgets/nsIWidgetListener.cpp
+++ b/widget/xpwidgets/nsIWidgetListener.cpp
@@ -97,16 +97,21 @@ nsIWidgetListener::PaintWindow(nsIWidget
 }
 
 void
 nsIWidgetListener::DidPaintWindow()
 {
 }
 
 void
+nsIWidgetListener::DidCompositeWindow()
+{
+}
+
+void
 nsIWidgetListener::RequestRepaint()
 {
 }
 
 nsEventStatus
 nsIWidgetListener::HandleEvent(WidgetGUIEvent* aEvent,
                                bool aUseAttachedEvents)
 {