Bug 1288885: Migrate downloads mochitests to xpcshell. r=aswan
☠☠ backed out by 68f0efbb38b4 ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Fri, 22 Jul 2016 18:49:50 -0700
changeset 349095 abd51b3d2b52da0669680153e5321b9b843d6cd2
parent 349094 d520f1245f5c43d6ca9d4c60684db344e183b0d0
child 349096 fca55647234643b6fde3b62b3337e491432ab0dc
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1288885
milestone50.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 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,24 +1,18 @@
 [DEFAULT]
 support-files =
   chrome_head.js
   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_trustworthy_origin.html]
 [test_chrome_ext_webnavigation_resolved_urls.html]
 skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.
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,31 +29,29 @@ 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_background_canvas.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_exporthelpers.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,22 +1,54 @@
 "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");
 
 ExtensionTestUtils.init(this);
+
+/**
+ * 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,79 @@
-<!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>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 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,52 +1,50 @@
-<!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="chrome_head.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";
 
 /* 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");
 
-const WINDOWS = (AppConstants.platform == "win");
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
 
-const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
+const WINDOWS = AppConstants.platform == "win";
+
+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];
@@ -114,29 +112,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",
@@ -155,65 +153,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");
@@ -222,25 +220,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,39 +1,79 @@
-<!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="chrome_head.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";
 
-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 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});
@@ -192,83 +232,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",
@@ -294,54 +335,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",
@@ -361,40 +402,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",
@@ -420,54 +461,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",
@@ -487,37 +528,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,
@@ -525,96 +566,95 @@ 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");
+  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",
@@ -634,25 +674,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",
@@ -678,144 +718,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,28 +1,18 @@
-<!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="chrome_head.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";
 
-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 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
 
@@ -86,104 +76,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,
@@ -222,29 +211,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.
@@ -394,17 +383,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");
@@ -412,13 +401,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,19 +1,25 @@
 [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_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_connect_no_receiver.js]