Bug 1330349 - Part 5 - add tests for new theme type WebExtensions uninstall, enable and disable behavior. r=mossop
☠☠ backed out by 28cdd3a63674 ☠ ☠
authorMike de Boer <mdeboer@mozilla.com>
Wed, 01 Mar 2017 18:03:21 +0100
changeset 394404 052d09a75fca21b2c62e78c0f9f62c2af36271fb
parent 394403 493cda68b22da5dc22829eabee301890b8e12dff
child 394405 ff033748d4c6d19493efd9ae81fd277522363f09
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmossop
bugs1330349
milestone54.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 1330349 - Part 5 - add tests for new theme type WebExtensions uninstall, enable and disable behavior. r=mossop MozReview-Commit-ID: AwYrDqwQXBQ
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]