summaryrefslogtreecommitdiffstats
path: root/image/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'image/test/unit')
-rw-r--r--image/test/unit/async_load_tests.js214
-rw-r--r--image/test/unit/bug413512.icobin0 -> 17759 bytes
-rw-r--r--image/test/unit/bug815359.icobin0 -> 4286 bytes
-rw-r--r--image/test/unit/image1.pngbin0 -> 8415 bytes
-rw-r--r--image/test/unit/image1png16x16.jpgbin0 -> 1051 bytes
-rw-r--r--image/test/unit/image1png64x64.jpgbin0 -> 4503 bytes
-rw-r--r--image/test/unit/image2.jpgbin0 -> 3494 bytes
-rw-r--r--image/test/unit/image2jpg16x16-win.pngbin0 -> 948 bytes
-rw-r--r--image/test/unit/image2jpg16x16.pngbin0 -> 950 bytes
-rw-r--r--image/test/unit/image2jpg16x16cropped.jpgbin0 -> 879 bytes
-rw-r--r--image/test/unit/image2jpg16x16cropped2.jpgbin0 -> 878 bytes
-rw-r--r--image/test/unit/image2jpg16x32cropped3.jpgbin0 -> 1127 bytes
-rw-r--r--image/test/unit/image2jpg16x32scaled.jpgbin0 -> 1219 bytes
-rw-r--r--image/test/unit/image2jpg32x16cropped4.jpgbin0 -> 1135 bytes
-rw-r--r--image/test/unit/image2jpg32x16scaled.jpgbin0 -> 1227 bytes
-rw-r--r--image/test/unit/image2jpg32x32-win.pngbin0 -> 3104 bytes
-rw-r--r--image/test/unit/image2jpg32x32.jpgbin0 -> 1634 bytes
-rw-r--r--image/test/unit/image2jpg32x32.pngbin0 -> 3105 bytes
-rw-r--r--image/test/unit/image3.icobin0 -> 1406 bytes
-rw-r--r--image/test/unit/image3ico16x16.pngbin0 -> 330 bytes
-rw-r--r--image/test/unit/image3ico32x32.pngbin0 -> 2285 bytes
-rw-r--r--image/test/unit/image4.gifbin0 -> 1809 bytes
-rw-r--r--image/test/unit/image4gif16x16bmp24bpp.icobin0 -> 894 bytes
-rw-r--r--image/test/unit/image4gif16x16bmp32bpp.icobin0 -> 1150 bytes
-rw-r--r--image/test/unit/image4gif32x32bmp24bpp.icobin0 -> 3262 bytes
-rw-r--r--image/test/unit/image4gif32x32bmp32bpp.icobin0 -> 4286 bytes
-rw-r--r--image/test/unit/image_load_helpers.js122
-rw-r--r--image/test/unit/test_async_notification.js10
-rw-r--r--image/test/unit/test_async_notification_404.js16
-rw-r--r--image/test/unit/test_async_notification_animated.js14
-rw-r--r--image/test/unit/test_encoder_apng.js470
-rw-r--r--image/test/unit/test_encoder_png.js256
-rw-r--r--image/test/unit/test_imgtools.js737
-rw-r--r--image/test/unit/test_moz_icon_uri.js157
-rw-r--r--image/test/unit/test_private_channel.js143
-rw-r--r--image/test/unit/xpcshell.ini45
36 files changed, 2184 insertions, 0 deletions
diff --git a/image/test/unit/async_load_tests.js b/image/test/unit/async_load_tests.js
new file mode 100644
index 000000000..fafb26f75
--- /dev/null
+++ b/image/test/unit/async_load_tests.js
@@ -0,0 +1,214 @@
+/*
+ * Test to ensure that image loading/decoding notifications are always
+ * delivered async, and in the order we expect.
+ *
+ * Must be included from a file that has a uri of the image to test defined in
+ * var uri.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var server = new HttpServer();
+server.registerDirectory("/", do_get_file(''));
+server.registerContentType("sjs", "sjs");
+server.start(-1);
+
+
+load('image_load_helpers.js');
+
+var requests = [];
+
+// Return a closure that holds on to the listener from the original
+// imgIRequest, and compares its results to the cloned one.
+function getCloneStopCallback(original_listener)
+{
+ return function cloneStop(listener) {
+ do_check_eq(original_listener.state, listener.state);
+
+ // Sanity check to make sure we didn't accidentally use the same listener
+ // twice.
+ do_check_neq(original_listener, listener);
+ do_test_finished();
+ }
+}
+
+// Make sure that cloned requests get all the same callbacks as the original,
+// but they aren't synchronous right now.
+function checkClone(other_listener, aRequest)
+{
+ do_test_pending();
+
+ // For as long as clone notification is synchronous, we can't test the clone state reliably.
+ var listener = new ImageListener(null, function(foo, bar) { do_test_finished(); } /*getCloneStopCallback(other_listener)*/);
+ listener.synchronous = false;
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener);
+ var clone = aRequest.clone(outer);
+ requests.push(clone);
+}
+
+// Ensure that all the callbacks were called on aRequest.
+function checkSizeAndLoad(listener, aRequest)
+{
+ do_check_neq(listener.state & SIZE_AVAILABLE, 0);
+ do_check_neq(listener.state & LOAD_COMPLETE, 0);
+
+ do_test_finished();
+}
+
+function secondLoadDone(oldlistener, aRequest)
+{
+ do_test_pending();
+
+ try {
+ var staticrequest = aRequest.getStaticRequest();
+
+ // For as long as clone notification is synchronous, we can't test the
+ // clone state reliably.
+ var listener = new ImageListener(null, checkSizeAndLoad);
+ listener.synchronous = false;
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener);
+ var staticrequestclone = staticrequest.clone(outer);
+ requests.push(staticrequestclone);
+ } catch(e) {
+ // We can't create a static request. Most likely the request we started
+ // with didn't load successfully.
+ do_test_finished();
+ }
+
+ run_loadImageWithChannel_tests();
+
+ do_test_finished();
+}
+
+// Load the request a second time. This should come from the image cache, and
+// therefore would be at most risk of being served synchronously.
+function checkSecondLoad()
+{
+ do_test_pending();
+
+ var listener = new ImageListener(checkClone, secondLoadDone);
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener);
+ requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null));
+ listener.synchronous = false;
+}
+
+function firstLoadDone(oldlistener, aRequest)
+{
+ checkSecondLoad(uri);
+
+ do_test_finished();
+}
+
+// Return a closure that allows us to check the stream listener's status when the
+// image finishes loading.
+function getChannelLoadImageStopCallback(streamlistener, next)
+{
+ return function channelLoadStop(imglistener, aRequest) {
+
+ next();
+
+ do_test_finished();
+ }
+}
+
+// Load the request a second time. This should come from the image cache, and
+// therefore would be at most risk of being served synchronously.
+function checkSecondChannelLoad()
+{
+ do_test_pending();
+ var channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+ var channellistener = new ChannelListener();
+ channel.asyncOpen2(channellistener);
+
+ var listener = new ImageListener(null,
+ getChannelLoadImageStopCallback(channellistener,
+ all_done_callback));
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener);
+ var outlistener = {};
+ requests.push(gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener));
+ channellistener.outputListener = outlistener.value;
+
+ listener.synchronous = false;
+}
+
+function run_loadImageWithChannel_tests()
+{
+ // To ensure we're testing what we expect to, create a new loader and cache.
+ gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
+
+ do_test_pending();
+ var channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+ var channellistener = new ChannelListener();
+ channel.asyncOpen2(channellistener);
+
+ var listener = new ImageListener(null,
+ getChannelLoadImageStopCallback(channellistener,
+ checkSecondChannelLoad));
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener);
+ var outlistener = {};
+ requests.push(gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener));
+ channellistener.outputListener = outlistener.value;
+
+ listener.synchronous = false;
+}
+
+function all_done_callback()
+{
+ server.stop(function() { do_test_finished(); });
+}
+
+function startImageCallback(otherCb)
+{
+ return function(listener, request)
+ {
+ // Make sure we can load the same image immediately out of the cache.
+ do_test_pending();
+ var listener2 = new ImageListener(null, function(foo, bar) { do_test_finished(); });
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener2);
+ requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null));
+ listener2.synchronous = false;
+
+ // Now that we've started another load, chain to the callback.
+ otherCb(listener, request);
+ }
+}
+
+var gCurrentLoader;
+
+function cleanup()
+{
+ for (var i = 0; i < requests.length; ++i) {
+ requests[i].cancelAndForgetObserver(0);
+ }
+}
+
+function run_test()
+{
+ do_register_cleanup(cleanup);
+
+ gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
+
+ do_test_pending();
+ var listener = new ImageListener(startImageCallback(checkClone), firstLoadDone);
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener);
+ var req = gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null);
+ requests.push(req);
+
+ // Ensure that we don't cause any mayhem when we lock an image.
+ req.lockImage();
+
+ listener.synchronous = false;
+}
diff --git a/image/test/unit/bug413512.ico b/image/test/unit/bug413512.ico
new file mode 100644
index 000000000..b2db0429f
--- /dev/null
+++ b/image/test/unit/bug413512.ico
Binary files differ
diff --git a/image/test/unit/bug815359.ico b/image/test/unit/bug815359.ico
new file mode 100644
index 000000000..a24b8fb6b
--- /dev/null
+++ b/image/test/unit/bug815359.ico
Binary files differ
diff --git a/image/test/unit/image1.png b/image/test/unit/image1.png
new file mode 100644
index 000000000..2fb37aeec
--- /dev/null
+++ b/image/test/unit/image1.png
Binary files differ
diff --git a/image/test/unit/image1png16x16.jpg b/image/test/unit/image1png16x16.jpg
new file mode 100644
index 000000000..ea14dbede
--- /dev/null
+++ b/image/test/unit/image1png16x16.jpg
Binary files differ
diff --git a/image/test/unit/image1png64x64.jpg b/image/test/unit/image1png64x64.jpg
new file mode 100644
index 000000000..11c34f6c6
--- /dev/null
+++ b/image/test/unit/image1png64x64.jpg
Binary files differ
diff --git a/image/test/unit/image2.jpg b/image/test/unit/image2.jpg
new file mode 100644
index 000000000..b2131bf0c
--- /dev/null
+++ b/image/test/unit/image2.jpg
Binary files differ
diff --git a/image/test/unit/image2jpg16x16-win.png b/image/test/unit/image2jpg16x16-win.png
new file mode 100644
index 000000000..a821626c0
--- /dev/null
+++ b/image/test/unit/image2jpg16x16-win.png
Binary files differ
diff --git a/image/test/unit/image2jpg16x16.png b/image/test/unit/image2jpg16x16.png
new file mode 100644
index 000000000..5722223c2
--- /dev/null
+++ b/image/test/unit/image2jpg16x16.png
Binary files differ
diff --git a/image/test/unit/image2jpg16x16cropped.jpg b/image/test/unit/image2jpg16x16cropped.jpg
new file mode 100644
index 000000000..fca22cb30
--- /dev/null
+++ b/image/test/unit/image2jpg16x16cropped.jpg
Binary files differ
diff --git a/image/test/unit/image2jpg16x16cropped2.jpg b/image/test/unit/image2jpg16x16cropped2.jpg
new file mode 100644
index 000000000..e51d3530d
--- /dev/null
+++ b/image/test/unit/image2jpg16x16cropped2.jpg
Binary files differ
diff --git a/image/test/unit/image2jpg16x32cropped3.jpg b/image/test/unit/image2jpg16x32cropped3.jpg
new file mode 100644
index 000000000..13a3d26e5
--- /dev/null
+++ b/image/test/unit/image2jpg16x32cropped3.jpg
Binary files differ
diff --git a/image/test/unit/image2jpg16x32scaled.jpg b/image/test/unit/image2jpg16x32scaled.jpg
new file mode 100644
index 000000000..6abef0f99
--- /dev/null
+++ b/image/test/unit/image2jpg16x32scaled.jpg
Binary files differ
diff --git a/image/test/unit/image2jpg32x16cropped4.jpg b/image/test/unit/image2jpg32x16cropped4.jpg
new file mode 100644
index 000000000..46f34918c
--- /dev/null
+++ b/image/test/unit/image2jpg32x16cropped4.jpg
Binary files differ
diff --git a/image/test/unit/image2jpg32x16scaled.jpg b/image/test/unit/image2jpg32x16scaled.jpg
new file mode 100644
index 000000000..e302fbafd
--- /dev/null
+++ b/image/test/unit/image2jpg32x16scaled.jpg
Binary files differ
diff --git a/image/test/unit/image2jpg32x32-win.png b/image/test/unit/image2jpg32x32-win.png
new file mode 100644
index 000000000..4d84df26a
--- /dev/null
+++ b/image/test/unit/image2jpg32x32-win.png
Binary files differ
diff --git a/image/test/unit/image2jpg32x32.jpg b/image/test/unit/image2jpg32x32.jpg
new file mode 100644
index 000000000..cf9a10a37
--- /dev/null
+++ b/image/test/unit/image2jpg32x32.jpg
Binary files differ
diff --git a/image/test/unit/image2jpg32x32.png b/image/test/unit/image2jpg32x32.png
new file mode 100644
index 000000000..723008771
--- /dev/null
+++ b/image/test/unit/image2jpg32x32.png
Binary files differ
diff --git a/image/test/unit/image3.ico b/image/test/unit/image3.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/image/test/unit/image3.ico
Binary files differ
diff --git a/image/test/unit/image3ico16x16.png b/image/test/unit/image3ico16x16.png
new file mode 100644
index 000000000..e9e520cb6
--- /dev/null
+++ b/image/test/unit/image3ico16x16.png
Binary files differ
diff --git a/image/test/unit/image3ico32x32.png b/image/test/unit/image3ico32x32.png
new file mode 100644
index 000000000..58497e3fa
--- /dev/null
+++ b/image/test/unit/image3ico32x32.png
Binary files differ
diff --git a/image/test/unit/image4.gif b/image/test/unit/image4.gif
new file mode 100644
index 000000000..b1530bc81
--- /dev/null
+++ b/image/test/unit/image4.gif
Binary files differ
diff --git a/image/test/unit/image4gif16x16bmp24bpp.ico b/image/test/unit/image4gif16x16bmp24bpp.ico
new file mode 100644
index 000000000..890c81c27
--- /dev/null
+++ b/image/test/unit/image4gif16x16bmp24bpp.ico
Binary files differ
diff --git a/image/test/unit/image4gif16x16bmp32bpp.ico b/image/test/unit/image4gif16x16bmp32bpp.ico
new file mode 100644
index 000000000..f8a9eb8ad
--- /dev/null
+++ b/image/test/unit/image4gif16x16bmp32bpp.ico
Binary files differ
diff --git a/image/test/unit/image4gif32x32bmp24bpp.ico b/image/test/unit/image4gif32x32bmp24bpp.ico
new file mode 100644
index 000000000..28092818d
--- /dev/null
+++ b/image/test/unit/image4gif32x32bmp24bpp.ico
Binary files differ
diff --git a/image/test/unit/image4gif32x32bmp32bpp.ico b/image/test/unit/image4gif32x32bmp32bpp.ico
new file mode 100644
index 000000000..0e2d28c82
--- /dev/null
+++ b/image/test/unit/image4gif32x32bmp32bpp.ico
Binary files differ
diff --git a/image/test/unit/image_load_helpers.js b/image/test/unit/image_load_helpers.js
new file mode 100644
index 000000000..e8d9a29f8
--- /dev/null
+++ b/image/test/unit/image_load_helpers.js
@@ -0,0 +1,122 @@
+/*
+ * Helper structures to track callbacks from image and channel loads.
+ */
+
+// START_REQUEST and STOP_REQUEST are used by ChannelListener, and
+// stored in ChannelListener.requestStatus.
+const START_REQUEST = 0x01;
+const STOP_REQUEST = 0x02;
+const DATA_AVAILABLE = 0x04;
+
+// One bit per callback that imageListener below implements. Stored in
+// ImageListener.state.
+const SIZE_AVAILABLE = 0x01;
+const FRAME_UPDATE = 0x02;
+const FRAME_COMPLETE = 0x04;
+const LOAD_COMPLETE = 0x08;
+const DECODE_COMPLETE = 0x10;
+
+// Safebrowsing requires that the profile dir is set.
+do_get_profile();
+
+// An implementation of imgIScriptedNotificationObserver with the ability to
+// call specified functions on onStartRequest and onStopRequest.
+function ImageListener(start_callback, stop_callback)
+{
+ this.sizeAvailable = function onSizeAvailable(aRequest)
+ {
+ do_check_false(this.synchronous);
+
+ this.state |= SIZE_AVAILABLE;
+
+ if (this.start_callback)
+ this.start_callback(this, aRequest);
+ }
+ this.frameComplete = function onFrameComplete(aRequest)
+ {
+ do_check_false(this.synchronous);
+
+ this.state |= FRAME_COMPLETE;
+ }
+ this.decodeComplete = function onDecodeComplete(aRequest)
+ {
+ do_check_false(this.synchronous);
+
+ this.state |= DECODE_COMPLETE;
+ }
+ this.loadComplete = function onLoadcomplete(aRequest)
+ {
+ do_check_false(this.synchronous);
+
+ this.state |= LOAD_COMPLETE;
+
+ if (this.stop_callback)
+ this.stop_callback(this, aRequest);
+ }
+ this.frameUpdate = function onFrameUpdate(aRequest)
+ {
+ }
+ this.isAnimated = function onIsAnimated()
+ {
+ }
+
+ // Initialize the synchronous flag to true to start. This must be set to
+ // false before exiting to the event loop!
+ this.synchronous = true;
+
+ // A function to call when onStartRequest is called.
+ this.start_callback = start_callback;
+
+ // A function to call when onStopRequest is called.
+ this.stop_callback = stop_callback;
+
+ // The image load/decode state.
+ // A bitfield that tracks which callbacks have been called. Takes the bits
+ // defined above.
+ this.state = 0;
+}
+
+function NS_FAILED(val)
+{
+ return !!(val & 0x80000000);
+}
+
+function ChannelListener()
+{
+ this.onStartRequest = function onStartRequest(aRequest, aContext)
+ {
+ if (this.outputListener)
+ this.outputListener.onStartRequest(aRequest, aContext);
+
+ this.requestStatus |= START_REQUEST;
+ }
+
+ this.onDataAvailable = function onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount)
+ {
+ if (this.outputListener)
+ this.outputListener.onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
+
+ this.requestStatus |= DATA_AVAILABLE;
+ }
+
+ this.onStopRequest = function onStopRequest(aRequest, aContext, aStatusCode)
+ {
+ if (this.outputListener)
+ this.outputListener.onStopRequest(aRequest, aContext, aStatusCode);
+
+ // If we failed (or were canceled - failure is implied if canceled),
+ // there's no use tracking our state, since it's meaningless.
+ if (NS_FAILED(aStatusCode))
+ this.requestStatus = 0;
+ else
+ this.requestStatus |= STOP_REQUEST;
+ }
+
+ // A listener to pass the notifications we get to.
+ this.outputListener = null;
+
+ // The request's status. A bitfield that holds one or both of START_REQUEST
+ // and STOP_REQUEST, according to which callbacks have been called on the
+ // associated request.
+ this.requestStatus = 0;
+}
diff --git a/image/test/unit/test_async_notification.js b/image/test/unit/test_async_notification.js
new file mode 100644
index 000000000..63d04b99b
--- /dev/null
+++ b/image/test/unit/test_async_notification.js
@@ -0,0 +1,10 @@
+/*
+ * Test for asynchronous image load/decode notifications in the case that the image load works.
+ */
+
+// A simple 3x3 png; rows go red, green, blue. Stolen from the PNG encoder test.
+var pngspec = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
+var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
+var uri = ioService.newURI(pngspec, null, null);
+
+load('async_load_tests.js');
diff --git a/image/test/unit/test_async_notification_404.js b/image/test/unit/test_async_notification_404.js
new file mode 100644
index 000000000..4949d282d
--- /dev/null
+++ b/image/test/unit/test_async_notification_404.js
@@ -0,0 +1,16 @@
+/*
+ * Test to ensure that load/decode notifications are delivered completely and
+ * asynchronously when dealing with a file that's a 404.
+ */
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+XPCOMUtils.defineLazyGetter(this, "uri", function() {
+ return ioService.newURI("http://localhost:" +
+ server.identity.primaryPort +
+ "/async-notification-never-here.jpg", null, null);
+});
+
+load('async_load_tests.js');
diff --git a/image/test/unit/test_async_notification_animated.js b/image/test/unit/test_async_notification_animated.js
new file mode 100644
index 000000000..bfe6e2d0d
--- /dev/null
+++ b/image/test/unit/test_async_notification_animated.js
@@ -0,0 +1,14 @@
+/*
+ * Test for asynchronous image load/decode notifications in the case that the
+ * image load works, but for an animated image.
+ *
+ * If this fails because a request wasn't cancelled, it's possible that
+ * imgContainer::ExtractFrame didn't set the new image's status correctly.
+ */
+
+// transparent-animation.gif from the gif reftests.
+var spec = "data:image/gif;base64,R0lGODlhZABkAIABAP8AAP///yH5BAkBAAEALAAAAABLAGQAAAK8jI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8YhMKpchgPMJjUqnVOipis1ir9qul+sNV8HistVkTj/JajG7/UXDy+95tm4fy/NdPF/q93dWIqgVWAhwWKgoyPjnyAeZJ2lHOWcJh9mmqcaZ5mkGSreHOCXqRloadRrGGkeoapoa6+TaN0tra4gbq3vHq+q7BVwqrMeEnKy8zNzs/AwdLT1NXW19jZ1tUgAAIfkECQEAAQAsAAAAADQAZAAAArCMj6nL7Q+jnLTai7PevPsPhuJIluaJpurKtu4Lx/JM1/aN5/rO9/7vAAiHxKLxiCRCkswmc+mMSqHSapJqzSof2u4Q67WCw1MuOTs+N9Pqq7kdZcON8vk2aF+/88g6358HaCc4Rwhn2IaopnjGSOYYBukl2UWpZYm2x0enuXnX4NnXGQqAKTYaalqlWoZH+snwWsQah+pJ64Sr5ypbCvQLHCw8TFxsfIycrLzM3PxQAAAh+QQJAQABACwAAAAAGwBkAAACUIyPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gTE8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvdrfYnH5LL5jE6r16sCADs=";
+var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
+var uri = ioService.newURI(spec, null, null);
+
+load('async_load_tests.js');
diff --git a/image/test/unit/test_encoder_apng.js b/image/test/unit/test_encoder_apng.js
new file mode 100644
index 000000000..e7b90c28b
--- /dev/null
+++ b/image/test/unit/test_encoder_apng.js
@@ -0,0 +1,470 @@
+/*
+ * Test for APNG encoding in ImageLib
+ *
+ */
+
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+ // dispose=[none|background|previous]
+ // blend=[source|over]
+
+var apng1A = {
+ // A 3x3 image with 3 frames, alternating red, green, blue. RGB format.
+ width : 3, height : 3, skipFirstFrame : false,
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGB,
+ transparency : null,
+ plays : 0,
+
+ frames : [
+ { // frame #1
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
+ transparency : null,
+
+ pixels : [
+ 255,0,0, 255,0,0, 255,0,0,
+ 255,0,0, 255,0,0, 255,0,0,
+ 255,0,0, 255,0,0, 255,0,0
+ ]
+ },
+
+ { // frame #2
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
+ transparency : null,
+
+ pixels : [
+ 0,255,0, 0,255,0, 0,255,0,
+ 0,255,0, 0,255,0, 0,255,0,
+ 0,255,0, 0,255,0, 0,255,0
+ ]
+ },
+
+ { // frame #3
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
+ transparency : null,
+
+ pixels : [
+ 0,0,255, 0,0,255, 0,0,255,
+ 0,0,255, 0,0,255, 0,0,255,
+ 0,0,255, 0,0,255, 0,0,255
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABBJREFUCJlj+M/AAEEMWFgAj44I+H2CySsAAAAaZmNUTAAAAAEAAAADAAAAAwAAAAAAAAAAAfQD6AAA7mXKzAAAABNmZEFUAAAAAgiZY2D4zwBFWFgAhpcI+I731VcAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABNmZEFUAAAABAiZY2Bg+A9DmCwAfaAI+AGmQVoAAAAASUVORK5CYII="
+};
+
+
+var apng1B = {
+ // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
+ width : 3, height : 3, skipFirstFrame : false,
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
+ transparency : null,
+ plays : 0,
+
+ frames : [
+ { // frame #1
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 255,0,0,255, 255,0,0,255, 255,0,0,255,
+ 255,0,0,255, 255,0,0,255, 255,0,0,255,
+ 255,0,0,255, 255,0,0,255, 255,0,0,255
+ ]
+ },
+
+ { // frame #2
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,255,0,255, 0,255,0,255, 0,255,0,255,
+ 0,255,0,255, 0,255,0,255, 0,255,0,255,
+ 0,255,0,255, 0,255,0,255, 0,255,0,255
+ ]
+ },
+
+ { // frame #3
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,0,255,255, 0,0,255,255, 0,0,255,255,
+ 0,0,255,255, 0,0,255,255, 0,0,255,255,
+ 0,0,255,255, 0,0,255,255, 0,0,255,255
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAO5lyswAAAASZmRBVAAAAAIImWNg+I8EcXIAVOAR77Vyl9QAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABRmZEFUAAAABAiZY2Bg+P8fgXFxAEvpEe8rClxSAAAAAElFTkSuQmCC"
+};
+
+
+var apng1C = {
+ // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
+ // The first frame is skipped, so it will only flash green/blue (or static red in an APNG-unaware viewer)
+ width : 3, height : 3, skipFirstFrame : true,
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
+ transparency : null,
+ plays : 0,
+
+ frames : [
+ { // frame #1
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 255,0,0,255, 255,0,0,255, 255,0,0,255,
+ 255,0,0,255, 255,0,0,255, 255,0,0,255,
+ 255,0,0,255, 255,0,0,255, 255,0,0,255
+ ]
+ },
+
+ { // frame #2
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,255,0,255, 0,255,0,255, 0,255,0,255,
+ 0,255,0,255, 0,255,0,255, 0,255,0,255,
+ 0,255,0,255, 0,255,0,255, 0,255,0,255
+ ]
+ },
+
+ { // frame #3
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,0,255,255, 0,0,255,255, 0,0,255,255,
+ 0,0,255,255, 0,0,255,255, 0,0,255,255,
+ 0,0,255,255, 0,0,255,255, 0,0,255,255
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAACAAAAAPONk3AAAAARSURBVAiZY/jPwPAfhhlwcgBd1xHvwqIidQAAABpmY1RMAAAAAAAAAAMAAAADAAAAAAAAAAAB9APoAAB1FiAYAAAAEmZkQVQAAAABCJljYPiPBHFyAFTgEe+kD/2tAAAAGmZjVEwAAAACAAAAAwAAAAMAAAAAAAAAAAH0A+gAAJiA8/EAAAAUZmRBVAAAAAMImWNgYPj/H4FxcQBL6RHvC5ggGQAAAABJRU5ErkJggg=="
+};
+
+
+var apng2A = {
+ // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
+ // blend = over mode
+ // (The green frame is a horizontal gradient, and the blue frame is a
+ // vertical gradient. They stack as they animate.)
+ width : 3, height : 3, skipFirstFrame : false,
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
+ transparency : null,
+ plays : 0,
+
+ frames : [
+ { // frame #1
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 255,0,0,255, 255,0,0,255, 255,0,0,255,
+ 255,0,0,255, 255,0,0,255, 255,0,0,255,
+ 255,0,0,255, 255,0,0,255, 255,0,0,255
+ ]
+ },
+
+ { // frame #2
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "over", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,255,0,255, 0,255,0,180, 0,255,0,75,
+ 0,255,0,255, 0,255,0,180, 0,255,0,75,
+ 0,255,0,255, 0,255,0,180, 0,255,0,75
+ ]
+ },
+
+ { // frame #3
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "over", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,0,255,75, 0,0,255,75, 0,0,255,75,
+ 0,0,255,180, 0,0,255,180, 0,0,255,180,
+ 0,0,255,255, 0,0,255,255, 0,0,255,255
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAZli+loAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gAAXT0KbMAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC"
+};
+
+
+var apng2B = {
+ // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
+ // blend = over, dispose = background
+ // (The green frame is a horizontal gradient, and the blue frame is a
+ // vertical gradient. Each frame is displayed individually, blended to
+ // whatever the background is.)
+ width : 3, height : 3, skipFirstFrame : false,
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
+ transparency : null,
+ plays : 0,
+
+ frames : [
+ { // frame #1
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "background", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 255,0,0,255, 255,0,0,255, 255,0,0,255,
+ 255,0,0,255, 255,0,0,255, 255,0,0,255,
+ 255,0,0,255, 255,0,0,255, 255,0,0,255
+ ]
+ },
+
+ { // frame #2
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "background", blend : "over", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,255,0,255, 0,255,0,180, 0,255,0,75,
+ 0,255,0,255, 0,255,0,180, 0,255,0,75,
+ 0,255,0,255, 0,255,0,180, 0,255,0,75
+ ]
+ },
+
+ { // frame #3
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "background", blend : "over", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,0,255,75, 0,0,255,75, 0,0,255,75,
+ 0,0,255,180, 0,0,255,180, 0,0,255,180,
+ 0,0,255,255, 0,0,255,255, 0,0,255,255
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AEAbA0RWQAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gBAYB5yxsAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gBAW3vGPIAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC"
+};
+
+
+var apng3 = {
+ // A 3x3 image with 4 frames. First frame is white, then 1x1 frames draw a diagonal line
+ width : 3, height : 3, skipFirstFrame : false,
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
+ transparency : null,
+ plays : 0,
+
+ frames : [
+ { // frame #1
+ width : 3, height : 3,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+
+ 255,255,255,255, 255,255,255,255, 255,255,255,255,
+ 255,255,255,255, 255,255,255,255, 255,255,255,255,
+ 255,255,255,255, 255,255,255,255, 255,255,255,255
+ ]
+ },
+
+ { // frame #2
+ width : 1, height : 1,
+ x_offset : 0, y_offset : 0,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,0,0,255
+ ]
+ },
+
+ { // frame #3
+ width : 1, height : 1,
+ x_offset : 1, y_offset : 1,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,0,0,255
+ ]
+ },
+
+ { // frame #4
+ width : 1, height : 1,
+ x_offset : 2, y_offset : 2,
+ dispose : "none", blend : "source", delay : 500,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 0,0,0,255
+ ]
+ }
+ ],
+
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAAEAAAAAHzNZtAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAAA5JREFUCJlj+I8EGHByALuHI91kueRqAAAAGmZjVEwAAAABAAAAAQAAAAEAAAAAAAAAAAH0A+gAADJXfawAAAARZmRBVAAAAAIImWNgYGD4DwABBAEAbr5mpgAAABpmY1RMAAAAAwAAAAEAAAABAAAAAQAAAAEB9APoAAC4OHoxAAAAEWZkQVQAAAAECJljYGBg+A8AAQQBAJZ8LRAAAAAaZmNUTAAAAAUAAAABAAAAAQAAAAIAAAACAfQD6AAA/fh01wAAABFmZEFUAAAABgiZY2BgYPgPAAEEAQB3Eum9AAAAAElFTkSuQmCC"
+};
+
+// Main test entry point.
+function run_test() {
+ dump("Checking apng1A...\n");
+ run_test_for(apng1A);
+ dump("Checking apng1B...\n");
+ run_test_for(apng1B);
+ dump("Checking apng1C...\n");
+ run_test_for(apng1C);
+
+ dump("Checking apng2A...\n");
+ run_test_for(apng2A);
+ dump("Checking apng2B...\n");
+ run_test_for(apng2B);
+
+ dump("Checking apng3...\n");
+ run_test_for(apng3);
+};
+
+
+function run_test_for(input) {
+ var encoder, dataURL;
+
+ encoder = encodeImage(input);
+ dataURL = makeDataURL(encoder, "image/png");
+ do_check_eq(dataURL, input.expected);
+};
+
+
+function encodeImage(input) {
+ var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance();
+ encoder.QueryInterface(Ci.imgIEncoder);
+
+ var options = "";
+ if (input.transparency) { options += "transparency=" + input.transparency; }
+ options += ";frames=" + input.frames.length;
+ options += ";skipfirstframe=" + (input.skipFirstFrame ? "yes" : "no");
+ options += ";plays=" + input.plays;
+ encoder.startImageEncode(input.width, input.height, input.format, options);
+
+ for (var i = 0; i < input.frames.length; i++) {
+ var frame = input.frames[i];
+
+ options = "";
+ if (frame.transparency) { options += "transparency=" + input.transparency; }
+ options += ";delay=" + frame.delay;
+ options += ";dispose=" + frame.dispose;
+ options += ";blend=" + frame.blend;
+ if (frame.x_offset > 0) { options += ";xoffset=" + frame.x_offset; }
+ if (frame.y_offset > 0) { options += ";yoffset=" + frame.y_offset; }
+
+ encoder.addImageFrame(frame.pixels, frame.pixels.length,
+ frame.width, frame.height, frame.stride, frame.format, options);
+ }
+
+ encoder.endImageEncode();
+
+ return encoder;
+}
+
+
+function makeDataURL(encoder, mimetype) {
+ var rawStream = encoder.QueryInterface(Ci.nsIInputStream);
+
+ var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance();
+ stream.QueryInterface(Ci.nsIBinaryInputStream);
+
+ stream.setInputStream(rawStream);
+
+ var bytes = stream.readByteArray(stream.available()); // returns int[]
+
+ var base64String = toBase64(bytes);
+
+ return "data:" + mimetype + ";base64," + base64String;
+}
+
+/* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */
+
+/* Convert data (an array of integers) to a Base64 string. */
+const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
+ '0123456789+/';
+const base64Pad = '=';
+function toBase64(data) {
+ var result = '';
+ var length = data.length;
+ var i;
+ // Convert every three bytes to 4 ascii characters.
+ for (i = 0; i < (length - 2); i += 3) {
+ result += toBase64Table[data[i] >> 2];
+ result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
+ result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
+ result += toBase64Table[data[i+2] & 0x3f];
+ }
+
+ // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+ if (length%3) {
+ i = length - (length%3);
+ result += toBase64Table[data[i] >> 2];
+ if ((length%3) == 2) {
+ result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
+ result += toBase64Table[(data[i+1] & 0x0f) << 2];
+ result += base64Pad;
+ } else {
+ result += toBase64Table[(data[i] & 0x03) << 4];
+ result += base64Pad + base64Pad;
+ }
+ }
+
+ return result;
+}
diff --git a/image/test/unit/test_encoder_png.js b/image/test/unit/test_encoder_png.js
new file mode 100644
index 000000000..67beb840f
--- /dev/null
+++ b/image/test/unit/test_encoder_png.js
@@ -0,0 +1,256 @@
+/*
+ * Test for PNG encoding in ImageLib
+ *
+ */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+var png1A = {
+ // A 3x3 image, rows are red, green, blue.
+ // RGB format, transparency defaults.
+
+ transparency : null,
+
+ frames : [
+ {
+ width : 3, height : 3,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
+
+ pixels : [
+ 255,0,0, 255,0,0, 255,0,0,
+ 0,255,0, 0,255,0, 0,255,0,
+ 0,0,255, 0,0,255, 0,0,255,
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII="
+};
+
+var png1B = {
+ // A 3x3 image, rows are red, green, blue.
+ // RGB format, transparency=none.
+
+ transparency : "none",
+
+ frames : [
+ {
+ width : 3, height : 3,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
+
+ pixels : [
+ 255,0,0, 255,0,0, 255,0,0,
+ 0,255,0, 0,255,0, 0,255,0,
+ 0,0,255, 0,0,255, 0,0,255,
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII="
+};
+
+var png2A = {
+ // A 3x3 image, rows are: red, green, blue. Columns are: 0%, 33%, 66% transparent.
+
+ transparency : null,
+
+ frames : [
+ {
+ width : 3, height : 3,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 255,0,0,255, 255,0,0,170, 255,0,0,85,
+ 0,255,0,255, 0,255,0,170, 0,255,0,85,
+ 0,0,255,255, 0,0,255,170, 0,0,255,85
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAAIElEQVQImSXJMQEAMAwEIUy+yZi8DmVFFBcjycn86GgPJw4O8v9DkkEAAAAASUVORK5CYII="
+};
+
+var png2B = {
+ // A 3x3 image, rows are: red, green, blue. Columns are: 0%, 33%, 66% transparent,
+ // but transparency will be ignored.
+
+ transparency : "none",
+
+ frames : [
+ {
+ width : 3, height : 3,
+
+ format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
+
+ pixels : [
+ 255,0,0,255, 255,0,0,170, 255,0,0,85,
+ 0,255,0,255, 0,255,0,170, 0,255,0,85,
+ 0,0,255,255, 0,0,255,170, 0,0,255,85
+ ]
+ }
+
+ ],
+ expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII="
+};
+
+// Main test entry point.
+function run_test() {
+ dump("Checking png1A...\n")
+ run_test_for(png1A);
+ dump("Checking png1B...\n")
+ run_test_for(png1B);
+ dump("Checking png2A...\n")
+ run_test_for(png2A);
+ dump("Checking png2B...\n")
+ run_test_for(png2B);
+};
+
+
+function run_test_for(input) {
+ var encoder, dataURL;
+
+ encoder = encodeImage(input);
+ dataURL = makeDataURL(encoder, "image/png");
+ do_check_eq(dataURL, input.expected);
+
+ encoder = encodeImageAsync(input);
+ dataURL = makeDataURLFromAsync(encoder, "image/png", input.expected);
+};
+
+
+function encodeImage(input) {
+ var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance();
+ encoder.QueryInterface(Ci.imgIEncoder);
+
+ var options = "";
+ if (input.transparency) {
+ options += "transparency=" + input.transparency;
+ }
+
+ var frame = input.frames[0];
+ encoder.initFromData(frame.pixels, frame.pixels.length,
+ frame.width, frame.height, frame.stride,
+ frame.format, options);
+ return encoder;
+}
+
+function _encodeImageAsyncFactory(frame, options, encoder)
+{
+ function finishEncode() {
+ encoder.addImageFrame(frame.pixels, frame.pixels.length,
+ frame.width, frame.height, frame.stride,
+ frame.format, options);
+ encoder.endImageEncode();
+ }
+ return finishEncode;
+}
+
+function encodeImageAsync(input)
+{
+ var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance();
+ encoder.QueryInterface(Ci.imgIEncoder);
+
+ var options = "";
+ if (input.transparency) {
+ options += "transparency=" + input.transparency;
+ }
+
+ var frame = input.frames[0];
+ encoder.startImageEncode(frame.width, frame.height,
+ frame.format, options);
+
+ do_timeout(50, _encodeImageAsyncFactory(frame, options, encoder));
+ return encoder;
+}
+
+
+function makeDataURL(encoder, mimetype) {
+ var rawStream = encoder.QueryInterface(Ci.nsIInputStream);
+
+ var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance();
+ stream.QueryInterface(Ci.nsIBinaryInputStream);
+
+ stream.setInputStream(rawStream);
+
+ var bytes = stream.readByteArray(stream.available()); // returns int[]
+
+ var base64String = toBase64(bytes);
+
+ return "data:" + mimetype + ";base64," + base64String;
+}
+
+function makeDataURLFromAsync(encoder, mimetype, expected) {
+ do_test_pending();
+ var rawStream = encoder.QueryInterface(Ci.nsIAsyncInputStream);
+
+ var currentThread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
+
+ var bytes = [];
+
+ var binarystream = Cc["@mozilla.org/binaryinputstream;1"].createInstance();
+ binarystream.QueryInterface(Ci.nsIBinaryInputStream);
+
+ var asyncReader =
+ {
+ onInputStreamReady: function(stream)
+ {
+ binarystream.setInputStream(stream);
+ var available = 0;
+ try {
+ available = stream.available();
+ } catch(e) { }
+
+ if (available > 0)
+ {
+ bytes = bytes.concat(binarystream.readByteArray(available));
+ stream.asyncWait(this, 0, 0, currentThread);
+ } else {
+ var base64String = toBase64(bytes);
+ var dataURL = "data:" + mimetype + ";base64," + base64String;
+ do_check_eq(dataURL, expected);
+ do_test_finished();
+ }
+
+ }
+ };
+ rawStream.asyncWait(asyncReader, 0, 0, currentThread);
+}
+
+/* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */
+
+/* Convert data (an array of integers) to a Base64 string. */
+const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
+ '0123456789+/';
+const base64Pad = '=';
+function toBase64(data) {
+ var result = '';
+ var length = data.length;
+ var i;
+ // Convert every three bytes to 4 ascii characters.
+ for (i = 0; i < (length - 2); i += 3) {
+ result += toBase64Table[data[i] >> 2];
+ result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
+ result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
+ result += toBase64Table[data[i+2] & 0x3f];
+ }
+
+ // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+ if (length%3) {
+ i = length - (length%3);
+ result += toBase64Table[data[i] >> 2];
+ if ((length%3) == 2) {
+ result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
+ result += toBase64Table[(data[i+1] & 0x0f) << 2];
+ result += base64Pad;
+ } else {
+ result += toBase64Table[(data[i] & 0x03) << 4];
+ result += base64Pad + base64Pad;
+ }
+ }
+
+ return result;
+}
diff --git a/image/test/unit/test_imgtools.js b/image/test/unit/test_imgtools.js
new file mode 100644
index 000000000..b9b8da82b
--- /dev/null
+++ b/image/test/unit/test_imgtools.js
@@ -0,0 +1,737 @@
+/*
+ * Tests for imgITools
+ */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+
+/*
+ * dumpToFile()
+ *
+ * For test development, dumps the specified array to a file.
+ * Call |dumpToFile(outData);| in a test to file to a file.
+ */
+function dumpToFile(aData) {
+ var outputFile = do_get_tempdir();
+ outputFile.append("testdump.png");
+
+ var outputStream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ // WR_ONLY|CREAT|TRUNC
+ outputStream.init(outputFile, 0x02 | 0x08 | 0x20, 0o644, null);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].
+ createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(outputStream);
+
+ bos.writeByteArray(aData, aData.length);
+
+ outputStream.close();
+}
+
+
+/*
+ * getFileInputStream()
+ *
+ * Returns an input stream for the specified file.
+ */
+function getFileInputStream(aFile) {
+ var inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ // init the stream as RD_ONLY, -1 == default permissions.
+ inputStream.init(aFile, 0x01, -1, null);
+
+ // Blah. The image decoders use ReadSegments, which isn't implemented on
+ // file input streams. Use a buffered stream to make it work.
+ var bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
+ createInstance(Ci.nsIBufferedInputStream);
+ bis.init(inputStream, 1024);
+
+ return bis;
+}
+
+
+/*
+ * streamToArray()
+ *
+ * Consumes an input stream, and returns its bytes as an array.
+ */
+function streamToArray(aStream) {
+ var size = aStream.available();
+
+ // use a binary input stream to grab the bytes.
+ var bis = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(aStream);
+
+ var bytes = bis.readByteArray(size);
+ if (size != bytes.length)
+ throw "Didn't read expected number of bytes";
+
+ return bytes;
+}
+
+
+/*
+ * compareArrays
+ *
+ * Compares two arrays, and throws if there's a difference.
+ */
+function compareArrays(aArray1, aArray2) {
+ do_check_eq(aArray1.length, aArray2.length);
+
+ for (var i = 0; i < aArray1.length; i++)
+ if (aArray1[i] != aArray2[i])
+ throw "arrays differ at index " + i;
+}
+
+
+/*
+ * checkExpectedError
+ *
+ * Checks to see if a thrown error was expected or not, and if it
+ * matches the expected value.
+ */
+function checkExpectedError (aExpectedError, aActualError) {
+ if (aExpectedError) {
+ if (!aActualError)
+ throw "Didn't throw as expected (" + aExpectedError + ")";
+
+ if (!aExpectedError.test(aActualError))
+ throw "Threw (" + aActualError + "), not (" + aExpectedError;
+
+ // We got the expected error, so make a note in the test log.
+ dump("...that error was expected.\n\n");
+ } else if (aActualError) {
+ throw "Threw unexpected error: " + aActualError;
+ }
+}
+
+
+function run_test() {
+
+try {
+
+
+/* ========== 0 ========== */
+var testnum = 0;
+var testdesc = "imgITools setup";
+var err = null;
+
+var imgTools = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools);
+
+if (!imgTools)
+ throw "Couldn't get imgITools service"
+
+// Ugh, this is an ugly hack. The pixel values we get in Windows are sometimes
+// +/- 1 value compared to other platforms, so we need to compare against a
+// different set of reference images. nsIXULRuntime.OS doesn't seem to be
+// available in xpcshell, so we'll use this as a kludgy way to figure out if
+// we're running on Windows.
+var isWindows = mozinfo.os == "win";
+
+
+/* ========== 1 ========== */
+testnum++;
+testdesc = "test decoding a PNG";
+
+// 64x64 png, 8415 bytes.
+var imgName = "image1.png";
+var inMimeType = "image/png";
+var imgFile = do_get_file(imgName);
+
+var istream = getFileInputStream(imgFile);
+do_check_eq(istream.available(), 8415);
+
+// Use decodeImageData for this test even though it's deprecated to ensure that
+// it correctly forwards to decodeImage and continues to work.
+var outParam = { value: null };
+imgTools.decodeImageData(istream, inMimeType, outParam);
+var container = outParam.value;
+
+// It's not easy to look at the pixel values from JS, so just
+// check the container's size.
+do_check_eq(container.width, 64);
+do_check_eq(container.height, 64);
+
+
+/* ========== 2 ========== */
+testnum++;
+testdesc = "test encoding a scaled JPEG";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 16);
+
+var encodedBytes = streamToArray(istream);
+// Get bytes for exected result
+var refName = "image1png16x16.jpg";
+var refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 1051);
+var referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 3 ========== */
+testnum++;
+testdesc = "test encoding an unscaled JPEG";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeImage(container, "image/jpeg");
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image1png64x64.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 4503);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 4 ========== */
+testnum++;
+testdesc = "test decoding a JPEG";
+
+// 32x32 jpeg, 3494 bytes.
+imgName = "image2.jpg";
+inMimeType = "image/jpeg";
+imgFile = do_get_file(imgName);
+
+istream = getFileInputStream(imgFile);
+do_check_eq(istream.available(), 3494);
+
+container = imgTools.decodeImage(istream, inMimeType);
+
+// It's not easy to look at the pixel values from JS, so just
+// check the container's size.
+do_check_eq(container.width, 32);
+do_check_eq(container.height, 32);
+
+
+/* ========== 5 ========== */
+testnum++;
+testdesc = "test encoding a scaled PNG";
+
+if (!isWindows) {
+// we'll reuse the container from the previous test
+istream = imgTools.encodeScaledImage(container, "image/png", 16, 16);
+
+encodedBytes = streamToArray(istream);
+// Get bytes for exected result
+refName = isWindows ? "image2jpg16x16-win.png" : "image2jpg16x16.png";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 950);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+}
+
+
+/* ========== 6 ========== */
+testnum++;
+testdesc = "test encoding an unscaled PNG";
+
+if (!isWindows) {
+// we'll reuse the container from the previous test
+istream = imgTools.encodeImage(container, "image/png");
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = isWindows ? "image2jpg32x32-win.png" : "image2jpg32x32.png";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 3105);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+}
+
+
+/* ========== 7 ========== */
+testnum++;
+testdesc = "test decoding a ICO";
+
+// 16x16 ico, 1406 bytes.
+imgName = "image3.ico";
+inMimeType = "image/x-icon";
+imgFile = do_get_file(imgName);
+
+istream = getFileInputStream(imgFile);
+do_check_eq(istream.available(), 1406);
+
+container = imgTools.decodeImage(istream, inMimeType);
+
+// It's not easy to look at the pixel values from JS, so just
+// check the container's size.
+do_check_eq(container.width, 16);
+do_check_eq(container.height, 16);
+
+
+/* ========== 8 ========== */
+testnum++;
+testdesc = "test encoding a scaled PNG"; // note that we're scaling UP
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeScaledImage(container, "image/png", 32, 32);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image3ico32x32.png";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 2285);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 9 ========== */
+testnum++;
+testdesc = "test encoding an unscaled PNG";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeImage(container, "image/png");
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image3ico16x16.png";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 330);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 10 ========== */
+testnum++;
+testdesc = "test decoding a GIF";
+
+// 32x32 gif, 1809 bytes.
+imgName = "image4.gif";
+inMimeType = "image/gif";
+imgFile = do_get_file(imgName);
+
+istream = getFileInputStream(imgFile);
+do_check_eq(istream.available(), 1809);
+
+container = imgTools.decodeImage(istream, inMimeType);
+
+// It's not easy to look at the pixel values from JS, so just
+// check the container's size.
+do_check_eq(container.width, 32);
+do_check_eq(container.height, 32);
+
+/* ========== 11 ========== */
+testnum++;
+testdesc = "test encoding an unscaled ICO with format options " +
+ "(format=bmp;bpp=32)";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeImage(container,
+ "image/vnd.microsoft.icon",
+ "format=bmp;bpp=32");
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image4gif32x32bmp32bpp.ico";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 4286);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+/* ========== 12 ========== */
+testnum++;
+testdesc = "test encoding a scaled ICO with format options " +
+ "(format=bmp;bpp=32)";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeScaledImage(container,
+ "image/vnd.microsoft.icon",
+ 16,
+ 16,
+ "format=bmp;bpp=32");
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image4gif16x16bmp32bpp.ico";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 1150);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+/* ========== 13 ========== */
+testnum++;
+testdesc = "test encoding an unscaled ICO with format options " +
+ "(format=bmp;bpp=24)";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeImage(container,
+ "image/vnd.microsoft.icon",
+ "format=bmp;bpp=24");
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image4gif32x32bmp24bpp.ico";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 3262);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+/* ========== 14 ========== */
+testnum++;
+testdesc = "test encoding a scaled ICO with format options " +
+ "(format=bmp;bpp=24)";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeScaledImage(container,
+ "image/vnd.microsoft.icon",
+ 16,
+ 16,
+ "format=bmp;bpp=24");
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image4gif16x16bmp24bpp.ico";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 894);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 15 ========== */
+testnum++;
+testdesc = "test cropping a JPG";
+
+// 32x32 jpeg, 3494 bytes.
+imgName = "image2.jpg";
+inMimeType = "image/jpeg";
+imgFile = do_get_file(imgName);
+
+istream = getFileInputStream(imgFile);
+do_check_eq(istream.available(), 3494);
+
+container = imgTools.decodeImage(istream, inMimeType);
+
+// It's not easy to look at the pixel values from JS, so just
+// check the container's size.
+do_check_eq(container.width, 32);
+do_check_eq(container.height, 32);
+
+// encode a cropped image
+istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 16);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image2jpg16x16cropped.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 879);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 16 ========== */
+testnum++;
+testdesc = "test cropping a JPG with an offset";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeCroppedImage(container, "image/jpeg", 16, 16, 16, 16);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image2jpg16x16cropped2.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 878);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 17 ========== */
+testnum++;
+testdesc = "test cropping a JPG without a given height";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 0);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image2jpg16x32cropped3.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 1127);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 18 ========== */
+testnum++;
+testdesc = "test cropping a JPG without a given width";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 16);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image2jpg32x16cropped4.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 1135);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 19 ========== */
+testnum++;
+testdesc = "test cropping a JPG without a given width and height";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 0);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image2jpg32x32.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 1634);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 20 ========== */
+testnum++;
+testdesc = "test scaling a JPG without a given width";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 16);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image2jpg32x16scaled.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 1227);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 21 ========== */
+testnum++;
+testdesc = "test scaling a JPG without a given height";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 0);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image2jpg16x32scaled.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 1219);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 22 ========== */
+testnum++;
+testdesc = "test scaling a JPG without a given width and height";
+
+// we'll reuse the container from the previous test
+istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 0);
+encodedBytes = streamToArray(istream);
+
+// Get bytes for exected result
+refName = "image2jpg32x32.jpg";
+refFile = do_get_file(refName);
+istream = getFileInputStream(refFile);
+do_check_eq(istream.available(), 1634);
+referenceBytes = streamToArray(istream);
+
+// compare the encoder's output to the reference file.
+compareArrays(encodedBytes, referenceBytes);
+
+
+/* ========== 22 ========== */
+testnum++;
+testdesc = "test invalid arguments for cropping";
+
+var numErrors = 0;
+
+try {
+ // width/height can't be negative
+ imgTools.encodeScaledImage(container, "image/jpeg", -1, -1);
+} catch (e) { numErrors++; }
+
+try {
+ // offsets can't be negative
+ imgTools.encodeCroppedImage(container, "image/jpeg", -1, -1, 16, 16);
+} catch (e) { numErrors++; }
+
+try {
+ // width/height can't be negative
+ imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, -1, -1);
+} catch (e) { numErrors++; }
+
+try {
+ // out of bounds
+ imgTools.encodeCroppedImage(container, "image/jpeg", 17, 17, 16, 16);
+} catch (e) { numErrors++; }
+
+try {
+ // out of bounds
+ imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 33, 33);
+} catch (e) { numErrors++; }
+
+try {
+ // out of bounds
+ imgTools.encodeCroppedImage(container, "image/jpeg", 1, 1, 0, 0);
+} catch (e) { numErrors++; }
+
+do_check_eq(numErrors, 6);
+
+
+/* ========== bug 363986 ========== */
+testnum = 363986;
+testdesc = "test PNG and JPEG encoders' Read/ReadSegments methods";
+
+var testData =
+ [{preImage: "image3.ico",
+ preImageMimeType: "image/x-icon",
+ refImage: "image3ico16x16.png",
+ refImageMimeType: "image/png"},
+ {preImage: "image1.png",
+ preImageMimeType: "image/png",
+ refImage: "image1png64x64.jpg",
+ refImageMimeType: "image/jpeg"}];
+
+for(var i=0; i<testData.length; ++i) {
+ var dict = testData[i];
+
+ var imgFile = do_get_file(dict["refImage"]);
+ var istream = getFileInputStream(imgFile);
+ var refBytes = streamToArray(istream);
+
+ imgFile = do_get_file(dict["preImage"]);
+ istream = getFileInputStream(imgFile);
+
+ var container = imgTools.decodeImage(istream, dict["preImageMimeType"]);
+
+ istream = imgTools.encodeImage(container, dict["refImageMimeType"]);
+
+ var sstream = Cc["@mozilla.org/storagestream;1"].
+ createInstance(Ci.nsIStorageStream);
+ sstream.init(4096, 4294967295, null);
+ var ostream = sstream.getOutputStream(0);
+ var bostream = Cc["@mozilla.org/network/buffered-output-stream;1"].
+ createInstance(Ci.nsIBufferedOutputStream);
+
+ //use a tiny buffer to make sure the image data doesn't fully fit in it
+ bostream.init(ostream, 8);
+
+ bostream.writeFrom(istream, istream.available());
+ bostream.flush(); bostream.close();
+
+ var encBytes = streamToArray(sstream.newInputStream(0));
+
+ compareArrays(refBytes, encBytes);
+}
+
+
+/* ========== bug 413512 ========== */
+testnum = 413512;
+testdesc = "test decoding bad favicon (bug 413512)";
+
+imgName = "bug413512.ico";
+inMimeType = "image/x-icon";
+imgFile = do_get_file(imgName);
+
+istream = getFileInputStream(imgFile);
+do_check_eq(istream.available(), 17759);
+var errsrc = "none";
+
+try {
+ container = imgTools.decodeImage(istream, inMimeType);
+
+ // We expect to hit an error during encoding because the ICO header of the
+ // image is fine, but the actual resources are corrupt. Since decodeImage()
+ // only performs a metadata decode, it doesn't decode far enough to realize
+ // this, but we'll find out when we do a full decode during encodeImage().
+ try {
+ istream = imgTools.encodeImage(container, "image/png");
+ } catch (e) {
+ err = e;
+ errsrc = "encode";
+ }
+} catch (e) {
+ err = e;
+ errsrc = "decode";
+}
+
+do_check_eq(errsrc, "encode");
+checkExpectedError(/NS_ERROR_FAILURE/, err);
+
+
+/* ========== bug 815359 ========== */
+testnum = 815359;
+testdesc = "test correct ico hotspots (bug 815359)";
+
+imgName = "bug815359.ico";
+inMimeType = "image/x-icon";
+imgFile = do_get_file(imgName);
+
+istream = getFileInputStream(imgFile);
+do_check_eq(istream.available(), 4286);
+
+container = imgTools.decodeImage(istream, inMimeType);
+
+var props = container.QueryInterface(Ci.nsIProperties);
+
+do_check_eq(props.get("hotspotX", Ci.nsISupportsPRUint32).data, 10);
+do_check_eq(props.get("hotspotY", Ci.nsISupportsPRUint32).data, 9);
+
+
+/* ========== end ========== */
+
+} catch (e) {
+ throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
+}
+};
diff --git a/image/test/unit/test_moz_icon_uri.js b/image/test/unit/test_moz_icon_uri.js
new file mode 100644
index 000000000..5e697fb21
--- /dev/null
+++ b/image/test/unit/test_moz_icon_uri.js
@@ -0,0 +1,157 @@
+/*
+ * Test icon URI functionality
+ *
+ */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+// There are 3 types of valid icon URIs:
+// 1. moz-icon:[valid URL]
+// 2. moz-icon://[file name]
+// 3. moz-icon://stock/[icon identifier]
+// Plus we also support moz-icon://[valid URL] for backwards compatibility.
+
+// Main test entry point.
+function run_test() {
+ let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ let currentSpec = ""; // the uri spec that we're currently testing
+ let exception = false; // whether or not an exception was thrown
+ let uri = null; // the current URI
+ let iconURI = null; // the current icon URI
+
+ // Note that if the scheme is not correct the ioservice won't even create an icon URI
+ // so don't bother testing incorrect schemes here.
+
+ // Make sure a valid file name icon URI can be created and that we can obtain
+ // all arguments, the spec, and the file extension.
+ currentSpec = "moz-icon://foo.html?contentType=bar&size=button&state=normal";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, false);
+ exception = false; // reset exception value
+
+ iconURI = uri.QueryInterface(Ci.nsIMozIconURI);
+ do_check_eq(iconURI.iconSize, "button");
+ do_check_eq(iconURI.iconState, "normal");
+ do_check_eq(iconURI.contentType, "bar");
+ do_check_eq(iconURI.fileExtension, ".html");
+
+ // Make sure a valid file name icon URI can be created with a numeric size,
+ // and make sure the numeric size is handled properly
+ currentSpec = "moz-icon://foo.html?size=3";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, false);
+ exception = false; // reset exception value
+
+ iconURI = uri.QueryInterface(Ci.nsIMozIconURI);
+ do_check_eq(iconURI.iconSize, "");
+ do_check_eq(iconURI.imageSize, 3);
+
+ // Make sure a valid stock icon URI can be created and that we can obtain
+ // the stock icon's name.
+ currentSpec = "moz-icon://stock/foo";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, false);
+ exception = false; // reset exception value
+
+ iconURI = uri.QueryInterface(Ci.nsIMozIconURI);
+ do_check_eq(iconURI.stockIcon, "foo");
+
+ // Make sure an invalid stock icon URI, missing icon identifier, throws.
+ currentSpec = "moz-icon://stock/?size=3";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_true(exception);
+ exception = false; // reset exception value
+
+ // Make sure a valid file URL icon URI can be created and that we can obtain
+ // the URL and QI it to an nsIFileURL.
+ currentSpec = "moz-icon:file://foo.txt";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, false);
+ exception = false; // reset exception value
+
+ iconURI = uri.QueryInterface(Ci.nsIMozIconURI);
+ let fileURL = null;
+ try {
+ fileURL = iconURI.iconURL.QueryInterface(Ci.nsIFileURL);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, false);
+ exception = false; // reset exception value
+
+ do_check_neq(fileURL, null);
+
+ // Now test a file URI which has been created with an extra //
+ currentSpec = "moz-icon://file://foo.txt";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, false);
+ exception = false; // reset exception value
+
+ iconURI = uri.QueryInterface(Ci.nsIMozIconURI);
+ fileURL = null;
+ try {
+ fileURL = iconURI.iconURL.QueryInterface(Ci.nsIFileURL);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, false);
+ exception = false; // reset exception value
+
+ do_check_neq(fileURL, null);
+
+ // Now test a simple invalid icon URI. This should fail.
+ currentSpec = "moz-icon:foo";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, true);
+ exception = false; // reset exception value
+
+ // Now test an icon URI that has a URI for a path but that is not a URL. This should fail.
+ // This is png data for a little red dot that I got from wikipedia.
+ currentSpec = "moz-icon:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0vr4MkhoXe0rZigAAAABJRU5ErkJggg==";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, true);
+ exception = false; // reset exception value
+
+ // Now test a URI that should be a file name but is ridiculously long. This should fail.
+ currentSpec = "moz-icon://data:application/vnd.ms-excel;base64,PHhtbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtOCI+PHNzOldvcmtib29rIHhtbG5zOnNzPSJ1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOm9mZmljZTpzcHJlYWRzaGVldCIgeG1sbnM6eD0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6ZXhjZWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSI+PG86RG9jdW1lbnRQcm9wZXJ0aWVzPjxvOlRpdGxlPkFycmF5IEdyaWQ8L286VGl0bGU+PC9vOkRvY3VtZW50UHJvcGVydGllcz48c3M6RXhjZWxXb3JrYm9vaz48c3M6V2luZG93SGVpZ2h0PjkwMDA8L3NzOldpbmRvd0hlaWdodD48c3M6V2luZG93V2lkdGg+MTc0ODA8L3NzOldpbmRvd1dpZHRoPjxzczpQcm90ZWN0U3RydWN0dXJlPkZhbHNlPC9zczpQcm90ZWN0U3RydWN0dXJlPjxzczpQcm90ZWN0V2luZG93cz5GYWxzZTwvc3M6UHJvdGVjdFdpbmRvd3M+PC9zczpFeGNlbFdvcmtib29rPjxzczpTdHlsZXM+PHNzOlN0eWxlIHNzOklEPSJEZWZhdWx0Ij48c3M6QWxpZ25tZW50IHNzOlZlcnRpY2FsPSJUb3AiIHNzOldyYXBUZXh0PSIxIiAvPjxzczpGb250IHNzOkZvbnROYW1lPSJhcmlhbCIgc3M6U2l6ZT0iMTAiIC8+PHNzOkJvcmRlcnM+PHNzOkJvcmRlciBzczpDb2xvcj0iI2U0ZTRlNCIgc3M6V2VpZ2h0PSIxIiBzczpMaW5lU3R5bGU9IkNvbnRpbnVvdXMiIHNzOlBvc2l0aW9uPSJUb3AiIC8+PHNzOkJvcmRlciBzczpDb2xvcj0iI2U0ZTRlNCIgc3M6V2VpZ2h0PSIxIiBzczpMaW5lU3R5bGU9IkNvbnRpbnVvdXMiIHNzOlBvc2l0aW9uPSJCb3R0b20iIC8+PHNzOkJvcmRlciBzczpDb2xvcj0iI2U0ZTRlNCIgc3M6V2VpZ2h0PSIxIiBzczpMaW5lU3R5bGU9IkNvbnRpbnVvdXMiIHNzOlBvc2l0aW9uPSJMZWZ0IiAvPjxzczpCb3JkZXIgc3M6Q29sb3I9IiNlNGU0ZTQiIHNzOldlaWdodD0iMSIgc3M6TGluZVN0eWxlPSJDb250aW51b3VzIiBzczpQb3NpdGlvbj0iUmlnaHQiIC8+PC9zczpCb3JkZXJzPjxzczpJbnRlcmlvciAvPjxzczpOdW1iZXJGb3JtYXQgLz48c3M6UHJvdGVjdGlvbiAvPjwvc3M6U3R5bGU+PHNzOlN0eWxlIHNzOklEPSJ0aXRsZSI+PHNzOkJvcmRlcnMgLz48c3M6Rm9udCAvPjxzczpBbGlnbm1lbnQgc3M6V3JhcFRleHQ9IjEiIHNzOlZlcnRpY2FsPSJDZW50ZXIiIHNzOkhvcml6b250YWw9IkNlbnRlciIgLz48c3M6TnVtYmVyRm9ybWF0IHNzOkZvcm1hdD0iQCIgLz48L3NzOlN0eWxlPjxzczpTdHlsZSBzczpJRD0iaGVhZGVyY2VsbCI+PHNzOkZvbnQgc3M6Qm9sZD0iMSIgc3M6U2l6ZT0iMTAiIC8+PHNzOkFsaWdubWVudCBzczpXcmFwVGV4dD0iMSIgc3M6SG9yaXpvbnRhbD0iQ2VudGVyIiAvPjxzczpJbnRlcmlvciBzczpQYXR0ZXJuPSJTb2xpZCIgc3M6Q29sb3I9IiNBM0M5RjEiIC8+PC9zczpTdHlsZT48c3M6U3R5bGUgc3M6SUQ9ImV2ZW4iPjxzczpJbnRlcmlvciBzczpQYXR0ZXJuPSJTb2xpZCIgc3M6Q29sb3I9IiNDQ0ZGRkYiIC8+PC9zczpTdHlsZT48c3M6U3R5bGUgc3M6UGFyZW50PSJldmVuIiBzczpJRD0iZXZlbmRhdGUiPjxzczpOdW1iZXJGb3JtYXQgc3M6Rm9ybWF0PSJ5eXl5LW1tLWRkIiAvPjwvc3M6U3R5bGU+PHNzOlN0eWxlIHNzOlBhcmVudD0iZXZlbiIgc3M6SUQ9ImV2ZW5pbnQiPjxzczpOdW1iZXJGb3JtYXQgc3M6Rm9ybWF0PSIwIiAvPjwvc3M6U3R5bGU+PHNzOlN0eWxlIHNzOlBhcmVudD0iZXZlbiIgc3M6SUQ9ImV2ZW5mbG9hdCI+PHNzOk51bWJlckZvcm1hdCBzczpGb3JtYXQ9IjAuMDAwIiAvPjwvc3M6U3R5bGU+PHNzOlN0eWxlIHNzOklEPSJvZGQiPjxzczpJbnRlcmlvciBzczpQYXR0ZXJuPSJTb2xpZCIgc3M6Q29sb3I9IiNDQ0NDRkYiIC8+PC9zczpTdHlsZT48c3M6U3R5bGUgc3M6UGFyZW50PSJvZGQiIHNzOklEPSJvZGRkYXRlIj48c3M6TnVtYmVyRm9ybWF0IHNzOkZvcm1hdD0ieXl5eS1tbS1kZCIgLz48L3NzOlN0eWxlPjxzczpTdHlsZSBzczpQYXJlbnQ9Im9kZCIgc3M6SUQ9Im9kZGludCI+PHNzOk51bWJlckZvcm1hdCBzczpGb3JtYXQ9IjAiIC8+PC9zczpTdHlsZT48c3M6U3R5bGUgc3M6UGFyZW50PSJvZGQiIHNzOklEPSJvZGRmbG9hdCI+PHNzOk51bWJlckZvcm1hdCBzczpGb3JtYXQ9IjAuMDAwIiAvPjwvc3M6U3R5bGU+PC9zczpTdHlsZXM+PHNzOldvcmtzaGVldCBzczpOYW1lPSJBcnJheSBHcmlkIj48c3M6TmFtZXM+PHNzOk5hbWVkUmFuZ2Ugc3M6TmFtZT0iUHJpbnRfVGl0bGVzIiBzczpSZWZlcnNUbz0iPSdBcnJheSBHcmlkJyFSMTpSMiIgLz48L3NzOk5hbWVzPjxzczpUYWJsZSB4OkZ1bGxSb3dzPSIxIiB4OkZ1bGxDb2x1bW5zPSIxIiBzczpFeHBhbmRlZENvbHVtbkNvdW50PSI1IiBzczpFeHBhbmRlZFJvd0NvdW50PSIzMSI+PHNzOkNvbHVtbiBzczpBdXRvRml0V2lkdGg9IjEiIHNzOldpZHRoPSIyNzEiIC8+PHNzOkNvbHVtbiBzczpBdXRvRml0V2lkdGg9IjEiIHNzOldpZHRoPSI3NSIgLz48c3M6Q29sdW1uIHNzOkF1dG9GaXRXaWR0aD0iMSIgc3M6V2lkdGg9Ijc1IiAvPjxzczpDb2x1bW4gc3M6QXV0b0ZpdFdpZHRoPSIxIiBzczpXaWR0aD0iNzUiIC8+PHNzOkNvbHVtbiBzczpBdXRvRml0V2lkdGg9IjEiIHNzOldpZHRoPSI4NSIgLz48c3M6Um93IHNzOkhlaWdodD0iMzgiPjxzczpDZWxsIHNzOlN0eWxlSUQ9InRpdGxlIiBzczpNZXJnZUFjcm9zcz0iNCI+PHNzOkRhdGEgeG1sbnM6aHRtbD0iaHR0cDovL3d3dy53My5vcmcvVFIvUkVDLWh0bWw0MCIgc3M6VHlwZT0iU3RyaW5nIj48aHRtbDpCPiAoYykyMDA4IFNFQk4gVUE8L2h0bWw6Qj48L3NzOkRhdGE+PHNzOk5hbWVkQ2VsbCBzczpOYW1lPSJQcmludF9UaXRsZXMiIC8+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3cgc3M6QXV0b0ZpdEhlaWdodD0iMSI+PHNzOkNlbGwgc3M6U3R5bGVJRD0iaGVhZGVyY2VsbCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5Db21wYW55PC9zczpEYXRhPjxzczpOYW1lZENlbGwgc3M6TmFtZT0iUHJpbnRfVGl0bGVzIiAvPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJoZWFkZXJjZWxsIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPlByaWNlPC9zczpEYXRhPjxzczpOYW1lZENlbGwgc3M6TmFtZT0iUHJpbnRfVGl0bGVzIiAvPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJoZWFkZXJjZWxsIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPkNoYW5nZTwvc3M6RGF0YT48c3M6TmFtZWRDZWxsIHNzOk5hbWU9IlByaW50X1RpdGxlcyIgLz48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iaGVhZGVyY2VsbCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4lIENoYW5nZTwvc3M6RGF0YT48c3M6TmFtZWRDZWxsIHNzOk5hbWU9IlByaW50X1RpdGxlcyIgLz48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iaGVhZGVyY2VsbCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5MYXN0IFVwZGF0ZWQ8L3NzOkRhdGE+PHNzOk5hbWVkQ2VsbCBzczpOYW1lPSJQcmludF9UaXRsZXMiIC8+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4zbSBDbzwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj43MS43Mjwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjAyPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuMDM8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+V2VkIFNlcCAwMSAyMDEwIDAwOjAwOjAwIEdNVCsxMDAwIChFU1QpPC9zczpEYXRhPjwvc3M6Q2VsbD48L3NzOlJvdz48c3M6Um93PjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5BVCZUIEluYy48L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4zMS42MTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPi0wLjQ4PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+LTEuNTQ8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5BbGNvYSBJbmM8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MjkuMDE8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC40Mjwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4xLjQ3PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PHNzOlJvdz48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+QWx0cmlhIEdyb3VwIEluYzwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjgzLjgxPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4yODwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuMzQ8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5BbWVyaWNhbiBFeHByZXNzIENvbXBhbnk8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+NTIuNTU8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4wMTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjAyPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PHNzOlJvdz48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+QW1lcmljYW4gSW50ZXJuYXRpb25hbCBHcm91cCwgSW5jLjwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjY0LjEzPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4zMTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuNDk8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5Cb2VpbmcgQ28uPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjc1LjQzPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuNTM8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC43MTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPkNhdGVycGlsbGFyIEluYy48L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj42Ny4yNzwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuOTI8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4xLjM5PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+V2VkIFNlcCAwMSAyMDEwIDAwOjAwOjAwIEdNVCsxMDAwIChFU1QpPC9zczpEYXRhPjwvc3M6Q2VsbD48L3NzOlJvdz48c3M6Um93PjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+Q2l0aWdyb3VwLCBJbmMuPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjQ5LjM3PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuMDI8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4wNDwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPkUuSS4gZHUgUG9udCBkZSBOZW1vdXJzIGFuZCBDb21wYW55PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+NDAuNDg8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjUxPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MS4yODwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PHNzOlJvdz48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPkV4eG9uIE1vYmlsIENvcnA8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+NjguMTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4tMC40Mzwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4tMC42NDwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPkdlbmVyYWwgRWxlY3RyaWMgQ29tcGFueTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjM0LjE0PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+LTAuMDg8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4tMC4yMzwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PHNzOlJvdz48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPkdlbmVyYWwgTW90b3JzIENvcnBvcmF0aW9uPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjMwLjI3PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjEuMDk8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+My43NDwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPkhld2xldHQtUGFja2FyZCBDby48L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4zNi41Mzwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPi0wLjAzPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+LTAuMDg8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5Ib25leXdlbGwgSW50bCBJbmM8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MzguNzc8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4wNTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjEzPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PHNzOlJvdz48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+SW50ZWwgQ29ycG9yYXRpb248L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4xOS44ODwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuMzE8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4xLjU4PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+V2VkIFNlcCAwMSAyMDEwIDAwOjAwOjAwIEdNVCsxMDAwIChFU1QpPC9zczpEYXRhPjwvc3M6Q2VsbD48L3NzOlJvdz48c3M6Um93PjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+SW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNoaW5lczwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj44MS40MTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjQ0PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuNTQ8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+V2VkIFNlcCAwMSAyMDEwIDAwOjAwOjAwIEdNVCsxMDAwIChFU1QpPC9zczpEYXRhPjwvc3M6Q2VsbD48L3NzOlJvdz48c3M6Um93PjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5KUCBNb3JnYW4gJiBDaGFzZSAmIENvPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+NDUuNzM8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjA3PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4xNTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PHNzOlJvdz48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPkpvaG5zb24gJiBKb2huc29uPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjY0LjcyPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuMDY8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4wOTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPk1jRG9uYWxkJ3MgQ29ycG9yYXRpb248L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4zNi43Njwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuODY8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4yLjQ8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5NZXJjayAmIENvLiwgSW5jLjwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj40MC45Njwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjQxPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjEuMDE8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+V2VkIFNlcCAwMSAyMDEwIDAwOjAwOjAwIEdNVCsxMDAwIChFU1QpPC9zczpEYXRhPjwvc3M6Q2VsbD48L3NzOlJvdz48c3M6Um93PjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5NaWNyb3NvZnQgQ29ycG9yYXRpb248L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4yNS44NDwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuMTQ8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjU0PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+V2VkIFNlcCAwMSAyMDEwIDAwOjAwOjAwIEdNVCsxMDAwIChFU1QpPC9zczpEYXRhPjwvc3M6Q2VsbD48L3NzOlJvdz48c3M6Um93PjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+UGZpemVyIEluYzwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4yNy45Njwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjQ8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MS40NTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPlRoZSBDb2NhLUNvbGEgQ29tcGFueTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjQ1LjA3PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4yNjwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjAuNTg8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5UaGUgSG9tZSBEZXBvdCwgSW5jLjwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4zNC42NDwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjM1PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjEuMDI8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+V2VkIFNlcCAwMSAyMDEwIDAwOjAwOjAwIEdNVCsxMDAwIChFU1QpPC9zczpEYXRhPjwvc3M6Q2VsbD48L3NzOlJvdz48c3M6Um93PjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5UaGUgUHJvY3RlciAmIEdhbWJsZSBDb21wYW55PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+NjEuOTE8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjAxPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4wMjwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PHNzOlJvdz48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPlVuaXRlZCBUZWNobm9sb2dpZXMgQ29ycG9yYXRpb248L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+NjMuMjY8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC41NTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4wLjg4PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PHNzOlJvdz48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+VmVyaXpvbiBDb21tdW5pY2F0aW9uczwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjM1LjU3PC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJvZGQiPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC4zOTwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0ib2RkIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPjEuMTE8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9Im9kZCI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XZWQgU2VwIDAxIDIwMTAgMDA6MDA6MDAgR01UKzEwMDAgKEVTVCk8L3NzOkRhdGE+PC9zczpDZWxsPjwvc3M6Um93PjxzczpSb3c+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj5XYWwtTWFydCBTdG9yZXMsIEluYy48L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+NDUuNDU8L3NzOkRhdGE+PC9zczpDZWxsPjxzczpDZWxsIHNzOlN0eWxlSUQ9ImV2ZW4iPjxzczpEYXRhIHNzOlR5cGU9IlN0cmluZyI+MC43Mzwvc3M6RGF0YT48L3NzOkNlbGw+PHNzOkNlbGwgc3M6U3R5bGVJRD0iZXZlbiI+PHNzOkRhdGEgc3M6VHlwZT0iU3RyaW5nIj4xLjYzPC9zczpEYXRhPjwvc3M6Q2VsbD48c3M6Q2VsbCBzczpTdHlsZUlEPSJldmVuIj48c3M6RGF0YSBzczpUeXBlPSJTdHJpbmciPldlZCBTZXAgMDEgMjAxMCAwMDowMDowMCBHTVQrMTAwMCAoRVNUKTwvc3M6RGF0YT48L3NzOkNlbGw+PC9zczpSb3c+PC9zczpUYWJsZT48eDpXb3Jrc2hlZXRPcHRpb25zPjx4OlBhZ2VTZXR1cD48eDpMYXlvdXQgeDpDZW50ZXJIb3Jpem9udGFsPSIxIiB4Ok9yaWVudGF0aW9uPSJMYW5kc2NhcGUiIC8+PHg6Rm9vdGVyIHg6RGF0YT0iUGFnZSAmYW1wO1Agb2YgJmFtcDtOIiB4Ok1hcmdpbj0iMC41IiAvPjx4OlBhZ2VNYXJnaW5zIHg6VG9wPSIwLjUiIHg6UmlnaHQ9IjAuNSIgeDpMZWZ0PSIwLjUiIHg6Qm90dG9tPSIwLjgiIC8+PC94OlBhZ2VTZXR1cD48eDpGaXRUb1BhZ2UgLz48eDpQcmludD48eDpQcmludEVycm9ycz5CbGFuazwveDpQcmludEVycm9ycz48eDpGaXRXaWR0aD4xPC94OkZpdFdpZHRoPjx4OkZpdEhlaWdodD4zMjc2NzwveDpGaXRIZWlnaHQ+PHg6VmFsaWRQcmludGVySW5mbyAvPjx4OlZlcnRpY2FsUmVzb2x1dGlvbj42MDA8L3g6VmVydGljYWxSZXNvbHV0aW9uPjwveDpQcmludD48eDpTZWxlY3RlZCAvPjx4OkRvTm90RGlzcGxheUdyaWRsaW5lcyAvPjx4OlByb3RlY3RPYmplY3RzPkZhbHNlPC94OlByb3RlY3RPYmplY3RzPjx4OlByb3RlY3RTY2VuYXJpb3M+RmFsc2U8L3g6UHJvdGVjdFNjZW5hcmlvcz48L3g6V29ya3NoZWV0T3B0aW9ucz48L3NzOldvcmtzaGVldD48L3NzOldvcmtib29rPg==";
+ try {
+ uri = ioService.newURI(currentSpec, null, null);
+ } catch (e) {
+ exception = true;
+ }
+ do_check_eq(exception, true);
+ exception = false; // reset exception value
+};
diff --git a/image/test/unit/test_private_channel.js b/image/test/unit/test_private_channel.js
new file mode 100644
index 000000000..960d6d69b
--- /dev/null
+++ b/image/test/unit/test_private_channel.js
@@ -0,0 +1,143 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+var server = new HttpServer();
+server.registerPathHandler('/image.png', imageHandler);
+server.start(-1);
+
+load('image_load_helpers.js');
+
+var gHits = 0;
+
+var gIoService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+var gPublicLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
+var gPrivateLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
+gPrivateLoader.QueryInterface(Ci.imgICache).respectPrivacyNotifications();
+
+function imageHandler(metadata, response) {
+ gHits++;
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+var requests = [];
+var listeners = [];
+
+function NotificationCallbacks(isPrivate) {
+ this.originAttributes.privateBrowsingId = isPrivate ? 1 : 0;
+ this.usePrivateBrowsing = isPrivate;
+}
+
+NotificationCallbacks.prototype = {
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsILoadContext))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ getInterface: function(iid) {
+ if (iid.equals(Ci.nsILoadContext))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ originAttributes: {
+ privateBrowsingId: 0
+ }
+};
+
+var gImgPath = 'http://localhost:' + server.identity.primaryPort + '/image.png';
+
+function setup_chan(path, isPrivate, callback) {
+ var uri = NetUtil.newURI(gImgPath);
+ var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
+ var principal = Services.scriptSecurityManager
+ .createCodebasePrincipal(uri, {privateBrowsingId: isPrivate ? 1 : 0});
+ var chan = NetUtil.newChannel({uri: uri, loadingPrincipal: principal,
+ securityFlags: securityFlags,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE});
+ chan.notificationCallbacks = new NotificationCallbacks(isPrivate);
+ var channelListener = new ChannelListener();
+ chan.asyncOpen2(channelListener);
+
+ var listener = new ImageListener(null, callback);
+ var outlistener = {};
+ var loader = isPrivate ? gPrivateLoader : gPublicLoader;
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener);
+ listeners.push(outer);
+ requests.push(loader.loadImageWithChannelXPCOM(chan, outer, null, outlistener));
+ channelListener.outputListener = outlistener.value;
+ listener.synchronous = false;
+}
+
+function loadImage(isPrivate, callback) {
+ var listener = new ImageListener(null, callback);
+ var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .createScriptedObserver(listener);
+ var uri = gIoService.newURI(gImgPath, null, null);
+ var loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
+ loadGroup.notificationCallbacks = new NotificationCallbacks(isPrivate);
+ var loader = isPrivate ? gPrivateLoader : gPublicLoader;
+ requests.push(loader.loadImageXPCOM(uri, null, null, "default", null, loadGroup, outer, null, 0, null));
+ listener.synchronous = false;
+}
+
+function run_loadImage_tests() {
+ function observer() {
+ Services.obs.removeObserver(observer, "cacheservice:empty-cache");
+ gHits = 0;
+ loadImage(false, function() {
+ loadImage(false, function() {
+ loadImage(true, function() {
+ loadImage(true, function() {
+ do_check_eq(gHits, 2);
+ server.stop(do_test_finished);
+ });
+ });
+ });
+ });
+ }
+
+ Services.obs.addObserver(observer, "cacheservice:empty-cache", false);
+ let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ cs.clear();
+}
+
+function cleanup()
+{
+ for (var i = 0; i < requests.length; ++i) {
+ requests[i].cancelAndForgetObserver(0);
+ }
+}
+
+function run_test() {
+ do_register_cleanup(cleanup);
+
+ do_test_pending();
+
+ // We create a public channel that loads an image, then an identical
+ // one that should cause a cache read. We then create a private channel
+ // and load the same image, and do that a second time to ensure a cache
+ // read. In total, we should cause two separate http responses to occur,
+ // since the private channels shouldn't be able to use the public cache.
+ setup_chan('/image.png', false, function() {
+ setup_chan('/image.png', false, function() {
+ setup_chan('/image.png', true, function() {
+ setup_chan('/image.png', true, function() {
+ do_check_eq(gHits, 2);
+ run_loadImage_tests();
+ });
+ });
+ });
+ });
+}
diff --git a/image/test/unit/xpcshell.ini b/image/test/unit/xpcshell.ini
new file mode 100644
index 000000000..fea361bb5
--- /dev/null
+++ b/image/test/unit/xpcshell.ini
@@ -0,0 +1,45 @@
+[DEFAULT]
+head =
+tail =
+support-files =
+ async_load_tests.js
+ bug413512.ico
+ bug815359.ico
+ image1.png
+ image1png16x16.jpg
+ image1png64x64.jpg
+ image2.jpg
+ image2jpg16x16-win.png
+ image2jpg16x16.png
+ image2jpg16x16cropped.jpg
+ image2jpg16x16cropped2.jpg
+ image2jpg16x32cropped3.jpg
+ image2jpg16x32scaled.jpg
+ image2jpg32x16cropped4.jpg
+ image2jpg32x16scaled.jpg
+ image2jpg32x32-win.png
+ image2jpg32x32.jpg
+ image2jpg32x32.png
+ image3.ico
+ image3ico16x16.png
+ image3ico32x32.png
+ image4.gif
+ image4gif16x16bmp24bpp.ico
+ image4gif16x16bmp32bpp.ico
+ image4gif32x32bmp24bpp.ico
+ image4gif32x32bmp32bpp.ico
+ image_load_helpers.js
+
+
+[test_async_notification.js]
+# Bug 1156452: frequent crash on Android 4.3
+skip-if = os == "android"
+[test_async_notification_404.js]
+[test_async_notification_animated.js]
+[test_encoder_apng.js]
+[test_encoder_png.js]
+[test_imgtools.js]
+# Bug 676968
+skip-if = os == "android"
+[test_moz_icon_uri.js]
+[test_private_channel.js]