Bug 781053 - Part 2 - Trigger empty transactions when an animated image in an ImageLayer changes frame. r=roc
authorMatt Woodrow <mwoodrow@mozilla.com>
Tue, 25 Sep 2012 08:31:30 +1200
changeset 108403 213b15f0c3c332809f523304dff530da44a67403
parent 108402 b12bf692dc2d37acca3100faed7bd169a727169b
child 108404 cbcc52822df2df1164a1ddd457f7d4b4a7748d9f
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersroc
bugs781053
milestone18.0a1
Bug 781053 - Part 2 - Trigger empty transactions when an animated image in an ImageLayer changes frame. r=roc
image/src/RasterImage.cpp
image/src/RasterImage.h
layout/base/FrameLayerBuilder.cpp
layout/base/FrameLayerBuilder.h
layout/generic/nsImageFrame.cpp
layout/xul/base/src/nsImageBoxFrame.cpp
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -166,17 +166,18 @@ RasterImage::RasterImage(imgStatusTracke
   mDecodeOnDraw(false),
   mMultipart(false),
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
   mHasBeenDecoded(false),
   mInDecoder(false),
   mAnimationFinished(false),
-  mFinishing(false)
+  mFinishing(false),
+  mInUpdateImageContainer(false)
 {
   // Set up the discard tracker node.
   mDiscardTrackerNode.img = this;
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 
   // Statistics
   num_containers++;
 
@@ -287,17 +288,16 @@ RasterImage::AdvanceFrame(TimeStamp aTim
 {
   NS_ASSERTION(aTime <= TimeStamp::Now(),
                "Given time appears to be in the future");
 
   imgFrame* nextFrame = nullptr;
   uint32_t currentFrameIndex = mAnim->currentAnimationFrameIndex;
   uint32_t nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
   uint32_t timeout = 0;
-  mImageContainer = nullptr;
 
   // Figure out if we have the next full frame. This is more complicated than
   // just checking for mFrames.Length() because decoders append their frames
   // before they're filled in.
   NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
                     "How did we get 2 indices too far by incrementing?");
 
   // If we don't have a decoder, we know we've got everything we're going to
@@ -434,16 +434,17 @@ RasterImage::RequestRefresh(const mozill
 
     // Notify listeners that our frame has actually changed, but do this only
     // once for all frames that we've now passed (if AdvanceFrame() was called
     // more than once).
     #ifdef DEBUG
       mFramesNotified++;
     #endif
 
+    UpdateImageContainer();
     observer->FrameChanged(nullptr, this, &dirtyRect);
   }
 }
 
 //******************************************************************************
 /* [noscript] imgIContainer extractFrame(uint32_t aWhichFrame,
  *                                       [const] in nsIntRect aRegion,
  *                                       in uint32_t aFlags); */
@@ -857,51 +858,78 @@ RasterImage::GetFrame(uint32_t aWhichFra
     framesurf = imgsurf;
   }
 
   *_retval = framesurf.forget().get();
 
   return rv;
 }
 
+already_AddRefed<layers::Image>
+RasterImage::GetCurrentImage()
+{
+  nsRefPtr<gfxASurface> imageSurface;
+  nsresult rv = GetFrame(FRAME_CURRENT, FLAG_SYNC_DECODE, getter_AddRefs(imageSurface));
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  CairoImage::Data cairoData;
+  cairoData.mSurface = imageSurface;
+  GetWidth(&cairoData.mSize.width);
+  GetHeight(&cairoData.mSize.height);
+
+  ImageFormat cairoFormat = CAIRO_SURFACE;
+  nsRefPtr<layers::Image> image = mImageContainer->CreateImage(&cairoFormat, 1);
+  NS_ASSERTION(image, "Failed to create Image");
+  
+  NS_ASSERTION(image->GetFormat() == cairoFormat, "Wrong format");
+  static_cast<CairoImage*>(image.get())->SetData(cairoData);
+
+  return image.forget();
+}
+
 
 NS_IMETHODIMP
 RasterImage::GetImageContainer(ImageContainer **_retval)
 {
   if (mImageContainer) {
     *_retval = mImageContainer;
     NS_ADDREF(*_retval);
     return NS_OK;
   }
   
-  CairoImage::Data cairoData;
-  nsRefPtr<gfxASurface> imageSurface;
-  nsresult rv = GetFrame(FRAME_CURRENT, FLAG_SYNC_DECODE, getter_AddRefs(imageSurface));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  cairoData.mSurface = imageSurface;
-  GetWidth(&cairoData.mSize.width);
-  GetHeight(&cairoData.mSize.height);
-
   mImageContainer = LayerManager::CreateImageContainer();
   
-  // Now create a CairoImage to display the surface.
-  ImageFormat cairoFormat = CAIRO_SURFACE;
-  nsRefPtr<layers::Image> image = mImageContainer->CreateImage(&cairoFormat, 1);
-  NS_ASSERTION(image, "Failed to create Image");
-
-  NS_ASSERTION(image->GetFormat() == cairoFormat, "Wrong format");
-  static_cast<CairoImage*>(image.get())->SetData(cairoData);
+  nsRefPtr<layers::Image> image = GetCurrentImage();
+  if (!image) {
+    return NS_ERROR_FAILURE;
+  }
   mImageContainer->SetCurrentImageInTransaction(image);
 
   *_retval = mImageContainer;
   NS_ADDREF(*_retval);
   return NS_OK;
 }
 
+void
+RasterImage::UpdateImageContainer()
+{
+  if (!mImageContainer || IsInUpdateImageContainer()) {
+    return;
+  }
+
+  SetInUpdateImageContainer(true);
+
+  nsRefPtr<layers::Image> image = GetCurrentImage();
+  if (!image) {
+    return;
+  }
+  mImageContainer->SetCurrentImage(image);
+  SetInUpdateImageContainer(false);
+}
+
 size_t
 RasterImage::HeapSizeOfSourceWithComputedFallback(nsMallocSizeOfFun aMallocSizeOf) const
 {
   // n == 0 is possible for two reasons. 
   // - This is a zero-length image.
   // - We're on a platform where moz_malloc_size_of always returns 0.
   // In either case the fallback works appropriately.
   size_t n = mSourceData.SizeOfExcludingThis(aMallocSizeOf);
@@ -1180,18 +1208,21 @@ void
 RasterImage::FrameUpdated(uint32_t aFrameNum, nsIntRect &aUpdatedRect)
 {
   NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
 
   imgFrame *frame = GetImgFrameNoDecode(aFrameNum);
   NS_ABORT_IF_FALSE(frame, "Calling FrameUpdated on frame that doesn't exist!");
 
   frame->ImageUpdated(aUpdatedRect);
-  // The image has changed, so we need to invalidate our cached ImageContainer.
-  mImageContainer = NULL;
+    
+  if (aFrameNum == GetCurrentImgFrameIndex()) {
+    // The image has changed, so we need to invalidate our cached ImageContainer.
+    UpdateImageContainer();
+  }
 }
 
 nsresult
 RasterImage::SetFrameDisposalMethod(uint32_t aFrameNum,
                                     int32_t aDisposalMethod)
 {
   if (mError)
     return NS_ERROR_FAILURE;
@@ -1375,17 +1406,17 @@ RasterImage::ResetAnimation()
 
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
   mAnim->lastCompositedFrameIndex = -1;
   mAnim->currentAnimationFrameIndex = 0;
-  mImageContainer = nullptr;
+  UpdateImageContainer();
 
   // Note - We probably want to kick off a redecode somewhere around here when
   // we fix bug 500402.
 
   // Update display if we were animating before
   nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
   if (mAnimating && observer)
     observer->FrameChanged(nullptr, this, &(mAnim->firstFrameRefreshArea));
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -122,16 +122,17 @@ class nsIInputStream;
  * because the first two have public setters and the observer we only get
  * in Init().
  */
 
 namespace mozilla {
 namespace layers {
 class LayerManager;
 class ImageContainer;
+class Image;
 }
 namespace image {
 
 class Decoder;
 
 class RasterImage : public Image
                   , public nsIProperties
                   , public nsSupportsWeakReference
@@ -569,16 +570,22 @@ private:
                                   uint32_t **paletteData, uint32_t *paletteLength);
   nsresult InternalAddFrame(uint32_t framenum, int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight,
                             gfxASurface::gfxImageFormat aFormat, uint8_t aPaletteDepth,
                             uint8_t **imageData, uint32_t *imageLength,
                             uint32_t **paletteData, uint32_t *paletteLength);
 
   bool ApplyDecodeFlags(uint32_t aNewFlags);
 
+  already_AddRefed<layers::Image> GetCurrentImage();
+  void UpdateImageContainer();
+
+  void SetInUpdateImageContainer(bool aInUpdate) { mInUpdateImageContainer = aInUpdate; }
+  bool IsInUpdateImageContainer() { return mInUpdateImageContainer; }
+
 private: // data
 
   nsIntSize                  mSize;
 
   // Whether mFrames below were decoded using any special flags.
   // Some flags (e.g. unpremultiplied data) may not be compatible
   // with the browser's needs for displaying the image to the user.
   // As such, we may need to redecode if we're being asked for
@@ -649,16 +656,18 @@ private: // data
 
   // Whether the animation can stop, due to running out
   // of frames, or no more owning request
   bool                       mAnimationFinished:1;
 
   // Whether we're calling Decoder::Finish() from ShutdownDecoder.
   bool                       mFinishing:1;
 
+  bool                       mInUpdateImageContainer:1;
+
   // Decoding
   nsresult WantDecodedFrames();
   nsresult SyncDecode();
   nsresult InitDecoder(bool aDoSizeDecode);
   nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount);
   nsresult DecodeSomeData(uint32_t aMaxBytes);
   bool     IsDecodeFinished();
   TimeStamp mDrawStartTime;
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -870,16 +870,35 @@ FrameLayerBuilder::DidBeginRetainedLayer
     mInvalidateAllLayers = data->mInvalidateAllLayers;
   } else {
     data = new LayerManagerData();
     aManager->SetUserData(&gLayerManagerUserData, data);
   }
 }
 
 void
+FrameLayerBuilder::StoreOptimizedLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey, Layer* aImage)
+{
+  DisplayItemDataEntry *entry = mNewDisplayItemData.GetEntry(aFrame);
+  if (!entry)
+    return;
+
+  nsTArray<nsRefPtr<DisplayItemData> > *array = &entry->mData;
+  if (!array)
+    return;
+
+  for (uint32_t i = 0; i < array->Length(); ++i) {
+    if (array->ElementAt(i)->mDisplayItemKey == aDisplayItemKey) {
+      array->ElementAt(i)->mOptLayer = aImage;
+      return;
+    }
+  }
+}
+
+void
 FrameLayerBuilder::DidEndTransaction()
 {
   GetMaskLayerImageCache()->Sweep();
 }
 
 void
 FrameLayerBuilder::WillEndTransaction()
 {
@@ -1605,16 +1624,19 @@ ContainerState::PopThebesLayerData()
       imageLayer->SetPostScale(mParameters.mXScale,
                                mParameters.mYScale);
       if (data->mItemClip.mHaveClipRect) {
         nsIntRect clip = ScaleToNearestPixels(data->mItemClip.mClipRect);
         clip.MoveBy(mParameters.mOffset);
         imageLayer->IntersectClipRect(clip);
       }
       layer = imageLayer;
+      mLayerBuilder->StoreOptimizedLayerForFrame(data->mImage->GetUnderlyingFrame(), 
+                                                 data->mImage->GetPerFrameKey(),
+                                                 imageLayer);
     } else {
       nsRefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer(data->mLayer);
       colorLayer->SetIsFixedPosition(data->mLayer->GetIsFixedPosition());
       colorLayer->SetColor(data->mSolidColor);
 
       // Copy transform
       colorLayer->SetBaseTransform(data->mLayer->GetBaseTransform());
       colorLayer->SetPostScale(data->mLayer->GetPostXScale(), data->mLayer->GetPostYScale());
@@ -2986,16 +3008,20 @@ FrameLayerBuilder::GetDedicatedLayer(nsI
   // in the normal widget manager, and as a different layer (or no layer)
   // in the secondary manager
 
   DisplayItemData *data = GetDisplayItemDataForManager(aFrame, aDisplayItemKey);
   if (!data) {
     return nullptr;
   }
 
+  if (data->mOptLayer) {
+    return data->mOptLayer;
+  }
+
   Layer* layer = data->mLayer;
   if (!layer->HasUserData(&gColorLayerUserData) &&
       !layer->HasUserData(&gImageLayerUserData) &&
       !layer->HasUserData(&gThebesDisplayItemLayerUserData)) {
     return layer;
   }
   return nullptr;
 }
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -8,16 +8,17 @@
 
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 #include "nsRegion.h"
 #include "nsIFrame.h"
 #include "nsDisplayListInvalidation.h"
 #include "LayerTreeInvalidation.h"
+#include "ImageLayers.h"
 
 class nsDisplayListBuilder;
 class nsDisplayList;
 class nsDisplayItem;
 class gfxContext;
 class nsRootPresContext;
 
 namespace mozilla {
@@ -95,16 +96,17 @@ public:
  * integer types (nsIntPoint/nsIntSize/nsIntRect/nsIntRegion) are all in layer
  * coordinates, post-scaling, whereas appunit types are all pre-scaling.
  */
 class FrameLayerBuilder : public layers::LayerUserData {
 public:
   typedef layers::ContainerLayer ContainerLayer;
   typedef layers::Layer Layer;
   typedef layers::ThebesLayer ThebesLayer;
+  typedef layers::ImageLayer ImageLayer;
   typedef layers::LayerManager LayerManager;
 
   FrameLayerBuilder() :
     mRetainingManager(nullptr),
     mDetectedDOMModification(false),
     mInvalidateAllLayers(false),
     mContainerLayerGeneration(0),
     mMaxContainerLayerGeneration(0)
@@ -406,16 +408,24 @@ public:
    * assuming they are being painted to retained layers. This takes into account
    * the resolution the contents of the ContainerLayer containing aFrame are
    * being rendered at, as well as any currently-inactive transforms between
    * aFrame and that container layer.
    */
   static gfxSize GetThebesLayerScaleForFrame(nsIFrame* aFrame);
 
   /**
+   * Stores a Layer as the dedicated layer in the DisplayItemData for a given frame/key pair.
+   *
+   * Used when we optimize a ThebesLayer into an ImageLayer and want to retroactively update the 
+   * DisplayItemData so we can retrieve the layer from within layout.
+   */
+  void StoreOptimizedLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey, Layer* aImage);
+
+  /**
    * Clip represents the intersection of an optional rectangle with a
    * list of rounded rectangles.
    */
   struct Clip {
     struct RoundedRect {
       nsRect mRect;
       // Indices into mRadii are the NS_CORNER_* constants in nsStyleConsts.h
       nscoord mRadii[8];
@@ -529,16 +539,17 @@ protected:
     void AddFrame(nsIFrame* aFrame)
     {
       mFrameList.AppendElement(aFrame);
     }
 
     bool FrameListMatches(nsDisplayItem* aOther);
 
     nsRefPtr<Layer> mLayer;
+    nsRefPtr<Layer> mOptLayer;
     nsRefPtr<LayerManager> mInactiveManager;
     nsAutoTArray<nsIFrame*, 2> mFrameList;
     nsAutoPtr<nsDisplayItemGeometry> mGeometry;
     Clip            mClip;
     uint32_t        mDisplayItemKey;
     uint32_t        mContainerLayerGeneration;
     LayerState      mLayerState;
 
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -674,21 +674,17 @@ nsImageFrame::FrameChanged(imgIRequest *
     return NS_OK;
   }
 
   if (IsPendingLoad(aContainer)) {
     // We don't care about it
     return NS_OK;
   }
 
-  if (aDirtyRect->IsEqualInterior(nsIntRect::GetMaxSizedIntRect())) {
-    InvalidateFrame();
-  } else {
-    InvalidateFrameWithRect(SourceRectToDest(*aDirtyRect));
-  }
+  InvalidateLayer(nsDisplayItem::TYPE_IMAGE);
   return NS_OK;
 }
 
 void
 nsImageFrame::EnsureIntrinsicSizeAndRatio(nsPresContext* aPresContext)
 {
   // if mIntrinsicSize.width and height are 0, then we should
   // check to see if the size is already known by the image container.
--- a/layout/xul/base/src/nsImageBoxFrame.cpp
+++ b/layout/xul/base/src/nsImageBoxFrame.cpp
@@ -633,19 +633,18 @@ NS_IMETHODIMP nsImageBoxFrame::OnImageIs
 
 NS_IMETHODIMP nsImageBoxFrame::FrameChanged(imgIRequest *aRequest,
                                             imgIContainer *aContainer,
                                             const nsIntRect *aDirtyRect)
 {
   if ((0 == mRect.width) || (0 == mRect.height)) {
     return NS_OK;
   }
-  
-  nsBoxLayoutState state(PresContext());
-  this->Redraw(state);
+ 
+  InvalidateLayer(nsDisplayItem::TYPE_XUL_IMAGE);
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS2(nsImageBoxListener, imgIDecoderObserver, imgIContainerObserver)
 
 nsImageBoxListener::nsImageBoxListener()
 {