Bug 564388 - Make the 'quality' parameter to ToDataURL work for image/jpeg. Also fixes bug 401795 (handle excess arguments to ToDataURL by ignoring them). r=bz,roc
authorBrian O'Keefe <bokeefe@alum.wpi.edu>
Thu, 26 May 2011 09:28:26 +0200
changeset 70189 769b00d49ce763d96131011c3ac6de7341dc453d
parent 70188 e237b7b684ac267741bbbb9a7f55a3b3e22427e0
child 70190 ecbe9bb3433f54fc48fab8c408bf00796a6c7cd0
push id20223
push userdgottwald@mozilla.com
push dateThu, 26 May 2011 07:34:36 +0000
treeherdermozilla-central@a5b5280c08ed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, roc
bugs564388, 401795
milestone7.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 564388 - Make the 'quality' parameter to ToDataURL work for image/jpeg. Also fixes bug 401795 (handle excess arguments to ToDataURL by ignoring them). r=bz,roc
browser/app/profile/extensions/testpilot@labs.mozilla.com/content/experiment-page.js
browser/base/content/tabview/tabitems.js
content/canvas/test/Makefile.in
content/canvas/test/test_canvas.html
content/canvas/test/test_toDataURL_parameters.html
content/html/content/public/nsHTMLCanvasElement.h
content/html/content/src/nsHTMLCanvasElement.cpp
dom/interfaces/html/nsIDOMHTMLCanvasElement.idl
--- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/content/experiment-page.js
+++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/content/experiment-page.js
@@ -118,17 +118,17 @@ var stringBundle;
 	response == nsIFilePicker.returnReplace) {
       const nsIWebBrowserPersist = Components.interfaces.nsIWebBrowserPersist;
       let file = filePicker.file;
 
       // create a data url from the canvas and then create URIs of the source
       // and targets
       let io = Components.classes["@mozilla.org/network/io-service;1"].
 	getService(Components.interfaces.nsIIOService);
-      let source = io.newURI(canvas.toDataURL("image/png", ""), "UTF8", null);
+      let source = io.newURI(canvas.toDataURL("image/png"), "UTF8", null);
       let target = io.newFileURI(file);
 
       // prepare to save the canvas data
       let persist = Components.classes[
 	"@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].
 	  createInstance(nsIWebBrowserPersist);
       persist.persistFlags = nsIWebBrowserPersist.
 	PERSIST_FLAGS_REPLACE_EXISTING_FILES;
--- a/browser/base/content/tabview/tabitems.js
+++ b/browser/base/content/tabview/tabitems.js
@@ -1553,11 +1553,11 @@ TabCanvas.prototype = {
     let top = win.scrollY;
 
     return new Rect(left, top, width, height);
   },
 
   // ----------
   // Function: toImageData
   toImageData: function TabCanvas_toImageData() {
-    return this.canvas.toDataURL("image/png", "");
+    return this.canvas.toDataURL("image/png");
   }
 };
--- a/content/canvas/test/Makefile.in
+++ b/content/canvas/test/Makefile.in
@@ -75,16 +75,17 @@ include $(topsrcdir)/config/rules.mk
 	test_2d.composite.image.destination-atop.html \
 	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_toDataURL_lowercase_ascii.html \
+	test_toDataURL_parameters.html \
 	test_mozGetAsFile.html \
 	test_canvas_strokeStyle_getter.html \
 	test_bug613794.html \
 	test_drawImage_edge_cases.html \
 	$(NULL)
 
 ifneq (1_Linux,$(MOZ_SUITE)_$(OS_ARCH))
 # This test fails in Suite on Linux for some reason, disable it there
--- a/content/canvas/test/test_canvas.html
+++ b/content/canvas/test/test_canvas.html
@@ -19532,31 +19532,33 @@ function test_bug397524() {
 <canvas id="c614" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
 <script>
 function test_bug405982() {
 
 var canvas = document.getElementById('c614');
 var ctx = canvas.getContext('2d');
 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
+var _threw = false;
 try {
   var data = canvas.toDataURL('image/png', 'quality=100');
-  ok(false, "Should have thrown an exception for invalid args to png encoder");
 }
 catch (e) {
-  is(e.result, Components.results.NS_ERROR_INVALID_ARG, "Exception was wrong for png encoder");
-}
-
+  _threw = true;
+}
+ok(!_threw, "Should not throw an exception for invalid args to png encoder");
+
+_threw = false;
 try {
   var data = canvas.toDataURL('image/jpeg', 'foobar=true');
-  ok(false, "Should have thrown an exception for invalid args to jpeg encoder");
 }
 catch (e) {
-  is(e.result, Components.results.NS_ERROR_INVALID_ARG, "Exception was wrong for jpeg encoder");
-}
+  _threw = true;
+}
+ok(!_threw, "Should not throw an exception for invalid args to jpeg encoder");
 
 }
 </script>
 <!-- [[[ test_context.arguments.extra.html ]]] -->
 
 <p>Canvas test: context.arguments.extra</p>
 <canvas id="c615" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
 <script>
@@ -20756,17 +20758,17 @@ var _thrown_outer = false;
 try {
 
 var data = canvas.toDataURL('image/png', 'another argument that should not raise an exception');
 ok(/^data:image\/png[;,]/.test(data), "data =~ /^data:image\\/png[;,]/");
 
 } catch (e) {
     _thrown_outer = true;
 }
-todo(!_thrown_outer, 'should not throw exception');
+ok(!_thrown_outer, 'should not throw exception');
 
 
 }
 </script>
 
 <!-- [[[ test_toDataURL.arguments.2.html ]]] -->
 
 <p>Canvas test: toDataURL.arguments.2 - bug 401795</p>
@@ -20783,17 +20785,17 @@ var _thrown_outer = false;
 try {
 
 var data = canvas.toDataURL('image/png', 'another argument that should not raise an exception', 'and another');
 ok(/^data:image\/png[;,]/.test(data), "data =~ /^data:image\\/png[;,]/");
 
 } catch (e) {
     _thrown_outer = true;
 }
-todo(!_thrown_outer, 'should not throw exception');
+ok(!_thrown_outer, 'should not throw exception');
 
 
 }
 </script>
 
 <!-- [[[ test_toDataURL.arguments.3.html ]]] -->
 
 <p>Canvas test: toDataURL.arguments.3 - bug 401795</p>
@@ -20811,17 +20813,17 @@ try {
 
 // More arguments that should not raise exceptions
 var data = canvas.toDataURL('image/png', null, null, null);
 ok(/^data:image\/png[;,]/.test(data), "data =~ /^data:image\\/png[;,]/");
 
 } catch (e) {
     _thrown_outer = true;
 }
-todo(!_thrown_outer, 'should not throw exception');
+ok(!_thrown_outer, 'should not throw exception');
 
 
 }
 </script>
 
 <!-- [[[ test_toDataURL.complexcolours.html ]]] -->
 
 <p>Canvas test: toDataURL.complexcolours</p>
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/test_toDataURL_parameters.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<title>Canvas test: toDataURL parameters (Bug 564388)</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<p>
+This test covers the JPEG quality parameter. If (when) the HTML5 spec changes the
+allowed parameters for ToDataURL, new tests should go here.
+</p>
+<canvas id="c" width="100" height="100"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+var canvas = document.getElementById('c');
+var ctx = canvas.getContext("2d");
+
+ctx.strokeStyle = '#FF0000';
+ctx.fillStyle = '#00FF00';
+ctx.fillRect(0, 0, 100, 100);
+ctx.beginPath();
+ctx.moveTo(10, 10);
+ctx.lineTo(90, 90);
+ctx.stroke();
+
+var pngData = canvas.toDataURL('image/png');
+var pngQuality = canvas.toDataURL('image/png', 0.1);
+is(pngQuality, pngData, "Quality is not supported for PNG images");
+
+var data = canvas.toDataURL('image/jpeg');
+if (data.match(/^data:image\/jpeg[;,]/)) {
+    // Test the JPEG quality parameter
+    
+    var fullQuality = canvas.toDataURL('image/jpeg', 1.0);
+    var lowQuality = canvas.toDataURL('image/jpeg', 0.1);
+    isnot(lowQuality, fullQuality, "A low quality (0.1) should differ from high quality (1.0)");
+    
+    var medQuality = canvas.toDataURL('image/jpeg', 0.5);
+    isnot(medQuality, fullQuality, "A medium quality (0.5) should differ from high (1.0)");
+    isnot(medQuality, lowQuality, "A medium quality (0.5) should differ from low (0.5)");
+    
+    var tooHigh = canvas.toDataURL('image/jpeg', 2.0);
+    is(tooHigh, data, "Quality above 1.0 is treated as unspecified");
+    
+    var tooLow = canvas.toDataURL('image/jpeg', -1.0);
+    is(tooLow, data, "Quality below 0.0 is treated as unspecified");
+    
+    var lowQualityExtra = canvas.toDataURL('image/jpeg', 0.1, 'foo', 'bar', null);
+    is(lowQualityExtra, lowQuality, "Quality applies even if extra arguments are present");
+    
+    var lowQualityUppercase = canvas.toDataURL('IMAGE/JPEG', 0.1);
+    is(lowQualityUppercase, lowQuality, "Quality applies to image/jpeg regardless of case");
+    
+    var lowQualityString = canvas.toDataURL('image/jpeg', '0.1');
+    isnot(lowQualityString, lowQuality, "Quality must be a number (should not be a string)");
+}
+</script>
--- a/content/html/content/public/nsHTMLCanvasElement.h
+++ b/content/html/content/public/nsHTMLCanvasElement.h
@@ -176,17 +176,17 @@ protected:
 
   nsresult UpdateContext(nsIPropertyBag *aNewContextOptions = nsnull);
   nsresult ExtractData(const nsAString& aType,
                        const nsAString& aOptions,
                        char*& aData,
                        PRUint32& aSize,
                        bool& aFellBackToPNG);
   nsresult ToDataURLImpl(const nsAString& aMimeType,
-                         const nsAString& aEncoderOptions,
+                         nsIVariant* aEncoderOptions,
                          nsAString& aDataURL);
   nsresult MozGetAsFileImpl(const nsAString& aName,
                             const nsAString& aType,
                             nsIDOMFile** aResult);
   nsresult GetContextHelper(const nsAString& aContextId,
                             nsICanvasRenderingContextInternal **aContext);
 
   nsString mCurrentContextId;
--- a/content/html/content/src/nsHTMLCanvasElement.cpp
+++ b/content/html/content/src/nsHTMLCanvasElement.cpp
@@ -42,16 +42,17 @@
 #include "prmem.h"
 #include "nsDOMFile.h"
 #include "CheckedInt.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 #include "nsJSUtils.h"
+#include "nsMathUtils.h"
 
 #include "nsFrameManager.h"
 #include "nsDisplayList.h"
 #include "ImageLayers.h"
 #include "BasicLayers.h"
 #include "imgIEncoder.h"
 
 #include "nsIWritablePropertyBag2.h"
@@ -191,42 +192,27 @@ nsHTMLCanvasElement::ParseAttribute(PRIn
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 
 // nsHTMLCanvasElement::toDataURL
 
 NS_IMETHODIMP
-nsHTMLCanvasElement::ToDataURL(const nsAString& aType, const nsAString& aParams,
+nsHTMLCanvasElement::ToDataURL(const nsAString& aType, nsIVariant* aParams,
                                PRUint8 optional_argc, nsAString& aDataURL)
 {
   // do a trust check if this is a write-only canvas
-  // or if we're trying to use the 2-arg form
-  if ((mWriteOnly || optional_argc >= 2) &&
-      !nsContentUtils::IsCallerTrustedForRead()) {
+  if (mWriteOnly && !nsContentUtils::IsCallerTrustedForRead()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   return ToDataURLImpl(aType, aParams, aDataURL);
 }
 
-
-// nsHTMLCanvasElement::toDataURLAs
-//
-// Native-callers only
-
-NS_IMETHODIMP
-nsHTMLCanvasElement::ToDataURLAs(const nsAString& aMimeType,
-                                 const nsAString& aEncoderOptions,
-                                 nsAString& aDataURL)
-{
-  return ToDataURLImpl(aMimeType, aEncoderOptions, aDataURL);
-}
-
 nsresult
 nsHTMLCanvasElement::ExtractData(const nsAString& aType,
                                  const nsAString& aOptions,
                                  char*& aResult,
                                  PRUint32& aSize,
                                  bool& aFellBackToPNG)
 {
   // note that if we don't have a current context, the spec says we're
@@ -318,35 +304,54 @@ nsHTMLCanvasElement::ExtractData(const n
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 nsHTMLCanvasElement::ToDataURLImpl(const nsAString& aMimeType,
-                                   const nsAString& aEncoderOptions,
+                                   nsIVariant* aEncoderOptions,
                                    nsAString& aDataURL)
 {
   bool fallbackToPNG = false;
 
   nsIntSize size = GetWidthHeight();
   if (size.height == 0 || size.width == 0) {
     aDataURL = NS_LITERAL_STRING("data:,");
     return NS_OK;
   }
 
   nsAutoString type;
   nsContentUtils::ASCIIToLower(aMimeType, type);
 
+  nsAutoString params;
+
+  // Quality parameter is only valid for the image/jpeg MIME type
+  if (type.EqualsLiteral("image/jpeg")) {
+    PRUint16 vartype;
+
+    if (aEncoderOptions &&
+        NS_SUCCEEDED(aEncoderOptions->GetDataType(&vartype)) &&
+        vartype <= nsIDataType::VTYPE_DOUBLE) {
+
+      double quality;
+      // Quality must be between 0.0 and 1.0, inclusive
+      if (NS_SUCCEEDED(aEncoderOptions->GetAsDouble(&quality)) &&
+          quality >= 0.0 && quality <= 1.0) {
+        params.AppendLiteral("quality=");
+        params.AppendInt(NS_lround(quality * 100.0));
+      }
+    }
+  }
+
   PRUint32 imgSize = 0;
   char* imgData;
 
-  nsresult rv = ExtractData(type, aEncoderOptions, imgData,
-                            imgSize, fallbackToPNG);
+  nsresult rv = ExtractData(type, params, imgData, imgSize, fallbackToPNG);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // base 64, result will be NULL terminated
   char* encodedImg = PL_Base64Encode(imgData, imgSize, nsnull);
   PR_Free(imgData);
   if (!encodedImg) // not sure why this would fail
     return NS_ERROR_OUT_OF_MEMORY;
 
--- a/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl
@@ -48,43 +48,36 @@
  *
  * For more information on this interface, please see
  * http://www.whatwg.org/specs/web-apps/current-work/#graphics
  *
  * @status UNDER_DEVELOPMENT
  */
 
 interface nsIDOMFile;
+interface nsIVariant;
 
-[scriptable, uuid(2e98cd39-2269-493a-a3bb-abe85be2523c)]
+[scriptable, uuid(010d8e6f-86ba-47ad-a04f-1a4d75f1caf8)]
 interface nsIDOMHTMLCanvasElement : nsIDOMHTMLElement
 {
   attribute unsigned long width;
   attribute unsigned long height;
   attribute boolean mozOpaque;
 
   nsISupports getContext(in DOMString contextId,
                          [optional] in jsval contextOptions);
 
 
   // Valid calls are:
   //  toDataURL();              -- defaults to image/png
   //  toDataURL(type);          -- uses given type
-  //  toDataURL(type, params);  -- only available to trusted callers
+  //  toDataURL(type, params);  -- uses given type, and any valid parameters
   [optional_argc] DOMString toDataURL([optional] in DOMString type,
-                                      [optional] in DOMString params);
+                                      [optional] in nsIVariant params);
 
-  // This version lets you specify different image types and pass parameters
-  // to the encoder. For example toDataURLAs("image/png", "transparency=none")
-  // gives you a PNG with the alpha channel discarded. See the encoder for
-  // the options string that it supports. Separate multiple options with
-  // semicolons.
-  [noscript] DOMString toDataURLAs(in DOMString mimeType, in DOMString encoderOptions);
-
- 
   // 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);
 
   // A Mozilla-only extension to get a canvas context backed by double-buffered
   // shared memory. Only privileged callers can call this.