bug 1245606 - Implement chrome.downloads.getFileInfo r=kmag
authorMark Striemer <mstriemer@mozilla.com>
Fri, 15 Apr 2016 16:08:16 -0500
changeset 331980 baa84a5b4465238deaf0eb1ba28ec7fd38154ef6
parent 331979 252417bdd33d5224a90a37043469592632e5cbc7
child 331981 71e2c45790dc01e3cc7ffd00f35bca771da21b84
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1245606
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1245606 - Implement chrome.downloads.getFileInfo r=kmag MozReview-Commit-ID: 6dfpctduYtp
toolkit/components/extensions/ext-downloads.js
toolkit/components/extensions/schemas/downloads.json
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
--- a/toolkit/components/extensions/ext-downloads.js
+++ b/toolkit/components/extensions/ext-downloads.js
@@ -588,16 +588,77 @@ extensions.registerSchemaAPI("downloads"
           return download.download.showContainingDirectory();
         }).then(() => {
           return true;
         }).catch(error => {
           return Promise.reject({message: error.message});
         });
       },
 
+      getFileIcon(downloadId, options) {
+        return DownloadMap.lazyInit().then(() => {
+          let size = options && options.size ? options.size : 32;
+          let download = DownloadMap.fromId(downloadId).download;
+          let pathPrefix = "";
+          let path;
+
+          if (download.succeeded) {
+            let file = FileUtils.File(download.target.path);
+            path = Services.io.newFileURI(file).spec;
+          } else {
+            path = OS.Path.basename(download.target.path);
+            pathPrefix = "//";
+          }
+
+          return new Promise((resolve, reject) => {
+            let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
+            chromeWebNav
+              .QueryInterface(Ci.nsIInterfaceRequestor)
+              .getInterface(Ci.nsIDocShell)
+              .createAboutBlankContentViewer(Services.scriptSecurityManager.getSystemPrincipal());
+
+            let img = chromeWebNav.document.createElement("img");
+            img.width = size;
+            img.height = size;
+
+            let handleLoad;
+            let handleError;
+            const cleanup = () => {
+              img.removeEventListener("load", handleLoad);
+              img.removeEventListener("error", handleError);
+              chromeWebNav.close();
+              chromeWebNav = null;
+            };
+
+            handleLoad = () => {
+              let canvas = chromeWebNav.document.createElement("canvas");
+              canvas.width = size;
+              canvas.height = size;
+              let context = canvas.getContext("2d");
+              context.drawImage(img, 0, 0, size, size);
+              let dataURL = canvas.toDataURL("image/png");
+              cleanup();
+              resolve(dataURL);
+            };
+
+            handleError = (error) => {
+              Cu.reportError(error);
+              cleanup();
+              reject(new Error("An unexpected error occurred"));
+            };
+
+            img.addEventListener("load", handleLoad);
+            img.addEventListener("error", handleError);
+            img.src = `moz-icon:${pathPrefix}${path}?size=${size}`;
+          });
+        }).catch((error) => {
+          return Promise.reject({message: error.message});
+        });
+      },
+
       // When we do setShelfEnabled(), check for additional "downloads.shelf" permission.
       // i.e.:
       // setShelfEnabled(enabled) {
       //   if (!extension.hasPermission("downloads.shelf")) {
       //     throw new context.cloneScope.Error("Permission denied because 'downloads.shelf' permission is missing.");
       //   }
       //   ...
       // }
--- a/toolkit/components/extensions/schemas/downloads.json
+++ b/toolkit/components/extensions/schemas/downloads.json
@@ -515,31 +515,33 @@
             "parameters": [],
             "type": "function"
           }
         ]
       },
       {
         "name": "getFileIcon",
         "type": "function",
-        "unsupported": true,
+        "async": "callback",
         "description": "Retrieve an icon for the specified download. For new downloads, file icons are available after the <a href='#event-onCreated'>onCreated</a> event has been received. The image returned by this function while a download is in progress may be different from the image returned after the download is complete. Icon retrieval is done by querying the underlying operating system or toolkit depending on the platform. The icon that is returned will therefore depend on a number of factors including state of the download, platform, registered file types and visual theme. If a file icon cannot be determined, <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain an error message.",
         "parameters": [
           {
             "description": "The identifier for the download.",
             "name": "downloadId",
             "type": "integer"
           },
           {
             "name": "options",
             "optional": true,
             "properties": {
               "size": {
                 "description": "The size of the icon.  The returned icon will be square with dimensions size * size pixels.  The default size for the icon is 32x32 pixels.",
                 "optional": true,
+                "minimum": 1,
+                "maximum": 127,
                 "type": "integer"
               }
             },
             "type": "object"
           },
           {
             "name": "callback",
             "parameters": [
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
@@ -737,16 +737,80 @@ add_task(function* test_erase() {
   ], {inorder: false});
   is(msg.status, "success", "received 2 onErased events");
 
   msg = yield runInExtension("search", {});
   is(msg.status, "success", "search succeded");
   is(msg.result.length, 0, "search found 0 downloads");
 });
 
+function loadImage(img, data) {
+  return new Promise((resolve) => {
+    let handle = () => {
+      img.removeEventListener("load", handle);
+      resolve();
+    };
+    img.addEventListener("load", handle);
+    img.src = data;
+  });
+}
+
+add_task(function* test_getFileIcon() {
+  let img = document.createElement("img");
+  let msg = yield runInExtension("download", {url: TXT_URL});
+  is(msg.status, "success", "download() succeeded");
+  const id = msg.result;
+
+  msg = yield runInExtension("getFileIcon", id);
+  is(msg.status, "success", "getFileIcon() succeeded");
+  yield loadImage(img, msg.result);
+  is(img.height, 32, "returns an icon with the right height");
+  is(img.width, 32, "returns an icon with the right width");
+
+  msg = yield runInExtension("waitForEvents", [
+    {type: "onCreated", data: {id, url: TXT_URL}},
+    {type: "onChanged"},
+  ]);
+  is(msg.status, "success", "got events");
+
+  msg = yield runInExtension("getFileIcon", id);
+  is(msg.status, "success", "getFileIcon() succeeded");
+  yield loadImage(img, msg.result);
+  is(img.height, 32, "returns an icon with the right height after download");
+  is(img.width, 32, "returns an icon with the right width after download");
+
+  msg = yield runInExtension("getFileIcon", id + 100);
+  is(msg.status, "error", "getFileIcon() failed");
+  ok(msg.errmsg.includes("Invalid download id"), "download id is invalid");
+
+  msg = yield runInExtension("getFileIcon", id, {size: 127});
+  is(msg.status, "success", "getFileIcon() succeeded");
+  yield loadImage(img, msg.result);
+  is(img.height, 127, "returns an icon with the right custom height");
+  is(img.width, 127, "returns an icon with the right custom width");
+
+  msg = yield runInExtension("getFileIcon", id, {size: 1});
+  is(msg.status, "success", "getFileIcon() succeeded");
+  yield loadImage(img, msg.result);
+  is(img.height, 1, "returns an icon with the right custom height");
+  is(img.width, 1, "returns an icon with the right custom width");
+
+  msg = yield runInExtension("getFileIcon", id, {size: "foo"});
+  is(msg.status, "error", "getFileIcon() fails");
+  ok(msg.errmsg.includes("Error processing size"), "size is not a number");
+
+  msg = yield runInExtension("getFileIcon", id, {size: 0});
+  is(msg.status, "error", "getFileIcon() fails");
+  ok(msg.errmsg.includes("Error processing size"), "size is too small");
+
+  msg = yield runInExtension("getFileIcon", id, {size: 128});
+  is(msg.status, "error", "getFileIcon() fails");
+  ok(msg.errmsg.includes("Error processing size"), "size is too big");
+});
+
 add_task(function* cleanup() {
   yield extension.unload();
 });
 
 </script>
 
 </body>
 </html>