Allow SurfaceFromElement to return the Image. draft
authorJeff Gilbert <jdashg@gmail.com>
Thu, 17 Dec 2015 16:16:54 -0800
changeset 316109 62958b4e1e632e8de45bbbf7630b0b44d9db26ac
parent 316108 9eb24b1e2cde26f49cdd309ad445a5cc5b8085e7
child 316110 28b575dea21805eca8c1529b141f6b12d50fcf92
push id8514
push userjgilbert@mozilla.com
push dateFri, 18 Dec 2015 00:24:33 +0000
milestone45.0a1
Allow SurfaceFromElement to return the Image.
dom/base/nsDOMWindowUtils.cpp
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/ImageBitmap.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLTextureUpload.cpp
layout/base/nsCSSRendering.cpp
layout/base/nsIPresShell.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -983,17 +983,17 @@ nsDOMWindowUtils::SendKeyEvent(const nsA
                                int32_t aKeyCode,
                                int32_t aCharCode,
                                int32_t aModifiers,
                                uint32_t aAdditionalFlags,
                                bool* aDefaultActionTaken)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
-  
+
   return nsContentUtils::SendKeyEvent(widget, aType, aKeyCode, aCharCode,
                                       aModifiers, aAdditionalFlags,
                                       aDefaultActionTaken);
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeKeyEvent(int32_t aNativeKeyboardLayout,
                                      int32_t aNativeKeyCode,
@@ -1312,17 +1312,17 @@ nsDOMWindowUtils::NodesFromRect(float aX
                                 float aBottomSize, float aLeftSize,
                                 bool aIgnoreRootScrollFrame,
                                 bool aFlushLayout,
                                 nsIDOMNodeList** aReturn)
 {
   nsCOMPtr<nsIDocument> doc = GetDocument();
   NS_ENSURE_STATE(doc);
 
-  return doc->NodesFromRectHelper(aX, aY, aTopSize, aRightSize, aBottomSize, aLeftSize, 
+  return doc->NodesFromRectHelper(aX, aY, aTopSize, aRightSize, aBottomSize, aLeftSize,
                                   aIgnoreRootScrollFrame, aFlushLayout, aReturn);
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetTranslationNodes(nsIDOMNode* aRoot,
                                       nsITranslationNodeList** aRetVal)
 {
   NS_ENSURE_ARG_POINTER(aRetVal);
@@ -1408,17 +1408,19 @@ CanvasToDataSourceSurface(nsIDOMHTMLCanv
     return nullptr;
   }
 
   MOZ_ASSERT(node->IsElement(),
              "An nsINode that implements nsIDOMHTMLCanvasElement should "
              "be an element.");
   nsLayoutUtils::SurfaceFromElementResult result =
     nsLayoutUtils::SurfaceFromElement(node->AsElement());
-  return result.mSourceSurface->GetDataSurface();
+
+  MOZ_ASSERT(result.GetSourceSurface());
+  return result.GetSourceSurface()->GetDataSurface();
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::CompareCanvases(nsIDOMHTMLCanvasElement *aCanvas1,
                                   nsIDOMHTMLCanvasElement *aCanvas2,
                                   uint32_t* aMaxDifference,
                                   uint32_t* retVal)
 {
@@ -1749,17 +1751,17 @@ nsDOMWindowUtils::GetFullZoom(float* aFu
   if (!presContext) {
     return NS_OK;
   }
 
   *aFullZoom = presContext->DeviceContext()->GetFullZoom();
 
   return NS_OK;
 }
- 
+
 NS_IMETHODIMP
 nsDOMWindowUtils::DispatchDOMEventViaPresShell(nsIDOMNode* aTarget,
                                                nsIDOMEvent* aEvent,
                                                bool aTrusted,
                                                bool* aRetVal)
 {
   NS_ENSURE_STATE(aEvent);
   aEvent->SetTrusted(aTrusted);
@@ -2452,17 +2454,17 @@ nsDOMWindowUtils::RenderDocument(const n
     // Get Primary Shell
     nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
     NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
 
     // Render Document
     return presShell->RenderDocument(aRect, aFlags, aBackgroundColor, aThebesContext);
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsDOMWindowUtils::GetCursorType(int16_t *aCursor)
 {
   NS_ENSURE_ARG_POINTER(aCursor);
 
   nsIDocument* doc = GetDocument();
   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
   bool isSameDoc = false;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1196,18 +1196,18 @@ bool CanvasRenderingContext2D::SwitchRen
   }
 
 #ifdef USE_SKIA_GPU
   if (mRenderingMode == RenderingMode::OpenGLBackendMode) {
     if (mVideoTexture) {
       gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->MakeCurrent();
       gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->fDeleteTextures(1, &mVideoTexture);
     }
-	  mCurrentVideoSize.width = 0;
-	  mCurrentVideoSize.height = 0;
+    mCurrentVideoSize.width = 0;
+    mCurrentVideoSize.height = 0;
   }
 #endif
 
   RefPtr<SourceSurface> snapshot;
   Matrix transform;
 
   if (mTarget) {
     snapshot = mTarget->Snapshot();
@@ -2100,25 +2100,24 @@ CanvasRenderingContext2D::CreatePattern(
   EnsureTarget();
 
   // The canvas spec says that createPattern should use the first frame
   // of animated images
   nsLayoutUtils::SurfaceFromElementResult res =
     nsLayoutUtils::SurfaceFromElement(htmlElement,
       nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget);
 
-  if (!res.mSourceSurface) {
+  if (!res.GetSourceSurface()) {
     error.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  RefPtr<CanvasPattern> pat =
-    new CanvasPattern(this, res.mSourceSurface, repeatMode, res.mPrincipal,
-                             res.mIsWriteOnly, res.mCORSUsed);
-
+  RefPtr<CanvasPattern> pat = new CanvasPattern(this, res.GetSourceSurface(), repeatMode,
+                                                res.mPrincipal, res.mIsWriteOnly,
+                                                res.mCORSUsed);
   return pat.forget();
 }
 
 //
 // shadows
 //
 void
 CanvasRenderingContext2D::SetShadowColor(const nsAString& shadowColor)
@@ -5394,19 +5393,19 @@ CanvasRenderingContext2D::PutImageData_e
 
   uint32_t copyX = dirtyRect.x - x;
   uint32_t copyY = dirtyRect.y - y;
   //uint8_t *src = aArray->Data();
   uint8_t *dst = imgsurf->Data();
   uint8_t* srcLine = aArray->Data() + copyY * (w * 4) + copyX * 4;
 #if 0
   printf("PutImageData_explicit: dirty x=%d y=%d w=%d h=%d copy x=%d y=%d w=%d h=%d ext x=%d y=%d w=%d h=%d\n",
-	     dirtyRect.x, dirtyRect.y, copyWidth, copyHeight,
-	     copyX, copyY, copyWidth, copyHeight,
-	     x, y, w, h);
+       dirtyRect.x, dirtyRect.y, copyWidth, copyHeight,
+       copyX, copyY, copyWidth, copyHeight,
+       x, y, w, h);
 #endif
   for (uint32_t j = 0; j < copyHeight; j++) {
     uint8_t *src = srcLine;
     for (uint32_t i = 0; i < copyWidth; i++) {
       uint8_t r = *src++;
       uint8_t g = *src++;
       uint8_t b = *src++;
       uint8_t a = *src++;
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -350,22 +350,23 @@ GetSurfaceFromElement(nsIGlobalObject* a
     nsLayoutUtils::SurfaceFromElement(&aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME);
 
   // check origin-clean
   if (!CheckSecurityForHTMLElements(res)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
-  if (NS_WARN_IF(!res.mSourceSurface)) {
+  RefPtr<SourceSurface> surface = res.GetSourceSurface();
+
+  if (NS_WARN_IF(!surface)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  RefPtr<SourceSurface> surface(res.mSourceSurface);
   return surface.forget();
 }
 
 /*
  * The specification doesn't allow to create an ImegeBitmap from a vector image.
  * This function is used to check if the given HTMLImageElement contains a
  * raster image.
  */
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1318,21 +1318,16 @@ public:
 
         if (!mPixelStore_PremultiplyAlpha)
             flags |= nsLayoutUtils::SFE_PREFER_NO_PREMULTIPLY_ALPHA;
 
         gfx::DrawTarget* idealDrawTarget = nullptr; // Don't care for now.
         return nsLayoutUtils::SurfaceFromElement(elem, flags, idealDrawTarget);
     }
 
-    nsresult
-    SurfaceFromElementResultToImageSurface(const nsLayoutUtils::SurfaceFromElementResult& res,
-                                           RefPtr<gfx::DataSourceSurface>* const out_image,
-                                           WebGLTexelFormat* const out_format);
-
 protected:
     // Returns false if `object` is null or not valid.
     template<class ObjectType>
     bool ValidateObject(const char* info, ObjectType* object);
 
     // Returns false if `object` is not valid.  Considers null to be valid.
     template<class ObjectType>
     bool ValidateObjectAllowNull(const char* info, ObjectType* object);
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1794,89 +1794,16 @@ WebGLContext::StencilOpSeparate(GLenum f
         !ValidateStencilOpEnum(dpfail, "stencilOpSeparate: dpfail") ||
         !ValidateStencilOpEnum(dppass, "stencilOpSeparate: dppass"))
         return;
 
     MakeContextCurrent();
     gl->fStencilOpSeparate(face, sfail, dpfail, dppass);
 }
 
-nsresult
-WebGLContext::SurfaceFromElementResultToImageSurface(const nsLayoutUtils::SurfaceFromElementResult& res,
-                                                     RefPtr<DataSourceSurface>* const out_image,
-                                                     WebGLTexelFormat* const out_format)
-{
-    *out_format = WebGLTexelFormat::None;
-
-    if (!res.mSourceSurface)
-        return NS_OK;
-
-    RefPtr<DataSourceSurface> data = res.mSourceSurface->GetDataSurface();
-    if (!data) {
-        // SurfaceFromElement lied!
-        return NS_OK;
-    }
-
-    // We disallow loading cross-domain images and videos that have not been validated
-    // with CORS as WebGL textures. The reason for doing that is that timing
-    // attacks on WebGL shaders are able to retrieve approximations of the
-    // pixel values in WebGL textures; see bug 655987.
-    //
-    // To prevent a loophole where a Canvas2D would be used as a proxy to load
-    // cross-domain textures, we also disallow loading textures from write-only
-    // Canvas2D's.
-
-    // part 1: check that the DOM element is same-origin, or has otherwise been
-    // validated for cross-domain use.
-    if (!res.mCORSUsed) {
-        bool subsumes;
-        nsresult rv = mCanvasElement->NodePrincipal()->Subsumes(res.mPrincipal, &subsumes);
-        if (NS_FAILED(rv) || !subsumes) {
-            GenerateWarning("It is forbidden to load a WebGL texture from a cross-domain element that has not been validated with CORS. "
-                                "See https://developer.mozilla.org/en/WebGL/Cross-Domain_Textures");
-            return NS_ERROR_DOM_SECURITY_ERR;
-        }
-    }
-
-    // part 2: if the DOM element is write-only, it might contain
-    // cross-domain image data.
-    if (res.mIsWriteOnly) {
-        GenerateWarning("The canvas used as source for texImage2D here is tainted (write-only). It is forbidden "
-                        "to load a WebGL texture from a tainted canvas. A Canvas becomes tainted for example "
-                        "when a cross-domain image is drawn on it. "
-                        "See https://developer.mozilla.org/en/WebGL/Cross-Domain_Textures");
-        return NS_ERROR_DOM_SECURITY_ERR;
-    }
-
-    // End of security checks, now we should be safe regarding cross-domain images
-    // Notice that there is never a need to mark the WebGL canvas as write-only, since we reject write-only/cross-domain
-    // texture sources in the first place.
-
-    switch (data->GetFormat()) {
-        case SurfaceFormat::B8G8R8A8:
-            *out_format = WebGLTexelFormat::BGRA8; // careful, our ARGB means BGRA
-            break;
-        case SurfaceFormat::B8G8R8X8:
-            *out_format = WebGLTexelFormat::BGRX8; // careful, our RGB24 is not tightly packed. Whence BGRX8.
-            break;
-        case SurfaceFormat::A8:
-            *out_format = WebGLTexelFormat::A8;
-            break;
-        case SurfaceFormat::R5G6B5_UINT16:
-            *out_format = WebGLTexelFormat::RGB565;
-            break;
-        default:
-            NS_ASSERTION(false, "Unsupported image format. Unimplemented.");
-            return NS_ERROR_NOT_IMPLEMENTED;
-    }
-
-    *out_image = data;
-    return NS_OK;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Uniform setters.
 
 void
 WebGLContext::Uniform1i(WebGLUniformLocation* loc, GLint a1)
 {
     GLuint rawLoc;
     if (!ValidateUniformSetter(loc, 1, LOCAL_GL_INT, "uniform1i", &rawLoc))
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -247,17 +247,19 @@ WebGLTexture::TexOrSubImage(bool isSubIm
 void
 WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                             GLint level, GLenum internalFormat, GLint xOffset,
                             GLint yOffset, GLint zOffset, GLenum unpackFormat,
                             GLenum unpackType, dom::Element* elem,
                             ErrorResult* const out_error)
 {
     auto sfer = mContext->SurfaceFromElement(elem);
-    if (!sfer.mSourceSurface) {
+
+    const auto& layersImage = sfer.mLayersImage;
+    if (!layersImage && !sfer.GetSourceSurface()) {
         mContext->ErrorInvalidOperation("%s: Failed to get data from DOM element.",
                                         funcName);
         return;
     }
 
     if (sfer.mIsWriteOnly) {
         mContext->GenerateWarning("%s: Element is write-only.", funcName);
         out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
@@ -271,17 +273,17 @@ WebGLTexture::TexOrSubImage(bool isSubIm
         if (!dstPrincipal->Subsumes(srcPrincipal)) {
             mContext->GenerateWarning("%s: Cross-origin elements require CORS.",
                                       funcName);
             out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
             return;
         }
     }
 
-    auto& srcSurf = sfer.mSourceSurface;
+    auto& srcSurf = sfer.GetSourceSurface();
 
     UniquePtr<webgl::TexUnpackBlob> blob;
     blob.reset(new webgl::TexUnpackSurface(srcSurf, sfer.mIsPremultiplied));
 
     const GLint border = 0;
     TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset, yOffset,
                   zOffset, border, unpackFormat, unpackType, blob.get());
 }
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -4765,17 +4765,17 @@ nsImageRenderer::PrepareImage()
         mPrepareResult = DrawResult::BAD_IMAGE;
         return false;
       }
 
       // If the referenced element is an <img>, <canvas>, or <video> element,
       // prefer SurfaceFromElement as it's more reliable.
       mImageElementSurface =
         nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
-      if (!mImageElementSurface.mSourceSurface) {
+      if (!mImageElementSurface.GetSourceSurface()) {
         mPaintServerFrame = property->GetReferencedFrame();
         if (!mPaintServerFrame) {
           mPrepareResult = DrawResult::BAD_IMAGE;
           return false;
         }
       }
 
       mPrepareResult = DrawResult::SUCCESS;
@@ -4850,17 +4850,18 @@ nsImageRenderer::ComputeIntrinsicSize()
             mForFrame->PresContext()->AppUnitsPerDevPixel();
           result.SetSize(
             IntSizeToAppUnits(
               nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
                 ToNearestPixels(appUnitsPerDevPixel),
               appUnitsPerDevPixel));
         }
       } else {
-        NS_ASSERTION(mImageElementSurface.mSourceSurface, "Surface should be ready.");
+        NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
+                     "Surface should be ready.");
         IntSize surfaceSize = mImageElementSurface.mSize;
         result.SetSize(
           nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
                  nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
       }
       break;
     }
     case eStyleImageType_Gradient:
@@ -5090,19 +5091,19 @@ nsImageRenderer::DrawableForElement(cons
       nsSVGIntegrationUtils::DrawableFromPaintServer(
         mPaintServerFrame, mForFrame, mSize, imageSize,
         aRenderingContext.GetDrawTarget(),
         aRenderingContext.ThebesContext()->CurrentMatrix(),
         nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
 
     return drawable.forget();
   }
-  NS_ASSERTION(mImageElementSurface.mSourceSurface, "Surface should be ready.");
+  NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
   RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
-                                mImageElementSurface.mSourceSurface,
+                                mImageElementSurface.GetSourceSurface().get(),
                                 mImageElementSurface.mSize);
   return drawable.forget();
 }
 
 DrawResult
 nsImageRenderer::DrawBackground(nsPresContext*       aPresContext,
                                 nsRenderingContext&  aRenderingContext,
                                 const nsRect&        aDest,
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -1118,17 +1118,17 @@ public:
   };
 
   /**
    * Renders a node aNode to a surface and returns it. The aRegion may be used
    * to clip the rendering. This region is measured in CSS pixels from the
    * edge of the presshell area. The aPoint, aScreenRect and aFlags arguments
    * function in a similar manner as RenderSelection.
    */
-  virtual already_AddRefed<SourceSurface>
+  virtual already_AddRefed<mozilla::gfx::SourceSurface>
   RenderNode(nsIDOMNode* aNode,
              nsIntRegion* aRegion,
              nsIntPoint& aPoint,
              nsIntRect* aScreenRect,
              uint32_t aFlags) = 0;
 
   /**
    * Renders a selection to a surface and returns it. This method is primarily
@@ -1141,17 +1141,17 @@ public:
    * set, the image will be scaled down. The argument aPoint is used in this
    * case as a reference point when determining the new screen rectangle after
    * scaling. Typically, this will be the mouse position, so that the screen
    * rectangle is positioned such that the mouse is over the same point in the
    * scaled image as in the original. When scaling does not occur, the mouse
    * point isn't used because the position can be determined from the displayed
    * frames.
    */
-  virtual already_AddRefed<SourceSurface>
+  virtual already_AddRefed<mozilla::gfx::SourceSurface>
   RenderSelection(nsISelection* aSelection,
                   nsIntPoint& aPoint,
                   nsIntRect* aScreenRect,
                   uint32_t aFlags) = 0;
 
   void AddWeakFrameInternal(nsWeakFrame* aWeakFrame);
   virtual void AddWeakFrameExternal(nsWeakFrame* aWeakFrame);
 
@@ -1260,17 +1260,17 @@ public:
   static CapturingContentInfo gCaptureInfo;
 
   struct PointerCaptureInfo
   {
     nsCOMPtr<nsIContent> mPendingContent;
     nsCOMPtr<nsIContent> mOverrideContent;
     bool                 mReleaseContent;
     bool                 mPrimaryState;
-    
+
     explicit PointerCaptureInfo(nsIContent* aPendingContent, bool aPrimaryState) :
       mPendingContent(aPendingContent), mReleaseContent(false), mPrimaryState(aPrimaryState)
     {
       MOZ_COUNT_CTOR(PointerCaptureInfo);
     }
     ~PointerCaptureInfo()
     {
       MOZ_COUNT_DTOR(PointerCaptureInfo);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -7159,39 +7159,42 @@ nsLayoutUtils::SurfaceFromElement(HTMLVi
     return result;
   }
 
   // If it doesn't have a principal, just bail
   nsCOMPtr<nsIPrincipal> principal = aElement->GetCurrentPrincipal();
   if (!principal)
     return result;
 
-  ImageContainer *container = aElement->GetImageContainer();
+  ImageContainer* container = aElement->GetImageContainer();
   if (!container)
     return result;
 
   AutoLockImage lockImage(container);
-  layers::Image* image = lockImage.GetImage();
-  if (!image) {
-    return result;
-  }
-  result.mSourceSurface = image->GetAsSourceSurface();
-  if (!result.mSourceSurface)
+
+  result.mLayersImage = lockImage.GetImage();
+  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)
+      return result;
+
     RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface);
     if (opt) {
       result.mSourceSurface = opt;
     }
   }
 
   result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE;
   result.mHasSize = true;
-  result.mSize = image->GetSize();
+  result.mSize = result.mLayersImage->GetSize();
   result.mPrincipal = principal.forget();
   result.mIsWriteOnly = false;
 
   return result;
 }
 
 nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromElement(dom::Element* aElement,
@@ -8253,26 +8256,45 @@ nsLayoutUtils::DoLogTestDataForPaint(Lay
 }
 
 /* static */ bool
 nsLayoutUtils::IsAPZTestLoggingEnabled()
 {
   return gfxPrefs::APZTestLoggingEnabled();
 }
 
+////////////////////////////////////////
+// SurfaceFromElementResult
+
 nsLayoutUtils::SurfaceFromElementResult::SurfaceFromElementResult()
   // Use safe default values here
   : mIsWriteOnly(true)
   , mIsStillLoading(false)
   , mHasSize(false)
   , mCORSUsed(false)
   , mIsPremultiplied(true)
 {
 }
 
+nsLayoutUtils::SurfaceFromElementResult::~SurfaceFromElementResult()
+{
+}
+
+const RefPtr<mozilla::gfx::SourceSurface>&
+nsLayoutUtils::SurfaceFromElementResult::GetSourceSurface()
+{
+  if (!mSourceSurface && mLayersImage) {
+    mSourceSurface = mLayersImage->GetAsSourceSurface();
+  }
+
+  return mSourceSurface;
+}
+
+////////////////////////////////////////
+
 bool
 nsLayoutUtils::IsNonWrapperBlock(nsIFrame* aFrame)
 {
   return GetAsBlock(aFrame) && !aFrame->IsBlockWrapper();
 }
 
 bool
 nsLayoutUtils::NeedsPrintPreviewBackground(nsPresContext* aPresContext)
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -62,27 +62,29 @@ struct nsOverflowAreas;
 
 namespace mozilla {
 class EventListenerManager;
 class SVGImageContext;
 struct IntrinsicSize;
 struct ContainerLayerParameters;
 class WritingMode;
 namespace dom {
+class CanvasRenderingContext2D;
 class DOMRectList;
 class Element;
 class HTMLImageElement;
 class HTMLCanvasElement;
 class HTMLVideoElement;
 class Selection;
 } // namespace dom
 namespace gfx {
 struct RectCornerRadii;
 } // namespace gfx
 namespace layers {
+class Image;
 class Layer;
 } // namespace layers
 } // namespace mozilla
 
 namespace mozilla {
 
 struct DisplayPortPropertyData {
   DisplayPortPropertyData(const nsRect& aRect, uint32_t aPriority)
@@ -2090,20 +2092,27 @@ public:
     nsCOMPtr<imgIContainer> mImgContainer;
     /* which frame to draw */
     uint32_t mWhichFrame;
     /* imgIContainer flags to use when drawing */
     uint32_t mDrawingFlags;
   };
 
   struct SurfaceFromElementResult {
-    SurfaceFromElementResult();
+    friend class mozilla::dom::CanvasRenderingContext2D;
+    friend class nsLayoutUtils;
+
+    /* Video elements (at least) often are already decoded as layers::Images. */
+    RefPtr<mozilla::layers::Image> mLayersImage;
 
+protected:
     /* mSourceSurface will contain the resulting surface, or will be nullptr on error */
-    RefPtr<SourceSurface> mSourceSurface;
+    RefPtr<mozilla::gfx::SourceSurface> mSourceSurface;
+
+public:
     /* Contains info for drawing when there is no mSourceSurface. */
     DirectDrawInfo mDrawInfo;
 
     /* The size of the surface */
     mozilla::gfx::IntSize mSize;
     /* The principal associated with the element whose surface was returned.
        If there is a surface, this will never be null. */
     nsCOMPtr<nsIPrincipal> mPrincipal;
@@ -2115,16 +2124,24 @@ public:
        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;
+
+    // Methods:
+
+    SurfaceFromElementResult();
+    ~SurfaceFromElementResult();
+
+    // Gets mSourceSurface, or makes a SourceSurface from mLayersImage.
+    const RefPtr<mozilla::gfx::SourceSurface>& GetSourceSurface();
   };
 
   static SurfaceFromElementResult SurfaceFromElement(mozilla::dom::Element *aElement,
                                                      uint32_t aSurfaceFlags = 0,
                                                      DrawTarget *aTarget = nullptr);
   static SurfaceFromElementResult SurfaceFromElement(nsIImageLoadingContent *aElement,
                                                      uint32_t aSurfaceFlags = 0,
                                                      DrawTarget *aTarget = nullptr);
@@ -2639,17 +2656,17 @@ public:
   /**
    * Returns true if the widget owning the given frame has builtin APZ support
    * enabled.
    */
   static bool AsyncPanZoomEnabled(nsIFrame* aFrame);
 
   /**
    * Log a key/value pair for APZ testing during a paint.
-   * @param aManager   The data will be written to the APZTestData associated 
+   * @param aManager   The data will be written to the APZTestData associated
    *                   with this layer manager.
    * @param aScrollId Identifies the scroll frame to which the data pertains.
    * @param aKey The key under which to log the data.
    * @param aValue The value of the data to be logged.
    */
   static void LogTestDataForPaint(mozilla::layers::LayerManager* aManager,
                                   ViewID aScrollId,
                                   const std::string& aKey,