Bug 1318573 - (Part 11) Move CanvasDrawImage to BasicRenderingContext2D. r?mstange draft
authorKevin Chen <kechen@mozilla.com>
Thu, 23 Feb 2017 11:20:47 +0800
changeset 499864 6ff5325752ff8e1c92559d055385ce2b653e50a5
parent 499863 2db0dd1b074cb148ee576f3ffd97b47f06fccbcd
child 499865 8e7a965eb7d9dd8f8f41d500ba0b2864a390a3d9
push id49564
push userbmo:kechen@mozilla.com
push dateThu, 16 Mar 2017 09:50:15 +0000
reviewersmstange
bugs1318573
milestone54.0a1
Bug 1318573 - (Part 11) Move CanvasDrawImage to BasicRenderingContext2D. r?mstange MozReview-Commit-ID: 7gFvEmEPuN5
dom/canvas/BasicRenderingContext2D.cpp
dom/canvas/BasicRenderingContext2D.h
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
layout/base/nsLayoutUtils.h
--- a/dom/canvas/BasicRenderingContext2D.cpp
+++ b/dom/canvas/BasicRenderingContext2D.cpp
@@ -1,43 +1,62 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AdjustedTarget.h"
+#include "CanvasImageCache.h"
 #include "CanvasUtils.h"
+#include "DrawResult.h"
 #include "gfxUtils.h"
+#include "GLContext.h"
+#include "ImageRegion.h"
 #include "mozilla/dom/BasicRenderingContext2D.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "nsContentUtils.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "nsPrintfCString.h"
 #include "nsStyleUtil.h"
 
+#include "SkiaGLGlue.h"
+#ifdef USE_SKIA
+#include "GLBlitHelper.h"
+#include "SurfaceTypes.h"
+#endif
+
 using mozilla::CanvasUtils::FloatValidate;
 using mozilla::gfx::AntialiasMode;
 using mozilla::gfx::ArcToBezier;
 using mozilla::gfx::CapStyle;
 using mozilla::gfx::Color;
 using mozilla::gfx::DrawOptions;
+using mozilla::gfx::DrawSurfaceOptions;
 using mozilla::gfx::ExtendMode;
 using mozilla::gfx::FillRule;
+using mozilla::gfx::IntPoint;
 using mozilla::gfx::JoinStyle;
+using mozilla::gfx::LogReason;
 using mozilla::gfx::IntSize;
+using mozilla::gfx::NativeSurface;
+using mozilla::gfx::NativeSurfaceType;
 using mozilla::gfx::Path;
+using mozilla::gfx::SamplingBounds;
 using mozilla::gfx::SamplingFilter;
 using mozilla::gfx::Size;
 using mozilla::gfx::SourceSurface;
 using mozilla::gfx::StrokeOptions;
 using mozilla::gfx::ToDeviceColor;
+using mozilla::image::DrawResult;
+using mozilla::image::ImageRegion;
+using mozilla::layers::AutoLockImage;
 
 namespace mozilla {
 namespace dom{
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
@@ -1333,16 +1352,498 @@ BasicRenderingContext2D::SetLineDashOffs
   CurrentState().dashOffset = aOffset;
 }
 
 double
 BasicRenderingContext2D::LineDashOffset() const {
   return CurrentState().dashOffset;
 }
 
+// Returns a surface that contains only the part needed to draw aSourceRect.
+// On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
+// relative to the returned surface.
+static already_AddRefed<SourceSurface>
+ExtractSubrect(SourceSurface* aSurface, gfx::Rect* aSourceRect, DrawTarget* aTargetDT)
+{
+  gfx::Rect roundedOutSourceRect = *aSourceRect;
+  roundedOutSourceRect.RoundOut();
+  gfx::IntRect roundedOutSourceRectInt;
+  if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) {
+    RefPtr<SourceSurface> surface(aSurface);
+    return surface.forget();
+  }
+
+  RefPtr<DrawTarget> subrectDT =
+    aTargetDT->CreateSimilarDrawTarget(roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8);
+
+  if (!subrectDT) {
+    RefPtr<SourceSurface> surface(aSurface);
+    return surface.forget();
+  }
+
+  *aSourceRect -= roundedOutSourceRect.TopLeft();
+
+  subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
+  return subrectDT->Snapshot();
+}
+
+//
+// image
+//
+
+void
+BasicRenderingContext2D::DrawDirectlyToCanvas(
+                         const nsLayoutUtils::DirectDrawInfo& aImage,
+                         gfx::Rect* aBounds,
+                         gfx::Rect aDest,
+                         gfx::Rect aSrc,
+                         gfx::IntSize aImgSize)
+{
+  MOZ_ASSERT(aSrc.width > 0 && aSrc.height > 0,
+             "Need positive source width and height");
+
+  gfxMatrix contextMatrix;
+  AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr: aBounds);
+
+  // Get any existing transforms on the context, including transformations used
+  // for context shadow.
+  if (tempTarget) {
+    Matrix matrix = tempTarget->GetTransform();
+    contextMatrix = gfxMatrix(matrix._11, matrix._12, matrix._21,
+                              matrix._22, matrix._31, matrix._32);
+  }
+  gfxSize contextScale(contextMatrix.ScaleFactors(true));
+
+  // Scale the dest rect to include the context scale.
+  aDest.Scale(contextScale.width, contextScale.height);
+
+  // Scale the image size to the dest rect, and adjust the source rect to match.
+  gfxSize scale(aDest.width / aSrc.width, aDest.height / aSrc.height);
+  IntSize scaledImageSize = IntSize::Ceil(aImgSize.width * scale.width,
+                                          aImgSize.height * scale.height);
+  aSrc.Scale(scale.width, scale.height);
+
+  // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
+  // the matrix even though this is a temp gfxContext.
+  AutoRestoreTransform autoRestoreTransform(mTarget);
+
+  RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget);
+  if (!context) {
+    gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem";
+    return;
+  }
+  context->SetMatrix(contextMatrix.
+                       Scale(1.0 / contextScale.width,
+                             1.0 / contextScale.height).
+                       Translate(aDest.x - aSrc.x, aDest.y - aSrc.y));
+
+  // FLAG_CLAMP is added for increased performance, since we never tile here.
+  uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
+
+  CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels?
+  SVGImageContext svgContext(sz, Nothing(), CurrentState().globalAlpha);
+
+  auto result = aImage.mImgContainer->
+    Draw(context, scaledImageSize,
+         ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)),
+         aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags, 1.0);
+
+  if (result != DrawResult::SUCCESS) {
+    NS_WARNING("imgIContainer::Draw failed");
+  }
+}
+
+
+static void
+ClipImageDimension(double& aSourceCoord, double& aSourceSize, int32_t aImageSize,
+                   double& aDestCoord, double& aDestSize)
+{
+  double scale = aDestSize / aSourceSize;
+  if (aSourceCoord < 0.0) {
+    double destEnd = aDestCoord + aDestSize;
+    aDestCoord -= aSourceCoord * scale;
+    aDestSize = destEnd - aDestCoord;
+    aSourceSize += aSourceCoord;
+    aSourceCoord = 0.0;
+  }
+  double delta = aImageSize - (aSourceCoord + aSourceSize);
+  if (delta < 0.0) {
+    aDestSize += delta * scale;
+    aSourceSize = aImageSize - aSourceCoord;
+  }
+}
+
+bool
+BasicRenderingContext2D::AllowOpenGLCanvas() const
+{
+  // If we somehow didn't have the correct compositor in the constructor,
+  // we could do something like this to get it:
+  //
+  // HTMLCanvasElement* el = GetCanvas();
+  // if (el) {
+  //   mCompositorBackend = el->GetCompositorBackendType();
+  // }
+  //
+  // We could have LAYERS_NONE if there was no widget at the time of
+  // canvas creation, but in that case the
+  // HTMLCanvasElement::GetCompositorBackendType would return LAYERS_NONE
+  // as well, so it wouldn't help much.
+
+  return (mCompositorBackend == layers::LayersBackend::LAYERS_OPENGL) &&
+    gfxPlatform::GetPlatform()->AllowOpenGLCanvas();
+}
+
+// Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
+// to pull a SourceSurface from our cache. This allows us to avoid
+// reoptimizing surfaces if content and canvas backends are different.
+nsLayoutUtils::SurfaceFromElementResult
+BasicRenderingContext2D::CachedSurfaceFromElement(Element* aElement)
+{
+  nsLayoutUtils::SurfaceFromElementResult res;
+  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
+  if (!imageLoader) {
+    return res;
+  }
+
+  nsCOMPtr<imgIRequest> imgRequest;
+  imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+                          getter_AddRefs(imgRequest));
+  if (!imgRequest) {
+    return res;
+  }
+
+  uint32_t status = 0;
+  if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
+      !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+    return res;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal;
+  if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
+      !principal) {
+    return res;
+  }
+
+  res.mSourceSurface =
+    CanvasImageCache::LookupAllCanvas(aElement, mIsSkiaGL);
+  if (!res.mSourceSurface) {
+    return res;
+  }
+
+  int32_t corsmode = imgIRequest::CORS_NONE;
+  if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
+    res.mCORSUsed = corsmode != imgIRequest::CORS_NONE;
+  }
+
+  res.mSize = res.mSourceSurface->GetSize();
+  res.mPrincipal = principal.forget();
+  res.mIsWriteOnly = false;
+  res.mImageRequest = imgRequest.forget();
+
+  return res;
+}
+
+// drawImage(in HTMLImageElement image, in float dx, in float dy);
+//   -- render image from 0,0 at dx,dy top-left coords
+// drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw, in float dh);
+//   -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh
+// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);
+//   -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas
+
+// If only dx and dy are passed in then optional_argc should be 0. If only
+// dx, dy, dw and dh are passed in then optional_argc should be 2. The only
+// other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
+// are all passed in.
+
+void
+BasicRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
+                                   double aSx, double aSy, double aSw,
+                                   double aSh, double aDx, double aDy,
+                                   double aDw, double aDh,
+                                   uint8_t aOptional_argc,
+                                   ErrorResult& aError)
+{
+  DidImageDrawCall();
+
+  MOZ_ASSERT(aOptional_argc == 0 || aOptional_argc == 2 || aOptional_argc == 6);
+
+  if (!ValidateRect(aDx, aDy, aDw, aDh, true)) {
+    return;
+  }
+  if (aOptional_argc == 6) {
+    if (!ValidateRect(aSx, aSy, aSw, aSh, true)) {
+      return;
+    }
+  }
+
+  RefPtr<SourceSurface> srcSurf;
+  gfx::IntSize imgSize;
+
+  Element* element = nullptr;
+
+  EnsureTarget();
+  if (aImage.IsHTMLCanvasElement()) {
+    HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement();
+    element = canvas;
+    nsIntSize size = canvas->GetSize();
+    if (size.width == 0 || size.height == 0) {
+      aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+      return;
+    }
+  } else if (aImage.IsImageBitmap()) {
+    ImageBitmap& imageBitmap = aImage.GetAsImageBitmap();
+    srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
+
+    if (!srcSurf) {
+      return;
+    }
+
+    imgSize = gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
+  }
+  else {
+    if (aImage.IsHTMLImageElement()) {
+      HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
+      element = img;
+    } else {
+      HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
+      video->MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
+      element = video;
+    }
+
+    srcSurf =
+     CanvasImageCache::LookupCanvas(element, GetCanvasElement(), &imgSize, mIsSkiaGL);
+  }
+
+  nsLayoutUtils::DirectDrawInfo drawInfo;
+
+#ifdef USE_SKIA_GPU
+  if (mRenderingMode == RenderingMode::OpenGLBackendMode &&
+      mIsSkiaGL &&
+      !srcSurf &&
+      aImage.IsHTMLVideoElement() &&
+      AllowOpenGLCanvas()) {
+    mozilla::gl::GLContext* gl = gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext();
+    MOZ_ASSERT(gl);
+
+    HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
+    if (!video) {
+      return;
+    }
+
+    if (video->ContainsRestrictedContent()) {
+      aError.Throw(NS_ERROR_NOT_AVAILABLE);
+      return;
+    }
+
+    uint16_t readyState;
+    if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
+        readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
+      // still loading, just return
+      return;
+    }
+
+    // If it doesn't have a principal, just bail
+    nsCOMPtr<nsIPrincipal> principal = video->GetCurrentVideoPrincipal();
+    if (!principal) {
+      aError.Throw(NS_ERROR_NOT_AVAILABLE);
+      return;
+    }
+
+    mozilla::layers::ImageContainer* container = video->GetImageContainer();
+    if (!container) {
+      aError.Throw(NS_ERROR_NOT_AVAILABLE);
+      return;
+    }
+
+    AutoLockImage lockImage(container);
+    layers::Image* srcImage = lockImage.GetImage();
+    if (!srcImage) {
+      aError.Throw(NS_ERROR_NOT_AVAILABLE);
+      return;
+    }
+
+    gl->MakeCurrent();
+    GLuint videoTexture = 0;
+    gl->fGenTextures(1, &videoTexture);
+    // skiaGL expect upload on drawing, and uses texture 0 for texturing,
+    // so we must active texture 0 and bind the texture for it.
+    gl->fActiveTexture(LOCAL_GL_TEXTURE0);
+    gl->fBindTexture(LOCAL_GL_TEXTURE_2D, videoTexture);
+
+    gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
+
+    const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
+    bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, srcImage->GetSize(),
+                                                   videoTexture, LOCAL_GL_TEXTURE_2D,
+                                                   destOrigin);
+    if (ok) {
+      NativeSurface texSurf;
+      texSurf.mType = NativeSurfaceType::OPENGL_TEXTURE;
+      texSurf.mFormat = SurfaceFormat::R5G6B5_UINT16;
+      texSurf.mSize.width = srcImage->GetSize().width;
+      texSurf.mSize.height = srcImage->GetSize().height;
+      texSurf.mSurface = (void*)((uintptr_t)videoTexture);
+
+      srcSurf = mTarget->CreateSourceSurfaceFromNativeSurface(texSurf);
+      if (!srcSurf) {
+        gl->fDeleteTextures(1, &videoTexture);
+      }
+      imgSize.width = srcImage->GetSize().width;
+      imgSize.height = srcImage->GetSize().height;
+
+      int32_t displayWidth = video->VideoWidth();
+      int32_t displayHeight = video->VideoHeight();
+      aSw *= (double)imgSize.width / (double)displayWidth;
+      aSh *= (double)imgSize.height / (double)displayHeight;
+    } else {
+      gl->fDeleteTextures(1, &videoTexture);
+    }
+    srcImage = nullptr;
+
+    if (GetCanvasElement()) {
+      CanvasUtils::DoDrawImageSecurityCheck(GetCanvasElement(),
+                                            principal, false,
+                                            video->GetCORSMode() != CORS_NONE);
+    }
+
+  }
+#endif
+  if (!srcSurf) {
+    // The canvas spec says that drawImage should draw the first frame
+    // of animated images. We also don't want to rasterize vector images.
+    uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME |
+                        nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS;
+
+    nsLayoutUtils::SurfaceFromElementResult res =
+      BasicRenderingContext2D::CachedSurfaceFromElement(element);
+
+    if (!res.mSourceSurface) {
+      res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
+    }
+
+    if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
+      // The spec says to silently do nothing in the following cases:
+      //   - The element is still loading.
+      //   - The image is bad, but it's not in the broken state (i.e., we could
+      //     decode the headers and get the size).
+      if (!res.mIsStillLoading && !res.mHasSize) {
+        aError.Throw(NS_ERROR_NOT_AVAILABLE);
+      }
+      return;
+    }
+
+    imgSize = res.mSize;
+
+    // Scale sw/sh based on aspect ratio
+    if (aImage.IsHTMLVideoElement()) {
+      HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
+      int32_t displayWidth = video->VideoWidth();
+      int32_t displayHeight = video->VideoHeight();
+      aSw *= (double)imgSize.width / (double)displayWidth;
+      aSh *= (double)imgSize.height / (double)displayHeight;
+    }
+
+    if (GetCanvasElement()) {
+      CanvasUtils::DoDrawImageSecurityCheck(GetCanvasElement(),
+                                            res.mPrincipal, res.mIsWriteOnly,
+                                            res.mCORSUsed);
+    }
+
+    if (res.mSourceSurface) {
+      if (res.mImageRequest) {
+        CanvasImageCache::NotifyDrawImage(element, GetCanvasElement(), res.mSourceSurface, imgSize, mIsSkiaGL);
+      }
+      srcSurf = res.mSourceSurface;
+    } else {
+      drawInfo = res.mDrawInfo;
+    }
+  }
+
+  if (aOptional_argc == 0) {
+    aSx = aSy = 0.0;
+    aDw = aSw = (double) imgSize.width;
+    aDh = aSh = (double) imgSize.height;
+  } else if (aOptional_argc == 2) {
+    aSx = aSy = 0.0;
+    aSw = (double) imgSize.width;
+    aSh = (double) imgSize.height;
+  }
+
+  if (aSw == 0.0 || aSh == 0.0) {
+    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return;
+  }
+
+  ClipImageDimension(aSx, aSw, imgSize.width, aDx, aDw);
+  ClipImageDimension(aSy, aSh, imgSize.height, aDy, aDh);
+
+  if (aSw <= 0.0 || aSh <= 0.0 ||
+      aDw <= 0.0 || aDh <= 0.0) {
+    // source and/or destination are fully clipped, so nothing is painted
+    return;
+  }
+
+  SamplingFilter samplingFilter;
+  AntialiasMode antialiasMode;
+
+  if (CurrentState().imageSmoothingEnabled) {
+    samplingFilter = gfx::SamplingFilter::LINEAR;
+    antialiasMode = AntialiasMode::DEFAULT;
+  } else {
+    samplingFilter = gfx::SamplingFilter::POINT;
+    antialiasMode = AntialiasMode::NONE;
+  }
+
+  gfx::Rect bounds;
+
+  if (NeedToCalculateBounds()) {
+    bounds = gfx::Rect(aDx, aDy, aDw, aDh);
+    bounds = mTarget->GetTransform().TransformBounds(bounds);
+  }
+
+  if (!IsTargetValid()) {
+    aError.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  if (srcSurf) {
+    gfx::Rect sourceRect(aSx, aSy, aSw, aSh);
+    if (element == GetCanvasElement()) {
+      // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
+      // trigger a COW copy of the whole canvas into srcSurf. That's a huge
+      // waste if sourceRect doesn't cover the whole canvas.
+      // We avoid copying the whole canvas by manually copying just the part
+      // that we need.
+      srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
+    }
+
+    AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds);
+    if (!tempTarget) {
+      gfxDevCrash(LogReason::InvalidDrawTarget) << "Invalid adjusted target in Canvas2D " << gfx::hexa((DrawTarget*)mTarget) << ", " << NeedToDrawShadow() << NeedToApplyFilter();
+      return;
+    }
+    tempTarget->DrawSurface(srcSurf,
+                  gfx::Rect(aDx, aDy, aDw, aDh),
+                  sourceRect,
+                  DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED),
+                  DrawOptions(CurrentState().globalAlpha, UsedOperation(), antialiasMode));
+  } else {
+    DrawDirectlyToCanvas(drawInfo, &bounds,
+                         gfx::Rect(aDx, aDy, aDw, aDh),
+                         gfx::Rect(aSx, aSy, aSw, aSh),
+                         imgSize);
+  }
+
+  RedrawUser(gfxRect(aDx, aDy, aDw, aDh));
+}
+
 Pattern&
 CanvasGeneralPattern::ForStyle(BasicRenderingContext2D* aCtx,
                                BasicRenderingContext2D::Style aStyle,
                                DrawTarget* aRT)
 {
   // This should only be called once or the mPattern destructor will
   // not be executed.
   NS_ASSERTION(!mPattern.GetPattern(), "ForStyle() should only be called once on CanvasGeneralPattern!");
@@ -1368,17 +1869,16 @@ CanvasGeneralPattern::ForStyle(BasicRend
                                        gradient->GetGradientStopsForTarget(aRT));
   } else if (state.patternStyles[aStyle]) {
     if (aCtx->GetCanvasElement()) {
       CanvasUtils::DoDrawImageSecurityCheck(aCtx->GetCanvasElement(),
                                             state.patternStyles[aStyle]->mPrincipal,
                                             state.patternStyles[aStyle]->mForceWriteOnly,
                                             state.patternStyles[aStyle]->mCORSUsed);
     }
-
     ExtendMode mode;
     if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) {
       mode = ExtendMode::CLAMP;
     } else {
       mode = ExtendMode::REPEAT;
     }
 
     SamplingFilter samplingFilter;
--- a/dom/canvas/BasicRenderingContext2D.h
+++ b/dom/canvas/BasicRenderingContext2D.h
@@ -75,24 +75,30 @@ public:
     } else {
       mozilla::gfx::Matrix transform = mTarget->GetTransform();
       mDSPathBuilder->BezierTo(transform.TransformPoint(aCP1),
                                transform.TransformPoint(aCP2),
                                transform.TransformPoint(aCP3));
     }
   }
 
+  // Check the global setup, as well as the compositor type:
+  bool AllowOpenGLCanvas() const;
+
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_BASICRENDERINGCONTEXT2D_IID)
 protected:
   virtual ~BasicRenderingContext2D() {}
 public:
   explicit BasicRenderingContext2D(layers::LayersBackend aCompositorBackend)
-    // these are the default values from the Canvas spec
-    : mWidth(0), mHeight(0)
-    , mPathTransformWillUpdate(false) {}
+    : mRenderingMode(RenderingMode::OpenGLBackendMode)
+    , mCompositorBackend(aCompositorBackend)
+      // these are the default values from the Canvas spec
+    , mWidth(0), mHeight(0)
+    , mPathTransformWillUpdate(false)
+    , mIsSkiaGL(false) {}
 
   //
   // CanvasState
   //
   void Save();
   void Restore();
 
   //
@@ -244,36 +250,36 @@ public:
   bool IsPointInPath(double aX, double aY, const CanvasWindingRule& aWinding);
   bool IsPointInPath(const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding);
   bool IsPointInStroke(double aX, double aY);
   bool IsPointInStroke(const CanvasPath& aPath, double aX, double aY);
 
   //
   // CanvasDrawImage
   //
-  virtual void DrawImage(const CanvasImageSource& aImage,
-                         double aDx,
-                         double aDy,
-                         mozilla::ErrorResult& aError) = 0;
-  virtual void DrawImage(const CanvasImageSource& aImage,
-                         double aDx,
-                         double aDy,
-                         double aDw,
-                         double aDh,
-                         mozilla::ErrorResult& aError) = 0;
-  virtual void DrawImage(const CanvasImageSource& aImage,
-                         double aSx,
-                         double aSy,
-                         double aSw,
-                         double aSh,
-                         double aDx,
-                         double aDy,
-                         double aDw,
-                         double aDh,
-                         mozilla::ErrorResult& aError) = 0;
+  void DrawImage(const CanvasImageSource& aImage, double aDx, double aDy,
+                 mozilla::ErrorResult& aError)
+  {
+    DrawImage(aImage, 0.0, 0.0, 0.0, 0.0, aDx, aDy, 0.0, 0.0, 0, aError);
+  }
+
+  void DrawImage(const CanvasImageSource& aImage, double aDx, double aDy,
+                 double aDw, double aDh, mozilla::ErrorResult& aError)
+  {
+    DrawImage(aImage, 0.0, 0.0, 0.0, 0.0, aDx, aDy, aDw, aDh, 2, aError);
+  }
+
+  void DrawImage(const CanvasImageSource& aImage,
+                 double aSx, double aSy, double aSw, double aSh,
+                 double aDx, double aDy, double aDw, double aDh,
+                 mozilla::ErrorResult& aError)
+  {
+    DrawImage(aImage, aSx, aSy, aSw, aSh, aDx, aDy, aDw, aDh, 6, aError);
+  }
+
 
   //
   // CanvasPathDrawingStyles
   //
   double LineWidth()
   {
     return CurrentState().lineWidth;
   }
@@ -563,16 +569,21 @@ protected:
   // tainted state of the canvas itself, we update our filters accordingly.
   bool filterSourceGraphicTainted;
 
   bool imageSmoothingEnabled;
   bool fontExplicitLanguage;
   };
 
   // Member vars
+
+  RenderingMode mRenderingMode;
+
+  layers::LayersBackend mCompositorBackend;
+
   AutoTArray<ContextState, 3> mStyleStack;
 
   int32_t mWidth, mHeight;
 
   /**
     * We also have a device space pathbuilder. The reason for this is as
     * follows, when a path is being built, but the transform changes, we
     * can no longer keep a single path in userspace, considering there's
@@ -622,16 +633,20 @@ protected:
     * builder present at the same time. There is also never a path and a
     * path builder present at the same time. When writing proceeds on an
     * existing path the Path is cleared and a new builder is created.
     *
     * mPath is always in user-space.
     */
   RefPtr<mozilla::gfx::PathBuilder> mDSPathBuilder;
 
+  // True if the current DrawTarget is using skia-gl, used so we can avoid
+  // requesting the DT from mBufferProvider to check.
+  bool mIsSkiaGL;
+
 protected:
   virtual HTMLCanvasElement* GetCanvasElement() = 0;
 
   virtual bool AlreadyShutDown() const = 0;
 
   /**
    * Create the backing surfacing, if it doesn't exist. If there is an error
    * in creating the target then it will put sErrorTarget in place. If there
@@ -652,16 +667,21 @@ protected:
   inline ContextState& CurrentState() {
     return mStyleStack[mStyleStack.Length() - 1];
   }
 
   inline const ContextState& CurrentState() const {
     return mStyleStack[mStyleStack.Length() - 1];
   }
 
+  void DrawImage(const CanvasImageSource& aImgElt,
+                 double aSx, double aSy, double aSw, double aSh,
+                 double aDx, double aDy, double aDw, double aDh,
+                 uint8_t aOptional_argc, mozilla::ErrorResult& aError);
+
   /**
    * Needs to be called before updating the transform. This makes a call to
    * EnsureTarget() so you don't have to.
    */
   void TransformWillUpdate();
 
   void SetTransformInternal(const Matrix& aTransform);
 
@@ -720,30 +740,42 @@ protected:
   }
 
   /**
     * Returns true if the result of a drawing operation should be
     * drawn with a filter.
     */
   virtual bool NeedToApplyFilter() = 0;
 
+  virtual void DidImageDrawCall() = 0;
+
   bool NeedToCalculateBounds()
   {
     return NeedToDrawShadow() || NeedToApplyFilter();
   }
 
   // Ensures a path in UserSpace is available.
   void EnsureUserSpacePath(const CanvasWindingRule& aWinding = CanvasWindingRule::Nonzero);
 
   /* This function ensures there is a writable pathbuilder available, this
    * pathbuilder may be working in user space or in device space or
    * device space.
    * After calling this function mPathTransformWillUpdate will be false
    */
   void EnsureWritablePath();
+
+  nsLayoutUtils::SurfaceFromElementResult
+    CachedSurfaceFromElement(Element* aElement);
+
+
+  void DrawDirectlyToCanvas(const nsLayoutUtils::DirectDrawInfo& aImage,
+                            mozilla::gfx::Rect* aBounds,
+                            mozilla::gfx::Rect aDest,
+                            mozilla::gfx::Rect aSrc,
+                            gfx::IntSize aImgSize);
 };
 
  NS_DEFINE_STATIC_IID_ACCESSOR(BasicRenderingContext2D,
                                NS_BASICRENDERINGCONTEXT2D_IID)
 
 // This class is named 'GeneralCanvasPattern' instead of just
 // 'GeneralPattern' to keep Windows PGO builds from confusing the
 // GeneralPattern class in gfxContext.cpp with this one.
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -40,17 +40,16 @@
 #include "nsPIDOMWindow.h"
 #include "nsDisplayList.h"
 #include "nsFocusManager.h"
 #include "nsContentUtils.h"
 
 #include "nsTArray.h"
 
 #include "ImageEncoder.h"
-#include "ImageRegion.h"
 
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 #include "gfxFont.h"
 #include "gfxBlur.h"
 #include "gfxPrefs.h"
 
 #include "nsFrameLoader.h"
@@ -485,22 +484,19 @@ uint32_t CanvasRenderingContext2D::sNumL
 DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr;
 static bool sMaxContextsInitialized = false;
 static int32_t sMaxContexts = 0;
 
 
 
 CanvasRenderingContext2D::CanvasRenderingContext2D(layers::LayersBackend aCompositorBackend)
   : BasicRenderingContext2D(aCompositorBackend)
-  , mRenderingMode(RenderingMode::OpenGLBackendMode)
-  , mCompositorBackend(aCompositorBackend)
   , mZero(false), mOpaque(false)
   , mResetLayer(true)
   , mIPC(false)
-  , mIsSkiaGL(false)
   , mHasPendingStableStateCallback(false)
   , mDrawObserver(nullptr)
   , mIsEntireFrameInvalid(false)
   , mPredictManyRedrawCalls(false)
   , mIsCapturedFrameInvalid(false)
   , mInvalidateCount(0)
 {
   if (!sMaxContextsInitialized) {
@@ -628,16 +624,24 @@ void
 CanvasRenderingContext2D::RemoveShutdownObserver()
 {
   if (mShutdownObserver) {
     nsContentUtils::UnregisterShutdownObserver(mShutdownObserver);
     mShutdownObserver = nullptr;
   }
 }
 
+void
+CanvasRenderingContext2D::DidImageDrawCall()
+{
+  if (mDrawObserver) {
+    mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage);
+  }
+}
+
 // static
 nsresult
 CanvasRenderingContext2D::Redraw()
 {
   mIsCapturedFrameInvalid = true;
 
   if (mIsEntireFrameInvalid) {
     return NS_OK;
@@ -705,36 +709,16 @@ CanvasRenderingContext2D::RedrawUser(con
     ++mInvalidateCount;
     return;
   }
 
   gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR));
   Redraw(newr);
 }
 
-bool
-CanvasRenderingContext2D::AllowOpenGLCanvas() const
-{
-  // If we somehow didn't have the correct compositor in the constructor,
-  // we could do something like this to get it:
-  //
-  // HTMLCanvasElement* el = GetCanvas();
-  // if (el) {
-  //   mCompositorBackend = el->GetCompositorBackendType();
-  // }
-  //
-  // We could have LAYERS_NONE if there was no widget at the time of
-  // canvas creation, but in that case the
-  // HTMLCanvasElement::GetCompositorBackendType would return LAYERS_NONE
-  // as well, so it wouldn't help much.
-
-  return (mCompositorBackend == LayersBackend::LAYERS_OPENGL) &&
-    gfxPlatform::GetPlatform()->AllowOpenGLCanvas();
-}
-
 bool CanvasRenderingContext2D::SwitchRenderingMode(RenderingMode aRenderingMode)
 {
   if (!(IsTargetValid() || mBufferProvider) || mRenderingMode == aRenderingMode) {
     return false;
   }
 
   MOZ_ASSERT(mBufferProvider);
 
@@ -2918,478 +2902,19 @@ gfxFontGroup *CanvasRenderingContext2D::
         NS_ERROR("Default canvas font is invalid");
       }
     }
   }
 
   return CurrentState().fontGroup;
 }
 
-// Returns a surface that contains only the part needed to draw aSourceRect.
-// On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
-// relative to the returned surface.
-static already_AddRefed<SourceSurface>
-ExtractSubrect(SourceSurface* aSurface, gfx::Rect* aSourceRect, DrawTarget* aTargetDT)
-{
-  gfx::Rect roundedOutSourceRect = *aSourceRect;
-  roundedOutSourceRect.RoundOut();
-  gfx::IntRect roundedOutSourceRectInt;
-  if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) {
-    RefPtr<SourceSurface> surface(aSurface);
-    return surface.forget();
-  }
-
-  RefPtr<DrawTarget> subrectDT =
-    aTargetDT->CreateSimilarDrawTarget(roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8);
-
-  if (!subrectDT) {
-    RefPtr<SourceSurface> surface(aSurface);
-    return surface.forget();
-  }
-
-  *aSourceRect -= roundedOutSourceRect.TopLeft();
-
-  subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
-  return subrectDT->Snapshot();
-}
-
 //
 // image
 //
-
-static void
-ClipImageDimension(double& aSourceCoord, double& aSourceSize, int32_t aImageSize,
-                   double& aDestCoord, double& aDestSize)
-{
-  double scale = aDestSize / aSourceSize;
-  if (aSourceCoord < 0.0) {
-    double destEnd = aDestCoord + aDestSize;
-    aDestCoord -= aSourceCoord * scale;
-    aDestSize = destEnd - aDestCoord;
-    aSourceSize += aSourceCoord;
-    aSourceCoord = 0.0;
-  }
-  double delta = aImageSize - (aSourceCoord + aSourceSize);
-  if (delta < 0.0) {
-    aDestSize += delta * scale;
-    aSourceSize = aImageSize - aSourceCoord;
-  }
-}
-
-// Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
-// to pull a SourceSurface from our cache. This allows us to avoid
-// reoptimizing surfaces if content and canvas backends are different.
-nsLayoutUtils::SurfaceFromElementResult
-CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement)
-{
-  nsLayoutUtils::SurfaceFromElementResult res;
-  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
-  if (!imageLoader) {
-    return res;
-  }
-
-  nsCOMPtr<imgIRequest> imgRequest;
-  imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
-                          getter_AddRefs(imgRequest));
-  if (!imgRequest) {
-    return res;
-  }
-
-  uint32_t status = 0;
-  if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
-      !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
-    return res;
-  }
-
-  nsCOMPtr<nsIPrincipal> principal;
-  if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
-      !principal) {
-    return res;
-  }
-
-  res.mSourceSurface =
-    CanvasImageCache::LookupAllCanvas(aElement, mIsSkiaGL);
-  if (!res.mSourceSurface) {
-    return res;
-  }
-
-  int32_t corsmode = imgIRequest::CORS_NONE;
-  if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
-    res.mCORSUsed = corsmode != imgIRequest::CORS_NONE;
-  }
-
-  res.mSize = res.mSourceSurface->GetSize();
-  res.mPrincipal = principal.forget();
-  res.mIsWriteOnly = false;
-  res.mImageRequest = imgRequest.forget();
-
-  return res;
-}
-
-// drawImage(in HTMLImageElement image, in float dx, in float dy);
-//   -- render image from 0,0 at dx,dy top-left coords
-// drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw, in float dh);
-//   -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh
-// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);
-//   -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas
-
-// If only dx and dy are passed in then optional_argc should be 0. If only
-// dx, dy, dw and dh are passed in then optional_argc should be 2. The only
-// other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
-// are all passed in.
-
-void
-CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
-                                    double aSx, double aSy, double aSw,
-                                    double aSh, double aDx, double aDy,
-                                    double aDw, double aDh,
-                                    uint8_t aOptional_argc,
-                                    ErrorResult& aError)
-{
-  if (mDrawObserver) {
-    mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage);
-  }
-
-  MOZ_ASSERT(aOptional_argc == 0 || aOptional_argc == 2 || aOptional_argc == 6);
-
-  if (!ValidateRect(aDx, aDy, aDw, aDh, true)) {
-    return;
-  }
-  if (aOptional_argc == 6) {
-    if (!ValidateRect(aSx, aSy, aSw, aSh, true)) {
-      return;
-    }
-  }
-
-  RefPtr<SourceSurface> srcSurf;
-  gfx::IntSize imgSize;
-
-  Element* element = nullptr;
-
-  EnsureTarget();
-  if (aImage.IsHTMLCanvasElement()) {
-    HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement();
-    element = canvas;
-    nsIntSize size = canvas->GetSize();
-    if (size.width == 0 || size.height == 0) {
-      aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-      return;
-    }
-  } else if (aImage.IsImageBitmap()) {
-    ImageBitmap& imageBitmap = aImage.GetAsImageBitmap();
-    srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
-
-    if (!srcSurf) {
-      return;
-    }
-
-    imgSize = gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
-  }
-  else {
-    if (aImage.IsHTMLImageElement()) {
-      HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
-      element = img;
-    } else {
-      HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
-      video->MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
-      element = video;
-    }
-
-    srcSurf =
-     CanvasImageCache::LookupCanvas(element, mCanvasElement, &imgSize, mIsSkiaGL);
-  }
-
-  nsLayoutUtils::DirectDrawInfo drawInfo;
-
-#ifdef USE_SKIA_GPU
-  if (mRenderingMode == RenderingMode::OpenGLBackendMode &&
-      mIsSkiaGL &&
-      !srcSurf &&
-      aImage.IsHTMLVideoElement() &&
-      AllowOpenGLCanvas()) {
-    mozilla::gl::GLContext* gl = gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext();
-    MOZ_ASSERT(gl);
-
-    HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
-    if (!video) {
-      return;
-    }
-
-    if (video->ContainsRestrictedContent()) {
-      aError.Throw(NS_ERROR_NOT_AVAILABLE);
-      return;
-    }
-
-    uint16_t readyState;
-    if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
-        readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
-      // still loading, just return
-      return;
-    }
-
-    // If it doesn't have a principal, just bail
-    nsCOMPtr<nsIPrincipal> principal = video->GetCurrentVideoPrincipal();
-    if (!principal) {
-      aError.Throw(NS_ERROR_NOT_AVAILABLE);
-      return;
-    }
-
-    mozilla::layers::ImageContainer* container = video->GetImageContainer();
-    if (!container) {
-      aError.Throw(NS_ERROR_NOT_AVAILABLE);
-      return;
-    }
-
-    AutoLockImage lockImage(container);
-    layers::Image* srcImage = lockImage.GetImage();
-    if (!srcImage) {
-      aError.Throw(NS_ERROR_NOT_AVAILABLE);
-      return;
-    }
-
-    gl->MakeCurrent();
-    GLuint videoTexture = 0;
-    gl->fGenTextures(1, &videoTexture);
-    // skiaGL expect upload on drawing, and uses texture 0 for texturing,
-    // so we must active texture 0 and bind the texture for it.
-    gl->fActiveTexture(LOCAL_GL_TEXTURE0);
-    gl->fBindTexture(LOCAL_GL_TEXTURE_2D, videoTexture);
-
-    gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
-    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
-    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
-    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
-
-    const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
-    bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, srcImage->GetSize(),
-                                                   videoTexture, LOCAL_GL_TEXTURE_2D,
-                                                   destOrigin);
-    if (ok) {
-      NativeSurface texSurf;
-      texSurf.mType = NativeSurfaceType::OPENGL_TEXTURE;
-      texSurf.mFormat = SurfaceFormat::R5G6B5_UINT16;
-      texSurf.mSize.width = srcImage->GetSize().width;
-      texSurf.mSize.height = srcImage->GetSize().height;
-      texSurf.mSurface = (void*)((uintptr_t)videoTexture);
-
-      srcSurf = mTarget->CreateSourceSurfaceFromNativeSurface(texSurf);
-      if (!srcSurf) {
-        gl->fDeleteTextures(1, &videoTexture);
-      }
-      imgSize.width = srcImage->GetSize().width;
-      imgSize.height = srcImage->GetSize().height;
-
-      int32_t displayWidth = video->VideoWidth();
-      int32_t displayHeight = video->VideoHeight();
-      aSw *= (double)imgSize.width / (double)displayWidth;
-      aSh *= (double)imgSize.height / (double)displayHeight;
-    } else {
-      gl->fDeleteTextures(1, &videoTexture);
-    }
-    srcImage = nullptr;
-
-    if (mCanvasElement) {
-      CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
-                                            principal, false,
-                                            video->GetCORSMode() != CORS_NONE);
-    }
-  }
-#endif
-  if (!srcSurf) {
-    // The canvas spec says that drawImage should draw the first frame
-    // of animated images. We also don't want to rasterize vector images.
-    uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME |
-                        nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS;
-
-    nsLayoutUtils::SurfaceFromElementResult res =
-      CanvasRenderingContext2D::CachedSurfaceFromElement(element);
-
-    if (!res.mSourceSurface) {
-      res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
-    }
-
-    if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
-      // The spec says to silently do nothing in the following cases:
-      //   - The element is still loading.
-      //   - The image is bad, but it's not in the broken state (i.e., we could
-      //     decode the headers and get the size).
-      if (!res.mIsStillLoading && !res.mHasSize) {
-        aError.Throw(NS_ERROR_NOT_AVAILABLE);
-      }
-      return;
-    }
-
-    imgSize = res.mSize;
-
-    // Scale sw/sh based on aspect ratio
-    if (aImage.IsHTMLVideoElement()) {
-      HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
-      int32_t displayWidth = video->VideoWidth();
-      int32_t displayHeight = video->VideoHeight();
-      aSw *= (double)imgSize.width / (double)displayWidth;
-      aSh *= (double)imgSize.height / (double)displayHeight;
-    }
-
-    if (mCanvasElement) {
-      CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
-                                            res.mPrincipal, res.mIsWriteOnly,
-                                            res.mCORSUsed);
-    }
-
-    if (res.mSourceSurface) {
-      if (res.mImageRequest) {
-        CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mSourceSurface, imgSize, mIsSkiaGL);
-      }
-      srcSurf = res.mSourceSurface;
-    } else {
-      drawInfo = res.mDrawInfo;
-    }
-  }
-
-  if (aOptional_argc == 0) {
-    aSx = aSy = 0.0;
-    aDw = aSw = (double) imgSize.width;
-    aDh = aSh = (double) imgSize.height;
-  } else if (aOptional_argc == 2) {
-    aSx = aSy = 0.0;
-    aSw = (double) imgSize.width;
-    aSh = (double) imgSize.height;
-  }
-
-  if (aSw == 0.0 || aSh == 0.0) {
-    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
-    return;
-  }
-
-  ClipImageDimension(aSx, aSw, imgSize.width, aDx, aDw);
-  ClipImageDimension(aSy, aSh, imgSize.height, aDy, aDh);
-
-  if (aSw <= 0.0 || aSh <= 0.0 ||
-      aDw <= 0.0 || aDh <= 0.0) {
-    // source and/or destination are fully clipped, so nothing is painted
-    return;
-  }
-
-  SamplingFilter samplingFilter;
-  AntialiasMode antialiasMode;
-
-  if (CurrentState().imageSmoothingEnabled) {
-    samplingFilter = gfx::SamplingFilter::LINEAR;
-    antialiasMode = AntialiasMode::DEFAULT;
-  } else {
-    samplingFilter = gfx::SamplingFilter::POINT;
-    antialiasMode = AntialiasMode::NONE;
-  }
-
-  gfx::Rect bounds;
-
-  if (NeedToCalculateBounds()) {
-    bounds = gfx::Rect(aDx, aDy, aDw, aDh);
-    bounds = mTarget->GetTransform().TransformBounds(bounds);
-  }
-
-  if (!IsTargetValid()) {
-    aError.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  if (srcSurf) {
-    gfx::Rect sourceRect(aSx, aSy, aSw, aSh);
-    if (element == mCanvasElement) {
-      // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
-      // trigger a COW copy of the whole canvas into srcSurf. That's a huge
-      // waste if sourceRect doesn't cover the whole canvas.
-      // We avoid copying the whole canvas by manually copying just the part
-      // that we need.
-      srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
-    }
-
-    AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds);
-    if (!tempTarget) {
-      gfxDevCrash(LogReason::InvalidDrawTarget) << "Invalid adjusted target in Canvas2D " << gfx::hexa((DrawTarget*)mTarget) << ", " << NeedToDrawShadow() << NeedToApplyFilter();
-      return;
-    }
-    tempTarget->DrawSurface(srcSurf,
-                  gfx::Rect(aDx, aDy, aDw, aDh),
-                  sourceRect,
-                  DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED),
-                  DrawOptions(CurrentState().globalAlpha, UsedOperation(), antialiasMode));
-  } else {
-    DrawDirectlyToCanvas(drawInfo, &bounds,
-                         gfx::Rect(aDx, aDy, aDw, aDh),
-                         gfx::Rect(aSx, aSy, aSw, aSh),
-                         imgSize);
-  }
-
-  RedrawUser(gfxRect(aDx, aDy, aDw, aDh));
-}
-
-void
-CanvasRenderingContext2D::DrawDirectlyToCanvas(
-                          const nsLayoutUtils::DirectDrawInfo& aImage,
-                          gfx::Rect* aBounds,
-                          gfx::Rect aDest,
-                          gfx::Rect aSrc,
-                          gfx::IntSize aImgSize)
-{
-  MOZ_ASSERT(aSrc.width > 0 && aSrc.height > 0,
-             "Need positive source width and height");
-
-  gfxMatrix contextMatrix;
-  AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr: aBounds);
-
-  // Get any existing transforms on the context, including transformations used
-  // for context shadow.
-  if (tempTarget) {
-    Matrix matrix = tempTarget->GetTransform();
-    contextMatrix = gfxMatrix(matrix._11, matrix._12, matrix._21,
-                              matrix._22, matrix._31, matrix._32);
-  }
-  gfxSize contextScale(contextMatrix.ScaleFactors(true));
-
-  // Scale the dest rect to include the context scale.
-  aDest.Scale(contextScale.width, contextScale.height);
-
-  // Scale the image size to the dest rect, and adjust the source rect to match.
-  gfxSize scale(aDest.width / aSrc.width, aDest.height / aSrc.height);
-  IntSize scaledImageSize = IntSize::Ceil(aImgSize.width * scale.width,
-                                          aImgSize.height * scale.height);
-  aSrc.Scale(scale.width, scale.height);
-
-  // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
-  // the matrix even though this is a temp gfxContext.
-  AutoRestoreTransform autoRestoreTransform(mTarget);
-
-  RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget);
-  if (!context) {
-    gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem";
-    return;
-  }
-  context->SetMatrix(contextMatrix.
-                       Scale(1.0 / contextScale.width,
-                             1.0 / contextScale.height).
-                       Translate(aDest.x - aSrc.x, aDest.y - aSrc.y));
-
-  // FLAG_CLAMP is added for increased performance, since we never tile here.
-  uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
-
-  CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels?
-  SVGImageContext svgContext(sz, Nothing(), CurrentState().globalAlpha);
-
-  auto result = aImage.mImgContainer->
-    Draw(context, scaledImageSize,
-         ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)),
-         aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags, 1.0);
-
-  if (result != DrawResult::SUCCESS) {
-    NS_WARNING("imgIContainer::Draw failed");
-  }
-}
-
 void
 CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& aWindow, double aX,
                                      double aY, double aW, double aH,
                                      const nsAString& aBgColor,
                                      uint32_t aFlags, ErrorResult& aError)
 {
   MOZ_ASSERT(aWindow.IsInnerWindow());
 
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -90,36 +90,16 @@ public:
                   mozilla::ErrorResult& aError);
   TextMetrics*
     MeasureText(const nsAString& aRawText, mozilla::ErrorResult& aError);
 
   void AddHitRegion(const HitRegionOptions& aOptions, mozilla::ErrorResult& aError);
   void RemoveHitRegion(const nsAString& aId);
   void ClearHitRegions();
 
-  void DrawImage(const CanvasImageSource& aImage, double aDx, double aDy,
-                 mozilla::ErrorResult& aError) override
-  {
-    DrawImage(aImage, 0.0, 0.0, 0.0, 0.0, aDx, aDy, 0.0, 0.0, 0, aError);
-  }
-
-  void DrawImage(const CanvasImageSource& aImage, double aDx, double aDy,
-                 double aDw, double aDh, mozilla::ErrorResult& aError) override
-  {
-    DrawImage(aImage, 0.0, 0.0, 0.0, 0.0, aDx, aDy, aDw, aDh, 2, aError);
-  }
-
-  void DrawImage(const CanvasImageSource& aImage,
-                 double aSx, double aSy, double aSw, double aSh,
-                 double aDx, double aDy, double aDw, double aDh,
-                 mozilla::ErrorResult& aError) override
-  {
-    DrawImage(aImage, aSx, aSy, aSw, aSh, aDx, aDy, aDw, aDh, 6, aError);
-  }
-
   already_AddRefed<ImageData>
     CreateImageData(JSContext* aCx, double aSw, double aSh,
                     mozilla::ErrorResult& aError);
   already_AddRefed<ImageData>
     CreateImageData(JSContext* aCx, ImageData& aImagedata,
                     mozilla::ErrorResult& aError);
   already_AddRefed<ImageData>
     GetImageData(JSContext* aCx, double aSx, double aSy, double aSw, double aSh,
@@ -270,19 +250,16 @@ public:
   nsString GetHitRegion(const mozilla::gfx::Point& aPoint) override;
 
 
   // return true and fills in the bound rect if element has a hit region.
   bool GetHitRegionRect(Element* aElement, nsRect& aRect) override;
 
   void OnShutdown();
 
-  // Check the global setup, as well as the compositor type:
-  bool AllowOpenGLCanvas() const;
-
 protected:
   HTMLCanvasElement* GetCanvasElement() override { return mCanvasElement; }
   nsresult GetImageDataArray(JSContext* aCx, int32_t aX, int32_t aY,
                              uint32_t aWidth, uint32_t aHeight,
                              JSObject** aRetval);
 
   nsresult PutImageData_explicit(int32_t aX, int32_t aY, uint32_t aW, uint32_t aH,
                                  dom::Uint8ClampedArray* aArray,
@@ -401,30 +378,16 @@ protected:
 
   /**
    * Update CurrentState().filter with the filter description for
    * CurrentState().filterChain.
    * Flushes the PresShell, so the world can change if you call this function.
    */
   void UpdateFilter();
 
-  nsLayoutUtils::SurfaceFromElementResult
-    CachedSurfaceFromElement(Element* aElement);
-
-  void DrawImage(const CanvasImageSource& aImgElt,
-                 double aSx, double aSy, double aSw, double aSh,
-                 double aDx, double aDy, double aDw, double aDh,
-                 uint8_t aOptional_argc, mozilla::ErrorResult& aError);
-
-  void DrawDirectlyToCanvas(const nsLayoutUtils::DirectDrawInfo& aImage,
-                            mozilla::gfx::Rect* aBounds,
-                            mozilla::gfx::Rect aDest,
-                            mozilla::gfx::Rect aSrc,
-                            gfx::IntSize aImgSize);
-
   nsString& GetFont()
   {
     /* will initilize the value if not set, else does nothing */
     GetCurrentFontStyle();
 
     return CurrentState().font;
   }
 
@@ -432,36 +395,29 @@ protected:
   // objects. We need to ensure that no entries persist beyond unlink,
   // since the objects are logically destructed at that point.
   static std::vector<CanvasRenderingContext2D*>& DemotableContexts();
   static void DemoteOldestContextIfNecessary();
 
   static void AddDemotableContext(CanvasRenderingContext2D* aContext);
   static void RemoveDemotableContext(CanvasRenderingContext2D* aContext);
 
-  RenderingMode mRenderingMode;
-
-  layers::LayersBackend mCompositorBackend;
-
   // Member vars
 
   // This is true when the canvas is valid, but of zero size, this requires
   // specific behavior on some operations.
   bool mZero;
 
   bool mOpaque;
 
   // This is true when the next time our layer is retrieved we need to
   // recreate it (i.e. our backing surface changed)
   bool mResetLayer;
   // This is needed for drawing in drawAsyncXULElement
   bool mIPC;
-  // True if the current DrawTarget is using skia-gl, used so we can avoid
-  // requesting the DT from mBufferProvider to check.
-  bool mIsSkiaGL;
 
   bool mHasPendingStableStateCallback;
 
   nsTArray<CanvasRenderingContext2DUserData*> mUserDatas;
 
   // If mCanvasElement is not provided, then a docshell is
   nsCOMPtr<nsIDocShell> mDocShell;
 
@@ -469,16 +425,17 @@ protected:
 
   uint32_t SkiaGLTex() const;
 
   // This observes our draw calls at the beginning of the canvas
   // lifetime and switches to software or GPU mode depending on
   // what it thinks is best
   CanvasDrawObserver* mDrawObserver;
   void RemoveDrawObserver();
+  void DidImageDrawCall() override;
 
   RefPtr<CanvasShutdownObserver> mShutdownObserver;
   void RemoveShutdownObserver();
   virtual bool AlreadyShutDown() const override{ return !mShutdownObserver; }
 
   /**
     * Flag to avoid duplicate calls to InvalidateFrame. Set to true whenever
     * Redraw is called, reset to false when Render is called.
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -65,17 +65,17 @@ struct nsOverflowAreas;
 
 namespace mozilla {
 enum class CSSPseudoElementType : uint8_t;
 class EventListenerManager;
 struct IntrinsicSize;
 struct ContainerLayerParameters;
 class WritingMode;
 namespace dom {
-class CanvasRenderingContext2D;
+class BasicRenderingContext2D;
 class DOMRectList;
 class Element;
 class HTMLImageElement;
 class HTMLCanvasElement;
 class HTMLVideoElement;
 class OffscreenCanvas;
 class Selection;
 } // namespace dom
@@ -2093,17 +2093,17 @@ public:
     nsCOMPtr<imgIContainer> mImgContainer;
     /* which frame to draw */
     uint32_t mWhichFrame;
     /* imgIContainer flags to use when drawing */
     uint32_t mDrawingFlags;
   };
 
   struct SurfaceFromElementResult {
-    friend class mozilla::dom::CanvasRenderingContext2D;
+    friend class mozilla::dom::BasicRenderingContext2D;
     friend class nsLayoutUtils;
 
     /* If SFEResult contains a valid surface, it either mLayersImage or mSourceSurface
      * will be non-null, and GetSourceSurface() will not be null.
      *
      * For valid surfaces, mSourceSurface may be null if mLayersImage is non-null, but
      * GetSourceSurface() will create mSourceSurface from mLayersImage when called.
      */