Bug 1355763 - Add gfxAlphaType for specifying opaque/premult/non-premult. - r=daoshengmu
authorJeff Gilbert <jgilbert@mozilla.com>
Wed, 12 Apr 2017 02:55:23 -0700
changeset 403458 f0e8fd36b730c776f054d462e0375081288c6ff0
parent 403457 ab34b3ca99013d693ef70fbcc3dba1d5921fdcf2
child 403459 bbd23665a2d1a5c514b4d2f1f93e3ff46aa74dcc
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaoshengmu
bugs1355763
milestone55.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 1355763 - Add gfxAlphaType for specifying opaque/premult/non-premult. - r=daoshengmu MozReview-Commit-ID: Jzr8aPYlEcO
dom/canvas/CanvasRenderingContext2D.h
dom/canvas/ImageBitmap.cpp
dom/canvas/ImageBitmap.h
dom/canvas/ImageBitmapRenderingContext.cpp
dom/canvas/ImageBitmapRenderingContext.h
dom/canvas/OffscreenCanvas.cpp
dom/canvas/OffscreenCanvas.h
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/nsICanvasRenderingContextInternal.h
dom/html/HTMLCanvasElement.cpp
dom/html/HTMLCanvasElement.h
gfx/layers/ImageContainer.h
gfx/thebes/gfxTypes.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -404,17 +404,17 @@ public:
   void SetImageSmoothingEnabled(bool aImageSmoothingEnabled) override
   {
     if (aImageSmoothingEnabled != CurrentState().imageSmoothingEnabled) {
       CurrentState().imageSmoothingEnabled = aImageSmoothingEnabled;
     }
   }
 
   void DrawWindow(nsGlobalWindow& aWindow, double aX, double aY,
-		  double aW, double aH,
+                  double aW, double aH,
                   const nsAString& aBgColor, uint32_t aFlags,
                   mozilla::ErrorResult& aError);
 
   enum RenderingMode {
     SoftwareBackendMode,
     OpenGLBackendMode,
     DefaultBackendMode
   };
@@ -446,21 +446,22 @@ public:
   NS_IMETHOD SetDimensions(int32_t aWidth, int32_t aHeight) override;
   NS_IMETHOD InitializeWithDrawTarget(nsIDocShell* aShell,
                                       NotNull<gfx::DrawTarget*> aTarget) override;
 
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const char16_t* aEncoderOptions,
                             nsIInputStream** aStream) override;
 
-  already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override
+  already_AddRefed<mozilla::gfx::SourceSurface>
+  GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType = nullptr) override
   {
     EnsureTarget();
-    if (aPremultAlpha) {
-      *aPremultAlpha = true;
+    if (aOutAlphaType) {
+      *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
     }
     return mTarget->Snapshot();
   }
 
   NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
   already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -427,23 +427,23 @@ HasRasterImage(HTMLImageElement& aImageE
       return true;
     }
   }
 
   return false;
 }
 
 ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
-                         bool aIsPremultipliedAlpha /* = true */)
+                         gfxAlphaType aAlphaType)
   : mParent(aGlobal)
   , mData(aData)
   , mSurface(nullptr)
   , mDataWrapper(new ImageUtils(mData))
   , mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height)
-  , mIsPremultipliedAlpha(aIsPremultipliedAlpha)
+  , mAlphaType(aAlphaType)
   , mIsCroppingAreaOutSideOfSourceImage(false)
 {
   MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
 }
 
 ImageBitmap::~ImageBitmap()
 {
 }
@@ -617,23 +617,20 @@ ImageBitmap::PrepareForDrawTarget(gfx::D
     target->CopySurface(mSurface, surfPortion, dest);
     mSurface = target->Snapshot();
 
     // Make mCropRect match new surface we've cropped to
     mPictureRect.MoveTo(0, 0);
   }
 
   // Pre-multiply alpha here.
-  // Apply pre-multiply alpha only if mIsPremultipliedAlpha is false.
   // Ignore this step if the source surface does not have alpha channel; this
   // kind of source surfaces might come form layers::PlanarYCbCrImage.
-  if (!mIsPremultipliedAlpha &&
-      mSurface->GetFormat() != SurfaceFormat::B8G8R8X8 &&
-      mSurface->GetFormat() != SurfaceFormat::R8G8B8X8 &&
-      mSurface->GetFormat() != SurfaceFormat::X8R8G8B8) {
+  MOZ_ASSERT(IsOpaque(mSurface->GetFormat()) == (mAlphaType == gfxAlphaType::Opaque));
+  if (mAlphaType == gfxAlphaType::NonPremult) {
     MOZ_ASSERT(mSurface->GetFormat() == SurfaceFormat::R8G8B8A8 ||
                mSurface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
                mSurface->GetFormat() == SurfaceFormat::A8R8G8B8);
 
     RefPtr<DataSourceSurface> dstSurface = mSurface->GetDataSurface();
     MOZ_ASSERT(dstSurface);
 
     RefPtr<DataSourceSurface> srcSurface;
@@ -687,33 +684,32 @@ ImageBitmap::TransferAsImage()
   return image.forget();
 }
 
 UniquePtr<ImageBitmapCloneData>
 ImageBitmap::ToCloneData() const
 {
   UniquePtr<ImageBitmapCloneData> result(new ImageBitmapCloneData());
   result->mPictureRect = mPictureRect;
-  result->mIsPremultipliedAlpha = mIsPremultipliedAlpha;
+  result->mAlphaType = mAlphaType;
   result->mIsCroppingAreaOutSideOfSourceImage = mIsCroppingAreaOutSideOfSourceImage;
   RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
   result->mSurface = surface->GetDataSurface();
   MOZ_ASSERT(result->mSurface);
 
   return Move(result);
 }
 
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateFromCloneData(nsIGlobalObject* aGlobal,
                                  ImageBitmapCloneData* aData)
 {
   RefPtr<layers::Image> data = CreateImageFromSurface(aData->mSurface);
 
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data,
-                                            aData->mIsPremultipliedAlpha);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aData->mAlphaType);
 
   // Report memory allocation.
   RegisterAllocation(aGlobal, aData->mSurface);
 
   ret->mIsCroppingAreaOutSideOfSourceImage =
     aData->mIsCroppingAreaOutSideOfSourceImage;
 
   ErrorResult rv;
@@ -921,16 +917,18 @@ ImageBitmap::CreateInternal(nsIGlobalObj
 {
   // Copy data into SourceSurface.
   dom::Uint8ClampedArray array;
   DebugOnly<bool> inited = array.Init(aImageData.GetDataObject());
   MOZ_ASSERT(inited);
 
   array.ComputeLengthAndData();
   const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8;
+  // ImageData's underlying data is not alpha-premultiplied.
+  const auto alphaType = gfxAlphaType::NonPremult;
   const uint32_t BYTES_PER_PIXEL = BytesPerPixel(FORMAT);
   const uint32_t imageWidth = aImageData.Width();
   const uint32_t imageHeight = aImageData.Height();
   const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL;
   const uint32_t dataLength = array.Length();
   const gfx::IntSize imageSize(imageWidth, imageHeight);
 
   // Check the ImageData is neutered or not.
@@ -959,18 +957,17 @@ ImageBitmap::CreateInternal(nsIGlobalObj
   }
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   // Create an ImageBimtap.
-  // ImageData's underlying data is not alpha-premultiplied.
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, false);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, alphaType);
 
   // Report memory allocation.
   RegisterAllocation(aGlobal, data);
 
   // The cropping information has been handled in the CreateImageFromRawData()
   // function.
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
@@ -1030,17 +1027,17 @@ ImageBitmap::CreateInternal(nsIGlobalObj
                             const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
 {
   if (!aImageBitmap.mData) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   RefPtr<layers::Image> data = aImageBitmap.mData;
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aImageBitmap.mIsPremultipliedAlpha);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aImageBitmap.mAlphaType);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   if (aImageBitmap.mIsCroppingAreaOutSideOfSourceImage == true) {
@@ -1494,45 +1491,45 @@ ImageBitmap::ReadStructuredClone(JSConte
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aReader);
   // aParent might be null.
 
   uint32_t picRectX_;
   uint32_t picRectY_;
   uint32_t picRectWidth_;
   uint32_t picRectHeight_;
-  uint32_t isPremultipliedAlpha_;
+  uint32_t alphaType_;
   uint32_t isCroppingAreaOutSideOfSourceImage_;
 
   if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
       !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) ||
-      !JS_ReadUint32Pair(aReader, &isPremultipliedAlpha_,
+      !JS_ReadUint32Pair(aReader, &alphaType_,
                                   &isCroppingAreaOutSideOfSourceImage_)) {
     return nullptr;
   }
 
   int32_t picRectX = BitwiseCast<int32_t>(picRectX_);
   int32_t picRectY = BitwiseCast<int32_t>(picRectY_);
   int32_t picRectWidth = BitwiseCast<int32_t>(picRectWidth_);
   int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_);
+  const auto alphaType = BitwiseCast<gfxAlphaType>(alphaType_);
 
   // Create a new ImageBitmap.
   MOZ_ASSERT(!aClonedSurfaces.IsEmpty());
   MOZ_ASSERT(aIndex < aClonedSurfaces.Length());
 
   // RefPtr<ImageBitmap> needs to go out of scope before toObjectOrNull() is
   // called because the static analysis thinks dereferencing XPCOM objects
   // can GC (because in some cases it can!), and a return statement with a
   // JSObject* type means that JSObject* is on the stack as a raw pointer
   // while destructors are running.
   JS::Rooted<JS::Value> value(aCx);
   {
     RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
-    RefPtr<ImageBitmap> imageBitmap =
-      new ImageBitmap(aParent, img, isPremultipliedAlpha_);
+    RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aParent, img, alphaType);
 
     imageBitmap->mIsCroppingAreaOutSideOfSourceImage =
       isCroppingAreaOutSideOfSourceImage_;
 
     ErrorResult error;
     imageBitmap->SetPictureRect(IntRect(picRectX, picRectY,
                                         picRectWidth, picRectHeight), error);
     if (NS_WARN_IF(error.Failed())) {
@@ -1558,26 +1555,26 @@ ImageBitmap::WriteStructuredClone(JSStru
 {
   MOZ_ASSERT(aWriter);
   MOZ_ASSERT(aImageBitmap);
 
   const uint32_t picRectX = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.x);
   const uint32_t picRectY = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.y);
   const uint32_t picRectWidth = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width);
   const uint32_t picRectHeight = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height);
-  const uint32_t isPremultipliedAlpha = aImageBitmap->mIsPremultipliedAlpha ? 1 : 0;
+  const uint32_t alphaType = BitwiseCast<uint32_t>(aImageBitmap->mAlphaType);
   const uint32_t isCroppingAreaOutSideOfSourceImage = aImageBitmap->mIsCroppingAreaOutSideOfSourceImage ? 1 : 0;
 
   // Indexing the cloned surfaces and send the index to the receiver.
   uint32_t index = aClonedSurfaces.Length();
 
   if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) ||
       NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) ||
       NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectWidth, picRectHeight)) ||
-      NS_WARN_IF(!JS_WriteUint32Pair(aWriter, isPremultipliedAlpha,
+      NS_WARN_IF(!JS_WriteUint32Pair(aWriter, alphaType,
                                               isCroppingAreaOutSideOfSourceImage))) {
     return false;
   }
 
   RefPtr<SourceSurface> surface =
     aImageBitmap->mData->GetAsSourceSurface();
   RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
   RefPtr<DataSourceSurface> dstDataSurface;
@@ -2130,17 +2127,18 @@ ImageBitmap::Create(nsIGlobalObject* aGl
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return promise.forget();
   }
 
   // Create an ImageBimtap.
   // Assume the data from an external buffer is not alpha-premultiplied.
-  RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data, false);
+  RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data,
+                                                    gfxAlphaType::NonPremult);
 
   // Report memory allocation.
   RegisterAllocation(aGlobal, data);
 
   // We don't need to call SetPictureRect() here because there is no cropping
   // supported and the ImageBitmap's mPictureRect is the size of the source
   // image in default
 
--- a/dom/canvas/ImageBitmap.h
+++ b/dom/canvas/ImageBitmap.h
@@ -58,17 +58,17 @@ class ImageUtils;
 template<typename T> class MapDataIntoBufferSource;
 class Promise;
 class PostMessageEvent; // For StructuredClone between windows.
 
 struct ImageBitmapCloneData final
 {
   RefPtr<gfx::DataSourceSurface> mSurface;
   gfx::IntRect mPictureRect;
-  bool mIsPremultipliedAlpha;
+  gfxAlphaType mAlphaType;
   bool mIsCroppingAreaOutSideOfSourceImage;
 };
 
 /*
  * ImageBitmap is an opaque handler to several kinds of image-like objects from
  * HTMLImageElement, HTMLVideoElement, HTMLCanvasElement, ImageData to
  * CanvasRenderingContext2D and Image Blob.
  *
@@ -195,17 +195,17 @@ protected:
    *    re-decoding if the original decoded data is alpha-premultiplied) and
    * 2) while decoding a blob. But we do not do it in both code path too.
    *
    * ImageData's underlying data is triggered as non-premultipliedAlpha, so set
    * the aIsPremultipliedAlpha to be false in the
    * CreateInternal(from ImageData) method.
    */
   ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
-              bool aIsPremultipliedAlpha = true);
+              gfxAlphaType aAlphaType = gfxAlphaType::Premult);
 
   virtual ~ImageBitmap();
 
   void SetPictureRect(const gfx::IntRect& aRect, ErrorResult& aRv);
 
   void SetIsCroppingAreaOutSideOfSourceImage(const gfx::IntSize& aSourceSize,
                                              const Maybe<gfx::IntRect>& aCroppingRect);
 
@@ -267,17 +267,17 @@ protected:
    * Note that if the CreateInternal() copies and crops data from the source
    * image, then this mPictureRect is just the size of the final mData.
    *
    * The mPictureRect will be used at PrepareForDrawTarget() while user is going
    * to draw this ImageBitmap into a HTMLCanvasElement.
    */
   gfx::IntRect mPictureRect;
 
-  const bool mIsPremultipliedAlpha;
+  const gfxAlphaType mAlphaType;
 
   /*
    * Set mIsCroppingAreaOutSideOfSourceImage if image bitmap was cropped to the
    * source rectangle so that it contains any transparent black pixels (cropping
    * area is outside of the source image).
    * This is used in mapDataInto() to check if we should reject promise with
    * IndexSizeError.
    */
--- a/dom/canvas/ImageBitmapRenderingContext.cpp
+++ b/dom/canvas/ImageBitmapRenderingContext.cpp
@@ -177,24 +177,24 @@ ImageBitmapRenderingContext::GetInputStr
     return NS_ERROR_FAILURE;
   }
 
   return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(), format,
                                       encoder, aEncoderOptions, aStream);
 }
 
 already_AddRefed<mozilla::gfx::SourceSurface>
-ImageBitmapRenderingContext::GetSurfaceSnapshot(bool* aPremultAlpha)
+ImageBitmapRenderingContext::GetSurfaceSnapshot(gfxAlphaType* const aOutAlphaType)
 {
   if (!mImage) {
     return nullptr;
   }
 
-  if (aPremultAlpha) {
-    *aPremultAlpha = true;
+  if (aOutAlphaType) {
+    *aOutAlphaType = (GetIsOpaque() ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
   }
 
   RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
   if (surface->GetSize() != IntSize(mWidth, mHeight)) {
     return MatchWithIntrinsicSize();
   }
 
   return surface.forget();
--- a/dom/canvas/ImageBitmapRenderingContext.h
+++ b/dom/canvas/ImageBitmapRenderingContext.h
@@ -59,17 +59,17 @@ public:
                                       NotNull<gfx::DrawTarget*> aTarget) override;
 
   virtual mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const char16_t* aEncoderOptions,
                             nsIInputStream** aStream) override;
 
   virtual already_AddRefed<mozilla::gfx::SourceSurface>
-  GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override;
+  GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType) override;
 
   NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
   virtual bool GetIsOpaque() override;
   NS_IMETHOD Reset() override;
   virtual already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                  Layer* aOldLayer,
                                                  LayerManager* aManager,
                                                  bool aMirror = false) override;
--- a/dom/canvas/OffscreenCanvas.cpp
+++ b/dom/canvas/OffscreenCanvas.cpp
@@ -297,23 +297,23 @@ OffscreenCanvas::ToBlob(JSContext* aCx,
 
   CanvasRenderingContextHelper::ToBlob(aCx, global,
                                        callback, aType, aParams, aRv);
 
   return promise.forget();
 }
 
 already_AddRefed<gfx::SourceSurface>
-OffscreenCanvas::GetSurfaceSnapshot(bool* aPremultAlpha)
+OffscreenCanvas::GetSurfaceSnapshot(gfxAlphaType* const aOutAlphaType)
 {
   if (!mCurrentContext) {
     return nullptr;
   }
 
-  return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
+  return mCurrentContext->GetSurfaceSnapshot(aOutAlphaType);
 }
 
 nsCOMPtr<nsIGlobalObject>
 OffscreenCanvas::GetGlobalObject()
 {
   if (NS_IsMainThread()) {
     return GetParentObject();
   }
--- a/dom/canvas/OffscreenCanvas.h
+++ b/dom/canvas/OffscreenCanvas.h
@@ -119,17 +119,17 @@ public:
          JS::Handle<JS::Value> aParams,
          ErrorResult& aRv);
 
   nsICanvasRenderingContextInternal* GetContext() const
   {
     return mCurrentContext;
   }
 
-  already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
+  already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType = nullptr);
 
   static already_AddRefed<OffscreenCanvas>
   CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData);
 
   static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
 
   // Return true on main-thread, and return gfx.offscreencanvas.enabled
   // on worker thread.
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -265,31 +265,31 @@ ZeroOn2D(TexImageTarget target, uint32_t
 static uint32_t
 FallbackOnZero(uint32_t val, uint32_t fallback)
 {
     return (val ? val : fallback);
 }
 
 TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
                              uint32_t rowLength, uint32_t width, uint32_t height,
-                             uint32_t depth, bool srcIsPremult)
+                             uint32_t depth, gfxAlphaType srcAlphaType)
     : mAlignment(webgl->mPixelStore_UnpackAlignment)
     , mRowLength(rowLength)
     , mImageHeight(FallbackOnZero(ZeroOn2D(target, webgl->mPixelStore_UnpackImageHeight),
                                   height))
 
     , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
     , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
     , mSkipImages(ZeroOn2D(target, webgl->mPixelStore_UnpackSkipImages))
 
     , mWidth(width)
     , mHeight(height)
     , mDepth(depth)
 
-    , mSrcIsPremult(srcIsPremult)
+    , mSrcAlphaType(srcAlphaType)
 
     , mNeedsExactUpload(false)
 {
     MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
 }
 
 static bool
 HasColorAndAlpha(const WebGLTexelFormat format)
@@ -322,26 +322,37 @@ TexUnpackBlob::ConvertIfNeeded(WebGLCont
     MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
     MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
 
     *out_begin = srcBegin;
 
     if (!rowLength || !rowCount)
         return true;
 
+    const auto srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
     const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
+    const auto fnHasPremultMismatch = [&]() {
+        if (mSrcAlphaType == gfxAlphaType::Opaque)
+            return false;
+
+        if (!HasColorAndAlpha(srcFormat))
+            return false;
+
+        return srcIsPremult != dstIsPremult;
+    };
+
     const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
                                                      : gl::OriginPos::BottomLeft);
     const auto dstOrigin = gl::OriginPos::BottomLeft;
 
     if (srcFormat != dstFormat) {
         webgl->GeneratePerfWarning("%s: Conversion requires pixel reformatting. (%u->%u)",
                                    funcName, uint32_t(srcFormat),
                                    uint32_t(dstFormat));
-    } else if (mSrcIsPremult != dstIsPremult && HasColorAndAlpha(srcFormat)) {
+    } else if (fnHasPremultMismatch()) {
         webgl->GeneratePerfWarning("%s: Conversion requires change in"
                                    " alpha-premultiplication.",
                                    funcName);
     } else if (srcOrigin != dstOrigin) {
         webgl->GeneratePerfWarning("%s: Conversion requires y-flip.", funcName);
     } else if (srcStride != dstStride) {
         webgl->GeneratePerfWarning("%s: Conversion requires change in stride. (%u->%u)",
                                    funcName, uint32_t(srcStride), uint32_t(dstStride));
@@ -364,17 +375,17 @@ TexUnpackBlob::ConvertIfNeeded(WebGLCont
     }
     const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
 
     ////
 
     // And go!:
     bool wasTrivial;
     if (!ConvertImage(rowLength, rowCount,
-                      srcBegin, srcStride, srcOrigin, srcFormat, mSrcIsPremult,
+                      srcBegin, srcStride, srcOrigin, srcFormat, srcIsPremult,
                       dstBegin, dstStride, dstOrigin, dstFormat, dstIsPremult,
                       &wasTrivial))
     {
         webgl->ErrorImplementationBug("%s: ConvertImage failed.", funcName);
         return false;
     }
 
     *out_begin = dstBegin;
@@ -398,17 +409,17 @@ DoTexOrSubImage(bool isSubImage, gl::GLC
 //////////////////////////////////////////////////////////////////////////////////////////
 // TexUnpackBytes
 
 TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
                                uint32_t width, uint32_t height, uint32_t depth,
                                bool isClientData, const uint8_t* ptr, size_t availBytes)
     : TexUnpackBlob(webgl, target,
                     FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
-                    width, height, depth, false)
+                    width, height, depth, gfxAlphaType::NonPremult)
     , mIsClientData(isClientData)
     , mPtr(ptr)
     , mAvailBytes(availBytes)
 { }
 
 bool
 TexUnpackBytes::Validate(WebGLContext* webgl, const char* funcName,
                          const webgl::PackingInfo& pi)
@@ -573,19 +584,19 @@ TexUnpackBytes::TexOrSubImage(bool isSub
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackImage
 
 TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, TexImageTarget target,
                                uint32_t width, uint32_t height, uint32_t depth,
-                               layers::Image* image, bool isAlphaPremult)
+                               layers::Image* image, gfxAlphaType srcAlphaType)
     : TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth,
-                    isAlphaPremult)
+                    srcAlphaType)
     , mImage(image)
 { }
 
 TexUnpackImage::~TexUnpackImage()
 { }
 
 bool
 TexUnpackImage::Validate(WebGLContext* webgl, const char* funcName,
@@ -621,25 +632,34 @@ TexUnpackImage::TexOrSubImage(bool isSub
 
     const char* fallbackReason;
     do {
         if (mDepth != 1) {
             fallbackReason = "depth is not 1";
             break;
         }
 
-        const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
-        if (mSrcIsPremult != dstIsPremult) {
+        const auto fnHasPremultMismatch = [&]() {
+            if (mSrcAlphaType == gfxAlphaType::Opaque)
+                return false;
+
+            const bool srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
+            const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
+            if (srcIsPremult == dstIsPremult)
+                return false;
+
             if (dstIsPremult) {
                 fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
             } else {
                 fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
             }
+            return true;
+        };
+        if (fnHasPremultMismatch())
             break;
-        }
 
         if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA) {
             fallbackReason = "`format` is not RGB or RGBA";
             break;
         }
 
         if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE) {
             fallbackReason = "`type` is not UNSIGNED_BYTE";
@@ -696,31 +716,32 @@ TexUnpackImage::TexOrSubImage(bool isSub
     if (!dataSurf) {
         webgl->ErrorOutOfMemory("%s: GetAsSourceSurface or GetDataSurface failed after"
                                 " blit failed for TexUnpackImage.",
                                 funcName);
         return false;
     }
 
     const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf,
-                                    mSrcIsPremult);
+                                    mSrcAlphaType);
 
     return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level,
                                   dui, xOffset, yOffset, zOffset, out_error);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackSurface
 
 TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target,
                                    uint32_t width, uint32_t height, uint32_t depth,
-                                   gfx::DataSourceSurface* surf, bool isAlphaPremult)
+                                   gfx::DataSourceSurface* surf,
+                                   gfxAlphaType srcAlphaType)
     : TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth,
-                    isAlphaPremult)
+                    srcAlphaType)
     , mSurf(surf)
 { }
 
 //////////
 
 static bool
 GetFormatForSurf(gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat,
                  uint8_t* const out_bpp)
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -46,23 +46,24 @@ public:
     const uint32_t mImageHeight;
     const uint32_t mSkipPixels;
     const uint32_t mSkipRows;
     const uint32_t mSkipImages;
     const uint32_t mWidth;
     const uint32_t mHeight;
     const uint32_t mDepth;
 
-    const bool mSrcIsPremult;
+    const gfxAlphaType mSrcAlphaType;
 
     bool mNeedsExactUpload;
 
 protected:
     TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target, uint32_t rowLength,
-                  uint32_t width, uint32_t height, uint32_t depth, bool isSrcPremult);
+                  uint32_t width, uint32_t height, uint32_t depth,
+                  gfxAlphaType srcAlphaType);
 
 public:
     virtual ~TexUnpackBlob() { }
 
 protected:
     bool ConvertIfNeeded(WebGLContext* webgl, const char* funcName,
                          const uint32_t rowLength, const uint32_t rowCount,
                          WebGLTexelFormat srcFormat,
@@ -112,17 +113,17 @@ public:
 
 class TexUnpackImage final : public TexUnpackBlob
 {
 public:
     const RefPtr<layers::Image> mImage;
 
     TexUnpackImage(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
                    uint32_t height, uint32_t depth, layers::Image* image,
-                   bool isAlphaPremult);
+                   gfxAlphaType srcAlphaType);
 
     ~TexUnpackImage(); // Prevent needing to define layers::Image in the header.
 
     virtual bool Validate(WebGLContext* webgl, const char* funcName,
                           const webgl::PackingInfo& pi) override;
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                WebGLTexture* tex, TexImageTarget target, GLint level,
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
@@ -132,17 +133,17 @@ public:
 
 class TexUnpackSurface final : public TexUnpackBlob
 {
 public:
     const RefPtr<gfx::DataSourceSurface> mSurf;
 
     TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
                      uint32_t height, uint32_t depth, gfx::DataSourceSurface* surf,
-                     bool isAlphaPremult);
+                     gfxAlphaType srcAlphaType);
 
     virtual bool Validate(WebGLContext* webgl, const char* funcName,
                           const webgl::PackingInfo& pi) override;
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                WebGLTexture* tex, TexImageTarget target, GLint level,
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
                                GLint yOffset, GLint zOffset,
                                GLenum* const out_error) const override;
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1242,24 +1242,20 @@ WebGLContext::LoseOldestWebGLContextIfLi
 }
 
 UniquePtr<uint8_t[]>
 WebGLContext::GetImageBuffer(int32_t* out_format)
 {
     *out_format = 0;
 
     // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
-    bool premult;
-    RefPtr<SourceSurface> snapshot =
-      GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult);
-    if (!snapshot) {
+    gfxAlphaType any;
+    RefPtr<SourceSurface> snapshot = GetSurfaceSnapshot(&any);
+    if (!snapshot)
         return nullptr;
-    }
-
-    MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!");
 
     RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
 
     return gfxUtils::GetImageBuffer(dataSurface, mOptions.premultipliedAlpha,
                                     out_format);
 }
 
 NS_IMETHODIMP
@@ -1267,24 +1263,21 @@ WebGLContext::GetInputStream(const char*
                              const char16_t* encoderOptions,
                              nsIInputStream** out_stream)
 {
     NS_ASSERTION(gl, "GetInputStream on invalid context?");
     if (!gl)
         return NS_ERROR_FAILURE;
 
     // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
-    bool premult;
-    RefPtr<SourceSurface> snapshot =
-      GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult);
+    gfxAlphaType any;
+    RefPtr<SourceSurface> snapshot = GetSurfaceSnapshot(&any);
     if (!snapshot)
         return NS_ERROR_FAILURE;
 
-    MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!");
-
     RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
     return gfxUtils::GetInputStream(dataSurface, mOptions.premultipliedAlpha, mimeType,
                                     encoderOptions, out_stream);
 }
 
 void
 WebGLContext::UpdateLastUseIndex()
 {
@@ -1929,31 +1922,29 @@ WebGLContext::ForceRestoreContext()
 
 void
 WebGLContext::MakeContextCurrent() const
 {
     gl->MakeCurrent();
 }
 
 already_AddRefed<mozilla::gfx::SourceSurface>
-WebGLContext::GetSurfaceSnapshot(bool* out_premultAlpha)
+WebGLContext::GetSurfaceSnapshot(gfxAlphaType* const out_alphaType)
 {
     if (!gl)
         return nullptr;
 
-    bool hasAlpha = mOptions.alpha;
-    SurfaceFormat surfFormat = hasAlpha ? SurfaceFormat::B8G8R8A8
-                                        : SurfaceFormat::B8G8R8X8;
+    const auto surfFormat = mOptions.alpha ? SurfaceFormat::B8G8R8A8
+                                           : SurfaceFormat::B8G8R8X8;
     RefPtr<DataSourceSurface> surf;
     surf = Factory::CreateDataSourceSurfaceWithStride(IntSize(mWidth, mHeight),
                                                       surfFormat,
                                                       mWidth * 4);
-    if (NS_WARN_IF(!surf)) {
+    if (NS_WARN_IF(!surf))
         return nullptr;
-    }
 
     gl->MakeCurrent();
     {
         ScopedBindFramebuffer autoFB(gl, 0);
         ClearBackbufferIfNeeded();
 
         // Save, override, then restore glReadBuffer.
         const GLenum readBufferMode = gl->Screen()->GetReadBufferMode();
@@ -1963,36 +1954,41 @@ WebGLContext::GetSurfaceSnapshot(bool* o
         }
         ReadPixelsIntoDataSurface(gl, surf);
 
         if (readBufferMode != LOCAL_GL_BACK) {
             gl->Screen()->SetReadBuffer(readBufferMode);
         }
     }
 
-    if (out_premultAlpha) {
-        *out_premultAlpha = true;
+    gfxAlphaType alphaType;
+    if (!mOptions.alpha) {
+        alphaType = gfxAlphaType::Opaque;
+    } else if (mOptions.premultipliedAlpha) {
+        alphaType = gfxAlphaType::Premult;
+    } else {
+        alphaType = gfxAlphaType::NonPremult;
     }
-    bool srcPremultAlpha = mOptions.premultipliedAlpha;
-    if (!srcPremultAlpha) {
-        if (out_premultAlpha) {
-            *out_premultAlpha = false;
-        } else if(hasAlpha) {
+
+    if (out_alphaType) {
+        *out_alphaType = alphaType;
+    } else {
+        // Expects Opaque or Premult
+        if (alphaType == gfxAlphaType::NonPremult) {
             gfxUtils::PremultiplyDataSurface(surf, surf);
+            alphaType = gfxAlphaType::Premult;
         }
     }
 
     RefPtr<DrawTarget> dt =
         Factory::CreateDrawTarget(gfxPlatform::GetPlatform()->GetSoftwareBackend(),
                                   IntSize(mWidth, mHeight),
                                   SurfaceFormat::B8G8R8A8);
-
-    if (!dt) {
+    if (!dt)
         return nullptr;
-    }
 
     dt->SetTransform(Matrix::Translation(0.0, mHeight).PreScale(1.0, -1.0));
 
     dt->DrawSurface(surf,
                     Rect(0, 0, mWidth, mHeight),
                     Rect(0, 0, mWidth, mHeight),
                     DrawSurfaceOptions(),
                     DrawOptions(1.0f, CompositionOp::OP_SOURCE));
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -370,18 +370,18 @@ public:
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     virtual UniquePtr<uint8_t[]> GetImageBuffer(int32_t* out_format) override;
     NS_IMETHOD GetInputStream(const char* mimeType,
                               const char16_t* encoderOptions,
                               nsIInputStream** out_stream) override;
 
-    already_AddRefed<mozilla::gfx::SourceSurface>
-    GetSurfaceSnapshot(bool* out_premultAlpha) override;
+    virtual already_AddRefed<mozilla::gfx::SourceSurface>
+    GetSurfaceSnapshot(gfxAlphaType* out_alphaType) override;
 
     NS_IMETHOD SetIsOpaque(bool) override { return NS_OK; };
     bool GetIsOpaque() override { return false; }
     NS_IMETHOD SetContextOptions(JSContext* cx,
                                  JS::Handle<JS::Value> options,
                                  ErrorResult& aRvForDictionaryInit) override;
 
     NS_IMETHOD SetIsIPC(bool) override {
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -215,35 +215,29 @@ FromPboOffset(WebGLContext* webgl, const
 static UniquePtr<webgl::TexUnpackBlob>
 FromImageBitmap(WebGLContext* webgl, const char* funcName, TexImageTarget target,
               uint32_t width, uint32_t height, uint32_t depth,
               const dom::ImageBitmap& imageBitmap)
 {
     UniquePtr<dom::ImageBitmapCloneData> cloneData = Move(imageBitmap.ToCloneData());
     const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
 
-    ////
-
     if (!width) {
         width = surf->GetSize().width;
     }
 
     if (!height) {
         height = surf->GetSize().height;
     }
 
-    ////
-
-
     // WhatWG "HTML Living Standard" (30 October 2015):
     // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
     //  non-premultiplied alpha values."
-    const bool isAlphaPremult = cloneData->mIsPremultipliedAlpha;
     return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf,
-                                               isAlphaPremult);
+                                               cloneData->mAlphaType);
 }
 
 static UniquePtr<webgl::TexUnpackBlob>
 FromImageData(WebGLContext* webgl, const char* funcName, TexImageTarget target,
               uint32_t width, uint32_t height, uint32_t depth,
               const dom::ImageData& imageData, dom::Uint8ClampedArray* scopedArr)
 {
     DebugOnly<bool> inited = scopedArr->Init(imageData.GetDataObject());
@@ -252,16 +246,21 @@ FromImageData(WebGLContext* webgl, const
     scopedArr->ComputeLengthAndData();
     const DebugOnly<size_t> dataSize = scopedArr->Length();
     const void* const data = scopedArr->Data();
 
     const gfx::IntSize size(imageData.Width(), imageData.Height());
     const size_t stride = size.width * 4;
     const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
 
+    // WhatWG "HTML Living Standard" (30 October 2015):
+    // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
+    //  non-premultiplied alpha values."
+    const auto alphaType = gfxAlphaType::NonPremult;
+
     MOZ_ASSERT(dataSize == stride * size.height);
 
     uint8_t* wrappableData = (uint8_t*)data;
 
     const RefPtr<gfx::DataSourceSurface> surf =
         gfx::Factory::CreateWrappingDataSourceSurface(wrappableData, stride, size,
                                                       surfFormat);
     if (!surf) {
@@ -276,22 +275,18 @@ FromImageData(WebGLContext* webgl, const
     }
 
     if (!height) {
         height = imageData.Height();
     }
 
     ////
 
-    // WhatWG "HTML Living Standard" (30 October 2015):
-    // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
-    //  non-premultiplied alpha values."
-    const bool isAlphaPremult = false;
     return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf,
-                                               isAlphaPremult);
+                                               alphaType);
 }
 
 UniquePtr<webgl::TexUnpackBlob>
 WebGLContext::FromDomElem(const char* funcName, TexImageTarget target, uint32_t width,
                           uint32_t height, uint32_t depth, const dom::Element& elem,
                           ErrorResult* const out_error)
 {
     // The canvas spec says that drawImage should draw the first frame of
@@ -374,26 +369,24 @@ WebGLContext::FromDomElem(const char* fu
         GenerateWarning("%s: Element is write-only, thus cannot be uploaded.", funcName);
         out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
         return nullptr;
     }
 
     //////
     // Ok, we're good!
 
-    const bool isAlphaPremult = sfer.mIsPremultiplied;
-
     if (layersImage) {
         return MakeUnique<webgl::TexUnpackImage>(this, target, width, height, depth,
-                                                 layersImage,  isAlphaPremult);
+                                                 layersImage, sfer.mAlphaType);
     }
 
     MOZ_ASSERT(dataSurf);
     return MakeUnique<webgl::TexUnpackSurface>(this, target, width, height, depth,
-                                               dataSurf, isAlphaPremult);
+                                               dataSurf, sfer.mAlphaType);
 }
 
 ////////////////////////////////////////
 
 UniquePtr<webgl::TexUnpackBlob>
 WebGLContext::From(const char* funcName, TexImageTarget target, GLsizei rawWidth,
                    GLsizei rawHeight, GLsizei rawDepth, GLint border,
                    const TexImageSource& src, dom::Uint8ClampedArray* const scopedArr)
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -114,17 +114,18 @@ public:
                             const char16_t *encoderOptions,
                             nsIInputStream **stream) = 0;
 
   // This gets an Azure SourceSurface for the canvas, this will be a snapshot
   // of the canvas at the time it was called.
   // If premultAlpha is provided, then it assumed the callee can handle
   // un-premultiplied surfaces, and *premultAlpha will be set to false
   // if one is returned.
-  virtual already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(bool* premultAlpha = nullptr) = 0;
+  virtual already_AddRefed<mozilla::gfx::SourceSurface>
+  GetSurfaceSnapshot(gfxAlphaType* out_alphaType = nullptr) = 0;
 
   // If this context is opaque, the backing store of the canvas should
   // be created as opaque; all compositing operators should assume the
   // dst alpha is always 1.0.  If this is never called, the context
   // defaults to false (not opaque).
   NS_IMETHOD SetIsOpaque(bool isOpaque) = 0;
   virtual bool GetIsOpaque() = 0;
 
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -1303,22 +1303,22 @@ HTMLCanvasElement::SetFrameCapture(alrea
     }
 
     RefPtr<Image> imageRefCopy = image.get();
     listener->NewFrame(imageRefCopy.forget(), aTime);
   }
 }
 
 already_AddRefed<SourceSurface>
-HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
+HTMLCanvasElement::GetSurfaceSnapshot(gfxAlphaType* const aOutAlphaType)
 {
   if (!mCurrentContext)
     return nullptr;
 
-  return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
+  return mCurrentContext->GetSurfaceSnapshot(aOutAlphaType);
 }
 
 AsyncCanvasRenderer*
 HTMLCanvasElement::GetAsyncCanvasRenderer()
 {
   if (!mAsyncCanvasRenderer) {
     mAsyncCanvasRenderer = new AsyncCanvasRenderer();
     mAsyncCanvasRenderer->mHTMLCanvasElement = this;
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -249,17 +249,18 @@ public:
 
   /*
    * Returns true if the canvas context content is guaranteed to be opaque
    * across its entire area.
    */
   bool GetIsOpaque();
   virtual bool GetOpaqueAttr() override;
 
-  virtual already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
+  virtual already_AddRefed<gfx::SourceSurface>
+  GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType = nullptr);
 
   /*
    * Register a FrameCaptureListener with this canvas.
    * The canvas hooks into the RefreshDriver while there are
    * FrameCaptureListeners registered.
    * The registered FrameCaptureListeners are stored as WeakPtrs, thus it's the
    * caller's responsibility to keep them alive. Once a registered
    * FrameCaptureListener is destroyed it will be automatically deregistered.
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -169,24 +169,24 @@ class SharedRGBImage;
 class SurfaceTextureImage;
 #elif defined(XP_MACOSX)
 class MacIOSurfaceImage;
 #endif
 
 /**
  * A class representing a buffer of pixel data. The data can be in one
  * of various formats including YCbCr.
- * 
+ *
  * Create an image using an ImageContainer. Fill the image with data, and
  * then call ImageContainer::SetImage to display it. An image must not be
  * modified after calling SetImage. Image implementations do not need to
  * perform locking; when filling an Image, the Image client is responsible
  * for ensuring only one thread accesses the Image at a time, and after
  * SetImage the image is immutable.
- * 
+ *
  * When resampling an Image, only pixels within the buffer should be
  * sampled. For example, cairo images should be sampled in EXTEND_PAD mode.
  */
 class Image {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Image)
 
 public:
   ImageFormat GetFormat() { return mFormat; }
@@ -253,17 +253,17 @@ protected:
   int32_t mSerial;
   ImageFormat mFormat;
 
   static mozilla::Atomic<int32_t> sSerialCounter;
 };
 
 /**
  * A RecycleBin is owned by an ImageContainer. We store buffers in it that we
- * want to recycle from one image to the next.It's a separate object from 
+ * want to recycle from one image to the next.It's a separate object from
  * ImageContainer because images need to store a strong ref to their RecycleBin
  * and we must avoid creating a reference loop between an ImageContainer and
  * its active image.
  */
 class BufferRecycleBin final {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BufferRecycleBin)
 
   //typedef mozilla::gl::GLContext GLContext;
@@ -336,17 +336,17 @@ public:
 private:
   typedef mozilla::Mutex Mutex;
 
   ~ImageContainerListener();
 
   Mutex mLock;
   ImageContainer* mImageContainer;
 };
- 
+
 /**
  * A class that manages Images for an ImageLayer. The only reason
  * we need a separate class here is that ImageLayers aren't threadsafe
  * (because layers can only be used on the main thread) and we want to
  * be able to set the current Image from any thread, to facilitate
  * video playback without involving the main thread, for example.
  *
  * An ImageContainer can operate in one of these modes:
@@ -416,17 +416,17 @@ public:
    * Every element of aImages must have non-null mImage.
    * mFrameID can be zero, in which case you won't get meaningful
    * painted/dropped frame counts. Otherwise you should use a unique and
    * increasing ID for each decoded and submitted frame (but it's OK to
    * pass the same frame to SetCurrentImages).
    * mProducerID is a unique ID for the stream of images. A change in the
    * mProducerID means changing to a new mFrameID namespace. All frames in
    * aImages must have the same mProducerID.
-   * 
+   *
    * The Image data must not be modified after this method is called!
    * Note that this must not be called if ENABLE_ASYNC has not been set.
    *
    * The implementation calls CurrentImageChanged() while holding
    * mReentrantMonitor.
    *
    * If this ImageContainer has an ImageClient for async video:
    * Schedule a task to send the image to the compositor using the
@@ -452,21 +452,21 @@ public:
    * See Bug 901224.
    */
   void ClearImagesFromImageBridge();
 
   /**
    * Set an Image as the current image to display. The Image must have
    * been created by this ImageContainer.
    * Must be called on the main thread, within a layers transaction.
-   * 
+   *
    * This method takes mReentrantMonitor
    * when accessing thread-shared state.
    * aImage can be null. While it's null, nothing will be painted.
-   * 
+   *
    * The Image data must not be modified after this method is called!
    * Note that this must not be called if ENABLE_ASYNC been set.
    *
    * You won't get meaningful painted/dropped counts when using this method.
    */
   void SetCurrentImageInTransaction(Image* aImage);
   void SetCurrentImagesInTransaction(const nsTArray<NonOwningImage>& aImages);
 
@@ -721,17 +721,17 @@ struct PlanarYCbCrData {
  * The YCbCr format can be:
  *
  * 4:4:4 - CbCr width/height are the same as Y.
  * 4:2:2 - CbCr width is half that of Y. Height is the same.
  * 4:2:0 - CbCr width and height is half that of Y.
  *
  * The color format is detected based on the height/width ratios
  * defined above.
- * 
+ *
  * The Image that is rendered is the picture region defined by
  * mPicX, mPicY and mPicSize. The size of the rendered image is
  * mPicSize, not mYSize or mCbCrSize.
  *
  * mYSkip, mCbSkip, mCrSkip are added to support various output
  * formats from hardware decoder. They are per-pixel skips in the
  * source image.
  *
--- a/gfx/thebes/gfxTypes.h
+++ b/gfx/thebes/gfxTypes.h
@@ -74,9 +74,15 @@ enum class gfxSurfaceType {
 
 enum class gfxContentType {
   COLOR       = 0x1000,
   ALPHA       = 0x2000,
   COLOR_ALPHA = 0x3000,
   SENTINEL    = 0xffff
 };
 
+enum class gfxAlphaType {
+  Opaque,
+  Premult,
+  NonPremult,
+};
+
 #endif /* GFX_TYPES_H */
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -7179,30 +7179,26 @@ nsLayoutUtils::IsReallyFixedPos(nsIFrame
 
 nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromOffscreenCanvas(OffscreenCanvas* aOffscreenCanvas,
                                           uint32_t aSurfaceFlags,
                                           RefPtr<DrawTarget>& aTarget)
 {
   SurfaceFromElementResult result;
 
-  bool* isPremultiplied = nullptr;
-  if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
-    isPremultiplied = &result.mIsPremultiplied;
-  }
-
   nsIntSize size = aOffscreenCanvas->GetWidthHeight();
 
-  result.mSourceSurface = aOffscreenCanvas->GetSurfaceSnapshot(isPremultiplied);
-    if (!result.mSourceSurface) {
-      // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
-      // draw nothing, so return an empty surface.
-      DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-      RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
-                                                           SurfaceFormat::B8G8R8A8);
+  result.mSourceSurface = aOffscreenCanvas->GetSurfaceSnapshot(&result.mAlphaType);
+  if (!result.mSourceSurface) {
+    // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
+    // draw nothing, so return an empty surface.
+    result.mAlphaType = gfxAlphaType::Opaque;
+    DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+    RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
+                                                         SurfaceFormat::B8G8R8A8);
     if (dt) {
       result.mSourceSurface = dt->Snapshot();
     }
   } else if (aTarget) {
     RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface);
     if (opt) {
       result.mSourceSurface = opt;
     }
@@ -7271,17 +7267,16 @@ nsLayoutUtils::SurfaceFromElement(nsIIma
                         ? (uint32_t) imgIContainer::FRAME_FIRST
                         : (uint32_t) imgIContainer::FRAME_CURRENT;
   uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE
                       | imgIContainer::FLAG_ASYNC_NOTIFY;
   if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION)
     frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
   if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
     frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
-    result.mIsPremultiplied = false;
   }
 
   int32_t imgWidth, imgHeight;
   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
   HTMLImageElement* element = HTMLImageElement::FromContentOrNull(content);
   if (aSurfaceFlags & SFE_USE_ELEMENT_SIZE_IF_VECTOR &&
       element &&
       imgContainer->GetType() == imgIContainer::TYPE_VECTOR) {
@@ -7309,16 +7304,25 @@ nsLayoutUtils::SurfaceFromElement(nsIIma
     // upfront if aTarget is specified.
     if (aTarget) {
       RefPtr<SourceSurface> optSurface =
         aTarget->OptimizeSourceSurface(result.mSourceSurface);
       if (optSurface) {
         result.mSourceSurface = optSurface;
       }
     }
+
+    const auto& format = result.mSourceSurface->GetFormat();
+    if (IsOpaque(format)) {
+      result.mAlphaType = gfxAlphaType::Opaque;
+    } else if (frameFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
+      result.mAlphaType = gfxAlphaType::NonPremult;
+    } else {
+      result.mAlphaType = gfxAlphaType::Premult;
+    }
   } else {
     result.mDrawInfo.mImgContainer = imgContainer;
     result.mDrawInfo.mWhichFrame = whichFrame;
     result.mDrawInfo.mDrawingFlags = frameFlags;
   }
 
   int32_t corsmode;
   if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
@@ -7343,33 +7347,29 @@ nsLayoutUtils::SurfaceFromElement(HTMLIm
 
 nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromElement(HTMLCanvasElement* aElement,
                                   uint32_t aSurfaceFlags,
                                   RefPtr<DrawTarget>& aTarget)
 {
   SurfaceFromElementResult result;
 
-  bool* isPremultiplied = nullptr;
-  if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
-    isPremultiplied = &result.mIsPremultiplied;
-  }
-
   IntSize size = aElement->GetSize();
 
-  result.mSourceSurface = aElement->GetSurfaceSnapshot(isPremultiplied);
+  result.mSourceSurface = aElement->GetSurfaceSnapshot(&result.mAlphaType);
   if (!result.mSourceSurface) {
-     // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
-     // draw nothing, so return an empty surface.
-     DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-     RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
-                                                          SurfaceFormat::B8G8R8A8);
-     if (dt) {
-       result.mSourceSurface = dt->Snapshot();
-     }
+    // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
+    // draw nothing, so return an empty surface.
+    result.mAlphaType = gfxAlphaType::Opaque;
+    DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+    RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
+                                                        SurfaceFormat::B8G8R8A8);
+    if (dt) {
+      result.mSourceSurface = dt->Snapshot();
+    }
   } else if (aTarget) {
     RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface);
     if (opt) {
       result.mSourceSurface = opt;
     }
   }
 
   // Ensure that any future changes to the canvas trigger proper invalidation,
@@ -7385,16 +7385,17 @@ nsLayoutUtils::SurfaceFromElement(HTMLCa
 }
 
 nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement,
                                   uint32_t aSurfaceFlags,
                                   RefPtr<DrawTarget>& aTarget)
 {
   SurfaceFromElementResult result;
+  result.mAlphaType = gfxAlphaType::Opaque; // Assume opaque.
 
   if (aElement->ContainsRestrictedContent()) {
     return result;
   }
 
   uint16_t readyState;
   if (NS_SUCCEEDED(aElement->GetReadyState(&readyState)) &&
       (readyState == nsIDOMHTMLMediaElement::HAVE_NOTHING ||
@@ -7404,17 +7405,16 @@ nsLayoutUtils::SurfaceFromElement(HTMLVi
   }
 
   // If it doesn't have a principal, just bail
   nsCOMPtr<nsIPrincipal> principal = aElement->GetCurrentVideoPrincipal();
   if (!principal)
     return result;
 
   result.mLayersImage = aElement->GetCurrentImage();
-
   if (!result.mLayersImage)
     return result;
 
   if (aTarget) {
     // They gave us a DrawTarget to optimize for, so even though we have a layers::Image,
     // we should unconditionally grab a SourceSurface and try to optimize it.
     result.mSourceSurface = result.mLayersImage->GetAsSourceSurface();
     if (!result.mSourceSurface)
@@ -8440,17 +8440,17 @@ nsLayoutUtils::IsAPZTestLoggingEnabled()
 // SurfaceFromElementResult
 
 nsLayoutUtils::SurfaceFromElementResult::SurfaceFromElementResult()
   // Use safe default values here
   : mIsWriteOnly(true)
   , mIsStillLoading(false)
   , mHasSize(false)
   , mCORSUsed(false)
-  , mIsPremultiplied(true)
+  , mAlphaType(gfxAlphaType::Opaque)
 {
 }
 
 const RefPtr<mozilla::gfx::SourceSurface>&
 nsLayoutUtils::SurfaceFromElementResult::GetSourceSurface()
 {
   if (!mSourceSurface && mLayersImage) {
     mSourceSurface = mLayersImage->GetAsSourceSurface();
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2077,19 +2077,18 @@ public:
   enum {
     /* When creating a new surface, create an image surface */
     SFE_WANT_IMAGE_SURFACE = 1 << 0,
     /* Whether to extract the first frame (as opposed to the
        current frame) in the case that the element is an image. */
     SFE_WANT_FIRST_FRAME_IF_IMAGE = 1 << 1,
     /* Whether we should skip colorspace/gamma conversion */
     SFE_NO_COLORSPACE_CONVERSION = 1 << 2,
-    /* Specifies that the caller wants unpremultiplied pixel data.
-       If this is can be done efficiently, the result will be a
-       DataSourceSurface and mIsPremultiplied with be set to false. */
+    /* Specifies that the caller wants either OPAQUE or NON_PREMULT mAlphaType,
+       if this is can be done efficiently. */
     SFE_PREFER_NO_PREMULTIPLY_ALPHA = 1 << 3,
     /* Whether we should skip getting a surface for vector images and
        return a DirectDrawInfo containing an imgIContainer instead. */
     SFE_NO_RASTERIZING_VECTORS = 1 << 4,
     /* If image type is vector, the return surface size will same as
        element size, not image's intrinsic size. */
     SFE_USE_ELEMENT_SIZE_IF_VECTOR = 1 << 5
   };
@@ -2137,18 +2136,18 @@ public:
     bool mIsWriteOnly;
     /* Whether the element was still loading.  Some consumers need to handle
        this case specially. */
     bool mIsStillLoading;
     /* Whether the element has a valid size. */
     bool mHasSize;
     /* Whether the element used CORS when loading. */
     bool mCORSUsed;
-    /* Whether the returned image contains premultiplied pixel data */
-    bool mIsPremultiplied;
+
+    gfxAlphaType mAlphaType;
 
     // Methods:
 
     SurfaceFromElementResult();
 
     // Gets mSourceSurface, or makes a SourceSurface from mLayersImage.
     const RefPtr<mozilla::gfx::SourceSurface>& GetSourceSurface();
   };