Bug 1330349 - Part 5 - add tests for new theme type WebExtensions uninstall, enable and disable behavior. r?Mossop draft
authorMike de Boer <mdeboer@mozilla.com>
Thu, 02 Mar 2017 14:22:26 +0100
changeset 491927 bcdff925907b31c94d6b35036393cdd972b5a256
parent 491926 87370044cee487a820e097fc9fc82b2b97522cb0
child 491930 65acccced96db9e8ffaaa05f11bbcb13ec3a6d96
push id47458
push usermdeboer@mozilla.com
push dateThu, 02 Mar 2017 13:47:01 +0000
reviewersMossop
bugs1330349
milestone54.0a1
Bug 1330349 - Part 5 - add tests for new theme type WebExtensions uninstall, enable and disable behavior. r?Mossop MozReview-Commit-ID: 7FI9rFYtn4D
toolkit/mozapps/extensions/test/xpcshell/head_addons.js
toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js
toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -1049,16 +1049,40 @@ function completeAllInstalls(aInstalls, 
 function installAllFiles(aFiles, aCallback, aIgnoreIncompatible) {
   promiseInstallAllFiles(aFiles, aIgnoreIncompatible).then(aCallback);
 }
 
 const EXTENSIONS_DB = "extensions.json";
 var gExtensionsJSON = gProfD.clone();
 gExtensionsJSON.append(EXTENSIONS_DB);
 
+function promiseWebExtensionStartup() {
+  const {Management} = Components.utils.import("resource://gre/modules/Extension.jsm", {});
+
+  return new Promise(resolve => {
+    let listener = (evt, extension) => {
+      Management.off("ready", listener);
+      resolve(extension);
+    };
+
+    Management.on("ready", listener);
+  });
+}
+
+function promiseInstallWebExtension(aData) {
+  let addonFile = createTempWebExtensionFile(aData);
+
+  return promiseInstallAllFiles([addonFile]).then(installs => {
+    Services.obs.notifyObservers(addonFile, "flush-cache-entry", null);
+    // Since themes are disabled by default, it won't start up.
+    if ("theme" in aData.manifest)
+      return installs[0].addon;
+    return promiseWebExtensionStartup();
+  });
+}
 
 // By default use strict compatibility
 Services.prefs.setBoolPref("extensions.strictCompatibility", true);
 
 // By default, set min compatible versions to 0
 Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0");
 Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, "0");
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js
@@ -346,16 +346,83 @@ add_task(function* canUndoUninstallDisab
   do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
 
   do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
 
   t1.uninstall();
   yield promiseRestartManager();
 });
 
+add_task(function* uninstallWebExtensionOffersUndo() {
+  let { id: addonId } = yield promiseInstallWebExtension({
+    manifest: {
+      "author": "Some author",
+      manifest_version: 2,
+      name: "Web Extension Name",
+      version: "1.0",
+      theme: { images: { headerURL: "https://example.com/example.png" } },
+    }
+  });
+
+  let [ t1, d ] = yield promiseAddonsByIDs([addonId, "default@tests.mozilla.org"]);
+
+  Assert.ok(t1, "Addon should be there");
+  Assert.ok(!t1.isActive);
+  Assert.ok(t1.userDisabled);
+  Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+  Assert.ok(d, "Addon should be there");
+  Assert.ok(d.isActive);
+  Assert.ok(!d.userDisabled);
+  Assert.equal(d.pendingOperations, AddonManager.PENDING_NONE);
+
+  Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
+
+  prepare_test({ [addonId]: [ "onUninstalling" ] });
+  t1.uninstall(true);
+  ensure_test_completed();
+
+  Assert.ok(!t1.isActive);
+  Assert.ok(t1.userDisabled);
+  Assert.ok(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL));
+
+  Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
+
+  prepare_test({
+    [addonId]: [
+      "onOperationCancelled"
+    ]
+  });
+  t1.cancelUninstall();
+  ensure_test_completed();
+
+  Assert.ok(!t1.isActive);
+  Assert.ok(t1.userDisabled);
+  Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+  yield promiseRestartManager();
+
+  [ t1, d ] = yield promiseAddonsByIDs([addonId, "default@tests.mozilla.org"]);
+
+  Assert.ok(d);
+  Assert.ok(d.isActive);
+  Assert.ok(!d.userDisabled);
+  Assert.equal(d.pendingOperations, AddonManager.PENDING_NONE);
+
+  Assert.ok(t1);
+  Assert.ok(!t1.isActive);
+  Assert.ok(t1.userDisabled);
+  Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+  Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
+
+  t1.uninstall();
+  yield promiseRestartManager();
+});
+
 // Tests that uninstalling an enabled lightweight theme offers the option to undo
 add_task(function* uninstallLWTOffersUndo() {
   // skipped since lightweight themes don't support undoable uninstall yet
 
   /*
   LightweightThemeManager.currentTheme = dummyLWTheme("theme1");
 
   let [ t1, d ] = yield promiseAddonsByIDs(["theme1@personas.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
@@ -9,47 +9,24 @@ const ID = "webextension1@tests.mozilla.
 const PREF_SELECTED_LOCALE = "general.useragent.locale";
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 startupManager();
 
-const { GlobalManager, Management } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
-
-function promiseAddonStartup() {
-  return new Promise(resolve => {
-    let listener = (evt, extension) => {
-      Management.off("ready", listener);
-      resolve(extension);
-    };
-
-    Management.on("ready", listener);
-  });
-}
-
-function promiseInstallWebExtension(aData) {
-  let addonFile = createTempWebExtensionFile(aData);
-
-  return promiseInstallAllFiles([addonFile]).then(installs => {
-    Services.obs.notifyObservers(addonFile, "flush-cache-entry", null);
-    // Since themes are disabled by default, it won't start up.
-    if ("theme" in aData.manifest)
-      return installs[0].addon;
-    return promiseAddonStartup();
-  });
-}
+const { GlobalManager } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
 
 add_task(function*() {
   equal(GlobalManager.extensionMap.size, 0);
 
   yield Promise.all([
     promiseInstallAllFiles([do_get_addon("webextension_1")], true),
-    promiseAddonStartup()
+    promiseWebExtensionStartup()
   ]);
 
   equal(GlobalManager.extensionMap.size, 1);
   ok(GlobalManager.extensionMap.has(ID));
 
   let chromeReg = AM_Cc["@mozilla.org/chrome/chrome-registry;1"].
                   getService(AM_Ci.nsIChromeRegistry);
   try {
@@ -77,17 +54,17 @@ add_task(function*() {
   do_check_eq(addon.icon64URL, uri + "icon64.png");
 
   // Should persist through a restart
   yield promiseShutdownManager();
 
   equal(GlobalManager.extensionMap.size, 0);
 
   startupManager();
-  yield promiseAddonStartup();
+  yield promiseWebExtensionStartup();
 
   equal(GlobalManager.extensionMap.size, 1);
   ok(GlobalManager.extensionMap.has(ID));
 
   addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_eq(addon.version, "1.0");
   do_check_eq(addon.name, "Web Extension Name");
@@ -106,17 +83,17 @@ add_task(function*() {
   do_check_eq(addon.iconURL, uri + "icon48.png");
   do_check_eq(addon.icon64URL, uri + "icon64.png");
 
   addon.userDisabled = true;
 
   equal(GlobalManager.extensionMap.size, 0);
 
   addon.userDisabled = false;
-  yield promiseAddonStartup();
+  yield promiseWebExtensionStartup();
 
   equal(GlobalManager.extensionMap.size, 1);
   ok(GlobalManager.extensionMap.has(ID));
 
   addon.uninstall();
 
   equal(GlobalManager.extensionMap.size, 0);
   do_check_false(GlobalManager.extensionMap.has(ID));
@@ -133,17 +110,17 @@ add_task(function*() {
     applications: {
       gecko: {
         id: ID
       }
     }
   }, profileDir);
 
   startupManager();
-  yield promiseAddonStartup();
+  yield promiseWebExtensionStartup();
 
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_eq(addon.version, "1.0");
   do_check_eq(addon.name, "Web Extension Name");
   do_check_true(addon.isCompatible);
   do_check_false(addon.appDisabled);
   do_check_true(addon.isActive);
@@ -158,17 +135,17 @@ add_task(function*() {
 
   yield promiseRestartManager();
 });
 
 add_task(function* test_manifest_localization() {
   const extensionId = "webextension3@tests.mozilla.org";
 
   yield promiseInstallAllFiles([do_get_addon("webextension_3")], true);
-  yield promiseAddonStartup();
+  yield promiseWebExtensionStartup();
 
   let addon = yield promiseAddonByID(extensionId);
   addon.userDisabled = true;
 
   equal(addon.name, "Web Extensiøn foo ☹");
   equal(addon.description, "Descriptïon bar ☹ of add-on");
 
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR");
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js
@@ -0,0 +1,236 @@
+"use strict";
+
+/**
+ * This file contains test for 'theme' type WebExtension addons. Tests focus mostly
+ * on interoperability between the different theme formats (XUL and LWT) and
+ * Addon Manager integration.
+ *
+ * Coverage may overlap with other tests in this folder.
+ */
+
+const {LightweightThemeManager} = AM_Cu.import("resource://gre/modules/LightweightThemeManager.jsm", {});
+const THEME_IDS = ["theme1@tests.mozilla.org", "theme3@tests.mozilla.org",
+  "theme2@personas.mozilla.org", "default@tests.mozilla.org"];
+const REQUIRE_RESTART = { [THEME_IDS[0]]: 1 };
+const DEFAULT_THEME = THEME_IDS[3];
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+// We remember the last/ currently active theme for tracking events.
+var gActiveTheme = null;
+
+add_task(function* setup_to_default_browserish_state() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+
+  writeInstallRDFForExtension({
+    id: THEME_IDS[0],
+    version: "1.0",
+    name: "Test 1",
+    type: 4,
+    skinnable: true,
+    internalName: "theme1/1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "2"
+    }]
+  }, profileDir);
+
+  yield promiseWriteWebManifestForExtension({
+    author: "Some author",
+    manifest_version: 2,
+    name: "Web Extension Name",
+    version: "1.0",
+    theme: { images: { headerURL: "https://example.com/example.png" } },
+    applications: {
+      gecko: {
+        id: THEME_IDS[1]
+      }
+    }
+  }, profileDir);
+
+  // We need a default theme for some of these things to work but we have hidden
+  // the one in the application directory.
+  writeInstallRDFForExtension({
+    id: DEFAULT_THEME,
+    version: "1.0",
+    name: "Default",
+    internalName: "classic/1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "2"
+    }]
+  }, profileDir);
+
+  startupManager();
+
+  // We can add an LWT only after the Addon Manager was started.
+  LightweightThemeManager.currentTheme = {
+    id: THEME_IDS[2].substr(0, THEME_IDS[2].indexOf("@")),
+    version: "1",
+    name: "Bling",
+    description: "SO MUCH BLING!",
+    author: "Pixel Pusher",
+    homepageURL: "http://mochi.test:8888/data/index.html",
+    headerURL: "http://mochi.test:8888/data/header.png",
+    previewURL: "http://mochi.test:8888/data/preview.png",
+    iconURL: "http://mochi.test:8888/data/icon.png",
+    textcolor: Math.random().toString(),
+    accentcolor: Math.random().toString()
+  };
+
+  let [ t1, t2, t3, d ] = yield promiseAddonsByIDs(THEME_IDS);
+  Assert.ok(t1, "Theme addon should exist");
+  Assert.ok(t2, "Theme addon should exist");
+  Assert.ok(t3, "Theme addon should exist");
+  Assert.ok(d, "Theme addon should exist");
+
+  t1.userDisabled = t2.userDisabled = t3.userDisabled = true;
+  Assert.ok(!t1.isActive, "Theme should be disabled");
+  Assert.ok(!t2.isActive, "Theme should be disabled");
+  Assert.ok(!t3.isActive, "Theme should be disabled");
+  Assert.ok(d.isActive, "Default theme should be active");
+
+  yield promiseRestartManager();
+
+  [ t1, t2, t3, d ] = yield promiseAddonsByIDs(THEME_IDS);
+  Assert.ok(!t1.isActive, "Theme should still be disabled");
+  Assert.ok(!t2.isActive, "Theme should still be disabled");
+  Assert.ok(!t3.isActive, "Theme should still be disabled");
+  Assert.ok(d.isActive, "Default theme should still be active");
+
+  gActiveTheme = d.id;
+});
+
+/**
+ * Set the `userDisabled` property of one specific theme and check if the theme
+ * switching works as expected by checking the state of all installed themes.
+ *
+ * @param {String}  which    ID of the addon to set the `userDisabled` property on
+ * @param {Boolean} disabled Flag value to switch to
+ */
+function* setDisabledStateAndCheck(which, disabled = false) {
+  if (disabled)
+    Assert.equal(which, gActiveTheme, "Only the active theme can be disabled");
+
+  let themeToDisable = disabled ? which : gActiveTheme;
+  let themeToEnable = disabled ? DEFAULT_THEME : which;
+  let expectRestart = !!(REQUIRE_RESTART[themeToDisable] || REQUIRE_RESTART[themeToEnable]);
+
+  let expectedStates = {
+    [themeToDisable]: true,
+    [themeToEnable]: false
+  };
+  let expectedEvents = {
+    [themeToDisable]: [ [ "onDisabling", expectRestart ] ],
+    [themeToEnable]: [ [ "onEnabling", expectRestart ] ]
+  };
+  if (!expectRestart) {
+    expectedEvents[themeToDisable].push([ "onDisabled", false ]);
+    expectedEvents[themeToEnable].push([ "onEnabled", false ]);
+  }
+
+  // Set the state of the theme to change.
+  let theme = yield promiseAddonByID(which);
+  prepare_test(expectedEvents);
+  theme.userDisabled = disabled;
+
+  let isDisabled;
+  for (theme of yield promiseAddonsByIDs(THEME_IDS)) {
+    isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true;
+    Assert.equal(theme.userDisabled, isDisabled,
+      `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`);
+    // Some themes need a restart to get their act together.
+    if (expectRestart && (theme.id == themeToEnable || theme.id == themeToDisable)) {
+      let expectedFlag = theme.id == themeToEnable ? AddonManager.PENDING_ENABLE : AddonManager.PENDING_DISABLE;
+      Assert.ok(hasFlag(theme.pendingOperations, expectedFlag),
+        "When expecting a restart, the pending operation flags should match");
+    } else {
+      Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE,
+        "There should be no pending operations when no restart is expected");
+      Assert.equal(theme.isActive, !isDisabled,
+        `Theme '${theme.id} should be ${isDisabled ? "in" : ""}active`);
+    }
+  }
+
+  yield promiseRestartManager();
+
+  // All should still be good after a restart of the Addon Manager.
+  for (theme of yield promiseAddonsByIDs(THEME_IDS)) {
+    isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true;
+    Assert.equal(theme.userDisabled, isDisabled,
+      `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`);
+    Assert.equal(theme.isActive, !isDisabled,
+      `Theme '${theme.id}' should be ${isDisabled ? "in" : ""}active`);
+    Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE,
+      "There should be no pending operations left");
+    if (!isDisabled)
+      gActiveTheme = theme.id;
+  }
+
+  ensure_test_completed();
+}
+
+add_task(function* test_dss_themes() {
+  // Enable the complete theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[0]);
+
+  // Disabling the complete theme should revert to the default theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[0], true);
+
+  // Enable it again.
+  yield* setDisabledStateAndCheck(THEME_IDS[0]);
+
+  // Enabling a WebExtension theme should disable the active theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[1]);
+
+  // Switching back should disable the WebExtension theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[0]);
+});
+
+add_task(function* test_WebExtension_themes() {
+  // Enable the WebExtension theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[1]);
+
+  // Disabling WebExtension should revert to the default theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[1], true);
+
+  // Enable it again.
+  yield* setDisabledStateAndCheck(THEME_IDS[1]);
+
+  // Enabling an LWT should disable the active theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[2]);
+
+  // Switching back should disable the LWT.
+  yield* setDisabledStateAndCheck(THEME_IDS[1]);
+});
+
+add_task(function* test_LWTs() {
+  // Start with enabling an LWT.
+  yield* setDisabledStateAndCheck(THEME_IDS[2]);
+
+  // Disabling LWT should revert to the default theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[2], true);
+
+  // Enable it again.
+  yield* setDisabledStateAndCheck(THEME_IDS[2]);
+
+  // Enabling a WebExtension theme should disable the active theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[1]);
+
+  // Switching back should disable the LWT.
+  yield* setDisabledStateAndCheck(THEME_IDS[2]);
+});
+
+add_task(function* test_default_theme() {
+  // Explicitly enable the default theme.
+  yield* setDisabledStateAndCheck(DEFAULT_THEME);
+
+  // Swith to the WebExtension theme.
+  yield* setDisabledStateAndCheck(THEME_IDS[1]);
+
+  // Enable it again.
+  yield* setDisabledStateAndCheck(DEFAULT_THEME);
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
@@ -3,10 +3,12 @@ head = head_addons.js head_unpack.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android'
 dupe-manifest =
 tags = addons
 
 [test_webextension_paths.js]
 tags = webextensions
+[test_webextension_theme.js]
+tags = webextensions
 
 [include:xpcshell-shared.ini]