Bug 1094821 - Make it possible for an application to load extra themes into LightweightThemeManager.usedThemes;r=Gijs
authorBrian Grinstead <bgrinstead@mozilla.com>
Mon, 23 Mar 2015 15:32:43 -0700
changeset 264141 8129ad6db86a37c4efcefb66c77b776c96307093
parent 264140 a12de055e090438061e91c3731041de672be0fe6
child 264142 683bcd6c86d019dabba207466f5f61b7e1e7b41d
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1094821
milestone39.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 1094821 - Make it possible for an application to load extra themes into LightweightThemeManager.usedThemes;r=Gijs When calling addBuiltInTheme, theme objects are stored in a Map keyed on ID and are appended onto the usedThemes array. These aren't removeable and don't get stored in the pref.
toolkit/mozapps/extensions/LightweightThemeManager.jsm
toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js
--- a/toolkit/mozapps/extensions/LightweightThemeManager.jsm
+++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm
@@ -90,23 +90,29 @@ var _themeIDBeingDisabled = null;
       _prefs.setCharPref("selectedThemeID", themes[0].id);
     }
   }
 })();
 
 this.LightweightThemeManager = {
   get name() "LightweightThemeManager",
 
+  // Themes that can be added for an application.  They can't be removed, and
+  // will always show up at the top of the list.
+  _builtInThemes: new Map(),
+
   get usedThemes () {
+    let themes = [];
     try {
-      return JSON.parse(_prefs.getComplexValue("usedThemes",
-                                               Ci.nsISupportsString).data);
-    } catch (e) {
-      return [];
-    }
+      themes = JSON.parse(_prefs.getComplexValue("usedThemes",
+                                                 Ci.nsISupportsString).data);
+    } catch (e) { }
+
+    themes.push(...this._builtInThemes.values());
+    return themes;
   },
 
   get currentTheme () {
     let selectedThemeID = null;
     try {
       selectedThemeID = _prefs.getCharPref("selectedThemeID");
     } catch (e) {}
 
@@ -147,32 +153,56 @@ this.LightweightThemeManager = {
       if (usedTheme.id == aId)
         return usedTheme;
     }
     return null;
   },
 
   forgetUsedTheme: function LightweightThemeManager_forgetUsedTheme(aId) {
     let theme = this.getUsedTheme(aId);
-    if (!theme)
+    if (!theme || LightweightThemeManager._builtInThemes.has(theme.id))
       return;
 
     let wrapper = new AddonWrapper(theme);
     AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
 
     var currentTheme = this.currentTheme;
     if (currentTheme && currentTheme.id == aId) {
       this.themeChanged(null);
       AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false);
     }
 
     _updateUsedThemes(_usedThemesExceptId(aId));
     AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
   },
 
+  addBuiltInTheme: function LightweightThemeManager_addBuiltInTheme(theme) {
+    if (!theme || !theme.id || this.usedThemes.some(t => t.id == theme.id)) {
+      throw new Error("Trying to add invalid builtIn theme");
+    }
+
+    this._builtInThemes.set(theme.id, theme);
+  },
+
+  forgetBuiltInTheme: function LightweightThemeManager_forgetBuiltInTheme(id) {
+    if (!this._builtInThemes.has(id)) {
+      let currentTheme = this.currentTheme;
+      if (currentTheme && currentTheme.id == id) {
+        this.currentTheme = null;
+      }
+    }
+    return this._builtInThemes.delete(id);
+  },
+
+  clearBuiltInThemes: function LightweightThemeManager_clearBuiltInThemes() {
+    for (let id of this._builtInThemes.keys()) {
+      this.forgetBuiltInTheme(id);
+    }
+  },
+
   previewTheme: function LightweightThemeManager_previewTheme(aData) {
     let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
     cancel.data = false;
     Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested",
                                  JSON.stringify(aData));
     if (cancel.data)
       return;
 
@@ -488,17 +518,21 @@ function AddonWrapper(aTheme) {
 
   this.__defineGetter__("size", function AddonWrapper_sizeGetter() {
     // The size changes depending on whether the theme is in use or not, this is
     // probably not worth exposing.
     return null;
   });
 
   this.__defineGetter__("permissions", function AddonWrapper_permissionsGetter() {
-    let permissions = AddonManager.PERM_CAN_UNINSTALL;
+    let permissions = 0;
+
+    // Do not allow uninstall of builtIn themes.
+    if (!LightweightThemeManager._builtInThemes.has(aTheme.id))
+      permissions = AddonManager.PERM_CAN_UNINSTALL;
     if (this.userDisabled)
       permissions |= AddonManager.PERM_CAN_ENABLE;
     else
       permissions |= AddonManager.PERM_CAN_DISABLE;
     return permissions;
   });
 
   this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() {
@@ -705,16 +739,19 @@ function _usedThemesExceptId(aId)
 
 function _version(aThemeData)
   aThemeData.version || "";
 
 function _makeURI(aURL, aBaseURI)
   Services.io.newURI(aURL, null, aBaseURI);
 
 function _updateUsedThemes(aList) {
+  // Remove app-specific themes before saving them to the usedThemes pref.
+  aList = aList.filter(theme => !LightweightThemeManager._builtInThemes.has(theme.id));
+
   // Send uninstall events for all themes that need to be removed.
   while (aList.length > _maxUsedThemes) {
     let wrapper = new AddonWrapper(aList[aList.length - 1]);
     AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
     aList.pop();
     AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
   }
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js
@@ -14,28 +14,30 @@ function dummy(id) {
     name: Math.random().toString(),
     headerURL: "http://lwttest.invalid/a.png",
     footerURL: "http://lwttest.invalid/b.png",
     textcolor: Math.random().toString(),
     accentcolor: Math.random().toString()
   };
 }
 
+function hasPermission(aAddon, aPerm) {
+  var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
+  return !!(aAddon.permissions & perm);
+}
+
 function run_test() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
   startupManager();
 
   Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 8);
 
-  var temp = {};
-  Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
-  do_check_eq(typeof temp.LightweightThemeManager, "object");
+  let {LightweightThemeManager: ltm} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
 
-  var ltm = temp.LightweightThemeManager;
-
+  do_check_eq(typeof ltm, "object");
   do_check_eq(typeof ltm.usedThemes, "object");
   do_check_eq(ltm.usedThemes.length, 0);
   do_check_eq(ltm.currentTheme, null);
 
   ltm.previewTheme(dummy("preview0"));
   do_check_eq(ltm.usedThemes.length, 0);
   do_check_eq(ltm.currentTheme, null);
 
@@ -506,9 +508,91 @@ function run_test() {
 
   ltm.currentTheme = dummy("x33");
 
   do_check_eq(ltm.usedThemes.length, 32);
 
   Services.prefs.clearUserPref("lightweightThemes.maxUsedThemes");
 
   do_check_eq(ltm.usedThemes.length, 30);
+
+  let usedThemes = ltm.usedThemes;
+  for (let theme of usedThemes) {
+    ltm.forgetUsedTheme(theme.id);
+  }
+
+  // Check builtInTheme functionality for Bug 1094821
+  do_check_eq(ltm._builtInThemes.toString(), "[object Map]");
+  do_check_eq([...ltm._builtInThemes.entries()].length, 0);
+  do_check_eq(ltm.usedThemes.length, 0);
+
+  ltm.addBuiltInTheme(dummy("builtInTheme0"));
+  do_check_eq([...ltm._builtInThemes].length, 1);
+  do_check_eq(ltm.usedThemes.length, 1);
+  do_check_eq(ltm.usedThemes[0].id, "builtInTheme0");
+
+  ltm.addBuiltInTheme(dummy("builtInTheme1"));
+  do_check_eq([...ltm._builtInThemes].length, 2);
+  do_check_eq(ltm.usedThemes.length, 2);
+  do_check_eq(ltm.usedThemes[1].id, "builtInTheme1");
+
+  // Clear all and then re-add
+  ltm.clearBuiltInThemes();
+  do_check_eq([...ltm._builtInThemes].length, 0);
+  do_check_eq(ltm.usedThemes.length, 0);
+
+  ltm.addBuiltInTheme(dummy("builtInTheme0"));
+  ltm.addBuiltInTheme(dummy("builtInTheme1"));
+  do_check_eq([...ltm._builtInThemes].length, 2);
+  do_check_eq(ltm.usedThemes.length, 2);
+
+  do_test_pending();
+
+  AddonManager.getAddonByID("builtInTheme0@personas.mozilla.org", aAddon => {
+    // App specific theme can't be uninstalled or disabled,
+    // but can be enabled (since it isn't already applied).
+    do_check_eq(hasPermission(aAddon, "uninstall"), false);
+    do_check_eq(hasPermission(aAddon, "disable"), false);
+    do_check_eq(hasPermission(aAddon, "enable"), true);
+
+    ltm.currentTheme = dummy("x0");
+    do_check_eq([...ltm._builtInThemes].length, 2);
+    do_check_eq(ltm.usedThemes.length, 3);
+    do_check_eq(ltm.usedThemes[0].id, "x0");
+    do_check_eq(ltm.currentTheme.id, "x0");
+    do_check_eq(ltm.usedThemes[1].id, "builtInTheme0");
+    do_check_eq(ltm.usedThemes[2].id, "builtInTheme1");
+
+    Assert.throws(() => { ltm.addBuiltInTheme(dummy("builtInTheme0")) },
+      "Exception is thrown adding a duplicate theme");
+    Assert.throws(() => { ltm.addBuiltInTheme("not a theme object") },
+      "Exception is thrown adding an invalid theme");
+
+    AddonManager.getAddonByID("x0@personas.mozilla.org", aAddon => {
+      // Currently applied (non-app-specific) can be uninstalled or disabled,
+      // but can't be enabled (since it's already applied).
+      do_check_eq(hasPermission(aAddon, "uninstall"), true);
+      do_check_eq(hasPermission(aAddon, "disable"), true);
+      do_check_eq(hasPermission(aAddon, "enable"), false);
+
+      ltm.forgetUsedTheme("x0");
+      do_check_eq(ltm.currentTheme, null);
+
+      // Removing the currently applied app specific theme should unapply it
+      ltm.currentTheme = ltm.getUsedTheme("builtInTheme0");
+      do_check_eq(ltm.currentTheme.id, "builtInTheme0");
+      do_check_true(ltm.forgetBuiltInTheme("builtInTheme0"));
+      do_check_eq(ltm.currentTheme, null);
+
+      do_check_eq([...ltm._builtInThemes].length, 1);
+      do_check_eq(ltm.usedThemes.length, 1);
+
+      do_check_true(ltm.forgetBuiltInTheme("builtInTheme1"));
+      do_check_false(ltm.forgetBuiltInTheme("not-an-existing-theme-id"));
+
+      do_check_eq([...ltm._builtInThemes].length, 0);
+      do_check_eq(ltm.usedThemes.length, 0);
+      do_check_eq(ltm.currentTheme, null);
+
+      do_test_finished();
+    });
+  });
 }