Bug 1502799 - Implement origin-clean algorithm for ImageBitmap, r=aosmond
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 06 Nov 2018 23:31:34 +0100
changeset 444665 a5e9d42be7b3437129a0074da6f039e25f613ff1
parent 444664 f62458d789a570e1b2956d3a94f14953af3c2ae5
child 444666 8352f46310c7ecb0f96f50e7dae9615c1a2c9aac
push id109627
push useramarchesini@mozilla.com
push dateTue, 06 Nov 2018 22:31:51 +0000
treeherdermozilla-inbound@a5e9d42be7b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaosmond
bugs1502799
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1502799 - Implement origin-clean algorithm for ImageBitmap, r=aosmond
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
dom/canvas/ImageBitmap.cpp
dom/canvas/ImageBitmap.h
dom/canvas/WebGLContext.h
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/test/test_imagebitmap.html
dom/html/HTMLCanvasElement.cpp
dom/html/HTMLCanvasElement.h
testing/web-platform/meta/2dcontext/imagebitmap/createImageBitmap-origin.sub.html.ini
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1074,16 +1074,17 @@ CanvasRenderingContext2D::CanvasRenderin
   , mIsSkiaGL(false)
   , mHasPendingStableStateCallback(false)
   , mDrawObserver(nullptr)
   , mIsEntireFrameInvalid(false)
   , mPredictManyRedrawCalls(false)
   , mIsCapturedFrameInvalid(false)
   , mPathTransformWillUpdate(false)
   , mInvalidateCount(0)
+  , mWriteOnly(false)
 {
   if (!sMaxContextsInitialized) {
     sMaxContexts = gfxPrefs::CanvasAzureAcceleratedLimit();
     sMaxContextsInitialized = true;
   }
 
   sNumLivingContexts++;
 
@@ -2560,17 +2561,18 @@ CanvasRenderingContext2D::CreatePattern(
       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, false, true);
+      new CanvasPattern(this, srcSurf, repeatMode, nullptr,
+                        imgBitmap.IsWriteOnly(), true);
 
     return pat.forget();
   }
 
   EnsureTarget();
   if (!IsTargetValid()) {
     aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
@@ -4915,24 +4917,32 @@ CanvasRenderingContext2D::DrawImage(cons
   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();
@@ -5443,17 +5453,18 @@ CanvasRenderingContext2D::GetImageData(J
   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 (mCanvasElement && !mCanvasElement->CallerCanRead(aCx)) {
+  if (IsWriteOnly() ||
+      (mCanvasElement && !mCanvasElement->CallerCanRead(aCx))) {
     // 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);
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -36,16 +36,17 @@ class nsXULElement;
 namespace mozilla {
 namespace gl {
 class SourceSurface;
 } // namespace gl
 
 namespace dom {
 class HTMLImageElementOrSVGImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap;
 typedef HTMLImageElementOrSVGImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap CanvasImageSource;
+class ImageBitmap;
 class ImageData;
 class StringOrCanvasGradientOrCanvasPattern;
 class OwningStringOrCanvasGradientOrCanvasPattern;
 class TextMetrics;
 class CanvasPath;
 
 extern const mozilla::gfx::Float SIGMA_MAX;
 
@@ -1187,16 +1188,29 @@ protected:
     if (aPerDevPixel)
       *aPerDevPixel = devPixel;
     if (aPerCSSPixel)
       *aPerCSSPixel = cssPixel;
   }
 
   friend struct CanvasBidiProcessor;
   friend class CanvasDrawObserver;
+  friend class ImageBitmap;
+
+  void SetWriteOnly()
+  {
+    mWriteOnly = true;
+  }
+
+  bool IsWriteOnly() const
+  {
+    return mWriteOnly;
+  }
+
+  bool mWriteOnly;
 };
 
 size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext);
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* CanvasRenderingContext2D_h */
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -466,46 +466,45 @@ CheckSecurityForElements(const nsLayoutU
 }
 
 /*
  * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the
  * security checking.
  */
 template<class ElementType>
 static already_AddRefed<SourceSurface>
-GetSurfaceFromElement(nsIGlobalObject* aGlobal, ElementType& aElement, ErrorResult& aRv)
+GetSurfaceFromElement(nsIGlobalObject* aGlobal, ElementType& aElement,
+                      bool* aWriteOnly, ErrorResult& aRv)
 {
   nsLayoutUtils::SurfaceFromElementResult res =
     nsLayoutUtils::SurfaceFromElement(&aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE);
 
   RefPtr<SourceSurface> surface = res.GetSourceSurface();
   if (NS_WARN_IF(!surface)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
 
-  // check origin-clean
-  if (!CheckSecurityForElements(res)) {
-    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return nullptr;
-  }
+  // check write-only mode
+  *aWriteOnly = !CheckSecurityForElements(res);
 
   return surface.forget();
 }
 
 ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
-                         gfxAlphaType aAlphaType)
+                         bool aWriteOnly, gfxAlphaType aAlphaType)
   : mParent(aGlobal)
   , mData(aData)
   , mSurface(nullptr)
   , mDataWrapper(new ImageUtils(mData))
   , mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height)
   , mAlphaType(aAlphaType)
   , mAllocatedImageData(false)
+  , mWriteOnly(aWriteOnly)
 {
   MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
 
   mShutdownObserver = new ImageBitmapShutdownObserver(this);
   mShutdownObserver->RegisterObserver();
 }
 
 ImageBitmap::~ImageBitmap()
@@ -758,135 +757,141 @@ ImageBitmap::ToCloneData() const
   }
 
   UniquePtr<ImageBitmapCloneData> result(new ImageBitmapCloneData());
   result->mPictureRect = mPictureRect;
   result->mAlphaType = mAlphaType;
   RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
   result->mSurface = surface->GetDataSurface();
   MOZ_ASSERT(result->mSurface);
+  result->mWriteOnly = mWriteOnly;
 
   return result;
 }
 
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateFromSourceSurface(nsIGlobalObject* aGlobal,
                                      gfx::SourceSurface* aSource,
                                      ErrorResult& aRv)
 {
   RefPtr<layers::Image> data = CreateImageFromSurface(aSource);
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data,
+                                            false /* writeOnly */);
   ret->mAllocatedImageData = true;
   return ret.forget();
 }
 
 /* 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->mAlphaType);
+  RefPtr<ImageBitmap> ret =
+    new ImageBitmap(aGlobal, data, aData->mWriteOnly, aData->mAlphaType);
 
   ret->mAllocatedImageData = true;
 
   ErrorResult rv;
   ret->SetPictureRect(aData->mPictureRect, rv);
   return ret.forget();
 }
 
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
                                        OffscreenCanvas& aOffscreenCanvas,
                                        ErrorResult& aRv)
 {
-  // Check origin-clean.
-  if (aOffscreenCanvas.IsWriteOnly()) {
-    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return nullptr;
-  }
+  // Check write-only mode.
+  bool writeOnly = aOffscreenCanvas.IsWriteOnly();
 
   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);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
 
   ret->mAllocatedImageData = true;
 
   return ret.forget();
 }
 
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
                             const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
 {
   // Check if the image element is completely available or not.
   if (!aImageEl.Complete()) {
     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, aRv);
+  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aImageEl,
+                                                        &writeOnly, 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);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   return ret.forget();
 }
 
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, SVGImageElement& aImageEl,
                             const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
 {
+  bool writeOnly = true;
+
   // Get the SourceSurface out from the image element and then do security
   // checking.
-  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aImageEl, aRv);
+  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aImageEl,
+                                                        &writeOnly, 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);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   return ret.forget();
 }
@@ -908,28 +913,25 @@ ImageBitmap::CreateInternal(nsIGlobalObj
   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;
-  if (!CheckSecurityForElements(false, CORSUsed, principal)) {
-    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return nullptr;
-  }
+  bool writeOnly = !CheckSecurityForElements(false, CORSUsed, principal);
 
   // 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);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   return ret.forget();
 }
@@ -938,22 +940,28 @@ ImageBitmap::CreateInternal(nsIGlobalObj
 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;
   }
 
-  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aCanvasEl, aRv);
+  bool writeOnly = true;
+  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aCanvasEl,
+                                                        &writeOnly, 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;
@@ -977,17 +985,17 @@ ImageBitmap::CreateInternal(nsIGlobalObj
   // Create an Image from the SourceSurface.
   RefPtr<layers::Image> data = CreateImageFromSurface(croppedSurface);
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
 
   if (needToReportMemoryAllocation) {
     ret->mAllocatedImageData = true;
   }
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(cropRect, aRv);
@@ -1042,17 +1050,18 @@ ImageBitmap::CreateInternal(nsIGlobalObj
   }
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   // Create an ImageBimtap.
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, alphaType);
+  RefPtr<ImageBitmap> ret =
+    new ImageBitmap(aGlobal, data, false /* write-only */, alphaType);
 
   ret->mAllocatedImageData = true;
 
   // The cropping information has been handled in the CreateImageFromRawData()
   // function.
 
   return ret.forget();
 }
@@ -1065,21 +1074,18 @@ ImageBitmap::CreateInternal(nsIGlobalObj
   nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(win);
   if (NS_WARN_IF(!window) || !window->GetExtantDoc()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   window->GetExtantDoc()->WarnOnceAbout(nsIDocument::eCreateImageBitmapCanvasRenderingContext2D);
 
-  // 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;
   }
 
@@ -1091,17 +1097,18 @@ ImageBitmap::CreateInternal(nsIGlobalObj
 
   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);
+  RefPtr<ImageBitmap> ret =
+    new ImageBitmap(aGlobal, data, writeOnly);
 
   ret->mAllocatedImageData = true;
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
@@ -1113,17 +1120,19 @@ ImageBitmap::CreateInternal(nsIGlobalObj
                             const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
 {
   if (!aImageBitmap.mData) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   RefPtr<layers::Image> data = aImageBitmap.mData;
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aImageBitmap.mAlphaType);
+  RefPtr<ImageBitmap> ret =
+    new ImageBitmap(aGlobal, data, aImageBitmap.mWriteOnly,
+                    aImageBitmap.mAlphaType);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   return ret.forget();
 }
@@ -1427,26 +1436,24 @@ ImageBitmap::ReadStructuredClone(JSConte
   MOZ_ASSERT(aReader);
   // aParent might be null.
 
   uint32_t picRectX_;
   uint32_t picRectY_;
   uint32_t picRectWidth_;
   uint32_t picRectHeight_;
   uint32_t alphaType_;
-  uint32_t dummy;
+  uint32_t writeOnly;
 
   if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
       !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) ||
-      !JS_ReadUint32Pair(aReader, &alphaType_, &dummy)) {
+      !JS_ReadUint32Pair(aReader, &alphaType_, &writeOnly)) {
     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());
@@ -1460,17 +1467,18 @@ ImageBitmap::ReadStructuredClone(JSConte
   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, alphaType);
+    RefPtr<ImageBitmap> imageBitmap =
+      new ImageBitmap(aParent, img, !!writeOnly, alphaType);
 
     ErrorResult error;
     imageBitmap->SetPictureRect(IntRect(picRectX, picRectY,
                                         picRectWidth, picRectHeight), error);
     if (NS_WARN_IF(error.Failed())) {
       error.SuppressException();
       return nullptr;
     }
@@ -1500,17 +1508,17 @@ ImageBitmap::WriteStructuredClone(JSStru
   const uint32_t alphaType = BitwiseCast<uint32_t>(aImageBitmap->mAlphaType);
 
   // 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, 0))) {
+      NS_WARN_IF(!JS_WriteUint32Pair(aWriter, alphaType, aImageBitmap->mWriteOnly))) {
     return false;
   }
 
   RefPtr<SourceSurface> surface =
     aImageBitmap->mData->GetAsSourceSurface();
   RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
   RefPtr<DataSourceSurface> dstDataSurface;
   {
@@ -1834,17 +1842,18 @@ CreateImageBitmapFromBlob::MimeTypeAndDe
   });
 
   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);
+  RefPtr<ImageBitmap> imageBitmap =
+    new ImageBitmap(mGlobalObject, aImage, false /* write-only */);
 
   if (mCropRect.isSome()) {
     ErrorResult rv;
     imageBitmap->SetPictureRect(mCropRect.ref(), rv);
 
     if (rv.Failed()) {
       mPromise->MaybeReject(rv);
       return;
--- a/dom/canvas/ImageBitmap.h
+++ b/dom/canvas/ImageBitmap.h
@@ -55,16 +55,17 @@ class Promise;
 class PostMessageEvent; // For StructuredClone between windows.
 class SVGImageElement;
 
 struct ImageBitmapCloneData final
 {
   RefPtr<gfx::DataSourceSurface> mSurface;
   gfx::IntRect mPictureRect;
   gfxAlphaType mAlphaType;
+  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.
@@ -147,16 +148,21 @@ public:
   friend CreateImageBitmapFromBlob;
   friend CreateImageBitmapFromBlobTask;
   friend CreateImageBitmapFromBlobWorkerTask;
 
   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
@@ -169,16 +175,17 @@ protected:
    *    re-decoding if the original decoded data is alpha-premultiplied) and
    * 2) while decoding a blob. But we do not do it in both code path too.
    *
    * ImageData's underlying data is triggered as non-premultipliedAlpha, so set
    * the aIsPremultipliedAlpha to be false in the
    * CreateInternal(from ImageData) method.
    */
   ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
+              bool aWriteOnly,
               gfxAlphaType aAlphaType = gfxAlphaType::Premult);
 
   virtual ~ImageBitmap();
 
   void SetPictureRect(const gfx::IntRect& aRect, ErrorResult& aRv);
 
   static already_AddRefed<ImageBitmap>
   CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
@@ -250,16 +257,23 @@ protected:
   const gfxAlphaType mAlphaType;
 
   RefPtr<ImageBitmapShutdownObserver> mShutdownObserver;
 
   /*
    * 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/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -240,18 +240,21 @@ struct TexImageSourceAdapter final : pub
     TexImageSourceAdapter(const WebGLsizeiptr* pboOffset, GLuint ignored1, GLuint ignored2 = 0) {
         mPboOffset = pboOffset;
     }
 
     TexImageSourceAdapter(const WebGLsizeiptr* pboOffset, ErrorResult* ignored) {
         mPboOffset = pboOffset;
     }
 
-    TexImageSourceAdapter(const dom::ImageBitmap* imageBitmap, ErrorResult*) {
+    TexImageSourceAdapter(const dom::ImageBitmap* imageBitmap,
+                          ErrorResult* out_error)
+    {
         mImageBitmap = imageBitmap;
+        mOut_error = out_error;
     }
 
     TexImageSourceAdapter(const dom::ImageData* imageData, ErrorResult*) {
         mImageData = imageData;
     }
 
     TexImageSourceAdapter(const dom::Element* domElem, ErrorResult* const out_error) {
         mDomElem = domElem;
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -7,16 +7,17 @@
 
 #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"
@@ -217,21 +218,26 @@ FromPboOffset(WebGLContext* webgl, TexIm
     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, TexImageTarget target,
                 uint32_t width, uint32_t height, uint32_t depth,
-                const dom::ImageBitmap& imageBitmap)
+                const dom::ImageBitmap& imageBitmap, ErrorResult* aRv)
 {
+    if (imageBitmap.IsWriteOnly()) {
+        aRv->Throw(NS_ERROR_DOM_SECURITY_ERR);
+        return nullptr;
+    }
+
     UniquePtr<dom::ImageBitmapCloneData> cloneData = imageBitmap.ToCloneData();
     if (!cloneData) {
-      return nullptr;
+        return nullptr;
     }
 
     const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
 
     if (!width) {
         width = surf->GetSize().width;
     }
 
@@ -295,16 +301,24 @@ FromImageData(WebGLContext* webgl, TexIm
                                                alphaType);
 }
 
 UniquePtr<webgl::TexUnpackBlob>
 WebGLContext::FromDomElem(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)
@@ -416,17 +430,17 @@ WebGLContext::From(TexImageTarget target
 
     if (mBoundPixelUnpackBuffer) {
         ErrorInvalidOperation("PIXEL_UNPACK_BUFFER must be null.");
         return nullptr;
     }
 
     if (src.mImageBitmap) {
         return FromImageBitmap(this, target, width, height, depth,
-                               *(src.mImageBitmap));
+                               *(src.mImageBitmap), src.mOut_error);
     }
 
     if (src.mImageData) {
         return FromImageData(this, 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,23 +265,32 @@ function testSecurityErrors() {
         reject();
       }
 
       uncleanImage.src = "http://example.com/tests/dom/canvas/test/crossorigin/image.png";
     });
   }
 
   function checkPromiseFailedWithSecurityError(p) {
-    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 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 Promise.all([
     checkPromiseFailedWithSecurityError(getUncleanImagePromise()),
     checkPromiseFailedWithSecurityError(getUncleanVideoPromise()),
     checkPromiseFailedWithSecurityError(getTaintedCanvasPromise()),
     checkPromiseFailedWithSecurityError(getTaintedCanvasRenderingContex2dPromise()),
   ]);
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -1078,17 +1078,17 @@ HTMLCanvasElement::MozGetIPCContext(cons
 
 nsIntSize
 HTMLCanvasElement::GetSize()
 {
   return GetWidthHeight();
 }
 
 bool
-HTMLCanvasElement::IsWriteOnly()
+HTMLCanvasElement::IsWriteOnly() const
 {
   return mWriteOnly;
 }
 
 void
 HTMLCanvasElement::SetWriteOnly()
 {
   mExpandedReader = nullptr;
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -218,17 +218,17 @@ public:
   /**
    * Get the size in pixels of this canvas element
    */
   nsIntSize GetSize();
 
   /**
    * Determine whether the canvas is write-only.
    */
-  bool IsWriteOnly();
+  bool IsWriteOnly() const;
 
   /**
    * Force the canvas to be write-only.
    */
   void SetWriteOnly();
 
   /**
    * Force the canvas to be write-only, except for readers from
--- a/testing/web-platform/meta/2dcontext/imagebitmap/createImageBitmap-origin.sub.html.ini
+++ b/testing/web-platform/meta/2dcontext/imagebitmap/createImageBitmap-origin.sub.html.ini
@@ -1,23 +1,4 @@
 [createImageBitmap-origin.sub.html]
   prefs: [network.http.send_window_size:0]
-  [cross-origin HTMLImageElement]
-    expected: FAIL
-
-  [cross-origin SVGImageElement]
-    expected: FAIL
-
-  [cross-origin HTMLVideoElement]
-    expected: FAIL
-
-  [unclean HTMLCanvasElement]
-    expected: FAIL
-
-  [unclean ImageBitmap]
-    expected: FAIL
-
-  [redirected to cross-origin HTMLVideoElement]
-    expected: FAIL
-
   [redirected to same-origin HTMLVideoElement]
     expected: FAIL
-