diff options
Diffstat (limited to 'image/test/unit')
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 Binary files differnew file mode 100644 index 000000000..b2db0429f --- /dev/null +++ b/image/test/unit/bug413512.ico diff --git a/image/test/unit/bug815359.ico b/image/test/unit/bug815359.ico Binary files differnew file mode 100644 index 000000000..a24b8fb6b --- /dev/null +++ b/image/test/unit/bug815359.ico diff --git a/image/test/unit/image1.png b/image/test/unit/image1.png Binary files differnew file mode 100644 index 000000000..2fb37aeec --- /dev/null +++ b/image/test/unit/image1.png diff --git a/image/test/unit/image1png16x16.jpg b/image/test/unit/image1png16x16.jpg Binary files differnew file mode 100644 index 000000000..ea14dbede --- /dev/null +++ b/image/test/unit/image1png16x16.jpg diff --git a/image/test/unit/image1png64x64.jpg b/image/test/unit/image1png64x64.jpg Binary files differnew file mode 100644 index 000000000..11c34f6c6 --- /dev/null +++ b/image/test/unit/image1png64x64.jpg diff --git a/image/test/unit/image2.jpg b/image/test/unit/image2.jpg Binary files differnew file mode 100644 index 000000000..b2131bf0c --- /dev/null +++ b/image/test/unit/image2.jpg diff --git a/image/test/unit/image2jpg16x16-win.png b/image/test/unit/image2jpg16x16-win.png Binary files differnew file mode 100644 index 000000000..a821626c0 --- /dev/null +++ b/image/test/unit/image2jpg16x16-win.png diff --git a/image/test/unit/image2jpg16x16.png b/image/test/unit/image2jpg16x16.png Binary files differnew file mode 100644 index 000000000..5722223c2 --- /dev/null +++ b/image/test/unit/image2jpg16x16.png diff --git a/image/test/unit/image2jpg16x16cropped.jpg b/image/test/unit/image2jpg16x16cropped.jpg Binary files differnew file mode 100644 index 000000000..fca22cb30 --- /dev/null +++ b/image/test/unit/image2jpg16x16cropped.jpg diff --git a/image/test/unit/image2jpg16x16cropped2.jpg b/image/test/unit/image2jpg16x16cropped2.jpg Binary files differnew file mode 100644 index 000000000..e51d3530d --- /dev/null +++ b/image/test/unit/image2jpg16x16cropped2.jpg diff --git a/image/test/unit/image2jpg16x32cropped3.jpg b/image/test/unit/image2jpg16x32cropped3.jpg Binary files differnew file mode 100644 index 000000000..13a3d26e5 --- /dev/null +++ b/image/test/unit/image2jpg16x32cropped3.jpg diff --git a/image/test/unit/image2jpg16x32scaled.jpg b/image/test/unit/image2jpg16x32scaled.jpg Binary files differnew file mode 100644 index 000000000..6abef0f99 --- /dev/null +++ b/image/test/unit/image2jpg16x32scaled.jpg diff --git a/image/test/unit/image2jpg32x16cropped4.jpg b/image/test/unit/image2jpg32x16cropped4.jpg Binary files differnew file mode 100644 index 000000000..46f34918c --- /dev/null +++ b/image/test/unit/image2jpg32x16cropped4.jpg diff --git a/image/test/unit/image2jpg32x16scaled.jpg b/image/test/unit/image2jpg32x16scaled.jpg Binary files differnew file mode 100644 index 000000000..e302fbafd --- /dev/null +++ b/image/test/unit/image2jpg32x16scaled.jpg diff --git a/image/test/unit/image2jpg32x32-win.png b/image/test/unit/image2jpg32x32-win.png Binary files differnew file mode 100644 index 000000000..4d84df26a --- /dev/null +++ b/image/test/unit/image2jpg32x32-win.png diff --git a/image/test/unit/image2jpg32x32.jpg b/image/test/unit/image2jpg32x32.jpg Binary files differnew file mode 100644 index 000000000..cf9a10a37 --- /dev/null +++ b/image/test/unit/image2jpg32x32.jpg diff --git a/image/test/unit/image2jpg32x32.png b/image/test/unit/image2jpg32x32.png Binary files differnew file mode 100644 index 000000000..723008771 --- /dev/null +++ b/image/test/unit/image2jpg32x32.png diff --git a/image/test/unit/image3.ico b/image/test/unit/image3.ico Binary files differnew file mode 100644 index 000000000..d44438903 --- /dev/null +++ b/image/test/unit/image3.ico diff --git a/image/test/unit/image3ico16x16.png b/image/test/unit/image3ico16x16.png Binary files differnew file mode 100644 index 000000000..e9e520cb6 --- /dev/null +++ b/image/test/unit/image3ico16x16.png diff --git a/image/test/unit/image3ico32x32.png b/image/test/unit/image3ico32x32.png Binary files differnew file mode 100644 index 000000000..58497e3fa --- /dev/null +++ b/image/test/unit/image3ico32x32.png diff --git a/image/test/unit/image4.gif b/image/test/unit/image4.gif Binary files differnew file mode 100644 index 000000000..b1530bc81 --- /dev/null +++ b/image/test/unit/image4.gif diff --git a/image/test/unit/image4gif16x16bmp24bpp.ico b/image/test/unit/image4gif16x16bmp24bpp.ico Binary files differnew file mode 100644 index 000000000..890c81c27 --- /dev/null +++ b/image/test/unit/image4gif16x16bmp24bpp.ico diff --git a/image/test/unit/image4gif16x16bmp32bpp.ico b/image/test/unit/image4gif16x16bmp32bpp.ico Binary files differnew file mode 100644 index 000000000..f8a9eb8ad --- /dev/null +++ b/image/test/unit/image4gif16x16bmp32bpp.ico diff --git a/image/test/unit/image4gif32x32bmp24bpp.ico b/image/test/unit/image4gif32x32bmp24bpp.ico Binary files differnew file mode 100644 index 000000000..28092818d --- /dev/null +++ b/image/test/unit/image4gif32x32bmp24bpp.ico diff --git a/image/test/unit/image4gif32x32bmp32bpp.ico b/image/test/unit/image4gif32x32bmp32bpp.ico Binary files differnew file mode 100644 index 000000000..0e2d28c82 --- /dev/null +++ b/image/test/unit/image4gif32x32bmp32bpp.ico 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] |