Bug 1219985 - The canvas rendering context 2d should be opaque if either the moz-opaque attribute is set or if it has been initialized with alpha:false. r=jrmuizel
authorMarkus Stange <mstange@themasta.com>
Wed, 02 May 2018 11:23:53 -0400
changeset 472805 1292058fb7248a32e4ad3062397c0c86defaaaab
parent 472804 acddc570c047cab990a0b232010898244c8e9d0d
child 472806 ec8911b8f9c7ea3fcb51a7957903503eb163ae30
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1219985
milestone61.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 1219985 - The canvas rendering context 2d should be opaque if either the moz-opaque attribute is set or if it has been initialized with alpha:false. r=jrmuizel If the canvas is cleared by setting the width or height attributes, its opaqueness should not be affected. This patch keeps support for moz-opaque, and also keeps the behavior that changing the moz-opaque attribute clears the canvas, even if this does not affect the actual opaqueness of the canvas. MozReview-Commit-ID: LOlsJxiP9kc
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
dom/canvas/CanvasRenderingContextHelper.cpp
dom/canvas/ImageBitmapRenderingContext.cpp
dom/canvas/ImageBitmapRenderingContext.h
dom/canvas/WebGLContext.h
dom/canvas/nsICanvasRenderingContextInternal.h
layout/reftests/bugs/1219985-1.html
layout/reftests/bugs/1219985-2.html
layout/reftests/bugs/1219985-3.html
layout/reftests/bugs/1219985-4.html
layout/reftests/bugs/1219985-5.html
layout/reftests/bugs/1219985-6.html
layout/reftests/bugs/1219985-7.html
layout/reftests/bugs/1219985-8.html
layout/reftests/bugs/1219985-ref-opaque-clear.html
layout/reftests/bugs/1219985-ref-opaque-with-rendering.html
layout/reftests/bugs/1219985-ref-transparent-clear.html
layout/reftests/bugs/1219985-ref-transparent-with-rendering.html
layout/reftests/bugs/reftest.list
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1080,17 +1080,20 @@ static int32_t sMaxContexts = 0;
 
 
 
 CanvasRenderingContext2D::CanvasRenderingContext2D(layers::LayersBackend aCompositorBackend)
   : mRenderingMode(RenderingMode::OpenGLBackendMode)
   , mCompositorBackend(aCompositorBackend)
   // these are the default values from the Canvas spec
   , mWidth(0), mHeight(0)
-  , mZero(false), mOpaque(false)
+  , mZero(false)
+  , mOpaqueAttrValue(false)
+  , mContextAttributesHasAlpha(true)
+  , mOpaque(false)
   , mResetLayer(true)
   , mIPC(false)
   , mIsSkiaGL(false)
   , mHasPendingStableStateCallback(false)
   , mDrawObserver(nullptr)
   , mIsEntireFrameInvalid(false)
   , mPredictManyRedrawCalls(false)
   , mIsCapturedFrameInvalid(false)
@@ -1990,22 +1993,29 @@ CanvasRenderingContext2D::InitializeWith
     // Cf comment in EnsureTarget
     mTarget->PushClipRect(gfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
   }
 
   return NS_OK;
 }
 
 void
-CanvasRenderingContext2D::SetIsOpaque(bool aIsOpaque)
-{
-  if (aIsOpaque != mOpaque) {
-    mOpaque = aIsOpaque;
-    ClearTarget();
-  }
+CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue)
+{
+  if (aOpaqueAttrValue != mOpaqueAttrValue) {
+    mOpaqueAttrValue = aOpaqueAttrValue;
+    UpdateIsOpaque();
+  }
+}
+
+void
+CanvasRenderingContext2D::UpdateIsOpaque()
+{
+  mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue;
+  ClearTarget();
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::SetIsIPC(bool aIsIPC)
 {
   if (aIsIPC != mIPC) {
     mIPC = aIsIPC;
     ClearTarget();
@@ -2038,19 +2048,18 @@ CanvasRenderingContext2D::SetContextOpti
 
       // We want to lock into software, so remove the observer that
       // may potentially change that...
       RemoveDrawObserver();
       mRenderingMode = RenderingMode::SoftwareBackendMode;
     }
   }
 
-  if (!attributes.mAlpha) {
-    SetIsOpaque(true);
-  }
+  mContextAttributesHasAlpha = attributes.mAlpha;
+  UpdateIsOpaque();
 
   return NS_OK;
 }
 
 UniquePtr<uint8_t[]>
 CanvasRenderingContext2D::GetImageBuffer(int32_t* aFormat)
 {
   UniquePtr<uint8_t[]> ret;
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -455,17 +455,17 @@ public:
   {
     EnsureTarget();
     if (aOutAlphaType) {
       *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
     }
     return mTarget->Snapshot();
   }
 
-  virtual void SetIsOpaque(bool aIsOpaque) override;
+  virtual void SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
   already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          Layer* aOldLayer,
                                          LayerManager* aManager) override;
 
   bool UpdateWebRenderCanvasData(nsDisplayListBuilder* aBuilder,
                                  WebRenderCanvasData* aCanvasData) override;
@@ -614,16 +614,19 @@ protected:
    // Returns whether a filter was successfully parsed.
   bool ParseFilter(const nsAString& aString,
                    nsTArray<nsStyleFilter>& aFilterChain,
                    ErrorResult& aError);
 
   // Returns whether the font was successfully updated.
   bool SetFontInternal(const nsAString& aFont, mozilla::ErrorResult& aError);
 
+  // Clears the target and updates mOpaque based on mOpaqueAttrValue and
+  // mContextAttributesHasAlpha.
+  void UpdateIsOpaque();
 
   /**
    * Creates the error target, if it doesn't exist
    */
   static void EnsureErrorTarget();
 
   /* This function ensures there is a writable pathbuilder available, this
    * pathbuilder may be working in user space or in device space or
@@ -759,16 +762,27 @@ protected:
 
   // Member vars
   int32_t mWidth, mHeight;
 
   // This is true when the canvas is valid, but of zero size, this requires
   // specific behavior on some operations.
   bool mZero;
 
+  // The two ways to set the opaqueness of the canvas.
+  // mOpaqueAttrValue: Whether the <canvas> element has the moz-opaque attribute
+  // set. Can change during the lifetime of the context. Non-standard, should
+  // hopefully go away soon.
+  // mContextAttributesHasAlpha: The standard way of setting canvas opaqueness.
+  // Set at context initialization time and never changes.
+  bool mOpaqueAttrValue;
+  bool mContextAttributesHasAlpha;
+
+  // Determines the context's opaqueness. Is computed from mOpaqueAttrValue and
+  // mContextAttributesHasAlpha in UpdateIsOpaque().
   bool mOpaque;
 
   // This is true when the next time our layer is retrieved we need to
   // recreate it (i.e. our backing surface changed)
   bool mResetLayer;
   // This is needed for drawing in drawAsyncXULElement
   bool mIPC;
   // True if the current DrawTarget is using skia-gl, used so we can avoid
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -226,17 +226,17 @@ CanvasRenderingContextHelper::UpdateCont
 {
   if (!mCurrentContext)
     return NS_OK;
 
   nsIntSize sz = GetWidthHeight();
 
   nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
 
-  currentContext->SetIsOpaque(GetOpaqueAttr());
+  currentContext->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr());
 
   nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions,
                                          aRvForDictionaryInit);
   if (NS_FAILED(rv)) {
     mCurrentContext = nullptr;
     return rv;
   }
 
--- a/dom/canvas/ImageBitmapRenderingContext.cpp
+++ b/dom/canvas/ImageBitmapRenderingContext.cpp
@@ -184,18 +184,19 @@ ImageBitmapRenderingContext::GetSurfaceS
   if (surface->GetSize() != IntSize(mWidth, mHeight)) {
     return MatchWithIntrinsicSize();
   }
 
   return surface.forget();
 }
 
 void
-ImageBitmapRenderingContext::SetIsOpaque(bool aIsOpaque)
+ImageBitmapRenderingContext::SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue)
 {
+  // ignored
 }
 
 bool
 ImageBitmapRenderingContext::GetIsOpaque()
 {
   return false;
 }
 
--- a/dom/canvas/ImageBitmapRenderingContext.h
+++ b/dom/canvas/ImageBitmapRenderingContext.h
@@ -61,17 +61,17 @@ public:
   virtual mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const char16_t* aEncoderOptions,
                             nsIInputStream** aStream) override;
 
   virtual already_AddRefed<mozilla::gfx::SourceSurface>
   GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType) override;
 
-  virtual void SetIsOpaque(bool aIsOpaque) override;
+  virtual void SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue) override;
   virtual bool GetIsOpaque() override;
   NS_IMETHOD Reset() override;
   virtual already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                  Layer* aOldLayer,
                                                  LayerManager* aManager) override;
   virtual void MarkContextClean() override;
 
   NS_IMETHOD Redraw(const gfxRect& aDirty) override;
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -375,17 +375,17 @@ public:
     virtual UniquePtr<uint8_t[]> GetImageBuffer(int32_t* out_format) override;
     NS_IMETHOD GetInputStream(const char* mimeType,
                               const char16_t* encoderOptions,
                               nsIInputStream** out_stream) override;
 
     virtual already_AddRefed<mozilla::gfx::SourceSurface>
     GetSurfaceSnapshot(gfxAlphaType* out_alphaType) override;
 
-    virtual void SetIsOpaque(bool) override {};
+    virtual void SetOpaqueValueFromOpaqueAttr(bool) override {};
     bool GetIsOpaque() override { return !mOptions.alpha; }
     NS_IMETHOD SetContextOptions(JSContext* cx,
                                  JS::Handle<JS::Value> options,
                                  ErrorResult& aRvForDictionaryInit) override;
 
     NS_IMETHOD SetIsIPC(bool) override {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -121,21 +121,26 @@ public:
   // This gets an Azure SourceSurface for the canvas, this will be a snapshot
   // of the canvas at the time it was called.
   // If premultAlpha is provided, then it assumed the callee can handle
   // un-premultiplied surfaces, and *premultAlpha will be set to false
   // if one is returned.
   virtual already_AddRefed<mozilla::gfx::SourceSurface>
   GetSurfaceSnapshot(gfxAlphaType* out_alphaType = nullptr) = 0;
 
-  // If this context is opaque, the backing store of the canvas should
+  // If this is called with true, the backing store of the canvas should
   // be created as opaque; all compositing operators should assume the
-  // dst alpha is always 1.0.  If this is never called, the context
-  // defaults to false (not opaque).
-  virtual void SetIsOpaque(bool isOpaque) = 0;
+  // dst alpha is always 1.0.  If this is never called, the context's
+  // opaqueness is determined by the context attributes that it's initialized
+  // with.
+  virtual void SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue) = 0;
+
+  // Returns whether the context is opaque. This value can be based both on
+  // the value of the moz-opaque attribute and on the context's initialization
+  // attributes.
   virtual bool GetIsOpaque() = 0;
 
   // Invalidate this context and release any held resources, in preperation
   // for possibly reinitializing with SetDimensions/InitializeWithSurface.
   NS_IMETHOD Reset() = 0;
 
   // Return the CanvasLayer for this context, creating
   // one for the given layer manager if not available.
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Basic transparent rendering</title>
+
+<div style="width: 200px; height: 200px; background-color: red;">
+  <canvas id="c" width="200" height="200"></canvas>
+</div>
+
+<script>
+
+var c = document.getElementById('c');
+var ctx = c.getContext('2d', { alpha: true });
+ctx.fillStyle = 'green';
+ctx.fillRect(50, 50, 100, 100);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Setting the canvas size should clear the canvas</title>
+
+<div style="width: 200px; height: 200px; background-color: red;">
+  <canvas id="c" width="200" height="200"></canvas>
+</div>
+
+<script>
+
+var c = document.getElementById('c');
+var ctx = c.getContext('2d', { alpha: true });
+ctx.fillStyle = 'green';
+ctx.fillRect(50, 50, 100, 100);
+c.width = 200;
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-3.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Basic rendering into a non-alpha canvas</title>
+
+<div style="width: 200px; height: 200px; background-color: red;">
+  <canvas id="c" width="200" height="200"></canvas>
+</div>
+
+<script>
+
+var c = document.getElementById('c');
+var ctx = c.getContext('2d', { alpha: false });
+ctx.fillStyle = 'green';
+ctx.fillRect(50, 50, 100, 100);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-4.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Setting the canvas size on a non-alpha should clear the canvas to opaque black</title>
+
+<div style="width: 200px; height: 200px; background-color: red;">
+  <canvas id="c" width="200" height="200"></canvas>
+</div>
+
+<script>
+
+var c = document.getElementById('c');
+var ctx = c.getContext('2d', { alpha: false });
+ctx.fillStyle = 'green';
+ctx.fillRect(50, 50, 100, 100);
+c.width = 200;
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-5.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Only the context attributes from the first getContext call should be respected.</title>
+
+<div style="width: 200px; height: 200px; background-color: red;">
+  <canvas id="c" width="200" height="200"></canvas>
+</div>
+
+<script>
+
+var c = document.getElementById('c');
+var ctx = c.getContext('2d', { alpha: true });
+ctx = c.getContext('2d', { alpha: false });
+ctx.fillStyle = 'green';
+ctx.fillRect(50, 50, 100, 100);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-6.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: moz-opaque should have the same effect as alpha:false</title>
+
+<div style="width: 200px; height: 200px; background-color: red;">
+  <canvas id="c" width="200" height="200" moz-opaque="true"></canvas>
+</div>
+
+<script>
+
+var c = document.getElementById('c');
+var ctx = c.getContext('2d', { alpha: true });
+ctx.fillStyle = 'green';
+ctx.fillRect(50, 50, 100, 100);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-7.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Unsetting moz-opaque should clear the canvas</title>
+
+<div style="width: 200px; height: 200px; background-color: red;">
+  <canvas id="c" width="200" height="200" moz-opaque="true"></canvas>
+</div>
+
+<script>
+
+var c = document.getElementById('c');
+var ctx = c.getContext('2d', { alpha: true });
+ctx.fillStyle = 'green';
+ctx.fillRect(50, 50, 100, 100);
+c.removeAttribute("moz-opaque");
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-8.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Unsetting moz-opaque should clear the canvas even if the canvas has been created with alpha:false, and the canvas should stay opaque.</title>
+
+<div style="width: 200px; height: 200px; background-color: red;">
+  <canvas id="c" width="200" height="200" moz-opaque="true"></canvas>
+</div>
+
+<script>
+
+var c = document.getElementById('c');
+var ctx = c.getContext('2d', { alpha: false });
+ctx.fillStyle = 'green';
+ctx.fillRect(50, 50, 100, 100);
+c.removeAttribute("moz-opaque");
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-ref-opaque-clear.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Reference for an opaque canvas with nothing rendered in it</title>
+
+<div style="width: 200px; height: 200px; background: black"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-ref-opaque-with-rendering.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Reference for an opaque canvas with a green square rendered in it</title>
+
+<div style="width: 200px; height: 200px; background: black; display: flex;">
+  <div style="width: 100px; height: 100px; margin: auto; background: green;"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-ref-transparent-clear.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Reference for a regular (non-opaque) canvas with nothing rendered in it. The red background behind the canvas is visible.</title>
+
+<div style="width: 200px; height: 200px; background: red"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1219985-ref-transparent-with-rendering.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug 1219985: Reference for a regular (non-opaque) canvas with a green square rendered in it. The red background behind the canvas is visible.</title>
+
+<div style="width: 200px; height: 200px; background: red; display: flex;">
+  <div style="width: 100px; height: 100px; margin: auto; background: green;"></div>
+</div>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1954,16 +1954,24 @@ fuzzy(1,74) fails-if(Android||gtkWidget)
 == 1202512-1.html 1202512-1-ref.html
 fuzzy-if(skiaContent,1,1) == 1202512-2.html 1202512-2-ref.html
 != 1207326-1.html about:blank
 == 1209603-1.html 1209603-1-ref.html
 == 1209994-1.html 1209994-1-ref.html
 == 1209994-2.html 1209994-2-ref.html
 == 1209994-3.html 1209994-3-ref.html
 == 1209994-4.html 1209994-4-ref.html
+== 1219985-1.html 1219985-ref-transparent-with-rendering.html
+== 1219985-2.html 1219985-ref-transparent-clear.html
+== 1219985-3.html 1219985-ref-opaque-with-rendering.html
+== 1219985-4.html 1219985-ref-opaque-clear.html
+== 1219985-5.html 1219985-ref-transparent-with-rendering.html
+== 1219985-6.html 1219985-ref-opaque-with-rendering.html
+== 1219985-7.html 1219985-ref-transparent-clear.html
+== 1219985-8.html 1219985-ref-opaque-clear.html
 == 1222226-1.html 1222226-1-ref.html
 pref(layout.css.overflow-clip-box.enabled,true) == 1226278.html 1226278-ref.html
 == 1230466.html about:blank
 random-if(gtkWidget) != 1238243-1.html 1238243-1-notref.html # may fail on Linux, depending on Korean fonts available
 == 1238243-2.html 1238243-2-ref.html
 fuzzy(100,2000) == 1239564.html 1239564-ref.html
 == 1242172-1.html 1242172-1-ref.html
 fuzzy-if(webrender,0-2,0-2601) == 1242172-2.html 1242172-2-ref.html