Bug 1371762 Part 2 Treat webextension experiments as legacy draft
authorAndrew Swan <aswan@mozilla.com>
Wed, 19 Jul 2017 10:28:23 -0700
changeset 611404 404502e41135d8a88299209e0522c0801d8d28ed
parent 611403 f15343f26adde16d8f799f7e518404121be99ec5
child 638156 6bdb11350ede0cbe16bd583d5f30103ef01f9c05
push id69215
push useraswan@mozilla.com
push dateWed, 19 Jul 2017 17:29:25 +0000
bugs1371762
milestone56.0a1
Bug 1371762 Part 2 Treat webextension experiments as legacy MozReview-Commit-ID: ATa0DXnV2au
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/test_legacy.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -257,17 +257,22 @@ const CHROME_TYPES = new Set([
 const SIGNED_TYPES = new Set([
   "apiextension",
   "extension",
   "experiment",
   "webextension",
   "webextension-theme",
 ]);
 
-const ALL_TYPES = new Set([
+const LEGACY_TYPES = new Set([
+  "apiextension",
+  "extension",
+]);
+
+const ALL_EXTERNAL_TYPES = new Set([
   "dictionary",
   "extension",
   "experiment",
   "locale",
   "theme",
 ]);
 
 // Whether add-on signing is required.
@@ -830,18 +835,18 @@ function isUsableAddon(aAddon) {
       let active = XPIProvider.activeAddons.get(id);
       return active && !active.disable;
     };
 
     if (aAddon.dependencies.some(id => !isActive(id)))
       return false;
   }
 
-  if (!AddonSettings.ALLOW_LEGACY_EXTENSIONS &&
-      aAddon.type == "extension" && !aAddon._installLocation.isSystem &&
+  if (!AddonSettings.ALLOW_LEGACY_EXTENSIONS && LEGACY_TYPES.has(aAddon.type) &&
+      !aAddon._installLocation.isSystem &&
       aAddon.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED) {
     logger.warn(`disabling legacy extension ${aAddon.id}`);
     return false;
   }
 
   if (!ALLOW_NON_MPC && aAddon.type == "extension" &&
       aAddon.multiprocessCompatible !== true) {
     logger.warn(`disabling ${aAddon.id} since it is not multiprocess compatible`);
@@ -3583,17 +3588,17 @@ this.XPIProvider = {
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types.
    * @param  aCallback
    *         A callback to pass an array of Addons to
    */
   getAddonsByTypes(aTypes, aCallback) {
     let typesToGet = getAllAliasesForTypes(aTypes);
-    if (typesToGet && !typesToGet.some(type => ALL_TYPES.has(type))) {
+    if (typesToGet && !typesToGet.some(type => ALL_EXTERNAL_TYPES.has(type))) {
       aCallback([]);
       return;
     }
 
     XPIDatabase.getVisibleAddons(typesToGet, function(aAddons) {
       aCallback(aAddons.map(a => a.wrapper));
     });
   },
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_legacy.js
@@ -0,0 +1,131 @@
+
+const LEGACY_PREF = "extensions.legacy.enabled";
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+startupManager();
+
+add_task(async function test_disable() {
+  let legacy = [
+    {
+      id: "bootstrap@tests.mozilla.org",
+      name: "Bootstrap add-on",
+      version: "1.0",
+      bootstrap: true,
+      multiprocessCompatible: true,
+    },
+    {
+      id: "apiexperiment@tests.mozilla.org",
+      name: "WebExtension Experiment",
+      version: "1.0",
+      type: 256,
+    },
+  ];
+
+  let nonLegacy = [
+    {
+      id: "webextension@tests.mozilla.org",
+      manifest: {
+        applications: {gecko: {id: "webextension@tests.mozilla.org"}},
+      },
+    },
+    {
+      id: "privileged@tests.mozilla.org",
+      name: "Privileged Bootstrap add-on",
+      version: "1.0",
+      bootstrap: true,
+      multiprocessCompatible: true,
+    },
+    {
+      id: "langpack@tests.mozilla.org",
+      name: "Test Langpack",
+      version: "1.0",
+      type: "8",
+    },
+    {
+      id: "dictionary@tests.mozilla.org",
+      name: "Test Dictionary",
+      version: "1.0",
+      type: "64",
+    }
+  ];
+
+  function makeXPI(info) {
+    if (info.manifest) {
+      return createTempWebExtensionFile(info);
+    }
+
+    return createTempXPIFile(Object.assign({}, info, {
+      targetApplications: [{
+        id: "xpcshell@tests.mozilla.org",
+        minVersion: "1",
+        maxVersion: "1"
+      }],
+    }));
+  }
+
+  AddonTestUtils.usePrivilegedSignatures = id => id.startsWith("privileged");
+
+  // Start out with legacy extensions disabled, installing non-legacy
+  // extensions should succeed.
+  Services.prefs.setBoolPref(LEGACY_PREF, false);
+  let installs = await Promise.all(nonLegacy.map(info => {
+    let xpi = makeXPI(info);
+    return AddonManager.getInstallForFile(xpi);
+  }));
+  await promiseCompleteAllInstalls(installs);
+  for (let install of installs) {
+    do_check_eq(install.state, AddonManager.STATE_INSTALLED);
+    do_check_eq(install.error, 0);
+  }
+  let addons = await AddonManager.getAddonsByIDs(nonLegacy.map(a => a.id));
+  for (let addon of addons) {
+    do_check_eq(addon.appDisabled, false);
+  }
+
+  // And installing legacy extensions should fail
+  let legacyXPIs = legacy.map(makeXPI);
+  installs = await Promise.all(legacyXPIs.map(xpi => AddonManager.getInstallForFile(xpi)));
+
+  // Yuck, the AddonInstall API is atrocious.  Installs of incompatible
+  // extensions are detected when the install reaches the DOWNLOADED state
+  // and the install is abandoned at that point.  Since this is a local file
+  // install we just start out in the DONWLOADED state.
+  for (let install of installs) {
+    do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
+    do_check_eq(install.addon.appDisabled, true);
+  }
+
+  // Now enable legacy extensions, and we should be able to install
+  // the legacy extensions.
+  Services.prefs.setBoolPref(LEGACY_PREF, true);
+  installs = await Promise.all(legacyXPIs.map(xpi => AddonManager.getInstallForFile(xpi)));
+  for (let install of installs) {
+    do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
+    do_check_eq(install.addon.appDisabled, false);
+  }
+  await promiseCompleteAllInstalls(installs);
+  for (let install of installs) {
+    do_check_eq(install.state, AddonManager.STATE_INSTALLED);
+    do_check_eq(install.error, 0);
+  }
+  addons = await AddonManager.getAddonsByIDs(legacy.map(a => a.id));
+  for (let addon of addons) {
+    do_check_eq(addon.appDisabled, false);
+  }
+
+  // Flip the preference back, the legacy extensions should become disabled
+  // but non-legacy extensions should remain enabled.
+  Services.prefs.setBoolPref(LEGACY_PREF, false);
+  addons = await AddonManager.getAddonsByIDs(nonLegacy.map(a => a.id));
+  for (let addon of addons) {
+    do_check_eq(addon.appDisabled, false);
+    addon.uninstall();
+  }
+  addons = await AddonManager.getAddonsByIDs(legacy.map(a => a.id));
+  for (let addon of addons) {
+    do_check_eq(addon.appDisabled, true);
+    addon.uninstall();
+  }
+
+  Services.prefs.clearUserPref(LEGACY_PREF);
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -234,16 +234,17 @@ tags = blocklist
 [test_install_icons.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_install_strictcompat.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 run-sequentially = Uses hardcoded ports in xpi files.
 [test_isDebuggable.js]
+[test_legacy.js]
 [test_locale.js]
 [test_locked.js]
 [test_locked2.js]
 [test_locked_strictcompat.js]
 [test_manifest.js]
 [test_mapURIToAddonID.js]
 # Same as test_bootstrap.js
 skip-if = os == "android"