Backed out changeset ebd350549d83 (bug 1502799) for build bustages on HTMLCanvasElement.cpp. a=backout
authorCosmin Sabou <csabou@mozilla.com>
Tue, 14 May 2019 12:06:56 +0300
changeset 451199 9d3b9a48a91710e8bf4eb36c5181e391c7c14abe
parent 451198 ebd350549d83bde2f5e12ec9ea1178d73ee1c25e
child 451200 64ee63facd4ff96b3e8590cff559d7e97ac6b061
push id426
push usercsabou@mozilla.com
push dateTue, 14 May 2019 09:08:12 +0000
treeherdermozilla-esr60@9d3b9a48a917 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1502799
milestone60.7.0
backs outebd350549d83bde2f5e12ec9ea1178d73ee1c25e
Backed out changeset ebd350549d83 (bug 1502799) for build bustages on HTMLCanvasElement.cpp. a=backout
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
dom/canvas/CanvasUtils.cpp
dom/canvas/CanvasUtils.h
dom/canvas/ImageBitmap.cpp
dom/canvas/ImageBitmap.h
dom/canvas/ImageBitmapRenderingContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/test/test_imagebitmap.html
dom/html/HTMLCanvasElement.cpp
dom/html/HTMLCanvasElement.h
layout/base/nsLayoutUtils.cpp
testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.cross.html.ini
testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.redirect.html.ini
testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.cross.html.ini
testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.redirect.html.ini
testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.cross.html.ini
testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.redirect.html.ini
testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.cross.html.ini
testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.redirect.html.ini
testing/web-platform/tests/2dcontext/imagebitmap/createImageBitmap-origin.sub.html
testing/web-platform/tests/common/canvas-tests.js
toolkit/components/extensions/test/xpcshell/test_ext_contentscript_canvas_tainting.js
toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1075,18 +1075,17 @@ CanvasRenderingContext2D::CanvasRenderin
       mIPC(false),
       mIsSkiaGL(false),
       mHasPendingStableStateCallback(false),
       mDrawObserver(nullptr),
       mIsEntireFrameInvalid(false),
       mPredictManyRedrawCalls(false),
       mIsCapturedFrameInvalid(false),
       mPathTransformWillUpdate(false),
-      mInvalidateCount(0),
-      mWriteOnly(false) {
+      mInvalidateCount(0) {
   if (!sMaxContextsInitialized) {
     sMaxContexts = gfxPrefs::CanvasAzureAcceleratedLimit();
     sMaxContextsInitialized = true;
   }
 
   sNumLivingContexts++;
 
   mShutdownObserver = new CanvasShutdownObserver(this);
@@ -2342,21 +2341,17 @@ void CanvasRenderingContext2D::SetStyleF
   }
 
   if (aValue.IsCanvasGradient()) {
     SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle);
     return;
   }
 
   if (aValue.IsCanvasPattern()) {
-    CanvasPattern& pattern = aValue.GetAsCanvasPattern();
-    SetStyleFromPattern(pattern, aWhichStyle);
-    if (pattern.mForceWriteOnly) {
-      SetWriteOnly();
-    }
+    SetStyleFromPattern(aValue.GetAsCanvasPattern(), aWhichStyle);
     return;
   }
 
   MOZ_ASSERT_UNREACHABLE("Invalid union value");
 }
 
 void CanvasRenderingContext2D::SetFillRule(const nsAString& aString) {
   FillRule rule;
@@ -2497,41 +2492,40 @@ already_AddRefed<CanvasPattern> CanvasRe
       aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
       return nullptr;
     }
 
     // An ImageBitmap never taints others so we set principalForSecurityCheck to
     // nullptr and set CORSUsed to true for passing the security check in
     // CanvasUtils::DoDrawImageSecurityCheck().
     RefPtr<CanvasPattern> pat =
-      new CanvasPattern(this, srcSurf, repeatMode, nullptr, imgBitmap.IsWriteOnly(), true);
+        new CanvasPattern(this, srcSurf, repeatMode, nullptr, false, true);
 
     return pat.forget();
   }
 
   EnsureTarget();
   if (!IsTargetValid()) {
     aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   // The canvas spec says that createPattern should use the first frame
   // of animated images
   nsLayoutUtils::SurfaceFromElementResult res =
       nsLayoutUtils::SurfaceFromElement(
           element, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE, mTarget);
 
-  RefPtr<SourceSurface> surface = res.GetSourceSurface();
-  if (!surface) {
+  if (!res.GetSourceSurface()) {
     return nullptr;
   }
 
   RefPtr<CanvasPattern> pat =
-      new CanvasPattern(this, surface, repeatMode, res.mPrincipal,
-                        res.mIsWriteOnly, res.mCORSUsed);
+      new CanvasPattern(this, res.GetSourceSurface(), repeatMode,
+                        res.mPrincipal, res.mIsWriteOnly, res.mCORSUsed);
   return pat.forget();
 }
 
 //
 // shadows
 //
 void CanvasRenderingContext2D::SetShadowColor(const nsAString& aShadowColor) {
   nscolor color;
@@ -4849,18 +4843,18 @@ CanvasRenderingContext2D::CachedSurfaceF
 
   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();
-  res.mIsWriteOnly = CheckWriteOnlySecurity(res.mCORSUsed, res.mPrincipal);
 
   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);
@@ -4909,32 +4903,24 @@ void CanvasRenderingContext2D::DrawImage
   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;
     }
-
-    if (canvas->IsWriteOnly()) {
-      SetWriteOnly();
-    }
   } else if (aImage.IsImageBitmap()) {
     ImageBitmap& imageBitmap = aImage.GetAsImageBitmap();
     srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
 
     if (!srcSurf) {
       return;
     }
 
-    if (imageBitmap.IsWriteOnly()) {
-      SetWriteOnly();
-    }
-
     imgSize = gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
   } else {
     if (aImage.IsHTMLImageElement()) {
       HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
       element = img;
     } else if (aImage.IsSVGImageElement()) {
       SVGImageElement* img = &aImage.GetAsSVGImageElement();
       element = img;
@@ -5430,18 +5416,23 @@ already_AddRefed<ImageData> CanvasRender
   if (!mCanvasElement && !mDocShell) {
     NS_ERROR("No canvas element and no docshell in GetImageData!!!");
     aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   // Check only if we have a canvas element; if we were created with a docshell,
   // then it's special internal use.
-  if (IsWriteOnly() ||
-      (mCanvasElement && !mCanvasElement->CallerCanRead(aCx))) {
+  if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
+      // We could ask bindings for the caller type, but they already hand us a
+      // JSContext, and we're at least _somewhat_ perf-sensitive (so may not
+      // want to compute the caller type in the common non-write-only case), so
+      // let's just use what we have.
+      !nsContentUtils::CallerHasPermission(aCx,
+                                           nsGkAtoms::all_urlsPermission)) {
     // XXX ERRMSG we need to report an error to developers here! (bug 329026)
     aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   if (!IsFinite(aSx) || !IsFinite(aSy) || !IsFinite(aSw) || !IsFinite(aSh)) {
     aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
@@ -6033,23 +6024,16 @@ bool CanvasRenderingContext2D::IsContext
   return !mIsCapturedFrameInvalid;
 }
 
 bool CanvasRenderingContext2D::ShouldForceInactiveLayer(
     LayerManager* aManager) {
   return !aManager->CanUseCanvasLayerForSize(GetSize());
 }
 
-void CanvasRenderingContext2D::SetWriteOnly() {
-  mWriteOnly = true;
-  if (mCanvasElement) {
-    mCanvasElement->SetWriteOnly();
-  }
-}
-
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
 
 CanvasPath::CanvasPath(nsISupports* aParent) : mParent(aParent) {
   mPathBuilder = gfxPlatform::GetPlatform()
                      ->ScreenReferenceDrawTarget()
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -38,17 +38,16 @@ namespace gl {
 class SourceSurface;
 }  // namespace gl
 
 namespace dom {
 class
     HTMLImageElementOrSVGImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap;
 typedef HTMLImageElementOrSVGImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap
     CanvasImageSource;
-class ImageBitmap;
 class ImageData;
 class StringOrCanvasGradientOrCanvasPattern;
 class OwningStringOrCanvasGradientOrCanvasPattern;
 class TextMetrics;
 class CanvasFilterChainObserver;
 class CanvasPath;
 
 extern const mozilla::gfx::Float SIGMA_MAX;
@@ -1087,26 +1086,16 @@ class CanvasRenderingContext2D final : p
 
   FINISH:
     if (aPerDevPixel) *aPerDevPixel = devPixel;
     if (aPerCSSPixel) *aPerCSSPixel = cssPixel;
   }
 
   friend struct CanvasBidiProcessor;
   friend class CanvasDrawObserver;
-  friend class ImageBitmap;
-
-  void SetWriteOnly();
-
-  bool IsWriteOnly() const
-  {
-    return mWriteOnly;
-  }
-
-  bool mWriteOnly;
 };
 
 size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext);
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif /* CanvasRenderingContext2D_h */
--- a/dom/canvas/CanvasUtils.cpp
+++ b/dom/canvas/CanvasUtils.cpp
@@ -231,19 +231,17 @@ void DoDrawImageSecurityCheck(dom::HTMLC
                               nsIPrincipal* aPrincipal, bool forceWriteOnly,
                               bool CORSUsed) {
   // Callers should ensure that mCanvasElement is non-null before calling this
   if (!aCanvasElement) {
     NS_WARNING("DoDrawImageSecurityCheck called without canvas element!");
     return;
   }
 
-  if (aCanvasElement->IsWriteOnly() && !aCanvasElement->mExpandedReader) {
-    return;
-  }
+  if (aCanvasElement->IsWriteOnly()) return;
 
   // If we explicitly set WriteOnly just do it and get out
   if (forceWriteOnly) {
     aCanvasElement->SetWriteOnly();
     return;
   }
 
   // No need to do a security check if the image used CORS for the load
@@ -251,35 +249,16 @@ void DoDrawImageSecurityCheck(dom::HTMLC
 
   NS_PRECONDITION(aPrincipal, "Must have a principal here");
 
   if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) {
     // This canvas has access to that image anyway
     return;
   }
 
-    if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
-        // This is a resource from an extension content script principal.
-
-        if (aCanvasElement->mExpandedReader &&
-            aCanvasElement->mExpandedReader->Subsumes(aPrincipal)) {
-            // This canvas already allows reading from this principal.
-            return;
-        }
-
-        if (!aCanvasElement->mExpandedReader) {
-            // Allow future reads from this same princial only.
-            aCanvasElement->SetWriteOnly(aPrincipal);
-            return;
-        }
-
-        // If we got here, this must be the *second* extension tainting
-        // the canvas.  Fall through to mark it WriteOnly for everyone.
-    }
-
   aCanvasElement->SetWriteOnly();
 }
 
 bool CoerceDouble(const JS::Value& v, double* d) {
   if (v.isDouble()) {
     *d = v.toDouble();
   } else if (v.isInt32()) {
     *d = double(v.toInt32());
@@ -291,30 +270,10 @@ bool CoerceDouble(const JS::Value& v, do
   return true;
 }
 
 bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* /* unused */) {
   return nsContentUtils::CallerHasPermission(aCx,
                                              nsGkAtoms::all_urlsPermission);
 }
 
-bool CheckWriteOnlySecurity(bool aCORSUsed, nsIPrincipal* aPrincipal) {
-  if (!aPrincipal) {
-    return true;
-  }
-
-  if (!aCORSUsed) {
-    nsIGlobalObject* incumbentSettingsObject = dom::GetIncumbentGlobal();
-    if (NS_WARN_IF(!incumbentSettingsObject)) {
-      return true;
-    }
-
-    nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull();
-    if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
 }  // namespace CanvasUtils
 }  // namespace mozilla
--- a/dom/canvas/CanvasUtils.h
+++ b/dom/canvas/CanvasUtils.h
@@ -6,17 +6,16 @@
 #ifndef _CANVASUTILS_H_
 #define _CANVASUTILS_H_
 
 #include "CanvasRenderingContextHelper.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "jsapi.h"
 #include "mozilla/FloatingPoint.h"
-#include "nsLayoutUtils.h"
 
 class nsIPrincipal;
 
 namespace mozilla {
 
 namespace dom {
 class HTMLCanvasElement;
 }  // namespace dom
@@ -168,16 +167,12 @@ void DashArrayToJSVal(nsTArray<T>& dashe
     return;
   }
   JS::Rooted<JS::Value> val(cx);
   if (!mozilla::dom::ToJSValue(cx, dashes, retval)) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
   }
 }
 
-// returns true if write-only mode must used for this principal based on
-// the incumbent global.
-bool CheckWriteOnlySecurity(bool aCORSUsed, nsIPrincipal* aPrincipal);
-
 }  // namespace CanvasUtils
 }  // namespace mozilla
 
 #endif /* _CANVASUTILS_H_ */
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -402,37 +402,67 @@ class CreateImageFromRawDataInMainThread
   uint8_t* mBuffer;
   uint32_t mBufferLength;
   uint32_t mStride;
   gfx::SurfaceFormat mFormat;
   gfx::IntSize mSize;
   const Maybe<IntRect>& mCropRect;
 };
 
+static bool CheckSecurityForHTMLElements(bool aIsWriteOnly, bool aCORSUsed,
+                                         nsIPrincipal* aPrincipal) {
+  if (aIsWriteOnly || !aPrincipal) {
+    return false;
+  }
+
+  if (!aCORSUsed) {
+    nsIGlobalObject* incumbentSettingsObject = GetIncumbentGlobal();
+    if (NS_WARN_IF(!incumbentSettingsObject)) {
+      return false;
+    }
+
+    nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull();
+    if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static bool CheckSecurityForHTMLElements(
+    const nsLayoutUtils::SurfaceFromElementResult& aRes) {
+  return CheckSecurityForHTMLElements(aRes.mIsWriteOnly, aRes.mCORSUsed,
+                                      aRes.mPrincipal);
+}
+
 /*
  * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the
  * security checking.
  */
-template <class ElementType>
+template <class HTMLElementType>
 static already_AddRefed<SourceSurface> GetSurfaceFromElement(
-    nsIGlobalObject* aGlobal, ElementType& aElement, bool* aWriteOnly,
-    ErrorResult& aRv) {
+    nsIGlobalObject* aGlobal, HTMLElementType& aElement, ErrorResult& aRv) {
   nsLayoutUtils::SurfaceFromElementResult res =
       nsLayoutUtils::SurfaceFromElement(
           &aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE);
 
+  // check origin-clean
+  if (!CheckSecurityForHTMLElements(res)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
   RefPtr<SourceSurface> surface = res.GetSourceSurface();
 
   if (NS_WARN_IF(!surface)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  *aWriteOnly = res.mIsWriteOnly;
-
   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.
  */
@@ -450,26 +480,25 @@ static bool HasRasterImage(HTMLImageElem
       return true;
     }
   }
 
   return false;
 }
 
 ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
-                         bool aWriteOnly, gfxAlphaType aAlphaType)
+                         gfxAlphaType aAlphaType)
     : mParent(aGlobal),
       mData(aData),
       mSurface(nullptr),
       mDataWrapper(new ImageUtils(mData)),
       mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height),
       mAlphaType(aAlphaType),
       mIsCroppingAreaOutSideOfSourceImage(false),
-      mAllocatedImageData(false),
-      mWriteOnly(aWriteOnly) {
+      mAllocatedImageData(false) {
   MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
 
   mShutdownObserver = new ImageBitmapShutdownObserver(this);
   mShutdownObserver->RegisterObserver();
 }
 
 ImageBitmap::~ImageBitmap() {
   if (mShutdownObserver) {
@@ -717,59 +746,60 @@ UniquePtr<ImageBitmapCloneData> ImageBit
   UniquePtr<ImageBitmapCloneData> result(new ImageBitmapCloneData());
   result->mPictureRect = mPictureRect;
   result->mAlphaType = mAlphaType;
   result->mIsCroppingAreaOutSideOfSourceImage =
       mIsCroppingAreaOutSideOfSourceImage;
   RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
   result->mSurface = surface->GetDataSurface();
   MOZ_ASSERT(result->mSurface);
-  result->mWriteOnly = mWriteOnly;
 
   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->mWriteOnly, aData->mAlphaType);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aData->mAlphaType);
 
   ret->mAllocatedImageData = true;
 
   ret->mIsCroppingAreaOutSideOfSourceImage =
       aData->mIsCroppingAreaOutSideOfSourceImage;
 
   ErrorResult rv;
   ret->SetPictureRect(aData->mPictureRect, rv);
   return ret.forget();
 }
 
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
                                        OffscreenCanvas& aOffscreenCanvas,
                                        ErrorResult& aRv) {
-  // Check write-only mode.
-  bool writeOnly = aOffscreenCanvas.IsWriteOnly();
+  // Check origin-clean.
+  if (aOffscreenCanvas.IsWriteOnly()) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
 
   nsLayoutUtils::SurfaceFromElementResult res =
       nsLayoutUtils::SurfaceFromOffscreenCanvas(
           &aOffscreenCanvas, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE);
 
   RefPtr<SourceSurface> surface = res.GetSourceSurface();
 
   if (NS_WARN_IF(!surface)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   RefPtr<layers::Image> data = CreateImageFromSurface(surface);
 
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
 
   ret->mAllocatedImageData = true;
 
   return ret.forget();
 }
 
 /* static */ already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
     nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
@@ -781,36 +811,33 @@ ImageBitmap::CreateFromOffscreenCanvas(n
   }
 
   // Check if the image element is a bitmap (e.g. it's a vector graphic) or not.
   if (!HasRasterImage(aImageEl)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
-  bool writeOnly = true;
-
   // Get the SourceSurface out from the image element and then do security
   // checking.
-  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aImageEl,
-                                                        &writeOnly, aRv);
+  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aImageEl, aRv);
 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   // Create ImageBitmap.
   RefPtr<layers::Image> data = CreateImageFromSurface(surface);
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
@@ -836,25 +863,28 @@ ImageBitmap::CreateFromOffscreenCanvas(n
   if (aVideoEl.ReadyState() <= HAVE_METADATA) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   // Check security.
   nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentVideoPrincipal();
   bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE;
-  bool writeOnly = CheckWriteOnlySecurity(CORSUsed, principal);
+  if (!CheckSecurityForHTMLElements(false, CORSUsed, principal)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
 
   // Create ImageBitmap.
   RefPtr<layers::Image> data = aVideoEl.GetCurrentImage();
   if (!data) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   ret->SetIsCroppingAreaOutSideOfSourceImage(data->GetSize(), aCropRect);
@@ -865,28 +895,23 @@ ImageBitmap::CreateFromOffscreenCanvas(n
 /* static */ already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
     nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl,
     const Maybe<IntRect>& aCropRect, ErrorResult& aRv) {
   if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
-  bool writeOnly = true;
-  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aCanvasEl,
-                                                        &writeOnly, aRv);
+  RefPtr<SourceSurface> surface =
+      GetSurfaceFromElement(aGlobal, aCanvasEl, aRv);
 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  if (!writeOnly) {
-    writeOnly = aCanvasEl.IsWriteOnly();
-  }
-
   // Crop the source surface if needed.
   RefPtr<SourceSurface> croppedSurface;
   IntRect cropRect = aCropRect.valueOr(IntRect());
 
   // If the HTMLCanvasElement's rendering context is WebGL, then the snapshot
   // we got from the HTMLCanvasElement is a DataSourceSurface which is a copy
   // of the rendering context. We handle cropping in this case.
   bool needToReportMemoryAllocation = false;
@@ -909,17 +934,17 @@ ImageBitmap::CreateFromOffscreenCanvas(n
   // Create an Image from the SourceSurface.
   RefPtr<layers::Image> data = CreateImageFromSurface(croppedSurface);
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
 
   if (needToReportMemoryAllocation) {
     ret->mAllocatedImageData = true;
   }
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(cropRect, aRv);
@@ -971,18 +996,17 @@ ImageBitmap::CreateFromOffscreenCanvas(n
   }
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   // Create an ImageBimtap.
-  RefPtr<ImageBitmap> ret =
-    new ImageBitmap(aGlobal, data, false /* write-only */, alphaType);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, alphaType);
 
   ret->mAllocatedImageData = true;
 
   // The cropping information has been handled in the CreateImageFromRawData()
   // function.
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   ret->SetIsCroppingAreaOutSideOfSourceImage(imageSize, aCropRect);
@@ -994,20 +1018,16 @@ ImageBitmap::CreateFromOffscreenCanvas(n
     nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx,
     const Maybe<IntRect>& aCropRect, ErrorResult& aRv) {
   // Check origin-clean.
   if (aCanvasCtx.GetCanvas()->IsWriteOnly()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
-  // Check write-only mode.
-  bool writeOnly =
-      aCanvasCtx.GetCanvas()->IsWriteOnly() || aCanvasCtx.IsWriteOnly();
-
   RefPtr<SourceSurface> surface = aCanvasCtx.GetSurfaceSnapshot();
 
   if (NS_WARN_IF(!surface)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   const IntSize surfaceSize = surface->GetSize();
@@ -1018,18 +1038,17 @@ ImageBitmap::CreateFromOffscreenCanvas(n
 
   RefPtr<layers::Image> data = CreateImageFromSurface(surface);
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  RefPtr<ImageBitmap> ret =
-    new ImageBitmap(aGlobal, data, writeOnly);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
 
   ret->mAllocatedImageData = true;
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
@@ -1044,18 +1063,17 @@ ImageBitmap::CreateFromOffscreenCanvas(n
     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.mWriteOnly,
-                      aImageBitmap.mAlphaType);
+      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) {
@@ -1361,29 +1379,24 @@ static void AsyncCreateImageBitmapFromBl
   // aParent might be null.
 
   uint32_t picRectX_;
   uint32_t picRectY_;
   uint32_t picRectWidth_;
   uint32_t picRectHeight_;
   uint32_t alphaType_;
   uint32_t isCroppingAreaOutSideOfSourceImage_;
-  uint32_t writeOnly;
-  uint32_t dummy;
 
   if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
       !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) ||
       !JS_ReadUint32Pair(aReader, &alphaType_,
-                         &isCroppingAreaOutSideOfSourceImage_) ||
-      !JS_ReadUint32Pair(aReader, &writeOnly, &dummy)) {
+                         &isCroppingAreaOutSideOfSourceImage_)) {
     return nullptr;
   }
 
-  MOZ_ASSERT(dummy == 0);
-
   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());
@@ -1397,17 +1410,17 @@ static void AsyncCreateImageBitmapFromBl
   JS::Rooted<JS::Value> value(aCx);
   {
 #ifdef FUZZING
     if (aIndex >= aClonedSurfaces.Length()) {
       return nullptr;
     }
 #endif
     RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
-    RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aParent, img, !!writeOnly, alphaType);
+    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())) {
@@ -1444,18 +1457,17 @@ static void AsyncCreateImageBitmapFromBl
 
   // 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, alphaType,
-                                     isCroppingAreaOutSideOfSourceImage)) ||
-      NS_WARN_IF(!JS_WriteUint32Pair(aWriter, aImageBitmap->mWriteOnly, 0))) {
+                                     isCroppingAreaOutSideOfSourceImage))) {
     return false;
   }
 
   RefPtr<SourceSurface> surface = aImageBitmap->mData->GetAsSourceSurface();
   RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
   RefPtr<DataSourceSurface> dstDataSurface;
   {
     // DataSourceSurfaceD2D1::GetStride() will call EnsureMapped implicitly and
@@ -1986,18 +1998,17 @@ class CreateImageFromBufferSourceRawData
   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 /* write-only */,
-                      gfxAlphaType::NonPremult);
+      new ImageBitmap(aGlobal, data, gfxAlphaType::NonPremult);
 
   imageBitmap->mAllocatedImageData = true;
 
   // 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
 
   // We don't need to set mIsCroppingAreaOutSideOfSourceImage here because there
@@ -2223,18 +2234,17 @@ void CreateImageBitmapFromBlob::DecodeAn
   });
 
   if (NS_WARN_IF(NS_FAILED(aStatus))) {
     mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   // Create ImageBitmap object.
-  RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, aImage,
-                                                    false /* write-only */);
+  RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, aImage);
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   imageBitmap->SetIsCroppingAreaOutSideOfSourceImage(mSourceSize,
                                                      mOriginalCropRect);
 
   if (mCropRect.isSome()) {
     ErrorResult rv;
     imageBitmap->SetPictureRect(mCropRect.ref(), rv);
--- a/dom/canvas/ImageBitmap.h
+++ b/dom/canvas/ImageBitmap.h
@@ -58,17 +58,16 @@ class Promise;
 class PostMessageEvent;  // For StructuredClone between windows.
 class ImageBitmapShutdownObserver;
 
 struct ImageBitmapCloneData final {
   RefPtr<gfx::DataSourceSurface> mSurface;
   gfx::IntRect mPictureRect;
   gfxAlphaType mAlphaType;
   bool mIsCroppingAreaOutSideOfSourceImage;
-  bool mWriteOnly;
 };
 
 /*
  * ImageBitmap is an opaque handler to several kinds of image-like objects from
  * HTMLImageElement, HTMLVideoElement, HTMLCanvasElement, ImageData to
  * CanvasRenderingContext2D and Image Blob.
  *
  * An ImageBitmap could be painted to a canvas element.
@@ -156,21 +155,16 @@ class ImageBitmap final : public nsISupp
       JSContext* aCx, ImageBitmapFormat aFormat,
       const ArrayBufferViewOrArrayBuffer& aBuffer, int32_t aOffset,
       ErrorResult& aRv);
 
   size_t GetAllocatedSize() const;
 
   void OnShutdown();
 
-  bool IsWriteOnly() const
-  {
-    return mWriteOnly;
-  }
-
  protected:
   /*
    * The default value of aIsPremultipliedAlpha is TRUE because that the
    * data stored in HTMLImageElement, HTMLVideoElement, HTMLCanvasElement,
    * CanvasRenderingContext2D are alpha-premultiplied in default.
    *
    * Actually, if one HTMLCanvasElement's rendering context is WebGLContext, it
    * is possible to get un-premultipliedAlpha data out. But, we do not do it in
@@ -182,17 +176,16 @@ class ImageBitmap final : public nsISupp
    *    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 aWriteOnly,
               gfxAlphaType aAlphaType = gfxAlphaType::Premult);
 
   virtual ~ImageBitmap();
 
   void SetPictureRect(const gfx::IntRect& aRect, ErrorResult& aRv);
 
   void SetIsCroppingAreaOutSideOfSourceImage(
       const gfx::IntSize& aSourceSize,
@@ -273,21 +266,14 @@ class ImageBitmap final : public nsISupp
    * IndexSizeError.
    */
   bool mIsCroppingAreaOutSideOfSourceImage;
 
   /*
    * Whether this object allocated allocated and owns the image data.
    */
   bool mAllocatedImageData;
-
-  /*
-   * Write-Only flag is set to true if this image has been generated from a
-   * cross-origin source. This is the opposite of what is called 'origin-clean'
-   * in the spec.
-   */
-  bool mWriteOnly;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_ImageBitmap_h
--- a/dom/canvas/ImageBitmapRenderingContext.cpp
+++ b/dom/canvas/ImageBitmapRenderingContext.cpp
@@ -55,20 +55,16 @@ void ImageBitmapRenderingContext::Transf
     ImageBitmap& aImageBitmap) {
   Reset();
   mImage = aImageBitmap.TransferAsImage();
 
   if (!mImage) {
     return;
   }
 
-  if (aImageBitmap.IsWriteOnly() && mCanvasElement) {
-    mCanvasElement->SetWriteOnly();
-  }
-
   Redraw(gfxRect(0, 0, mWidth, mHeight));
 }
 
 NS_IMETHODIMP
 ImageBitmapRenderingContext::SetDimensions(int32_t aWidth, int32_t aHeight) {
   mWidth = aWidth;
   mHeight = aHeight;
   return NS_OK;
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -236,19 +236,18 @@ struct TexImageSourceAdapter final : pub
                         GLuint ignored2 = 0) {
     mPboOffset = pboOffset;
   }
 
   TexImageSourceAdapter(const WebGLsizeiptr* pboOffset, ErrorResult* ignored) {
     mPboOffset = pboOffset;
   }
 
-  TexImageSourceAdapter(const dom::ImageBitmap* imageBitmap, ErrorResult* out_error) {
+  TexImageSourceAdapter(const dom::ImageBitmap* imageBitmap, ErrorResult*) {
     mImageBitmap = imageBitmap;
-        mOut_error = out_error;
   }
 
   TexImageSourceAdapter(const dom::ImageData* imageData, ErrorResult*) {
     mImageData = imageData;
   }
 
   TexImageSourceAdapter(const dom::Element* domElem,
                         ErrorResult* const out_error) {
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -7,17 +7,16 @@
 
 #include <algorithm>
 
 #include "CanvasUtils.h"
 #include "gfxPrefs.h"
 #include "GLBlitHelper.h"
 #include "GLContext.h"
 #include "mozilla/gfx/2D.h"
-#include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/Unused.h"
 #include "ScopedGLHelpers.h"
 #include "TexUnpackBlob.h"
@@ -218,22 +217,17 @@ static UniquePtr<webgl::TexUnpackBytes> 
   const auto ptr = (const uint8_t*)pboOffset;
   return MakeUnique<webgl::TexUnpackBytes>(webgl, target, width, height, depth,
                                            isClientData, ptr, availBufferBytes);
 }
 
 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, ErrorResult* aRv) {
-    if (imageBitmap.IsWriteOnly()) {
-        aRv->Throw(NS_ERROR_DOM_SECURITY_ERR);
-        return nullptr;
-    }
-
+    const dom::ImageBitmap& imageBitmap) {
   UniquePtr<dom::ImageBitmapCloneData> cloneData =
       Move(imageBitmap.ToCloneData());
   const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
 
   if (!width) {
     width = surf->GetSize().width;
   }
 
@@ -295,24 +289,16 @@ static UniquePtr<webgl::TexUnpackBlob> F
   return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height,
                                              depth, surf, 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) {
-    if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
-        const dom::HTMLCanvasElement* canvas = static_cast<const dom::HTMLCanvasElement*>(&elem);
-        if (canvas->IsWriteOnly()) {
-            out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
-            return nullptr;
-        }
-    }
-
   // The canvas spec says that drawImage should draw the first frame of
   // animated images. The webgl spec doesn't mention the issue, so we do the
   // same as drawImage.
   uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
                    nsLayoutUtils::SFE_WANT_IMAGE_SURFACE |
                    nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR;
 
   if (mPixelStore_ColorspaceConversion == LOCAL_GL_NONE)
@@ -423,17 +409,17 @@ UniquePtr<webgl::TexUnpackBlob> WebGLCon
 
   if (mBoundPixelUnpackBuffer) {
     ErrorInvalidOperation("%s: PIXEL_UNPACK_BUFFER must be null.", funcName);
     return nullptr;
   }
 
   if (src.mImageBitmap) {
     return FromImageBitmap(this, funcName, target, width, height, depth,
-                           *(src.mImageBitmap), src.mOut_error);
+                           *(src.mImageBitmap));
   }
 
   if (src.mImageData) {
     return FromImageData(this, funcName, target, width, height, depth,
                          *(src.mImageData), scopedArr);
   }
 
   if (src.mDomElem) {
--- a/dom/canvas/test/test_imagebitmap.html
+++ b/dom/canvas/test/test_imagebitmap.html
@@ -265,32 +265,23 @@ function testSecurityErrors() {
         reject();
       }
 
       uncleanImage.src = "http://example.com/tests/dom/canvas/test/crossorigin/image.png";
     });
   }
 
   function checkPromiseFailedWithSecurityError(p) {
-    return p.then(imageBitmap => {
-      ok(!!imageBitmap, "ImageBitmaps are always created");
-      const context = document.createElement("canvas").getContext("2d");
-      context.drawImage(imageBitmap, 0, 0);
-      try {
-        context.getImageData(0, 0, 1, 1);
-        ok(false, "Did not get SecurityError with unclean source. ImageBitmap was created successfully.");
-      } catch (ex) {
-        if (ex == "SecurityError: The operation is insecure.") {
-          ok(true, ex);
-        }
-        else {
-          ok(false, "Did not get SecurityError with unclean source. Error Message: " + ex);
-        }
-      }
-    });
+    return p.then( function(reason) { ok(false, "Did not get SecurityError with unclean source. ImageBitmap was created successfully."); },
+                   function(reason) { if (reason == "SecurityError: The operation is insecure.") {
+                                        ok(true, reason);
+                                      }
+                                      else {
+                                        ok(false, "Did not get SecurityError with unclean source. Error Message: " + reason);
+                                      }});
   }
 
   return Promise.all([
     checkPromiseFailedWithSecurityError(getUncleanImagePromise()),
     checkPromiseFailedWithSecurityError(getUncleanVideoPromise()),
     checkPromiseFailedWithSecurityError(getTaintedCanvasPromise()),
     checkPromiseFailedWithSecurityError(getTaintedCanvasRenderingContex2dPromise()),
   ]);
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -581,18 +581,19 @@ bool HTMLCanvasElement::ParseAttribute(i
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aMaybeScriptedPrincipal, aResult);
 }
 
 void HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType,
                                   JS::Handle<JS::Value> aParams,
                                   nsAString& aDataURL, ErrorResult& aRv) {
-  // mWriteOnly check is redundant, but optimizes for the common case.
-  if (mWriteOnly && !CallerCanRead(aCx)) {
+  // do a trust check if this is a write-only canvas
+  if (mWriteOnly && !nsContentUtils::CallerHasPermission(
+                        aCx, nsGkAtoms::all_urlsPermission)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   aRv = ToDataURLImpl(aCx, aType, aParams, aDataURL);
 }
 
 void HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback) {
@@ -755,18 +756,19 @@ nsresult HTMLCanvasElement::ToDataURLImp
   return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count,
                                  aDataURL.Length());
 }
 
 void HTMLCanvasElement::ToBlob(JSContext* aCx, BlobCallback& aCallback,
                                const nsAString& aType,
                                JS::Handle<JS::Value> aParams,
                                ErrorResult& aRv) {
-  // mWriteOnly check is redundant, but optimizes for the common case.
-  if (mWriteOnly && !CallerCanRead(aCx)) {
+  // do a trust check if this is a write-only canvas
+  if (mWriteOnly && !nsContentUtils::CallerHasPermission(
+                        aCx, nsGkAtoms::all_urlsPermission)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
   MOZ_ASSERT(global);
 
   nsIntSize elemSize = GetWidthHeight();
@@ -934,46 +936,17 @@ already_AddRefed<nsISupports> HTMLCanvas
   nsCOMPtr<nsISupports> context(mCurrentContext);
   return context.forget();
 }
 
 nsIntSize HTMLCanvasElement::GetSize() { return GetWidthHeight(); }
 
 bool HTMLCanvasElement::IsWriteOnly() { return mWriteOnly; }
 
-void HTMLCanvasElement::SetWriteOnly() { 
-  mExpandedReader = nullptr;
-  mWriteOnly = true;
-}
-
-void
-HTMLCanvasElement::SetWriteOnly(nsIPrincipal* aExpandedReader)
-{
-  mExpandedReader = aExpandedReader;
-  mWriteOnly = true;
-}
-
-bool
-HTMLCanvasElement::CallerCanRead(JSContext* aCx)
-{
-  if (!mWriteOnly) {
-    return true;
-  }
-
-  nsIPrincipal* prin = nsContentUtils::SubjectPrincipal(aCx);
-
-  // If mExpandedReader is set, this canvas was tainted only by
-  // mExpandedReader's resources. So allow reading if the subject
-  // principal subsumes mExpandedReader.
-  if (mExpandedReader && prin->Subsumes(mExpandedReader)) {
-    return true;
-  }
-
-  return nsContentUtils::PrincipalHasPermission(prin, nsGkAtoms::all_urlsPermission);
-}
+void HTMLCanvasElement::SetWriteOnly() { mWriteOnly = true; }
 
 void HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect* damageRect) {
   // We don't need to flush anything here; if there's no frame or if
   // we plan to reframe we don't need to invalidate it anyway.
   nsIFrame* frame = GetPrimaryFrame();
   if (!frame) return;
 
   ActiveLayerTracker::NotifyContentChange(frame);
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -196,30 +196,24 @@ class HTMLCanvasElement final : public n
   /**
    * Get the size in pixels of this canvas element
    */
   nsIntSize GetSize();
 
   /**
    * Determine whether the canvas is write-only.
    */
-  bool IsWriteOnly() const;
+  bool IsWriteOnly();
 
   /**
    * Force the canvas to be write-only.
    */
   void SetWriteOnly();
 
   /**
-   * Force the canvas to be write-only, except for readers from
-   * a specific extension's content script expanded principal.
-   */
-  void SetWriteOnly(nsIPrincipal* aExpandedReader);
-
-  /**
    * Notify that some canvas content has changed and the window may
    * need to be updated. aDamageRect is in canvas coordinates.
    */
   void InvalidateCanvasContent(const mozilla::gfx::Rect* aDamageRect);
   /*
    * Notify that we need to repaint the entire canvas, including updating of
    * the layer tree.
    */
@@ -370,24 +364,17 @@ class HTMLCanvasElement final : public n
   RefPtr<HTMLCanvasElementObserver> mContextObserver;
 
  public:
   // Record whether this canvas should be write-only or not.
   // We set this when script paints an image from a different origin.
   // We also transitively set it when script paints a canvas which
   // is itself write-only.
   bool mWriteOnly;
- 
-  // When this canvas is (only) tainted by an image from an extension
-  // content script, allow reads from the same extension afterwards.
-  RefPtr<nsIPrincipal> mExpandedReader;
 
-  // Determines if the caller should be able to read the content.
-  bool CallerCanRead(JSContext* aCx);
-  
   bool IsPrintCallbackDone();
 
   void HandlePrintCallback(nsPresContext::nsPresContextType aType);
 
   nsresult DispatchPrintCallback(nsITimerCallback* aCallback);
 
   void ResetPrintCallback();
 
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3,17 +3,16 @@
 /* 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 "nsLayoutUtils.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/BasicEvents.h"
-#include "mozilla/dom/CanvasUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/layers/PAPZ.h"
@@ -7302,20 +7301,19 @@ nsLayoutUtils::SurfaceFromElementResult 
   }
 
   int32_t corsmode;
   if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
     result.mCORSUsed = (corsmode != imgIRequest::CORS_NONE);
   }
 
   result.mPrincipal = principal.forget();
+  // no images, including SVG images, can load content from another domain.
+  result.mIsWriteOnly = false;
   result.mImageRequest = imgRequest.forget();
-  result.mIsWriteOnly =
-      CanvasUtils::CheckWriteOnlySecurity(result.mCORSUsed, result.mPrincipal);
-
   return result;
 }
 
 nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
     HTMLImageElement* aElement, uint32_t aSurfaceFlags,
     RefPtr<DrawTarget>& aTarget) {
   return SurfaceFromElement(static_cast<nsIImageLoadingContent*>(aElement),
                             aSurfaceFlags, aTarget);
@@ -7398,18 +7396,17 @@ nsLayoutUtils::SurfaceFromElementResult 
       result.mSourceSurface = opt;
     }
   }
 
   result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE;
   result.mHasSize = true;
   result.mSize = result.mLayersImage->GetSize();
   result.mPrincipal = principal.forget();
-  result.mIsWriteOnly =
-      CanvasUtils::CheckWriteOnlySecurity(result.mCORSUsed, result.mPrincipal);
+  result.mIsWriteOnly = false;
 
   return result;
 }
 
 nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
     dom::Element* aElement, uint32_t aSurfaceFlags,
     RefPtr<DrawTarget>& aTarget) {
   // If it's a <canvas>, we may be able to just grab its internal surface
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.cross.html.ini
@@ -0,0 +1,4 @@
+[security.pattern.canvas.fillStyle.cross.html]
+  [Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.redirect.html.ini
@@ -0,0 +1,4 @@
+[security.pattern.canvas.fillStyle.redirect.html]
+  [Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.cross.html.ini
@@ -0,0 +1,4 @@
+[security.pattern.canvas.strokeStyle.cross.html]
+  [Setting strokeStyle to a pattern of an unclean canvas makes the canvas origin-unclean]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.redirect.html.ini
@@ -0,0 +1,4 @@
+[security.pattern.canvas.strokeStyle.redirect.html]
+  [Setting strokeStyle to a pattern of an unclean canvas makes the canvas origin-unclean]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.cross.html.ini
@@ -0,0 +1,4 @@
+[security.pattern.image.fillStyle.cross.html]
+  [Setting fillStyle to a pattern of a different-origin image makes the canvas origin-unclean]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.redirect.html.ini
@@ -0,0 +1,4 @@
+[security.pattern.image.fillStyle.redirect.html]
+  [Setting fillStyle to a pattern of a different-origin image makes the canvas origin-unclean]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.cross.html.ini
@@ -0,0 +1,4 @@
+[security.pattern.image.strokeStyle.cross.html]
+  [Setting strokeStyle to a pattern of a different-origin image makes the canvas origin-unclean]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.redirect.html.ini
@@ -0,0 +1,4 @@
+[security.pattern.image.strokeStyle.redirect.html]
+  [Setting strokeStyle to a pattern of a different-origin image makes the canvas origin-unclean]
+    expected: FAIL
+
--- a/testing/web-platform/tests/2dcontext/imagebitmap/createImageBitmap-origin.sub.html
+++ b/testing/web-platform/tests/2dcontext/imagebitmap/createImageBitmap-origin.sub.html
@@ -1,27 +1,98 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>createImageBitmap: origin-clean flag</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/common/media.js"></script>
 <script src="/common/namespaces.js"></script>
-<script src="/common/canvas-tests.js"></script>
 <div id=log></div>
 <script>
+const crossOriginImageUrl = "http://{{domains[www1]}}:{{ports[http][0]}}/images/red.png";
 
 function assert_origin_unclean(bitmap) {
   const context = document.createElement("canvas").getContext("2d");
   context.drawImage(bitmap, 0, 0);
   assert_throws("SecurityError", () => {
     context.getImageData(0, 0, 1, 1);
   });
 }
 
-forEachCanvasSource("http://{{domains[www1]}}:{{ports[http][0]}}",
-                    "http://{{domains[]}}:{{ports[http][0]}}",
-                    (name, factory) => {
+function makeImage() {
+  return new Promise((resolve, reject) => {
+    const image = new Image();
+    image.onload = () => resolve(image);
+    image.onerror = reject;
+    image.src = crossOriginImageUrl;
+  });
+}
+
+const arguments = [
+  {
+    name: "cross-origin HTMLImageElement",
+    factory: makeImage,
+  },
+
+  {
+    name: "cross-origin SVGImageElement",
+    factory: () => {
+      return new Promise((resolve, reject) => {
+        const image = document.createElementNS(NAMESPACES.svg, "image");
+        image.onload = () => resolve(image);
+        image.onerror = reject;
+        image.setAttribute("externalResourcesRequired", "true");
+        image.setAttributeNS(NAMESPACES.xlink, 'xlink:href', crossOriginImageUrl);
+        document.body.appendChild(image);
+      });
+    },
+  },
+
+  {
+    name: "cross-origin HTMLVideoElement",
+    factory: () => {
+      return new Promise((resolve, reject) => {
+        const video = document.createElement("video");
+        video.oncanplaythrough = () => resolve(video);
+        video.onerror = reject;
+        video.src = getVideoURI("http://{{domains[www1]}}:{{ports[http][0]}}/media/movie_300");
+      });
+    },
+  },
+
+  {
+    name: "redirected to cross-origin HTMLVideoElement",
+    factory: () => {
+      return new Promise((resolve, reject) => {
+        const video = document.createElement("video");
+        video.oncanplaythrough = () => resolve(video);
+        video.onerror = reject;
+        video.src = "/common/redirect.py?location=" + getVideoURI("http://{{domains[www1]}}:{{ports[http][0]}}/media/movie_300");
+      });
+    },
+  },
+
+  {
+    name: "unclean HTMLCanvasElement",
+    factory: () => {
+      return makeImage().then(image => {
+        const canvas = document.createElement("canvas");
+        const context = canvas.getContext("2d");
+        context.drawImage(image, 0, 0);
+        return canvas;
+      });
+    },
+  },
+
+  {
+    name: "unclean ImageBitmap",
+    factory: () => {
+      return makeImage().then(createImageBitmap);
+    },
+  },
+];
+
+for (let { name, factory } of arguments) {
   promise_test(function() {
     return factory().then(createImageBitmap).then(assert_origin_unclean);
   }, name);
-});
+}
 </script>
--- a/testing/web-platform/tests/common/canvas-tests.js
+++ b/testing/web-platform/tests/common/canvas-tests.js
@@ -98,99 +98,8 @@ function addCrossOriginRedirectYellowIma
 {
     var img = new Image();
     img.id = "yellow.png";
     img.className = "resource";
     img.src = get_host_info().HTTP_ORIGIN + "/common/redirect.py?location=" +
         get_host_info().HTTP_REMOTE_ORIGIN + "/images/yellow.png";
     document.body.appendChild(img);
 }
-
-function forEachCanvasSource(crossOriginUrl, sameOriginUrl, callback) {
-  function makeImage() {
-    return new Promise((resolve, reject) => {
-      const image = new Image();
-      image.onload = () => resolve(image);
-      image.onerror = reject;
-      image.src = crossOriginUrl + "/images/red.png";
-    });
-  }
-
-  const arguments = [
-    {
-      name: "cross-origin HTMLImageElement",
-      factory: makeImage,
-    },
-
-    {
-      name: "cross-origin SVGImageElement",
-      factory: () => {
-        return new Promise((resolve, reject) => {
-          const image = document.createElementNS(NAMESPACES.svg, "image");
-          image.onload = () => resolve(image);
-          image.onerror = reject;
-          image.setAttribute("externalResourcesRequired", "true");
-          image.setAttributeNS(NAMESPACES.xlink, 'xlink:href', crossOriginUrl + "/images/red.png");
-          document.body.appendChild(image);
-        });
-      },
-    },
-
-    {
-      name: "cross-origin HTMLVideoElement",
-      factory: () => {
-        return new Promise((resolve, reject) => {
-          const video = document.createElement("video");
-          video.oncanplaythrough = () => resolve(video);
-          video.onerror = reject;
-          video.src = getVideoURI(crossOriginUrl + "/media/movie_300");
-        });
-      },
-    },
-
-    {
-      name: "redirected to cross-origin HTMLVideoElement",
-      factory: () => {
-        return new Promise((resolve, reject) => {
-          const video = document.createElement("video");
-          video.oncanplaythrough = () => resolve(video);
-          video.onerror = reject;
-          video.src = "/common/redirect.py?location=" + getVideoURI(crossOriginUrl + "/media/movie_300");
-        });
-      },
-    },
-
-    {
-      name: "redirected to same-origin HTMLVideoElement",
-      factory: () => {
-        return new Promise((resolve, reject) => {
-          const video = document.createElement("video");
-          video.oncanplaythrough = () => resolve(video);
-          video.onerror = reject;
-          video.src = crossOriginUrl + "/common/redirect.py?location=" + getVideoURI(sameOriginUrl + "/media/movie_300");
-        });
-      },
-    },
-
-    {
-      name: "unclean HTMLCanvasElement",
-      factory: () => {
-        return makeImage().then(image => {
-          const canvas = document.createElement("canvas");
-          const context = canvas.getContext("2d");
-          context.drawImage(image, 0, 0);
-          return canvas;
-        });
-      },
-    },
-
-    {
-      name: "unclean ImageBitmap",
-      factory: () => {
-        return makeImage().then(createImageBitmap);
-      },
-    },
-  ];
-
-  for (let { name, factory } of arguments) {
-    callback(name, factory);
-  }
-}
deleted file mode 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_canvas_tainting.js
+++ /dev/null
@@ -1,86 +0,0 @@
-"use strict";
-
-const server = createHttpServer({hosts: ["green.example.com", "red.example.com"]});
-
-server.registerDirectory("/data/", do_get_file("data"));
-
-server.registerPathHandler("/pixel.html", (request, response) => {
-  response.setStatusLine(request.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "text/html", false);
-  response.write(`<!DOCTYPE html>
-    <script>
-      function readByWeb() {
-        let ctx = document.querySelector("canvas").getContext("2d");
-        let {data} = ctx.getImageData(0, 0, 1, 1);
-        return data.slice(0, 3).join();
-      }
-    </script>
-  `);
-});
-
-add_task(async function test_contentscript_canvas_tainting() {
-  async function contentScript() {
-    let canvas = document.createElement("canvas");
-    let ctx = canvas.getContext("2d");
-    document.body.appendChild(canvas);
-
-    function draw(url) {
-      return new Promise(resolve => {
-        let img = document.createElement("img");
-        img.onload = () => {
-          ctx.drawImage(img, 0, 0, 1, 1);
-          resolve();
-        };
-        img.src = url;
-      });
-    }
-
-    function readByExt() {
-      let {data} = ctx.getImageData(0, 0, 1, 1);
-      return data.slice(0, 3).join();
-    }
-
-    let readByWeb = window.wrappedJSObject.readByWeb;
-
-    // Test reading after drawing an image from the same origin as the web page.
-    await draw("http://green.example.com/data/pixel_green.gif");
-    browser.test.assertEq(readByWeb(), "0,255,0", "Content can read same-origin image");
-    browser.test.assertEq(readByExt(), "0,255,0", "Extension can read same-origin image");
-
-    // Test reading after drawing a blue pixel data URI from extension content script.
-    await draw("data:image/gif;base64,R0lGODlhAQABAIABAAAA/wAAACwAAAAAAQABAAACAkQBADs=");
-    browser.test.assertThrows(readByWeb, /operation is insecure/, "Content can't read extension's image");
-    browser.test.assertEq(readByExt(), "0,0,255", "Extension can read its own image");
-
-    // Test after tainting the canvas with an image from a third party domain.
-    await draw("http://red.example.com/data/pixel_red.gif");
-    browser.test.assertThrows(readByWeb, /operation is insecure/, "Content can't read third party image");
-    browser.test.assertThrows(readByExt, /operation is insecure/, "Extension can't read fully tainted");
-
-    // Test canvas is still fully tainted after drawing extension's data: image again.
-    await draw("data:image/gif;base64,R0lGODlhAQABAIABAAAA/wAAACwAAAAAAQABAAACAkQBADs=");
-    browser.test.assertThrows(readByWeb, /operation is insecure/, "Canvas still fully tainted for content");
-    browser.test.assertThrows(readByExt, /operation is insecure/, "Canvas still fully tainted for extension");
-
-    browser.test.sendMessage("done");
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    manifest: {
-      content_scripts: [{
-        "matches": ["http://green.example.com/pixel.html"],
-        "js": ["cs.js"],
-      }],
-    },
-    files: {
-      "cs.js": contentScript,
-    },
-  });
-
-  await extension.startup();
-  let contentPage = await ExtensionTestUtils.loadContentPage("http://green.example.com/pixel.html");
-  await extension.awaitMessage("done");
-
-  await contentPage.close();
-  await extension.unload();
-});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
@@ -1,13 +1,12 @@
 [test_ext_i18n.js]
 skip-if = os == "android" || (os == "win" && debug) || (os == "linux")
 [test_ext_i18n_css.js]
 [test_ext_contentscript.js]
 [test_ext_contentscript_about_blank_start.js]
-[test_ext_contentscript_canvas_tainting.js]
 [test_ext_contentscript_scriptCreated.js]
 [test_ext_contentscript_triggeringPrincipal.js]
 skip-if = (os == "android" && debug) || (os == "win" && debug) # Windows: Bug 1438796
 [test_ext_contentscript_xrays.js]
 [test_ext_contentScripts_register.js]
 skip-if = os == "android"
 [test_ext_adoption_with_xrays.js]