Bug 1288885: Migrate downloads mochitests to xpcshell. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 22 Jul 2016 18:49:50 -0700
changeset 392108 10dc22514ed8e3ae6923fdfc3ca6bb271774d633
parent 392107 3fa6ba1ef4a82fde5af16e4e5f8cd216ee68e49d
child 392109 28801a9b961d997b5bc4809664ee0794ef195a78
push id23940
push usermaglione.k@gmail.com
push dateSat, 23 Jul 2016 02:07:34 +0000
reviewersaswan
bugs1288885
milestone50.0a1
Bug 1288885: Migrate downloads mochitests to xpcshell. r?aswan MozReview-Commit-ID: Z67uTNUcqD
toolkit/components/extensions/test/mochitest/chrome.ini
toolkit/components/extensions/test/mochitest/file_download.html
toolkit/components/extensions/test/mochitest/file_download.txt
toolkit/components/extensions/test/mochitest/interruptible.sjs
toolkit/components/extensions/test/mochitest/mochitest.ini
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_download.html
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_search.html
toolkit/components/extensions/test/mochitest/test_ext_downloads.html
toolkit/components/extensions/test/xpcshell/data/file_download.html
toolkit/components/extensions/test/xpcshell/data/file_download.txt
toolkit/components/extensions/test/xpcshell/head.js
toolkit/components/extensions/test/xpcshell/test_ext_downloads.js
toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js
toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
toolkit/components/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -1,23 +1,17 @@
 [DEFAULT]
 support-files =
   head.js
-  file_download.html
-  file_download.txt
-  interruptible.sjs
   file_sample.html
 
 [test_chrome_ext_background_debug_global.html]
 skip-if = (os == 'android') # android doesn't have devtools
 [test_chrome_ext_background_page.html]
 skip-if = (toolkit == 'android') # android doesn't have devtools
-[test_chrome_ext_downloads_download.html]
-[test_chrome_ext_downloads_misc.html]
-[test_chrome_ext_downloads_search.html]
 [test_chrome_ext_eventpage_warning.html]
 [test_chrome_ext_native_messaging.html]
 skip-if = os == "android"  # native messaging is not supported on android
 [test_chrome_ext_contentscript_unrecognizedprop_warning.html]
 skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.
 [test_chrome_ext_webnavigation_resolved_urls.html]
 skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.
 [test_chrome_native_messaging_paths.html]
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/interruptible.sjs
+++ /dev/null
@@ -1,38 +0,0 @@
-const TEST_DATA = "This is 31 bytes of sample data";
-const TOTAL_LEN = TEST_DATA.length;
-const PARTIAL_LEN = 15;
-
-// A handler to let us systematically test pausing/resuming/canceling
-// of downloads.  This target represents a small text file but a simple
-// GET will stall after sending part of the data, to give the test code
-// a chance to pause or do other operations on an in-progress download.
-// A resumed download (ie, a GET with a Range: header) will allow the
-// download to complete.
-function handleRequest(request, response) {
-  response.setHeader("Content-Type", "text/plain", false);
-
-  if (request.hasHeader("Range")) {
-    let start, end;
-    let matches = request.getHeader("Range")
-        .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
-    if (matches != null) {
-      start = matches[1] ? parseInt(matches[1], 10) : 0;
-      end = matches[2] ? pareInt(matchs[2], 10) : (TOTAL_LEN - 1);
-    }
-
-    if (end == undefined || end >= TOTAL_LEN) {
-      response.setStatusLine(request.httpVersion, 416, "Requested Range Not Satisfiable");
-      response.setHeader("Content-Range", `*/${TOTAL_LEN}`, false);
-      response.finish();
-      return;
-    }
-
-    response.setStatusLine(request.httpVersion, 206, "Partial Content");
-    response.setHeader("Content-Range", `${start}-${end}/${TOTAL_LEN}`, false);
-    response.write(TEST_DATA.slice(start, end + 1));
-  } else {
-    response.processAsync();
-    response.setHeader("Content-Length", `${TOTAL_LEN}`, false);
-    response.write(TEST_DATA.slice(0, PARTIAL_LEN));
-  }
-}
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -29,29 +29,27 @@ support-files =
   file_script_bad.js
   file_script_redirect.js
   file_script_xhr.js
   file_sample.html
   redirection.sjs
   file_privilege_escalation.html
   file_ext_test_api_injection.js
   file_permission_xhr.html
-  file_download.txt
 
 [test_ext_inIncognitoContext_window.html]
 skip-if = os == 'android' # Android does not currently support windows.
 [test_ext_geturl.html]
 [test_ext_content_security_policy.html]
 [test_ext_contentscript.html]
 skip-if = buildapp == 'b2g' # runat != document_idle is not supported.
 [test_ext_contentscript_api_injection.html]
 [test_ext_contentscript_create_iframe.html]
 [test_ext_contentscript_devtools_metadata.html]
 [test_ext_contentscript_css.html]
-[test_ext_downloads.html]
 [test_ext_exclude_include_globs.html]
 [test_ext_i18n_css.html]
 [test_ext_generate.html]
 [test_ext_notifications.html]
 [test_ext_permission_xhr.html]
 skip-if = buildapp == 'b2g' # JavaScript error: jar:remoteopenfile:///data/local/tmp/generated-extension.xpi!/content.js, line 46: NS_ERROR_ILLEGAL_VALUE:
 [test_ext_runtime_connect.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
rename from toolkit/components/extensions/test/mochitest/file_download.html
rename to toolkit/components/extensions/test/xpcshell/data/file_download.html
rename from toolkit/components/extensions/test/mochitest/file_download.txt
rename to toolkit/components/extensions/test/xpcshell/data/file_download.txt
--- a/toolkit/components/extensions/test/xpcshell/head.js
+++ b/toolkit/components/extensions/test/xpcshell/head.js
@@ -1,20 +1,52 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+/* exported createHttpServer */
+
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils",
                                   "resource://testing-common/ExtensionXPCShellUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+                                  "resource://testing-common/httpd.js");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
+
+/**
+ * Creates a new HttpServer for testing, and begins listening on the
+ * specified port. Automatically shuts down the server when the test
+ * unit ends.
+ *
+ * @param {integer} [port]
+ *        The port to listen on. If omitted, listen on a random
+ *        port. The latter is the preferred behavior.
+ *
+ * @returns {HttpServer}
+ */
+function createHttpServer(port = -1) {
+  let server = new HttpServer();
+  server.start(port);
+
+  do_register_cleanup(() => {
+    return new Promise(resolve => {
+      server.stop(resolve);
+    });
+  });
+
+  return server;
+}
rename from toolkit/components/extensions/test/mochitest/test_ext_downloads.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_downloads.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_downloads.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads.js
@@ -1,100 +1,83 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
 
-<script type="text/javascript">
-"use strict";
+/* globals browser */
+
+ExtensionTestUtils.init(this);
 
 add_task(function* test_downloads_api_namespace_and_permissions() {
   function backgroundScript() {
-    browser.test.assertTrue(!!chrome.downloads, "`downloads` API is present.");
-    browser.test.assertTrue(!!chrome.downloads.FilenameConflictAction,
+    browser.test.assertTrue(!!browser.downloads, "`downloads` API is present.");
+    browser.test.assertTrue(!!browser.downloads.FilenameConflictAction,
                             "`downloads.FilenameConflictAction` enum is present.");
-    browser.test.assertTrue(!!chrome.downloads.InterruptReason,
+    browser.test.assertTrue(!!browser.downloads.InterruptReason,
                             "`downloads.InterruptReason` enum is present.");
-    browser.test.assertTrue(!!chrome.downloads.DangerType,
+    browser.test.assertTrue(!!browser.downloads.DangerType,
                             "`downloads.DangerType` enum is present.");
-    browser.test.assertTrue(!!chrome.downloads.State,
+    browser.test.assertTrue(!!browser.downloads.State,
                             "`downloads.State` enum is present.");
     browser.test.notifyPass("downloads tests");
   }
 
   let extensionData = {
-    background: "(" + backgroundScript.toString() + ")()",
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads", "downloads.open", "downloads.shelf"],
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
-  info("extension loaded");
   yield extension.awaitFinish("downloads tests");
   yield extension.unload();
-  info("extension unloaded");
 });
 
 add_task(function* test_downloads_open_permission() {
   function backgroundScript() {
     browser.test.assertFalse("open" in browser.downloads,
                              "`downloads.open` permission is required.");
     browser.test.notifyPass("downloads tests");
   }
 
   let extensionData = {
-    background: "(" + backgroundScript.toString() + ")()",
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads"],
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
-  info("extension loaded");
   yield extension.awaitFinish("downloads tests");
   yield extension.unload();
-  info("extension unloaded");
 });
 
 add_task(function* test_downloads_open() {
   function backgroundScript() {
-    browser.downloads.open(10, () => {
-      let error = chrome.runtime.lastError;
-      browser.test.assertTrue(error, "An error exists.");
-      browser.test.assertTrue(
-        error.message === "Invalid download id 10",
-        `The error is informative. (${error.message})`);
+    browser.downloads.open(10).then(() => {
+      browser.test.fail("Expected an error");
+      browser.test.notifyFail("downloads tests");
+    }, error => {
+      browser.test.assertEq(error.message, "Invalid download id 10",
+                            "The error is informative.");
+
       browser.test.notifyPass("downloads tests");
     });
 
     // TODO: Once downloads.{pause,cancel,resume} lands (bug 1245602) test that this gives a good
     // error when called with an incompleted download.
   }
 
   let extensionData = {
-    background: "(" + backgroundScript.toString() + ")()",
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads", "downloads.open"],
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
-  info("extension loaded");
   yield extension.awaitFinish("downloads tests");
   yield extension.unload();
-  info("extension unloaded");
 });
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_download.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_download.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
@@ -1,57 +1,54 @@
-<!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">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const {
-  interfaces: Ci,
-  utils: Cu,
-} = Components;
+/* globals browser */
+
+ExtensionTestUtils.init(this);
 
 /* global OS */
 
 Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Downloads.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
+
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
 
-const WINDOWS = (AppConstants.platform == "win");
+const WINDOWS = AppConstants.platform == "win";
 
-const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
+const BASE = `http://localhost:${server.identity.primaryPort}/data`;
 const FILE_NAME = "file_download.txt";
 const FILE_URL = BASE + "/" + FILE_NAME;
 const FILE_NAME_UNIQUE = "file_download(1).txt";
 const FILE_LEN = 46;
 
 let downloadDir;
 
 function setup() {
   downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
   downloadDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-  info(`Using download directory ${downloadDir.path}`);
+  do_print(`Using download directory ${downloadDir.path}`);
 
   Services.prefs.setIntPref("browser.download.folderList", 2);
   Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, downloadDir);
 
-  SimpleTest.registerCleanupFunction(() => {
+  do_register_cleanup(() => {
     Services.prefs.clearUserPref("browser.download.folderList");
     Services.prefs.clearUserPref("browser.download.dir");
+
+    let entries = downloadDir.directoryEntries;
+    while (entries.hasMoreElements()) {
+      let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+      ok(false, `Leftover file ${entry.path} in download directory`);
+      entry.remove(false);
+    }
+
+    downloadDir.remove(false);
   });
 }
 
 function backgroundScript() {
   let blobUrl;
   browser.test.onMessage.addListener((msg, ...args) => {
     if (msg == "download.request") {
       let options = args[0];
@@ -119,29 +116,29 @@ add_task(function* test_downloads() {
 
   function download(options) {
     extension.sendMessage("download.request", options);
     return extension.awaitMessage("download.done");
   }
 
   function testDownload(options, localFile, expectedSize, description) {
     return download(options).then(msg => {
-      is(msg.status, "success", `downloads.download() works with ${description}`);
+      equal(msg.status, "success", `downloads.download() works with ${description}`);
       return waitForDownloads();
     }).then(() => {
       let localPath = downloadDir.clone();
       localPath.append(localFile);
-      is(localPath.fileSize, expectedSize, "Downloaded file has expected size");
+      equal(localPath.fileSize, expectedSize, "Downloaded file has expected size");
       localPath.remove(false);
     });
   }
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
-  info("extension started");
+  do_print("extension started");
 
   // Call download() with just the url property.
   yield testDownload({url: FILE_URL}, FILE_NAME, FILE_LEN, "just source");
 
   // Call download() with a filename property.
   yield testDownload({
     url: FILE_URL,
     filename: "newpath.txt",
@@ -160,65 +157,65 @@ add_task(function* test_downloads() {
   touch(FILE_NAME);
   yield testDownload({
     url: FILE_URL,
     conflictAction: "overwrite",
   }, FILE_NAME, FILE_LEN, "conflictAction=overwrite");
 
   // Try to download in invalid url
   yield download({url: "this is not a valid URL"}).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with invalid url");
+    equal(msg.status, "error", "downloads.download() fails with invalid url");
     ok(/not a valid URL/.test(msg.errmsg), "error message for invalid url is correct");
   });
 
   // Try to download to an empty path.
   yield download({
     url: FILE_URL,
     filename: "",
   }).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with empty filename");
-    is(msg.errmsg, "filename must not be empty", "error message for empty filename is correct");
+    equal(msg.status, "error", "downloads.download() fails with empty filename");
+    equal(msg.errmsg, "filename must not be empty", "error message for empty filename is correct");
   });
 
   // Try to download to an absolute path.
   const absolutePath = OS.Path.join(WINDOWS ? "\\tmp" : "/tmp", "file_download.txt");
   yield download({
     url: FILE_URL,
     filename: absolutePath,
   }).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with absolute filename");
-    is(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`);
+    equal(msg.status, "error", "downloads.download() fails with absolute filename");
+    equal(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`);
   });
 
   if (WINDOWS) {
     yield download({
       url: FILE_URL,
       filename: "C:\\file_download.txt",
     }).then(msg => {
-      is(msg.status, "error", "downloads.download() fails with absolute filename");
-      is(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct");
+      equal(msg.status, "error", "downloads.download() fails with absolute filename");
+      equal(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct");
     });
   }
 
   // Try to download to a relative path containing ..
   yield download({
     url: FILE_URL,
     filename: OS.Path.join("..", "file_download.txt"),
   }).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with back-references");
-    is(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
+    equal(msg.status, "error", "downloads.download() fails with back-references");
+    equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
   });
 
   // Try to download to a long relative path containing ..
   yield download({
     url: FILE_URL,
     filename: OS.Path.join("foo", "..", "..", "file_download.txt"),
   }).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with back-references");
-    is(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
+    equal(msg.status, "error", "downloads.download() fails with back-references");
+    equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
   });
 
   // Try to download a blob url
   const BLOB_STRING = "Hello, world";
   yield testDownload({
     blobme: [BLOB_STRING],
     filename: FILE_NAME,
   }, FILE_NAME, BLOB_STRING.length, "blob url");
@@ -227,25 +224,8 @@ add_task(function* test_downloads() {
   // Try to download a blob url without a given filename
   yield testDownload({
     blobme: [BLOB_STRING],
   }, "download", BLOB_STRING.length, "blob url with no filename");
   extension.sendMessage("killTheBlob");
 
   yield extension.unload();
 });
-
-// check for leftover files in the download directory
-add_task(function* () {
-  let entries = downloadDir.directoryEntries;
-  while (entries.hasMoreElements()) {
-    let entry = entries.getNext().QueryInterface(Ci.nsIFile);
-    ok(false, `Leftover file ${entry.path} in download directory`);
-    entry.remove(false);
-  }
-
-  downloadDir.remove(false);
-});
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js
@@ -1,44 +1,83 @@
-<!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">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const {
-  interfaces: Ci,
-  utils: Cu,
-} = Components;
+/* globals browser */
 
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
+ExtensionTestUtils.init(this);
+
 Cu.import("resource://gre/modules/Downloads.jsm");
 
-const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
+
+const ROOT = `http://localhost:${server.identity.primaryPort}`;
+const BASE = `${ROOT}/data`;
 const TXT_FILE = "file_download.txt";
 const TXT_URL = BASE + "/" + TXT_FILE;
 
 // Keep these in sync with code in interruptible.sjs
 const INT_PARTIAL_LEN = 15;
 const INT_TOTAL_LEN = 31;
 
+const TEST_DATA = "This is 31 bytes of sample data";
+const TOTAL_LEN = TEST_DATA.length;
+const PARTIAL_LEN = 15;
+
+// A handler to let us systematically test pausing/resuming/canceling
+// of downloads.  This target represents a small text file but a simple
+// GET will stall after sending part of the data, to give the test code
+// a chance to pause or do other operations on an in-progress download.
+// A resumed download (ie, a GET with a Range: header) will allow the
+// download to complete.
+function handleRequest(request, response) {
+  response.setHeader("Content-Type", "text/plain", false);
+
+  if (request.hasHeader("Range")) {
+    let start, end;
+    let matches = request.getHeader("Range")
+        .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+    if (matches != null) {
+      start = matches[1] ? parseInt(matches[1], 10) : 0;
+      end = matches[2] ? parseInt(matches[2], 10) : (TOTAL_LEN - 1);
+    }
+
+    if (end == undefined || end >= TOTAL_LEN) {
+      response.setStatusLine(request.httpVersion, 416, "Requested Range Not Satisfiable");
+      response.setHeader("Content-Range", `*/${TOTAL_LEN}`, false);
+      response.finish();
+      return;
+    }
+
+    response.setStatusLine(request.httpVersion, 206, "Partial Content");
+    response.setHeader("Content-Range", `${start}-${end}/${TOTAL_LEN}`, false);
+    response.write(TEST_DATA.slice(start, end + 1));
+  } else {
+    response.processAsync();
+    response.setHeader("Content-Length", `${TOTAL_LEN}`, false);
+    response.write(TEST_DATA.slice(0, PARTIAL_LEN));
+  }
+
+  do_register_cleanup(() => {
+    try {
+      response.finish();
+    } catch (e) {
+      // This will throw, but we don't care at this point.
+    }
+  });
+}
+
+server.registerPathHandler("/interruptible.html", handleRequest);
+
 let interruptibleCount = 0;
 function getInterruptibleUrl() {
   let n = interruptibleCount++;
-  return `${BASE}/interruptible.sjs?count=${n}`;
+  return `${ROOT}/interruptible.html?count=${n}`;
 }
 
 function backgroundScript() {
   let events = new Set();
   let eventWaiter = null;
 
   browser.downloads.onCreated.addListener(data => {
     events.add({type: "onCreated", data});
@@ -197,83 +236,84 @@ function waitForProgress(url, bytes) {
                     list.addView(view);
                   }));
 }
 
 add_task(function* setup() {
   const nsIFile = Ci.nsIFile;
   downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
   downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-  info(`downloadDir ${downloadDir.path}`);
+  do_print(`downloadDir ${downloadDir.path}`);
 
   Services.prefs.setIntPref("browser.download.folderList", 2);
   Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir);
 
-  SimpleTest.registerCleanupFunction(() => {
+  do_register_cleanup(() => {
     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`);
+    do_print(`removed ${downloads.length} pre-existing downloads from history`);
   });
 
   extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads"],
     },
   });
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
-  info("extension started");
 });
 
 add_task(function* test_events() {
   let msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id, url: TXT_URL}},
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
           current: "complete",
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onCreated and onChanged events");
+  equal(msg.status, "success", "got onCreated and onChanged events");
 });
 
 add_task(function* test_cancel() {
   let url = getInterruptibleUrl();
+  do_print(url);
   let msg = yield runInExtension("download", {url});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   let progressPromise = waitForProgress(url, INT_PARTIAL_LEN);
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id}},
   ]);
-  is(msg.status, "success", "got created and changed events");
+  equal(msg.status, "success", "got created and changed events");
 
   yield progressPromise;
-  info(`download reached ${INT_PARTIAL_LEN} bytes`);
+  do_print(`download reached ${INT_PARTIAL_LEN} bytes`);
 
   msg = yield runInExtension("cancel", id);
-  is(msg.status, "success", "cancel() succeeded");
+  equal(msg.status, "success", "cancel() succeeded");
 
   // This sequence of events is bogus (bug 1256243)
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         state: {
           previous: "in_progress",
@@ -299,54 +339,54 @@ add_task(function* test_cancel() {
         id,
         paused: {
           previous: true,
           current: false,
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onChanged events corresponding to cancel()");
+  equal(msg.status, "success", "got onChanged events corresponding to cancel()");
 
   msg = yield runInExtension("search", {error: "USER_CANCELED"});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].id, id, "download.id is correct");
-  is(msg.result[0].state, "interrupted", "download.state is correct");
-  is(msg.result[0].paused, false, "download.paused is correct");
-  is(msg.result[0].canResume, false, "download.canResume is correct");
-  is(msg.result[0].error, "USER_CANCELED", "download.error is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, false, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].id, id, "download.id is correct");
+  equal(msg.result[0].state, "interrupted", "download.state is correct");
+  equal(msg.result[0].paused, false, "download.paused is correct");
+  equal(msg.result[0].canResume, false, "download.canResume is correct");
+  equal(msg.result[0].error, "USER_CANCELED", "download.error is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, false, "download.exists is correct");
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "error", "cannot pause a canceled download");
+  equal(msg.status, "error", "cannot pause a canceled download");
 
   msg = yield runInExtension("resume", id);
-  is(msg.status, "error", "cannot resume a canceled download");
+  equal(msg.status, "error", "cannot resume a canceled download");
 });
 
 add_task(function* test_pauseresume() {
   let url = getInterruptibleUrl();
   let msg = yield runInExtension("download", {url});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   let progressPromise = waitForProgress(url, INT_PARTIAL_LEN);
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id}},
   ]);
-  is(msg.status, "success", "got created and changed events");
+  equal(msg.status, "success", "got created and changed events");
 
   yield progressPromise;
-  info(`download reached ${INT_PARTIAL_LEN} bytes`);
+  do_print(`download reached ${INT_PARTIAL_LEN} bytes`);
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "success", "pause() succeeded");
+  equal(msg.status, "success", "pause() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
@@ -366,40 +406,40 @@ add_task(function* test_pauseresume() {
       data: {
         id,
         error: {
           previous: null,
           current: "USER_CANCELED",
         },
       },
     }]);
-  is(msg.status, "success", "got onChanged event corresponding to pause");
+  equal(msg.status, "success", "got onChanged event corresponding to pause");
 
   msg = yield runInExtension("search", {paused: true});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].id, id, "download.id is correct");
-  is(msg.result[0].state, "interrupted", "download.state is correct");
-  is(msg.result[0].paused, true, "download.paused is correct");
-  is(msg.result[0].canResume, true, "download.canResume is correct");
-  is(msg.result[0].error, "USER_CANCELED", "download.error is correct");
-  is(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, false, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].id, id, "download.id is correct");
+  equal(msg.result[0].state, "interrupted", "download.state is correct");
+  equal(msg.result[0].paused, true, "download.paused is correct");
+  equal(msg.result[0].canResume, true, "download.canResume is correct");
+  equal(msg.result[0].error, "USER_CANCELED", "download.error is correct");
+  equal(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, false, "download.exists is correct");
 
   msg = yield runInExtension("search", {error: "USER_CANCELED"});
-  is(msg.status, "success", "search() succeeded");
+  equal(msg.status, "success", "search() succeeded");
   let found = msg.result.filter(item => item.id == id);
-  is(found.length, 1, "search() by error found the paused download");
+  equal(found.length, 1, "search() by error found the paused download");
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "error", "cannot pause an already paused download");
+  equal(msg.status, "error", "cannot pause an already paused download");
 
   msg = yield runInExtension("resume", id);
-  is(msg.status, "success", "resume() succeeded");
+  equal(msg.status, "success", "resume() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "interrupted",
@@ -425,54 +465,54 @@ add_task(function* test_pauseresume() {
         id,
         state: {
           previous: "in_progress",
           current: "complete",
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onChanged events for resume and complete");
+  equal(msg.status, "success", "got onChanged events for resume and complete");
 
   msg = yield runInExtension("search", {id});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].state, "complete", "download.state is correct");
-  is(msg.result[0].paused, false, "download.paused is correct");
-  is(msg.result[0].canResume, false, "download.canResume is correct");
-  is(msg.result[0].error, null, "download.error is correct");
-  is(msg.result[0].bytesReceived, INT_TOTAL_LEN, "download.bytesReceived is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, true, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].state, "complete", "download.state is correct");
+  equal(msg.result[0].paused, false, "download.paused is correct");
+  equal(msg.result[0].canResume, false, "download.canResume is correct");
+  equal(msg.result[0].error, null, "download.error is correct");
+  equal(msg.result[0].bytesReceived, INT_TOTAL_LEN, "download.bytesReceived is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, true, "download.exists is correct");
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "error", "cannot pause a completed download");
+  equal(msg.status, "error", "cannot pause a completed download");
 
   msg = yield runInExtension("resume", id);
-  is(msg.status, "error", "cannot resume a completed download");
+  equal(msg.status, "error", "cannot resume a completed download");
 });
 
 add_task(function* test_pausecancel() {
   let url = getInterruptibleUrl();
   let msg = yield runInExtension("download", {url});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   let progressPromise = waitForProgress(url, INT_PARTIAL_LEN);
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id}},
   ]);
-  is(msg.status, "success", "got created and changed events");
+  equal(msg.status, "success", "got created and changed events");
 
   yield progressPromise;
-  info(`download reached ${INT_PARTIAL_LEN} bytes`);
+  do_print(`download reached ${INT_PARTIAL_LEN} bytes`);
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "success", "pause() succeeded");
+  equal(msg.status, "success", "pause() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
@@ -492,37 +532,37 @@ add_task(function* test_pausecancel() {
       data: {
         id,
         error: {
           previous: null,
           current: "USER_CANCELED",
         },
       },
     }]);
-  is(msg.status, "success", "got onChanged event corresponding to pause");
+  equal(msg.status, "success", "got onChanged event corresponding to pause");
 
   msg = yield runInExtension("search", {paused: true});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].id, id, "download.id is correct");
-  is(msg.result[0].state, "interrupted", "download.state is correct");
-  is(msg.result[0].paused, true, "download.paused is correct");
-  is(msg.result[0].canResume, true, "download.canResume is correct");
-  is(msg.result[0].error, "USER_CANCELED", "download.error is correct");
-  is(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, false, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].id, id, "download.id is correct");
+  equal(msg.result[0].state, "interrupted", "download.state is correct");
+  equal(msg.result[0].paused, true, "download.paused is correct");
+  equal(msg.result[0].canResume, true, "download.canResume is correct");
+  equal(msg.result[0].error, "USER_CANCELED", "download.error is correct");
+  equal(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, false, "download.exists is correct");
 
   msg = yield runInExtension("search", {error: "USER_CANCELED"});
-  is(msg.status, "success", "search() succeeded");
+  equal(msg.status, "success", "search() succeeded");
   let found = msg.result.filter(item => item.id == id);
-  is(found.length, 1, "search() by error found the paused download");
+  equal(found.length, 1, "search() by error found the paused download");
 
   msg = yield runInExtension("cancel", id);
-  is(msg.status, "success", "cancel() succeeded");
+  equal(msg.status, "success", "cancel() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         paused: {
           previous: true,
@@ -530,96 +570,96 @@ add_task(function* test_pausecancel() {
         },
         canResume: {
           previous: true,
           current: false,
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onChanged event for cancel");
+  equal(msg.status, "success", "got onChanged event for cancel");
 
   msg = yield runInExtension("search", {id});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].state, "interrupted", "download.state is correct");
-  is(msg.result[0].paused, false, "download.paused is correct");
-  is(msg.result[0].canResume, false, "download.canResume is correct");
-  is(msg.result[0].error, "USER_CANCELED", "download.error is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, false, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].state, "interrupted", "download.state is correct");
+  equal(msg.result[0].paused, false, "download.paused is correct");
+  equal(msg.result[0].canResume, false, "download.canResume is correct");
+  equal(msg.result[0].error, "USER_CANCELED", "download.error is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, false, "download.exists is correct");
 });
 
 add_task(function* test_pause_resume_cancel_badargs() {
   let BAD_ID = 1000;
 
   let msg = yield runInExtension("pause", BAD_ID);
-  info(JSON.stringify(msg));
-  is(msg.status, "error", "pause() failed with a bad download id");
+  do_print(JSON.stringify(msg));
+  equal(msg.status, "error", "pause() failed with a bad download id");
   ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive");
 
   msg = yield runInExtension("resume", BAD_ID);
-  is(msg.status, "error", "resume() failed with a bad download id");
+  equal(msg.status, "error", "resume() failed with a bad download id");
   ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive");
 
   msg = yield runInExtension("cancel", BAD_ID);
-  is(msg.status, "error", "cancel() failed with a bad download id");
+  equal(msg.status, "error", "cancel() failed with a bad download id");
   ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive");
 });
 
 add_task(function* test_file_removal() {
   let msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id, url: TXT_URL}},
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
           current: "complete",
         },
       },
     },
   ]);
 
-  is(msg.status, "success", "got onCreated and onChanged events");
+  equal(msg.status, "success", "got onCreated and onChanged events");
 
   msg = yield runInExtension("removeFile", id);
-  is(msg.status, "success", "removeFile() succeeded");
+  equal(msg.status, "success", "removeFile() succeeded");
 
   msg = yield runInExtension("removeFile", id);
-  is(msg.status, "error", "removeFile() fails since the file was already removed.");
+  equal(msg.status, "error", "removeFile() fails since the file was already removed.");
   ok(/file doesn't exist/.test(msg.errmsg), "removeFile() failed on removed file.");
 
   msg = yield runInExtension("removeFile", 1000);
   ok(/Invalid download id/.test(msg.errmsg), "removeFile() failed due to non-existent id");
 });
 
 add_task(function* test_removal_of_incomplete_download() {
   let url = getInterruptibleUrl();
   let msg = yield runInExtension("download", {url});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   let progressPromise = waitForProgress(url, INT_PARTIAL_LEN);
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id}},
   ]);
-  is(msg.status, "success", "got created and changed events");
+  equal(msg.status, "success", "got created and changed events");
 
   yield progressPromise;
-  info(`download reached ${INT_PARTIAL_LEN} bytes`);
+  do_print(`download reached ${INT_PARTIAL_LEN} bytes`);
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "success", "pause() succeeded");
+  equal(msg.status, "success", "pause() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
@@ -639,25 +679,25 @@ add_task(function* test_removal_of_incom
       data: {
         id,
         error: {
           previous: null,
           current: "USER_CANCELED",
         },
       },
     }]);
-  is(msg.status, "success", "got onChanged event corresponding to pause");
+  equal(msg.status, "success", "got onChanged event corresponding to pause");
 
   msg = yield runInExtension("removeFile", id);
-  is(msg.status, "error", "removeFile() on paused download failed");
+  equal(msg.status, "error", "removeFile() on paused download failed");
 
   ok(/Cannot remove incomplete download/.test(msg.errmsg), "removeFile() failed due to download being incomplete");
 
   msg = yield runInExtension("resume", id);
-  is(msg.status, "success", "resume() succeeded");
+  equal(msg.status, "success", "resume() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "interrupted",
@@ -683,144 +723,145 @@ add_task(function* test_removal_of_incom
         id,
         state: {
           previous: "in_progress",
           current: "complete",
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onChanged events for resume and complete");
+  equal(msg.status, "success", "got onChanged events for resume and complete");
 
   msg = yield runInExtension("removeFile", id);
-  is(msg.status, "success", "removeFile() succeeded following completion of resumed download.");
+  equal(msg.status, "success", "removeFile() succeeded following completion of resumed download.");
 });
 
 // Test erase().  We don't do elaborate testing of the query handling
 // since it uses the exact same engine as search() which is tested
 // more thoroughly in test_chrome_ext_downloads_search.html
 add_task(function* test_erase() {
   yield clearDownloads();
 
   yield runInExtension("clearEvents");
 
   function* download() {
     let msg = yield runInExtension("download", {url: TXT_URL});
-    is(msg.status, "success", "download succeeded");
+    equal(msg.status, "success", "download succeeded");
     let id = msg.result;
 
     msg = yield runInExtension("waitForEvents", [{
       type: "onChanged", data: {id, state: {current: "complete"}},
     }], {exact: false});
-    is(msg.status, "success", "download finished");
+    equal(msg.status, "success", "download finished");
 
     return id;
   }
 
   let ids = {};
   ids.dl1 = yield download();
   ids.dl2 = yield download();
   ids.dl3 = yield download();
 
   let msg = yield runInExtension("search", {});
-  is(msg.status, "success", "search succeded");
-  is(msg.result.length, 3, "search found 3 downloads");
+  equal(msg.status, "success", "search succeded");
+  equal(msg.result.length, 3, "search found 3 downloads");
 
   msg = yield runInExtension("clearEvents");
 
   msg = yield runInExtension("erase", {id: ids.dl1});
-  is(msg.status, "success", "erase by id succeeded");
+  equal(msg.status, "success", "erase by id succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onErased", data: ids.dl1},
   ]);
-  is(msg.status, "success", "received onErased event");
+  equal(msg.status, "success", "received onErased event");
 
   msg = yield runInExtension("search", {});
-  is(msg.status, "success", "search succeded");
-  is(msg.result.length, 2, "search found 2 downloads");
+  equal(msg.status, "success", "search succeded");
+  equal(msg.result.length, 2, "search found 2 downloads");
 
   msg = yield runInExtension("erase", {});
-  is(msg.status, "success", "erase everything succeeded");
+  equal(msg.status, "success", "erase everything succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onErased", data: ids.dl2},
     {type: "onErased", data: ids.dl3},
   ], {inorder: false});
-  is(msg.status, "success", "received 2 onErased events");
+  equal(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");
+  equal(msg.status, "success", "search succeded");
+  equal(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;
+    img.onload = resolve;
   });
 }
 
 add_task(function* test_getFileIcon() {
-  let img = document.createElement("img");
+  let webNav = Services.appShell.createWindowlessBrowser(false);
+  let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIDocShell);
+
+  let system = Services.scriptSecurityManager.getSystemPrincipal();
+  docShell.createAboutBlankContentViewer(system);
+
+  let img = webNav.document.createElement("img");
+
   let msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   msg = yield runInExtension("getFileIcon", id);
-  is(msg.status, "success", "getFileIcon() succeeded");
+  equal(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");
+  equal(img.height, 32, "returns an icon with the right height");
+  equal(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");
+  equal(msg.status, "success", "got events");
 
   msg = yield runInExtension("getFileIcon", id);
-  is(msg.status, "success", "getFileIcon() succeeded");
+  equal(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");
+  equal(img.height, 32, "returns an icon with the right height after download");
+  equal(img.width, 32, "returns an icon with the right width after download");
 
   msg = yield runInExtension("getFileIcon", id + 100);
-  is(msg.status, "error", "getFileIcon() failed");
+  equal(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");
+  equal(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");
+  equal(img.height, 127, "returns an icon with the right custom height");
+  equal(img.width, 127, "returns an icon with the right custom width");
 
   msg = yield runInExtension("getFileIcon", id, {size: 1});
-  is(msg.status, "success", "getFileIcon() succeeded");
+  equal(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");
+  equal(img.height, 1, "returns an icon with the right custom height");
+  equal(img.width, 1, "returns an icon with the right custom width");
 
   msg = yield runInExtension("getFileIcon", id, {size: "foo"});
-  is(msg.status, "error", "getFileIcon() fails");
+  equal(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");
+  equal(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");
+  equal(msg.status, "error", "getFileIcon() fails");
   ok(msg.errmsg.includes("Error processing size"), "size is too big");
+
+  webNav.close();
 });
 
 add_task(function* cleanup() {
   yield extension.unload();
 });
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_search.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_search.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
@@ -1,33 +1,22 @@
-<!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">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const {
-  interfaces: Ci,
-  utils: Cu,
-} = Components;
+/* globals browser */
 
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
+ExtensionTestUtils.init(this);
+
 Cu.import("resource://gre/modules/Downloads.jsm");
 
-const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
+
+const BASE = `http://localhost:${server.identity.primaryPort}/data`;
 const TXT_FILE = "file_download.txt";
 const TXT_URL = BASE + "/" + TXT_FILE;
 const TXT_LEN = 46;
 const HTML_FILE = "file_download.html";
 const HTML_URL = BASE + "/" + HTML_FILE;
 const HTML_LEN = 117;
 const BIG_LEN = 1000;  // something bigger both TXT_LEN and HTML_LEN
 
@@ -91,104 +80,103 @@ function clearDownloads(callback) {
     });
   });
 }
 
 add_task(function* test_search() {
   const nsIFile = Ci.nsIFile;
   let downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
   downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-  info(`downloadDir ${downloadDir.path}`);
+  do_print(`downloadDir ${downloadDir.path}`);
 
   function downloadPath(filename) {
     let path = downloadDir.clone();
     path.append(filename);
     return path.path;
   }
 
   Services.prefs.setIntPref("browser.download.folderList", 2);
   Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir);
 
-  SimpleTest.registerCleanupFunction(() => {
+  do_register_cleanup(() => {
     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`);
+    do_print(`removed ${downloads.length} pre-existing downloads from history`);
   });
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads"],
     },
   });
 
   function download(options) {
     extension.sendMessage("download.request", options);
     return extension.awaitMessage("download.done").then(result => {
       let promise;
       if (result.status == "success") {
-        info(`wait for onChanged event to indicate ${result.id} is complete`);
+        do_print(`wait for onChanged event to indicate ${result.id} is complete`);
         extension.sendMessage("waitForComplete.request", result.id);
         promise = extension.awaitMessage("waitForComplete.done");
       } else {
         promise = Promise.resolve();
       }
       return promise.then(() => result);
     });
   }
 
   function search(query) {
     extension.sendMessage("search.request", query);
     return extension.awaitMessage("search.done");
   }
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
-  info("extension started");
 
   // Do some downloads...
   const time1 = new Date();
 
   let downloadIds = {};
   let msg = yield download({url: TXT_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   downloadIds.txt1 = msg.id;
 
   const TXT_FILE2 = "NewFile.txt";
   msg = yield download({url: TXT_URL, filename: TXT_FILE2});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   downloadIds.txt2 = msg.id;
 
   const time2 = new Date();
 
   msg = yield download({url: HTML_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   downloadIds.html1 = msg.id;
 
   const HTML_FILE2 = "renamed.html";
   msg = yield download({url: HTML_URL, filename: HTML_FILE2});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   downloadIds.html2 = msg.id;
 
   const time3 = new Date();
 
   // Search for each individual download and check
   // the corresponding DownloadItem.
   function* checkDownloadItem(id, expect) {
     let msg = yield search({id});
-    is(msg.status, "success", "search() succeeded");
-    is(msg.downloads.length, 1, "search() found exactly 1 download");
+    equal(msg.status, "success", "search() succeeded");
+    equal(msg.downloads.length, 1, "search() found exactly 1 download");
 
     Object.keys(expect).forEach(function(field) {
-      is(msg.downloads[0][field], expect[field], `DownloadItem.${field} is correct"`);
+      equal(msg.downloads[0][field], expect[field], `DownloadItem.${field} is correct"`);
     });
   }
   yield checkDownloadItem(downloadIds.txt1, {
     url: TXT_URL,
     filename: downloadPath(TXT_FILE),
     mime: "text/plain",
     state: "complete",
     bytesReceived: TXT_LEN,
@@ -227,29 +215,29 @@ add_task(function* test_search() {
     bytesReceived: HTML_LEN,
     totalBytes: HTML_LEN,
     fileSize: HTML_LEN,
     exists: true,
   });
 
   function* checkSearch(query, expected, description, exact) {
     let msg = yield search(query);
-    is(msg.status, "success", "search() succeeded");
-    is(msg.downloads.length, expected.length, `search() for ${description} found exactly ${expected.length} downloads`);
+    equal(msg.status, "success", "search() succeeded");
+    equal(msg.downloads.length, expected.length, `search() for ${description} found exactly ${expected.length} downloads`);
 
     let receivedIds = msg.downloads.map(item => item.id);
     if (exact) {
       receivedIds.forEach((id, idx) => {
-        is(id, downloadIds[expected[idx]], `search() for ${description} returned ${expected[idx]} in position ${idx}`);
+        equal(id, downloadIds[expected[idx]], `search() for ${description} returned ${expected[idx]} in position ${idx}`);
       });
     } else {
       Object.keys(downloadIds).forEach(key => {
         const id = downloadIds[key];
         const thisExpected = expected.includes(key);
-        is(receivedIds.includes(id), thisExpected,
+        equal(receivedIds.includes(id), thisExpected,
            `search() for ${description} ${thisExpected ? "includes" : "does not include"} ${key}`);
       });
     }
   }
 
   // Check that search with an invalid id returns nothing.
   // NB: for now ids are not persistent and we start numbering them at 1
   //     so a sufficiently large number will be unused.
@@ -399,17 +387,17 @@ add_task(function* test_search() {
   yield checkSearch({orderBy: ["url", "-startTime"]}, ["html2", "html1", "txt2", "txt1"], "orderBy with multiple fields", true);
 
   // Check orderBy with limit.
   yield checkSearch({orderBy: ["url"], limit: 1}, ["html1"], "orderBy with limit", true);
 
   // Check bad arguments.
   function* checkBadSearch(query, pattern, description) {
     let msg = yield search(query);
-    is(msg.status, "error", "search() failed");
+    equal(msg.status, "error", "search() failed");
     ok(pattern.test(msg.errmsg), `error message for ${description} was correct (${msg.errmsg}).`);
   }
 
   yield checkBadSearch("myquery", /Incorrect argument type/, "query is not an object");
   yield checkBadSearch({bogus: "boo"}, /Unexpected property/, "query contains an unknown field");
   yield checkBadSearch({query: "query string"}, /Expected array/, "query.query is a string");
   yield checkBadSearch({startedBefore: "i am not a time"}, /Type error/, "query.startedBefore is not a valid time");
   yield checkBadSearch({startedAfter: "i am not a time"}, /Type error/, "query.startedAfter is not a valid time");
@@ -417,13 +405,8 @@ add_task(function* test_search() {
   yield checkBadSearch({endedAfter: "i am not a time"}, /Type error/, "query.endedAfter is not a valid time");
   yield checkBadSearch({urlRegex: "["}, /Invalid urlRegex/, "query.urlRegexp is not a valid regular expression");
   yield checkBadSearch({filenameRegex: "["}, /Invalid filenameRegex/, "query.filenameRegexp is not a valid regular expression");
   yield checkBadSearch({orderBy: "startTime"}, /Expected array/, "query.orderBy is not an array");
   yield checkBadSearch({orderBy: ["bogus"]}, /Invalid orderBy field/, "query.orderBy references a non-existent field");
 
   yield extension.unload();
 });
-
-</script>
-
-</body>
-</html>
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -1,21 +1,27 @@
 [DEFAULT]
 head = head.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'gonk' || appname == "thunderbird"
+support-files =
+  data/**
 
 [test_csp_custom_policies.js]
 [test_csp_validator.js]
 [test_ext_alarms.js]
 [test_ext_background_runtime_connect_params.js]
 [test_ext_bookmarks.js]
 skip-if = (os == 'android' || buildapp == 'b2g') # unimplemented api. Bug 1258975 on android.
 [test_ext_contexts.js]
+[test_ext_downloads.js]
+[test_ext_downloads_download.js]
+[test_ext_downloads_misc.js]
+[test_ext_downloads_search.js]
 [test_ext_extension.js]
 [test_ext_idle.js]
 [test_ext_json_parser.js]
 [test_ext_localStorage.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
 [test_ext_onmessage_removelistener.js]
 [test_ext_runtime_getPlatformInfo.js]