Bug 1290293 - Part 2e. Make SurfacePipe users clear unwritten pixels if the image is truncated. r=tnikkel
☠☠ backed out by 6098f45a8745 ☠ ☠
authorAndrew Osmond <aosmond@mozilla.com>
Thu, 18 Aug 2016 09:55:45 -0400
changeset 389340 8401d12fe93637f11fe8acffba79c16ee192183e
parent 389339 d87488b69c186a55bec1f021b6c02a5f663c8c4b
child 389341 90a284ea19e34c8d26d3d95fe69f2352162f511c
push id7198
push userjlorenzo@mozilla.com
push dateTue, 18 Apr 2017 12:07:49 +0000
treeherdermozilla-beta@d57aa49c3948 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs1290293
milestone54.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 1290293 - Part 2e. Make SurfacePipe users clear unwritten pixels if the image is truncated. r=tnikkel
image/DownscalingFilter.h
image/SurfaceFilters.h
image/SurfacePipe.cpp
image/SurfacePipe.h
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsIconDecoder.cpp
image/decoders/nsIconDecoder.h
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/imgFrame.cpp
image/imgFrame.h
--- a/image/DownscalingFilter.h
+++ b/image/DownscalingFilter.h
@@ -67,24 +67,26 @@ struct DownscalingConfig
  * should avoid this by ensuring that they do not request downscaling in
  * non-Skia builds.
  */
 template <typename Next>
 class DownscalingFilter final : public SurfaceFilter
 {
 public:
   Maybe<SurfaceInvalidRect> TakeInvalidRect() override { return Nothing(); }
+  bool GetClearValue(uint8_t& aValue) const override { return false; }
 
   template <typename... Rest>
   nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
   {
     return NS_ERROR_FAILURE;
   }
 
 protected:
+  void DoZeroOutRestOfSurface() override { }
   uint8_t* DoResetToFirstRow() override { MOZ_CRASH(); return nullptr; }
   uint8_t* DoAdvanceRow() override { MOZ_CRASH(); return nullptr; }
 };
 
 #else
 
 /**
  * DownscalingFilter performs Lanczos downscaling, taking image input data at one size
@@ -166,17 +168,19 @@ public:
     // Allocate the buffer, which contains scanlines of the input image.
     mRowBuffer.reset(new (fallible)
                        uint8_t[PaddedWidthInBytes(mInputSize.width)]);
     if (MOZ_UNLIKELY(!mRowBuffer)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     // Clear the buffer to avoid writing uninitialized memory to the output.
-    memset(mRowBuffer.get(), 0, PaddedWidthInBytes(mInputSize.width));
+    uint8_t clear = 0;
+    GetClearValue(clear);
+    memset(mRowBuffer.get(), clear, PaddedWidthInBytes(mInputSize.width));
 
     // Allocate the window, which contains horizontally downscaled scanlines. (We
     // can store scanlines which are already downscaled because our downscaling
     // filter is separable.)
     mWindowCapacity = mYFilter->max_filter();
     mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
     if (MOZ_UNLIKELY(!mWindow)) {
       return NS_ERROR_OUT_OF_MEMORY;
@@ -209,17 +213,29 @@ public:
     if (invalidRect) {
       // Compute the input space invalid rect by scaling.
       invalidRect->mInputSpaceRect.ScaleRoundOut(mScale.width, mScale.height);
     }
 
     return invalidRect;
   }
 
+  bool GetClearValue(uint8_t& aValue) const override
+  {
+    return mNext.GetClearValue(aValue);
+  }
+
 protected:
+  void DoZeroOutRestOfSurface() override
+  {
+    mNext.ZeroOutRestOfSurface();
+    mInputRow = mInputSize.height;
+    mOutputRow = mNext.InputSize().height;
+  }
+
   uint8_t* DoResetToFirstRow() override
   {
     mNext.ResetToFirstRow();
 
     mInputRow = 0;
     mOutputRow = 0;
     mRowsInWindow = 0;
 
--- a/image/SurfaceFilters.h
+++ b/image/SurfaceFilters.h
@@ -108,33 +108,47 @@ public:
     // downscaling them), the rows may no longer exist in their original form on
     // the surface itself.
     mBuffer.reset(new (fallible) uint8_t[bufferSize]);
     if (MOZ_UNLIKELY(!mBuffer)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     // Clear the buffer to avoid writing uninitialized memory to the output.
-    memset(mBuffer.get(), 0, bufferSize);
+    uint8_t clear = 0;
+    GetClearValue(clear);
+    memset(mBuffer.get(), clear, bufferSize);
 
     ConfigureFilter(outputSize, sizeof(PixelType));
     return NS_OK;
   }
 
   bool IsValidPalettedPipe() const override
   {
     return sizeof(PixelType) == 1 && mNext.IsValidPalettedPipe();
   }
 
   Maybe<SurfaceInvalidRect> TakeInvalidRect() override
   {
     return mNext.TakeInvalidRect();
   }
 
+  bool GetClearValue(uint8_t& aValue) const override
+  {
+    return mNext.GetClearValue(aValue);
+  }
+
 protected:
+  void DoZeroOutRestOfSurface() override
+  {
+    mNext.ZeroOutRestOfSurface();
+    mInputRow = InputSize().height;
+    mPass = 4;
+  }
+
   uint8_t* DoResetToFirstRow() override
   {
     mNext.ResetToFirstRow();
     mPass = 0;
     mInputRow = 0;
     mOutputRow = InterlaceOffset(mPass);
     return GetRowPointer(mOutputRow);
   }
@@ -397,29 +411,42 @@ public:
     // all, and we'll need a buffer to give that data a place to go.
     if (mFrameRect.width < mUnclampedFrameRect.width) {
       mBuffer.reset(new (fallible) uint8_t[mUnclampedFrameRect.width *
                                            sizeof(uint32_t)]);
       if (MOZ_UNLIKELY(!mBuffer)) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
 
-      memset(mBuffer.get(), 0, mUnclampedFrameRect.width * sizeof(uint32_t));
+      uint8_t clear = 0;
+      GetClearValue(clear);
+      memset(mBuffer.get(), clear, mUnclampedFrameRect.width * sizeof(uint32_t));
     }
 
     ConfigureFilter(mUnclampedFrameRect.Size(), sizeof(uint32_t));
     return NS_OK;
   }
 
   Maybe<SurfaceInvalidRect> TakeInvalidRect() override
   {
     return mNext.TakeInvalidRect();
   }
 
+  bool GetClearValue(uint8_t& aValue) const override
+  {
+    return mNext.GetClearValue(aValue);
+  }
+
 protected:
+  void DoZeroOutRestOfSurface() override
+  {
+    mNext.ZeroOutRestOfSurface();
+    mRow = mFrameRect.YMost();
+  }
+
   uint8_t* DoResetToFirstRow() override
   {
     uint8_t* rowPtr = mNext.ResetToFirstRow();
     if (rowPtr == nullptr) {
       mRow = mFrameRect.YMost();
       return nullptr;
     }
 
@@ -463,33 +490,48 @@ protected:
       // This row is outside of the frame rect, so just drop it on the floor.
       rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
       return AdjustRowPointer(rowPtr);
     } else if (currentRow >= mFrameRect.YMost()) {
       NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect");
       return nullptr;
     }
 
-    // If we had to buffer, copy the data. Otherwise, just advance the row.
+    // If we had to buffer, copy the data.
     if (mBuffer) {
       // We write from the beginning of the buffer unless |mUnclampedFrameRect.x|
       // is negative; if that's the case, we have to skip the portion of the
       // unclamped frame rect that's outside the row.
       uint32_t* source = reinterpret_cast<uint32_t*>(mBuffer.get()) -
                          std::min(mUnclampedFrameRect.x, 0);
 
       // We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
       // already clamped these values to the size of the output, so we don't
       // have to worry about bounds checking here (though WriteBuffer() will do
       // it for us in any case).
       WriteState state = mNext.WriteBuffer(source, mFrameRect.x, mFrameRect.width);
 
       rowPtr = state == WriteState::NEED_MORE_DATA ? mBuffer.get()
                                                    : nullptr;
     } else {
+      // We need to manually clear the pixels that are not written to before we
+      // advance the row.
+      uint8_t* currentRowPtr = CurrentRowPointer();
+      if (currentRowPtr) {
+        uint8_t clear = 0;
+        if (GetClearValue(clear)) {
+          gfx::IntSize outputSize = mNext.InputSize();
+          const size_t pixelSize = sizeof(uint32_t);
+          const size_t prefixOffset = mFrameRect.x * pixelSize;
+          const size_t postfixOffset = mFrameRect.width * pixelSize;
+          const size_t postfixLength = (outputSize.width - mFrameRect.XMost()) * pixelSize;
+          memset(currentRowPtr - prefixOffset, clear, prefixOffset);
+          memset(currentRowPtr + postfixOffset, clear, postfixLength);
+        }
+      }
       rowPtr = mNext.AdvanceRow();
     }
 
     // If there's still more data coming or we're already done, just adjust the
     // pointer and return.
     if (mRow < mFrameRect.YMost() || rowPtr == nullptr) {
       return AdjustRowPointer(rowPtr);
     }
@@ -611,29 +653,43 @@ public:
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     mCurrentRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
     if (MOZ_UNLIKELY(!mCurrentRow)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    memset(mPreviousRow.get(), 0, inputWidthInBytes);
-    memset(mCurrentRow.get(), 0, inputWidthInBytes);
+    uint8_t clear = 0;
+    GetClearValue(clear);
+    memset(mPreviousRow.get(), clear, inputWidthInBytes);
+    memset(mCurrentRow.get(), clear, inputWidthInBytes);
 
     ConfigureFilter(mNext.InputSize(), sizeof(uint32_t));
     return NS_OK;
   }
 
   Maybe<SurfaceInvalidRect> TakeInvalidRect() override
   {
     return mNext.TakeInvalidRect();
   }
 
+  bool GetClearValue(uint8_t& aValue) const override
+  {
+    return mNext.GetClearValue(aValue);
+  }
+
 protected:
+  void DoZeroOutRestOfSurface() override
+  {
+    mNext.ZeroOutRestOfSurface();
+    mRow = InputSize().height;
+    mPass = 7;
+  }
+
   uint8_t* DoResetToFirstRow() override
   {
     mRow = 0;
     mPass = std::min(mPass + 1, 7);
 
     uint8_t* rowPtr = mNext.ResetToFirstRow();
     if (mPass == 7) {
       // Short circuit this filter on the final pass, since all pixels have
--- a/image/SurfacePipe.cpp
+++ b/image/SurfacePipe.cpp
@@ -57,16 +57,69 @@ AbstractSurfaceSink::TakeInvalidRect()
   invalidRect.mInputSpaceRect = invalidRect.mOutputSpaceRect = mInvalidRect;
 
   // Forget about the invalid rect we're returning.
   mInvalidRect = IntRect();
 
   return Some(invalidRect);
 }
 
+void
+AbstractSurfaceSink::DoZeroOutRestOfSurface()
+{
+  // If there is no explicit clear value, we know the surface was already
+  // zeroed out by default.
+  if (!mClearRequired) {
+    return;
+  }
+
+  // The reason we do not use the typical WritePixels methods here is
+  // because if it is a progressively decoded image, we know that we
+  // already have valid data written to the buffer, just not the final
+  // result. There is no need to clear the buffers in that case.
+  const int32_t width = InputSize().width;
+  const int32_t height = InputSize().height;
+  const int32_t pixelSize = IsValidPalettedPipe() ? sizeof(uint8_t)
+                                                  : sizeof(uint32_t);
+  const int32_t stride = width * pixelSize;
+
+  // In addition to fully unwritten rows, the current row may be partially
+  // written (i.e. mCol > 0). Since the row was not advanced, we have yet to
+  // update mWrittenRect so it would be cleared without the exception below.
+  const int32_t col = CurrentColumn();
+  if (MOZ_UNLIKELY(col > 0 && col <= width)) {
+    uint8_t* rowPtr = CurrentRowPointer();
+    MOZ_ASSERT(rowPtr);
+    memset(rowPtr + col * pixelSize, mClearValue, (width - col) * pixelSize);
+    AdvanceRow(); // updates mInvalidRect and mWrittenRect
+  }
+
+  MOZ_ASSERT(mWrittenRect.x == 0);
+  MOZ_ASSERT(mWrittenRect.width == 0 || mWrittenRect.width == width);
+
+  if (MOZ_UNLIKELY(mWrittenRect.y > 0)) {
+    const uint32_t length = mWrittenRect.y * stride;
+    auto updateRect = IntRect(0, 0, width, mWrittenRect.y);
+    MOZ_ASSERT(mImageDataLength >= length);
+    memset(mImageData, mClearValue, length);
+    mInvalidRect.UnionRect(mInvalidRect, updateRect);
+    mWrittenRect.UnionRect(mWrittenRect, updateRect);
+  }
+
+  const int32_t top = mWrittenRect.y + mWrittenRect.height;
+  if (MOZ_UNLIKELY(top < height)) {
+    const int32_t remainder = height - top;
+    auto updateRect = IntRect(0, top, width, remainder);
+    MOZ_ASSERT(mImageDataLength >= (uint32_t)(height * stride));
+    memset(mImageData + top * stride, mClearValue, remainder * stride);
+    mInvalidRect.UnionRect(mInvalidRect, updateRect);
+    mWrittenRect.UnionRect(mWrittenRect, updateRect);
+  }
+}
+
 uint8_t*
 AbstractSurfaceSink::DoResetToFirstRow()
 {
   mRow = 0;
   return GetRowPointer();
 }
 
 uint8_t*
@@ -78,16 +131,17 @@ AbstractSurfaceSink::DoAdvanceRow()
 
   // If we're vertically flipping the output, we need to flip the invalid rect. Since we're
   // dealing with an axis-aligned rect, only the y coordinate needs to change.
   int32_t invalidY = mFlipVertically
                    ? InputSize().height - (mRow + 1)
                    : mRow;
   mInvalidRect.UnionRect(mInvalidRect,
                          IntRect(0, invalidY, InputSize().width, 1));
+  mWrittenRect.UnionRect(mWrittenRect, mInvalidRect);
 
   mRow = min(uint32_t(InputSize().height), mRow + 1);
 
   return mRow < uint32_t(InputSize().height) ? GetRowPointer()
                                              : nullptr;
 }
 
 nsresult
@@ -111,16 +165,24 @@ SurfaceSink::Configure(const SurfaceConf
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mImageData = aConfig.mDecoder->mImageData;
   mImageDataLength = aConfig.mDecoder->mImageDataLength;
   mFlipVertically = aConfig.mFlipVertically;
 
+  if (aConfig.mFormat == SurfaceFormat::B8G8R8X8) {
+    mClearRequired = true;
+    mClearValue = 0xFF;
+  } else if (aConfig.mDecoder->mCurrentFrame->OnHeap()) {
+    mClearRequired = true;
+    mClearValue = 0;
+  }
+
   MOZ_ASSERT(mImageData);
   MOZ_ASSERT(mImageDataLength ==
                uint32_t(surfaceSize.width * surfaceSize.height * sizeof(uint32_t)));
 
   ConfigureFilter(surfaceSize, sizeof(uint32_t));
   return NS_OK;
 }
 
@@ -143,16 +205,17 @@ SurfaceSink::GetRowPointer() const
   return rowPtr;
 }
 
 
 nsresult
 PalettedSurfaceSink::Configure(const PalettedSurfaceConfig& aConfig)
 {
   MOZ_ASSERT(aConfig.mFormat == SurfaceFormat::B8G8R8A8);
+  MOZ_ASSERT(mClearValue == 0);
 
   // For paletted surfaces, the surface size is the size of the frame rect.
   IntSize surfaceSize = aConfig.mFrameRect.Size();
 
   // Allocate the frame.
   // XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
   // to allocate the frame directly here and get rid of Decoder::AllocateFrame
   // altogether.
@@ -164,16 +227,17 @@ PalettedSurfaceSink::Configure(const Pal
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mImageData = aConfig.mDecoder->mImageData;
   mImageDataLength = aConfig.mDecoder->mImageDataLength;
   mFlipVertically = aConfig.mFlipVertically;
   mFrameRect = aConfig.mFrameRect;
+  mClearRequired = true;
 
   MOZ_ASSERT(mImageData);
   MOZ_ASSERT(mImageDataLength ==
                uint32_t(mFrameRect.width * mFrameRect.height * sizeof(uint8_t)));
 
   ConfigureFilter(surfaceSize, sizeof(uint8_t));
   return NS_OK;
 }
--- a/image/SurfacePipe.h
+++ b/image/SurfacePipe.h
@@ -101,16 +101,27 @@ public:
     : mRowPointer(nullptr)
     , mCol(0)
     , mPixelSize(0)
   { }
 
   virtual ~SurfaceFilter() { }
 
   /**
+   * Called when there are no more pixels from the caller to write. It will
+   * ensure that any unwritten pixels are cleared with an appropriate value.
+   */
+  void ZeroOutRestOfSurface()
+  {
+    DoZeroOutRestOfSurface();
+    mCol = 0;
+    mRowPointer = nullptr;
+  }
+
+  /**
    * Reset this surface to the first row. It's legal for this filter to throw
    * away any previously written data at this point, as all rows must be written
    * to on every pass.
    *
    * @return a pointer to the buffer for the first row.
    */
   uint8_t* ResetToFirstRow()
   {
@@ -294,32 +305,38 @@ public:
     PixelType* dest = reinterpret_cast<PixelType*>(mRowPointer);
 
     // Clear the area before |aStartColumn|.
     const size_t prefixLength = std::min<size_t>(mInputSize.width, aStartColumn);
     if (MOZ_UNLIKELY(prefixLength != aStartColumn)) {
       NS_WARNING("Provided starting column is out-of-bounds in WriteBuffer");
     }
 
-    memset(dest, 0, mInputSize.width * sizeof(PixelType));
+    uint8_t clear = 0;
+    bool clearRequired = GetClearValue(clear);
+    if (clearRequired) {
+      memset(dest, clear, mInputSize.width * sizeof(PixelType));
+    }
     dest += prefixLength;
 
     // Write |aLength| pixels from |aSource| into the row, with bounds checking.
     const size_t bufferLength =
       std::min<size_t>(mInputSize.width - prefixLength, aLength);
     if (MOZ_UNLIKELY(bufferLength != aLength)) {
       NS_WARNING("Provided buffer length is out-of-bounds in WriteBuffer");
     }
 
     memcpy(dest, aSource, bufferLength * sizeof(PixelType));
     dest += bufferLength;
 
     // Clear the rest of the row.
     const size_t suffixLength = mInputSize.width - (prefixLength + bufferLength);
-    memset(dest, 0, suffixLength * sizeof(PixelType));
+    if (clearRequired) {
+      memset(dest, clear, suffixLength * sizeof(PixelType));
+    }
 
     AdvanceRow();
 
     return IsSurfaceFinished() ? WriteState::FINISHED
                                : WriteState::NEED_MORE_DATA;
   }
 
   /**
@@ -330,23 +347,28 @@ public:
    *         Otherwise, returns WriteState::NEED_MORE_DATA.
    */
   WriteState WriteEmptyRow()
   {
     if (IsSurfaceFinished()) {
       return WriteState::FINISHED;  // Already done.
     }
 
-    memset(mRowPointer, 0, mInputSize.width * mPixelSize);
+    uint8_t clear = 0;
+    if (GetClearValue(clear)) {
+      memset(mRowPointer, clear, mInputSize.width * mPixelSize);
+    }
     AdvanceRow();
 
     return IsSurfaceFinished() ? WriteState::FINISHED
                                : WriteState::NEED_MORE_DATA;
   }
 
+  virtual bool GetClearValue(uint8_t& aValue) const = 0;
+
   /**
    * Write a row to the surface by calling a lambda that uses a pointer to
    * directly write to the row. This is unsafe because SurfaceFilter can't
    * provide any bounds checking; that's up to the lambda itself. For this
    * reason, the other Write*() methods should be preferred whenever it's
    * possible to use them; WriteUnsafeComputedRow() should be used only when
    * it's absolutely necessary to avoid extra copies or other performance
    * penalties.
@@ -401,33 +423,39 @@ public:
    * @return a SurfaceInvalidRect representing the region of the surface that
    *         has been written to since the last time TakeInvalidRect() was
    *         called, or Nothing() if the region is empty (i.e. nothing has been
    *         written).
    */
   virtual Maybe<SurfaceInvalidRect> TakeInvalidRect() = 0;
 
 protected:
+  /// @return the column for the next pixel to be written in the current row.
+  int32_t CurrentColumn() const { return mCol; }
+
+  /* Called by ZeroOutRestOfSurface() to actually perform the clearing of the
+   * unwritten pixels.
+   */
+  virtual void DoZeroOutRestOfSurface() = 0;
 
   /**
    * Called by ResetToFirstRow() to actually perform the reset. It's legal to
    * throw away any previously written data at this point, as all rows must be
    * written to on every pass.
    */
   virtual uint8_t* DoResetToFirstRow() = 0;
 
   /**
    * Called by AdvanceRow() to actually advance this filter to the next row.
    *
    * @return a pointer to the buffer for the next row, or nullptr to indicate
    *         that we've finished the entire surface.
    */
   virtual uint8_t* DoAdvanceRow() = 0;
 
-
   //////////////////////////////////////////////////////////////////////////////
   // Methods For Internal Use By Subclasses
   //////////////////////////////////////////////////////////////////////////////
 
   /**
    * Called by subclasses' Configure() methods to initialize the configuration
    * of this filter. After the filter is configured, calls ResetToFirstRow().
    *
@@ -479,17 +507,17 @@ private:
         continue;
       }
 
       switch (result.template as<WriteState>()) {
         case WriteState::NEED_MORE_DATA:
           return Some(WriteState::NEED_MORE_DATA);
 
         case WriteState::FINISHED:
-          ZeroOutRestOfSurface<PixelType>();
+          ZeroOutRestOfSurface();
           return Some(WriteState::FINISHED);
 
         case WriteState::FAILURE:
           // Note that we don't need to record this anywhere, because this
           // indicates an error in aFunc, and there's nothing wrong with our
           // machinery. The caller can recover as needed and continue writing to
           // the row.
           return Some(WriteState::FAILURE);
@@ -497,22 +525,16 @@ private:
     }
 
     AdvanceRow();  // We've finished the row.
 
     return IsSurfaceFinished() ? Some(WriteState::FINISHED)
                                : Nothing();
   }
 
-  template <typename PixelType>
-  void ZeroOutRestOfSurface()
-  {
-    WritePixels<PixelType>([]{ return AsVariant(PixelType(0)); });
-  }
-
   gfx::IntSize mInputSize;  /// The size of the input this filter expects.
   uint8_t* mRowPointer;     /// Pointer to the current row or null if finished.
   int32_t mCol;             /// The current column we're writing to. (0-indexed)
   uint8_t  mPixelSize;      /// How large each pixel in the surface is, in bytes.
 };
 
 class NullSurfaceSink;
 
@@ -538,17 +560,20 @@ public:
   static NullSurfaceSink* Singleton();
 
   virtual ~NullSurfaceSink() { }
 
   nsresult Configure(const NullSurfaceConfig& aConfig);
 
   Maybe<SurfaceInvalidRect> TakeInvalidRect() override { return Nothing(); }
 
+  bool GetClearValue(uint8_t& aValue) const override { return false; }
+
 protected:
+  void DoZeroOutRestOfSurface() override { }
   uint8_t* DoResetToFirstRow() override { return nullptr; }
   uint8_t* DoAdvanceRow() override { return nullptr; }
 
 private:
   static UniquePtr<NullSurfaceSink> sSingleton;  /// The singleton instance.
 };
 
 
@@ -658,16 +683,32 @@ public:
    *
    * @see SurfaceFilter::WriteEmptyRow() for the canonical documentation.
    */
   WriteState WriteEmptyRow()
   {
     return mHead->WriteEmptyRow();
   }
 
+  /**
+   * Write all remaining unset rows to an empty row to the surface. If some pixels
+   * have already been written to this row, they'll be discarded.
+   *
+   * @see SurfaceFilter::WriteEmptyRow() for the canonical documentation.
+   */
+  void ZeroOutRestOfSurface()
+  {
+    mHead->ZeroOutRestOfSurface();
+  }
+
+  bool GetClearValue(uint8_t& aValue) const
+  {
+    return mHead->GetClearValue(aValue);
+  }
+
   /// @return true if we've finished writing to the surface.
   bool IsSurfaceFinished() const { return mHead->IsSurfaceFinished(); }
 
   /// @see SurfaceFilter::TakeInvalidRect() for the canonical documentation.
   Maybe<SurfaceInvalidRect> TakeInvalidRect() const
   {
     return mHead->TakeInvalidRect();
   }
@@ -693,31 +734,44 @@ private:
 class AbstractSurfaceSink : public SurfaceFilter
 {
 public:
   AbstractSurfaceSink()
     : mImageData(nullptr)
     , mImageDataLength(0)
     , mRow(0)
     , mFlipVertically(false)
+    , mClearRequired(false)
+    , mClearValue(0)
   { }
 
   Maybe<SurfaceInvalidRect> TakeInvalidRect() override final;
 
+  bool GetClearValue(uint8_t& aValue) const override final
+  {
+    aValue = mClearValue;
+    return mClearRequired;
+  }
+
 protected:
+  void DoZeroOutRestOfSurface() override final;
   uint8_t* DoResetToFirstRow() override final;
   uint8_t* DoAdvanceRow() override final;
   virtual uint8_t* GetRowPointer() const = 0;
 
   gfx::IntRect mInvalidRect;  /// The region of the surface that has been written
                               /// to since the last call to TakeInvalidRect().
+  gfx::IntRect mWrittenRect;  /// The region of the surface that has been written
+                              /// to at least once.
   uint8_t*  mImageData;       /// A pointer to the beginning of the surface data.
   uint32_t  mImageDataLength; /// The length of the surface data.
   uint32_t  mRow;             /// The row to which we're writing. (0-indexed)
   bool      mFlipVertically;  /// If true, write the rows from top to bottom.
+  bool      mClearRequired;   /// If true, pixels need to be cleared to given.
+  uint8_t   mClearValue;      /// Value written to cleared pixels.
 };
 
 class SurfaceSink;
 
 /// A configuration struct for SurfaceSink.
 struct SurfaceConfig
 {
   using Filter = SurfaceSink;
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -100,16 +100,17 @@ nsGIFDecoder2::~nsGIFDecoder2()
 {
   free(mGIFStruct.local_colormap);
 }
 
 nsresult
 nsGIFDecoder2::FinishInternal()
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
+  mPipe.ZeroOutRestOfSurface();
 
   // If the GIF got cut off, handle it anyway
   if (!IsMetadataDecode() && mGIFOpen) {
     if (mCurrentFrameIndex == mGIFStruct.images_decoded) {
       EndImageFrame();
     }
     PostDecodeDone(mGIFStruct.loop_count - 1);
     mGIFOpen = false;
@@ -230,16 +231,17 @@ nsGIFDecoder2::BeginImageFrame(const Int
   return NS_OK;
 }
 
 
 //******************************************************************************
 void
 nsGIFDecoder2::EndImageFrame()
 {
+  mPipe.ZeroOutRestOfSurface();
   Opacity opacity = Opacity::SOME_TRANSPARENCY;
 
   if (mGIFStruct.images_decoded == 0) {
     // We need to send invalidations for the first frame.
     FlushImageData();
 
     // The first frame was preallocated with alpha; if it wasn't transparent, we
     // should fix that. We can also mark it opaque unconditionally if we didn't
--- a/image/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -22,16 +22,23 @@ nsIconDecoder::nsIconDecoder(RasterImage
  , mBytesPerRow()   // set by ReadHeader()
 {
   // Nothing to do
 }
 
 nsIconDecoder::~nsIconDecoder()
 { }
 
+nsresult
+nsIconDecoder::FinishInternal()
+{
+  mPipe.ZeroOutRestOfSurface();
+  return NS_OK;
+}
+
 LexerResult
 nsIconDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
 
   return mLexer.Lex(aIterator, aOnResume,
                     [=](State aState, const char* aData, size_t aLength) {
     switch (aState) {
--- a/image/decoders/nsIconDecoder.h
+++ b/image/decoders/nsIconDecoder.h
@@ -32,18 +32,20 @@ class RasterImage;
 //
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsIconDecoder : public Decoder
 {
 public:
   virtual ~nsIconDecoder();
 
+protected:
   LexerResult DoDecode(SourceBufferIterator& aIterator,
                        IResumable* aOnResume) override;
+  nsresult FinishInternal() override;
 
 private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
   explicit nsIconDecoder(RasterImage* aImage);
 
   enum class State {
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -250,16 +250,17 @@ nsPNGDecoder::CreateFrame(const FrameInf
 // set timeout and frame disposal method for the current frame
 void
 nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden) {
     return;
   }
 
+  mPipe.ZeroOutRestOfSurface();
   mNumFrames++;
 
   Opacity opacity = mFormat == SurfaceFormat::B8G8R8X8
                   ? Opacity::FULLY_OPAQUE
                   : Opacity::SOME_TRANSPARENCY;
 
   PostFrameStop(opacity, mAnimInfo.mDispose,
                 FrameTimeout::FromRawMilliseconds(mAnimInfo.mTimeout),
@@ -355,16 +356,23 @@ nsPNGDecoder::InitInternal()
   png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
                               nsPNGDecoder::info_callback,
                               nsPNGDecoder::row_callback,
                               nsPNGDecoder::end_callback);
 
   return NS_OK;
 }
 
+nsresult
+nsPNGDecoder::FinishInternal()
+{
+  mPipe.ZeroOutRestOfSurface();
+  return NS_OK;
+}
+
 LexerResult
 nsPNGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
 
   return mLexer.Lex(aIterator, aOnResume,
                     [=](State aState, const char* aData, size_t aLength) {
     switch (aState) {
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -24,16 +24,17 @@ public:
 
   /// @return true if this PNG is a valid ICO resource.
   bool IsValidICO() const;
 
 protected:
   nsresult InitInternal() override;
   LexerResult DoDecode(SourceBufferIterator& aIterator,
                        IResumable* aOnResume) override;
+  nsresult FinishInternal() override;
 
   Maybe<Telemetry::ID> SpeedHistogram() const override;
 
 private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
   explicit nsPNGDecoder(RasterImage* aImage);
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -654,16 +654,23 @@ imgFrame::GetImageData() const
 {
   uint8_t* data;
   uint32_t length;
   GetImageData(&data, &length);
   return data;
 }
 
 bool
+imgFrame::OnHeap() const
+{
+  MonitorAutoLock lock(mMonitor);
+  return mRawSurface ? mRawSurface->OnHeap() : true;
+}
+
+bool
 imgFrame::GetIsPaletted() const
 {
   return mPalettedImageData != nullptr;
 }
 
 void
 imgFrame::GetPaletteData(uint32_t** aPalette, uint32_t* length) const
 {
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -324,16 +324,18 @@ public:
   uint32_t GetBytesPerPixel() const { return GetIsPaletted() ? 1 : 4; }
 
   IntSize GetImageSize() const { return mImageSize; }
   IntRect GetRect() const { return mFrameRect; }
   IntSize GetSize() const { return mFrameRect.Size(); }
   void GetImageData(uint8_t** aData, uint32_t* length) const;
   uint8_t* GetImageData() const;
 
+  bool OnHeap() const;
+
   bool GetIsPaletted() const;
   void GetPaletteData(uint32_t** aPalette, uint32_t* length) const;
   uint32_t* GetPaletteData() const;
   uint8_t GetPaletteDepth() const { return mPaletteDepth; }
 
   AnimationData GetAnimationData() const;
 
   bool GetCompositingFailed() const;