Bug 421865 - Handle zero dimensions correctly in canvas GetWidthHeight(). r=joe sr=roc
authorBenjamin Peterson <benjamin@python.org>
Thu, 19 May 2011 12:49:18 +0200
changeset 69956 41f93610ff0c73f06476ca1bf9c43efdbd70344e
parent 69955 b63a7d9c17353ec0c8afeffaa8b7b4119465c36c
child 69957 970f93ab763f77846ad84bdbe7356b84c05f0162
push id76
push userbzbarsky@mozilla.com
push dateTue, 05 Jul 2011 17:00:57 +0000
treeherdermozilla-beta@d3a2732c35f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe, roc
bugs421865
milestone6.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 421865 - Handle zero dimensions correctly in canvas GetWidthHeight(). r=joe sr=roc
content/canvas/src/WebGLContext.cpp
content/canvas/src/nsCanvasRenderingContext2D.cpp
content/canvas/test/test_canvas.html
content/html/content/src/nsHTMLCanvasElement.cpp
layout/reftests/canvas/reftest.list
layout/reftests/canvas/zero-dimensions-ref.html
layout/reftests/canvas/zero-dimensions.html
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -354,16 +354,22 @@ WebGLContext::SetDimensions(PRInt32 widt
 {
     if (mCanvasElement) {
         HTMLCanvasElement()->InvalidateCanvas();
     }
 
     if (gl && mWidth == width && mHeight == height)
         return NS_OK;
 
+    // Zero-sized surfaces can cause problems.
+    if (width == 0 || height == 0) {
+        width = 1;
+        height = 1;
+    }
+
     // If we already have a gl context, then we just need to resize
     // FB0.
     if (gl &&
         gl->ResizeOffscreen(gfxIntSize(width, height)))
     {
         // everything's good, we're done here
         mWidth = width;
         mHeight = height;
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -467,16 +467,17 @@ protected:
      * Returns the image format this canvas should be allocated using. Takes
      * into account mOpaque, platform requirements, etc.
      */
     gfxASurface::gfxImageFormat GetImageFormat() const;
 
     // Member vars
     PRInt32 mWidth, mHeight;
     PRPackedBool mValid;
+    PRPackedBool mZero;
     PRPackedBool mOpaque;
     PRPackedBool mResetLayer;
     PRPackedBool mIPC;
 
     // the canvas element we're a context of
     nsCOMPtr<nsIDOMHTMLCanvasElement> mCanvasElement;
     nsHTMLCanvasElement *HTMLCanvasElement() {
         return static_cast<nsHTMLCanvasElement*>(mCanvasElement.get());
@@ -819,17 +820,17 @@ NS_NewCanvasRenderingContext2D(nsIDOMCan
     if (!ctx)
         return NS_ERROR_OUT_OF_MEMORY;
 
     *aResult = ctx.forget().get();
     return NS_OK;
 }
 
 nsCanvasRenderingContext2D::nsCanvasRenderingContext2D()
-    : mValid(PR_FALSE), mOpaque(PR_FALSE), mResetLayer(PR_TRUE)
+    : mValid(PR_FALSE), mZero(PR_FALSE), mOpaque(PR_FALSE), mResetLayer(PR_TRUE)
     , mIPC(PR_FALSE)
     , mCanvasElement(nsnull)
     , mSaveCount(0), mIsEntireFrameInvalid(PR_FALSE)
     , mPredictManyRedrawCalls(PR_FALSE), mHasPath(PR_FALSE), mInvalidateCount(0)
     , mLastStyle(STYLE_MAX), mStyleStack(20)
 {
     sNumLivingContexts++;
 }
@@ -1085,16 +1086,22 @@ nsCanvasRenderingContext2D::RedrawUser(c
 NS_IMETHODIMP
 nsCanvasRenderingContext2D::SetDimensions(PRInt32 width, PRInt32 height)
 {
     nsRefPtr<gfxASurface> surface;
 
     // Check that the dimensions are sane
     gfxIntSize size(width, height);
     if (gfxASurface::CheckSurfaceSize(size, 0xffff)) {
+        // Zero sized surfaces have problems, so just use a 1 by 1.
+        if (height == 0 || width == 0) {
+            mZero = PR_TRUE;
+            height = 1;
+            width = 1;
+        }
 
         gfxASurface::gfxImageFormat format = GetImageFormat();
 
         if (PR_GetEnv("MOZ_CANVAS_IMAGE_SURFACE")) {
             surface = new gfxImageSurface(gfxIntSize(width, height), format);
         } else {
             nsCOMPtr<nsIContent> content =
                 do_QueryInterface(static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement));
@@ -3750,38 +3757,40 @@ nsCanvasRenderingContext2D::GetImageData
     if (tmpsurf->CairoStatus())
         return NS_ERROR_FAILURE;
 
     nsRefPtr<gfxContext> tmpctx = new gfxContext(tmpsurf);
 
     if (tmpctx->HasError())
         return NS_ERROR_FAILURE;
 
-    gfxRect srcRect(0, 0, mWidth, mHeight);
-    gfxRect destRect(x, y, w, h);
-
-    bool finishedPainting = false;
-    // In the common case, we want to avoid the Rectangle call.
-    if (!srcRect.Contains(destRect)) {
-        // If the requested area is entirely outside the canvas, we're done.
-        gfxRect tmp = srcRect.Intersect(destRect);
-        finishedPainting = tmp.IsEmpty();
-
-        // Set clipping region if necessary.
+    if (!mZero) {
+        gfxRect srcRect(0, 0, mWidth, mHeight);
+        gfxRect destRect(x, y, w, h);
+
+        bool finishedPainting = false;
+        // In the common case, we want to avoid the Rectangle call.
+        if (!srcRect.Contains(destRect)) {
+            // If the requested area is entirely outside the canvas, we're done.
+            gfxRect tmp = srcRect.Intersect(destRect);
+            finishedPainting = tmp.IsEmpty();
+
+            // Set clipping region if necessary.
+            if (!finishedPainting) {
+                tmpctx->Rectangle(tmp);
+            }
+        }
+
         if (!finishedPainting) {
-            tmpctx->Rectangle(tmp);
+            tmpctx->SetOperator(gfxContext::OPERATOR_SOURCE);
+            tmpctx->SetSource(mSurface, gfxPoint(-x, -y));
+            tmpctx->Paint();
         }
     }
 
-    if (!finishedPainting) {
-        tmpctx->SetOperator(gfxContext::OPERATOR_SOURCE);
-        tmpctx->SetSource(mSurface, gfxPoint(-x, -y));
-        tmpctx->Paint();
-    }
-
     // make sure sUnpremultiplyTable has been created
     EnsureUnpremultiplyTable();
 
     // NOTE! dst is the same as src, and this relies on reading
     // from src and advancing that ptr before writing to dst.
     PRUint8 *src = aData;
     PRUint8 *dst = aData;
 
--- a/content/canvas/test/test_canvas.html
+++ b/content/canvas/test/test_canvas.html
@@ -14957,20 +14957,20 @@ ctx2.fillRect(0, 0, 100, 50);
 var pattern = ctx.createPattern(canvas2, 'repeat');
 
 ctx.fillStyle = '#0f0';
 ctx.fillRect(0, 0, 100, 50);
 ctx.fillStyle = '#f00';
 ctx.fillStyle = pattern;
 ctx.fillRect(0, 0, 100, 50);
 
-todo_isPixel(ctx, 1,1, 0,255,0,255, 0);
-todo_isPixel(ctx, 98,1, 0,255,0,255, 0);
-todo_isPixel(ctx, 1,48, 0,255,0,255, 0);
-todo_isPixel(ctx, 98,48, 0,255,0,255, 0);
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
 
 
 }
 </script>
 
 <!-- [[[ test_2d.pattern.crosscanvas.html ]]] -->
 
 <p>Canvas test: 2d.pattern.crosscanvas</p>
@@ -21289,16 +21289,43 @@ function test_2d_imageSmoothing() {
       pixels[1] == 255 &&
       pixels[2] == 0 &&
       pixels[3] == 255,
       "pixel is " + pixels.toSource() + " (expected [0,255,0,255])");
 }
 
 </script>
 
+<p>Canvas test: zero_dimensions</p>
+<canvas id="c684" width="0" height="0"></canvas>
+<script type="text/javascript">
+function test_zero_dimensions() {
+  var c = document.getElementById("c684");
+  ok(c.width == 0, "c.width not 0");
+  ok(c.height == 0, "c.height not 0");
+}
+</script>
+
+<p>Canvas test: zero_dimensions_image_data</p>
+<canvas id="c685" width="0" height="0"></canvas>
+<script type="text/javascript">
+function test_zero_dimensions_imagedata() {
+  var c = document.getElementById("c685");
+  var ctx = c.getContext("2d");
+  ctx.fillStyle = "blue";
+  ctx.fillRect(0, 0, 100, 100);
+  var imgdata = ctx.getImageData(0, 0, 100, 100);
+  var isTransparentBlack = true;
+  for (var i = 0; i < imgdata.data.length; ++i)
+      if (imgdata.data[i] !== 0)
+          isTransparentBlack = false;
+  ok(isTransparentBlack, "isTransparentBlack");
+}
+</script>
+
 <script>
 
 function asyncTestsDone() {
 	if (isDone_test_2d_drawImage_animated_apng &&
 		isDone_test_2d_drawImage_animated_gif) {
 		SimpleTest.finish();
 	} else {
 		setTimeout(asyncTestsDone, 500);
@@ -24549,16 +24576,26 @@ function runTests() {
   ok(false, "unexpected exception thrown in: test_2d_imagedata_coercion");
  }
  try {
   test_2d_imageSmoothing();
  } catch (e) {
   ok(false, "unexpected exception thrown in: test_2d_imageSmoothing");
  }
  try {
+  test_zero_dimensions();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_zero_dimensions");
+ }
+ try {
+  test_zero_dimensions_imagedata();
+ } catch(e) {
+  ok(false, "unexpected exception thrown in: test_zero_dimensions_imagedata");
+ }
+ try {
   // run this test last since it replaces the getContext method
   test_type_replace();
  } catch (e) {
   ok(false, "unexpected exception thrown in: test_type_replace");
  }
  
  //run the asynchronous tests
  try {
--- a/content/html/content/src/nsHTMLCanvasElement.cpp
+++ b/content/html/content/src/nsHTMLCanvasElement.cpp
@@ -98,36 +98,31 @@ NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION
                                                nsGenericHTMLElement)
 NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLCanvasElement)
 
 NS_IMPL_ELEMENT_CLONE(nsHTMLCanvasElement)
 
 nsIntSize
 nsHTMLCanvasElement::GetWidthHeight()
 {
-  nsIntSize size(0,0);
+  nsIntSize size(DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT);
   const nsAttrValue* value;
 
   if ((value = GetParsedAttr(nsGkAtoms::width)) &&
       value->Type() == nsAttrValue::eInteger)
   {
       size.width = value->GetIntegerValue();
   }
 
   if ((value = GetParsedAttr(nsGkAtoms::height)) &&
       value->Type() == nsAttrValue::eInteger)
   {
       size.height = value->GetIntegerValue();
   }
 
-  if (size.width <= 0)
-    size.width = DEFAULT_CANVAS_WIDTH;
-  if (size.height <= 0)
-    size.height = DEFAULT_CANVAS_HEIGHT;
-
   return size;
 }
 
 NS_IMPL_UINT_ATTR_DEFAULT_VALUE(nsHTMLCanvasElement, Width, width, DEFAULT_CANVAS_WIDTH)
 NS_IMPL_UINT_ATTR_DEFAULT_VALUE(nsHTMLCanvasElement, Height, height, DEFAULT_CANVAS_HEIGHT)
 NS_IMPL_BOOL_ATTR(nsHTMLCanvasElement, MozOpaque, moz_opaque)
 
 nsresult
@@ -641,28 +636,30 @@ nsHTMLCanvasElement::InvalidateCanvasCon
     return;
 
   frame->MarkLayersActive();
 
   nsRect invalRect;
   nsRect contentArea = frame->GetContentRect();
   if (damageRect) {
     nsIntSize size = GetWidthHeight();
+    if (size.width != 0 && size.height != 0) {
 
-    // damageRect and size are in CSS pixels; contentArea is in appunits
-    // We want a rect in appunits; so avoid doing pixels-to-appunits and
-    // vice versa conversion here.
-    gfxRect realRect(*damageRect);
-    realRect.Scale(contentArea.width / gfxFloat(size.width),
-                   contentArea.height / gfxFloat(size.height));
-    realRect.RoundOut();
+      // damageRect and size are in CSS pixels; contentArea is in appunits
+      // We want a rect in appunits; so avoid doing pixels-to-appunits and
+      // vice versa conversion here.
+      gfxRect realRect(*damageRect);
+      realRect.Scale(contentArea.width / gfxFloat(size.width),
+                     contentArea.height / gfxFloat(size.height));
+      realRect.RoundOut();
 
-    // then make it a nsRect
-    invalRect = nsRect(realRect.X(), realRect.Y(),
-                       realRect.Width(), realRect.Height());
+      // then make it a nsRect
+      invalRect = nsRect(realRect.X(), realRect.Y(),
+                         realRect.Width(), realRect.Height());
+    }
   } else {
     invalRect = nsRect(nsPoint(0, 0), contentArea.Size());
   }
   invalRect.MoveBy(contentArea.TopLeft() - frame->GetPosition());
 
   Layer* layer = frame->InvalidateLayer(invalRect, nsDisplayItem::TYPE_CANVAS);
   if (layer) {
     static_cast<CanvasLayer*>(layer)->Updated();
--- a/layout/reftests/canvas/reftest.list
+++ b/layout/reftests/canvas/reftest.list
@@ -40,8 +40,10 @@ asserts-if(cocoaWidget,0-2) == size-chan
 
 fails-if(Android) != text-font-lang.html text-font-lang-notref.html
 
 == strokeText-path.html strokeText-path-ref.html
 
 # gradient off-by-one, fails on windows and linux
 == linear-gradient-1a.html linear-gradient-1-ref.html
 fails-if(cocoaWidget) == linear-gradient-1b.html linear-gradient-1-ref.html
+
+== zero-dimensions.html zero-dimensions-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/canvas/zero-dimensions-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Empty Canvas</title>
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/canvas/zero-dimensions.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Empty Canvas</title>
+</head>
+<body>
+<canvas style="border-width: 2px solid black" width="0" height="0"></canvas>
+</body>
+</html>