Bug 1447903: Part 22 - Update test_install.js to use restartless extensions. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 22 Mar 2018 21:22:50 -0700
changeset 771494 5966d50d1d4116f0db9bcd1f684226a7ec378fc3
parent 771493 914983967ad3eb00790a73ee25cb7273309604e3
child 771496 1a98ffcd6be0728e97b355522f358c75ca606bc4
push id103689
push usermaglione.k@gmail.com
push dateFri, 23 Mar 2018 04:23:40 +0000
reviewersaswan
bugs1447903
milestone61.0a1
Bug 1447903: Part 22 - Update test_install.js to use restartless extensions. r?aswan MozReview-Commit-ID: 7CjSbQ7gCDZ
toolkit/mozapps/extensions/test/xpcshell/test_install.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js
@@ -0,0 +1,1268 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+ChromeUtils.import("resource://testing-common/httpd.js");
+
+var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
+var gInstallDate;
+
+// This verifies that add-ons can be installed from XPI files
+// install.rdf size, icon.png, icon64.png size
+const ADDON1_SIZE = 612;
+
+const ADDONS = {
+  test_install1: {
+    "install.rdf": {
+      id: "addon1@tests.mozilla.org",
+      version: "1.0",
+      name: "Test 1",
+      description: "Test Description",
+      bootstrap: true,
+
+      targetApplications: [{
+          id: "xpcshell@tests.mozilla.org",
+          minVersion: "1",
+          maxVersion: "1"}],
+    },
+    "icon.png": "Fake icon image",
+    "icon64.png": "Fake icon image",
+  },
+  test_install2_1: {
+    "install.rdf": {
+      id: "addon2@tests.mozilla.org",
+      version: "2.0",
+      name: "Real Test 2",
+      description: "Test Description",
+      bootstrap: true,
+
+      targetApplications: [{
+          id: "xpcshell@tests.mozilla.org",
+          minVersion: "1",
+          maxVersion: "1"}],
+
+    },
+    "icon.png": "Fake icon image",
+  },
+  test_install2_2: {
+    "install.rdf": {
+      id: "addon2@tests.mozilla.org",
+      version: "3.0",
+      name: "Real Test 3",
+      description: "Test Description",
+      bootstrap: true,
+
+      targetApplications: [{
+          id: "xpcshell@tests.mozilla.org",
+          minVersion: "1",
+          maxVersion: "1"}],
+    },
+  },
+  test_install3: {
+    "install.rdf": {
+      id: "addon3@tests.mozilla.org",
+      version: "1.0",
+      name: "Real Test 4",
+      description: "Test Description",
+      bootstrap: true,
+
+      updateURL: "http://example.com/data/test_install.rdf",
+
+      targetApplications: [{
+          id: "xpcshell@tests.mozilla.org",
+          minVersion: "0",
+          maxVersion: "0"}],
+    },
+  },
+  test_install6: {
+    "install.rdf": {
+      id: "addon6@tests.mozilla.org",
+      version: "1.0",
+      name: "Addon Test 6",
+      description: "Test Description",
+      bootstrap: true,
+
+      targetApplications: [{
+          id: "xpcshell@tests.mozilla.org",
+          minVersion: "1",
+          maxVersion: "1"}],
+    },
+  },
+  test_install7: {
+    "install.rdf": {
+      type: "32",
+    },
+  },
+  test_install8: {
+    "install.rdf": {
+      type: "32",
+    },
+  },
+};
+
+const XPIS = {};
+
+for (let [name, files] of Object.entries(ADDONS)) {
+  XPIS[name] = AddonTestUtils.createTempXPIFile(files);
+  testserver.registerFile(`/addons/${name}.xpi`, XPIS[name]);
+}
+
+const ZipReader = Components.Constructor(
+  "@mozilla.org/libjar/zip-reader;1", "nsIZipReader",
+  "open");
+
+// The test extension uses an insecure update url.
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+add_task(async function setup() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+
+  await promiseStartupManager();
+
+  // Make sure we only register once despite multiple calls
+  AddonManager.addInstallListener(InstallListener);
+  AddonManager.addAddonListener(AddonListener);
+  AddonManager.addInstallListener(InstallListener);
+  AddonManager.addAddonListener(AddonListener);
+
+  // Create and configure the HTTP server.
+  testserver.registerDirectory("/data/", do_get_file("data"));
+  testserver.registerPathHandler("/redirect", function(aRequest, aResponse) {
+    aResponse.setStatusLine(null, 301, "Moved Permanently");
+    let url = aRequest.host + ":" + aRequest.port + aRequest.queryString;
+    aResponse.setHeader("Location", "http://" + url);
+  });
+  gPort = testserver.identity.primaryPort;
+});
+
+// Checks that an install from a local file proceeds as expected
+add_task(async function test_1() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let install = await AddonManager.getInstallForFile(XPIS.test_install1);
+  ensure_test_completed();
+
+  notEqual(install, null);
+  equal(install.type, "extension");
+  equal(install.version, "1.0");
+  equal(install.name, "Test 1");
+  equal(install.state, AddonManager.STATE_DOWNLOADED);
+  ok(install.addon.hasResource("install.rdf"));
+  notEqual(install.addon.syncGUID, null);
+  equal(install.addon.install, install);
+  equal(install.addon.size, ADDON1_SIZE);
+  let file = XPIS.test_install1;
+  let uri = Services.io.newFileURI(file).spec;
+  equal(install.addon.getResourceURI("install.rdf").spec, "jar:" + uri + "!/install.rdf");
+  equal(install.addon.iconURL, "jar:" + uri + "!/icon.png");
+  equal(install.addon.icon64URL, "jar:" + uri + "!/icon64.png");
+  equal(install.iconURL, null);
+
+  equal(install.sourceURI.spec, uri);
+  equal(install.addon.sourceURI.spec, uri);
+
+  let activeInstalls = await AddonManager.getAllInstalls();
+  equal(activeInstalls.length, 1);
+  equal(activeInstalls[0], install);
+
+  let fooInstalls = await AddonManager.getInstallsByTypes(["foo"]);
+  equal(fooInstalls.length, 0);
+
+  let extensionInstalls = await AddonManager.getInstallsByTypes(["extension"]);
+  equal(extensionInstalls.length, 1);
+  equal(extensionInstalls[0], install);
+
+  let installSyncGUID = await new Promise(resolve => {
+    prepare_test({
+      "addon1@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled",
+      ]
+    }, [
+      "onInstallStarted",
+      "onInstallEnded",
+    ], () => {
+      resolve(install.addon.syncGUID);
+    });
+    install.install();
+  });
+
+  ensure_test_completed();
+
+  let addon = await AddonManager.getAddonByID("addon1@tests.mozilla.org");
+  ok(addon);
+
+  let pendingAddons = await AddonManager.getAddonsWithOperationsByTypes(null);
+  equal(pendingAddons.length, 0);
+
+  uri = NetUtil.newURI(addon.iconURL);
+  if (uri instanceof Ci.nsIJARURI) {
+    let archiveURI = uri.JARFile;
+    let archiveFile = archiveURI.QueryInterface(Ci.nsIFileURL).file;
+    let zipReader = new ZipReader(archiveFile);
+    try {
+      ok(zipReader.hasEntry(uri.JAREntry));
+    } finally {
+      zipReader.close();
+    }
+  } else {
+    let iconFile = uri.QueryInterface(Ci.nsIFileURL).file;
+    ok(iconFile.exists());
+    // Make the iconFile predictably old.
+    iconFile.lastModifiedTime = Date.now() - MAKE_FILE_OLD_DIFFERENCE;
+  }
+
+  // Make the pending install have a sensible date
+  let updateDate = Date.now();
+  let extURI = addon.getResourceURI("");
+  let ext = extURI.QueryInterface(Ci.nsIFileURL).file;
+  setExtensionModifiedTime(ext, updateDate);
+
+  ok(!hasFlag(addon.permissions, AddonManager.PERM_CAN_ENABLE));
+  ok(hasFlag(addon.permissions, AddonManager.PERM_CAN_DISABLE));
+
+  await promiseRestartManager();
+
+  activeInstalls = await AddonManager.getAllInstalls();
+  equal(activeInstalls, 0);
+
+  let a1 = await AddonManager.getAddonByID("addon1@tests.mozilla.org");
+  notEqual(a1, null);
+  notEqual(a1.syncGUID, null);
+  ok(a1.syncGUID.length >= 9);
+  equal(a1.syncGUID, installSyncGUID);
+  equal(a1.type, "extension");
+  equal(a1.version, "1.0");
+  equal(a1.name, "Test 1");
+  ok(isExtensionInBootstrappedList(profileDir, a1.id));
+  ok(XPIS.test_install1.exists());
+  do_check_in_crash_annotation(a1.id, a1.version);
+  equal(a1.size, ADDON1_SIZE);
+  ok(!a1.foreignInstall);
+
+  equal(a1.sourceURI.spec,
+        Services.io.newFileURI(XPIS.test_install1).spec);
+  let difference = a1.installDate.getTime() - updateDate;
+  if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
+    do_throw("Add-on install time was out by " + difference + "ms");
+
+  difference = a1.updateDate.getTime() - updateDate;
+  if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
+    do_throw("Add-on update time was out by " + difference + "ms");
+
+  ok(a1.hasResource("install.rdf"));
+  ok(!a1.hasResource("foo.bar"));
+
+  let uri2 = do_get_addon_root_uri(profileDir, "addon1@tests.mozilla.org");
+  equal(a1.getResourceURI("install.rdf").spec, uri2 + "install.rdf");
+  equal(a1.iconURL, uri2 + "icon.png");
+  equal(a1.icon64URL, uri2 + "icon64.png");
+
+  // Ensure that extension bundle (or icon if unpacked) has updated
+  // lastModifiedDate.
+  let testURI = a1.getResourceURI(TEST_UNPACKED ? "icon.png" : "");
+  let testFile = testURI.QueryInterface(Ci.nsIFileURL).file;
+  ok(testFile.exists());
+  difference = testFile.lastModifiedTime - Date.now();
+  ok(Math.abs(difference) < MAX_TIME_DIFFERENCE);
+
+  a1.uninstall();
+  let { id, version } = a1;
+  await promiseRestartManager();
+  do_check_not_in_crash_annotation(id, version);
+});
+
+// Tests that an install from a url downloads.
+add_task(async function test_2() {
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let install = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall", null, "Test 2", null, "1.0");
+  notEqual(install, null);
+  equal(install.version, "1.0");
+  equal(install.name, "Test 2");
+  equal(install.state, AddonManager.STATE_AVAILABLE);
+  equal(install.iconURL, null);
+  equal(install.sourceURI.spec, url);
+
+  let activeInstalls = await AddonManager.getAllInstalls();
+  equal(activeInstalls.length, 1);
+  equal(activeInstalls[0], install);
+
+  install = await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+    ], install1 => {
+      resolve(install1);
+      return false;
+    });
+
+    install.addListener({
+      onDownloadProgress() {
+        executeSoon(function() {
+          Cu.forceGC();
+        });
+      }
+    });
+
+    install.install();
+  });
+
+  ensure_test_completed();
+  equal(install.version, "2.0");
+  equal(install.name, "Real Test 2");
+  equal(install.state, AddonManager.STATE_DOWNLOADED);
+  equal(install.addon.install, install);
+  equal(install.iconURL, null);
+
+  install = await new Promise(resolve => {
+    prepare_test({
+      "addon2@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled"
+      ]
+    }, [
+      "onInstallStarted",
+      "onInstallEnded",
+    ], resolve);
+    install.install();
+  });
+
+  // Make the pending install have a sensible date
+  let updateDate = Date.now();
+  let extURI = install.addon.getResourceURI("");
+  let ext = extURI.QueryInterface(Ci.nsIFileURL).file;
+  setExtensionModifiedTime(ext, updateDate);
+
+  ensure_test_completed();
+  let olda2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  ok(olda2);
+
+  await promiseRestartManager();
+
+  let installs = await AddonManager.getAllInstalls();
+  equal(installs, 0);
+
+  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  notEqual(a2, null);
+  notEqual(a2.syncGUID, null);
+  equal(a2.type, "extension");
+  equal(a2.version, "2.0");
+  equal(a2.name, "Real Test 2");
+  ok(isExtensionInBootstrappedList(profileDir, a2.id));
+  ok(XPIS.test_install2_1.exists());
+  do_check_in_crash_annotation(a2.id, a2.version);
+  equal(a2.sourceURI.spec,
+               "http://example.com/addons/test_install2_1.xpi");
+
+  let difference = a2.installDate.getTime() - updateDate;
+  if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
+    do_throw("Add-on install time was out by " + difference + "ms");
+
+  difference = a2.updateDate.getTime() - updateDate;
+  if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
+    do_throw("Add-on update time was out by " + difference + "ms");
+
+  gInstallDate = a2.installDate.getTime();
+});
+
+// Tests that installing a new version of an existing add-on works
+add_task(async function test_4() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install2_2.xpi";
+  let install = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall", null, "Test 3", null, "3.0");
+  ensure_test_completed();
+
+  notEqual(install, null);
+  equal(install.version, "3.0");
+  equal(install.name, "Test 3");
+  equal(install.state, AddonManager.STATE_AVAILABLE);
+
+  let activeInstalls = await AddonManager.getAllInstalls();
+  equal(activeInstalls.length, 1);
+  equal(activeInstalls[0], install);
+  equal(install.existingAddon, null);
+
+  install = await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+    ], install1 => {
+      resolve(install1);
+      return false;
+    });
+    install.install();
+  });
+
+  ensure_test_completed();
+
+  equal(install.version, "3.0");
+  equal(install.name, "Real Test 3");
+  equal(install.state, AddonManager.STATE_DOWNLOADED);
+  ok(install.existingAddon);
+  equal(install.existingAddon.id, "addon2@tests.mozilla.org");
+  equal(install.addon.install, install);
+
+  // Installation will continue when there is nothing returned.
+  install = await new Promise(resolve => {
+    prepare_test({
+      "addon2@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled",
+      ]
+    }, [
+      "onInstallStarted",
+      "onInstallEnded",
+    ], resolve);
+    install.install();
+  });
+
+  ensure_test_completed();
+
+  await promiseRestartManager();
+
+  let installs2 = await AddonManager.getInstallsByTypes(null);
+  equal(installs2.length, 0);
+
+  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  notEqual(a2, null);
+  equal(a2.type, "extension");
+  equal(a2.version, "3.0");
+  equal(a2.name, "Real Test 3");
+  ok(a2.isActive);
+  ok(isExtensionInBootstrappedList(profileDir, a2.id));
+  ok(XPIS.test_install2_2.exists());
+  do_check_in_crash_annotation(a2.id, a2.version);
+  equal(a2.sourceURI.spec,
+               "http://example.com/addons/test_install2_2.xpi");
+  ok(!a2.foreignInstall);
+
+  equal(a2.installDate.getTime(), gInstallDate);
+  // Update date should be later (or the same if this test is too fast)
+  ok(a2.installDate <= a2.updateDate);
+
+  a2.uninstall();
+});
+
+// Tests that an install that requires a compatibility update works
+add_task(async function test_6() {
+  await promiseRestartManager();
+
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install3.xpi";
+  let install = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall", null, "Real Test 4", null, "1.0");
+  ensure_test_completed();
+
+  notEqual(install, null);
+  equal(install.version, "1.0");
+  equal(install.name, "Real Test 4");
+  equal(install.state, AddonManager.STATE_AVAILABLE);
+
+  let activeInstalls = await AddonManager.getInstallsByTypes(null);
+  equal(activeInstalls.length, 1);
+  equal(activeInstalls[0], install);
+
+  install = await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+    ], install1 => {
+      resolve(install1);
+      return false;
+    });
+    install.install();
+  });
+
+  ensure_test_completed();
+  equal(install.version, "1.0");
+  equal(install.name, "Real Test 4");
+  equal(install.state, AddonManager.STATE_DOWNLOADED);
+  equal(install.existingAddon, null);
+  ok(!install.addon.appDisabled);
+
+  // Continue the install
+  await new Promise(resolve => {
+    prepare_test({
+      "addon3@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled",
+      ]
+    }, [
+      "onInstallStarted",
+      "onInstallEnded",
+    ], resolve);
+    install.install();
+  });
+
+  ensure_test_completed();
+
+  await promiseRestartManager();
+
+  let installs = await AddonManager.getAllInstalls();
+  equal(installs, 0);
+
+  let a3 = await AddonManager.getAddonByID("addon3@tests.mozilla.org");
+  notEqual(a3, null);
+  notEqual(a3.syncGUID, null);
+  equal(a3.type, "extension");
+  equal(a3.version, "1.0");
+  equal(a3.name, "Real Test 4");
+  ok(a3.isActive);
+  ok(!a3.appDisabled);
+  ok(isExtensionInBootstrappedList(profileDir, a3.id));
+  ok(XPIS.test_install3.exists());
+  a3.uninstall();
+});
+
+add_task(async function test_8() {
+  await promiseRestartManager();
+
+  AddonManager.addInstallListener(InstallListener);
+  AddonManager.addAddonListener(AddonListener);
+
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let install = await AddonManager.getInstallForFile(XPIS.test_install3);
+  ok(install.addon.isCompatible);
+
+  await new Promise(resolve => {
+    prepare_test({
+      "addon3@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled",
+      ]
+    }, [
+      "onInstallStarted",
+      "onInstallEnded",
+    ], resolve);
+    install.install();
+  });
+
+  await promiseRestartManager();
+
+  let a3 = await AddonManager.getAddonByID("addon3@tests.mozilla.org");
+  notEqual(a3, null);
+  notEqual(a3.syncGUID, null);
+  equal(a3.type, "extension");
+  equal(a3.version, "1.0");
+  equal(a3.name, "Real Test 4");
+  ok(a3.isActive);
+  ok(!a3.appDisabled);
+  ok(isExtensionInBootstrappedList(profileDir, a3.id));
+  ok(XPIS.test_install3.exists());
+  a3.uninstall();
+});
+
+// Test that after cancelling a download it is removed from the active installs
+add_task(async function test_9() {
+  await promiseRestartManager();
+
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install3.xpi";
+  let install = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall", null, "Real Test 4", null, "1.0");
+  ensure_test_completed();
+
+  notEqual(install, null);
+  equal(install.version, "1.0");
+  equal(install.name, "Real Test 4");
+  equal(install.state, AddonManager.STATE_AVAILABLE);
+
+  let activeInstalls = await AddonManager.getInstallsByTypes(null);
+  equal(activeInstalls.length, 1);
+  equal(activeInstalls[0], install);
+
+  install = await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+    ], () => {
+      prepare_test({}, [
+        "onDownloadCancelled"
+      ], resolve);
+
+      install.cancel();
+    });
+
+    install.install();
+  });
+
+  let file = install.file;
+
+  // Allow the file removal to complete
+  activeInstalls = await AddonManager.getAllInstalls();
+  equal(activeInstalls.length, 0);
+  ok(!file.exists());
+});
+
+// Check that cancelling the install from onDownloadStarted actually cancels it
+add_task(async function test_14() {
+  await promiseRestartManager();
+
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let install = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  ensure_test_completed();
+
+  equal(install.file, null);
+
+  install = await new Promise(resolve => {
+    prepare_test({ }, [
+      "onDownloadStarted"
+    ], resolve);
+    install.install();
+  });
+
+  // Wait for the channel to be ready to cancel
+  executeSoon(function() {
+    install.cancel();
+  });
+
+  await new Promise(resolve => {
+    prepare_test({ }, [
+      "onDownloadCancelled"
+    ], resolve);
+  });
+
+  let file = install.file;
+
+  install.addListener({
+    onDownloadProgress() {
+      do_throw("Download should not have continued");
+    },
+    onDownloadEnded() {
+      do_throw("Download should not have continued");
+    }
+  });
+
+  // Allow the listener to return to see if it continues downloading. The
+  // The listener only really tests if we give it time to see progress, the
+  // file check isn't ideal either
+  ok(!file.exists());
+});
+
+// Checks that cancelling the install from onDownloadEnded actually cancels it
+add_task(async function test_15() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let install = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  ensure_test_completed();
+
+  equal(install.file, null);
+
+  await new Promise(resolve => {
+    prepare_test({ }, [
+      "onDownloadStarted",
+      "onDownloadEnded"
+    ], () => {
+      prepare_test({ }, [
+        "onDownloadCancelled"
+      ]);
+
+      install.cancel();
+      resolve();
+    });
+    install.install();
+  });
+
+  ensure_test_completed();
+
+  install.addListener({
+    onInstallStarted() {
+      do_throw("Install should not have continued");
+    }
+  });
+});
+
+// Verify that the userDisabled value carries over to the upgrade by default
+add_task(async function test_16() {
+  await promiseRestartManager();
+
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  await new Promise(resolve => {
+    aInstall.addListener({
+      onInstallStarted() {
+        ok(!aInstall.addon.userDisabled);
+        aInstall.addon.userDisabled = true;
+      },
+
+      onInstallEnded() {
+        resolve();
+      }
+    });
+    aInstall.install();
+  });
+
+  await promiseRestartManager();
+
+  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  ok(a2.userDisabled);
+  ok(!a2.isActive);
+
+  let url_2 = "http://example.com/addons/test_install2_2.xpi";
+  let aInstall_2 = await AddonManager.getInstallForURL(url_2, null, "application/x-xpinstall");
+  await new Promise(resolve => {
+    aInstall_2.addListener({
+      onInstallEnded() {
+        resolve();
+      }
+    });
+    aInstall_2.install();
+  });
+
+  ok(aInstall_2.addon.userDisabled);
+
+  await promiseRestartManager();
+
+  let a2_2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  ok(a2_2.userDisabled);
+  ok(!a2_2.isActive);
+
+  a2_2.uninstall();
+});
+
+// Verify that changing the userDisabled value before onInstallEnded works
+add_task(async function test_17() {
+  await promiseRestartManager();
+
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  await new Promise(resolve => {
+    aInstall.addListener({
+      onInstallEnded() {
+        resolve();
+      }
+    });
+    aInstall.install();
+  });
+
+  ok(!aInstall.addon.userDisabled);
+
+  await promiseRestartManager();
+
+  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  ok(!a2.userDisabled);
+  ok(a2.isActive);
+
+  let url_2 = "http://example.com/addons/test_install2_2.xpi";
+  let aInstall_2 = await AddonManager.getInstallForURL(url_2, null, "application/x-xpinstall");
+
+  await new Promise(resolve => {
+    aInstall_2.addListener({
+      onInstallStarted() {
+        ok(!aInstall_2.addon.userDisabled);
+        aInstall_2.addon.userDisabled = true;
+      },
+
+      onInstallEnded() {
+        resolve();
+      }
+    });
+    aInstall_2.install();
+  });
+  await promiseRestartManager();
+
+  let a2_2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  ok(a2_2.userDisabled);
+  ok(!a2_2.isActive);
+
+  a2_2.uninstall();
+});
+
+// Verify that changing the userDisabled value before onInstallEnded works
+add_task(async function test_18() {
+  await promiseRestartManager();
+
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  await new Promise(resolve => {
+    aInstall.addListener({
+      onInstallStarted() {
+        ok(!aInstall.addon.userDisabled);
+        aInstall.addon.userDisabled = true;
+      },
+
+      onInstallEnded() {
+        resolve();
+      }
+    });
+    aInstall.install();
+  });
+
+  await promiseRestartManager();
+
+  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  ok(a2.userDisabled);
+  ok(!a2.isActive);
+
+  let url_2 = "http://example.com/addons/test_install2_2.xpi";
+  let aInstall_2 = await AddonManager.getInstallForURL(url_2, null, "application/x-xpinstall");
+  await new Promise(resolve => {
+    aInstall_2.addListener({
+      onInstallStarted() {
+        ok(aInstall_2.addon.userDisabled);
+        aInstall_2.addon.userDisabled = false;
+      },
+
+      onInstallEnded() {
+        resolve();
+      }
+    });
+    aInstall_2.install();
+  });
+
+  await promiseRestartManager();
+
+  let a2_2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  ok(!a2_2.userDisabled);
+  ok(a2_2.isActive);
+
+  a2_2.uninstall();
+});
+
+
+// Checks that metadata is not stored if the pref is set to false
+add_task(async function test_18_1() {
+  await promiseRestartManager();
+
+  Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true);
+  Services.prefs.setCharPref(PREF_GETADDONS_BYIDS,
+                             "http://example.com/data/test_install_addons.json");
+  Services.prefs.setCharPref(PREF_COMPAT_OVERRIDES,
+                             "http://example.com/data/test_install_compat.json");
+
+  Services.prefs.setBoolPref("extensions.addon2@tests.mozilla.org.getAddons.cache.enabled", false);
+
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+
+  let addon = await new Promise(resolve => {
+    aInstall.addListener({
+      onInstallEnded(unused, aAddon) {
+        resolve(aAddon);
+      }
+    });
+    aInstall.install();
+  });
+
+  notEqual(addon.fullDescription, "Repository description");
+
+  await promiseRestartManager();
+
+  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  notEqual(a2.fullDescription, "Repository description");
+
+  a2.uninstall();
+});
+
+// Checks that metadata is downloaded for new installs and is visible before and
+// after restart
+add_task(async function test_19() {
+  await promiseRestartManager();
+  Services.prefs.setBoolPref("extensions.addon2@tests.mozilla.org.getAddons.cache.enabled", true);
+
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  await new Promise(resolve => {
+    aInstall.addListener({
+      onInstallEnded(unused, aAddon) {
+        resolve(aAddon);
+      }
+    });
+    aInstall.install();
+  });
+
+  let a1 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  equal(a1.fullDescription, "Repository description");
+
+  await promiseRestartManager();
+
+  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  equal(a2.fullDescription, "Repository description");
+
+  a2.uninstall();
+});
+
+// Do the same again to make sure it works when the data is already in the cache
+add_task(async function test_20() {
+  await promiseRestartManager();
+
+  let url = "http://example.com/addons/test_install2_1.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  await new Promise(resolve => {
+    aInstall.addListener({
+      onInstallEnded(unused, aAddon) {
+        resolve(aAddon);
+      }
+    });
+    aInstall.install();
+  });
+
+  let a1 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  equal(a1.fullDescription, "Repository description");
+
+  await promiseRestartManager();
+
+  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
+  equal(a2.fullDescription, "Repository description");
+
+  a2.uninstall();
+});
+
+// Tests that an install can be restarted after being cancelled
+add_task(async function test_22() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install3.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  ensure_test_completed();
+
+  notEqual(aInstall, null);
+  equal(aInstall.state, AddonManager.STATE_AVAILABLE);
+
+  let install = await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+    ], install1 => {
+      prepare_test({}, [
+        "onDownloadCancelled"
+      ]);
+      aInstall.cancel();
+      resolve(install1);
+    });
+    aInstall.install();
+  });
+
+  ensure_test_completed();
+
+  await new Promise(resolve => {
+    prepare_test({
+      "addon3@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled"
+      ]
+    }, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+      "onInstallStarted",
+      "onInstallEnded"
+    ], resolve);
+
+    install.install();
+  });
+
+  ensure_test_completed();
+
+  AddonManager.removeAddonListener(AddonListener);
+  install.addon.uninstall();
+});
+
+// Tests that an install can be restarted after being cancelled when a hash
+// was provided
+add_task(async function test_23() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install3.xpi";
+  let install = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall", do_get_file_hash(XPIS.test_install3));
+
+  ensure_test_completed();
+
+  notEqual(install, null);
+  equal(install.state, AddonManager.STATE_AVAILABLE);
+
+  await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+    ], () => {
+      prepare_test({}, [
+        "onDownloadCancelled"
+      ]);
+
+      install.cancel();
+      resolve();
+    });
+    install.install();
+  });
+
+  ensure_test_completed();
+
+  await new Promise(resolve => {
+    prepare_test({
+      "addon3@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled",
+      ]
+    }, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+      "onInstallStarted",
+      "onInstallEnded"
+    ], resolve);
+
+    install.install();
+  });
+
+  ensure_test_completed();
+
+  AddonManager.removeAddonListener(AddonListener);
+  install.addon.uninstall();
+});
+
+// Tests that an install with a bad hash can be restarted after it fails, though
+// it will only fail again
+add_task(async function test_24() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install3.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall", "sha1:foo");
+  ensure_test_completed();
+
+  notEqual(aInstall, null);
+  equal(aInstall.state, AddonManager.STATE_AVAILABLE);
+
+  let install = await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadFailed",
+    ], resolve);
+    aInstall.install();
+  });
+
+  await new Promise(resolve => {
+    prepare_test({ }, [
+      "onDownloadStarted",
+      "onDownloadFailed"
+    ], resolve);
+
+    install.install();
+  });
+});
+
+// Tests that installs with a hash for a local file work
+add_task(async function test_25() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = Services.io.newFileURI(XPIS.test_install3).spec;
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall", do_get_file_hash(XPIS.test_install3));
+  ensure_test_completed();
+
+  notEqual(aInstall, null);
+  equal(aInstall.state, AddonManager.STATE_DOWNLOADED);
+  equal(aInstall.error, 0);
+
+  prepare_test({ }, [
+    "onDownloadCancelled"
+  ]);
+
+  aInstall.cancel();
+
+  ensure_test_completed();
+});
+
+add_task(async function test_26() {
+  prepare_test({ }, [
+    "onNewInstall",
+    "onDownloadStarted",
+    "onDownloadCancelled"
+  ]);
+
+  let url = "http://example.com/redirect?/addons/test_install1.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+
+  await new Promise(resolve => {
+    let observerService = Cc["@mozilla.org/network/http-activity-distributor;1"].
+                          getService(Ci.nsIHttpActivityDistributor);
+    observerService.addObserver({
+      observeActivity(aChannel, aType, aSubtype, aTimestamp, aSizeData,
+                                aStringData) {
+        aChannel.QueryInterface(Ci.nsIChannel);
+        // Wait for the final event for the redirected URL
+        if (aChannel.URI.spec != "http://example.com/addons/test_install1.xpi" ||
+            aType != Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION ||
+            aSubtype != Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE)
+          return;
+
+        // Request should have been cancelled
+        equal(aChannel.status, Cr.NS_BINDING_ABORTED);
+
+        observerService.removeObserver(this);
+
+        resolve();
+      }
+    });
+
+    aInstall.addListener({
+      onDownloadProgress(aDownloadProgressInstall) {
+        aDownloadProgressInstall.cancel();
+      }
+    });
+
+    aInstall.install();
+  });
+});
+
+
+// Tests that an install can be restarted during onDownloadCancelled after being
+// cancelled in mid-download
+add_task(async function test_27() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install3.xpi";
+  let aInstall = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall");
+  ensure_test_completed();
+
+  notEqual(aInstall, null);
+  equal(aInstall.state, AddonManager.STATE_AVAILABLE);
+
+  aInstall.addListener({
+    onDownloadProgress() {
+      aInstall.removeListener(this);
+      aInstall.cancel();
+    }
+  });
+
+  let install = await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadCancelled",
+    ], resolve);
+    aInstall.install();
+  });
+
+  install = await new Promise(resolve => {
+    prepare_test({
+      "addon3@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled",
+      ]
+    }, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+      "onInstallStarted",
+      "onInstallEnded"
+    ], resolve);
+
+    let file = install.file;
+    install.install();
+    notEqual(file.path, install.file.path);
+    ok(!file.exists());
+  });
+
+  ensure_test_completed();
+
+  AddonManager.removeAddonListener(AddonListener);
+  install.addon.uninstall();
+});
+
+// Tests that an install with a matching compatibility override has appDisabled
+// set correctly.
+add_task(async function test_29() {
+  Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true);
+
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://example.com/addons/test_install6.xpi";
+  let install = await AddonManager.getInstallForURL(url, null, "application/x-xpinstall", null, "Addon Test 6", null, "1.0");
+  ensure_test_completed();
+
+  notEqual(install, null);
+  equal(install.version, "1.0");
+  equal(install.name, "Addon Test 6");
+  equal(install.state, AddonManager.STATE_AVAILABLE);
+
+  let activeInstalls = await AddonManager.getInstallsByTypes(null);
+  equal(activeInstalls.length, 1);
+  equal(activeInstalls[0], install);
+
+  install = await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded"
+    ], install2 => {
+      resolve(install2);
+      return false;
+    });
+    install.install();
+  });
+
+  // ensure_test_completed();
+  equal(install.state, AddonManager.STATE_DOWNLOADED);
+  notEqual(install.addon, null);
+  ok(!install.addon.isCompatible);
+  ok(install.addon.appDisabled);
+
+  await new Promise(resolve => {
+    prepare_test({}, [
+      "onDownloadCancelled"
+    ], resolve);
+    install.cancel();
+  });
+});
+
+// Tests that a multi-package XPI with no add-ons inside shows up as a
+// corrupt file
+add_task(async function test_30() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let install = await AddonManager.getInstallForFile(XPIS.test_install7);
+  ensure_test_completed();
+
+  notEqual(install, null);
+  equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
+  equal(install.error, AddonManager.ERROR_CORRUPT_FILE);
+});
+
+// Tests that a multi-package XPI with no valid add-ons inside shows up as a
+// corrupt file
+add_task(async function test_31() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let install = await AddonManager.getInstallForFile(XPIS.test_install8);
+  ensure_test_completed();
+
+  notEqual(install, null);
+  equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
+  equal(install.error, AddonManager.ERROR_CORRUPT_FILE);
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -215,16 +215,17 @@ tags = blocklist
 [test_gfxBlacklist_Vendor.js]
 tags = blocklist
 [test_gfxBlacklist_Version.js]
 tags = blocklist
 [test_gfxBlacklist_prefs.js]
 # Bug 1248787 - consistently fails
 skip-if = true
 tags = blocklist
+[test_install.js]
 [test_install_icons.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_isDebuggable.js]
 [test_legacy.js]
 skip-if = !allow_legacy_extensions || appname == "thunderbird"
 [test_locale.js]
 [test_manifest.js]