Bug 806029: Have remote content drop their buffers when they're hidden. r=mattwoodrow sr=roc
authorChris Jones <jones.chris.g@gmail.com>
Wed, 07 Nov 2012 19:51:55 -0800
changeset 120600 ea85f4e3fc2de25e94235875648923f562c7252f
parent 120599 de95b48ab6e14d4d8d91ef14dfc3098fe5a352be
child 120601 c0d1ffbe9a87102cbba20b533c6a3f8bc208efe4
push id1997
push userakeybl@mozilla.com
push dateMon, 07 Jan 2013 21:25:26 +0000
treeherdermozilla-beta@4baf45cdcf21 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, roc
bugs806029
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 806029: Have remote content drop their buffers when they're hidden. r=mattwoodrow sr=roc
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
gfx/layers/Layers.h
gfx/layers/basic/BasicCanvasLayer.cpp
gfx/layers/basic/BasicImageLayer.cpp
gfx/layers/basic/BasicLayerManager.cpp
gfx/layers/basic/BasicLayers.h
gfx/layers/basic/BasicThebesLayer.h
gfx/layers/ipc/PLayers.ipdl
gfx/layers/ipc/ShadowLayersParent.cpp
gfx/layers/ipc/ShadowLayersParent.h
gfx/layers/opengl/ContainerLayerOGL.cpp
gfx/layers/opengl/LayerManagerOGL.cpp
gfx/layers/opengl/LayerManagerOGL.h
layout/base/nsPresShell.cpp
widget/xpwidgets/PuppetWidget.cpp
widget/xpwidgets/PuppetWidget.h
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1787,16 +1787,32 @@ TabChild::NotifyPainted()
 }
 
 bool
 TabChild::IsAsyncPanZoomEnabled()
 {
     return mScrolling == ASYNC_PAN_ZOOM;
 }
 
+void
+TabChild::MakeVisible()
+{
+    if (mWidget) {
+        mWidget->Show(true);
+    }
+}
+
+void
+TabChild::MakeHidden()
+{
+    if (mWidget) {
+        mWidget->Show(false);
+    }
+}
+
 NS_IMETHODIMP
 TabChild::GetMessageManager(nsIContentFrameMessageManager** aResult)
 {
   if (mTabChildGlobal) {
     NS_ADDREF(*aResult = mTabChildGlobal);
     return NS_OK;
   }
   *aResult = nullptr;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -287,16 +287,24 @@ public:
     void GetDPI(float* aDPI);
 
     void SetBackgroundColor(const nscolor& aColor);
 
     void NotifyPainted();
 
     bool IsAsyncPanZoomEnabled();
 
+    /**
+     * Signal to this TabChild that it should be made visible:
+     * activated widget, retained layer tree, etc.  (Respectively,
+     * made not visible.)
+     */
+    void MakeVisible();
+    void MakeHidden();
+
 protected:
     virtual PRenderFrameChild* AllocPRenderFrame(ScrollingBehavior* aScrolling,
                                                  LayersBackend* aBackend,
                                                  int32_t* aMaxTextureSize,
                                                  uint64_t* aLayersId) MOZ_OVERRIDE;
     virtual bool DeallocPRenderFrame(PRenderFrameChild* aFrame) MOZ_OVERRIDE;
     virtual bool RecvDestroy() MOZ_OVERRIDE;
 
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -426,16 +426,34 @@ public:
    * manager.
    */
   LayerUserData* GetUserData(void* aKey)
   { 
     return static_cast<LayerUserData*>(mUserData.Get(static_cast<gfx::UserDataKey*>(aKey)));
   }
 
   /**
+   * Must be called outside of a layers transaction.
+   *
+   * For the subtree rooted at |aSubtree|, this attempts to free up
+   * any free-able resources like retained buffers, but may do nothing
+   * at all.  After this call, the layer tree is left in an undefined
+   * state; the layers in |aSubtree|'s subtree may no longer have
+   * buffers with valid content and may no longer be able to draw
+   * their visible and valid regions.
+   *
+   * In general, a painting or forwarding transaction on |this| must
+   * complete on the tree before it returns to a valid state.
+   *
+   * Resource freeing begins from |aSubtree| or |mRoot| if |aSubtree|
+   * is null.  |aSubtree|'s manager must be this.
+   */
+  virtual void ClearCachedResources(Layer* aSubtree = nullptr) {}
+
+  /**
    * Flag the next paint as the first for a document.
    */
   virtual void SetIsFirstPaint() {}
 
   // We always declare the following logging symbols, because it's
   // extremely tricky to conditionally declare them.  However, for
   // ifndef MOZ_LAYERS_HAVE_LOG builds, they only have trivial
   // definitions in Layers.cpp.
--- a/gfx/layers/basic/BasicCanvasLayer.cpp
+++ b/gfx/layers/basic/BasicCanvasLayer.cpp
@@ -311,16 +311,21 @@ public:
   {
     DestroyBackBuffer();
     MOZ_COUNT_DTOR(BasicShadowableCanvasLayer);
   }
 
   virtual void Initialize(const Data& aData);
   virtual void Paint(gfxContext* aContext, Layer* aMaskLayer);
 
+  virtual void ClearCachedResources() MOZ_OVERRIDE
+  {
+    DestroyBackBuffer();
+  }
+
   virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
   {
     aAttrs = CanvasLayerAttributes(mFilter);
   }
 
   virtual Layer* AsLayer() { return this; }
   virtual ShadowableLayer* AsShadowableLayer() { return this; }
 
--- a/gfx/layers/basic/BasicImageLayer.cpp
+++ b/gfx/layers/basic/BasicImageLayer.cpp
@@ -176,16 +176,21 @@ public:
   virtual ~BasicShadowableImageLayer()
   {
     DestroyBackBuffer();
     MOZ_COUNT_DTOR(BasicShadowableImageLayer);
   }
 
   virtual void Paint(gfxContext* aContext, Layer* aMaskLayer);
 
+  virtual void ClearCachedResources() MOZ_OVERRIDE
+  {
+    DestroyBackBuffer();
+  }
+
   virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
   {
     aAttrs = ImageLayerAttributes(mFilter, mForceSingleTile);
   }
 
   virtual Layer* AsLayer() { return this; }
   virtual ShadowableLayer* AsShadowableLayer() { return this; }
 
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -993,22 +993,24 @@ BasicLayerManager::PaintLayer(gfxContext
       aTarget->Rectangle(destRect, true);
       aTarget->Clip();
       FlushGroup(paintContext, needsClipToVisibleRegion);
     }
   }
 }
 
 void
-BasicLayerManager::ClearCachedResources()
+BasicLayerManager::ClearCachedResources(Layer* aSubtree)
 {
-  if (mRoot) {
+  MOZ_ASSERT(!aSubtree || aSubtree->Manager() == this);
+  if (aSubtree) {
+    ClearLayer(aSubtree);
+  } else if (mRoot) {
     ClearLayer(mRoot);
   }
-
   mCachedSurface.Expire();
 }
 void
 BasicLayerManager::ClearLayer(Layer* aLayer)
 {
   ToData(aLayer)->ClearCachedResources();
   for (Layer* child = aLayer->GetFirstChild(); child;
        child = child->GetNextSibling()) {
@@ -1278,16 +1280,26 @@ BasicShadowLayerManager::IsCompositingCh
 }
 
 void
 BasicShadowLayerManager::SetIsFirstPaint()
 {
   ShadowLayerForwarder::SetIsFirstPaint();
 }
 
+void
+BasicShadowLayerManager::ClearCachedResources(Layer* aSubtree)
+{
+  MOZ_ASSERT(!HasShadowManager() || !aSubtree);
+  if (PLayersChild* manager = GetShadowManager()) {
+    manager->SendClearCachedResources();
+  }
+  BasicLayerManager::ClearCachedResources(aSubtree);
+}
+
 bool
 BasicShadowLayerManager::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent,
                                                    gfx::Rect& aViewport,
                                                    float& aScaleX,
                                                    float& aScaleY)
 {
 #ifdef MOZ_WIDGET_ANDROID
   Layer* primaryScrollable = GetPrimaryScrollableLayer();
--- a/gfx/layers/basic/BasicLayers.h
+++ b/gfx/layers/basic/BasicLayers.h
@@ -133,18 +133,18 @@ public:
   gfxContext* GetTarget() { return mTarget; }
   void SetTarget(gfxContext* aTarget) { mUsingDefaultTarget = false; mTarget = aTarget; }
   bool IsRetained() { return mWidget != nullptr; }
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   virtual const char* Name() const { return "Basic"; }
 #endif // MOZ_LAYERS_HAVE_LOG
 
-  // Clear the cached contents of this layer.
-  void ClearCachedResources();
+  // Clear the cached contents of this layer tree.
+  virtual void ClearCachedResources(Layer* aSubtree = nullptr) MOZ_OVERRIDE;
 
   void SetTransactionIncomplete() { mTransactionIncomplete = true; }
   bool IsTransactionIncomplete() { return mTransactionIncomplete; }
 
   already_AddRefed<gfxContext> PushGroupForLayer(gfxContext* aContext, Layer* aLayer,
                                                  const nsIntRegion& aRegion,
                                                  bool* aNeedsClipToVisibleRegion);
   already_AddRefed<gfxContext> PushGroupWithCachedSurface(gfxContext *aTarget,
@@ -266,16 +266,20 @@ public:
 
   bool HasShadowManager() const { return ShadowLayerForwarder::HasShadowManager(); }
 
   virtual bool IsCompositingCheap();
   virtual bool HasShadowManagerInternal() const { return HasShadowManager(); }
 
   virtual void SetIsFirstPaint() MOZ_OVERRIDE;
 
+  // Drop cached resources and ask our shadow manager to do the same,
+  // if we have one.
+  virtual void ClearCachedResources(Layer* aSubtree = nullptr) MOZ_OVERRIDE;
+
   void SetRepeatTransaction() { mRepeatTransaction = true; }
 
   bool IsRepeatTransaction() { return mIsRepeatTransaction; }
 
   /**
    * Called for each iteration of a progressive tile update. Fills
    * aViewport, aScaleX and aScaleY with the current scale and viewport
    * being used to composite the layers in this manager, to determine what area
--- a/gfx/layers/basic/BasicThebesLayer.h
+++ b/gfx/layers/basic/BasicThebesLayer.h
@@ -126,16 +126,25 @@ public:
   virtual ~BasicShadowableThebesLayer();
 
   virtual void PaintThebes(gfxContext* aContext,
                            Layer* aMaskLayer,
                            LayerManager::DrawThebesLayerCallback aCallback,
                            void* aCallbackData,
                            ReadbackProcessor* aReadback);
 
+  virtual void ClearCachedResources() MOZ_OVERRIDE
+  {
+    BasicThebesLayer::ClearCachedResources();
+    DestroyBackBuffer();
+    // Don't try to read back from this, it soon may be invalid.
+    mROFrontBuffer = null_t();
+    mFrontAndBackBufferDiffer = false;
+  }
+
   virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
   {
     aAttrs = ThebesLayerAttributes(GetValidRegion());
   }
 
   virtual Layer* AsLayer() { return this; }
   virtual ShadowableLayer* AsShadowableLayer() { return this; }
 
--- a/gfx/layers/ipc/PLayers.ipdl
+++ b/gfx/layers/ipc/PLayers.ipdl
@@ -323,13 +323,17 @@ parent:
   // for a particular document.
   sync Update(Edit[] cset, TargetConfig targetConfig, bool isFirstPaint)
     returns (EditReply[] reply);
 
   // We don't need to send a sync transaction if
   // no transaction operate require a swap.
   async UpdateNoSwap(Edit[] cset, TargetConfig targetConfig, bool isFirstPaint);
 
+  // Drop any front buffers that might be retained on the compositor
+  // side.
+  async ClearCachedResources();
+
   async __delete__();
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/ShadowLayersParent.cpp
+++ b/gfx/layers/ipc/ShadowLayersParent.cpp
@@ -476,16 +476,28 @@ ShadowLayersParent::RecvUpdate(const Inf
   if (compositeTime > 15) {
     printf_stderr("Compositor: Layers update took %i ms (blocking gecko).\n", compositeTime);
   }
 #endif
 
   return true;
 }
 
+bool
+ShadowLayersParent::RecvClearCachedResources()
+{
+  if (mRoot) {
+    // NB: |mRoot| here is the *child* context's root.  In this parent
+    // context, it's just a subtree root.  We need to scope the clear
+    // of resources to exactly that subtree, so we specify it here.
+    mLayerManager->ClearCachedResources(mRoot);
+  }
+  return true;
+}
+
 PGrallocBufferParent*
 ShadowLayersParent::AllocPGrallocBuffer(const gfxIntSize& aSize,
                                         const gfxContentType& aContent,
                                         MaybeMagicGrallocBufferHandle* aOutHandle)
 {
 #ifdef MOZ_HAVE_SURFACEDESCRIPTORGRALLOC
   return GrallocBufferActor::Create(aSize, aContent, aOutHandle);
 #else
--- a/gfx/layers/ipc/ShadowLayersParent.h
+++ b/gfx/layers/ipc/ShadowLayersParent.h
@@ -51,16 +51,18 @@ protected:
                           const TargetConfig& targetConfig,
                           const bool& isFirstPaint,
                           EditReplyArray* reply) MOZ_OVERRIDE;
 
   virtual bool RecvUpdateNoSwap(const EditArray& cset,
                                 const TargetConfig& targetConfig,
                                 const bool& isFirstPaint) MOZ_OVERRIDE;
 
+  virtual bool RecvClearCachedResources() MOZ_OVERRIDE;
+
   virtual PGrallocBufferParent*
   AllocPGrallocBuffer(const gfxIntSize& aSize, const gfxContentType& aContent,
                       MaybeMagicGrallocBufferHandle* aOutHandle) MOZ_OVERRIDE;
   virtual bool
   DeallocPGrallocBuffer(PGrallocBufferParent* actor) MOZ_OVERRIDE;
 
   virtual PLayerParent* AllocPLayer() MOZ_OVERRIDE;
   virtual bool DeallocPLayer(PLayerParent* actor) MOZ_OVERRIDE;
--- a/gfx/layers/opengl/ContainerLayerOGL.cpp
+++ b/gfx/layers/opengl/ContainerLayerOGL.cpp
@@ -488,12 +488,13 @@ ShadowRefLayerOGL::RenderLayer(int aPrev
                                const nsIntPoint& aOffset)
 {
   ContainerRender(this, aPreviousFrameBuffer, aOffset, mOGLManager);
 }
 
 void
 ShadowRefLayerOGL::CleanupResources()
 {
+  MOZ_ASSERT(!mFirstChild);
 }
 
 } /* layers */
 } /* mozilla */
--- a/gfx/layers/opengl/LayerManagerOGL.cpp
+++ b/gfx/layers/opengl/LayerManagerOGL.cpp
@@ -295,16 +295,19 @@ LayerManagerOGL::LayerManagerOGL(nsIWidg
   : mWidget(aWidget)
   , mWidgetSize(-1, -1)
   , mSurfaceSize(aSurfaceWidth, aSurfaceHeight)
   , mBackBufferFBO(0)
   , mBackBufferTexture(0)
   , mBackBufferSize(-1, -1)
   , mHasBGRA(0)
   , mIsRenderingToEGLSurface(aIsRenderingToEGLSurface)
+#ifdef DEBUG
+  , mMaybeInvalidTree(false)
+#endif
 {
 }
 
 LayerManagerOGL::~LayerManagerOGL()
 {
   Destroy();
 }
 
@@ -589,22 +592,28 @@ LayerManagerOGL::SetClippingRegion(const
 {
   mClippingRegion = aClippingRegion;
 }
 
 void
 LayerManagerOGL::BeginTransaction()
 {
   mInTransaction = true;
+#ifdef DEBUG
+  mMaybeInvalidTree = false;
+#endif
 }
 
 void
 LayerManagerOGL::BeginTransactionWithTarget(gfxContext *aTarget)
 {
   mInTransaction = true;
+#ifdef DEBUG
+  mMaybeInvalidTree = false;
+#endif
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   MOZ_LAYERS_LOG(("[----- BeginTransaction"));
   Log();
 #endif
 
   if (mDestroyed) {
     NS_WARNING("Call on destroyed layer manager");
@@ -612,16 +621,20 @@ LayerManagerOGL::BeginTransactionWithTar
   }
 
   mTarget = aTarget;
 }
 
 bool
 LayerManagerOGL::EndEmptyTransaction(EndTransactionFlags aFlags)
 {
+  // NB: this makes the somewhat bogus assumption that pure
+  // compositing txns don't call BeginTransaction(), because that's
+  // the behavior of CompositorParent.
+  MOZ_ASSERT(!mMaybeInvalidTree);
   mInTransaction = false;
 
   if (!mRoot)
     return false;
 
   EndTransaction(nullptr, nullptr, aFlags);
   return true;
 }
@@ -734,25 +747,60 @@ LayerManagerOGL::CreateCanvasLayer()
     NS_WARNING("Call on destroyed layer manager");
     return nullptr;
   }
 
   nsRefPtr<CanvasLayer> layer = new CanvasLayerOGL(this);
   return layer.forget();
 }
 
+static LayerOGL*
+ToLayerOGL(Layer* aLayer)
+{
+  return static_cast<LayerOGL*>(aLayer->ImplData());
+}
+
+static void ClearSubtree(Layer* aLayer)
+{
+  ToLayerOGL(aLayer)->CleanupResources();
+  for (Layer* child = aLayer->GetFirstChild(); child;
+       child = child->GetNextSibling()) {
+    ClearSubtree(child);
+  }
+}
+
+void
+LayerManagerOGL::ClearCachedResources(Layer* aSubtree)
+{
+  MOZ_ASSERT(!aSubtree || aSubtree->Manager() == this);
+  Layer* subtree = aSubtree ? aSubtree : mRoot.get();
+  if (!subtree) {
+    return;
+  }
+
+  ClearSubtree(subtree);
+#ifdef DEBUG
+  // If this subtree is reachable from the root layer, then it's
+  // possibly onscreen, and the resource clear means that composites
+  // until the next received transaction may draw garbage to the
+  // framebuffer.
+  for(; subtree && subtree != mRoot; subtree = subtree->GetParent());
+  mMaybeInvalidTree = (subtree == mRoot);
+#endif  // DEBUG
+}
+
 LayerOGL*
 LayerManagerOGL::RootLayer() const
 {
   if (mDestroyed) {
     NS_WARNING("Call on destroyed layer manager");
     return nullptr;
   }
 
-  return static_cast<LayerOGL*>(mRoot->ImplData());
+  return ToLayerOGL(mRoot);
 }
 
 bool LayerManagerOGL::sDrawFPS = false;
 bool LayerManagerOGL::sFrameCounter = false;
 
 static uint16_t sFrameCount = 0;
 void
 FPSState::DrawFrameCounter(GLContext* context)
--- a/gfx/layers/opengl/LayerManagerOGL.h
+++ b/gfx/layers/opengl/LayerManagerOGL.h
@@ -140,16 +140,18 @@ public:
   virtual already_AddRefed<ShadowRefLayer> CreateShadowRefLayer();
 
   virtual LayersBackend GetBackendType() { return LAYERS_OPENGL; }
   virtual void GetBackendName(nsAString& name) { name.AssignLiteral("OpenGL"); }
 
   virtual already_AddRefed<gfxASurface>
     CreateOptimalMaskSurface(const gfxIntSize &aSize);
 
+  virtual void ClearCachedResources(Layer* aSubtree = nullptr) MOZ_OVERRIDE;
+
   /**
    * Helper methods.
    */
   void MakeCurrent(bool aForce = false) {
     if (mDestroyed) {
       NS_WARNING("Call on destroyed layer manager");
       return;
     }
@@ -459,16 +461,23 @@ private:
                                              const gfx3DMatrix& aTransform);
 
   /* Thebes layer callbacks; valid at the end of a transaciton,
    * while rendering */
   DrawThebesLayerCallback mThebesLayerCallback;
   void *mThebesLayerCallbackData;
   gfxMatrix mWorldMatrix;
   nsAutoPtr<FPSState> mFPS;
+#ifdef DEBUG
+  // NB: only interesting when this is a purely compositing layer
+  // manager.  True after possibly onscreen layers have had their
+  // cached resources cleared outside of a transaction, and before the
+  // next forwarded transaction that re-validates their buffers.
+  bool mMaybeInvalidTree;
+#endif
 
   static bool sDrawFPS;
   static bool sFrameCounter;
 };
 
 /**
  * General information and tree management for OGL layers.
  */
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -9065,16 +9065,48 @@ PresShell::SetIsActive(bool aIsActive)
 #ifdef ACCESSIBILITY
   if (aIsActive) {
     nsAccessibilityService* accService = AccService();
     if (accService) {
       accService->PresShellActivated(this);
     }
   }
 #endif
+
+  // We have this odd special case here because remote content behaves
+  // differently from same-process content when "hidden".  In
+  // desktop-type "browser UIs", hidden "tabs" have documents that are
+  // part of the chrome tree.  When the tabs are hidden, their content
+  // is no longer part of the visible document tree, and the layers
+  // for the content are naturally released.
+  //
+  // Remote content is its own top-level tree in its subprocess.  When
+  // it's "hidden", there's no transaction in which the document
+  // thinks it's not visible, so layers can be retained forever.  This
+  // is problematic when those layers uselessly hold on to precious
+  // resources like directly texturable memory.
+  //
+  // PresShell::SetIsActive() is the first C++ entry point at which we
+  // (i) know that our parent process wants our content to be hidden;
+  // and (ii) has easy access to the TabChild.  So we use this
+  // notification to signal the TabChild to drop its layer tree and
+  // stop trying to repaint.
+  if (TabChild* tab = GetTabChildFrom(this)) {
+    if (aIsActive) {
+      tab->MakeVisible();
+      if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
+        FrameLayerBuilder::InvalidateAllLayersForFrame(
+          nsLayoutUtils::GetDisplayRootFrame(root));
+        root->SchedulePaint();
+      }
+    } else {
+      tab->MakeHidden();
+    }
+  }
+
   return rv;
 }
 
 /*
  * Determines the current image locking state. Called when one of the
  * dependent factors changes.
  */
 nsresult
--- a/widget/xpwidgets/PuppetWidget.cpp
+++ b/widget/xpwidgets/PuppetWidget.cpp
@@ -157,18 +157,27 @@ NS_IMETHODIMP
 PuppetWidget::Show(bool aState)
 {
   NS_ASSERTION(mEnabled,
                "does it make sense to Show()/Hide() a disabled widget?");
 
   bool wasVisible = mVisible;
   mVisible = aState;
 
+  if (mChild) {
+    mChild->mVisible = aState;
+  }
+
+  if (!mVisible && mLayerManager) {
+    mLayerManager->ClearCachedResources();
+  }
+
   if (!wasVisible && mVisible) {
     Resize(mBounds.width, mBounds.height, false);
+    Invalidate(mBounds);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PuppetWidget::Resize(int32_t aWidth,
                      int32_t aHeight,
@@ -543,16 +552,22 @@ NS_IMETHODIMP
 PuppetWidget::PaintTask::Run()
 {
   if (mWidget) {
     mWidget->Paint();
   }
   return NS_OK;
 }
 
+bool
+PuppetWidget::NeedsPaint()
+{
+  return mVisible;
+}
+
 float
 PuppetWidget::GetDPI()
 {
   if (mDPI < 0) {
     NS_ABORT_IF_FALSE(mTabChild, "Need TabChild to get the DPI from!");
     mTabChild->GetDPI(&mDPI);
   }
 
--- a/widget/xpwidgets/PuppetWidget.h
+++ b/widget/xpwidgets/PuppetWidget.h
@@ -167,16 +167,18 @@ public:
   }
 
   // Gets the DPI of the screen corresponding to this widget.
   // Contacts the parent process which gets the DPI from the
   // proper widget there. TODO: Handle DPI changes that happen
   // later on.
   virtual float GetDPI();
 
+  virtual bool NeedsPaint() MOZ_OVERRIDE;
+
   virtual TabChild* GetOwningTabChild() MOZ_OVERRIDE { return mTabChild; }
 
 private:
   nsresult Paint();
 
   void SetChild(PuppetWidget* aChild);
 
   nsresult IMEEndComposition(bool aCancel);