From e34df7b9a124ced471d36cea0354c76f3bdb67e2 Mon Sep 17 00:00:00 2001 From: Mark Striemer Date: Fri, 15 Apr 2016 16:08:16 -0500 Subject: [PATCH] bug 1245606 - Implement chrome.downloads.getFileInfo r=kmag MozReview-Commit-ID: 6dfpctduYtp --HG-- extra : transplant_source : %F4%B1%1AD%92%A7%B0.%26%83%AB%D8%D5%11%A0%A4%24%A7%82T --- .../components/extensions/ext-downloads.js | 61 ++++++++++++++++++ .../extensions/schemas/downloads.json | 4 +- .../test_chrome_ext_downloads_misc.html | 64 +++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/toolkit/components/extensions/ext-downloads.js b/toolkit/components/extensions/ext-downloads.js index 7e99ed881e36..446483cbf61e 100644 --- a/toolkit/components/extensions/ext-downloads.js +++ b/toolkit/components/extensions/ext-downloads.js @@ -593,6 +593,67 @@ extensions.registerSchemaAPI("downloads", "downloads", (extension, context) => { }); }, + 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) { diff --git a/toolkit/components/extensions/schemas/downloads.json b/toolkit/components/extensions/schemas/downloads.json index 613f0f461754..e0a075914c72 100644 --- a/toolkit/components/extensions/schemas/downloads.json +++ b/toolkit/components/extensions/schemas/downloads.json @@ -520,7 +520,7 @@ { "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 onCreated 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, chrome.extension.lastError will contain an error message.", "parameters": [ { @@ -535,6 +535,8 @@ "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" } }, diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html index 52b602bc48c3..187600ab0380 100644 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html @@ -742,6 +742,70 @@ add_task(function* test_erase() { 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(); });