Bug 648610: Implement <canvas>.toBlob. r=smaug
authorKyle Huey <khuey@kylehuey.com>
Mon, 15 Oct 2012 14:15:25 -0700
changeset 110466 227e87a7c628cc57cff6f8649ef74a917f31e483
parent 110465 76dcbb473416a298551654a23f9e0efbbf71c034
child 110467 6593f27f18985a359a8d4df7e747099d9b692fe5
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewerssmaug
bugs648610
milestone19.0a1
Bug 648610: Implement <canvas>.toBlob. r=smaug
content/canvas/test/Makefile.in
content/canvas/test/test_toBlob.html
content/html/content/src/nsHTMLCanvasElement.cpp
dom/interfaces/html/nsIDOMHTMLCanvasElement.idl
--- a/content/canvas/test/Makefile.in
+++ b/content/canvas/test/Makefile.in
@@ -45,16 +45,17 @@ MOCHITEST_FILES = \
 	test_2d.composite.image.destination-in.html \
 	test_2d.composite.image.source-in.html \
 	test_2d.composite.image.source-out.html \
 	test_2d.composite.uncovered.image.destination-in.html \
 	test_2d.composite.uncovered.image.source-in.html \
 	test_2d.composite.uncovered.image.source-out.html \
 	test_2d.drawImage.zerocanvas.html \
 	test_2d.strokeRect.zero.5.html \
+	test_toBlob.html \
 	test_toDataURL_alpha.html \
 	test_toDataURL_lowercase_ascii.html \
 	test_toDataURL_parameters.html \
 	test_mozGetAsFile.html \
 	test_canvas_strokeStyle_getter.html \
 	test_bug613794.html \
 	test_bug753758.html \
 	test_bug764125.html \
copy from content/canvas/test/test_mozGetAsFile.html
copy to content/canvas/test/test_toBlob.html
--- a/content/canvas/test/test_mozGetAsFile.html
+++ b/content/canvas/test/test_toBlob.html
@@ -2,22 +2,22 @@
 <title>Canvas test: mozGetAsFile</title>
 <script src="/MochiKit/MochiKit.js"></script>
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" href="/tests/SimpleTest/test.css">
 <body>
 <canvas id="c" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
 <script>
 
-var gCompares = 0;
+var gCompares = 2;
 
-function compareAsync(file, canvas, type)
+function BlobListener(type, canvas, file)
 {
-  ++gCompares;
-
+  is(file.type, type,
+     "When a valid type is specified that should be returned");
   var reader = new FileReader();
   reader.onload = 
     function(e) {
       is(e.target.result, canvas.toDataURL(type),
  "<canvas>.mozGetAsFile().getAsDataURL() should equal <canvas>.toDataURL()");
       if (--gCompares == 0) {
         SimpleTest.finish();
       }
@@ -28,23 +28,15 @@ function compareAsync(file, canvas, type
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function () {
 
 var canvas = document.getElementById('c');
 var ctx = canvas.getContext('2d');
 
 ctx.drawImage(document.getElementById('yellow75.png'), 0, 0);
 
-var pngfile = canvas.mozGetAsFile("foo.png");
-is(pngfile.type, "image/png", "Default type for mozGetAsFile should be PNG");
-compareAsync(pngfile, canvas, "image/png");
-is(pngfile.name, "foo.png", "File name should be what we passed in");
-
-var jpegfile = canvas.mozGetAsFile("bar.jpg", "image/jpeg");
-is(jpegfile.type, "image/jpeg",
-   "When a valid type is specified that should be returned");
-compareAsync(jpegfile, canvas, "image/jpeg");
-is(jpegfile.name, "bar.jpg", "File name should be what we passed in");
+canvas.toBlob(BlobListener.bind(undefined, "image/png", canvas));
+canvas.toBlob(BlobListener.bind(undefined, "image/jpeg", canvas), "image/jpeg");
 
 });
 </script>
 <img src="image_yellow75.png" id="yellow75.png" class="resource">
 
--- a/content/html/content/src/nsHTMLCanvasElement.cpp
+++ b/content/html/content/src/nsHTMLCanvasElement.cpp
@@ -35,16 +35,42 @@
 
 #define DEFAULT_CANVAS_WIDTH 300
 #define DEFAULT_CANVAS_HEIGHT 150
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
+namespace {
+
+class ToBlobRunnable : public nsRunnable
+{
+public:
+  ToBlobRunnable(nsIFileCallback* aCallback,
+                 nsIDOMBlob* aBlob)
+    : mCallback(aCallback),
+      mBlob(aBlob)
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  }
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+    mCallback->Receive(mBlob);
+    return NS_OK;
+  }
+private:
+  nsCOMPtr<nsIFileCallback> mCallback;
+  nsCOMPtr<nsIDOMBlob> mBlob;
+};
+
+} // anonymous namespace
+
 class nsHTMLCanvasPrintState : public nsIDOMMozCanvasPrintState
 {
 public:
   nsHTMLCanvasPrintState(nsHTMLCanvasElement* aCanvas,
                          nsICanvasRenderingContextInternal* aContext,
                          nsITimerCallback* aCallback)
     : mIsDone(false), mPendingNotify(false), mCanvas(aCanvas),
       mContext(aContext), mCallback(aCallback)
@@ -545,16 +571,62 @@ nsHTMLCanvasElement::ToDataURLImpl(const
   uint64_t count;
   rv = stream->Available(&count);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(count <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
   return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count, aDataURL.Length());
 }
 
+// XXXkhuey the encoding should be off the main thread, but we're lazy.
+NS_IMETHODIMP
+nsHTMLCanvasElement::ToBlob(nsIFileCallback* aCallback,
+                            const nsAString& aType,
+                            nsIVariant* aParams,
+                            uint8_t optional_argc)
+{
+  // do a trust check if this is a write-only canvas
+  if (mWriteOnly && !nsContentUtils::IsCallerTrustedForRead()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsAutoString type;
+  nsresult rv = nsContentUtils::ASCIIToLower(aType, type);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  bool fallbackToPNG = false;
+
+  nsCOMPtr<nsIInputStream> stream;
+  nsresult rv = ExtractData(type, EmptyString(), getter_AddRefs(stream),
+                            fallbackToPNG);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (fallbackToPNG) {
+    type.AssignLiteral("image/png");
+  }
+
+  uint64_t imgSize;
+  rv = stream->Available(&imgSize);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
+
+  void* imgData = nullptr;
+  rv = NS_ReadInputStreamToBuffer(stream, &imgData, imgSize);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // The DOMFile takes ownership of the buffer
+  nsRefPtr<nsDOMMemoryFile> blob =
+    new nsDOMMemoryFile(imgData, imgSize, type);
+
+  nsRefPtr<ToBlobRunnable> runnable = new ToBlobRunnable(aCallback, blob);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
 NS_IMETHODIMP
 nsHTMLCanvasElement::MozGetAsFile(const nsAString& aName,
                                   const nsAString& aType,
                                   uint8_t optional_argc,
                                   nsIDOMFile** aResult)
 {
   // do a trust check if this is a write-only canvas
   if ((mWriteOnly) &&
--- a/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl
@@ -15,16 +15,17 @@
  * <canvas> element.
  *
  * For more information on this interface, please see
  * http://www.whatwg.org/specs/web-apps/current-work/#graphics
  *
  * @status UNDER_DEVELOPMENT
  */
 
+interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIVariant;
 interface nsIInputStreamCallback;
 
 [scriptable, builtinclass, uuid(8d5fb8a0-7782-11e1-b0c4-0800200c9a67)]
 interface nsIDOMMozCanvasPrintState : nsISupports
 {
   // A canvas rendering context.
@@ -35,16 +36,21 @@ interface nsIDOMMozCanvasPrintState : ns
 };
 
 [scriptable, function, uuid(8d5fb8a0-7782-11e1-b0c4-0800200c9a66)]
 interface nsIPrintCallback : nsISupports
 {
   void render(in nsIDOMMozCanvasPrintState ctx);
 };
 
+[scriptable, function, uuid(6e9ffb59-2067-4aef-a51c-65e65a3e0d81)]
+interface nsIFileCallback : nsISupports {
+  void receive(in nsIDOMBlob file);
+};
+
 [scriptable, uuid(a7062fca-41c6-4520-b777-3bb30fd77273)]
 interface nsIDOMHTMLCanvasElement : nsIDOMHTMLElement
 {
   attribute unsigned long width;
   attribute unsigned long height;
   attribute boolean mozOpaque;
 
   nsISupports getContext(in DOMString contextId,
@@ -59,16 +65,20 @@ interface nsIDOMHTMLCanvasElement : nsID
                                       [optional] in nsIVariant params);
 
   // Valid calls are
   // mozGetAsFile(name);              -- defaults to image/png
   // mozGetAsFile(name, type);        -- uses given type
   [optional_argc] nsIDOMFile mozGetAsFile(in DOMString name,
                                           [optional] in DOMString type);
 
+  [optional_argc] void toBlob(in nsIFileCallback callback,
+                              [optional] in DOMString type,
+                              [optional] in nsIVariant params);
+
   // A Mozilla-only extension to get a canvas context backed by double-buffered
   // shared memory. Only privileged callers can call this.
   nsISupports MozGetIPCContext(in DOMString contextId);
 
   // A Mozilla-only extension that returns the canvas' image data as a data
   // stream in the desired image format.
   void mozFetchAsStream(in nsIInputStreamCallback callback,
                                         [optional] in DOMString type);