Bug 510909: Integrate most recently used lightweight themes into the themes list in the add-ons manager. r=robstrong, r=dao
authorDave Townsend <dtownsend@oxymoronical.com>
Thu, 17 Sep 2009 10:03:33 +0100
changeset 31856 e5a3d46e380be9adf810a235d1cc7397b9af4896
parent 31855 50d0ce028d8cfd3832cdb2a67d8d82d4784d40f9
child 31857 3eff386a77fbc5a7a8cd65a96f446faedb1a8676
push id211
push userdtownsend@mozilla.com
push dateTue, 22 Sep 2009 15:12:33 +0000
reviewersrobstrong, dao
bugs510909
milestone1.9.2b1pre
Bug 510909: Integrate most recently used lightweight themes into the themes list in the add-ons manager. r=robstrong, r=dao
toolkit/mozapps/extensions/content/about.js
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xul
toolkit/mozapps/extensions/src/LightweightThemeManager.jsm
toolkit/mozapps/extensions/src/nsExtensionManager.js.in
toolkit/mozapps/extensions/test/Makefile.in
toolkit/mozapps/extensions/test/browser/Makefile.in
toolkit/mozapps/extensions/test/browser/browser_bug510909.js
toolkit/themes/gnomestripe/mozapps/extensions/extensions.css
toolkit/themes/pinstripe/mozapps/extensions/extensions.css
toolkit/themes/winstripe/mozapps/extensions/extensions.css
--- a/toolkit/mozapps/extensions/content/about.js
+++ b/toolkit/mozapps/extensions/content/about.js
@@ -57,19 +57,23 @@ function init()
                 
   // Name       
   var nameArc = rdfs.GetResource(EM_NS("name"));
   var name = gExtensionDB.GetTarget(extension, nameArc, true);
   name = name.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
   // Version
   var versionArc = rdfs.GetResource(EM_NS("version"));
   var version = gExtensionDB.GetTarget(extension, versionArc, true);
-  version = version.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
+  if (version)
+    version = version.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
   // Description
-  var descriptionArc = rdfs.GetResource(EM_NS("description"));
+  if (gExtensionDB.hasArcOut(extension, rdfs.GetResource(EM_NS("lwtheme"))))
+    var descriptionArc = rdfs.GetResource(EM_NS("lwdescription"));
+  else
+    descriptionArc = rdfs.GetResource(EM_NS("description"));
   var description = gExtensionDB.GetTarget(extension, descriptionArc, true);
   if (description)
     description = description.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
   // Home Page URL
   var homepageArc = rdfs.GetResource(EM_NS("homepageURL"));
   var homepage = gExtensionDB.GetTarget(extension, homepageArc, true);
   if (homepage) {
     homepage = homepage.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
@@ -91,20 +95,26 @@ function init()
   var creator = gExtensionDB.GetTarget(extension, creatorArc, true);
   if (creator)
     creator = creator.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
   
   document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [name]);
   var extensionName = document.getElementById("extensionName");
   extensionName.setAttribute("value", name);
   var extensionVersion = document.getElementById("extensionVersion");
-  extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [version]));
+  if (version)
+    extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [version]));
+  else
+    extensionVersion.hidden = true;
   
   var extensionDescription = document.getElementById("extensionDescription");
-  extensionDescription.appendChild(document.createTextNode(description));
+  if (description)
+    extensionDescription.appendChild(document.createTextNode(description));
+  else
+    extensionDescription.hidden = true;
   
   var extensionCreator = document.getElementById("extensionCreator");
   extensionCreator.setAttribute("value", creator);
   
   var extensionHomepage = document.getElementById("extensionHomepage");
   if (homepage) {
     extensionHomepage.setAttribute("homepageURL", homepage);
     extensionHomepage.setAttribute("tooltiptext", homepage);
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -47,49 +47,63 @@ const nsIIOService           = Component
 const nsIFileProtocolHandler = Components.interfaces.nsIFileProtocolHandler;
 const nsIURL                 = Components.interfaces.nsIURL;
 const nsIAppStartup          = Components.interfaces.nsIAppStartup;
 
 var gView             = null;
 var gExtensionManager = null;
 var gExtensionsView   = null;
 var gExtensionStrings = null;
-var gCurrentTheme     = "classic/1.0";
-var gDefaultTheme     = "classic/1.0";
 var gDownloadManager  = null;
 var gObserverIndex    = -1;
 var gInSafeMode       = false;
 var gCheckCompat      = true;
 var gCheckUpdateSecurity = true;
 var gUpdatesOnly      = false;
 var gAppID            = "";
 var gPref             = null;
 var gPriorityCount    = 0;
 var gInstalling       = false;
 var gPendingActions   = false;
 var gPlugins          = null;
 var gPluginsDS        = null;
 var gSearchDS         = null;
+var gLWThemeDS        = null;
 var gAddonRepository  = null;
 var gShowGetAddonsPane = false;
 var gRetrievedResults = false;
 var gRecommendedAddons = null;
 var gRDF              = null;
 var gPendingInstalls  = {};
 var gNewAddons        = [];
 
+// The default heavyweight theme for the app.
+var gDefaultTheme     = null;
+// The heavyweight theme currently in use.
+var gCurrentTheme     = null;
+// The heavyweight theme to switch to after the application is restarted.
+// This will be equal to gCurrentTheme if no theme change is pending.
+var gThemeToSelect    = null;
+
+// The lightweight theme to switch to after the application is restarted.
+// This will be equal to LightweightThemeManager.currentTheme if no lightweight
+// theme change is pending or null if lightweight theme should be turned off
+// after the restart.
+var gLWThemeToSelect  = null;
+
 const PREF_EM_CHECK_COMPATIBILITY           = "extensions.checkCompatibility";
 const PREF_EM_CHECK_UPDATE_SECURITY         = "extensions.checkUpdateSecurity";
 const PREF_EXTENSIONS_GETMORETHEMESURL      = "extensions.getMoreThemesURL";
 const PREF_EXTENSIONS_GETMOREEXTENSIONSURL  = "extensions.getMoreExtensionsURL";
 const PREF_EXTENSIONS_GETMOREPLUGINSURL     = "extensions.getMorePluginsURL";
 const PREF_EXTENSIONS_DSS_ENABLED           = "extensions.dss.enabled";
 const PREF_EXTENSIONS_DSS_SWITCHPENDING     = "extensions.dss.switchPending";
 const PREF_EXTENSIONS_HIDE_INSTALL_BTN      = "extensions.hideInstallButton";
 const PREF_DSS_SKIN_TO_SELECT               = "extensions.lastSelectedSkin";
+const PREF_LWTHEME_TO_SELECT                = "extensions.lwThemeToSelect";
 const PREF_GENERAL_SKINS_SELECTEDSKIN       = "general.skins.selectedSkin";
 const PREF_UPDATE_NOTIFYUSER                = "extensions.update.notifyUser";
 const PREF_GETADDONS_SHOWPANE               = "extensions.getAddons.showPane";
 const PREF_GETADDONS_REPOSITORY             = "extensions.getAddons.repository";
 const PREF_GETADDONS_MAXRESULTS             = "extensions.getAddons.maxResults";
 
 const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
 const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";
@@ -99,29 +113,31 @@ const URI_NOTIFICATION_ICON_INFO      = 
 const URI_NOTIFICATION_ICON_WARNING   = "moz-icon://stock/gtk-dialog-warning?size=menu";
 #else
 const URI_NOTIFICATION_ICON_INFO      = "chrome://global/skin/icons/information-16.png";
 const URI_NOTIFICATION_ICON_WARNING   = "chrome://global/skin/icons/warning-16.png";
 #endif
 
 const RDFURI_ITEM_ROOT    = "urn:mozilla:item:root";
 const PREFIX_ITEM_URI     = "urn:mozilla:item:";
+const PREFIX_LWTHEME_URI  = "urn:mozilla:lwtheme:";
 const PREFIX_NS_EM        = "http://www.mozilla.org/2004/em-rdf#";
 const kXULNSURI           = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const XMLURI_PARSE_ERROR  = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
 
 const OP_NONE                         = "";
 const OP_NEEDS_INSTALL                = "needs-install";
 const OP_NEEDS_UPGRADE                = "needs-upgrade";
 const OP_NEEDS_UNINSTALL              = "needs-uninstall";
 const OP_NEEDS_ENABLE                 = "needs-enable";
 const OP_NEEDS_DISABLE                = "needs-disable";
 
 Components.utils.import("resource://gre/modules/PluralForm.jsm");
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm");
 
 ///////////////////////////////////////////////////////////////////////////////
 // Utility Functions
 function setElementDisabledByID(aID, aDoDisable) {
   var element = document.getElementById(aID);
   if (element) {
     if (aDoDisable)
       element.setAttribute("disabled", "true");
@@ -138,16 +154,22 @@ function getItemForInternalName(aInterna
   var property = gRDF.GetResource(PREFIX_NS_EM + "internalName");
   var name = gRDF.GetLiteral(aInternalName);
   var id = gExtensionManager.datasource.GetSource(property, name, true)
   if (id && id instanceof Components.interfaces.nsIRDFResource)
     return document.getElementById(id.ValueUTF8);
   return null;
 }
 
+function getActivedThemeItem() {
+  if (gLWThemeToSelect)
+    return document.getElementById(PREFIX_LWTHEME_URI + gLWThemeToSelect.id);
+  return getItemForInternalName(gThemeToSelect);
+}
+
 function isSafeURI(aURI) {
   try {
     var uri = makeURI(aURI);
     var scheme = uri.scheme;
   } catch (ex) {}
   return (uri && (scheme == "http" || scheme == "https" || scheme == "ftp"));
 }
 
@@ -303,16 +325,17 @@ function showView(aView) {
                         ["description", "?description"],
                         ["downloadURL", "?downloadURL"],
                         ["isDisabled", "?isDisabled"],
                         ["hidden", "?hidden"],
                         ["homepageURL", "?homepageURL"],
                         ["iconURL", "?iconURL"],
                         ["internalName", "?internalName"],
                         ["locked", "?locked"],
+                        ["lwtheme", "?lwtheme"],
                         ["name", "?name"],
                         ["optionsURL", "?optionsURL"],
                         ["opType", "?opType"],
                         ["plugin", "?plugin"],
                         ["previewImage", "?previewImage"],
                         ["satisfiesDependencies", "?satisfiesDependencies"],
                         ["providesUpdatesSecurely", "?providesUpdatesSecurely"],
                         ["type", "?type"],
@@ -478,17 +501,17 @@ function showView(aView) {
   document.getElementById("searchPanel").hidden = aView != "search";
 
   gExtensionsView.setAttribute("sortDirection", direction);
   AddonsViewBuilder.updateView(types, displays, bindingList, null);
 
   if (aView == "updates" || aView == "installs")
     gExtensionsView.selectedItem = gExtensionsView.children[0];
   else if (isThemes)
-    gExtensionsView.selectedItem = getItemForInternalName(gCurrentTheme);
+    gExtensionsView.selectedItem = getActivedThemeItem();
 
   if (showSkip) {
     var button = document.getElementById("installUpdatesAllButton");
     button.setAttribute("default", "true");
     window.setTimeout(function () { button.focus(); }, 0);
   } else
     document.getElementById("installUpdatesAllButton").removeAttribute("default");
 
@@ -525,20 +548,16 @@ function updateLastSelected(aView) {
   // (e.g. they themselves persist the last selected add-on in the view).
   // This prevents opening updates and installs views when they were the last
   // view selected and instead this will open the previously selected
   // extensions, themes, locales, plug-ins, etc. view.
   if (viewButton.hasAttribute("persist"))
     viewGroup.setAttribute("last-selected", aView);
 }
 
-function LOG(msg) {
-  dump("*** " + msg + "\n");
-}
-
 function getIDFromResourceURI(aURI)
 {
   if (aURI.substring(0, PREFIX_ITEM_URI.length) == PREFIX_ITEM_URI)
     return aURI.substring(PREFIX_ITEM_URI.length);
   return aURI;
 }
 
 function showProgressBar() {
@@ -863,16 +882,110 @@ function initSearchDS() {
                         .createInstance(Components.interfaces.nsIRDFDataSource);
   gExtensionsView.database.AddDataSource(gSearchDS);
   var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                             .getService(nsIIOService);
   if (!ioService.offline)
     retrieveRepositoryAddons(document.getElementById("searchfield").value);
 }
 
+function initLWThemeDS() {
+  gLWThemeDS = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
+                         .createInstance(Components.interfaces.nsIRDFDataSource);
+  rebuildLWThemeDS();
+}
+
+function rebuildLWThemeDS() {
+  var rdfCU = Components.classes["@mozilla.org/rdf/container-utils;1"]
+                        .getService(Components.interfaces.nsIRDFContainerUtils);
+  var rootctr = rdfCU.MakeSeq(gLWThemeDS, gRDF.GetResource(RDFURI_ITEM_ROOT));
+  var themes = LightweightThemeManager.usedThemes;
+
+  // Running in a batch stops the template builder from running
+  gLWThemeDS.beginUpdateBatch();
+
+  cleanDataSource(gLWThemeDS, rootctr);
+
+  for (var i = 0; i < themes.length; i++) {
+    var theme = themes[i];
+
+    var themeNode = gRDF.GetResource(PREFIX_LWTHEME_URI + theme.id);
+    rootctr.AppendElement(themeNode);
+    gLWThemeDS.Assert(themeNode,
+                      gRDF.GetResource(PREFIX_NS_EM + "name"),
+                      gRDF.GetLiteral(theme.name),
+                      true);
+    gLWThemeDS.Assert(themeNode,
+                      gRDF.GetResource(PREFIX_NS_EM + "addonID"),
+                      gRDF.GetLiteral(theme.id),
+                      true);
+    gLWThemeDS.Assert(themeNode,
+                      gRDF.GetResource(PREFIX_NS_EM + "isDisabled"),
+                      gRDF.GetLiteral("false"),
+                      true);
+    gLWThemeDS.Assert(themeNode,
+                      gRDF.GetResource(PREFIX_NS_EM + "blocklisted"),
+                      gRDF.GetLiteral("false"),
+                      true);
+    gLWThemeDS.Assert(themeNode,
+                      gRDF.GetResource(PREFIX_NS_EM + "blocklistedsoft"),
+                      gRDF.GetLiteral("false"),
+                      true);
+    gLWThemeDS.Assert(themeNode,
+                      gRDF.GetResource(PREFIX_NS_EM + "compatible"),
+                      gRDF.GetLiteral("true"),
+                      true);
+    gLWThemeDS.Assert(themeNode,
+                      gRDF.GetResource(PREFIX_NS_EM + "lwtheme"),
+                      gRDF.GetLiteral("true"),
+                      true);
+    gLWThemeDS.Assert(themeNode,
+                      gRDF.GetResource(PREFIX_NS_EM + "type"),
+                      gRDF.GetIntLiteral(nsIUpdateItem.TYPE_THEME),
+                      true);
+    if (theme.author) {
+      gLWThemeDS.Assert(themeNode,
+                        gRDF.GetResource(PREFIX_NS_EM + "description"),
+                        gRDF.GetLiteral(getExtensionString("lightweightThemeDescription",
+                                                           [theme.author])),
+                        true);
+      gLWThemeDS.Assert(themeNode,
+                        gRDF.GetResource(PREFIX_NS_EM + "creator"),
+                        gRDF.GetLiteral(theme.author),
+                        true);
+    }
+    if (theme.description) {
+      gLWThemeDS.Assert(themeNode,
+                        gRDF.GetResource(PREFIX_NS_EM + "lwdescription"),
+                        gRDF.GetLiteral(theme.description),
+                        true);
+    }
+    if (theme.homepageURL) {
+      gLWThemeDS.Assert(themeNode,
+                        gRDF.GetResource(PREFIX_NS_EM + "homepageURL"),
+                        gRDF.GetLiteral(theme.homepageURL),
+                        true);
+    }
+    if (theme.previewURL) {
+      gLWThemeDS.Assert(themeNode,
+                        gRDF.GetResource(PREFIX_NS_EM + "previewImage"),
+                        gRDF.GetLiteral(theme.previewURL),
+                        true);
+    }
+    if (theme.iconURL) {
+      gLWThemeDS.Assert(themeNode,
+                        gRDF.GetResource(PREFIX_NS_EM + "iconURL"),
+                        gRDF.GetLiteral(theme.iconURL),
+                        true);
+    }
+  }
+
+  gLWThemeDS.endUpdateBatch();
+}
+
 function initPluginsDS()
 {
   gPluginsDS = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
                          .createInstance(Components.interfaces.nsIRDFDataSource);
   rebuildPluginsDS();
 }
 
 function rebuildPluginsDS()
@@ -993,23 +1106,34 @@ function togglePluginDisabled(aName, aDe
 function Startup()
 {
   gExtensionStrings = document.getElementById("extensionsStrings");
   gPref = Components.classes["@mozilla.org/preferences-service;1"]
                     .getService(Components.interfaces.nsIPrefBranch2);
   var defaultPref = gPref.QueryInterface(Components.interfaces.nsIPrefService)
                          .getDefaultBranch(null);
   try {
-    gCurrentTheme = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
+    gThemeToSelect = gCurrentTheme = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
     gDefaultTheme = defaultPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
     if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING))
-      gCurrentTheme = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
+      gThemeToSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
   }
   catch (e) { }
 
+  if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
+    var id = gPref.getCharPref(PREF_LWTHEME_TO_SELECT);
+    if (id)
+      gLWThemeToSelect = LightweightThemeManager.getUsedTheme(id);
+    else
+      gLWThemeToSelect = null;
+  }
+  else {
+    gLWThemeToSelect = LightweightThemeManager.currentTheme;
+  }
+
   gExtensionsView = document.getElementById("extensionsView");
   gExtensionManager = Components.classes["@mozilla.org/extensions/manager;1"]
                                 .getService(nsIExtensionManager);
   var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
                           .getService(Components.interfaces.nsIXULAppInfo)
                           .QueryInterface(Components.interfaces.nsIXULRuntime);
   gInSafeMode = appInfo.inSafeMode;
   gAppID = appInfo.ID;
@@ -1033,16 +1157,18 @@ function Startup()
   gExtensionsView.controllers.appendController(gExtensionsViewController);
   gExtensionsView.addEventListener("select", onAddonSelect, false);
 
   gRDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
                    .getService(Components.interfaces.nsIRDFService);
 
   initPluginsDS();
   gExtensionsView.database.AddDataSource(gPluginsDS);
+  initLWThemeDS();
+  gExtensionsView.database.AddDataSource(gLWThemeDS);
   if (gShowGetAddonsPane)
     initSearchDS();
   gExtensionsView.database.AddDataSource(gExtensionManager.datasource);
   gExtensionsView.setAttribute("ref", RDFURI_ITEM_ROOT);
 
   document.getElementById("search-view").hidden = !gShowGetAddonsPane;
   updateOptionalViews();
 
@@ -1053,16 +1179,18 @@ function Startup()
 
   // Now look and see if we're being opened by XPInstall
   gDownloadManager = new XPInstallDownloadManager();
   var os = Components.classes["@mozilla.org/observer-service;1"]
                      .getService(Components.interfaces.nsIObserverService);
   os.addObserver(gDownloadManager, "xpinstall-download-started", false);
   os.addObserver(gAddonsMsgObserver, "addons-message-notification", false);
   os.addObserver(gPluginObserver, "plugins-list-updated", false);
+  os.addObserver(gLWThemeObserver, "lightweight-theme-list-changed", false);
+  os.addObserver(gLWThemeObserver, "lightweight-theme-changed", false);
 
   gObserverIndex = gExtensionManager.addInstallListener(gDownloadManager);
 
   if (!gCheckCompat) {
     var msgText = getExtensionString("disabledCompatMsg");
     var buttonLabel = getExtensionString("enableButtonLabel");
     var buttonAccesskey = getExtensionString("enableButtonAccesskey");
     var notifyData = "addons-enable-compatibility";
@@ -1231,16 +1359,18 @@ function Shutdown()
 
   gExtensionManager.removeInstallListenerAt(gObserverIndex);
 
   var os = Components.classes["@mozilla.org/observer-service;1"]
                      .getService(Components.interfaces.nsIObserverService);
   os.removeObserver(gAddonsMsgObserver, "addons-message-notification");
   os.removeObserver(gDownloadManager, "xpinstall-download-started");
   os.removeObserver(gPluginObserver, "plugins-list-updated");
+  os.removeObserver(gLWThemeObserver, "lightweight-theme-list-changed");
+  os.removeObserver(gLWThemeObserver, "lightweight-theme-changed");
   var currentNotification = document.getElementById("addonsMsg").currentNotification;
   if (currentNotification && currentNotification.value == "addons-no-updates")
     window.removeEventListener("select", noUpdatesDismiss, true);
 }
 
 var TemplateBuilderListener = {
   willRebuild: function(aBuilder) {
   },
@@ -1252,17 +1382,17 @@ var TemplateBuilderListener = {
         var item = document.getElementById(PREFIX_ITEM_URI + gNewAddons[i]);
         if (item)
           item.setAttribute("newAddon", "true");
       }
     }
 
     if (gView == "themes") {
       if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING)) {
-        var item = getItemForInternalName(gCurrentTheme);
+        var item = getActivedThemeItem();
         if (item)
           setRestartMessage(item);
       }
     }
   },
 
   QueryInterface: function (aIID)
   {
@@ -1631,16 +1761,23 @@ function onAddonSelect(aEvent)
           UpdateInfoLoader.loadInfo(uri);
         else
           previewImageDeck.selectedIndex = 4;
       }
     }
   }
 }
 
+function onPreviewImageError(aEvent) {
+  var previewImageDeck = document.getElementById("previewImageDeck");
+  var previewImage = document.getElementById("previewImage");
+  previewImageDeck.selectedIndex = 1;
+  previewImage.removeAttribute("src");
+}
+
 /**
  * Manages the retrieval of update information and the xsl stylesheet
  * used to format the inforation into chrome.
  */
 var UpdateInfoLoader = {
   _stylesheet: null,
   _styleRequest: null,
   _infoDocument: null,
@@ -2032,37 +2169,49 @@ const gAddonsMsgObserver = {
   }
 };
 
 const gPrefObserver = {
   observe: function (aSubject, aTopic, aData)
   {
     if (aData == PREF_GENERAL_SKINS_SELECTEDSKIN) {
       // Changed as the result of a dynamic theme switch
-      gCurrentTheme = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
+      gThemeToSelect = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
     }
     else if (aData == PREF_DSS_SKIN_TO_SELECT) {
       // Either a new skin has been selected or the switch has been cancelled
       if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING))
-        gCurrentTheme = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
+        gThemeToSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
       else
-        gCurrentTheme = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
+        gThemeToSelect = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
       updateOptionalViews();
       updateGlobalCommands();
     }
   }
 };
 
 const gPluginObserver = {
   observe: function (aSubject, aTopic, aData)
   {
     rebuildPluginsDS();
   }
 };
 
+const gLWThemeObserver = {
+  observe: function (aSubject, aTopic, aData) {
+    if (aTopic == "lightweight-theme-list-changed") {
+      rebuildLWThemeDS();
+    }
+    else if (aTopic == "lightweight-theme-changed") {
+      gLWThemeToSelect = LightweightThemeManager.currentTheme;
+      gPref.clearUserPref(PREF_LWTHEME_TO_SELECT);
+    }
+  }
+};
+
 function isXPInstallEnabled() {
   var enabled = false;
   var locked = false;
   try {
     enabled = gPref.getBoolPref("xpinstall.enabled");
     if (enabled)
       return true;
     locked = gPref.prefIsLocked("xpinstall.enabled");
@@ -2438,32 +2587,38 @@ var gExtensionsViewController = {
         return true;
       return false;
     }
     switch (aCommand) {
     case "cmd_installSearchResult":
       return selectedItem.getAttribute("action") == "" ||
              selectedItem.getAttribute("action") == "failed";
     case "cmd_useTheme":
+      if (selectedItem.hasAttribute("lwtheme"))
+        return !gLWThemeToSelect ||
+               selectedItem.getAttribute("addonID") != gLWThemeToSelect.id;
       return selectedItem.type == nsIUpdateItem.TYPE_THEME &&
              !selectedItem.isDisabled &&
              selectedItem.opType != OP_NEEDS_UNINSTALL &&
-             gCurrentTheme != selectedItem.getAttribute("internalName");
+             (gLWThemeToSelect ||
+              gThemeToSelect != selectedItem.getAttribute("internalName"));
     case "cmd_options":
       return selectedItem.type == nsIUpdateItem.TYPE_EXTENSION &&
              !selectedItem.isDisabled &&
              !gInSafeMode &&
              !selectedItem.opType &&
              selectedItem.getAttribute("optionsURL") != "";
     case "cmd_about":
       return selectedItem.opType != OP_NEEDS_INSTALL &&
              selectedItem.getAttribute("plugin") != "true";
     case "cmd_homepage":
       return selectedItem.getAttribute("homepageURL") != "";
     case "cmd_uninstall":
+      if (selectedItem.hasAttribute("lwtheme"))
+        return true;
       return (selectedItem.type != nsIUpdateItem.TYPE_THEME ||
              selectedItem.type == nsIUpdateItem.TYPE_THEME &&
              selectedItem.getAttribute("internalName") != gDefaultTheme) &&
              selectedItem.opType != OP_NEEDS_UNINSTALL &&
              selectedItem.getAttribute("locked") != "true" &&
              canWriteToLocation(selectedItem) &&
              !gExtensionsView.hasAttribute("update-operation");
     case "cmd_cancelUninstall":
@@ -2568,37 +2723,54 @@ var gExtensionsViewController = {
 
     cmd_close: function (aSelectedItem)
     {
       closeWindow(true);
     },
 
     cmd_useTheme: function (aSelectedItem)
     {
-      gCurrentTheme = aSelectedItem.getAttribute("internalName");
+      if (aSelectedItem.hasAttribute("lwtheme")) {
+        let newTheme = LightweightThemeManager.getUsedTheme(aSelectedItem.getAttribute("addonID"));
+        LightweightThemeManager.currentTheme = gLWThemeToSelect = newTheme;
 
-      // If choosing the current skin just reset the pending change
-      if (gCurrentTheme == gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
-        gPref.clearUserPref(PREF_EXTENSIONS_DSS_SWITCHPENDING);
-        gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
-        clearRestartMessage();
+        if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
+          clearRestartMessage();
+          setRestartMessage(aSelectedItem);
+        }
       }
       else {
-        if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_ENABLED)) {
-          gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, gCurrentTheme);
+        gThemeToSelect = aSelectedItem.getAttribute("internalName");
+
+        // If choosing the current skin just reset the pending change
+        if (gThemeToSelect == gCurrentTheme) {
+          gPref.clearUserPref(PREF_EXTENSIONS_DSS_SWITCHPENDING);
+          gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
+          gLWThemeToSelect = LightweightThemeManager.currentTheme = null;
+          clearRestartMessage();
         }
         else {
-          // Theme change will happen on next startup, this flag tells
-          // the Theme Manager that it needs to show "This theme will
-          // be selected after a restart" text in the selected theme
-          // item.
-          gPref.setBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING, true);
-          gPref.setCharPref(PREF_DSS_SKIN_TO_SELECT, gCurrentTheme);
-          clearRestartMessage();
-          setRestartMessage(aSelectedItem);
+          if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_ENABLED)) {
+            gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, gThemeToSelect);
+            gLWThemeToSelect = LightweightThemeManager.currentTheme = null;
+          }
+          else {
+            // Theme change will happen on next startup, this flag tells
+            // the Theme Manager that it needs to show "This theme will
+            // be selected after a restart" text in the selected theme
+            // item.
+            gPref.setBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING, true);
+            gPref.setCharPref(PREF_DSS_SKIN_TO_SELECT, gThemeToSelect);
+            if (gLWThemeToSelect) {
+              gLWThemeToSelect = null;
+              gPref.setCharPref(PREF_LWTHEME_TO_SELECT, "");
+            }
+            clearRestartMessage();
+            setRestartMessage(aSelectedItem);
+          }
         }
       }
 
       // Flush preference change to disk
       gPref.QueryInterface(Components.interfaces.nsIPrefService)
            .savePrefFile(null);
 
       // disable the useThemeButton
@@ -2691,28 +2863,41 @@ var gExtensionsViewController = {
       var includeUpdate = document.getAnonymousElementByAttribute(aSelectedItem, "anonid", "includeUpdate");
       includeUpdate.checked = !includeUpdate.checked;
     },
 
     cmd_uninstall: function (aSelectedItem)
     {
       // Confirm the uninstall
       var name = aSelectedItem.getAttribute("name");
-      var id = getIDFromResourceURI(aSelectedItem.id);
-      var dependentItems = gExtensionManager.getDependentItemListForID(id, true, { });
+      var dependentItems = [];
+      if (!aSelectedItem.hasAttribute("lwtheme")) {
+        var id = getIDFromResourceURI(aSelectedItem.id);
+        dependentItems = gExtensionManager.getDependentItemListForID(id, true, { });
+      }
       var result = confirmOperation(name, "uninstallTitle", "uninstallQueryMessage",
                                     "uninstallButton", "cancelButton",
                                     "uninstallWarnDependMsg", dependentItems);
       if (!result)
         return;
 
+      if (aSelectedItem.hasAttribute("lwtheme")) {
+        let lwid = aSelectedItem.getAttribute("addonID");
+        LightweightThemeManager.forgetUsedTheme(lwid);
+        if (gLWThemeToSelect && lwid == gLWThemeToSelect.id) {
+          gLWThemeToSelect = LightweightThemeManager.currentTheme;
+          gPref.clearUserPref(PREF_LWTHEME_TO_SELECT);
+        }
+        return;
+      }
+
       if (aSelectedItem.type == nsIUpdateItem.TYPE_THEME) {
         var theme = aSelectedItem.getAttribute("internalName");
         var selectedTheme = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
-        if (theme == gCurrentTheme) {
+        if (theme == gThemeToSelect) {
           if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING)) {
             var item = getItemForInternalName(selectedTheme);
             if (item && item.getAttribute("opType") == OP_NEEDS_UNINSTALL) {
               // We're uninstalling the theme to be switched to, but the current
               // theme is already marked for uninstall so switch to the default
               // theme
               this.cmd_useTheme(document.getElementById(PREFIX_ITEM_URI + "{972ce4c6-7e08-4474-a285-3208198ce6fd}"));
             }
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -209,17 +209,17 @@
             <vbox id="noThemeSelected" pack="center" align="center">
               <label class="previewText">&previewNoThemeSelected.label;</label>
             </vbox>
             <vbox id="noPreviewImage" pack="center" align="center">
               <label class="previewText">&previewNoPreviewImage.label;</label>
             </vbox>
             <vbox id="previewImageContainer" align="center" pack="center">
               <hbox>
-                <image id="previewImage"/>
+                <image id="previewImage" onerror="onPreviewImageError(event)"/>
               </hbox>
             </vbox>
             <vbox id="infoNoAddonSelected" align="center" pack="center">
               <label class="previewText">&infoNoAddonSelected.label;</label>
             </vbox>
             <vbox id="infoNoUpdateInfo" align="center" pack="center">
               <label class="previewText">&infoNoUpdateInfo.label;</label>
             </vbox>
--- a/toolkit/mozapps/extensions/src/LightweightThemeManager.jsm
+++ b/toolkit/mozapps/extensions/src/LightweightThemeManager.jsm
@@ -54,31 +54,39 @@ var LightweightThemeManager = {
     try {
       if (_prefs.getBoolPref("isThemeSelected"))
         return this.usedThemes[0];
     } catch (e) {}
     return null;
   },
 
   set currentTheme (aData) {
+    let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+    cancel.data = false;
+    _observerService.notifyObservers(cancel, "lightweight-theme-change-requested",
+                                     JSON.stringify(aData));
+
+    if (aData) {
+      let usedThemes = _usedThemesExceptId(aData.id);
+      if (cancel.data && _prefs.getBoolPref("isThemeSelected"))
+        usedThemes.splice(1, 0, aData);
+      else
+        usedThemes.unshift(aData);
+      _updateUsedThemes(usedThemes);
+    }
+
+    if (cancel.data)
+      return null;
+
     if (_previewTimer) {
       _previewTimer.cancel();
       _previewTimer = null;
     }
 
-    if (aData) {
-      let usedThemes = _usedThemesExceptId(aData.id);
-      usedThemes.unshift(aData);
-      _updateUsedThemes(usedThemes);
-
-      _prefs.setBoolPref("isThemeSelected", true);
-    } else {
-      _prefs.setBoolPref("isThemeSelected", false);
-    }
-
+    _prefs.setBoolPref("isThemeSelected", aData != null);
     _notifyWindows(aData);
 
     return aData;
   },
 
   getUsedTheme: function (aId) {
     var usedThemes = this.usedThemes;
     for (let i = 0; i < usedThemes.length; i++) {
@@ -95,16 +103,23 @@ var LightweightThemeManager = {
 
     _updateUsedThemes(_usedThemesExceptId(aId));
   },
 
   previewTheme: function (aData) {
     if (!aData)
       return;
 
+    let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+    cancel.data = false;
+    _observerService.notifyObservers(cancel, "lightweight-theme-preview-requested",
+                                     JSON.stringify(aData));
+    if (cancel.data)
+      return;
+
     if (_previewTimer)
       _previewTimer.cancel();
     else
       _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     _previewTimer.initWithCallback(_previewTimerCallback,
                                    MAX_PREVIEW_SECONDS * 1000,
                                    _previewTimer.TYPE_ONE_SHOT);
 
--- a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
+++ b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
@@ -48,16 +48,17 @@
 // - better logging
 //
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm");
 
 const PREF_EM_CHECK_COMPATIBILITY     = "extensions.checkCompatibility";
 const PREF_EM_CHECK_UPDATE_SECURITY   = "extensions.checkUpdateSecurity";
 const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
 const PREF_EM_ENABLED_ITEMS           = "extensions.enabledItems";
 const PREF_UPDATE_COUNT               = "extensions.update.count";
 const PREF_UPDATE_DEFAULT_URL         = "extensions.update.url";
 const PREF_EM_NEW_ADDONS_LIST         = "extensions.newAddons";
@@ -65,16 +66,17 @@ const PREF_EM_IGNOREMTIMECHANGES      = 
 const PREF_EM_DISABLEDOBSOLETE        = "extensions.disabledObsolete";
 const PREF_EM_EXTENSION_FORMAT        = "extensions.%UUID%.";
 const PREF_EM_ITEM_UPDATE_ENABLED     = "extensions.%UUID%.update.enabled";
 const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
 const PREF_EM_ITEM_UPDATE_URL         = "extensions.%UUID%.update.url";
 const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
 const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
 const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
+const PREF_LWTHEME_TO_SELECT          = "extensions.lwThemeToSelect";
 const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
 const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
 const PREF_EM_UPDATE_INTERVAL         = "extensions.update.interval";
 const PREF_UPDATE_NOTIFYUSER          = "extensions.update.notifyUser";
 const PREF_MATCH_OS_LOCALE            = "intl.locale.matchOS";
 const PREF_SELECTED_LOCALE            = "general.useragent.locale";
 
 const DIR_EXTENSIONS                  = "extensions";
@@ -177,16 +179,21 @@ var gDirService           = null;
 var gLoggingEnabled       = null;
 var gCheckCompatibility   = true;
 var gCheckUpdateSecurity  = true;
 var gLocale               = "en-US";
 var gFirstRun             = false;
 var gAllowFlush           = true;
 var gDSNeedsFlush         = false;
 var gManifestNeedsFlush   = false;
+var gDefaultTheme         = "classic/1.0";
+
+const URI_EXTENSION_MANAGER           = "chrome://mozapps/content/extensions/extensions.xul";
+const FEATURES_EXTENSION_MANAGER      = "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable";
+const FEATURES_EXTENSION_UPDATES      = "chrome,centerscreen,extra-chrome,dialog,resizable,modal";
 
 /**
  * Valid GUIDs fit this pattern.
  */
 var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
 
 // shared code for suppressing bad cert dialogs
 #include ../../shared/src/badCertHandler.js
@@ -2142,21 +2149,26 @@ function ExtensionManager() {
   } catch (ex) {
     // Provide a default for gXPCOMABI. It won't be compared to an
     // item's metadata (i.e. install.rdf can't specify e.g. WINNT_unknownABI
     // as targetPlatform), but it will be displayed in error messages and
     // transmitted to update URLs.
     gXPCOMABI = UNKNOWN_XPCOM_ABI;
   }
   gPref = Cc["@mozilla.org/preferences-service;1"].
-          getService(Ci.nsIPrefBranch2);
+          getService(Ci.nsIPrefBranch2).
+          QueryInterface(Ci.nsIPrefService);
+  var defaults = gPref.getDefaultBranch("");
+  gDefaultTheme = defaults.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
 
   gOS = Cc["@mozilla.org/observer-service;1"].
         getService(Ci.nsIObserverService);
   gOS.addObserver(this, "xpcom-shutdown", false);
+  gOS.addObserver(this, "lightweight-theme-preview-requested", false);
+  gOS.addObserver(this, "lightweight-theme-change-requested", false);
 
   gConsole = Cc["@mozilla.org/consoleservice;1"].
              getService(Ci.nsIConsoleService);
 
   gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
          getService(Ci.nsIRDFService);
   gInstallManifestRoot = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
 
@@ -2257,16 +2269,67 @@ ExtensionManager.prototype = {
       this._profileSelected();
       break;
     case "quit-application-requested":
       this._confirmCancelDownloadsOnQuit(subject);
       break;
     case "offline-requested":
       this._confirmCancelDownloadsOnOffline(subject);
       break;
+    case "lightweight-theme-preview-requested":
+      if (gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
+        let cancel = subject.QueryInterface(Ci.nsISupportsPRBool);
+        cancel.data = true;
+      }
+      break;
+    case "lightweight-theme-change-requested":
+      let theme = JSON.parse(data);
+      if (!theme)
+        return;
+
+      if (gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
+        if (getPref("getBoolPref", PREF_EM_DSS_ENABLED, false)) {
+          gPref.clearUserPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
+          return;
+        }
+
+        let cancel = subject.QueryInterface(Ci.nsISupportsPRBool);
+        cancel.data = true;
+        gPref.setBoolPref(PREF_DSS_SWITCHPENDING, true);
+        gPref.setCharPref(PREF_DSS_SKIN_TO_SELECT, gDefaultTheme);
+        gPref.setCharPref(PREF_LWTHEME_TO_SELECT, theme.id);
+
+        // Open the UI so the user can see that a restart is necessary
+        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+                 getService(Ci.nsIWindowMediator);
+        var win = wm.getMostRecentWindow("Extension:Manager");
+
+        if (win) {
+          win.showView("themes");
+          return;
+        }
+
+        var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+                 getService(Ci.nsIWindowWatcher);
+        var param = Cc["@mozilla.org/supports-array;1"].
+                    createInstance(Ci.nsISupportsArray);
+        var arg = Cc["@mozilla.org/supports-string;1"].
+                  createInstance(Ci.nsISupportsString);
+        arg.data = "themes";
+        param.AppendElement(arg);
+        ww.openWindow(null, URI_EXTENSION_MANAGER, null, FEATURES_EXTENSION_MANAGER, param);
+        return;
+      }
+      else {
+        // Cancel any pending theme change and allow the lightweight theme
+        // change to go ahead
+        gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
+        gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
+      }
+      break;
     case "xpcom-shutdown":
       this._shutdown();
       break;
     case "nsPref:changed":
       if (data == PREF_EM_LOGGING_ENABLED)
         this._loggingToggled();
       else if (data == PREF_EM_CHECK_COMPATIBILITY ||
                data == PREF_EM_CHECK_UPDATE_SECURITY)
@@ -2334,16 +2397,32 @@ ExtensionManager.prototype = {
   _profileSelected: function EM__profileSelected() {
     // Tell the Chrome Registry which Skin to select
     try {
       if (gPref.getBoolPref(PREF_DSS_SWITCHPENDING)) {
         var toSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
         gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, toSelect);
         gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
         gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
+
+        // If we've changed to a non-default theme make sure there is no
+        // lightweight theme selected
+        if (toSelect != gDefaultTheme) {
+          gPref.clearUserPref(PREF_LWTHEME_TO_SELECT);
+          LightweightThemeManager.currentTheme = null;
+        }
+      }
+
+      if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
+        var id = gPref.getCharPref(PREF_LWTHEME_TO_SELECT);
+        if (id)
+          LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(id);
+        else
+          LightweightThemeManager.currentTheme = null;
+        gPref.clearUserPref(PREF_LWTHEME_TO_SELECT);
       }
     }
     catch (e) {
     }
 
     if ("nsICrashReporter" in Ci && gApp instanceof Ci.nsICrashReporter) {
       // Annotate the crash report with the list of add-ons
       try {
@@ -2368,28 +2447,25 @@ ExtensionManager.prototype = {
 
   /**
    * Notify user that there are new addons updates
    */
   _showUpdatesWindow: function EM__showUpdatesWindow() {
     if (!getPref("getBoolPref", PREF_UPDATE_NOTIFYUSER, false))
       return;
 
-    const EMURL = "chrome://mozapps/content/extensions/extensions.xul";
-    const EMFEATURES = "chrome,centerscreen,extra-chrome,dialog,resizable,modal";
-
     var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
              getService(Ci.nsIWindowWatcher);
     var param = Cc["@mozilla.org/supports-array;1"].
                 createInstance(Ci.nsISupportsArray);
     var arg = Cc["@mozilla.org/supports-string;1"].
               createInstance(Ci.nsISupportsString);
     arg.data = "updates-only";
     param.AppendElement(arg);
-    ww.openWindow(null, EMURL, null, EMFEATURES, param);
+    ww.openWindow(null, URI_EXTENSION_MANAGER, null, FEATURES_EXTENSION_UPDATES, param);
   },
 
   /**
    * Clean up on application shutdown to avoid leaks.
    */
   _shutdown: function EM__shutdown() {
     if (!gAllowFlush) {
       // Something went wrong and there are potentially flushes pending.
@@ -2406,16 +2482,18 @@ ExtensionManager.prototype = {
         }
       }
       catch (e) {
         ERROR("Error flushing caches: " + e);
       }
     }
 
     gOS.removeObserver(this, "xpcom-shutdown");
+    gOS.removeObserver(this, "lightweight-theme-preview-requested");
+    gOS.removeObserver(this, "lightweight-theme-change-requested");
 
     // Release strongly held services.
     gOS = null;
     if (this._ds) {
       gRDF.UnregisterDataSource(this._ptr);
       this._ptr = null;
       this._ds.shutdown();
       this._ds = null;
@@ -5476,28 +5554,25 @@ BackgroundUpdateCheckListener.prototype 
     var win = wm.getMostRecentWindow("Extension:Manager");
     if (win) {
       win.focus();
       win.showView("updates");
       // Don't show the update notification on next startup
       gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false);
     }
     else {
-      const EMURL = "chrome://mozapps/content/extensions/extensions.xul";
-      const EMFEATURES = "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable";
-
       var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
                getService(Ci.nsIWindowWatcher);
       var param = Cc["@mozilla.org/supports-array;1"].
                   createInstance(Ci.nsISupportsArray);
       var arg = Cc["@mozilla.org/supports-string;1"].
                 createInstance(Ci.nsISupportsString);
       arg.data = "updates";
       param.AppendElement(arg);
-      ww.openWindow(null, EMURL, null, EMFEATURES, param);
+      ww.openWindow(null, URI_EXTENSION_MANAGER, null, FEATURES_EXTENSION_MANAGER, param);
     }
   },
   
   // nsIAddonUpdateCheckListener implementation
   onUpdateStarted: function BackgroundUpdateListener_onUpdateStarted() {
   },
 
   onUpdateEnded: function BackgroundUpdateListener_onUpdateEnded() {
--- a/toolkit/mozapps/extensions/test/Makefile.in
+++ b/toolkit/mozapps/extensions/test/Makefile.in
@@ -43,16 +43,20 @@ ADDONSRC = $(srcdir)/addons
 TESTROOT = $(CURDIR)/$(DEPTH)/_tests/xpcshell/$(MODULE)
 TESTXPI = $(TESTROOT)/unit/addons
 relativesrcdir  = toolkit/mozapps/extensions/test
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = test_extensionmanager
 
+DIRS = \
+  browser \
+  $(NULL)
+
 XPCSHELL_TESTS = \
   unit \
   $(NULL)
 
 _CHROME_FILES = \
   test_bug435743_1.xul \
   test_bug435743_2.xul \
   bug435743.rdf \
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/Makefile.in
@@ -0,0 +1,53 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Extension Manager.
+#
+# The Initial Developer of the Original Code is
+#      Dave Townsend <dtownsend@oxymoronical.com>.
+#
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH = ../../../../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir  = toolkit/mozapps/extensions/test
+
+include $(DEPTH)/config/autoconf.mk
+
+_TEST_FILES = \
+  browser_bug510909.js
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug510909.js
@@ -0,0 +1,199 @@
+Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm");
+
+var win = null;
+
+function WindowListener(url) {
+  this.url = url;
+
+  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+                     .getService(Components.interfaces.nsIWindowMediator);
+  wm.addListener(this);
+}
+
+WindowListener.prototype = {
+  url: null,
+
+  onWindowTitleChange: function(window, title) {
+  },
+
+  onOpenWindow: function(window) {
+    var domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                          .getInterface(Components.interfaces.nsIDOMWindowInternal);
+    var self = this;
+    domwindow.addEventListener("load", function() {
+      self.windowLoad(domwindow);
+    }, false);
+  },
+
+  // Window open handling
+  windowLoad: function(window) {
+    // Allow any other load handlers to execute
+    var self = this;
+    executeSoon(function() { self.windowReady(window); } );
+  },
+
+  windowReady: function(win) {
+    is(win.document.location.href, this.url, "Should have seen the right window");
+    win.document.documentElement.acceptDialog();
+  },
+
+  onCloseWindow: function(window) {
+    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+                       .getService(Components.interfaces.nsIWindowMediator);
+    wm.removeListener(this);
+  },
+
+  QueryInterface: function(iid) {
+    if (iid.equals(Components.interfaces.nsIWindowMediatorListener) ||
+        iid.equals(Components.interfaces.nsISupports))
+      return this;
+
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  }
+}
+
+function test() {
+  is(LightweightThemeManager.usedThemes.length, 0, "Should be no themes there");
+  ok(LightweightThemeManager.currentTheme == null, "Should not have a theme selected");
+
+  // Load up some lightweight themes
+  LightweightThemeManager.currentTheme = {
+    "id":"2",
+    "name":"Dirty Red",
+    "accentcolor":"#ffffff",
+    "textcolor":"#ffa0a0",
+    "description":null,
+    "author":"Mozilla",
+    "headerURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "footerURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "previewURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "iconURL":null
+  };
+  LightweightThemeManager.currentTheme = {
+    "id":"1",
+    "name":"Abstract Black",
+    "accentcolor":"#ffffff",
+    "textcolor":"#000000",
+    "description":null,
+    "author":"Mozilla",
+    "headerURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "footerURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "previewURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "iconURL":null
+  };
+  LightweightThemeManager.currentTheme = {
+    "id":"3",
+    "name":"Morose Mauve",
+    "accentcolor":"#ffffff",
+    "textcolor":"#e0b0ff",
+    "description":null,
+    "author":"Mozilla",
+    "headerURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "footerURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "iconURL":null
+  };
+  is(LightweightThemeManager.usedThemes.length, 3, "Should be all the themes there");
+  ok(LightweightThemeManager.currentTheme != null, "Should have selected a theme");
+  is(LightweightThemeManager.currentTheme.id, 3, "Should have selected the right theme");
+
+  win = window.openDialog("chrome://mozapps/content/extensions/extensions.xul", "",
+                          "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable");
+  win.addEventListener("load", function() { executeSoon(loaded); }, false);
+  waitForExplicitFinish();
+}
+
+function loaded() {
+  win.showView("themes");
+  is(win.gExtensionsView.childNodes.length,
+     LightweightThemeManager.usedThemes.length + 2,
+     "Should be all the lightweight themes and the default theme and the template");
+  is(win.gExtensionsView.childNodes[1].getAttribute("addonID"), 1,
+     "Themes should be in the right order");
+  is(win.gExtensionsView.childNodes[2].getAttribute("addonID"), "{972ce4c6-7e08-4474-a285-3208198ce6fd}",
+     "Themes should be in the right order");
+  is(win.gExtensionsView.childNodes[3].getAttribute("addonID"), 2,
+     "Themes should be in the right order");
+  is(win.gExtensionsView.childNodes[4].getAttribute("addonID"), 3,
+     "Themes should be in the right order");
+
+  var selected = win.gExtensionsView.selectedItem;
+  is(selected.getAttribute("addonID"),
+     LightweightThemeManager.currentTheme.id,
+     "Should have selected the current theme");
+  is(win.document.getElementById("previewImageDeck").selectedIndex, 1,
+     "Should be no preview to display");
+  var btn = win.document.getAnonymousElementByAttribute(selected, "command", "cmd_useTheme");
+  ok(btn.disabled, "Should not be able to switch to the current theme");
+
+  selected = win.gExtensionsView.selectedItem = win.gExtensionsView.childNodes[1];
+  is(win.document.getElementById("previewImageDeck").selectedIndex, 2,
+     "Should be a preview to display");
+  btn = win.document.getAnonymousElementByAttribute(selected, "command", "cmd_useTheme");
+  ok(!btn.disabled, "Should be able to switch to a different lightweight theme");
+  EventUtils.synthesizeMouse(btn, btn.boxObject.width / 2, btn.boxObject.height / 2, {}, win);
+  is(LightweightThemeManager.currentTheme.id, 1,
+     "Should have changed theme");
+  // A list rebuild happens so get the selected item again
+  selected = win.gExtensionsView.selectedItem;
+  btn = win.document.getAnonymousElementByAttribute(selected, "command", "cmd_useTheme");
+  ok(btn.disabled, "Should not be able to switch to the current theme");
+
+  selected = win.gExtensionsView.selectedItem = win.gExtensionsView.childNodes[2];
+  btn = win.document.getAnonymousElementByAttribute(selected, "command", "cmd_useTheme");
+  ok(!btn.disabled, "Should be able to switch to the default theme");
+  EventUtils.synthesizeMouse(btn, btn.boxObject.width / 2, btn.boxObject.height / 2, {}, win);
+  is(LightweightThemeManager.currentTheme, null,
+     "Should have disabled lightweight themes");
+  ok(btn.disabled, "Should not be able to switch to the current theme");
+
+  selected = win.gExtensionsView.selectedItem = win.gExtensionsView.childNodes[3];
+  btn = win.document.getAnonymousElementByAttribute(selected, "command", "cmd_useTheme");
+  ok(!btn.disabled, "Should be able to switch to a different lightweight theme");
+  EventUtils.synthesizeMouse(btn, btn.boxObject.width / 2, btn.boxObject.height / 2, {}, win);
+  is(LightweightThemeManager.currentTheme.id, 2,
+     "Should have changed theme");
+  // A list rebuild happens so get the selected item again
+  selected = win.gExtensionsView.selectedItem;
+  btn = win.document.getAnonymousElementByAttribute(selected, "command", "cmd_useTheme");
+  ok(btn.disabled, "Should not be able to switch to the current theme");
+  btn = win.document.getAnonymousElementByAttribute(selected, "command", "cmd_uninstall");
+  ok(!btn.disabled, "Should be able to uninstall a lightweight theme");
+  new WindowListener("chrome://mozapps/content/extensions/list.xul");
+  EventUtils.synthesizeMouse(btn, btn.boxObject.width / 2, btn.boxObject.height / 2, {}, win);
+  is(LightweightThemeManager.currentTheme, null,
+     "Should have turned off the lightweight theme");
+  is(LightweightThemeManager.usedThemes.length, 2, "Should have removed the theme");
+  is(win.gExtensionsView.childNodes.length,
+     LightweightThemeManager.usedThemes.length + 2,
+     "Should have updated the list");
+
+  LightweightThemeManager.currentTheme = {
+    "id":"2",
+    "name":"Dirty Red",
+    "accentcolor":"#ffffff",
+    "textcolor":"#ffa0a0",
+    "description":null,
+    "author":"Mozilla",
+    "headerURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "footerURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "previewURL":"http://example.com/browser/toolkit/mozapps/extensions/test/blank.png",
+    "iconURL":null
+  };
+  is(LightweightThemeManager.usedThemes.length, 3, "Should have added the theme");
+  is(win.gExtensionsView.childNodes.length,
+     LightweightThemeManager.usedThemes.length + 2,
+     "Should have updated the list");
+
+  win.close();
+  endTest();
+}
+
+function endTest() {
+  var themes = LightweightThemeManager.usedThemes;
+  themes.forEach(function(theme) {
+    LightweightThemeManager.forgetUsedTheme(theme.id);
+  });
+  is(LightweightThemeManager.usedThemes.length, 0, "Should be no themes left");
+  ok(LightweightThemeManager.currentTheme == null, "Should be no theme selected");
+  finish();
+}
--- a/toolkit/themes/gnomestripe/mozapps/extensions/extensions.css
+++ b/toolkit/themes/gnomestripe/mozapps/extensions/extensions.css
@@ -133,16 +133,20 @@ richlistitem[selected="true"] .text-link
   height: 32px;
 }
 
 .addonIcon > image {
   max-width: 32px;
   max-height: 32px;
 }
 
+richlistitem[lwtheme] .addonIcon > image {
+  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
+}
+
 .updateBadge,
 .notifyBadge {
   width: 16px;
   height: 16px;
   margin-bottom: -3px; 
 }
 
 .updateBadge {
--- a/toolkit/themes/pinstripe/mozapps/extensions/extensions.css
+++ b/toolkit/themes/pinstripe/mozapps/extensions/extensions.css
@@ -125,16 +125,20 @@ richlistitem:not([selected="true"]) .add
   height: 32px;
 }
 
 .addonIcon > image {
   max-width: 32px;
   max-height: 32px;
 }
 
+richlistitem[lwtheme] .addonIcon > image {
+  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
+}
+
 .updateBadge,
 .notifyBadge {
   width: 16px;
   height: 16px;
   margin-bottom: -3px;
   list-style-image: url("chrome://mozapps/skin/extensions/notifyBadges.png");
 }
 
--- a/toolkit/themes/winstripe/mozapps/extensions/extensions.css
+++ b/toolkit/themes/winstripe/mozapps/extensions/extensions.css
@@ -123,16 +123,20 @@ richlistitem[selected="true"] .text-link
   height: 32px;
 }
 
 .addonIcon > image {
   max-width: 32px;
   max-height: 32px;
 }
 
+richlistitem[lwtheme] .addonIcon > image {
+  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
+}
+
 .updateBadge,
 .notifyBadge {
   width: 16px;
   height: 16px;
   margin-bottom: -3px;
   list-style-image: url("chrome://mozapps/skin/extensions/notifyBadges.png");
 }