Bug 1245599 - Implement chrome.downloads.onCreated r?kmag draft
authorAndrew Swan <aswan@mozilla.com>
Mon, 07 Mar 2016 16:54:08 -0800
changeset 338653 e705d9a61ec6da7a4d9c11e12698ee7fb11ac695
parent 338652 6009eb63149d0dcacfa659b6d84a6f1eaa898c06
child 515829 722b9b3c771d8e30413d3921695935a5c15cfed0
push id12549
push useraswan@mozilla.com
push dateWed, 09 Mar 2016 18:48:40 +0000
reviewerskmag
bugs1245599
milestone48.0a1
Bug 1245599 - Implement chrome.downloads.onCreated r?kmag MozReview-Commit-ID: Bimv9lY651A
toolkit/components/extensions/ext-downloads.js
toolkit/components/extensions/schemas/downloads.json
toolkit/components/extensions/test/mochitest/chrome.ini
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
@@ -136,17 +136,18 @@ const DownloadMap = {
 
   lazyInit() {
     if (this.loadPromise == null) {
       EventEmitter.decorate(this);
       this.loadPromise = Downloads.getList(Downloads.ALL).then(list => {
         let self = this;
         return list.addView({
           onDownloadAdded(download) {
-            self.newFromDownload(download, null);
+            const item = self.newFromDownload(download, null);
+            self.emit("create", item);
           },
 
           onDownloadRemoved(download) {
             const item = self.byDownload.get(download);
             if (item != null) {
               self.byDownload.delete(download);
               self.byId.delete(item.id);
             }
@@ -473,14 +474,27 @@ extensions.registerSchemaAPI("downloads"
         });
         return () => {
           registerPromise.then(() => {
             DownloadMap.off("change", handler);
           });
         };
       }).api(),
 
-      onCreated: ignoreEvent(context, "downloads.onCreated"),
+      onCreated: new SingletonEventManager(context, "downloads.onCreated", fire => {
+        const handler = (what, item) => {
+          runSafeSync(context, fire, item.serialize());
+        };
+        let registerPromise = DownloadMap.getDownloadList().then(() => {
+          DownloadMap.on("create", handler);
+        });
+        return () => {
+          registerPromise.then(() => {
+            DownloadMap.off("create", handler);
+          });
+        };
+      }).api(),
+
       onErased: ignoreEvent(context, "downloads.onErased"),
       onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
     },
   };
 });
--- a/toolkit/components/extensions/schemas/downloads.json
+++ b/toolkit/components/extensions/schemas/downloads.json
@@ -668,17 +668,16 @@
           }
         ]
       }
     ],
     "events": [
       {
         "description": "This event fires with the <a href='#type-DownloadItem'>DownloadItem</a> object when a download begins.",
         "name": "onCreated",
-        "unsupported": true,
         "parameters": [
           {
             "$ref": "DownloadItem",
             "name": "downloadItem"
           }
         ],
         "type": "function"
       },
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 skip-if = os == 'android'
 support-files =
   file_download.html
   file_download.txt
 
 [test_chrome_ext_downloads_download.html]
+[test_chrome_ext_downloads_misc.html]
 [test_chrome_ext_downloads_search.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+const {
+  interfaces: Ci,
+  utils: Cu,
+} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Downloads.jsm");
+
+const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
+const TXT_FILE = "file_download.txt";
+const TXT_URL = BASE + "/" + TXT_FILE;
+
+function backgroundScript() {
+  let events = [];
+  let eventWaiter = null;
+
+  browser.downloads.onCreated.addListener(data => {
+    events.push({type: "onCreated", data});
+    if (eventWaiter) {
+      eventWaiter();
+    }
+  });
+
+  browser.downloads.onChanged.addListener(data => {
+    events.push({type: "onChanged", data});
+    if (eventWaiter) {
+      eventWaiter();
+    }
+  });
+
+  function waitForEvents(expected) {
+    function compare(received, expected) {
+      if (typeof expected == "object" && expected != null) {
+        return Object.keys(expected).every(fld => compare(received[fld], expected[fld]));
+      }
+      return (received == expected);
+    }
+    return new Promise((resolve, reject) => {
+      function check() {
+        if (events.length < expected.length) {
+          return;
+        }
+        if (expected.length > events.length) {
+          reject(new Error(`Got ${events.length} events but only expected ${expected.length}`));
+        }
+
+        for (let i = 0; i < expected.length; i++) {
+          if (!compare(events[i], expected[i])) {
+            reject(new Error(`Mismatch in event ${i}, expecting ${JSON.stringify(expected[i])} but got ${JSON.stringify(events[i])}`));
+          }
+        }
+
+        events = [];
+        eventWaiter = null;
+        resolve();
+      }
+      eventWaiter = check;
+      check();
+    });
+  }
+
+  browser.test.onMessage.addListener(function(msg) {
+    // extension functions throw on bad arguments, we can remove the extra
+    // promise when bug 1250223 is fixed.
+    if (msg == "download.request") {
+      Promise.resolve().then(() => browser.downloads.download(arguments[1]))
+                       .then(id => {
+                         browser.test.sendMessage("download.done", {status: "success", id});
+                       })
+                       .catch(error => {
+                         browser.test.sendMessage("download.done", {status: "error", errmsg: error.message});
+                       });
+    } else if (msg == "waitForEvents.request") {
+      waitForEvents(arguments[1]).then(() => {
+        browser.test.sendMessage("waitForEvents.done", {status: "success"});
+      });
+    }
+  });
+
+  browser.test.sendMessage("ready");
+}
+
+let downloadDir;
+let extension;
+
+function clearDownloads(callback) {
+  return Downloads.getList(Downloads.ALL).then(list => {
+    return list.getAll().then(downloads => {
+      return Promise.all(downloads.map(download => list.remove(download)))
+                    .then(() => downloads);
+    });
+  });
+}
+
+function* setup(backgroundScript) {
+  const nsIFile = Ci.nsIFile;
+  downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
+  downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  info(`downloadDir ${downloadDir.path}`);
+
+  Services.prefs.setIntPref("browser.download.folderList", 2);
+  Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir);
+
+  SimpleTest.registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("browser.download.folderList");
+    Services.prefs.clearUserPref("browser.download.dir");
+    downloadDir.remove(true);
+    return clearDownloads();
+  });
+
+  yield clearDownloads().then(downloads => {
+    info(`removed ${downloads.length} pre-existing downloads from history`);
+  });
+
+  extension = ExtensionTestUtils.loadExtension({
+    background: `(${backgroundScript})()`,
+    manifest: {
+      permissions: ["downloads"],
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitMessage("ready");
+  info("extension started");
+}
+
+function runInExtension(what, args) {
+  extension.sendMessage(`${what}.request`, args);
+  return extension.awaitMessage(`${what}.done`);
+}
+
+
+add_task(function* test_misc() {
+  yield setup(backgroundScript);
+
+  let msg = yield runInExtension("download", {url: TXT_URL});
+  is(msg.status, "success", "downoad succeeded");
+  const id = msg.id;
+
+  msg = yield runInExtension("waitForEvents", [
+    {type: "onCreated", data: {id, url: TXT_URL}},
+    {
+      type: "onChanged",
+      data: {
+        id,
+        state: {
+          previous: null,
+          current: "in_progress",
+        },
+      },
+    },
+    {
+      type: "onChanged",
+      data: {
+        id,
+        state: {
+          previous: "in_progress",
+          current: "complete",
+        },
+      },
+    },
+  ]);
+  is(msg.status, "success", "got onCreated and onChanged events");
+
+  yield extension.unload();
+});
+
+</script>
+
+</body>
+</html>