[Pkg-mozext-commits] [personasplus] 33/42: Changes from 1.7.2

David Prévot taffit at moszumanska.debian.org
Wed Feb 3 16:15:41 UTC 2016


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository personasplus.

commit 62b6669b01a5c775f64cd09564500a6f8260f620
Author: Andreas Wagner <mail at andreaswagner.org>
Date:   Wed Jan 27 19:50:00 2016 +0100

    Changes from 1.7.2
---
 extension/content/personas.js                  | 2080 ++++++++---------
 extension/locale/bg-BG/personas.properties     |    5 +-
 extension/locale/cs-CZ/personas.properties     |    1 +
 extension/locale/da/personas.properties        |    3 +-
 extension/locale/de/personas.properties        |    1 +
 extension/locale/el/personas.properties        |    3 +-
 extension/locale/en-US/personas.properties     |    1 +
 extension/locale/es-AR/personas.properties     |    1 +
 extension/locale/es-CL/personas.properties     |    1 +
 extension/locale/es-ES/personas.properties     |    1 +
 extension/locale/es-MX/personas.properties     |    1 +
 extension/locale/eu/personas.properties        |    1 +
 extension/locale/fi/personas.properties        |    1 +
 extension/locale/fr/personas.properties        |    1 +
 extension/locale/fy-NL/personas.properties     |    1 +
 extension/locale/ga-IE/personas.properties     |    1 +
 extension/locale/gl-ES/personas.properties     |    1 +
 extension/locale/he-IL/personas.properties     |    1 +
 extension/locale/hu-HU/personas.properties     |    1 +
 extension/locale/it/personas.properties        |    1 +
 extension/locale/ja-JP-mac/personas.properties |    1 +
 extension/locale/ja-JP/personas.properties     |    1 +
 extension/locale/ja/personas.properties        |    1 +
 extension/locale/ko-KR/personas.properties     |    1 +
 extension/locale/lt-LT/personas.properties     |    1 +
 extension/locale/lt/personas.properties        |    1 +
 extension/locale/mk-MK/personas.properties     |    1 +
 extension/locale/nl/personas.properties        |    1 +
 extension/locale/pl-PL/personas.properties     |    3 +-
 extension/locale/pl/personas.properties        |    1 +
 extension/locale/pt-BR/personas.properties     |    1 +
 extension/locale/ro/personas.properties        |    1 +
 extension/locale/ru-RU/personas.properties     |    1 +
 extension/locale/si-LK/personas.properties     |    1 +
 extension/locale/sk-SK/personas.properties     |    1 +
 extension/locale/sr/personas.properties        |    1 +
 extension/locale/sv-SE/personas.properties     |    1 +
 extension/locale/tr/personas.properties        |    1 +
 extension/locale/uk-UA/personas.properties     |    1 +
 extension/locale/vi/personas.properties        |    1 +
 extension/locale/zh-CN/personas.properties     |    1 +
 extension/locale/zh-TW/personas.properties     |    1 +
 extension/modules/service.js                   | 2828 ++++++++++++------------
 43 files changed, 2530 insertions(+), 2429 deletions(-)

diff --git a/extension/content/personas.js b/extension/content/personas.js
index 8d8c529..ffaf70d 100644
--- a/extension/content/personas.js
+++ b/extension/content/personas.js
@@ -44,13 +44,13 @@
 // conflict with modules with the same names imported by other extensions.
 
 // Define Components as var here as they are already defined for Firefox. See Bug 484062 for details.
-  if (typeof Cc == "undefined")
+if (typeof Cc == "undefined")
     var Cc = Components.classes;
-  if (typeof Ci == "undefined")
+if (typeof Ci == "undefined")
     var Ci = Components.interfaces;
-  if (typeof Cr == "undefined")
+if (typeof Cr == "undefined")
     var Cr = Components.results;
-  if (typeof Cu == "undefined")
+if (typeof Cu == "undefined")
     var Cu = Components.utils;
 
 // It's OK to import the service module into the global namespace because its
@@ -58,1100 +58,1114 @@
 Cu.import("resource://personas/modules/service.js");
 
 let PersonaController = {
-  _previewTimeoutID: null,
-  _resetTimeoutID: null,
-
-  //**************************************************************************//
-  // Shortcuts
-
-  // Generic modules get imported into these properties rather than
-  // the global namespace so they don't conflict with modules with the same
-  // names imported by other extensions.
-  Observers:               null,
-  Preferences:             null,
-  StringBundle:            null,
-  URI:                     null,
-  LightweightThemeManager: null,
-
-  // Access to extensions.personas.* preferences.  To access other preferences,
-  // call the Preferences module directly.
-  get _prefs() {
-    delete this._prefs;
-    return this._prefs = new this.Preferences("extensions.personas.");
-  },
-
-  get _strings() {
-    delete this._strings;
-    return this._strings = new this.StringBundle("chrome://personas/locale/personas.properties");
-  },
-
-  get _brandStrings() {
-    delete this._brandStrings;
-    return this._brandStrings =
-      new this.StringBundle("chrome://branding/locale/brand.properties");
-  },
-
-  get _menu() {
-    delete this._menu;
-    return this._menu = document.getElementById("personas-menu");
-  },
-
-  get _menuButton() {
-    delete this._menuButton;
-    return this._menuButton = document.getElementById("personas-selector-button");
-  },
-
-  get _menuPopup() {
-    delete this._menuPopup;
-    return this._menuPopup = document.getElementById("personas-selector-menu");
-  },
-
-  get _toolbarButton() {
-    delete this._toolbarButton;
-    return this._toolbarButton = document.getElementById("personas-toolbar-button");
-  },
-
-  get _sessionStore() {
-    delete this._sessionStore;
-    return this._sessionStore = Cc["@mozilla.org/browser/sessionstore;1"]
-                                .getService(Ci.nsISessionStore);
-  },
-
-  get _header() {
-    delete this._header;
-    switch (PersonaService.appInfo.ID) {
-      case PersonaService.THUNDERBIRD_ID:
-        return this._header = document.getElementById("messengerWindow");
-      case PersonaService.FIREFOX_ID:
-        return this._header = document.getElementById("main-window");
-      default:
-        throw "unknown application ID " + PersonaService.appInfo.ID;
-    }
-  },
-
-  get _footer() {
-    delete this._footer;
-    switch (PersonaService.appInfo.ID) {
-      case PersonaService.THUNDERBIRD_ID:
-        return this._footer = document.getElementById("status-bar");
-      case PersonaService.FIREFOX_ID:
-        return this._footer = document.getElementById("browser-bottombox");
-      default:
-        throw "unknown application ID " + PersonaService.appInfo.ID;
-    }
-  },
-
-  get _thunderbirdRegExp() {
-    delete this._thunderbirdRegExp;
-    return this._thunderbirdRegExp = new RegExp("^" + this._siteURL.replace(/\./g, "\\."));
-  },
-
-  get _siteURL() {
-    return "https://" + this._prefs.get("addons-host") + "/";
-  },
-
-  get _previewTimeout() {
-    return this._prefs.get("previewTimeout");
-  },
-
-  // XXX We used to use this to direct users to locale-specific directories
-  // on the personas server, but we're not using it anymore, as we no longer
-  // have locale-specific pages on the server.  And once we get them back,
-  // it'll probably make more sense for the browser and server to do locale
-  // negotiation using the standard mechanisms anyway, so this is no longer
-  // needed.
-  get _locale() {
-    switch (this.Preferences.get("general.useragent.locale", "en-US")) {
-      case 'ja':
-      case 'ja-JP-mac':
-        return "ja";
-    }
-    return "en-US";
-  },
-
-  /**
-   * Escape CSS special characters in unquoted URLs,
-   * per http://www.w3.org/TR/CSS21/syndata.html#uri.
-   */
-  _escapeURLForCSS: function(url) url.replace(/[(),\s'"]/g, "\$&"),
-
-  openURLInTab: function(url) {
-    switch (PersonaService.appInfo.ID) {
-      case PersonaService.THUNDERBIRD_ID:
-        // Thunderbird's "openTab" implementation for the "contentTab" mode
-        // automatically switches to an existing tab containing the URL we are
-        // opening, so we don't have to check for one here.
-        Cc['@mozilla.org/appshell/window-mediator;1'].
-        getService(Ci.nsIWindowMediator).
-        getMostRecentWindow("mail:3pane").
-        document.getElementById("tabmail").
-        openTab("contentTab", { contentPage: url,
-                                clickHandler: "specialTabs.siteClickHandler(event, PersonaController._thunderbirdRegExp);" });
-        break;
-
-      case PersonaService.FIREFOX_ID:
-      default: {
-        // Firefox's "openUILinkIn" implementation doesn't check if there is
-        // already an existing tab containing the URL we are opening, so we have
-        // to check for one here.
-        let found = false;
-        let tabBrowser = window.getBrowser();
-        // Check each tab of this browser for the editor XUL file
-        let numTabs = tabBrowser.browsers.length;
-        for (let index = 0; index < numTabs; index++) {
-          let currentBrowser = tabBrowser.getBrowserAtIndex(index);
-          if (url == currentBrowser.currentURI.spec) {
-            tabBrowser.selectedTab = tabBrowser.mTabs[index];
-            found = true;
-            break;
-          }
+    _previewTimeoutID: null,
+    _resetTimeoutID: null,
+
+    //**************************************************************************//
+    // Shortcuts
+
+    // Generic modules get imported into these properties rather than
+    // the global namespace so they don't conflict with modules with the same
+    // names imported by other extensions.
+    Observers: null,
+    Preferences: null,
+    StringBundle: null,
+    URI: null,
+    LightweightThemeManager: null,
+
+    // Access to extensions.personas.* preferences.  To access other preferences,
+    // call the Preferences module directly.
+    get _prefs() {
+        delete this._prefs;
+        return this._prefs = new this.Preferences("extensions.personas.");
+    },
+
+    get _strings() {
+        delete this._strings;
+        return this._strings = new this.StringBundle("chrome://personas/locale/personas.properties");
+    },
+
+    get _brandStrings() {
+        delete this._brandStrings;
+        return this._brandStrings =
+            new this.StringBundle("chrome://branding/locale/brand.properties");
+    },
+
+    get _menu() {
+        delete this._menu;
+        return this._menu = document.getElementById("personas-menu");
+    },
+
+    get _menuButton() {
+        delete this._menuButton;
+        return this._menuButton = document.getElementById("personas-selector-button");
+    },
+
+    get _menuPopup() {
+        delete this._menuPopup;
+        return this._menuPopup = document.getElementById("personas-selector-menu");
+    },
+
+    get _toolbarButton() {
+        delete this._toolbarButton;
+        return this._toolbarButton = document.getElementById("personas-toolbar-button");
+    },
+
+    get _sessionStore() {
+        delete this._sessionStore;
+        return this._sessionStore = Cc["@mozilla.org/browser/sessionstore;1"]
+            .getService(Ci.nsISessionStore);
+    },
+
+    get _header() {
+        delete this._header;
+        switch (PersonaService.appInfo.ID) {
+            case PersonaService.THUNDERBIRD_ID:
+                return this._header = document.getElementById("messengerWindow");
+            case PersonaService.FIREFOX_ID:
+                return this._header = document.getElementById("main-window");
+            default:
+                throw "unknown application ID " + PersonaService.appInfo.ID;
         }
-        if (!found)
-          window.openUILinkIn(url, "tab");
-        break;
-      }
-    }
-  },
-
-
-  //**************************************************************************//
-  // XPCOM Interface Implementations
-
-  // nsISupports
-  QueryInterface: function(aIID) {
-    if (aIID.equals(Ci.nsIObserver) ||
-        aIID.equals(Ci.nsIDOMEventListener) ||
-        aIID.equals(Ci.nsISupports))
-      return this;
-
-    throw Cr.NS_ERROR_NO_INTERFACE;
-  },
-
-  // nsIObserver
-  observe: function(subject, topic, data) {
-    switch (topic) {
-      case "domwindowopened":
-        // Since there's no explicit notification for windows restored from
-        // session store, we use this to apply the per-window persona
-        // (but only if one exists).
-        //
-        // Bug 534669 has been filed for adding SSWindowRestored support.
-        if (this._prefs.get("perwindow") &&
-            (window.document.documentElement.getAttribute("windowtype")
-              == "navigator:browser") &&
-            this._sessionStore.getWindowValue(window, "persona")) {
-          this._applyPersona(JSON.parse(
-            this._sessionStore.getWindowValue(window, "persona")
-          ));
+    },
+
+    get _footer() {
+        delete this._footer;
+        switch (PersonaService.appInfo.ID) {
+            case PersonaService.THUNDERBIRD_ID:
+                return this._footer = document.getElementById("status-bar");
+            case PersonaService.FIREFOX_ID:
+                return this._footer = document.getElementById("browser-bottombox");
+            default:
+                throw "unknown application ID " + PersonaService.appInfo.ID;
         }
-        break;
-      case "personas:persona:changed":
-        // Per-window personas are enabled
-        if (this._prefs.get("perwindow")) {
-          if (this._sessionStore.getWindowValue(window, "persona")) {
-            this._applyPersona(JSON.parse(
-              this._sessionStore.getWindowValue(window, "persona")
-            ));
-          } else {
-            this._applyDefault();
-          }
-        // Pan-window personas are enabled
-        } else {
-          if (PersonaService.previewingPersona) {
-            this._applyPersona(PersonaService.previewingPersona);
-          } else if (PersonaService.selected == "default") {
-            this._applyDefault();
-          } else {
-            this._applyPersona(PersonaService.currentPersona);
-          }
-          break;
+    },
+
+    get _thunderbirdRegExp() {
+        delete this._thunderbirdRegExp;
+        return this._thunderbirdRegExp = new RegExp("^" + this._siteURL.replace(/\./g, "\\."));
+    },
+
+    get _siteURL() {
+        return "https://" + this._prefs.get("addons-host") + "/";
+    },
+
+    get _previewTimeout() {
+        return this._prefs.get("previewTimeout");
+    },
+
+    // XXX We used to use this to direct users to locale-specific directories
+    // on the personas server, but we're not using it anymore, as we no longer
+    // have locale-specific pages on the server.  And once we get them back,
+    // it'll probably make more sense for the browser and server to do locale
+    // negotiation using the standard mechanisms anyway, so this is no longer
+    // needed.
+    get _locale() {
+        switch (this.Preferences.get("general.useragent.locale", "en-US")) {
+            case 'ja':
+            case 'ja-JP-mac':
+                return "ja";
         }
-    }
-  },
-
-  // nsIDOMEventListener
-  handleEvent: function(aEvent) {
-    switch (aEvent.type) {
-      case "SelectPersona":
-      case "PreviewPersona":
-      case "ResetPersona":
-        let doc = aEvent.originalTarget.ownerDocument;
-        if (Services.perms.testPermission(doc.documentURIObject, "install") 
-              == Services.perms.ALLOW_ACTION)
-          this.checkPrivateBrowsing();
-        break;
-      case "CheckPersonas":
-        this.onCheckPersonasFromContent(aEvent);
-        break;
-      case "AddFavoritePersona":
-        this.onAddFavoritePersonaFromContent(aEvent);
-        break;
-      case "RemoveFavoritePersona":
-        this.onRemoveFavoritePersonaFromContent(aEvent);
-        break;
-    }
-  },
-
-  // Tab Monitor methods (Thunderbird)
-  onTabTitleChanged : function(aTab) { /* ignored */ },
-  onTabSwitched : function(aTab, aOldTab) {
-  },
-
-  //**************************************************************************//
-  // Initialization & Destruction
-
-  startUp: function() {
-    // Set the label for the tooltip that informs users when personas data
-    // is unavailable.
-    // FIXME: make this a DTD entity rather than a properties string.
-    document.getElementById("personasDataUnavailableTooltip").label =
-      this._strings.get("dataUnavailable",
-                        [this._brandStrings.get("brandShortName")]);
-
-    document.addEventListener("SelectPersona", this, false, true);
-    document.addEventListener("PreviewPersona", this, false, true);
-    document.addEventListener("ResetPersona", this, false, true);
-    // Listen for various persona-related events that can bubble up from content,
-    // not handled by the LightweightThemeManager.
-    document.addEventListener("CheckPersonas", this, false, true);
-    document.addEventListener("AddFavoritePersona", this, false, true);
-    document.addEventListener("RemoveFavoritePersona", this, false, true);
-
-
-    Cu.import("resource://gre/modules/AddonManager.jsm", this);
-    this.AddonManager.getAddonByID(PERSONAS_EXTENSION_ID,
-      function(aAddon) {
-        this._prefs.set("lastversion", aAddon.version);
-      }.bind(this));
-
-    // Perform special operations for Firefox 4 compatibility:
-    // * Hide the status bar button
-    // * Install the toolbar button in the Add-on bar (first time only).
-    if (PersonaService.appInfo.ID == PersonaService.FIREFOX_ID) {
-      let addonBar = window.document.getElementById("addon-bar");
-      if (addonBar) {
-        this._menuButton.setAttribute("hidden", true);
-
-        if (!this._prefs.get("toolbarButtonInstalled")) {
-          this._installToolbarButton(addonBar);
-          this._prefs.set("toolbarButtonInstalled", true);
+        return "en-US";
+    },
+
+    /**
+     * Escape CSS special characters in unquoted URLs,
+     * per http://www.w3.org/TR/CSS21/syndata.html#uri.
+     */
+    _escapeURLForCSS: function(url) url.replace(/[(),\s'"]/g, "\$&"),
+
+    openURLInTab: function(url) {
+        switch (PersonaService.appInfo.ID) {
+            case PersonaService.THUNDERBIRD_ID:
+                // Thunderbird's "openTab" implementation for the "contentTab" mode
+                // automatically switches to an existing tab containing the URL we are
+                // opening, so we don't have to check for one here.
+                Cc['@mozilla.org/appshell/window-mediator;1'].
+                getService(Ci.nsIWindowMediator).
+                getMostRecentWindow("mail:3pane").
+                document.getElementById("tabmail").
+                openTab("contentTab", {
+                    contentPage: url,
+                    clickHandler: "specialTabs.siteClickHandler(event, PersonaController._thunderbirdRegExp);"
+                });
+                break;
+
+            case PersonaService.FIREFOX_ID:
+            default:
+                {
+                    // Firefox's "openUILinkIn" implementation doesn't check if there is
+                    // already an existing tab containing the URL we are opening, so we have
+                    // to check for one here.
+                    let found = false;
+                    let tabBrowser = window.getBrowser();
+                    // Check each tab of this browser for the editor XUL file
+                    let numTabs = tabBrowser.browsers.length;
+                    for (let index = 0; index < numTabs; index++) {
+                        let currentBrowser = tabBrowser.getBrowserAtIndex(index);
+                        if (url == currentBrowser.currentURI.spec) {
+                            tabBrowser.selectedTab = tabBrowser.mTabs[index];
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found)
+                        window.openUILinkIn(url, "tab");
+                    break;
+                }
         }
-      }
-    }
-  },
+    },
+
+
+    //**************************************************************************//
+    // XPCOM Interface Implementations
+
+    // nsISupports
+    QueryInterface: function(aIID) {
+        if (aIID.equals(Ci.nsIObserver) ||
+            aIID.equals(Ci.nsIDOMEventListener) ||
+            aIID.equals(Ci.nsISupports))
+            return this;
+
+        throw Cr.NS_ERROR_NO_INTERFACE;
+    },
+
+    // nsIObserver
+    observe: function(subject, topic, data) {
+        switch (topic) {
+            case "domwindowopened":
+                // Since there's no explicit notification for windows restored from
+                // session store, we use this to apply the per-window persona
+                // (but only if one exists).
+                //
+                // Bug 534669 has been filed for adding SSWindowRestored support.
+                if (this._prefs.get("perwindow") &&
+                    (window.document.documentElement.getAttribute("windowtype") == "navigator:browser") &&
+                    this._sessionStore.getWindowValue(window, "persona")) {
+                    this._applyPersona(JSON.parse(
+                        this._sessionStore.getWindowValue(window, "persona")
+                    ));
+                }
+                break;
+            case "personas:persona:changed":
+                // Per-window personas are enabled
+                if (this._prefs.get("perwindow")) {
+                    if (this._sessionStore.getWindowValue(window, "persona")) {
+                        this._applyPersona(JSON.parse(
+                            this._sessionStore.getWindowValue(window, "persona")
+                        ));
+                    } else {
+                        this._applyDefault();
+                    }
+                    // Pan-window personas are enabled
+                } else {
+                    if (PersonaService.previewingPersona) {
+                        this._applyPersona(PersonaService.previewingPersona);
+                    } else if (PersonaService.selected == "default") {
+                        this._applyDefault();
+                    } else {
+                        this._applyPersona(PersonaService.currentPersona);
+                    }
+                    break;
+                }
+        }
+    },
+
+    // nsIDOMEventListener
+    handleEvent: PersonaService.wrap(function(aEvent) {
+        switch (aEvent.type) {
+            case "SelectPersona":
+            case "PreviewPersona":
+            case "ResetPersona":
+                let doc = aEvent.originalTarget.ownerDocument;
+                if (Services.perms.testPermission(doc.documentURIObject, "install") == Services.perms.ALLOW_ACTION)
+                    this.checkPrivateBrowsing();
+                break;
+            case "CheckPersonas":
+                this.onCheckPersonasFromContent(aEvent);
+                break;
+            case "AddFavoritePersona":
+                this.onAddFavoritePersonaFromContent(aEvent);
+                break;
+            case "RemoveFavoritePersona":
+                this.onRemoveFavoritePersonaFromContent(aEvent);
+                break;
+        }
+    }),
+
+    // Tab Monitor methods (Thunderbird)
+    onTabTitleChanged: function(aTab) { /* ignored */ },
+    onTabSwitched: function(aTab, aOldTab) {},
+
+    //**************************************************************************//
+    // Initialization & Destruction
+
+    startUp: function() {
+        // Set the label for the tooltip that informs users when personas data
+        // is unavailable.
+        // FIXME: make this a DTD entity rather than a properties string.
+        document.getElementById("personasDataUnavailableTooltip").label =
+            this._strings.get("dataUnavailable", [this._brandStrings.get("brandShortName")]);
+
+        document.addEventListener("SelectPersona", this, false, true);
+        document.addEventListener("PreviewPersona", this, false, true);
+        document.addEventListener("ResetPersona", this, false, true);
+        // Listen for various persona-related events that can bubble up from content,
+        // not handled by the LightweightThemeManager.
+        document.addEventListener("CheckPersonas", this, false, true);
+        document.addEventListener("AddFavoritePersona", this, false, true);
+        document.addEventListener("RemoveFavoritePersona", this, false, true);
+
+
+        Cu.import("resource://gre/modules/AddonManager.jsm", this);
+        this.AddonManager.getAddonByID(PERSONAS_EXTENSION_ID,
+            function(aAddon) {
+                this._prefs.set("lastversion", aAddon.version);
+            }.bind(this));
+
+        // Perform special operations for Firefox 4 compatibility:
+        // * Hide the status bar button
+        // * Install the toolbar button in the Add-on bar (first time only).
+        if (PersonaService.appInfo.ID == PersonaService.FIREFOX_ID) {
+            let addonBar = window.document.getElementById("addon-bar");
+            if (addonBar) {
+                this._menuButton.setAttribute("hidden", true);
+
+                if (!this._prefs.get("toolbarButtonInstalled")) {
+                    this._installToolbarButton(addonBar);
+                    this._prefs.set("toolbarButtonInstalled", true);
+                }
+            }
+        }
+    },
 
-  shutDown: function() {
-    document.removeEventListener("SelectPersona", this, false);
-    document.removeEventListener("PreviewPersona", this, false);
-    document.removeEventListener("ResetPersona", this, false);
-    document.removeEventListener("CheckPersonas", this, false);
-    document.removeEventListener("AddFavoritePersona", this, false);
-    document.removeEventListener("RemoveFavoritePersona", this, false);
-  },
+    shutDown: function() {
+        document.removeEventListener("SelectPersona", this, false);
+        document.removeEventListener("PreviewPersona", this, false);
+        document.removeEventListener("ResetPersona", this, false);
+        document.removeEventListener("CheckPersonas", this, false);
+        document.removeEventListener("AddFavoritePersona", this, false);
+        document.removeEventListener("RemoveFavoritePersona", this, false);
+    },
 
-  _installToolbarButton : function(aToolbar) {
-    const PERSONAS_BUTTON_ID = "personas-toolbar-button";
+    _installToolbarButton: function(aToolbar) {
+        const PERSONAS_BUTTON_ID = "personas-toolbar-button";
 
-    let curSet = aToolbar.currentSet;
+        let curSet = aToolbar.currentSet;
 
-    // Add the button if it's not in the toolbar's current set
-    if (-1 == curSet.indexOf(PERSONAS_BUTTON_ID)) {
+        // Add the button if it's not in the toolbar's current set
+        if (-1 == curSet.indexOf(PERSONAS_BUTTON_ID)) {
 
-      // Insert the button at the end.
-      let newSet = curSet + "," + PERSONAS_BUTTON_ID;
+            // Insert the button at the end.
+            let newSet = curSet + "," + PERSONAS_BUTTON_ID;
 
-      aToolbar.currentSet = newSet;
-      aToolbar.setAttribute("currentset", newSet);
-      document.persist(aToolbar.id, "currentset");
+            aToolbar.currentSet = newSet;
+            aToolbar.setAttribute("currentset", newSet);
+            document.persist(aToolbar.id, "currentset");
 
-      try {
-        BrowserToolboxCustomizeDone(true);
-      }
-      catch(e){}
+            try {
+                BrowserToolboxCustomizeDone(true);
+            } catch (e) {}
 
-      // Make sure the toolbar is visible
-      if (aToolbar.getAttribute("collapsed") == "true")
-        aToolbar.setAttribute("collapsed", "false");
-      document.persist(aToolbar.id, "collapsed");
-    }
-  },
+            // Make sure the toolbar is visible
+            if (aToolbar.getAttribute("collapsed") == "true")
+                aToolbar.setAttribute("collapsed", "false");
+            document.persist(aToolbar.id, "collapsed");
+        }
+    },
 
-  //**************************************************************************//
-  // Appearance Updates
+    //**************************************************************************//
+    // Appearance Updates
 
-  _applyPersona: function(persona) {
+    _applyPersona: function(persona) {
 
-    // Style header and footer
-    this._header.setAttribute("persona", persona.id);
-    this._footer.setAttribute("persona", persona.id);
+        // Style header and footer
+        this._header.setAttribute("persona", persona.id);
+        this._footer.setAttribute("persona", persona.id);
 
-    // First try to obtain the images from the cache
-    let images = PersonaService.getCachedPersonaImages(persona);
-    if (images && images.header && images.footer) {
-      this._header.style.backgroundImage = "url(" + images.header + ")";
-      this._footer.style.backgroundImage = "url(" + images.footer + ")";
-    }
-    // Else set them from their original source
-    else {
-      // Use the URI module to resolve the possibly relative URI to an absolute one.
-      let headerURI = this.URI.get(persona.headerURL || persona.header,
-                                   null, null);
-      this._header.style.backgroundImage = "url(" + this._escapeURLForCSS(headerURI.spec) + ")";
-      // Use the URI module to resolve the possibly relative URI to an absolute one.
-      let footerURI = this.URI.get(persona.footerURL || persona.footer,
-                                   null, null);
-      this._footer.style.backgroundImage = "url(" + this._escapeURLForCSS(footerURI.spec) + ")";
-    }
+        // First try to obtain the images from the cache
+        let images = PersonaService.getCachedPersonaImages(persona);
+        if (images && images.header && images.footer) {
+            this._header.style.backgroundImage = "url(" + images.header + ")";
+            this._footer.style.backgroundImage = "url(" + images.footer + ")";
+        }
+        // Else set them from their original source
+        else {
+            // Use the URI module to resolve the possibly relative URI to an absolute one.
+            let headerURI = this.URI.get(persona.headerURL || persona.header,
+                null, null);
+            this._header.style.backgroundImage = "url(" + this._escapeURLForCSS(headerURI.spec) + ")";
+            // Use the URI module to resolve the possibly relative URI to an absolute one.
+            let footerURI = this.URI.get(persona.footerURL || persona.footer,
+                null, null);
+            this._footer.style.backgroundImage = "url(" + this._escapeURLForCSS(footerURI.spec) + ")";
+        }
 
-    // Style the text color.
-    if (this._prefs.get("useTextColor")) {
-      // FIXME: fall back on the default text color instead of "black".
-      let textColor = persona.textcolor || "black";
-      this._header.style.color = textColor;
-      for (let i = 0; i < document.styleSheets.length; i++) {
-        let styleSheet = document.styleSheets[i];
-        if (styleSheet.href == "chrome://personas/content/overlay.css") {
-          while (styleSheet.cssRules.length > 0)
-            styleSheet.deleteRule(0);
-
-          // On Mac we do several things differently:
-          // 1. make text be regular weight, not bold (not sure why);
-          // 2. explicitly style the Find toolbar label ("Find:" or
-          //    "Quick Find:" in en-US) and status message ("Phrase not found"),
-          //    which otherwise would be custom colors specified in findBar.css
-          //    (note: we only do this in Firefox);
-          // 3. style the tab color (Mac tabs are transparent).
-          // In order to style the Find toolbar text, we have to both explicitly
-          // reference it (.findbar-find-fast, .findbar-find-status) and make
-          // the declaration !important to override an !important declaration
-          // for the status text in findBar.css.
-          // XXX Isn't |#main-window[persona]| unnecessary in this rule,
-          // given that the rule is only inserted into the stylesheet when
-          // a persona is active?
-          if (PersonaService.appInfo.OS == "Darwin") {
-            switch (PersonaService.appInfo.ID) {
-              case PersonaService.FIREFOX_ID:
-                styleSheet.insertRule(
-                  "#main-window[persona] .tabbrowser-tab, " +
-                  "#navigator-toolbox menubar > menu, " +
-                  "#navigator-toolbox toolbarbutton, " +
-                  "#browser-bottombox, " +
-                  ".findbar-find-fast, " +
-                  ".findbar-find-status, " +
-                  "#browser-bottombox toolbarbutton " +
-                  "{ color: inherit; " +
-                  "font-weight: normal; }",
-                  0
-                );
-                break;
-              case PersonaService.THUNDERBIRD_ID:
-                styleSheet.insertRule(
-                  ".tabmail-tab, " +
-                  "#mail-toolbox menubar > menu, " +
-                  "#mail-toolbox toolbarbutton, " +
-                  "#mail-toolbox toolbaritem > label, " +
-                  "#status-bar " +
-                  "{ color: " + textColor + " !important; " +
-                  "font-weight: normal; }",
-                  0
-                );
-                break;
-              default:
-                break;
+        // Style the text color.
+        if (this._prefs.get("useTextColor")) {
+            // FIXME: fall back on the default text color instead of "black".
+            let textColor = persona.textcolor || "black";
+            this._header.style.color = textColor;
+            for (let i = 0; i < document.styleSheets.length; i++) {
+                let styleSheet = document.styleSheets[i];
+                if (styleSheet.href == "chrome://personas/content/overlay.css") {
+                    while (styleSheet.cssRules.length > 0)
+                        styleSheet.deleteRule(0);
+
+                    // On Mac we do several things differently:
+                    // 1. make text be regular weight, not bold (not sure why);
+                    // 2. explicitly style the Find toolbar label ("Find:" or
+                    //    "Quick Find:" in en-US) and status message ("Phrase not found"),
+                    //    which otherwise would be custom colors specified in findBar.css
+                    //    (note: we only do this in Firefox);
+                    // 3. style the tab color (Mac tabs are transparent).
+                    // In order to style the Find toolbar text, we have to both explicitly
+                    // reference it (.findbar-find-fast, .findbar-find-status) and make
+                    // the declaration !important to override an !important declaration
+                    // for the status text in findBar.css.
+                    // XXX Isn't |#main-window[persona]| unnecessary in this rule,
+                    // given that the rule is only inserted into the stylesheet when
+                    // a persona is active?
+                    if (PersonaService.appInfo.OS == "Darwin") {
+                        switch (PersonaService.appInfo.ID) {
+                            case PersonaService.FIREFOX_ID:
+                                styleSheet.insertRule(
+                                    "#main-window[persona] .tabbrowser-tab, " +
+                                    "#navigator-toolbox menubar > menu, " +
+                                    "#navigator-toolbox toolbarbutton, " +
+                                    "#browser-bottombox, " +
+                                    ".findbar-find-fast, " +
+                                    ".findbar-find-status, " +
+                                    "#browser-bottombox toolbarbutton " +
+                                    "{ color: inherit; " +
+                                    "font-weight: normal; }",
+                                    0
+                                );
+                                break;
+                            case PersonaService.THUNDERBIRD_ID:
+                                styleSheet.insertRule(
+                                    ".tabmail-tab, " +
+                                    "#mail-toolbox menubar > menu, " +
+                                    "#mail-toolbox toolbarbutton, " +
+                                    "#mail-toolbox toolbaritem > label, " +
+                                    "#status-bar " +
+                                    "{ color: " + textColor + " !important; " +
+                                    "font-weight: normal; }",
+                                    0
+                                );
+                                break;
+                            default:
+                                break;
+                        }
+                    } else {
+                        switch (PersonaService.appInfo.ID) {
+                            case PersonaService.FIREFOX_ID:
+                                styleSheet.insertRule(
+                                    "#navigator-toolbox menubar > menu, " +
+                                    "#navigator-toolbox toolbarbutton, " +
+                                    "#browser-bottombox, " +
+                                    "#browser-bottombox toolbarbutton " +
+                                    "{ color: inherit; }",
+                                    0
+                                );
+                                break;
+                            case PersonaService.THUNDERBIRD_ID:
+                                styleSheet.insertRule(
+                                    "#mail-toolbox menubar > menu, " +
+                                    "#mail-toolbox toolbarbutton, " +
+                                    "#mail-toolbox toolbaritem > label, " +
+                                    "#status-bar " +
+                                    "{ color: " + textColor + "}",
+                                    0
+                                );
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+
+                    // FIXME: figure out what to do about the disabled color.  Maybe we
+                    // should let personas specify it independently and then apply it via
+                    // a rule like this:
+                    // #navigator-toolbox toolbarbutton[disabled="true"],
+                    // #browser-toolbox toolbarbutton[disabled="true"],
+                    // #browser-bottombox toolbarbutton[disabled="true"]
+                    //   { color: #cccccc !important; }
+
+                    // Stop iterating through stylesheets.
+                    break;
+                }
             }
-          }
-          else {
-            switch (PersonaService.appInfo.ID) {
-              case PersonaService.FIREFOX_ID:
-                styleSheet.insertRule(
-                  "#navigator-toolbox menubar > menu, " +
-                  "#navigator-toolbox toolbarbutton, " +
-                  "#browser-bottombox, " +
-                  "#browser-bottombox toolbarbutton " +
-                  "{ color: inherit; }",
-                  0
-                );
-                break;
-              case PersonaService.THUNDERBIRD_ID:
-                styleSheet.insertRule(
-                  "#mail-toolbox menubar > menu, " +
-                  "#mail-toolbox toolbarbutton, " +
-                  "#mail-toolbox toolbaritem > label, " +
-                  "#status-bar " +
-                  "{ color: " + textColor + "}",
-                  0
-                );
-                break;
-              default:
+        }
+
+        // Style the titlebar with the accent color.
+        if (this._prefs.get("useAccentColor")) {
+            let general, active, inactive;
+            if (persona.accentcolor) {
+                general = persona.accentcolor;
+                active = persona.accentcolor;
+                inactive = persona.accentcolor;
+            } else {
+                general = "";
+                active = "";
+                inactive = "";
+            }
+            this._setTitlebarColors(general, active, inactive);
+        }
+
+        // Opacity overrides (firefox only)
+        if (PersonaService.appInfo.ID == PersonaService.FIREFOX_ID) {
+            let overrideOpacity = this._prefs.get("override.opacity");
+            let overrideActiveOpacity = this._prefs.get("override.activeOpacity");
+            for (let i = 0; i < document.styleSheets.length; i++) {
+                let styleSheet = document.styleSheets[i];
+                if (styleSheet.href == "chrome://personas/content/overlay.css") {
+                    if (typeof(overrideOpacity) != "undefined") {
+                        styleSheet.insertRule(
+                            "#main-window[persona] .tabbrowser-tab " +
+                            "{ opacity: " + overrideOpacity + " !important; }",
+                            0
+                        );
+                    }
+                    if (typeof(overrideActiveOpacity) != "undefined") {
+                        styleSheet.insertRule(
+                            "#main-window[persona] .tabbrowser-tab[selected=\"true\"] " +
+                            "{ opacity: " + overrideActiveOpacity + " !important; }",
+                            0
+                        );
+                        styleSheet.insertRule(
+                            "#main-window[persona] #urlbar, " +
+                            "#main-window[persona] #searchbar " +
+                            "{ opacity: " + overrideActiveOpacity + " !important; }",
+                            0
+                        );
+                    }
+                    break;
+                }
+            }
+        }
+
+    },
+
+    _applyDefault: function() {
+        // Reset the header.
+        this._header.removeAttribute("persona");
+        this._header.style.backgroundImage = "";
+
+        // Reset the footer.
+        this._footer.removeAttribute("persona");
+        this._footer.style.backgroundImage = "";
+
+        // Reset the text color.
+        for (let i = 0; i < document.styleSheets.length; i++) {
+            let styleSheet = document.styleSheets[i];
+            if (styleSheet.href == "chrome://personas/content/overlay.css") {
+                while (styleSheet.cssRules.length > 0)
+                    styleSheet.deleteRule(0);
                 break;
             }
-          }
-
-          // FIXME: figure out what to do about the disabled color.  Maybe we
-          // should let personas specify it independently and then apply it via
-          // a rule like this:
-          // #navigator-toolbox toolbarbutton[disabled="true"],
-          // #browser-toolbox toolbarbutton[disabled="true"],
-          // #browser-bottombox toolbarbutton[disabled="true"]
-          //   { color: #cccccc !important; }
-
-          // Stop iterating through stylesheets.
-          break;
         }
-      }
-    }
+        this._header.style.color = "";
 
-    // Style the titlebar with the accent color.
-    if (this._prefs.get("useAccentColor")) {
-      let general, active, inactive;
-      if (persona.accentcolor) {
-        general  = persona.accentcolor;
-        active   = persona.accentcolor;
-        inactive = persona.accentcolor;
-      }
-      else {
-        general  = "";
-        active   = "";
-        inactive = "";
-      }
-      this._setTitlebarColors(general, active, inactive);
-    }
+        // Reset the titlebar color.
+        if (this._prefs.get("useAccentColor")) {
+            this._setTitlebarColors("", "", "");
+        }
+    },
+
+    _setTitlebarColors: function(general, active, inactive) {
+        // Titlebar colors only have an effect on Mac.
+        if (PersonaService.appInfo.OS != "Darwin")
+            return;
 
-    // Opacity overrides (firefox only)
-    if (PersonaService.appInfo.ID == PersonaService.FIREFOX_ID) {
-      let overrideOpacity = this._prefs.get("override.opacity");
-      let overrideActiveOpacity = this._prefs.get("override.activeOpacity");
-      for (let i = 0; i < document.styleSheets.length; i++) {
-        let styleSheet = document.styleSheets[i];
-        if (styleSheet.href == "chrome://personas/content/overlay.css") {
-          if (typeof(overrideOpacity) != "undefined") {
-            styleSheet.insertRule(
-              "#main-window[persona] .tabbrowser-tab " +
-              "{ opacity: " + overrideOpacity + " !important; }",
-              0
-            );
-          }
-          if (typeof(overrideActiveOpacity) != "undefined") {
-            styleSheet.insertRule(
-              "#main-window[persona] .tabbrowser-tab[selected=\"true\"] " +
-              "{ opacity: " + overrideActiveOpacity + " !important; }",
-              0
-            );
-            styleSheet.insertRule(
-              "#main-window[persona] #urlbar, " +
-              "#main-window[persona] #searchbar " +
-              "{ opacity: " + overrideActiveOpacity + " !important; }",
-              0
-            );
-          }
-          break;
+        let changed = false;
+
+        if (general != this._header.getAttribute("titlebarcolor")) {
+            document.documentElement.setAttribute("titlebarcolor", general);
+            changed = true;
+        }
+        if (active != this._header.getAttribute("activetitlebarcolor")) {
+            document.documentElement.setAttribute("activetitlebarcolor", active);
+            changed = true;
+        }
+        if (inactive != this._header.getAttribute("inactivetitlebarcolor")) {
+            document.documentElement.setAttribute("inactivetitlebarcolor", inactive);
+            changed = true;
         }
-      }
-    }
 
-  },
-
-  _applyDefault: function() {
-    // Reset the header.
-    this._header.removeAttribute("persona");
-    this._header.style.backgroundImage = "";
-
-    // Reset the footer.
-    this._footer.removeAttribute("persona");
-    this._footer.style.backgroundImage = "";
-
-    // Reset the text color.
-    for (let i = 0; i < document.styleSheets.length; i++) {
-      let styleSheet = document.styleSheets[i];
-      if (styleSheet.href == "chrome://personas/content/overlay.css") {
-        while (styleSheet.cssRules.length > 0)
-          styleSheet.deleteRule(0);
-        break;
-      }
-    }
-    this._header.style.color = "";
+        if (changed && PersonaService.appInfo.platformVersion.indexOf("1.9.0") == 0) {
+            // FIXME: Incredibly gross hack in order to force a window redraw event
+            // that ensures that the titlebar color change is applied. We only have to
+            // do this for Firefox 3.0 (Gecko 1.9.0) because bug 485451 on the problem
+            // has been fixed for Firefox 3.5 (Gecko 1.9.1).
+            //
+            // This will unmaximize a maximized window on Windows and Linux,
+            // but we only do this on Mac (which is the only place
+            // the "titlebarcolor" attribute has any effect anyway at the moment),
+            // so that's ok for now.
+            //
+            // This will unminimize a minimized window on Mac, so we can't do it
+            // if the window is minimized.
+            if (window.windowState != Ci.nsIDOMChromeWindow.STATE_MINIMIZED) {
+                window.resizeTo(parseInt(window.outerWidth) + 1, window.outerHeight);
+                window.resizeTo(parseInt(window.outerWidth) - 1, window.outerHeight);
+            }
+        }
+    },
+
+    /**
+     * Checks if the current window is in private browsing mode, and shows
+     * an alert warning that personas will not work, if so.
+     */
+    checkPrivateBrowsing: function() {
+        if (typeof PrivateBrowsingUtils !== "undefined" &&
+            PrivateBrowsingUtils.isWindowPrivate(window)) {
+
+            const ID = "personas-plus-private-browsing-warning";
+
+            var notifications = gBrowser.getNotificationBox();
+            if (!notifications.getNotificationWithValue(ID)) {
+                let _ = this._strings.get.bind(this._strings);
+
+                notifications.appendNotification(
+                    _("pbm.message"), ID,
+                    "chrome://personas/content/personas_16x16.png",
+                    notifications.PRIORITY_WARNING_HIGH, [{
+                        label: _("pbm.button.label"),
+                        accessKey: _("pbm.button.accesskey"),
+                        callback: function(n) {
+                            n.close()
+                        }
+                    }]);
+            }
+        }
+    },
 
-    // Reset the titlebar color.
-    if (this._prefs.get("useAccentColor")) {
-      this._setTitlebarColors("", "", "");
-    }
-  },
 
-  _setTitlebarColors: function(general, active, inactive) {
-    // Titlebar colors only have an effect on Mac.
-    if (PersonaService.appInfo.OS != "Darwin")
-      return;
+    //**************************************************************************//
+    // Persona Selection, Preview, and Reset
 
-    let changed = false;
+    /**
+     * Select the persona specified by the DOM node target of the given event.
+     *
+     * @param event   {Event}
+     *        the SelectPersona DOM event
+     */
+    onSelectPersona: PersonaService.wrap(function(event) {
+        let node = event.target;
 
-    if (general != this._header.getAttribute("titlebarcolor")) {
-      document.documentElement.setAttribute("titlebarcolor", general);
-      changed = true;
-    }
-    if (active != this._header.getAttribute("activetitlebarcolor")) {
-      document.documentElement.setAttribute("activetitlebarcolor", active);
-      changed = true;
-    }
-    if (inactive != this._header.getAttribute("inactivetitlebarcolor")) {
-      document.documentElement.setAttribute("inactivetitlebarcolor", inactive);
-      changed = true;
-    }
+        if (!node.hasAttribute("persona"))
+            throw "node does not have 'persona' attribute";
 
-    if (changed && PersonaService.appInfo.platformVersion.indexOf("1.9.0") == 0) {
-      // FIXME: Incredibly gross hack in order to force a window redraw event
-      // that ensures that the titlebar color change is applied. We only have to
-      // do this for Firefox 3.0 (Gecko 1.9.0) because bug 485451 on the problem
-      // has been fixed for Firefox 3.5 (Gecko 1.9.1).
-      //
-      // This will unmaximize a maximized window on Windows and Linux,
-      // but we only do this on Mac (which is the only place
-      // the "titlebarcolor" attribute has any effect anyway at the moment),
-      // so that's ok for now.
-      //
-      // This will unminimize a minimized window on Mac, so we can't do it
-      // if the window is minimized.
-      if (window.windowState != Ci.nsIDOMChromeWindow.STATE_MINIMIZED) {
-        window.resizeTo(parseInt(window.outerWidth)+1, window.outerHeight);
-        window.resizeTo(parseInt(window.outerWidth)-1, window.outerHeight);
-      }
-    }
-  },
-
-  /**
-   * Checks if the current window is in private browsing mode, and shows
-   * an alert warning that personas will not work, if so.
-   */
-  checkPrivateBrowsing: function() {
-    if (typeof PrivateBrowsingUtils !== "undefined" &&
-          PrivateBrowsingUtils.isWindowPrivate(window)) {
-
-      const ID = "personas-plus-private-browsing-warning";
-
-      var notifications = gBrowser.getNotificationBox();
-      if(!notifications.getNotificationWithValue(ID)) {
-        let _ = this._strings.get.bind(this._strings);
-
-        notifications.appendNotification(
-            _("pbm.message"), ID,
-            "chrome://personas/content/personas_16x16.png",
-            notifications.PRIORITY_WARNING_HIGH,
-            [{ label: _("pbm.button.label"),
-                accessKey: _("pbm.button.accesskey"),
-                callback: function (n) { n.close() } }]);
-      }
-    }
-  },
-
-
-  //**************************************************************************//
-  // Persona Selection, Preview, and Reset
-
-  /**
-   * Select the persona specified by the DOM node target of the given event.
-   *
-   * @param event   {Event}
-   *        the SelectPersona DOM event
-   */
-  onSelectPersona: PersonaService.wrap(function(event) {
-    let node = event.target;
-
-    if (!node.hasAttribute("persona"))
-      throw "node does not have 'persona' attribute";
-
-    let persona = node.getAttribute("persona");
-
-    // We check if the user wants per-window personas
-    if (this._prefs.get("perwindow")) {
-      // Since per-window personas are window-specific, we persist and
-      // set them from here instead instead of going through PersonaService.
-      switch (persona) {
-        // We store the persona in the "persona" window property, and do not
-        // have a seperate type as is the case with pan-window personas. This
-        // is because we currently support only a single type of persona in
-        // per-window mode. We'll add a new type property when we support
-        // other types of selectable personas, such as "random".
-        case "default":
-          this._applyDefault();
-          this._sessionStore.setWindowValue(window, "persona", "default");
-          break;
-        default:
-          this._applyPersona(JSON.parse(persona));
-          this._sessionStore.setWindowValue(window, "persona", persona);
-          break;
-      }
-    // Usual, pan-window persona mode
-    } else {
-      // The persona attribute is either a JSON string specifying the persona
-      // to apply or a string identifying a special persona (default, random).
-      switch (persona) {
-        case "default":
-          PersonaService.changeToDefaultPersona();
-          break;
-        case "random":
-          PersonaService.changeToRandomPersona(node.getAttribute("category"));
-          break;
-        case "custom":
-          PersonaService.changeToPersona(PersonaService.customPersona);
-          break;
-        default:
-          PersonaService.changeToPersona(JSON.parse(persona));
-          break;
-      }
-    }
-  }),
-
-  onPreviewPersona: PersonaService.wrap(function(event) {
-    if (!this._prefs.get("previewEnabled"))
-      return;
-
-    if (!event.target.hasAttribute("persona"))
-      throw "node does not have 'persona' attribute";
-
-    //this._previewPersona(event.target.getAttribute("persona"));
-    let persona = JSON.parse(event.target.getAttribute("persona"));
-
-    // We check if the user wants per-window personas
-    if (this._prefs.get("perwindow")) {
-      // We temporarily set the window specific persona here and let
-      // onResetPersona reset it.
-      switch (persona) {
-        case "default":
-          this._applyDefault();
-          break;
-        default:
-          this._applyPersona(persona);
-          break;
-      }
-    } else {
-      if (this._resetTimeoutID) {
-        window.clearTimeout(this._resetTimeoutID);
-        this._resetTimeoutID = null;
-      }
-
-      let t = this;
-      let persona = JSON.parse(event.target.getAttribute("persona"));
-      let callback = function() { t._previewPersona(persona) };
-      this._previewTimeoutID =
-        window.setTimeout(callback, this._previewTimeout);
-    }
-  }),
-
-  _previewPersona: function(persona) {
-    PersonaService.previewPersona(persona);
-  },
-
-  onResetPersona: PersonaService.wrap(function(event) {
-    if (!this._prefs.get("previewEnabled"))
-      return;
-
-    //this._resetPersona();
-    // If per-window personas are enabled and there's a valid persona
-    // value set for this window, don't reset.
-    if (this._prefs.get("perwindow") &&
-      this._sessionStore.getWindowValue(window, "persona")) {
-      return;
-    }
+        let persona = node.getAttribute("persona");
 
-    if (this._previewTimeoutID) {
-      window.clearTimeout(this._previewTimeoutID);
-      this._previewTimeoutID = null;
-    }
+        // We check if the user wants per-window personas
+        if (this._prefs.get("perwindow")) {
+            // Since per-window personas are window-specific, we persist and
+            // set them from here instead instead of going through PersonaService.
+            switch (persona) {
+                // We store the persona in the "persona" window property, and do not
+                // have a seperate type as is the case with pan-window personas. This
+                // is because we currently support only a single type of persona in
+                // per-window mode. We'll add a new type property when we support
+                // other types of selectable personas, such as "random".
+                case "default":
+                    this._applyDefault();
+                    this._sessionStore.setWindowValue(window, "persona", "default");
+                    break;
+                default:
+                    this._applyPersona(JSON.parse(persona));
+                    this._sessionStore.setWindowValue(window, "persona", persona);
+                    break;
+            }
+            // Usual, pan-window persona mode
+        } else {
+            // The persona attribute is either a JSON string specifying the persona
+            // to apply or a string identifying a special persona (default, random).
+            switch (persona) {
+                case "default":
+                    PersonaService.changeToDefaultPersona();
+                    break;
+                case "random":
+                    PersonaService.changeToRandomPersona(node.getAttribute("category"));
+                    break;
+                case "custom":
+                    PersonaService.changeToPersona(PersonaService.customPersona);
+                    break;
+                default:
+                    PersonaService.changeToPersona(JSON.parse(persona));
+                    break;
+            }
+        }
+    }),
 
-    let t = this;
-    let callback = function() { t._resetPersona() };
-    this._resetTimeoutID = window.setTimeout(callback, this._previewTimeout);
-  }),
-
-  _resetPersona: function() {
-    PersonaService.resetPersona();
-  },
-
-  /**
-   * Confirm that Firefox has this Personas extension when requested by
-   * a web page via a CheckPersonas event.  Checks to ensure the page is hosted
-   * on a host in the whitelist before responding to the event, so only
-   * whitelisted pages can find out if Personas is installed.
-   *
-   * @param event   {Event}
-   *        the CheckPersonas DOM event
-   */
-  onCheckPersonasFromContent: PersonaService.wrap(function(event) {
-    this._authorizeHost(event);
-    event.target.setAttribute("personas", "true");
-  }),
-
-  onSelectPreferences: PersonaService.wrap(function() {
-    window.openDialog('chrome://personas/content/preferences.xul', '',
-                      'chrome,titlebar,toolbar,centerscreen');
-  }),
-
-  onEditCustomPersona: PersonaService.wrap(function() {
-    this.openURLInTab("chrome://personas/content/customPersonaEditor.xul");
-  }),
-
-  /**
-   * Adds the favorite persona specified by a web page via a AddFavoritePersona event.
-   * Checks to ensure the page is hosted on a server authorized to select personas.
-   *
-   * @param event   {Event}
-   *        the AddFavoritePersona DOM event
-   */
-  onAddFavoritePersonaFromContent: PersonaService.wrap(function(event) {
-    this._authorizeHost(event);
-    this.onAddFavoritePersona(event);
-  }),
-
-  /**
-   * Adds the persona specified by the DOM node target of the given event to
-   * the favorites list.
-   *
-   * @param event   {Event}
-   *        the AddFavoritePersona DOM event
-   */
-  onAddFavoritePersona: PersonaService.wrap(function(event) {
-    let node = event.target;
-
-    if (!node.hasAttribute("persona"))
-      throw "node does not have 'persona' attribute";
-
-    let persona = node.getAttribute("persona");
-    PersonaService.addFavoritePersona(JSON.parse(persona));
-  }),
-
-  /**
-   * Removes the favorite persona specified by a web page via a
-   * RemoveFavoritePersona event.
-   * Checks to ensure the page is hosted on a server authorized to select personas.
-   *
-   * @param event   {Event}
-   *        the RemoveFavoritePersona DOM event
-   */
-  onRemoveFavoritePersonaFromContent: PersonaService.wrap(function(event) {
-    this._authorizeHost(event);
-    this.onRemoveFavoritePersona(event);
-  }),
-
-  /**
-   * Removes the persona specified by the DOM node target of the given event
-   * from the favorites list.
-   *
-   * @param event   {Event}
-   *        the RemoveFavoritePersona DOM event
-   */
-  onRemoveFavoritePersonaFromContent: PersonaService.wrap(function(event) {
-    let node = event.target;
-
-    if (!node.hasAttribute("persona"))
-      throw "node does not have 'persona' attribute";
-
-    let persona = node.getAttribute("persona");
-    PersonaService.removeFavoritePersona(JSON.parse(persona));
-  }),
-
-  /**
-   * Ensure the host that loaded the document from which the given DOM event
-   * came matches an entry in the personas whitelist.  The host matches if it
-   * equals one of the entries in the whitelist.  For example, if
-   * www.mozilla.com is an entry in the whitelist, then www.mozilla.com matches,
-   * but labs.mozilla.com, mozilla.com, and evil.com do not.
-   *
-   * @param aEvent {Event} the DOM event
-   */
-  _authorizeHost: function(aEvent) {
-    let host = aEvent.target.ownerDocument.location.hostname;
-    let authorizedHosts = this._prefs.get("authorizedHosts").split(/[, ]+/);
-    if (!authorizedHosts.some(function(v) v == host))
-      throw host + " not authorized to modify personas";
-  },
-
-
-  //**************************************************************************//
-  // Popup Construction
-
-  onMenuButtonMouseDown: PersonaService.wrap(function(event) {
-    // If the menu popup isn't on the menu button, then move the popup
-    // onto the button so the popup appears when the user clicks it.
-    // We'll move the popup back onto the Personas menu in the Tools menu
-    // when the popup hides.
-    // FIXME: remove this workaround once bug 461899 is fixed.
-    if (this._menuPopup.parentNode != this._menuButton)
-      this._menuButton.appendChild(this._menuPopup);
-  }),
-
-  onToolbarButtonMouseDown: PersonaService.wrap(function(event) {
-    // If the menu popup isn't on the toolbar button, then move the popup
-    // onto the button so the popup appears when the user clicks it.
-    // We'll move the popup back onto the Personas menu in the Tools menu
-    // when the popup hides.
-    // FIXME: remove this workaround once bug 461899 is fixed.
-    if (this._menuPopup.parentNode != this._toolbarButton)
-      this._toolbarButton.appendChild(this._menuPopup);
-  }),
-
-  onPopupShowing: PersonaService.wrap(function(event) {
-    if (event.target == this._menuPopup)
-      this._rebuildMenu();
-
-    return true;
-  }),
-
-  onPopupHiding: PersonaService.wrap(function(event) {
-    if (event.target == this._menuPopup) {
-      // If the menu popup isn't on the Personas menu in the Tools menu,
-      // then move the popup back onto that menu so the popup appears when
-      // the user selects it.  We'll move the popup back onto the menu button
-      // in onMenuButtonMouseDown when the user clicks on the menu button.
-      if (this._menuPopup.parentNode != this._menu) {
-        this._menuPopup.parentNode.removeAttribute("open");
-        this._menu.appendChild(this._menuPopup);
-      }
-    }
-  }),
-
-  _rebuildMenu: function() {
-    // If we don't have personas data, we won't be able to fully build the menu,
-    // and we'll display a message to that effect in tooltips over the parts
-    // of the menu that are data-dependent (the Most Popular, New, and
-    // By Category submenus).  The message also suggests that the user try again
-    // in a few minutes, so here we immediately try to refresh data so it will
-    // be available when the user tries again.
-    if (!PersonaService.personas)
-      PersonaService.refreshData();
-
-    let openingSeparator = document.getElementById("personasOpeningSeparator");
-    let closingSeparator = document.getElementById("personasClosingSeparator");
-
-    // Remove everything between the two separators.
-    while (openingSeparator.nextSibling && openingSeparator.nextSibling != closingSeparator)
-      this._menuPopup.removeChild(openingSeparator.nextSibling);
-
-    // Update the item that identifies the current persona.
-    let personaStatus = document.getElementById("persona-current");
-    let name = PersonaService.currentPersona ? PersonaService.currentPersona.name
-                                             : this._strings.get("unnamedPersona");
-
-    personaStatus.setAttribute("class", "menuitem-iconic");
-
-    if (PersonaService.selected == "random") {
-      personaStatus.setAttribute("label", this._strings.get("randomPersona", [PersonaService.category, name]));
-      personaStatus.setAttribute("image", PersonaService.currentPersona.dataurl ? PersonaService.currentPersona.dataurl
-                                          : "chrome://personas/content/personas_16x16.png");
-
-    } if (PersonaService.selected == "default") {
-      personaStatus.setAttribute("label", this._strings.get("Default"));
-      personaStatus.removeAttribute("image");
-      personaStatus.removeAttribute("menuitem-iconic");
-    } else {
-      personaStatus.setAttribute("label", name);
-      personaStatus.setAttribute("image", PersonaService.currentPersona.dataurl ? PersonaService.currentPersona.dataurl
-                                          : "chrome://personas/content/personas_16x16.png");
-    }
+    onPreviewPersona: PersonaService.wrap(function(event) {
+        if (!this._prefs.get("previewEnabled"))
+            return;
 
-    let personaStatusDetail = document.getElementById("persona-current-view-detail");
-    personaStatusDetail.setAttribute("disabled", PersonaService.currentPersona.detailURL ? "false" : "true");
-    personaStatusDetail.setAttribute("label", this._strings.get("viewDetail"));
-    personaStatusDetail.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
-    personaStatusDetail.setAttribute("href", PersonaService.currentPersona.detailURL);
-
-    let personaStatusDesigner = document.getElementById("persona-current-view-designer");
-    // collapse the "More From User" menu item for custom personas or personas
-    // with null username. In this case we only check the username is not null
-    // because it is used to generate the url to go to the personas designer page
-    // (bug 526788).
-    let persona = PersonaService.currentPersona;
-    if (!persona.authorURL) {
-      personaStatusDesigner.setAttribute("collapsed", true);
-    } else {
-      personaStatusDesigner.removeAttribute("collapsed");
-      let designerLabel = persona.author || persona.username;
-      personaStatusDesigner.setAttribute("label", this._strings.get("viewDesigner", [designerLabel]));
-      personaStatusDesigner.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
-      personaStatusDesigner.setAttribute("href", persona.authorURL);
-    }
+        if (!event.target.hasAttribute("persona"))
+            throw "node does not have 'persona' attribute";
+
+        //this._previewPersona(event.target.getAttribute("persona"));
+        let persona = JSON.parse(event.target.getAttribute("persona"));
 
-    // Update the checkmark on the Default menu item.
-    document.getElementById("defaultPersona").setAttribute("checked", (PersonaService.selected == "default" ? "true" : "false"));
+        // We check if the user wants per-window personas
+        if (this._prefs.get("perwindow")) {
+            // We temporarily set the window specific persona here and let
+            // onResetPersona reset it.
+            switch (persona) {
+                case "default":
+                    this._applyDefault();
+                    break;
+                default:
+                    this._applyPersona(persona);
+                    break;
+            }
+        } else {
+            if (this._resetTimeoutID) {
+                window.clearTimeout(this._resetTimeoutID);
+                this._resetTimeoutID = null;
+            }
 
-    // FIXME: factor out the duplicate code below.
+            let t = this;
+            let persona = JSON.parse(event.target.getAttribute("persona"));
+            let callback = function() {
+                t._previewPersona(persona)
+            };
+            this._previewTimeoutID =
+                window.setTimeout(callback, this._previewTimeout);
+        }
+    }),
 
-    // Create the Favorites menu.
-    {
-      let menu = document.createElement("menu");
-      menu.setAttribute("label", this._strings.get("favorites"));
+    _previewPersona: function(persona) {
+        PersonaService.previewPersona(persona);
+    },
 
-      let popupmenu = menu.appendChild(document.createElement("menupopup"));
+    onResetPersona: PersonaService.wrap(function(event) {
+        if (!this._prefs.get("previewEnabled"))
+            return;
 
-      if (!PersonaService.favorites) {
-        let item = popupmenu.appendChild(document.createElement("menuitem"));
-        item.setAttribute("label", this._strings.get("favoritesSignIn"));
-        item.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
-        item.setAttribute("href", PersonaService.getURL("favorites-browse"));
-      } else {
-        let favorites = PersonaService.favorites;
-        if (favorites.length) {
-          for each (let persona in favorites)
-            popupmenu.appendChild(this._createPersonaItem(persona));
-          popupmenu.appendChild(document.createElement("menuseparator"));
-
-          // Disable random from favorites menu item if per-window
-          // personas are enabled
-          if (!this._prefs.get("perwindow")) {
-            let item = popupmenu.appendChild(document.createElement("menuitem"));
-            item.setAttribute("label", this._strings.get("useRandomPersona", [this._strings.get("favorites")]));
-            // item.setAttribute("type", "checkbox");
-            item.setAttribute("checked", (PersonaService.selected == "randomFavorite"));
-            item.setAttribute("autocheck", "false");
-            item.setAttribute("oncommand", "PersonaController.toggleFavoritesRotation()");
-          }
+        //this._resetPersona();
+        // If per-window personas are enabled and there's a valid persona
+        // value set for this window, don't reset.
+        if (this._prefs.get("perwindow") &&
+            this._sessionStore.getWindowValue(window, "persona")) {
+            return;
         }
 
-        // go to my favorites menu item
-        let item = popupmenu.appendChild(document.createElement("menuitem"));
-        item.setAttribute("label", this._strings.get("favoritesGoTo"));
-        item.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
-        item.setAttribute("href", PersonaService.getURL("favorites-browse"));
-      }
+        if (this._previewTimeoutID) {
+            window.clearTimeout(this._previewTimeoutID);
+            this._previewTimeoutID = null;
+        }
 
-      this._menuPopup.insertBefore(menu, closingSeparator);
-    }
+        let t = this;
+        let callback = function() {
+            t._resetPersona()
+        };
+        this._resetTimeoutID = window.setTimeout(callback, this._previewTimeout);
+    }),
+
+    _resetPersona: function() {
+        PersonaService.resetPersona();
+    },
+
+    /**
+     * Confirm that Firefox has this Personas extension when requested by
+     * a web page via a CheckPersonas event.  Checks to ensure the page is hosted
+     * on a host in the whitelist before responding to the event, so only
+     * whitelisted pages can find out if Personas is installed.
+     *
+     * @param event   {Event}
+     *        the CheckPersonas DOM event
+     */
+    onCheckPersonasFromContent: PersonaService.wrap(function(event) {
+        this._authorizeHost(event);
+        event.target.setAttribute("personas", "true");
+    }),
+
+    onSelectPreferences: PersonaService.wrap(function() {
+        window.openDialog('chrome://personas/content/preferences.xul', '',
+            'chrome,titlebar,toolbar,centerscreen');
+    }),
+
+    onEditCustomPersona: PersonaService.wrap(function() {
+        this.openURLInTab("chrome://personas/content/customPersonaEditor.xul");
+    }),
+
+    /**
+     * Adds the favorite persona specified by a web page via a AddFavoritePersona event.
+     * Checks to ensure the page is hosted on a server authorized to select personas.
+     *
+     * @param event   {Event}
+     *        the AddFavoritePersona DOM event
+     */
+    onAddFavoritePersonaFromContent: PersonaService.wrap(function(event) {
+        this._authorizeHost(event);
+        this.onAddFavoritePersona(event);
+    }),
+
+    /**
+     * Adds the persona specified by the DOM node target of the given event to
+     * the favorites list.
+     *
+     * @param event   {Event}
+     *        the AddFavoritePersona DOM event
+     */
+    onAddFavoritePersona: PersonaService.wrap(function(event) {
+        let node = event.target;
+
+        if (!node.hasAttribute("persona"))
+            throw "node does not have 'persona' attribute";
+
+        let persona = node.getAttribute("persona");
+        PersonaService.addFavoritePersona(JSON.parse(persona));
+    }),
+
+    /**
+     * Removes the favorite persona specified by a web page via a
+     * RemoveFavoritePersona event.
+     * Checks to ensure the page is hosted on a server authorized to select personas.
+     *
+     * @param event   {Event}
+     *        the RemoveFavoritePersona DOM event
+     */
+    onRemoveFavoritePersonaFromContent: PersonaService.wrap(function(event) {
+        this._authorizeHost(event);
+        this.onRemoveFavoritePersona(event);
+    }),
+
+    /**
+     * Removes the persona specified by the DOM node target of the given event
+     * from the favorites list.
+     *
+     * @param event   {Event}
+     *        the RemoveFavoritePersona DOM event
+     */
+    onRemoveFavoritePersonaFromContent: PersonaService.wrap(function(event) {
+        let node = event.target;
+
+        if (!node.hasAttribute("persona"))
+            throw "node does not have 'persona' attribute";
+
+        let persona = node.getAttribute("persona");
+        PersonaService.removeFavoritePersona(JSON.parse(persona));
+    }),
+
+    /**
+     * Ensure the host that loaded the document from which the given DOM event
+     * came matches an entry in the personas whitelist.  The host matches if it
+     * equals one of the entries in the whitelist.  For example, if
+     * www.mozilla.com is an entry in the whitelist, then www.mozilla.com matches,
+     * but labs.mozilla.com, mozilla.com, and evil.com do not.
+     *
+     * @param aEvent {Event} the DOM event
+     */
+    _authorizeHost: function(aEvent) {
+        let host = aEvent.target.ownerDocument.location.hostname;
+        let authorizedHosts = this._prefs.get("authorizedHosts").split(/[, ]+/);
+        if (!authorizedHosts.some(function(v) v == host))
+            throw host + " not authorized to modify personas";
+    },
+
+
+    //**************************************************************************//
+    // Popup Construction
+
+    onMenuButtonMouseDown: PersonaService.wrap(function(event) {
+        // If the menu popup isn't on the menu button, then move the popup
+        // onto the button so the popup appears when the user clicks it.
+        // We'll move the popup back onto the Personas menu in the Tools menu
+        // when the popup hides.
+        // FIXME: remove this workaround once bug 461899 is fixed.
+        if (this._menuPopup.parentNode != this._menuButton)
+            this._menuButton.appendChild(this._menuPopup);
+    }),
+
+    onToolbarButtonMouseDown: PersonaService.wrap(function(event) {
+        // If the menu popup isn't on the toolbar button, then move the popup
+        // onto the button so the popup appears when the user clicks it.
+        // We'll move the popup back onto the Personas menu in the Tools menu
+        // when the popup hides.
+        // FIXME: remove this workaround once bug 461899 is fixed.
+        if (this._menuPopup.parentNode != this._toolbarButton)
+            this._toolbarButton.appendChild(this._menuPopup);
+    }),
+
+    onPopupShowing: PersonaService.wrap(function(event) {
+        if (event.target == this._menuPopup)
+            this._rebuildMenu();
+
+        return true;
+    }),
+
+    onPopupHiding: PersonaService.wrap(function(event) {
+        if (event.target == this._menuPopup) {
+            // If the menu popup isn't on the Personas menu in the Tools menu,
+            // then move the popup back onto that menu so the popup appears when
+            // the user selects it.  We'll move the popup back onto the menu button
+            // in onMenuButtonMouseDown when the user clicks on the menu button.
+            if (this._menuPopup.parentNode != this._menu) {
+                this._menuPopup.parentNode.removeAttribute("open");
+                this._menu.appendChild(this._menuPopup);
+            }
+        }
+    }),
 
-    // Create the "Recently Selected" menu.
-    {
-      let menu = document.createElement("menu");
-      menu.setAttribute("label", this._strings.get("recent"));
-      let popupmenu = document.createElement("menupopup");
+    _rebuildMenu: function() {
+        // If we don't have personas data, we won't be able to fully build the menu,
+        // and we'll display a message to that effect in tooltips over the parts
+        // of the menu that are data-dependent (the Most Popular, New, and
+        // By Category submenus).  The message also suggests that the user try again
+        // in a few minutes, so here we immediately try to refresh data so it will
+        // be available when the user tries again.
+        if (!PersonaService.personas)
+            PersonaService.refreshData();
 
-      let recentPersonas = PersonaService.getRecentPersonas();
-      for each (let persona in recentPersonas) {
-        popupmenu.appendChild(this._createPersonaItem(persona));
-      }
+        let openingSeparator = document.getElementById("personasOpeningSeparator");
+        let closingSeparator = document.getElementById("personasClosingSeparator");
 
-      menu.appendChild(popupmenu);
-      this._menuPopup.insertBefore(menu, closingSeparator);
-      this._menuPopup.insertBefore(document.createElement("menuseparator"), closingSeparator);
-    }
+        // Remove everything between the two separators.
+        while (openingSeparator.nextSibling && openingSeparator.nextSibling != closingSeparator)
+            this._menuPopup.removeChild(openingSeparator.nextSibling);
+
+        // Update the item that identifies the current persona.
+        let personaStatus = document.getElementById("persona-current");
+        let name = PersonaService.currentPersona ? PersonaService.currentPersona.name : this._strings.get("unnamedPersona");
 
-    // Create the Featured menu.
-    {
-      let menu = document.createElement("menu");
-      menu.setAttribute("label", this._strings.get("featured"));
-
-      if (PersonaService.personas) {
-        let popupmenu = document.createElement("menupopup");
-        for each (let persona in PersonaService.personas.featured)
-          popupmenu.appendChild(this._createPersonaItem(persona));
-
-        // Create an item that picks a random persona from the category.
-        // Disable random from category menu item if per-window
-        // personas are enabled.
-        if (!this._prefs.get("perwindow")) {
-          popupmenu.appendChild(document.createElement("menuseparator"));
-          popupmenu.appendChild(this._createRandomItem(this._strings.get("featured"), "featured"));
+        personaStatus.setAttribute("class", "menuitem-iconic");
+
+        if (PersonaService.selected == "random") {
+            personaStatus.setAttribute("label", this._strings.get("randomPersona", [PersonaService.category, name]));
+            personaStatus.setAttribute("image", PersonaService.currentPersona.dataurl ? PersonaService.currentPersona.dataurl : "chrome://personas/content/personas_16x16.png");
+
+        }
+        if (PersonaService.selected == "default") {
+            personaStatus.setAttribute("label", this._strings.get("Default"));
+            personaStatus.removeAttribute("image");
+            personaStatus.removeAttribute("menuitem-iconic");
+        } else {
+            personaStatus.setAttribute("label", name);
+            personaStatus.setAttribute("image", PersonaService.currentPersona.dataurl ? PersonaService.currentPersona.dataurl : "chrome://personas/content/personas_16x16.png");
         }
 
-        // Create an item that links to the gallery for this category.
-        popupmenu.appendChild(
-          this._createViewMoreItem(this._strings.get("featured"),
-                                   "featured"));
+        let personaStatusDetail = document.getElementById("persona-current-view-detail");
+        personaStatusDetail.setAttribute("disabled", PersonaService.currentPersona.detailURL ? "false" : "true");
+        personaStatusDetail.setAttribute("label", this._strings.get("viewDetail"));
+        personaStatusDetail.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
+        personaStatusDetail.setAttribute("href", PersonaService.currentPersona.detailURL);
+
+        let personaStatusDesigner = document.getElementById("persona-current-view-designer");
+        // collapse the "More From User" menu item for custom personas or personas
+        // with null username. In this case we only check the username is not null
+        // because it is used to generate the url to go to the personas designer page
+        // (bug 526788).
+        let persona = PersonaService.currentPersona;
+        if (!persona.authorURL) {
+            personaStatusDesigner.setAttribute("collapsed", true);
+        } else {
+            personaStatusDesigner.removeAttribute("collapsed");
+            let designerLabel = persona.author || persona.username;
+            personaStatusDesigner.setAttribute("label", this._strings.get("viewDesigner", [designerLabel]));
+            personaStatusDesigner.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
+            personaStatusDesigner.setAttribute("href", persona.authorURL);
+        }
 
-        menu.appendChild(popupmenu);
-      }
-      else {
-        menu.setAttribute("disabled", "true");
-        menu.setAttribute("tooltip", "personasDataUnavailableTooltip");
-      }
+        // Update the checkmark on the Default menu item.
+        document.getElementById("defaultPersona").setAttribute("checked", (PersonaService.selected == "default" ? "true" : "false"));
+
+        // FIXME: factor out the duplicate code below.
+
+        // Create the Favorites menu.
+        {
+            let menu = document.createElement("menu");
+            menu.setAttribute("label", this._strings.get("favorites"));
+
+            let popupmenu = menu.appendChild(document.createElement("menupopup"));
+
+            if (PersonaService.favoritesError) {
+                let item = popupmenu.appendChild(document.createElement("menuitem"));
+                item.setAttribute("label", this._strings.get("favoritesError"));
+                item.setAttribute("disabled", "true");
+            } else if (!PersonaService.favorites) {
+                let item = popupmenu.appendChild(document.createElement("menuitem"));
+                item.setAttribute("label", this._strings.get("favoritesSignIn"));
+                item.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
+                item.setAttribute("href", PersonaService.getURL("favorites-browse"));
+            } else {
+                let favorites = PersonaService.favorites;
+                if (favorites.length) {
+                    for each(let persona in favorites)
+                    popupmenu.appendChild(this._createPersonaItem(persona));
+                    popupmenu.appendChild(document.createElement("menuseparator"));
+
+                    // Disable random from favorites menu item if per-window
+                    // personas are enabled
+                    if (!this._prefs.get("perwindow")) {
+                        let item = popupmenu.appendChild(document.createElement("menuitem"));
+                        item.setAttribute("label", this._strings.get("useRandomPersona", [this._strings.get("favorites")]));
+                        // item.setAttribute("type", "checkbox");
+                        item.setAttribute("checked", (PersonaService.selected == "randomFavorite"));
+                        item.setAttribute("autocheck", "false");
+                        item.setAttribute("oncommand", "PersonaController.toggleFavoritesRotation()");
+                    }
+                }
+
+                // go to my favorites menu item
+                let item = popupmenu.appendChild(document.createElement("menuitem"));
+                item.setAttribute("label", this._strings.get("favoritesGoTo"));
+                item.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
+                item.setAttribute("href", PersonaService.getURL("favorites-browse"));
+            }
 
-      this._menuPopup.insertBefore(menu, closingSeparator);
-    }
+            this._menuPopup.insertBefore(menu, closingSeparator);
+        }
 
-    // Update the Custom menu. Custom personas unavailable in per-window
-    // personas mode.
-    let customMenu = document.getElementById("personas-plus-custom-menu");
-    customMenu.hidden = this._prefs.get("perwindow") || !this._prefs.get("showCustomMenu");
-    if (!customMenu.hidden) {
-       let name = PersonaService.customPersona &&
-                   PersonaService.customPersona.name ? PersonaService.customPersona.name
-                                                     : this._strings.get("customPersona");
-       customMenu.setAttribute("label", name);
-    }
-  },
+        // Create the "Recently Selected" menu.
+        {
+            let menu = document.createElement("menu");
+            menu.setAttribute("label", this._strings.get("recent"));
+            let popupmenu = document.createElement("menupopup");
 
-  _createPersonaItem: function(persona) {
-    let item = document.createElement("menuitem");
-    let theme = PersonaService.getPersonaJSON(persona);
+            let recentPersonas = PersonaService.getRecentPersonas();
+            for each(let persona in recentPersonas) {
+                popupmenu.appendChild(this._createPersonaItem(persona));
+            }
 
-    let headerURI;
-    if (persona.custom) {
-      headerURI = theme.headerURL || theme.header;
-    } else {
-      headerURI = theme.dataurl || theme.iconURL;
-    }
+            menu.appendChild(popupmenu);
+            this._menuPopup.insertBefore(menu, closingSeparator);
+            this._menuPopup.insertBefore(document.createElement("menuseparator"), closingSeparator);
+        }
 
-    item.setAttribute("class", "menuitem-iconic");
-    item.setAttribute("image", headerURI);
-    item.setAttribute("label", persona.name);
-//    item.setAttribute("type", "checkbox");
-    item.setAttribute("checked", (PersonaService.selected != "default" &&
-                                  PersonaService.currentPersona &&
-                                  PersonaService.currentPersona.id == persona.id));
-    item.setAttribute("autocheck", "false");
-    item.setAttribute("oncommand", "PersonaController.onSelectPersona(event)");
-    item.setAttribute("recent", persona.recent ? "true" : "false");
-    item.setAttribute("persona", JSON.stringify(theme));
-    item.addEventListener("DOMMenuItemActive", function(evt) { PersonaController.onPreviewPersona(evt) }, false);
-    item.addEventListener("DOMMenuItemInactive", function(evt) { PersonaController.onResetPersona(evt) }, false);
-
-    return item;
-  },
-
-  _createViewMoreItem: function(category, categoryId) {
-    let item = document.createElement("menuitem");
-
-    item.setAttribute("class", "menuitem-iconic");
-    item.setAttribute("label", this._strings.get("viewMoreFrom", [category]));
-    item.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
-
-    if (categoryId == "featured") {
-      item.setAttribute("href", PersonaService.getURL("browse", { SORT: "featured" }));
-    }
+        // Create the Featured menu.
+        {
+            let menu = document.createElement("menu");
+            menu.setAttribute("label", this._strings.get("featured"));
+
+            if (PersonaService.personas) {
+                let popupmenu = document.createElement("menupopup");
+                for each(let persona in PersonaService.personas.featured)
+                popupmenu.appendChild(this._createPersonaItem(persona));
+
+                // Create an item that picks a random persona from the category.
+                // Disable random from category menu item if per-window
+                // personas are enabled.
+                if (!this._prefs.get("perwindow")) {
+                    popupmenu.appendChild(document.createElement("menuseparator"));
+                    popupmenu.appendChild(this._createRandomItem(this._strings.get("featured"), "featured"));
+                }
+
+                // Create an item that links to the gallery for this category.
+                popupmenu.appendChild(
+                    this._createViewMoreItem(this._strings.get("featured"),
+                        "featured"));
+
+                menu.appendChild(popupmenu);
+            } else {
+                menu.setAttribute("disabled", "true");
+                menu.setAttribute("tooltip", "personasDataUnavailableTooltip");
+            }
 
-    return item;
-  },
+            this._menuPopup.insertBefore(menu, closingSeparator);
+        }
+
+        // Update the Custom menu. Custom personas unavailable in per-window
+        // personas mode.
+        let customMenu = document.getElementById("personas-plus-custom-menu");
+        customMenu.hidden = this._prefs.get("perwindow") || !this._prefs.get("showCustomMenu");
+        if (!customMenu.hidden) {
+            let name = PersonaService.customPersona &&
+                PersonaService.customPersona.name ? PersonaService.customPersona.name : this._strings.get("customPersona");
+            customMenu.setAttribute("label", name);
+        }
+    },
+
+    _createPersonaItem: function(persona) {
+        let item = document.createElement("menuitem");
+        let theme = PersonaService.getPersonaJSON(persona);
 
-  _createRandomItem: function(aCategoryName, aCategory) {
-    let item = document.createElement("menuitem");
+        let headerURI;
+        if (persona.custom) {
+            headerURI = theme.headerURL || theme.header;
+        } else {
+            headerURI = theme.dataurl || theme.iconURL;
+        }
 
-    item.setAttribute("class", "menuitem-iconic");
-    item.setAttribute("label", this._strings.get("useRandomPersona", [aCategoryName]));
-    item.setAttribute("oncommand", "PersonaController.onSelectPersona(event)");
-    item.setAttribute("persona", "random");
-    item.setAttribute("category", aCategory || aCategoryName);
+        item.setAttribute("class", "menuitem-iconic");
+        item.setAttribute("image", headerURI);
+        item.setAttribute("label", persona.name);
+        //    item.setAttribute("type", "checkbox");
+        item.setAttribute("checked", (PersonaService.selected != "default" &&
+            PersonaService.currentPersona &&
+            PersonaService.currentPersona.id == persona.id));
+        item.setAttribute("autocheck", "false");
+        item.setAttribute("oncommand", "PersonaController.onSelectPersona(event)");
+        item.setAttribute("recent", persona.recent ? "true" : "false");
+        item.setAttribute("persona", JSON.stringify(theme));
+        item.addEventListener("DOMMenuItemActive", function(evt) {
+            PersonaController.onPreviewPersona(evt)
+        }, false);
+        item.addEventListener("DOMMenuItemInactive", function(evt) {
+            PersonaController.onResetPersona(evt)
+        }, false);
+
+        return item;
+    },
+
+    _createViewMoreItem: function(category, categoryId) {
+        let item = document.createElement("menuitem");
+
+        item.setAttribute("class", "menuitem-iconic");
+        item.setAttribute("label", this._strings.get("viewMoreFrom", [category]));
+        item.setAttribute("oncommand", "PersonaController.openURLInTab(this.getAttribute('href'))");
 
-    return item;
-  },
+        if (categoryId == "featured") {
+            item.setAttribute("href", PersonaService.getURL("browse", {
+                SORT: "featured"
+            }));
+        }
 
-  toggleFavoritesRotation : function() {
-    if (PersonaService.selected != "randomFavorite") {
-      PersonaService.changeToRandomFavoritePersona();
-    } else {
-      PersonaService.selected = "current";
+        return item;
+    },
+
+    _createRandomItem: function(aCategoryName, aCategory) {
+        let item = document.createElement("menuitem");
+
+        item.setAttribute("class", "menuitem-iconic");
+        item.setAttribute("label", this._strings.get("useRandomPersona", [aCategoryName]));
+        item.setAttribute("oncommand", "PersonaController.onSelectPersona(event)");
+        item.setAttribute("persona", "random");
+        item.setAttribute("category", aCategory || aCategoryName);
+
+        return item;
+    },
+
+    toggleFavoritesRotation: function() {
+        if (PersonaService.selected != "randomFavorite") {
+            PersonaService.changeToRandomFavoritePersona();
+        } else {
+            PersonaService.selected = "current";
+        }
     }
-  }
 };
 
 // Import generic modules into the persona controller rather than
 // the global namespace so they don't conflict with modules with the same names
 // imported by other extensions.
-Cu.import("resource://personas/modules/Observers.js",     PersonaController);
-Cu.import("resource://personas/modules/Preferences.js",   PersonaController);
-Cu.import("resource://personas/modules/StringBundle.js",  PersonaController);
-Cu.import("resource://personas/modules/URI.js",           PersonaController);
+Cu.import("resource://personas/modules/Observers.js", PersonaController);
+Cu.import("resource://personas/modules/Preferences.js", PersonaController);
+Cu.import("resource://personas/modules/StringBundle.js", PersonaController);
+Cu.import("resource://personas/modules/URI.js", PersonaController);
 
 // Import modules that come with Firefox into the persona controller rather
 // than the global namespace.
 // LightweightThemeManager may not be not available (Firefox < 3.6 or Thunderbird)
-try { Cu.import("resource://gre/modules/LightweightThemeManager.jsm", PersonaController); }
-catch (e) {}
-
-window.addEventListener("load", function(e) { PersonaController.startUp(e) }, false);
-window.addEventListener("unload", function(e) { PersonaController.shutDown(e) }, false);
+try {
+    Cu.import("resource://gre/modules/LightweightThemeManager.jsm", PersonaController);
+} catch (e) {}
+
+window.addEventListener("load", PersonaService.wrap(function(e) {
+    PersonaController.startUp(e)
+}), false);
+window.addEventListener("unload", PersonaService.wrap(function(e) {
+    PersonaController.shutDown(e)
+}), false);
\ No newline at end of file
diff --git a/extension/locale/bg-BG/personas.properties b/extension/locale/bg-BG/personas.properties
index 85a30b3..38ff649 100644
--- a/extension/locale/bg-BG/personas.properties
+++ b/extension/locale/bg-BG/personas.properties
@@ -1,9 +1,10 @@
 # The labels of various items in the personas menu.
 About=Относно
-featured= Featured
+featured=Featured
 recent=Скоро избирани
 favorites=Любими
 favoritesSignIn=Влезте, за да достъпите вашите любими
+favoritesError=При зареждане на вашите любими възникна грешка
 viewDetail=Преглед детайли
 favoritesGoTo=Към моите любими
 # Labels that identify the current persona when the current persona is either
@@ -19,7 +20,7 @@ viewDesigner=Още от %S
 # LOCALIZATION NOTE (viewMore): the label of a command that will load the gallery
 # page on getpersonas.com for this specific category.
 #  %1$S = number of additional personas in this category available on the site (e.g. 1,542)
-viewMoreFrom= More from %1$S...
+viewMoreFrom=More from %1$S...
 #  %2$2 = the name of the category (e.g. Music)
 # LOCALIZATION NOTE (notification.personaWasSelected): the text
 # of the notification shown when a persona is selected for the first time.
diff --git a/extension/locale/cs-CZ/personas.properties b/extension/locale/cs-CZ/personas.properties
index 4e22243..674ee41 100644
--- a/extension/locale/cs-CZ/personas.properties
+++ b/extension/locale/cs-CZ/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Posledně použité
 favorites=Oblíbené
 favoritesSignIn=Pro práci s oblíbenými položkami se musíte přihlásit
+favoritesError= An error occurred while loading your favorites
 viewDetail=Zobrazit podrobnosti
 favoritesGoTo=Přejít do oblíbených...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/da/personas.properties b/extension/locale/da/personas.properties
index 63cf430..cf486e0 100644
--- a/extension/locale/da/personas.properties
+++ b/extension/locale/da/personas.properties
@@ -4,6 +4,7 @@ featured=Anbefalede
 recent=Mine senest valgte
 favorites=Mine favoritter
 favoritesSignIn=Log ind for at få adgang til dine favoritter
+favoritesError=Der opstod en fejl under indlæsning af dine favoritter
 viewDetail=Vis detaljer…
 favoritesGoTo=Gå til mine favoritter…
 # Labels that identify the current persona when the current persona is either
@@ -15,7 +16,7 @@ customPersona=Tilpas Persona
 # LOCALIZATION NOTE (viewDesigner): a label that indicates the name of the designer
 # of the current persona as part of a link to more personas
 #  $@ = the name of the designer of the current persona
-viewDesigner=Flere fra %S…
+viewDesigner=Flere af %S…
 # LOCALIZATION NOTE (viewMore): the label of a command that will load the gallery
 # page on getpersonas.com for this specific category.
 #  %1$S = number of additional personas in this category available on the site (e.g. 1,542)
diff --git a/extension/locale/de/personas.properties b/extension/locale/de/personas.properties
index 5749b7e..f788d34 100644
--- a/extension/locale/de/personas.properties
+++ b/extension/locale/de/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Kürzlich ausgewählt
 favorites=Meine Favoriten
 favoritesSignIn=Melden Sie sich an, um auf Ihre Favoriten zuzugreifen
+favoritesError= An error occurred while loading your favorites
 viewDetail=Details ansehen...
 favoritesGoTo=Meinen Favoriten aufrufen...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/el/personas.properties b/extension/locale/el/personas.properties
index 032143d..2f2c6c6 100644
--- a/extension/locale/el/personas.properties
+++ b/extension/locale/el/personas.properties
@@ -4,6 +4,7 @@ featured=Προβεβλημένα
 recent=Επιλεγμένα πρόσφατα
 favorites=Αγαπημένα
 favoritesSignIn=Συνδεθείτε για να έχετε πρόσβαση στα αγαπημένα σας
+favoritesError=Παρουσιάστηκε σφάλμα κατά τη φόρτωση των αγαπημένων σας
 viewDetail=Εμφάνιση λεπτομερειών...
 favoritesGoTo=Μετάβαση στα αγαπημένα μου...
 # Labels that identify the current persona when the current persona is either
@@ -19,7 +20,7 @@ viewDesigner=Περισσότερα από %S...
 # LOCALIZATION NOTE (viewMore): the label of a command that will load the gallery
 # page on getpersonas.com for this specific category.
 #  %1$S = number of additional personas in this category available on the site (e.g. 1,542)
-viewMoreFrom= More from %1$S...
+viewMoreFrom=More from %1$S...
 #  %2$2 = the name of the category (e.g. Music)
 # LOCALIZATION NOTE (notification.personaWasSelected): the text
 # of the notification shown when a persona is selected for the first time.
diff --git a/extension/locale/en-US/personas.properties b/extension/locale/en-US/personas.properties
index be39106..0346770 100644
--- a/extension/locale/en-US/personas.properties
+++ b/extension/locale/en-US/personas.properties
@@ -4,6 +4,7 @@ featured          = Featured
 recent            = My Recently Selected
 favorites         = My Favorites
 favoritesSignIn   = Sign In to Access Your Favorites
+favoritesError    = An error occurred while loading your favorites
 viewDetail        = View Details...
 favoritesGoTo     = Go to My Favorites...
 
diff --git a/extension/locale/es-AR/personas.properties b/extension/locale/es-AR/personas.properties
index 8a50480..f3de633 100644
--- a/extension/locale/es-AR/personas.properties
+++ b/extension/locale/es-AR/personas.properties
@@ -4,6 +4,7 @@ featured=Destacadas
 recent=Mi selección reciente
 favorites=Mis favoritas
 favoritesSignIn=Ingresá para acceder a tus favoritas
+favoritesError=Ocurrió un error al cargar tus favoritas
 viewDetail=Ver detalles…
 favoritesGoTo=Ir a mis favoritas…
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/es-CL/personas.properties b/extension/locale/es-CL/personas.properties
index 83545a3..8010649 100644
--- a/extension/locale/es-CL/personas.properties
+++ b/extension/locale/es-CL/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Recientemente seleccionado
 favorites=Favoritos
 favoritesSignIn=Inicie sesión para acceder a sus favoritos
+favoritesError= An error occurred while loading your favorites
 viewDetail=Ver detalles...
 favoritesGoTo=Ir a Favoritos...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/es-ES/personas.properties b/extension/locale/es-ES/personas.properties
index 309af26..cbf655e 100644
--- a/extension/locale/es-ES/personas.properties
+++ b/extension/locale/es-ES/personas.properties
@@ -4,6 +4,7 @@ featured=Destacados
 recent=Seleccionados recientemente
 favorites=Mis Favoritos
 favoritesSignIn=Iniciar sesión para acceder a tus favoritos
+favoritesError=Se ha producido un error mientras se cargaban sus favoritos
 viewDetail=Ver detalles...
 favoritesGoTo=Ir a mis favoritos...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/es-MX/personas.properties b/extension/locale/es-MX/personas.properties
index e37b3d8..3acb36e 100644
--- a/extension/locale/es-MX/personas.properties
+++ b/extension/locale/es-MX/personas.properties
@@ -4,6 +4,7 @@ featured=Destacado
 recent=Mi selección reciente
 favorites=Mis Favoritos
 favoritesSignIn=Inicia sesión para acceder a tus favoritos
+favoritesError=Ocurrió un error al cargar tus favoritos
 viewDetail=Ver detalles...
 favoritesGoTo=Ir a mis favoritos
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/eu/personas.properties b/extension/locale/eu/personas.properties
index e1e142e..a56062b 100644
--- a/extension/locale/eu/personas.properties
+++ b/extension/locale/eu/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Azkenez hautatuak
 favorites=Gogokoenak
 favoritesSignIn=Saioa hasi zure gogokoenak atzitzeko
+favoritesError= An error occurred while loading your favorites
 viewDetail=Ikusi xehtasunak...
 favoritesGoTo=Joan nire gogokoetara...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/fi/personas.properties b/extension/locale/fi/personas.properties
index ddeb4f1..03da007 100644
--- a/extension/locale/fi/personas.properties
+++ b/extension/locale/fi/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Viimeksi valitut
 favorites=Suosikit
 favoritesSignIn=Kirjaudu sisään nähdäksesi suosikkisi
+favoritesError= An error occurred while loading your favorites
 viewDetail=Katso lisätietoja...
 favoritesGoTo=Siirry suosikkeihin...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/fr/personas.properties b/extension/locale/fr/personas.properties
index 9ab5f30..41d837d 100644
--- a/extension/locale/fr/personas.properties
+++ b/extension/locale/fr/personas.properties
@@ -4,6 +4,7 @@ featured=Thèmes vedettes
 recent=Choisis récemment
 favorites=Favoris
 favoritesSignIn=Identifiez-vous pour accéder à vos favoris
+favoritesError=Une erreur s'est produite pendant le chargement de vos favoris
 viewDetail=Détails…
 favoritesGoTo=Accéder à mes favoris…
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/fy-NL/personas.properties b/extension/locale/fy-NL/personas.properties
index dc3c183..635bcb7 100644
--- a/extension/locale/fy-NL/personas.properties
+++ b/extension/locale/fy-NL/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Resint selektearre
 favorites=Favoriten
 favoritesSignIn=Loch yn om jo favoriten te besjen
+favoritesError= An error occurred while loading your favorites
 viewDetail=Toan details...
 favoritesGoTo=Gean nei myn favoriten...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/ga-IE/personas.properties b/extension/locale/ga-IE/personas.properties
index 0a28841..6677c7c 100644
--- a/extension/locale/ga-IE/personas.properties
+++ b/extension/locale/ga-IE/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Roghnaithe agam le déanaí
 favorites=Ceanáin
 favoritesSignIn=Logáil Isteach chun do chuid Ceanán a fheiceáil
+favoritesError= An error occurred while loading your favorites
 viewDetail=Féach ar na mionsonraí...
 favoritesGoTo=Féach ar mo chuid Ceanán...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/gl-ES/personas.properties b/extension/locale/gl-ES/personas.properties
index 01b5883..474f14f 100644
--- a/extension/locale/gl-ES/personas.properties
+++ b/extension/locale/gl-ES/personas.properties
@@ -4,6 +4,7 @@ featured=Destacado
 recent=Seleccionados recentemente
 favorites=Favoritos
 favoritesSignIn=Acceda aos seus favoritos
+favoritesError=Produciuse un erro mentres se cargaban os favoritos.
 viewDetail=Ver detalles...
 favoritesGoTo=Ir aos favoritos...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/he-IL/personas.properties b/extension/locale/he-IL/personas.properties
index 7bf5df4..1acd641 100644
--- a/extension/locale/he-IL/personas.properties
+++ b/extension/locale/he-IL/personas.properties
@@ -4,6 +4,7 @@ featured=נבחרים
 recent=נבחר לאחרונה
 favorites=המועדפים שלי
 favoritesSignIn=התחברות כדי לגשת אל המועדפים שלך
+favoritesError= An error occurred while loading your favorites
 viewDetail=הצגת הפרטים...
 favoritesGoTo=לך אל המועדפים שלי...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/hu-HU/personas.properties b/extension/locale/hu-HU/personas.properties
index 151d4e2..b0a7308 100644
--- a/extension/locale/hu-HU/personas.properties
+++ b/extension/locale/hu-HU/personas.properties
@@ -4,6 +4,7 @@ featured=Kiemelt
 recent=Legutóbb kiválasztott
 favorites=Kedvencek
 favoritesSignIn=A Kedvencek eléréséhez be kell jelentkezni
+favoritesError=Hiba történt a kedvencek betöltése közben
 viewDetail=Részletek...
 favoritesGoTo=Kedvencek...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/it/personas.properties b/extension/locale/it/personas.properties
index 8d94063..bc444b6 100644
--- a/extension/locale/it/personas.properties
+++ b/extension/locale/it/personas.properties
@@ -4,6 +4,7 @@ featured=Consigliati
 recent=Selezionati recentemente
 favorites=Preferiti
 favoritesSignIn=Accedi per visualizzare i tuoi Preferiti
+favoritesError= An error occurred while loading your favorites
 viewDetail=Visualizza dettagli...
 favoritesGoTo=Visualizza i miei Preferiti
 # the default persona, a persona that doesn't have a name, or a custom persona
diff --git a/extension/locale/ja-JP-mac/personas.properties b/extension/locale/ja-JP-mac/personas.properties
index 400c517..5d32c50 100644
--- a/extension/locale/ja-JP-mac/personas.properties
+++ b/extension/locale/ja-JP-mac/personas.properties
@@ -4,6 +4,7 @@ featured=注目
 recent=最近の選択
 favorites=お気に入り
 favoritesSignIn=サインインしてお気に入りにアクセス
+favoritesError=お気に入りの読み込み中にエラーが発生しました
 viewDetail=詳細を表示...
 favoritesGoTo=お気に入りを開く...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/ja-JP/personas.properties b/extension/locale/ja-JP/personas.properties
index 172cd5a..bacd039 100644
--- a/extension/locale/ja-JP/personas.properties
+++ b/extension/locale/ja-JP/personas.properties
@@ -4,6 +4,7 @@ featured=注目
 recent=最近の選択
 favorites=お気に入り
 favoritesSignIn=サインインしてお気に入りにアクセス
+favoritesError=お気に入りを読み込み中にエラーが発生しました
 viewDetail=詳細を表示...
 favoritesGoTo=お気に入りに行く...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/ja/personas.properties b/extension/locale/ja/personas.properties
index fa374cf..d0d87be 100644
--- a/extension/locale/ja/personas.properties
+++ b/extension/locale/ja/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=最近の選択
 favorites=お気に入り
 favoritesSignIn=サインインしてお気に入りにアクセス
+favoritesError= An error occurred while loading your favorites
 viewDetail=詳細を表示...
 favoritesGoTo=お気に入りに行く...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/ko-KR/personas.properties b/extension/locale/ko-KR/personas.properties
index 48ebce5..6b5980d 100644
--- a/extension/locale/ko-KR/personas.properties
+++ b/extension/locale/ko-KR/personas.properties
@@ -4,6 +4,7 @@ featured=인기
 recent=최근 선택
 favorites=선호하는 스킨
 favoritesSignIn=당신이 선호하는 스킨 접속을 위해 로그인
+favoritesError= An error occurred while loading your favorites
 viewDetail=상세 보기
 favoritesGoTo=나의 즐겨찾기로 가기...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/lt-LT/personas.properties b/extension/locale/lt-LT/personas.properties
index 1c4be2c..ccac62c 100644
--- a/extension/locale/lt-LT/personas.properties
+++ b/extension/locale/lt-LT/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Neseniai naudotos
 favorites=Mėgstamiausios
 favoritesSignIn=Prisijungti, mėgstamiausių pasiekimui
+favoritesError= An error occurred while loading your favorites
 viewDetail=Išsamiau...
 favoritesGoTo=Eiti į mėgstamiausius...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/lt/personas.properties b/extension/locale/lt/personas.properties
index eccba4e..ab16499 100644
--- a/extension/locale/lt/personas.properties
+++ b/extension/locale/lt/personas.properties
@@ -4,6 +4,7 @@ featured=Siūlomos
 recent=Neseniai naudotos
 favorites=Mėgstamiausios
 favoritesSignIn=Prisijunkite ir galėsite pasiekti mėgstamiausias
+favoritesError= An error occurred while loading your favorites
 viewDetail=Išsamiau...
 favoritesGoTo=Eiti į mėgstamiausius...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/mk-MK/personas.properties b/extension/locale/mk-MK/personas.properties
index c9402bc..b561444 100644
--- a/extension/locale/mk-MK/personas.properties
+++ b/extension/locale/mk-MK/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Мои скоро одбрани
 favorites=Мои омилени
 favoritesSignIn=Највете се за да пристапите до Вашите омилени
+favoritesError= An error occurred while loading your favorites
 viewDetail=Деталји...
 favoritesGoTo=Отвори ги моите омилени
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/nl/personas.properties b/extension/locale/nl/personas.properties
index f357665..2789f76 100644
--- a/extension/locale/nl/personas.properties
+++ b/extension/locale/nl/personas.properties
@@ -4,6 +4,7 @@ featured= Aanbevolen
 recent= Onlangs geselecteerd
 favorites= Mijn favorieten
 favoritesSignIn= Meld u aan om naar uw favorieten te gaan
+favoritesError= An error occurred while loading your favorites
 viewDetail= Details bekijken…
 favoritesGoTo= Naar mijn favorieten…
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/pl-PL/personas.properties b/extension/locale/pl-PL/personas.properties
index 9f72998..8e5986c 100644
--- a/extension/locale/pl-PL/personas.properties
+++ b/extension/locale/pl-PL/personas.properties
@@ -4,6 +4,7 @@ featured=Polecane
 recent=Ostatnio używane
 favorites=Ulubione
 favoritesSignIn=Zaloguj się, aby uzyskać dostęp do ulubionych motywów
+favoritesError=Podczas wczytywania ulubionych motywów wystąpił błąd
 viewDetail=Wyświetl szczegóły…
 favoritesGoTo=Przejdź do ulubionych…
 # Labels that identify the current persona when the current persona is either
@@ -19,7 +20,7 @@ viewDesigner=Więcej autorstwa %S…
 #   %2$S = the name of the persona
 # f.e. "Random Selection from Scenery > Yosemite"
 # LOCALIZATION NOTE (useRandomPersona): the label of a command that selects
-viewMoreFrom= More from %1$S...
+viewMoreFrom=More from %1$S...
 # a category from which to pick random personas.
 #   %S = the category from which to pick random personas
 # f.e. "Random Selection from Scenery"
diff --git a/extension/locale/pl/personas.properties b/extension/locale/pl/personas.properties
index 72df175..b744e46 100644
--- a/extension/locale/pl/personas.properties
+++ b/extension/locale/pl/personas.properties
@@ -4,6 +4,7 @@ featured=Polecane
 recent=Ostatnio używane
 favorites=Ulubione
 favoritesSignIn=Zaloguj się, aby uzyskać dostęp do ulubionych motywów
+favoritesError=Podczas wczytywania ulubionych minimotywów wystąpił błąd
 viewDetail=Wyświetl szczegóły…
 favoritesGoTo=Przejdź do ulubionych…
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/pt-BR/personas.properties b/extension/locale/pt-BR/personas.properties
index b246d97..e37bf20 100644
--- a/extension/locale/pt-BR/personas.properties
+++ b/extension/locale/pt-BR/personas.properties
@@ -4,6 +4,7 @@ featured=Em destaque
 recent=Selecionadas recentemente
 favorites=Favoritos
 favoritesSignIn=Conecte-se para acessar seus favoritos
+favoritesError= An error occurred while loading your favorites
 viewDetail=Ver detalhes…
 favoritesGoTo=Ir para Favoritas…
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/ro/personas.properties b/extension/locale/ro/personas.properties
index 313dfec..44e49d1 100644
--- a/extension/locale/ro/personas.properties
+++ b/extension/locale/ro/personas.properties
@@ -4,6 +4,7 @@ featured=Recomandat
 recent=Recent selectate
 favorites=Favorite
 favoritesSignIn=Abonare pentru accesul la favorite…
+favoritesError= An error occurred while loading your favorites
 viewDetail=Vezi detalii…
 favoritesGoTo=Afișează favoritele
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/ru-RU/personas.properties b/extension/locale/ru-RU/personas.properties
index 63def6c..7be6c58 100644
--- a/extension/locale/ru-RU/personas.properties
+++ b/extension/locale/ru-RU/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Недавно выбранные
 favorites=Избранное
 favoritesSignIn=Войдите для просмотра Избранного
+favoritesError= An error occurred while loading your favorites
 viewDetail=Данные...
 favoritesGoTo=Перейти к Избранному...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/si-LK/personas.properties b/extension/locale/si-LK/personas.properties
index 4f5742f..e287ef7 100644
--- a/extension/locale/si-LK/personas.properties
+++ b/extension/locale/si-LK/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=මැතකදී තොරාගත්
 favorites=මම වඩාත් කැමති
 favoritesSignIn=ඔබ කැමති පුද්ගල සැකසුම තොරා ගැනීමට ඔබගේ ගිණුමට පිවිසෙන්න
+favoritesError= An error occurred while loading your favorites
 viewDetail=තොරතුරු පෙන්වන්න
 favoritesGoTo=මා කැමති පුද්ගල සැකසුම් පෙන්වන්න
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/sk-SK/personas.properties b/extension/locale/sk-SK/personas.properties
index 28a86dd..a2d9f74 100644
--- a/extension/locale/sk-SK/personas.properties
+++ b/extension/locale/sk-SK/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Najčastejšie vybrané
 favorites=Moje obľúbené
 favoritesSignIn=Na zobrazenie obľúbených sa musíte prihlásiť
+favoritesError= An error occurred while loading your favorites
 viewDetail=Zobraziť podrobnosti...
 favoritesGoTo=Prejsť na Moje obľúbené...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/sr/personas.properties b/extension/locale/sr/personas.properties
index 051b699..4405183 100644
--- a/extension/locale/sr/personas.properties
+++ b/extension/locale/sr/personas.properties
@@ -4,6 +4,7 @@ featured=Истакнута
 recent=Моја недавно изабрана
 favorites=Моја омиљена
 favoritesSignIn=Пријавите се да бисте приступили својим омиљеним оделима
+favoritesError=Дошло је до грешке приликом учитавања омиљених одела
 viewDetail=Прикажи детаље...
 favoritesGoTo=Иди до мојих омиљених одела...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/sv-SE/personas.properties b/extension/locale/sv-SE/personas.properties
index 78795b5..6bf7dbd 100644
--- a/extension/locale/sv-SE/personas.properties
+++ b/extension/locale/sv-SE/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Nyligen valda
 favorites=Favoriter
 favoritesSignIn=Logga in för att se dina favoriter
+favoritesError= An error occurred while loading your favorites
 viewDetail=Se Detaljer
 favoritesGoTo=Visa Mina Favoriter
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/tr/personas.properties b/extension/locale/tr/personas.properties
index 10b3dfb..784215c 100644
--- a/extension/locale/tr/personas.properties
+++ b/extension/locale/tr/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Son Seçilen
 favorites= Favorites
 favoritesSignIn= Sign In to Access Your Favorites
+favoritesError= An error occurred while loading your favorites
 viewDetail= View Details...
 favoritesGoTo= Go to My Favorites...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/uk-UA/personas.properties b/extension/locale/uk-UA/personas.properties
index 1214ef4..fd62b34 100644
--- a/extension/locale/uk-UA/personas.properties
+++ b/extension/locale/uk-UA/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent= Нещодавно вибрані мною
 favorites= Мої улюблені
 favoritesSignIn= Увійти для доступу в свої улюблені
+favoritesError= An error occurred while loading your favorites
 viewDetail= Переглянути типові...
 favoritesGoTo= Перейти до моїх улюблених...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/vi/personas.properties b/extension/locale/vi/personas.properties
index 1943134..adc0314 100644
--- a/extension/locale/vi/personas.properties
+++ b/extension/locale/vi/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=Được chọn Gần đây
 favorites=Ưa thích
 favoritesSignIn=Đăng nhập để Truy cập Giao diện Ưa thích của Bạn
+favoritesError= An error occurred while loading your favorites
 viewDetail=Xem chi tiết...
 favoritesGoTo=Vào Danh mục Ưa thích của Tôi...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/zh-CN/personas.properties b/extension/locale/zh-CN/personas.properties
index 2070403..2fe55e2 100644
--- a/extension/locale/zh-CN/personas.properties
+++ b/extension/locale/zh-CN/personas.properties
@@ -4,6 +4,7 @@ featured=精选
 recent=最近选择
 favorites=收藏
 favoritesSignIn=登录访问你的收藏
+favoritesError= An error occurred while loading your favorites
 viewDetail=查看详情...
 favoritesGoTo=转到我的收藏
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/locale/zh-TW/personas.properties b/extension/locale/zh-TW/personas.properties
index e97237f..53c329b 100644
--- a/extension/locale/zh-TW/personas.properties
+++ b/extension/locale/zh-TW/personas.properties
@@ -4,6 +4,7 @@ featured= Featured
 recent=最近使用
 favorites=我的最愛
 favoritesSignIn=登入我的最愛
+favoritesError= An error occurred while loading your favorites
 viewDetail=檢視細節...
 favoritesGoTo=到我的最愛...
 # Labels that identify the current persona when the current persona is either
diff --git a/extension/modules/service.js b/extension/modules/service.js
index 3911f7e..fedaf99 100644
--- a/extension/modules/service.js
+++ b/extension/modules/service.js
@@ -44,12 +44,17 @@ const Cr = Components.results;
 const Cu = Components.utils;
 
 // modules that come with Firefox
+Cu.import("resource://gre/modules/ctypes.jsm")
+Cu.import("resource://gre/modules/osfile.jsm")
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 // LightweightThemeManager may not be not available (Firefox < 3.6 or Thunderbird)
-try { Cu.import("resource://gre/modules/LightweightThemeManager.jsm"); }
-catch (e) { LightweightThemeManager = null; }
+try {
+    Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
+} catch (e) {
+    LightweightThemeManager = null;
+}
 
 // modules that are generic
 Cu.import("resource://personas/modules/log4moz.js");
@@ -63,1455 +68,1496 @@ const PERSONAS_EXTENSION_ID = "personas at christopher.beard";
 const COOKIE_INITIAL_PERSONA = "initial_persona";
 
 function endsWith(str, end) {
-  return !end || end.length <= str.length && end == str.slice(-end.length);
+    return !end || end.length <= str.length && end == str.slice(-end.length);
 }
 
 let PersonaService = {
-  THUNDERBIRD_ID: "{3550f703-e582-4d05-9a08-453d09bdfdc6}",
-  FIREFOX_ID:     "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
-
-  // Wraps event listener functions so errors are not lost.
-  wrap: function(fn) {
-    return function() {
-      try {
-        return fn.apply(this, arguments);
-      } catch (e) {
-        Cu.reportError(e);
-      }
-    };
-  },
-
-  //**************************************************************************//
-  // Shortcuts
-
-  // Access to extensions.personas.* preferences.  To access other preferences,
-  // call the Preferences module directly.
-  get _prefs() {
-    delete this._prefs;
-    return this._prefs = new Preferences("extensions.personas.");
-  },
-
-  get _strings() {
-    delete this._strings;
-    return this._strings = new StringBundle("chrome://personas/locale/personas.properties");
-  },
-
-  get appInfo() {
-    delete this.appInfo;
-    return this.appInfo = Cc["@mozilla.org/xre/app-info;1"].
-                           getService(Ci.nsIXULAppInfo).
-                           QueryInterface(Ci.nsIXULRuntime);
-  },
-
-  get _log() {
-    delete this._log;
-    return this._log = Log4Moz.getConfiguredLogger("PersonaService");
-  },
-
-
-  //**************************************************************************//
-  // Initialization & Destruction
-
-  _init: function() {
-    let t = this;
-    AddonManager.getAddonByID(PERSONAS_EXTENSION_ID, function (addon) {
-      t.urlSource = "personas-plus-" + addon.version;
-      t.onAddonsHostChanged();
-    });
-
-    // Observe quit so we can destroy ourselves.
-    Observers.add("quit-application", this.onQuitApplication, this);
-    // Observe the "cookie-changed" topic to load the favorite personas when
-    // the user signs in.
-    if (false)
-      // Skip this for now, since AMO session cookies are not currently
-      // cooperative
-      Observers.add("cookie-changed", this.onCookieChanged, this);
-    // Observe the "lightweight-theme-changed" to sync the add-on with the
-    // lightweight theme manager.
-    Observers.add("lightweight-theme-changed",
-                  this.onLightweightThemeChanged, this);
-    // Observe HTTP responses to detect login and logout
-    Observers.add("http-on-examine-response",
-                  this.onHTTPResponse, this);
-
-    this._prefs.observe("useTextColor",   this.onUseColorChanged,     this);
-    this._prefs.observe("useAccentColor", this.onUseColorChanged,     this);
-    this._prefs.observe("selected",       this.onSelectedModeChanged, this);
-    this._prefs.observe("addons-host",    this.onAddonsHostChanged,   this);
-
-    this._ltmSyncTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
-    // Change to the initial persona if preferences indicate that a persona
-    // should be active but they don't specify the persona that is active.
-    // This normally happens only on first run, although it could theoretically
-    // happen at other times.
-    //
-    // The initial persona is either the persona specified by a cookie (set by
-    // the gallery), the one specified by the extensions.personas.initial
-    // preference (set by a distribution.ini file for a BYOB/bundle/distributon
-    // build), or the Groovy Blue persona which is hardcoded into this code.
-    //
-    // NOTE: the logic here is carefully designed to achieve the correct outcome
-    // under a variety of circumstances and should be changed with caution!
-    // See bug 513765 and bug 503300 for some details on why it works this way.
-    //
-    if (this._prefs.get("selected") == "current" && !this._prefs.get("current")) {
-      if (this._prefs.has("initial"))
-        this.changeToPersona(JSON.parse(this._prefs.get("initial")));
-      else if (LightweightThemeManager && LightweightThemeManager.currentTheme)
-        this.changeToPersona(LightweightThemeManager.currentTheme);
-      else {
-        let addonSlug = this._prefs.get("initial.slug");
-        let url = this.getURL("addon-details", { "ADDON_SLUG": addonSlug });
-
-        this._makeRequest(url, function (event) {
-          let responseJSON = JSON.parse(event.target.responseText);
-          this._log.debug("Fetched persona from " + url + " " +
-                          JSON.stringify(responseJSON.theme));
-          this.changeToPersona(this.getPersonaJSON(responseJSON));
-          // There seems to be a bug in the LightweightThemeManager that
-          // prevents this from being called automatically at this
-          // point.
-          LightweightThemeManager.themeChanged(
-            LightweightThemeManager.currentTheme);
-        }.bind(this));
-      }
-    }
+    THUNDERBIRD_ID: "{3550f703-e582-4d05-9a08-453d09bdfdc6}",
+    FIREFOX_ID: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+
+    // Wraps event listener functions so errors are not lost.
+    wrap: function(fn) {
+        return function() {
+            try {
+                return fn.apply(this, arguments);
+            } catch (e) {
+                Cu.reportError(e);
+            }
+        };
+    },
+
+    //**************************************************************************//
+    // Shortcuts
+
+    // Access to extensions.personas.* preferences.  To access other preferences,
+    // call the Preferences module directly.
+    get _prefs() {
+        delete this._prefs;
+        return this._prefs = new Preferences("extensions.personas.");
+    },
+
+    get _strings() {
+        delete this._strings;
+        return this._strings = new StringBundle("chrome://personas/locale/personas.properties");
+    },
+
+    get appInfo() {
+        delete this.appInfo;
+        return this.appInfo = Cc["@mozilla.org/xre/app-info;1"].
+        getService(Ci.nsIXULAppInfo).
+        QueryInterface(Ci.nsIXULRuntime);
+    },
+
+    get _log() {
+        delete this._log;
+        return this._log = Log4Moz.getConfiguredLogger("PersonaService");
+    },
+
+
+    //**************************************************************************//
+    // Initialization & Destruction
+
+    _init: function() {
+        let t = this;
+        AddonManager.getAddonByID(PERSONAS_EXTENSION_ID, function(addon) {
+            t.urlSource = "personas-plus-" + addon.version;
+            t.onAddonsHostChanged();
+        });
+
+        // Observe quit so we can destroy ourselves.
+        Observers.add("quit-application", this.onQuitApplication, this);
+        // Observe the "cookie-changed" topic to load the favorite personas when
+        // the user signs in.
+        if (false)
+        // Skip this for now, since AMO session cookies are not currently
+        // cooperative
+            Observers.add("cookie-changed", this.onCookieChanged, this);
+        // Observe the "lightweight-theme-changed" to sync the add-on with the
+        // lightweight theme manager.
+        Observers.add("lightweight-theme-changed",
+            this.onLightweightThemeChanged, this);
+        // Observe HTTP responses to detect login and logout
+        Observers.add("http-on-examine-response",
+            this.onHTTPResponse, this);
+
+        this._prefs.observe("useTextColor", this.onUseColorChanged, this);
+        this._prefs.observe("useAccentColor", this.onUseColorChanged, this);
+        this._prefs.observe("selected", this.onSelectedModeChanged, this);
+        this._prefs.observe("addons-host", this.onAddonsHostChanged, this);
+
+        this._ltmSyncTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+        // Change to the initial persona if preferences indicate that a persona
+        // should be active but they don't specify the persona that is active.
+        // This normally happens only on first run, although it could theoretically
+        // happen at other times.
+        //
+        // The initial persona is either the persona specified by a cookie (set by
+        // the gallery), the one specified by the extensions.personas.initial
+        // preference (set by a distribution.ini file for a BYOB/bundle/distributon
+        // build), or the Groovy Blue persona which is hardcoded into this code.
+        //
+        // NOTE: the logic here is carefully designed to achieve the correct outcome
+        // under a variety of circumstances and should be changed with caution!
+        // See bug 513765 and bug 503300 for some details on why it works this way.
+        //
+        if (this._prefs.get("selected") == "current" && !this._prefs.get("current")) {
+            if (this._prefs.has("initial"))
+                this.changeToPersona(JSON.parse(this._prefs.get("initial")));
+            else if (LightweightThemeManager && LightweightThemeManager.currentTheme)
+                this.changeToPersona(LightweightThemeManager.currentTheme);
+            else {
+                let addonSlug = this._prefs.get("initial.slug");
+                let url = this.getURL("addon-details", {
+                    "ADDON_SLUG": addonSlug
+                });
+
+                this._makeRequest(url, function(event) {
+                    let responseJSON = JSON.parse(event.target.responseText);
+                    this._log.debug("Fetched persona from " + url + " " +
+                        JSON.stringify(responseJSON.theme));
+                    this.changeToPersona(this.getPersonaJSON(responseJSON));
+                    // There seems to be a bug in the LightweightThemeManager that
+                    // prevents this from being called automatically at this
+                    // point.
+                    LightweightThemeManager.themeChanged(
+                        LightweightThemeManager.currentTheme);
+                }.bind(this));
+            }
+        }
 
-    let timerManager = Cc["@mozilla.org/updates/timer-manager;1"].
-                       getService(Ci.nsIUpdateTimerManager);
-
-    // Load cached personas data
-    this.loadDataFromCache();
-
-    // Refresh data, then set a timer to refresh it periodically.
-    // This isn't quite right, since we always load data on startup, even if
-    // we've recently refreshed it.  And the timer that refreshes data ignores
-    // the data load on startup, so if it's been more than the timer interval
-    // since a user last started her browser, we load the data twice:
-    // once because the browser starts and once because the refresh timer fires.
-    this.refreshData();
-    let dataRefreshCallback = {
-      _svc: this,
-      notify: function(timer) { this._svc._refreshDataWithMetrics() }
-    };
-    timerManager.registerTimer("personas-data-refresh-timer",
-                               dataRefreshCallback,
-                               86400 /* in seconds == one day */);
-
-    // Refresh the current persona once per day.  We only do this for
-    // Thunderbird and Firefox < 3.6, since the LightweightThemeManager
-    // does this for Firefox 3.6.
-    if (!LightweightThemeManager) {
-      let personaRefreshCallback = {
-        _svc: this,
-        notify: function(timer) { this._svc._refreshPersona() }
-      };
-      timerManager.registerTimer("personas-persona-refresh-timer",
-                                 personaRefreshCallback,
-                                 86400 /* in seconds == one day */);
-    }
+        let timerManager = Cc["@mozilla.org/updates/timer-manager;1"].
+        getService(Ci.nsIUpdateTimerManager);
+
+        // Load cached personas data
+        this.loadDataFromCache();
+
+        // Refresh data, then set a timer to refresh it periodically.
+        // This isn't quite right, since we always load data on startup, even if
+        // we've recently refreshed it.  And the timer that refreshes data ignores
+        // the data load on startup, so if it's been more than the timer interval
+        // since a user last started her browser, we load the data twice:
+        // once because the browser starts and once because the refresh timer fires.
+        this.refreshData();
+        let dataRefreshCallback = {
+            _svc: this,
+            notify: function(timer) {
+                this._svc._refreshDataWithMetrics()
+            }
+        };
+        timerManager.registerTimer("personas-data-refresh-timer",
+            dataRefreshCallback,
+            86400 /* in seconds == one day */ );
+
+        // Refresh the current persona once per day.  We only do this for
+        // Thunderbird and Firefox < 3.6, since the LightweightThemeManager
+        // does this for Firefox 3.6.
+        if (!LightweightThemeManager) {
+            let personaRefreshCallback = {
+                _svc: this,
+                notify: function(timer) {
+                    this._svc._refreshPersona()
+                }
+            };
+            timerManager.registerTimer("personas-persona-refresh-timer",
+                personaRefreshCallback,
+                86400 /* in seconds == one day */ );
+        }
 
-    // Load cached favorite personas
-    this.loadFavoritesFromCache();
+        // Load cached favorite personas
+        this.loadFavoritesFromCache();
 
-    // Refresh the favorite personas once per day.
-    let favoritesRefreshCallback = {
-      _svc: this,
-      notify: function(timer) { this._svc.refreshFavorites() }
-    };
-    timerManager.registerTimer("personas-favorites-refresh-timer",
-                               favoritesRefreshCallback,
-                               24 * 60 * 60);
+        // Refresh the favorite personas once per day.
+        let favoritesRefreshCallback = {
+            _svc: this,
+            notify: function(timer) {
+                this._svc.refreshFavorites()
+            }
+        };
+        timerManager.registerTimer("personas-favorites-refresh-timer",
+            favoritesRefreshCallback,
+            24 * 60 * 60);
 
-    this._rotationTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    this.onSelectedModeChanged();
-  },
+        this._rotationTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+        this.onSelectedModeChanged();
+    },
 
-  _destroy: function() {
-    Observers.remove("cookie-changed", this.onCookieChanged, this);
-    Observers.remove("lightweight-theme-changed",
-                     this.onLightweightThemeChanged, this);
-    Observers.remove("http-on-examine-response", this.onHTTPResponse, this);
+    _destroy: function() {
+        Observers.remove("cookie-changed", this.onCookieChanged, this);
+        Observers.remove("lightweight-theme-changed",
+            this.onLightweightThemeChanged, this);
+        Observers.remove("http-on-examine-response", this.onHTTPResponse, this);
 
-    this._prefs.ignore("useTextColor",   this.onUseColorChanged,     this);
-    this._prefs.ignore("useAccentColor", this.onUseColorChanged,     this);
-    this._prefs.ignore("selected",       this.onSelectedModeChanged, this);
-    this._prefs.ignore("addons-host",    this.onAddonsHostChanged,   this);
-  },
+        this._prefs.ignore("useTextColor", this.onUseColorChanged, this);
+        this._prefs.ignore("useAccentColor", this.onUseColorChanged, this);
+        this._prefs.ignore("selected", this.onSelectedModeChanged, this);
+        this._prefs.ignore("addons-host", this.onAddonsHostChanged, this);
+    },
 
-  // The `src` parameter used in AMO requests.
-  urlSource: "personas-plus",
+    // The `src` parameter used in AMO requests.
+    urlSource: "personas-plus",
 
 
-  //**************************************************************************//
-  // XPCOM Plumbing
+    //**************************************************************************//
+    // XPCOM Plumbing
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
-                                         Ci.nsIDOMEventListener,
-                                         Ci.nsITimerCallback]),
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+        Ci.nsIDOMEventListener,
+        Ci.nsITimerCallback
+    ]),
 
 
-  //**************************************************************************//
-  // Data Retrieval
+    //**************************************************************************//
+    // Data Retrieval
 
-  _makeRequest: function(url, loadCallback, headers, aIsBinary, wantErrors) {
-    let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
+    _makeRequest: function(url, loadCallback, headers, aIsBinary, wantErrors, options) {
+        let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
 
-    request = request.QueryInterface(Ci.nsIDOMEventTarget);
-    request.addEventListener("load", this.wrap(loadCallback), false);
-    if (wantErrors)
-      request.addEventListener("error", this.wrap(loadCallback), false);
+        request.addEventListener("load", this.wrap(loadCallback), false);
+        if (wantErrors)
+            request.addEventListener("error", this.wrap(loadCallback), false);
 
-    request = request.QueryInterface(Ci.nsIXMLHttpRequest);
-    request.open("GET", url, true);
+        request.open("GET", url, true);
 
-    // Force the request to include cookies even though this chrome code
-    // is seen as a third-party, so the server knows the user for which we are
-    // requesting favorites (or anything else user-specific in the future).
-    // This only works in Firefox 3.6; in Firefox 3.5 the request will instead
-    // fail to send cookies if the user has disabled third-party cookies.
-    try {
-      request.channel.QueryInterface(Ci.nsIHttpChannelInternal).
-        forceAllowThirdPartyCookie = true;
-    }
-    catch(ex) { /* user is using Firefox 3.5 */ }
-
-    if (headers)
-      for (let header in headers)
-        request.setRequestHeader(header, headers[header]);
-
-    if (aIsBinary)
-      request.overrideMimeType('text/plain; charset=x-user-defined');
-
-    request.send(null);
-  },
-
-  /**
-   * Refresh data. This method gets called on demand (including on startup)
-   * and retrieves data without passing any additional information about
-   * the selected persona and the application (that information is only included
-   * in the daily retrieval so we can get consistent daily statistics from it
-   * no matter how many times a user starts the application in a given day).
-   */
-  refreshData: function() {
-    let url = this.getURL("featured-feed");
-    this._makeRequest(url, this.onDataLoadComplete.bind(this));
-  },
-
-  /**
-   * Refresh data, providing metrics on persona usage in the process.
-   * This method gets called approximately once per day on a cross-session timer
-   * (provided Firefox is run every day), updates the version of the data
-   * that is currently in memory, and passes information about the selected
-   * persona and the host application to the server for statistical analysis
-   * (f.e. figuring out which personas are the most popular).
-   */
-  _refreshDataWithMetrics: function() {
-    let appInfo     = Cc["@mozilla.org/xre/app-info;1"].
-                      getService(Ci.nsIXULAppInfo);
-    let xulRuntime  = Cc["@mozilla.org/xre/app-info;1"].
-                      getService(Ci.nsIXULRuntime);
-
-    // Calculate the amount of time (in hours) since the persona was last changed.
-    let duration = "";
-    if (this._prefs.has("persona.lastChanged"))
-      duration = Math.round((new Date() - new Date(parseInt(this._prefs.get("persona.lastChanged")))) / 1000 / 60 / 60);
-
-    // This logic is based on ExtensionManager::_updateLocale.
-    let locale;
-    try {
-      if (Preferences.get("intl.locale.matchOS")) {
-        let localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"].
-                        getService(Ci.nsILocaleService);
-        locale = localeSvc.getLocaleComponentForUserAgent();
-      }
-      else
-        throw "set locale in the catch block";
-    }
-    catch (ex) {
-      locale = Preferences.get("general.useragent.locale");
-    }
-
-    let params = {
-      type:       this.selected,
-      id:         this.currentPersona ? this.currentPersona.id : "",
-      duration:   duration,
-      appID:      appInfo.ID,
-      appVersion: appInfo.version,
-      appLocale:  locale,
-      appOS:      xulRuntime.OS,
-      appABI:     xulRuntime.XPCOMABI
-    };
-
-    //dump("params: " + [name + "=" + encodeURIComponent(params[name]) for (name in params)].join("&") + "\n");
-
-    let url = this.getURL("featured-feed") +
-              "?" + [name + "=" + encodeURIComponent(params[name]) for (name in params)].join("&");
-    this._makeRequest(url, this.onDataLoadComplete.bind(this));
-  },
-
-  onDataLoadComplete: function(aEvent) {
-    let request = aEvent.target;
-
-    // XXX Try to reload again sooner?
-    if (request.status != 200)
-      throw("problem loading data: " + request.status + " - " + request.statusText);
-
-    let response = JSON.parse(request.responseText);
-
-    this.personas = {
-        featured: response.addons
-    };
-
-    // Cache the response
-    let cacheDirectory =
-      FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
-    FileUtils.writeFile(cacheDirectory, "personas.json", request.responseText);
-  },
-
-  /**
-   * Attempts to load this.personas from the cached file in cache/personas.json
-   */
-  loadDataFromCache : function() {
-    let cacheDirectory =
-      FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
-    let data = FileUtils.readFile(cacheDirectory, "personas.json");
-
-    try { this.personas = JSON.parse(data); }
-    catch (e) {
-      // Could not load from cached data, file empty or does not exist perhaps
-      return;
-    }
+        // Force the request to include cookies even though this chrome code
+        // is seen as a third-party, so the server knows the user for which we are
+        // requesting favorites (or anything else user-specific in the future).
+        // This only works in Firefox 3.6; in Firefox 3.5 the request will instead
+        // fail to send cookies if the user has disabled third-party cookies.
+        try {
+            request.channel.QueryInterface(Ci.nsIHttpChannelInternal).
+            forceAllowThirdPartyCookie = true;
+        } catch (ex) { /* user is using Firefox 3.5 */ }
+
+        if (headers)
+            for (let header in headers)
+                request.setRequestHeader(header, headers[header]);
+
+        if (aIsBinary)
+            request.overrideMimeType('text/plain; charset=x-user-defined');
+        else if (/^file:/.test(url) && !/\.xml$/.test(url))
+            request.overrideMimeType('text/plain');
+
+        if (options)
+            for (let option in options)
+                request[option] = options[option];
+
+        request.send(null);
+    },
+
+    /**
+     * Refresh data. This method gets called on demand (including on startup)
+     * and retrieves data without passing any additional information about
+     * the selected persona and the application (that information is only included
+     * in the daily retrieval so we can get consistent daily statistics from it
+     * no matter how many times a user starts the application in a given day).
+     */
+    refreshData: function() {
+        let url = this.getURL("featured-feed");
+        this._makeRequest(url, this.onDataLoadComplete.bind(this));
+    },
+
+    /**
+     * Refresh data, providing metrics on persona usage in the process.
+     * This method gets called approximately once per day on a cross-session timer
+     * (provided Firefox is run every day), updates the version of the data
+     * that is currently in memory, and passes information about the selected
+     * persona and the host application to the server for statistical analysis
+     * (f.e. figuring out which personas are the most popular).
+     */
+    _refreshDataWithMetrics: function() {
+        let appInfo = Cc["@mozilla.org/xre/app-info;1"].
+        getService(Ci.nsIXULAppInfo);
+        let xulRuntime = Cc["@mozilla.org/xre/app-info;1"].
+        getService(Ci.nsIXULRuntime);
+
+        // Calculate the amount of time (in hours) since the persona was last changed.
+        let duration = "";
+        if (this._prefs.has("persona.lastChanged"))
+            duration = Math.round((new Date() - new Date(parseInt(this._prefs.get("persona.lastChanged")))) / 1000 / 60 / 60);
+
+        // This logic is based on ExtensionManager::_updateLocale.
+        let locale;
+        try {
+            if (Preferences.get("intl.locale.matchOS")) {
+                let localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"].
+                getService(Ci.nsILocaleService);
+                locale = localeSvc.getLocaleComponentForUserAgent();
+            } else
+                throw "set locale in the catch block";
+        } catch (ex) {
+            locale = Preferences.get("general.useragent.locale");
+        }
 
-    // Now that we have data, pick a new random persona.  Currently, this is
-    // the only time we pick a random persona besides when the user selects
-    // the "Random From [category]" menuitem, which means the user gets a new
-    // random persona each time they start the browser.
-    if (this.selected == "random") {
-      this.currentPersona = this._getRandomPersonaFromCategory(this.category);
-      this._prefs.reset("persona.lastRefreshed");
-      this._notifyPersonaChanged(this.currentPersona);
-    }
-  },
-
-  /**
-   * Makes a request to obtain the favorite personas json. This occurs only if
-   * a user is currenly signed in.
-   */
-  refreshFavorites : function() {
-    let url = this.getURL("favorites-feed");
-    this._makeRequest(url, this.onFavoritesLoadComplete.bind(this),
-                      null, null, true);
-  },
-
-  /**
-   * Handles the response from the refreshFavorites method. Loads the favorite
-   * personas list.
-   * @param aEvent The Http request event object.
-   */
-  onFavoritesLoadComplete : function(aEvent) {
-    let request = aEvent.target;
-
-    if (request.status != 200 || request.getResponseHeader("Content-Type") != "application/json") {
-      this._log.info("problem loading favorites from " + request.channel.name + ": " + request.status + " - " + request.statusText);
-      this.favorites = null;
-      return;
-    }
+        let params = {
+            type: this.selected,
+            id: this.currentPersona ? this.currentPersona.id : "",
+            duration: duration,
+            appID: appInfo.ID,
+            appVersion: appInfo.version,
+            appLocale: locale,
+            appOS: xulRuntime.OS,
+            appABI: xulRuntime.XPCOMABI
+        };
 
-    try {
-      this.favorites = JSON.parse(request.responseText)
-        .addons.filter(function (a) a.theme);
-    }
-    catch(ex) {
-      this._log.debug("error parsing favorites data: " + request.responseText.slice(0, 100) + "...");
-      return;
-    }
+        //dump("params: " + [name + "=" + encodeURIComponent(params[name]) for (name in params)].join("&") + "\n");
 
-    // Cache the response
-    let cacheDirectory =
-      FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
-    FileUtils.writeFile(cacheDirectory, "favorites.json", request.responseText);
-  },
-
-  /**
-   * Attempts to load this.favorites from the cached file in
-   * cache/favorites.json
-   */
-  loadFavoritesFromCache : function() {
-    let cacheDirectory =
-      FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
-    let data = FileUtils.readFile(cacheDirectory, "favorites.json");
-
-    try { this.favorites = JSON.parse(data); }
-    catch (e) {
-      // Could not load from cached data, file empty or does not exist perhaps
-    }
-  },
-
-  /**
-   * Adds the given persona to the favorites list. If the persona is already
-   * in the list then it is replaced.
-   * @param aPersona The persona object to be added.
-   */
-  addFavoritePersona : function(aPersona) {
-    // Make sure the favorites list exists.
-    if (!this.favorites)
-      this.favorites = [];
-
-    let i = this._findPersonaInArray(aPersona, this.favorites);
-    if (i >= 0)
-      this.favorites[i] = aPersona;
-    else
-      this.favorites.push(aPersona);
-  },
-
-  /**
-   * Removes the given persona from the favorites list, if found.
-   * @param aPersona The persona object to be removed.
-   */
-  removeFavoritePersona : function(aPersona) {
-    // Abort if the favorites list hasn't been created.
-    if (!this.favorites)
-      return;
-
-    let i = this._findPersonaInArray(aPersona, this.favorites);
-    if (i >= 0)
-      this.favorites.splice(i, 1);
-  },
-
-  _refreshPersona: function() {
-    // Only refresh the persona if the user selected a specific persona with an
-    // ID and update URL.  If the user selected a random persona, we'll change
-    // it the next time we refresh the directory; if the user selected
-    // the default persona, we don't need to refresh it, as it doesn't change;
-    // if the user selected a custom persona (which doesn't have an ID), it's
-    // not clear what refreshing it would mean; and if the persona doesn't have
-    // an update URL, then we don't have a way to refresh it.
-    if (this.selected != "current" ||
-        !this.currentPersona ||
-        !this.currentPersona.id ||
-        !this.currentPersona.updateURL)
-      return;
-
-    let headers = {};
-
-    if (this._prefs.has("persona.lastRefreshed")) {
-      let date = new Date(parseInt(this._prefs.get("persona.lastRefreshed")));
-      headers["If-Modified-Since"] = DateUtils.toRFC1123(date);
-    }
+        let url = this.getURL("featured-feed") +
+            "?" + [name + "=" + encodeURIComponent(params[name]) for (name in params)].join("&");
+        this._makeRequest(url, this.onDataLoadComplete.bind(this));
+    },
 
-    let currentPersona;
-    try       { currentPersona = JSON.stringify(this.currentPersona) }
-    catch(ex) { currentPersona = "error JSON.stringify-ing currentPersona " +
-                                 this.currentPersona + ": " + ex }
-    this._log.debug("_refreshPersona: currentPersona = " + currentPersona +
-                    "; url = " + this.currentPersona.updateURL);
-
-    let t = this;
-    this._makeRequest(this.currentPersona.updateURL,
-                      function(evt) { t.onPersonaLoadComplete(evt) },
-                      headers);
-  },
-
-  onPersonaLoadComplete: function(event) {
-    let request = event.target;
-
-    this._log.debug("onPersonaLoadComplete: status = " + request.status +
-                    "; responseText = " + request.responseText);
-
-    // 304 means the file we requested has not been modified since the
-    // If-Modified-Since date we specified, so there's nothing to do.
-    if (request.status == 304) {
-      //dump("304 - the persona has not been modified\n");
-      return;
-    }
+    onDataLoadComplete: function(aEvent) {
+        let request = aEvent.target;
 
-    if (request.status != 200)
-      throw("problem refreshing persona: " + request.status + " - " + request.statusText);
+        // XXX Try to reload again sooner?
+        if (request.status != 200)
+            throw ("problem loading data: " + request.status + " - " + request.statusText);
 
-    let persona = JSON.parse(request.responseText);
+        let response = JSON.parse(request.responseText);
 
-    // If the persona we're refreshing is no longer the selected persona,
-    // then cancel the refresh (otherwise we'd undo whatever changes the user
-    // has just made).
-    if (this.selected != "current" || !this.currentPersona ||
-        this.currentPersona.id != persona.id) {
-      //dump("persona " + persona.id + "(" + persona.name + ") no longer the current persona; ignoring refresh\n");
-      return;
-    }
+        this.personas = {
+            featured: response.addons
+        };
 
-    // If the version strings are identical, the persona hasn't changed.
-    if ((persona.version || "") == (this.currentPersona.version || ""))
-      return;
-
-    // Set the current persona to the updated version we got from the server,
-    // and notify observers about the change.
-    this.currentPersona = persona;
-    this._notifyPersonaChanged(this.currentPersona);
-
-    // Record when this refresh took place so the next refresh only looks
-    // for changes since this refresh.
-    // Note: we set the preference to a string value because preferences
-    // can't hold large enough integer values.
-    this._prefs.set("persona.lastRefreshed", new Date().getTime().toString());
-  },
-
-
-  //**************************************************************************//
-  // Implementation
-
-  // The JSON feed of personas retrieved from the server.
-  // Loaded upon service initialization and reloaded periodically thereafter.
-  personas: null,
-
-  // The JSON feed of favorite personas retrieved from the server.
-  favorites: null,
-
-  /**
-   * extensions.personas.selected: the type of persona that the user selected;
-   * possible values are default (the default Firefox theme), random (a random
-   * persona from a category), current (the value of this.currentPersona), and
-   * randomFavorite (a random persona from the favorite list).
-   */
-  get selected()        { return this._prefs.get("selected") },
-  set selected(newVal)  {        this._prefs.set("selected", newVal); },
-
-  /**
-   * extensions.personas.current: the current persona
-   */
-  get currentPersona() {
-    let current = this._prefs.get("current");
-    if (current) {
-      try       { return JSON.parse(current) }
-      catch(ex) { Cu.reportError("error getting current persona: " + ex) }
-    }
-    return null;
-  },
-  set currentPersona(newVal) {
-    try {
-      this._prefs.set("current", JSON.stringify(newVal));
-      this._cachePersonaImages(newVal);
-    }
-    catch(ex) { Cu.reportError("error setting current persona: " + ex) }
-  },
-
-  /**
-   * extensions.personas.category: the category from which to pick a random
-   * persona.
-   */
-  get category()        { return this._prefs.get("category") },
-  set category(newVal)  {        this._prefs.set("category", newVal) },
-
-  /**
-   * Returns a formatted URL from preferences.
-   */
-  getURL: function(pref, replacements) {
-    let defaults = {
-        SRC: this.urlSource
-    };
-
-    let t = this;
-    return this._prefs.get(pref + ".url")
-               .replace(/%([A-Z_]+)%/g, function(m0, m1) {
-      if (replacements && m1 in replacements)
-        return encodeURIComponent(replacements[m1]);
-
-      if (m1 in defaults)
-        return encodeURIComponent(defaults[m1]);
-
-      let pref = m1.toLowerCase().replace(/_/g, "-");
-      return encodeURIComponent(t._prefs.get(pref));
-    });
-  },
-
-  /**
-   * extensions.personas.custom: the custom persona.
-   */
-  get customPersona() {
-    let custom = this._prefs.get("custom");
-    if (custom) {
-      try       { return JSON.parse(custom) }
-      catch(ex) { Cu.reportError("error getting custom persona: " + ex) }
-    }
-    return null;
-  },
-  set customPersona(newVal) {
-    try       { this._prefs.set("custom", JSON.stringify(newVal)) }
-    catch(ex) { Cu.reportError("error setting custom persona: " + ex) }
-  },
-
-  /**
-   * Notifies the persona changes or uses the lightweight theme manager
-   * functionality for this purpose (if available)
-   * @param aPersona the persona to be set as current if the lightweight theme
-   * manager is available
-   */
-  _notifyPersonaChanged : function(aPersona) {
-    this._log.debug("_notifyPersonaChanged:\n" + Log4Moz.getStackTrace());
-    if (LightweightThemeManager) {
-      if (aPersona && aPersona.custom && LightweightThemeManager.setLocalTheme)
-        LightweightThemeManager.setLocalTheme(aPersona);
-      else
-        LightweightThemeManager.currentTheme = aPersona;
-    }
-    else
-      Observers.notify("personas:persona:changed");
-  },
-
-  changeToDefaultPersona: function() {
-    this.selected = "default";
-    this._prefs.set("persona.lastChanged", new Date().getTime().toString());
-    this._notifyPersonaChanged(null);
-  },
-
-  changeToRandomPersona: function(category) {
-    this.category = category;
-    this.currentPersona = this._getRandomPersonaFromCategory(category);
-    this.selected = "random";
-    this._prefs.set("persona.lastChanged", new Date().getTime().toString());
-    this._notifyPersonaChanged(this.currentPersona);
-  },
-
-  changeToRandomFavoritePersona : function() {
-    if (this.favorites && this.favorites.length > 0) {
-      this.currentPersona = this._getRandomPersonaFromArray(this.favorites);
-      this.selected = "randomFavorite";
-      this._prefs.set("persona.lastChanged", new Date().getTime().toString());
-      this._notifyPersonaChanged(this.currentPersona);
-    }
-  },
-
-  changeToPersona: function(persona) {
-    // The id must be a string and not an integer in order for the
-    // LightweightThemeManager to accept the persona. Older versions used to
-    // set a zero to the id of custom personas; if so, it needs to be changed
-    // to a "1". See: https://bugzilla.mozilla.org/show_bug.cgi?id=554220
-    if (persona.custom && persona.id === 0)
-      persona.id = "1";
-
-    // Check whether the persona is in the favorites or the recent lists,
-    // in which case the change-notification should not be shown.
-    let recent = this.getRecentPersonas();
-    let favorites = this.favorites;
-    let inRecent =
-      (recent && recent.some(function(v) v.id == persona.id));
-    let inFavorites =
-      (favorites && favorites.some(function(v) v.id == persona.id));
-
-    this.currentPersona = persona;
-    this._addPersonaToRecent(persona);
-    this.selected = "current";
-    this._prefs.reset("persona.lastRefreshed");
-    this._prefs.set("persona.lastChanged", new Date().getTime().toString());
-    this._notifyPersonaChanged(this.currentPersona);
-
-    // Show the notification if the selected persona is not in the favorite or
-    // recent lists, is not a custom persona and its author or username is not null.
-    // In this case we make sure at least one of these two fields is not null
-    // to prevent bug 526788.
-    if (!inRecent && !inFavorites && !persona.custom && (persona.author || persona.username))
-      this._showPersonaChangeNotification();
-  },
-
-  getPersonaJSON: function(data) {
-      if (data.theme) {
-          if (data.learnmore && !data.theme.detailURL)
-            data.theme.detailURL = this.updateURLSource(data.learnmore);
-          let authorURL = data.authors && data.authors[0] && data.authors[0].link;
-          if (authorURL)
-            data.theme.authorURL = this.updateURLSource(authorURL);
-          return data.theme;
-      }
-      return data;
-  },
-
-  updateURLSource: function(url) {
-    return url.replace(/([?&])src=api($|&)/, "$1src=personas-plus$2");
-  },
-
-  /**
-   * Reverts the current persona to the previously selected persona, if
-   * available
-   */
-  revertToPreviousPersona : function() {
-    let undonePersonaId = this.currentPersona.id;
-    let previousPersona = this._prefs.get("lastselected1");
-    if (previousPersona) {
-      this.currentPersona = JSON.parse(previousPersona);
-      this._revertRecent();
-      this.selected = "current";
-
-      if (LightweightThemeManager) {
-        // forget the lightweight theme too
-        LightweightThemeManager.forgetUsedTheme(undonePersonaId);
-
-        if (this.currentPersona && this.currentPersona.custom &&
-            LightweightThemeManager.setLocalTheme)
-          LightweightThemeManager.setLocalTheme(this.currentPersona);
-        else
-          LightweightThemeManager.currentTheme = this.currentPersona;
-      }
-      else
-        this.resetPersona();
-    }
-  },
-
-  /**
-   * Shows a notification displaying the currently selected persona and button
-   * to revert the changes.
-   */
-  _showPersonaChangeNotification : function() {
-    // Obtain most recent window and its notification box
-    let wm =
-      Cc["@mozilla.org/appshell/window-mediator;1"].
-        getService(Ci.nsIWindowMediator);
+        // Cache the response
+        let cacheDirectory =
+            FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
+        FileUtils.writeFile(cacheDirectory, "personas.json", request.responseText);
+    },
+
+    /**
+     * Attempts to load this.personas from the cached file in cache/personas.json
+     */
+    loadDataFromCache: function() {
+        let path =
+            FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
+        path.append("personas.json");
+
+        this._makeRequest(Services.io.newFileURI(path).spec, onload.bind(this),
+            null, false, true);
+
+        function onload(event) {
+            try {
+                this.personas = JSON.parse(event.target.responseText);
+            } catch (e) {
+                // Could not load from cached data, file empty or does not exist perhaps
+                return;
+            }
+
+            // Now that we have data, pick a new random persona.  Currently, this is
+            // the only time we pick a random persona besides when the user selects
+            // the "Random From [category]" menuitem, which means the user gets a new
+            // random persona each time they start the browser.
+            if (this.selected == "random") {
+                this.currentPersona = this._getRandomPersonaFromCategory(this.category);
+                this._prefs.reset("persona.lastRefreshed");
+                this._notifyPersonaChanged(this.currentPersona);
+            }
+        }
+    },
+
+    /**
+     * Makes a request to obtain the favorite personas json. This occurs only if
+     * a user is currenly signed in.
+     */
+    refreshFavorites: function(aURL) {
+        let url = aURL || this.getURL("favorites-feed");
+        this._makeRequest(url, this.onFavoritesLoadComplete.bind(this, aURL),
+            null, null, true);
+    },
+
+    /**
+     * Handles the response from the refreshFavorites method. Loads the favorite
+     * personas list.
+     * @param aURL The URL passed to refreshFavorites
+     * @param aEvent The Http request event object.
+     */
+    onFavoritesLoadComplete: function(aURL, aEvent) {
+        let request = aEvent.target;
+
+        this.favoritesError = false;
+        if (request.status != 200 || request.getResponseHeader("Content-Type") != "application/json") {
+            this._log.info("problem loading favorites from " + request.channel.name + ": " + request.status + " - " + request.statusText);
+            if (request.status != 200)
+                this.favoritesError = true;
+            this.favorites = null;
+            return;
+        }
 
-    let notificationBox;
-    switch (this.appInfo.ID) {
-      case this.FIREFOX_ID:
-        notificationBox = wm.getMostRecentWindow("navigator:browser").
-                          getBrowser().getNotificationBox();
-        break;
-      case this.THUNDERBIRD_ID:
-        notificationBox = wm.getMostRecentWindow("mail:3pane").
-                          document.getElementById("mail-notification-box");
-        break;
-      default:
-        throw "unknown application ID " + this.appInfo.ID;
-    }
+        try {
+            var json = JSON.parse(request.responseText)
+                .addons.filter(function(a) a.theme);
+        } catch (ex) {
+            this._log.debug("error parsing favorites data: " + request.responseText.slice(0, 100) + "...");
+            return;
+        }
 
-    // If there is another notification of the same kind already, remove it.
-    let oldNotification =
-      notificationBox.getNotificationWithValue("lwtheme-install-notification");
-    if (oldNotification)
-      notificationBox.removeNotification(oldNotification);
-
-    let message = this._strings.get("notification.personaWasSelected",
-                                    [this.currentPersona.name,
-                                     (this.currentPersona.author ?
-                                      this.currentPersona.author :
-                                      this.currentPersona.username)]);
-
-    let revertButton = {
-      label     : this._strings.get("notification.revertButton.label"),
-      accessKey : this._strings.get("notification.revertButton.accesskey"),
-      popup     : null,
-      callback  : function() { PersonaService.revertToPreviousPersona(); }
-    };
-
-    notificationBox.appendNotification(
-      message, "lwtheme-install-notification", null,
-      notificationBox.PRIORITY_INFO_MEDIUM, [ revertButton ] );
-  },
-
-  /**
-   * Looks for the given persona in the given array and returns its index.
-   * @param aPersona The persona to be found.
-   * @param aPersonaArray The array in which to look for the persona.
-   * @return The index of the persona in the array; -1 if not found.
-   */
-  _findPersonaInArray : function(aPersona, aPersonaArray) {
-    for (let i = 0; i < aPersonaArray.length; i++) {
-      if (aPersonaArray[i].id == aPersona.id)
-        return i;
-    }
-    return -1;
-  },
-
-  _getRandomPersonaFromArray : function(aPersonaArray) {
-    // Get a random item from the list, trying up to five times to get one
-    // that is different from the currently-selected item in the category
-    // (if any).  We use Math.floor instead of Math.round to pick a random
-    // number because the JS reference says Math.round returns a non-uniform
-    // distribution
-    // <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Math:random#Examples>.
-    if (aPersonaArray && aPersonaArray.length > 0) {
-      let randomIndex, randomItem;
-      for (let i = 0; i < 5; i++) {
-        randomIndex = Math.floor(Math.random() * aPersonaArray.length);
-        randomItem = aPersonaArray[randomIndex];
-        if (!this.currentPersona || randomItem.id != this.currentPersona.id)
-          break;
-      }
-
-      return this.getPersonaJSON(randomItem);
-    }
-    return this.currentPersona;
-  },
+        this[aURL ? "_favorites" : "favorites"] = json;
+    },
 
-  _getRandomPersonaFromCategory: function(aCategoryName) {
-    // If we have the list of categories, use it to pick a random persona
-    // from the selected category.
+    /**
+     * Attempts to load this.favorites from the cached file in
+     * cache/favorites.json
+     */
+    loadFavoritesFromCache: function() {
+        let path = FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
+        path.append("favorites.json");
 
-    if (this.personas) {
-      let personas = null;
+        try {
+            this.refreshFavorites(Services.io.newFileURI(path).spec);
+        } catch (e) {
+            Cu.reportError(e);
+        }
+    },
+
+    /**
+     * Adds the given persona to the favorites list. If the persona is already
+     * in the list then it is replaced.
+     * @param aPersona The persona object to be added.
+     */
+    addFavoritePersona: function(aPersona) {
+        // Make sure the favorites list exists.
+        if (!this.favorites)
+            this.favorites = [];
+
+        let i = this._findPersonaInArray(aPersona, this.favorites);
+        if (i >= 0)
+            this.favorites[i] = aPersona;
+        else
+            this.favorites.push(aPersona);
+    },
+
+    /**
+     * Removes the given persona from the favorites list, if found.
+     * @param aPersona The persona object to be removed.
+     */
+    removeFavoritePersona: function(aPersona) {
+        // Abort if the favorites list hasn't been created.
+        if (!this.favorites)
+            return;
+
+        let i = this._findPersonaInArray(aPersona, this.favorites);
+        if (i >= 0)
+            this.favorites.splice(i, 1);
+    },
+
+    _refreshPersona: function() {
+        // Only refresh the persona if the user selected a specific persona with an
+        // ID and update URL.  If the user selected a random persona, we'll change
+        // it the next time we refresh the directory; if the user selected
+        // the default persona, we don't need to refresh it, as it doesn't change;
+        // if the user selected a custom persona (which doesn't have an ID), it's
+        // not clear what refreshing it would mean; and if the persona doesn't have
+        // an update URL, then we don't have a way to refresh it.
+        if (this.selected != "current" ||
+            !this.currentPersona ||
+            !this.currentPersona.id ||
+            !this.currentPersona.updateURL)
+            return;
+
+        let headers = {};
+
+        if (this._prefs.has("persona.lastRefreshed")) {
+            let date = new Date(parseInt(this._prefs.get("persona.lastRefreshed")));
+            headers["If-Modified-Since"] = DateUtils.toRFC1123(date);
+        }
 
-      if (aCategoryName == "featured")
-        personas = this.personas.featured;
-      if (personas)
-        return this._getRandomPersonaFromArray(personas);
-    }
-    return this.currentPersona;
-  },
-
-  /**
-   * Obtains the list of recently selected personas, parsed from the
-   * "lastselected" preferences.
-   * @param aHowMany (Optional, default 4) How many personas to obtain.
-   * @return The list of recent personas.
-   */
-  getRecentPersonas : function(aHowMany) {
-    if (!aHowMany)
-      aHowMany = 4;
-
-    // Parse the list from the preferences
-    let personas = [];
-    for (let i = 0; i < aHowMany; i++) {
-      if (this._prefs.has("lastselected" + i)) {
+        let currentPersona;
         try {
-          personas.push(JSON.parse(this._prefs.get("lastselected" + i)));
+            currentPersona = JSON.stringify(this.currentPersona)
+        } catch (ex) {
+            currentPersona = "error JSON.stringify-ing currentPersona " +
+                this.currentPersona + ": " + ex
+        }
+        this._log.debug("_refreshPersona: currentPersona = " + currentPersona +
+            "; url = " + this.currentPersona.updateURL);
+
+        let t = this;
+        this._makeRequest(this.currentPersona.updateURL,
+            function(evt) {
+                t.onPersonaLoadComplete(evt)
+            },
+            headers);
+    },
+
+    onPersonaLoadComplete: function(event) {
+        let request = event.target;
+
+        this._log.debug("onPersonaLoadComplete: status = " + request.status +
+            "; responseText = " + request.responseText);
+
+        // 304 means the file we requested has not been modified since the
+        // If-Modified-Since date we specified, so there's nothing to do.
+        if (request.status == 304) {
+            //dump("304 - the persona has not been modified\n");
+            return;
         }
-        catch(ex) {}
-      }
-    }
 
-    return personas;
-  },
-
-  _addPersonaToRecent: function(persona) {
-    let personas = this.getRecentPersonas();
-
-    // Remove personas with the same ID (i.e. don't allow the recent persona
-    // to appear twice on the list).  Afterwards, we'll add the recent persona
-    // to the list in a way that makes it the most recent one.
-    if (persona.id)
-      personas = personas.filter(function(v) !v.id || v.id != persona.id);
-
-    // Make the new persona the most recent one.
-    personas.unshift(persona);
-
-    // Note: at this point, there might be five personas on the list, four
-    // that we parsed from preferences and the one we're now adding. But we
-    // only serialize the first four back to preferences, so the oldest one
-    // drops off the end of the list.
-
-    // We store five in case we need to revert changes in the list alter on,
-    // even though only four will be displayed.
-    for (let i = 0; i < 5; i++) {
-      if (i < personas.length)
-        this._prefs.set("lastselected" + i, JSON.stringify(personas[i]));
-      else
-        this._prefs.reset("lastselected" + i);
-    }
-  },
-
-  /**
-   * Removes the most recent persona from the recent list, and leaves the
-   * following four personas as the most recent.
-   */
-  _revertRecent: function() {
-    // Create a new list of recent personas, removing the first one, but
-    // including the 5th (hidden) one.
-    let personas = this.getRecentPersonas(5);
-    personas.shift();
-
-    // Serialize the list of recent personas.
-    for (let i = 0; i < 5; i++) {
-      if (i < personas.length)
-        this._prefs.set("lastselected" + i, JSON.stringify(personas[i]));
-      else
-        this._prefs.reset("lastselected" + i);
-    }
-  },
-
-  onUseColorChanged: function() {
-    // Notify observers that the persona has changed so the change in whether
-    // or not to use the text or accent color will get applied.  The persona
-    // hasn't really changed, but doing this has the desired effect without any
-    // known unwanted side effects.
-    Observers.notify("personas:persona:changed");
-  },
-
-  previewingPersona: null,
-
-  /**
-   * Display the given persona temporarily.  Useful for showing users who are
-   * browsing the directory of personas what a given persona will look like
-   * when selected, f.e. on mouseover.  Consumers who call this method should
-   * call resetPersona when the preview ends, f.e. on mouseout.
-   */
-  previewPersona: function(persona) {
-    if (LightweightThemeManager)
-      LightweightThemeManager.previewTheme(persona);
-    else {
-      this.previewingPersona = persona;
-      Observers.notify("personas:persona:changed");
-    }
-  },
-
-  /**
-   * Stop previewing a persona.
-   */
-  resetPersona: function() {
-    if (LightweightThemeManager)
-      LightweightThemeManager.resetPreview();
-    else {
-      this.previewingPersona = null;
-      Observers.notify("personas:persona:changed");
-    }
-  },
-
-  /**
-   * Gets the persona specified by the initial_persona cookie.
-   */
-  _getPersonaFromCookie: function() {
-    let authorizedHosts = this._prefs.get("authorizedHosts").split(/[, ]+/);
-    let cookieManager =
-      Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
-    let cookieEnu = cookieManager.enumerator;
-    let selectedCookie = null;
-
-    while (cookieEnu.hasMoreElements()) {
-      let cookie = cookieEnu.getNext().QueryInterface(Ci.nsICookie);
-
-      // XXX: Remove the initial dot from the host name, if any, before it is
-      // compared against the authorized hosts. This fixes the bug reported in
-      // bug 492392 that getpersonas.com and www.getpersonas.com cookies
-      // imported from IE have the cookie host .getpersonas.com, so they didn't
-      // match any of the authorized hosts.
-      let cookieHost = cookie.host.replace(/^\./, "");
-
-      if (cookie.name == COOKIE_INITIAL_PERSONA &&
-          authorizedHosts.some(function(v) v == cookieHost)) {
-
-        // There could be more than one "initial_persona" cookie. The cookie
-        // with latest expiration time is selected.
-        if (null == selectedCookie ||
-            cookie.expires > selectedCookie.expires) {
-          selectedCookie = cookie;
+        if (request.status != 200)
+            throw ("problem refreshing persona: " + request.status + " - " + request.statusText);
+
+        let persona = JSON.parse(request.responseText);
+
+        // If the persona we're refreshing is no longer the selected persona,
+        // then cancel the refresh (otherwise we'd undo whatever changes the user
+        // has just made).
+        if (this.selected != "current" || !this.currentPersona ||
+            this.currentPersona.id != persona.id) {
+            //dump("persona " + persona.id + "(" + persona.name + ") no longer the current persona; ignoring refresh\n");
+            return;
         }
 
-        cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
-      }
-    }
+        // If the version strings are identical, the persona hasn't changed.
+        if ((persona.version || "") == (this.currentPersona.version || ""))
+            return;
 
-    if (selectedCookie)
-      return JSON.parse(decodeURIComponent(selectedCookie.value));
-
-    return null;
-  },
-
-  //**************************************************************************//
-  // Lightweight Themes - Personas synchronization
-
-  _ltmSyncTimer : null,
-
-  /**
-   * Updates the add-on to reflect the changes from the Tools - Add-ons - Themes
-   * dialog. If a lightweight theme is set, it is also set as the add-on's current
-   * persona. If a regular theme is set, the current persona is set to "default".
-   */
-  onLightweightThemeChanged: function() {
-    let currentTheme = LightweightThemeManager.currentTheme;
-
-    this._ltmSyncTimer.cancel();
-
-    if (currentTheme &&
-        (currentTheme.id != this.currentPersona.id || this.selected == "default"))
-      this.changeToPersona(currentTheme);
-    else if (!currentTheme && this.selected != "default") {
-
-      // XXX: In Firefox 4, a persona change using the LightweightThemeManager causes
-      // two "lightweight-theme-changed" notifications to be fired instead of one:
-      // the first one with a null theme, and the second one with the actual theme.
-      // This causes the extension to get confused, as the first null theme causes
-      // it go change the persona to the default one, losing the "random" setting,
-      // amongst other things. To circumvent this, notifications with a "null" theme
-      // are delayed just a bit to allow the second notification to cancel it. If there's
-      // no second notification, then the null theme is applied.
-      // See: https://bugzilla.mozilla.org/show_bug.cgi?id=631803
-      let t = this;
-      this._ltmSyncTimer.initWithCallback(
-        { notify : function() { t.changeToDefaultPersona(); } },
-        100,
-        Ci.nsITimer.TYPE_ONE_SHOT);
-    }
-  },
+        // Set the current persona to the updated version we got from the server,
+        // and notify observers about the change.
+        this.currentPersona = persona;
+        this._notifyPersonaChanged(this.currentPersona);
 
-  //**************************************************************************//
-  // Random Rotation
+        // Record when this refresh took place so the next refresh only looks
+        // for changes since this refresh.
+        // Note: we set the preference to a string value because preferences
+        // can't hold large enough integer values.
+        this._prefs.set("persona.lastRefreshed", new Date().getTime().toString());
+    },
 
-  _rotationTimer : null,
 
-  /**
-   * Checks the current value of the "selected" preference and sets the
-   * rotation timer accordingly.
-   */
-  onSelectedModeChanged: function() {
-    this._rotationTimer.cancel();
+    //**************************************************************************//
+    // Implementation
 
-    let selectedMode = this.selected;
+    // The JSON feed of personas retrieved from the server.
+    // Loaded upon service initialization and reloaded periodically thereafter.
+    personas: null,
 
-    if (selectedMode == "random" || selectedMode == "randomFavorite") {
-      let interval = this._prefs.get("rotationInterval") * 1000;
-      let that = this;
+    // The JSON feed of favorite personas retrieved from the server.
+    _favorites: null,
 
-      this._rotationTimer.initWithCallback(
-        { notify: function(aTimer) { that._rotatePersona(); } },
-        interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+    get favorites() this._favorites,
+    set favorites(val) {
+        this._favorites = val;
 
-      this._rotatePersona();
-    }
-  },
-
-  /**
-   * Ensures that cookies are enabled for our current add-ons host, and
-   * prompts the user to enable them if not.
-   */
-  checkCookiesEnabled: function() {
-    const PERM = "cookie";
-    const ALLOW = 1, SESSION_ONLY = 8;
-
-    try {
-      let prefs = new Preferences("network.cookie.");
-      let cookiesEnabled = (prefs.get("alwaysAcceptSessionCookies") ||
-                            prefs.get("cookieBehavior") != 2);
-      let uri = Services.io.newURI(this.getURL("favorites-feed"), null, null);
-
-      let _ = this._strings.get.bind(this._strings);
-      if (cookiesEnabled
-            || ~[ALLOW, SESSION_ONLY].indexOf(Services.perms.testPermission(uri, PERM))) {
-        this._prefs.reset("naggedAboutCookies");
-      } else {
-        if (this._prefs.get("naggedAboutCookies") == this.addonsHost)
-          return;
-
-        let stopNagging = { value: true };
-        let enableCookies = Services.prompt.confirmCheck(
-            null, _("cookies.confirm.title"),
-            _("cookies.confirm.message", [this.addonsHost]),
-            _("cookies.confirm.checkbox"),
-            stopNagging);
-
-        if (enableCookies) {
-          Services.perms.remove(uri.host, PERM);
-          Services.perms.add(uri, PERM, ALLOW);
+        let path =
+            FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
+
+        // Cache the response
+        if (val && typeof val == "object")
+        // This FileUtils nonsense has to go...
+            FileUtils.writeFile(path, "favorites.json", JSON.stringify(val));
+        else {
+            path.append("favorites.json");
+            OS.File.remove(path.spec);
         }
-        else if (stopNagging.value) {
-          this._prefs.set("naggedAboutCookies", this.addonsHost);
+    },
+
+    /**
+     * extensions.personas.selected: the type of persona that the user selected;
+     * possible values are default (the default Firefox theme), random (a random
+     * persona from a category), current (the value of this.currentPersona), and
+     * randomFavorite (a random persona from the favorite list).
+     */
+    get selected() {
+        return this._prefs.get("selected")
+    },
+    set selected(newVal) {
+        this._prefs.set("selected", newVal);
+    },
+
+    /**
+     * extensions.personas.current: the current persona
+     */
+    get currentPersona() {
+        let current = this._prefs.get("current");
+        if (current) {
+            try {
+                return JSON.parse(current)
+            } catch (ex) {
+                Cu.reportError("error getting current persona: " + ex)
+            }
         }
-      }
-    }
-    catch (e) {
-        Cu.reportError(e);
-    }
-  },
-
-  /**
-   * Handles the addons-host preference being changed.
-   */
-  onAddonsHostChanged: function() {
-    this.addonsHost = this._prefs.get("addons-host");
-    this.checkCookiesEnabled()
-    this.refreshFavorites();
-  },
-
-  /**
-   * Changes the current persona to a random persona of the same category (while
-   * in "random" mode) or a random persona from he favorite list (while in
-   * "randomFavorite" mode).
-   */
-  _rotatePersona : function() {
-    switch (this.selected) {
-      case "random":
-        this.changeToRandomPersona(this.category);
-        break;
-      case "randomFavorite":
-        this.changeToRandomFavoritePersona();
-        break;
-    }
-  },
-
-  //**************************************************************************//
-  // Persona Images Caching
-
-  /**
-   * Caches the header and footer images of the given persona inside the
-   * directory [profile]/personas/cache/[persona.id]. It removes all other
-   * existing persona directories before doing so.
-   * @param aPersona The persona for which to cache the images.
-   */
-  _cachePersonaImages : function(aPersona) {
-    let cacheDirectory =
-      FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
-
-    // Remove all other subdirectories in the cache directory
-    // XXX: In the future, if we want to keep more than one persona cached
-    // this step would be removed.
-    let subdirs = FileUtils.getDirectoryEntries(cacheDirectory);
-    for (let i = 0; i < subdirs.length; i++) {
-      if (subdirs[i].isDirectory())
-        subdirs[i].remove(true);
-    }
-
-    // Create directory for the given persona
-    let personaDir = FileUtils.getDirectory(cacheDirectory, aPersona.id);
-
-    // Save header if specified.
-    let header = aPersona.headerURL || aPersona.header;
-    if (header) {
-      // The header can be a base64 string or a malformed URL, in which case
-      // the error can be safely ignored.
-      try {
-        let headerURI = URI.get(header, null, null)
-                           .QueryInterface(Ci.nsIURL);
-
-        let headerCallback = function(aEvent) {
-          let request = aEvent.target;
-          // Save only if the folder still exists (Could have been deleted already)
-          if (request.status == 200 && personaDir.exists()) {
-            FileUtils.writeBinaryFile(
-              personaDir.clone(),
-              "header" + "." + headerURI.fileExtension,
-              request.responseText);
-          }
+        return null;
+    },
+    set currentPersona(newVal) {
+        try {
+            this._prefs.set("current", JSON.stringify(newVal));
+            this._cachePersonaImages(newVal);
+        } catch (ex) {
+            Cu.reportError("error setting current persona: " + ex)
+        }
+    },
+
+    /**
+     * extensions.personas.category: the category from which to pick a random
+     * persona.
+     */
+    get category() {
+        return this._prefs.get("category")
+    },
+    set category(newVal) {
+        this._prefs.set("category", newVal)
+    },
+
+    /**
+     * Returns a formatted URL from preferences.
+     */
+    getURL: function(pref, replacements) {
+        let defaults = {
+            SRC: this.urlSource
         };
-        this._makeRequest(headerURI.spec, headerCallback, null, true);
-      }
-      catch (e) {}
-    }
 
-    // Save footer if specified.
-    let footer = aPersona.footerURL || aPersona.footer;
-    if (footer) {
-      // The footer can be a base64 string or a malformed URL, in which case
-      // the error can be safely ignored.
-      try {
-        let footerURI = URI.get(footer, null, null)
-                           .QueryInterface(Ci.nsIURL);
-        let footerCallback = function(aEvent) {
-          let request = aEvent.target;
-          // Save only if the folder still exists (Could have been deleted already)
-          if (request.status == 200 && personaDir.exists()) {
-            FileUtils.writeBinaryFile(
-              personaDir.clone(),
-              "footer" + "." + footerURI.fileExtension,
-              request.responseText);
-          }
-        };
-        this._makeRequest(footerURI.spec, footerCallback, null, true);
-      }
-      catch (e) {}
-    }
-  },
-
-  /**
-   * Obtains the cached images of the given persona. This are stored in the
-   * _cachePersonaImages method under the directory
-   * [profile]/personas/cache/[persona.id].
-   * @param aPersona The persona for which to look the cached images.
-   * @return An object with "header" and "footer" properties, each containing
-   * the file URL of the image. Null otherwise.
-   */
-  getCachedPersonaImages : function(aPersona) {
-    let cacheDirectory =
-      FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
-
-    let personaDir = FileUtils.getDirectory(cacheDirectory, aPersona.id, true);
-    if (personaDir.exists()) {
-
-      let headerFile = personaDir.clone();
-      let footerFile = personaDir.clone();
-
-      let headerFileExtension =
-        URI.get(aPersona.headerURL || aPersona.header, null, null)
-           .QueryInterface(Ci.nsIURL).fileExtension;
-
-      let footerFileExtension =
-        URI.get(aPersona.footerURL || aPersona.footer, null, null)
-           .QueryInterface(Ci.nsIURL).fileExtension;
-
-      headerFile.append("header" + "." + headerFileExtension);
-      footerFile.append("footer" + "." + footerFileExtension);
-
-      if (headerFile.exists() && footerFile.exists()) {
-        let ios =
-          Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
-
-        let headerURI = ios.newFileURI(headerFile);
-        let footerURI = ios.newFileURI(footerFile);
-
-        return {
-          header : headerURI.spec,
-          footer : footerURI.spec
+        let t = this;
+        return this._prefs.get(pref + ".url")
+            .replace(/%([A-Z_]+)%/g, function(m0, m1) {
+                if (replacements && m1 in replacements)
+                    return encodeURIComponent(replacements[m1]);
+
+                if (m1 in defaults)
+                    return encodeURIComponent(defaults[m1]);
+
+                let pref = m1.toLowerCase().replace(/_/g, "-");
+                return encodeURIComponent(t._prefs.get(pref));
+            });
+    },
+
+    /**
+     * extensions.personas.custom: the custom persona.
+     */
+    get customPersona() {
+        let custom = this._prefs.get("custom");
+        if (custom) {
+            try {
+                return JSON.parse(custom)
+            } catch (ex) {
+                Cu.reportError("error getting custom persona: " + ex)
+            }
+        }
+        return null;
+    },
+    set customPersona(newVal) {
+        try {
+            this._prefs.set("custom", JSON.stringify(newVal))
+        } catch (ex) {
+            Cu.reportError("error setting custom persona: " + ex)
+        }
+    },
+
+    /**
+     * Notifies the persona changes or uses the lightweight theme manager
+     * functionality for this purpose (if available)
+     * @param aPersona the persona to be set as current if the lightweight theme
+     * manager is available
+     */
+    _notifyPersonaChanged: function(aPersona) {
+        this._log.debug("_notifyPersonaChanged:\n" + Log4Moz.getStackTrace());
+        if (LightweightThemeManager) {
+            if (aPersona && aPersona.custom && LightweightThemeManager.setLocalTheme)
+                LightweightThemeManager.setLocalTheme(aPersona);
+            else
+                LightweightThemeManager.currentTheme = aPersona;
+        } else
+            Observers.notify("personas:persona:changed");
+    },
+
+    changeToDefaultPersona: function() {
+        this.selected = "default";
+        this._prefs.set("persona.lastChanged", new Date().getTime().toString());
+        this._notifyPersonaChanged(null);
+    },
+
+    changeToRandomPersona: function(category) {
+        this.category = category;
+        this.currentPersona = this._getRandomPersonaFromCategory(category);
+        this.selected = "random";
+        this._prefs.set("persona.lastChanged", new Date().getTime().toString());
+        this._notifyPersonaChanged(this.currentPersona);
+    },
+
+    changeToRandomFavoritePersona: function() {
+        if (this.favorites && this.favorites.length > 0) {
+            this.currentPersona = this._getRandomPersonaFromArray(this.favorites);
+            this.selected = "randomFavorite";
+            this._prefs.set("persona.lastChanged", new Date().getTime().toString());
+            this._notifyPersonaChanged(this.currentPersona);
+        }
+    },
+
+    changeToPersona: function(persona) {
+        // The id must be a string and not an integer in order for the
+        // LightweightThemeManager to accept the persona. Older versions used to
+        // set a zero to the id of custom personas; if so, it needs to be changed
+        // to a "1". See: https://bugzilla.mozilla.org/show_bug.cgi?id=554220
+        if (persona.custom && persona.id === 0)
+            persona.id = "1";
+
+        // Check whether the persona is in the favorites or the recent lists,
+        // in which case the change-notification should not be shown.
+        let recent = this.getRecentPersonas();
+        let favorites = this.favorites;
+        let inRecent =
+            (recent && recent.some(function(v) v.id == persona.id));
+        let inFavorites =
+            (favorites && favorites.some(function(v) v.id == persona.id));
+
+        this.currentPersona = persona;
+        this._addPersonaToRecent(persona);
+        this.selected = "current";
+        this._prefs.reset("persona.lastRefreshed");
+        this._prefs.set("persona.lastChanged", new Date().getTime().toString());
+        this._notifyPersonaChanged(this.currentPersona);
+
+        // Show the notification if the selected persona is not in the favorite or
+        // recent lists, is not a custom persona and its author or username is not null.
+        // In this case we make sure at least one of these two fields is not null
+        // to prevent bug 526788.
+        if (!inRecent && !inFavorites && !persona.custom && (persona.author || persona.username))
+            this._showPersonaChangeNotification();
+    },
+
+    getPersonaJSON: function(data) {
+        if (data.theme) {
+            if (data.learnmore && !data.theme.detailURL)
+                data.theme.detailURL = this.updateURLSource(data.learnmore);
+            let authorURL = data.authors && data.authors[0] && data.authors[0].link;
+            if (authorURL)
+                data.theme.authorURL = this.updateURLSource(authorURL);
+            return data.theme;
+        }
+        return data;
+    },
+
+    updateURLSource: function(url) {
+        return url.replace(/([?&])src=api($|&)/, "$1src=personas-plus$2");
+    },
+
+    /**
+     * Reverts the current persona to the previously selected persona, if
+     * available
+     */
+    revertToPreviousPersona: function() {
+        let undonePersonaId = this.currentPersona.id;
+        let previousPersona = this._prefs.get("lastselected1");
+        if (previousPersona) {
+            this.currentPersona = JSON.parse(previousPersona);
+            this._revertRecent();
+            this.selected = "current";
+
+            if (LightweightThemeManager) {
+                // forget the lightweight theme too
+                LightweightThemeManager.forgetUsedTheme(undonePersonaId);
+
+                if (this.currentPersona && this.currentPersona.custom &&
+                    LightweightThemeManager.setLocalTheme)
+                    LightweightThemeManager.setLocalTheme(this.currentPersona);
+                else
+                    LightweightThemeManager.currentTheme = this.currentPersona;
+            } else
+                this.resetPersona();
+        }
+    },
+
+    /**
+     * Shows a notification displaying the currently selected persona and button
+     * to revert the changes.
+     */
+    _showPersonaChangeNotification: function() {
+        // Obtain most recent window and its notification box
+        let wm =
+            Cc["@mozilla.org/appshell/window-mediator;1"].
+        getService(Ci.nsIWindowMediator);
+
+        let notificationBox;
+        switch (this.appInfo.ID) {
+            case this.FIREFOX_ID:
+                notificationBox = wm.getMostRecentWindow("navigator:browser").
+                getBrowser().getNotificationBox();
+                break;
+            case this.THUNDERBIRD_ID:
+                notificationBox = wm.getMostRecentWindow("mail:3pane").
+                document.getElementById("mail-notification-box");
+                break;
+            default:
+                throw "unknown application ID " + this.appInfo.ID;
+        }
+
+        // If there is another notification of the same kind already, remove it.
+        let oldNotification =
+            notificationBox.getNotificationWithValue("lwtheme-install-notification");
+        if (oldNotification)
+            notificationBox.removeNotification(oldNotification);
+
+        let message = this._strings.get("notification.personaWasSelected", [this.currentPersona.name, (this.currentPersona.author ?
+            this.currentPersona.author :
+            this.currentPersona.username)]);
+
+        let revertButton = {
+            label: this._strings.get("notification.revertButton.label"),
+            accessKey: this._strings.get("notification.revertButton.accesskey"),
+            popup: null,
+            callback: function() {
+                PersonaService.revertToPreviousPersona();
+            }
         };
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Monitors changes in cookies. If the modified cookie is the Personas session
-   * cookie, then the favorites are refreshed (if the user is signed in).
-   * @param aCookie The cookie that has been added, changed or removed.
-   */
-  onCookieChanged : function(aCookie, aChange) {
-    if (aCookie instanceof Ci.nsICookie) {
-      if (aCookie.name == "sessionid" && (aCookie.host == this.addonsHost ||
-                                          aCookie.host == "." + this.addonsHost)) {
-        if (aCookie.value == this._cookieValue)
-          return;
-        this._cookieValue = aCookie.value;
+
+        notificationBox.appendNotification(
+            message, "lwtheme-install-notification", null,
+            notificationBox.PRIORITY_INFO_MEDIUM, [revertButton]);
+    },
+
+    /**
+     * Looks for the given persona in the given array and returns its index.
+     * @param aPersona The persona to be found.
+     * @param aPersonaArray The array in which to look for the persona.
+     * @return The index of the persona in the array; -1 if not found.
+     */
+    _findPersonaInArray: function(aPersona, aPersonaArray) {
+        for (let i = 0; i < aPersonaArray.length; i++) {
+            if (aPersonaArray[i].id == aPersona.id)
+                return i;
+        }
+        return -1;
+    },
+
+    _getRandomPersonaFromArray: function(aPersonaArray) {
+        // Get a random item from the list, trying up to five times to get one
+        // that is different from the currently-selected item in the category
+        // (if any).  We use Math.floor instead of Math.round to pick a random
+        // number because the JS reference says Math.round returns a non-uniform
+        // distribution
+        // <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Math:random#Examples>.
+        if (aPersonaArray && aPersonaArray.length > 0) {
+            let randomIndex, randomItem;
+            for (let i = 0; i < 5; i++) {
+                randomIndex = Math.floor(Math.random() * aPersonaArray.length);
+                randomItem = aPersonaArray[randomIndex];
+                if (!this.currentPersona || randomItem.id != this.currentPersona.id)
+                    break;
+            }
+
+            return this.getPersonaJSON(randomItem);
+        }
+        return this.currentPersona;
+    },
+
+    _getRandomPersonaFromCategory: function(aCategoryName) {
+        // If we have the list of categories, use it to pick a random persona
+        // from the selected category.
+
+        if (this.personas) {
+            let personas = null;
+
+            if (aCategoryName == "featured")
+                personas = this.personas.featured;
+            if (personas)
+                return this._getRandomPersonaFromArray(personas);
+        }
+        return this.currentPersona;
+    },
+
+    /**
+     * Obtains the list of recently selected personas, parsed from the
+     * "lastselected" preferences.
+     * @param aHowMany (Optional, default 4) How many personas to obtain.
+     * @return The list of recent personas.
+     */
+    getRecentPersonas: function(aHowMany) {
+        if (!aHowMany)
+            aHowMany = 4;
+
+        // Parse the list from the preferences
+        let personas = [];
+        for (let i = 0; i < aHowMany; i++) {
+            if (this._prefs.has("lastselected" + i)) {
+                try {
+                    personas.push(JSON.parse(this._prefs.get("lastselected" + i)));
+                } catch (ex) {}
+            }
+        }
+
+        return personas;
+    },
+
+    _addPersonaToRecent: function(persona) {
+        let personas = this.getRecentPersonas();
+
+        // Remove personas with the same ID (i.e. don't allow the recent persona
+        // to appear twice on the list).  Afterwards, we'll add the recent persona
+        // to the list in a way that makes it the most recent one.
+        if (persona.id)
+            personas = personas.filter(function(v) !v.id || v.id != persona.id);
+
+        // Make the new persona the most recent one.
+        personas.unshift(persona);
+
+        // Note: at this point, there might be five personas on the list, four
+        // that we parsed from preferences and the one we're now adding. But we
+        // only serialize the first four back to preferences, so the oldest one
+        // drops off the end of the list.
+
+        // We store five in case we need to revert changes in the list alter on,
+        // even though only four will be displayed.
+        for (let i = 0; i < 5; i++) {
+            if (i < personas.length)
+                this._prefs.set("lastselected" + i, JSON.stringify(personas[i]));
+            else
+                this._prefs.reset("lastselected" + i);
+        }
+    },
+
+    /**
+     * Removes the most recent persona from the recent list, and leaves the
+     * following four personas as the most recent.
+     */
+    _revertRecent: function() {
+        // Create a new list of recent personas, removing the first one, but
+        // including the 5th (hidden) one.
+        let personas = this.getRecentPersonas(5);
+        personas.shift();
+
+        // Serialize the list of recent personas.
+        for (let i = 0; i < 5; i++) {
+            if (i < personas.length)
+                this._prefs.set("lastselected" + i, JSON.stringify(personas[i]));
+            else
+                this._prefs.reset("lastselected" + i);
+        }
+    },
+
+    onUseColorChanged: function() {
+        // Notify observers that the persona has changed so the change in whether
+        // or not to use the text or accent color will get applied.  The persona
+        // hasn't really changed, but doing this has the desired effect without any
+        // known unwanted side effects.
+        Observers.notify("personas:persona:changed");
+    },
+
+    previewingPersona: null,
+
+    /**
+     * Display the given persona temporarily.  Useful for showing users who are
+     * browsing the directory of personas what a given persona will look like
+     * when selected, f.e. on mouseover.  Consumers who call this method should
+     * call resetPersona when the preview ends, f.e. on mouseout.
+     */
+    previewPersona: function(persona) {
+        if (LightweightThemeManager)
+            LightweightThemeManager.previewTheme(persona);
+        else {
+            this.previewingPersona = persona;
+            Observers.notify("personas:persona:changed");
+        }
+    },
+
+    /**
+     * Stop previewing a persona.
+     */
+    resetPersona: function() {
+        if (LightweightThemeManager)
+            LightweightThemeManager.resetPreview();
+        else {
+            this.previewingPersona = null;
+            Observers.notify("personas:persona:changed");
+        }
+    },
+
+    /**
+     * Gets the persona specified by the initial_persona cookie.
+     */
+    _getPersonaFromCookie: function() {
+        let authorizedHosts = this._prefs.get("authorizedHosts").split(/[, ]+/);
+        let cookieManager =
+            Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+        let cookieEnu = cookieManager.enumerator;
+        let selectedCookie = null;
+
+        while (cookieEnu.hasMoreElements()) {
+            let cookie = cookieEnu.getNext().QueryInterface(Ci.nsICookie);
+
+            // XXX: Remove the initial dot from the host name, if any, before it is
+            // compared against the authorized hosts. This fixes the bug reported in
+            // bug 492392 that getpersonas.com and www.getpersonas.com cookies
+            // imported from IE have the cookie host .getpersonas.com, so they didn't
+            // match any of the authorized hosts.
+            let cookieHost = cookie.host.replace(/^\./, "");
+
+            if (cookie.name == COOKIE_INITIAL_PERSONA &&
+                authorizedHosts.some(function(v) v == cookieHost)) {
+
+                // There could be more than one "initial_persona" cookie. The cookie
+                // with latest expiration time is selected.
+                if (null == selectedCookie ||
+                    cookie.expires > selectedCookie.expires) {
+                    selectedCookie = cookie;
+                }
+
+                cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
+            }
+        }
+
+        if (selectedCookie)
+            return JSON.parse(decodeURIComponent(selectedCookie.value));
+
+        return null;
+    },
+
+    //**************************************************************************//
+    // Lightweight Themes - Personas synchronization
+
+    _ltmSyncTimer: null,
+
+    /**
+     * Updates the add-on to reflect the changes from the Tools - Add-ons - Themes
+     * dialog. If a lightweight theme is set, it is also set as the add-on's current
+     * persona. If a regular theme is set, the current persona is set to "default".
+     */
+    onLightweightThemeChanged: function() {
+        let currentTheme = LightweightThemeManager.currentTheme;
+
+        this._ltmSyncTimer.cancel();
+
+        if (currentTheme &&
+            (currentTheme.id != this.currentPersona.id || this.selected == "default"))
+            this.changeToPersona(currentTheme);
+        else if (!currentTheme && this.selected != "default") {
+
+            // XXX: In Firefox 4, a persona change using the LightweightThemeManager causes
+            // two "lightweight-theme-changed" notifications to be fired instead of one:
+            // the first one with a null theme, and the second one with the actual theme.
+            // This causes the extension to get confused, as the first null theme causes
+            // it go change the persona to the default one, losing the "random" setting,
+            // amongst other things. To circumvent this, notifications with a "null" theme
+            // are delayed just a bit to allow the second notification to cancel it. If there's
+            // no second notification, then the null theme is applied.
+            // See: https://bugzilla.mozilla.org/show_bug.cgi?id=631803
+            let t = this;
+            this._ltmSyncTimer.initWithCallback({
+                    notify: function() {
+                        t.changeToDefaultPersona();
+                    }
+                },
+                100,
+                Ci.nsITimer.TYPE_ONE_SHOT);
+        }
+    },
+
+    //**************************************************************************//
+    // Random Rotation
+
+    _rotationTimer: null,
+
+    /**
+     * Checks the current value of the "selected" preference and sets the
+     * rotation timer accordingly.
+     */
+    onSelectedModeChanged: function() {
+        this._rotationTimer.cancel();
+
+        let selectedMode = this.selected;
+
+        if (selectedMode == "random" || selectedMode == "randomFavorite") {
+            let interval = this._prefs.get("rotationInterval") * 1000;
+            let that = this;
+
+            this._rotationTimer.initWithCallback({
+                    notify: function(aTimer) {
+                        that._rotatePersona();
+                    }
+                },
+                interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+
+            this._rotatePersona();
+        }
+    },
+
+    /**
+     * Ensures that cookies are enabled for our current add-ons host, and
+     * prompts the user to enable them if not.
+     */
+    checkCookiesEnabled: function() {
+        const PERM = "cookie";
+        const ALLOW = 1,
+            SESSION_ONLY = 8;
+
+        try {
+            let prefs = new Preferences("network.cookie.");
+            let cookiesEnabled = (prefs.get("alwaysAcceptSessionCookies") ||
+                prefs.get("cookieBehavior") != 2);
+            let uri = Services.io.newURI(this.getURL("favorites-feed"), null, null);
+
+            let _ = this._strings.get.bind(this._strings);
+            if (cookiesEnabled || ~[ALLOW, SESSION_ONLY].indexOf(Services.perms.testPermission(uri, PERM))) {
+                this._prefs.reset("naggedAboutCookies");
+            } else {
+                if (this._prefs.get("naggedAboutCookies") == this.addonsHost)
+                    return;
+
+                let stopNagging = {
+                    value: true
+                };
+                let enableCookies = Services.prompt.confirmCheck(
+                    null, _("cookies.confirm.title"),
+                    _("cookies.confirm.message", [this.addonsHost]),
+                    _("cookies.confirm.checkbox"),
+                    stopNagging);
+
+                if (enableCookies) {
+                    Services.perms.remove(uri.host, PERM);
+                    Services.perms.add(uri, PERM, ALLOW);
+                } else if (stopNagging.value) {
+                    this._prefs.set("naggedAboutCookies", this.addonsHost);
+                }
+            }
+        } catch (e) {
+            Cu.reportError(e);
+        }
+    },
+
+    /**
+     * Handles the addons-host preference being changed.
+     */
+    onAddonsHostChanged: function() {
+        this.addonsHost = this._prefs.get("addons-host");
+        this.checkCookiesEnabled()
         this.refreshFavorites();
-      }
-    }
-    else if (aCookie instanceof Ci.nsIArray) {
-      for (let enum_ = aCookie.enumerate(); enum_.hasMoreElements();)
-        this.onCookieChanged(enum_.getNext());
+    },
+
+    /**
+     * Changes the current persona to a random persona of the same category (while
+     * in "random" mode) or a random persona from he favorite list (while in
+     * "randomFavorite" mode).
+     */
+    _rotatePersona: function() {
+        switch (this.selected) {
+            case "random":
+                this.changeToRandomPersona(this.category);
+                break;
+            case "randomFavorite":
+                this.changeToRandomFavoritePersona();
+                break;
+        }
+    },
+
+    //**************************************************************************//
+    // Persona Images Caching
+
+    /**
+     * Caches the header and footer images of the given persona inside the
+     * directory [profile]/personas/cache/[persona.id]. It removes all other
+     * existing persona directories before doing so.
+     * @param aPersona The persona for which to cache the images.
+     */
+    _cachePersonaImages: function(aPersona) {
+        let cacheDirectory =
+            FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
+
+        // Remove all other subdirectories in the cache directory
+        // XXX: In the future, if we want to keep more than one persona cached
+        // this step would be removed.
+        let subdirs = FileUtils.getDirectoryEntries(cacheDirectory);
+        for (let i = 0; i < subdirs.length; i++) {
+            if (subdirs[i].isDirectory())
+                subdirs[i].remove(true);
+        }
+
+        // Create directory for the given persona
+        let personaDir = FileUtils.getDirectory(cacheDirectory, aPersona.id);
+
+        // Save header if specified.
+        let header = aPersona.headerURL || aPersona.header;
+        if (header) {
+            // The header can be a base64 string or a malformed URL, in which case
+            // the error can be safely ignored.
+            try {
+                let headerURI = URI.get(header, null, null)
+                    .QueryInterface(Ci.nsIURL);
+
+                let headerCallback = function(aEvent) {
+                    let request = aEvent.target;
+                    // Save only if the folder still exists (Could have been deleted already)
+                    if (request.status == 200 && personaDir.exists()) {
+                        FileUtils.writeBinaryFile(
+                            personaDir.clone(),
+                            "header." + headerURI.fileExtension,
+                            new Uint8Array(request.response));
+                    }
+                };
+                this._makeRequest(headerURI.spec, headerCallback, null, true, true, {
+                    responseType: "arraybuffer"
+                });
+            } catch (e) {}
+        }
+
+        // Save footer if specified.
+        let footer = aPersona.footerURL || aPersona.footer;
+        if (footer) {
+            // The footer can be a base64 string or a malformed URL, in which case
+            // the error can be safely ignored.
+            try {
+                let footerURI = URI.get(footer, null, null)
+                    .QueryInterface(Ci.nsIURL);
+                let footerCallback = function(aEvent) {
+                    let request = aEvent.target;
+                    // Save only if the folder still exists (Could have been deleted already)
+                    if (request.status == 200 && personaDir.exists()) {
+                        FileUtils.writeBinaryFile(
+                            personaDir.clone(),
+                            "footer." + footerURI.fileExtension,
+                            new Uint8Array(request.response));
+                    }
+                };
+                this._makeRequest(footerURI.spec, footerCallback, null, true, true, {
+                    responseType: "arraybuffer"
+                });
+            } catch (e) {}
+        }
+    },
+
+    /**
+     * Obtains the cached images of the given persona. This are stored in the
+     * _cachePersonaImages method under the directory
+     * [profile]/personas/cache/[persona.id].
+     * @param aPersona The persona for which to look the cached images.
+     * @return An object with "header" and "footer" properties, each containing
+     * the file URL of the image. Null otherwise.
+     */
+    getCachedPersonaImages: function(aPersona) {
+        let cacheDirectory =
+            FileUtils.getDirectory(FileUtils.getPersonasDirectory(), "cache");
+
+        let personaDir = FileUtils.getDirectory(cacheDirectory, aPersona.id, true);
+        if (personaDir.exists()) {
+
+            let headerFile = personaDir.clone();
+            let footerFile = personaDir.clone();
+
+            let headerFileExtension =
+                URI.get(aPersona.headerURL || aPersona.header, null, null)
+                .QueryInterface(Ci.nsIURL).fileExtension;
+
+            let footerFileExtension =
+                URI.get(aPersona.footerURL || aPersona.footer, null, null)
+                .QueryInterface(Ci.nsIURL).fileExtension;
+
+            headerFile.append("header" + "." + headerFileExtension);
+            footerFile.append("footer" + "." + footerFileExtension);
+
+            if (headerFile.exists() && footerFile.exists()) {
+                let ios =
+                    Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+                let headerURI = ios.newFileURI(headerFile);
+                let footerURI = ios.newFileURI(footerFile);
+
+                return {
+                    header: headerURI.spec,
+                    footer: footerURI.spec
+                };
+            }
+        }
+        return null;
+    },
+
+    /**
+     * Monitors changes in cookies. If the modified cookie is the Personas session
+     * cookie, then the favorites are refreshed (if the user is signed in).
+     * @param aCookie The cookie that has been added, changed or removed.
+     */
+    onCookieChanged: function(aCookie, aChange) {
+        if (aCookie instanceof Ci.nsICookie) {
+            if (aCookie.name == "sessionid" && (aCookie.host == this.addonsHost ||
+                    aCookie.host == "." + this.addonsHost)) {
+                if (aCookie.value == this._cookieValue)
+                    return;
+                this._cookieValue = aCookie.value;
+                this.refreshFavorites();
+            }
+        } else if (aCookie instanceof Ci.nsIArray) {
+            for (let enum_ = aCookie.enumerate(); enum_.hasMoreElements();)
+                this.onCookieChanged(enum_.getNext());
+        }
+    },
+    _cookieValue: null,
+
+    /**
+     * Monitors HTTP responses so that we can heuristically detect logins
+     * and logouts.
+     * @param aRequest The HTTP request
+     */
+    onHTTPResponse: function(aRequest) {
+        aRequest.QueryInterface(Ci.nsIHttpChannel);
+
+        let uri = aRequest.URI.QueryInterface(Ci.nsIURL);
+        if (uri.host != this.addonsHost)
+            return;
+
+        if (endsWith(uri.filePath, "/users/logout") || aRequest.requestMethod == "POST" && endsWith(uri.filePath, "/users/login"))
+            this.refreshFavorites();
+    },
+
+    onQuitApplication: function() {
+        Observers.remove("quit-application", this.onQuitApplication, this);
+        this._destroy();
     }
-  },
-  _cookieValue: null,
-
-  /**
-   * Monitors HTTP responses so that we can heuristically detect logins
-   * and logouts.
-   * @param aRequest The HTTP request
-   */
-  onHTTPResponse : function(aRequest) {
-    aRequest.QueryInterface(Ci.nsIHttpChannel);
-
-    let uri = aRequest.URI.QueryInterface(Ci.nsIURL);
-    if (uri.host != this.addonsHost)
-      return;
-
-    if (endsWith(uri.filePath, "/users/logout")
-        || aRequest.requestMethod == "POST" && endsWith(uri.filePath, "/users/login"))
-      this.refreshFavorites();
-  },
-
-  onQuitApplication: function() {
-    Observers.remove("quit-application", this.onQuitApplication, this);
-    this._destroy();
-  }
 };
 
 let DateUtils = {
-  /**
-   * Returns the number as a string with a 0 prepended to it if it contains
-   * only one digit, for formats like ISO 8601 that require two digit month,
-   * day, hour, minute, and second values (f.e. so midnight on January 1, 2009
-   * becomes 2009:01:01T00:00:00Z instead of 2009:1:1T0:0:0Z, which would be
-   * invalid).
-   */
-  _pad: function(number) {
-    return (number >= 0 && number <= 9) ? "0" + number : "" + number;
-  },
-
-  /**
-   * Format a date per ISO 8601, in particular the subset described in
-   * http://www.w3.org/TR/NOTE-datetime, which is recommended for date
-   * interchange on the internet.
-   *
-   * Example: 1994-11-06T08:49:37Z
-   *
-   * @param   date  {Date}    the date to format
-   * @returns       {String}  the date formatted per ISO 8601
-   */
-  toISO8601: function(date) {
-    let year = date.getUTCFullYear();
-    let month = this._pad(date.getUTCMonth() + 1);
-    let day = this._pad(date.getUTCDate());
-    let hours = this._pad(date.getUTCHours());
-    let minutes = this._pad(date.getUTCMinutes());
-    let seconds = this._pad(date.getUTCSeconds());
-    return year + "-" + month + "-" + day + "T" +
-           hours + ":" + minutes + ":" + seconds + "Z";
-  },
-
-  /**
-   * Format a date per RFC 1123, which is the standard for HTTP headers.
-   *
-   * Example: Sun, 06 Nov 1994 08:49:37 GMT
-   *
-   * I'd love to use Datejs here, but its Date::toString formatting method
-   * doesn't convert dates to their UTC equivalents before formatting them,
-   * resulting in incorrect output (since RFC 1123 requires dates to be
-   * in UTC), so instead I roll my own.
-   *
-   * @param   date  {Date}    the date to format
-   * @returns       {String}  the date formatted per RFC 1123
-   */
-  toRFC1123: function(date) {
-    let dayOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][date.getUTCDay()];
-    let day = this._pad(date.getUTCDate());
-    let month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getUTCMonth()];
-    let year = date.getUTCFullYear();
-    let hours = this._pad(date.getUTCHours());
-    let minutes = this._pad(date.getUTCMinutes());
-    let seconds = this._pad(date.getUTCSeconds());
-    return dayOfWeek + ", " + day + " " + month + " " + year + " " +
-           hours + ":" + minutes + ":" + seconds + " GMT";
-  }
+    /**
+     * Returns the number as a string with a 0 prepended to it if it contains
+     * only one digit, for formats like ISO 8601 that require two digit month,
+     * day, hour, minute, and second values (f.e. so midnight on January 1, 2009
+     * becomes 2009:01:01T00:00:00Z instead of 2009:1:1T0:0:0Z, which would be
+     * invalid).
+     */
+    _pad: function(number) {
+        return (number >= 0 && number <= 9) ? "0" + number : "" + number;
+    },
+
+    /**
+     * Format a date per ISO 8601, in particular the subset described in
+     * http://www.w3.org/TR/NOTE-datetime, which is recommended for date
+     * interchange on the internet.
+     *
+     * Example: 1994-11-06T08:49:37Z
+     *
+     * @param   date  {Date}    the date to format
+     * @returns       {String}  the date formatted per ISO 8601
+     */
+    toISO8601: function(date) {
+        let year = date.getUTCFullYear();
+        let month = this._pad(date.getUTCMonth() + 1);
+        let day = this._pad(date.getUTCDate());
+        let hours = this._pad(date.getUTCHours());
+        let minutes = this._pad(date.getUTCMinutes());
+        let seconds = this._pad(date.getUTCSeconds());
+        return year + "-" + month + "-" + day + "T" +
+            hours + ":" + minutes + ":" + seconds + "Z";
+    },
+
+    /**
+     * Format a date per RFC 1123, which is the standard for HTTP headers.
+     *
+     * Example: Sun, 06 Nov 1994 08:49:37 GMT
+     *
+     * I'd love to use Datejs here, but its Date::toString formatting method
+     * doesn't convert dates to their UTC equivalents before formatting them,
+     * resulting in incorrect output (since RFC 1123 requires dates to be
+     * in UTC), so instead I roll my own.
+     *
+     * @param   date  {Date}    the date to format
+     * @returns       {String}  the date formatted per RFC 1123
+     */
+    toRFC1123: function(date) {
+        let dayOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][date.getUTCDay()];
+        let day = this._pad(date.getUTCDate());
+        let month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getUTCMonth()];
+        let year = date.getUTCFullYear();
+        let hours = this._pad(date.getUTCHours());
+        let minutes = this._pad(date.getUTCMinutes());
+        let seconds = this._pad(date.getUTCSeconds());
+        return dayOfWeek + ", " + day + " " + month + " " + year + " " +
+            hours + ":" + minutes + ":" + seconds + " GMT";
+    }
 };
 
 let FileUtils = {
-  /**
-   * Gets the [profile]/personas directory.
-   * @return The reference to the personas directory (nsIFile).
-   */
-  getPersonasDirectory : function() {
-    let directoryService =
-      Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
-    let dir = directoryService.get("ProfD", Ci.nsIFile);
-
-    return this.getDirectory(dir, "personas");
-  },
-
-  /**
-   * Gets a reference to a directory (nsIFile) specified by the given name,
-   * located inside the given parent directory.
-   * @param aParentDirectory The parent directory of the directory to obtain.
-   * @param aDirectoryName The name of the directory to obtain.
-   * @param aDontCreate (Optional) Whether or not to create the directory if it
-   * does not exist.
-   * @return The reference to the directory (nsIFile).
-   */
-  getDirectory : function(aParentDirectory, aDirectoryName, aDontCreate) {
-    let dir = aParentDirectory.clone();
-    try {
-      dir.append(aDirectoryName);
-      if (!dir.exists() || !dir.isDirectory()) {
-        if (!aDontCreate) {
-          // read and write permissions to owner and group, read-only for others.
-          dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0774);
+    /**
+     * Gets the [profile]/personas directory.
+     * @return The reference to the personas directory (nsIFile).
+     */
+    getPersonasDirectory: function() {
+        let directoryService =
+            Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+        let dir = directoryService.get("ProfD", Ci.nsIFile);
+
+        return this.getDirectory(dir, "personas");
+    },
+
+    /**
+     * Gets a reference to a directory (nsIFile) specified by the given name,
+     * located inside the given parent directory.
+     * @param aParentDirectory The parent directory of the directory to obtain.
+     * @param aDirectoryName The name of the directory to obtain.
+     * @param aDontCreate (Optional) Whether or not to create the directory if it
+     * does not exist.
+     * @return The reference to the directory (nsIFile).
+     */
+    getDirectory: function(aParentDirectory, aDirectoryName, aDontCreate) {
+        let dir = aParentDirectory.clone();
+        try {
+            dir.append(aDirectoryName);
+            if (!dir.exists() || !dir.isDirectory()) {
+                if (!aDontCreate) {
+                    // read and write permissions to owner and group, read-only for others.
+                    dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0774);
+                }
+            }
+        } catch (ex) {
+            Cu.reportError("Could not create '" + aDirectoryName + "' directory");
+            dir = null;
         }
-      }
-    }
-    catch (ex) {
-      Cu.reportError("Could not create '" + aDirectoryName + "' directory");
-      dir = null;
-    }
-    return dir;
-  },
-
-  /**
-   * Gets an array of the entries (nsIFile) found in the given directory.
-   * @param aDirectory The directory from which to obtain the entries.
-   * @return The array of entries.
-   */
-  getDirectoryEntries : function(aDirectory) {
-    let entries = [];
-    try {
-      let enu = aDirectory.directoryEntries;
-      while (enu.hasMoreElements()) {
-        let entry = enu.getNext().QueryInterface(Ci.nsIFile);
-        entries.push(entry);
-      }
-    }
-    catch (ex) {
-      Cu.reportError("Could not read entries of directory");
-    }
-    return entries;
-  },
-
-  /**
-   * Reads the contents of a text file located at the given directory.
-   * @param aDirectory The directory in which the file is read from (nsIFile)
-   * @param aFileName The name of the file to be read.
-   * @return The contents of the file (string), if any.
-   */
-  readFile : function(aDirectory, aFileName) {
-    let data = "";
-
-    try {
-      let file = aDirectory.clone();
-      file.append(aFileName);
-
-      if (file.exists()) {
-        let fstream =
-          Cc["@mozilla.org/network/file-input-stream;1"].
-            createInstance(Ci.nsIFileInputStream);
-        fstream.init(file, -1, 0, 0);
-
-        let cstream =
-          Cc["@mozilla.org/intl/converter-input-stream;1"].
-            createInstance(Ci.nsIConverterInputStream);
-        cstream.init(fstream, "UTF-8", 0, 0);
-
-        let str = {};
-        // read the whole file
-        while (cstream.readString(-1, str))
-          data += str.value;
-        cstream.close(); // this also closes fstream
-      }
-    }
-    catch (ex) {
-      Cu.reportError("Could not read file " + aFileName);
-    }
+        return dir;
+    },
+
+    /**
+     * Gets an array of the entries (nsIFile) found in the given directory.
+     * @param aDirectory The directory from which to obtain the entries.
+     * @return The array of entries.
+     */
+    getDirectoryEntries: function(aDirectory) {
+        let entries = [];
+        try {
+            let enu = aDirectory.directoryEntries;
+            while (enu.hasMoreElements()) {
+                let entry = enu.getNext().QueryInterface(Ci.nsIFile);
+                entries.push(entry);
+            }
+        } catch (ex) {
+            Cu.reportError("Could not read entries of directory");
+        }
+        return entries;
+    },
+
+    /**
+     * Reads the contents of a text file located at the given directory.
+     * @param aDirectory The directory in which the file is read from (nsIFile)
+     * @param aFileName The name of the file to be read.
+     * @return The contents of the file (string), if any.
+     */
+    readFile: function(aDirectory, aFileName) {
+        let data = "";
 
-    return data;
-  },
-
-  /**
-   * Writes a text file in the given directory. If the file already exists it is
-   * overwritten.
-   * @param aDirectory The directory in which the file will be written (nsIFile).
-   * @param aFileName The name of the file to be written.
-   * @param aData The contents of the file.
-   */
-  writeFile : function(aDirectory, aFileName, aData) {
-    try {
-      let file = aDirectory.clone();
-      file.append(aFileName);
-
-      let foStream =
-        Cc["@mozilla.org/network/file-output-stream;1"].
-          createInstance(Ci.nsIFileOutputStream);
-      // flags are write, create, truncate
-      foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
-
-      let converter =
-        Cc["@mozilla.org/intl/converter-output-stream;1"].
-          createInstance(Ci.nsIConverterOutputStream);
-      converter.init(foStream, "UTF-8", 0, 0);
-      converter.writeString(aData);
-      converter.close(); // this also closes foStream
-    }
-    catch (ex) {
-      Cu.reportError("Could not write file " + aFileName);
-    }
-  },
-
-  /**
-   * Writes a binary file in the given directory. If the file already exists it
-   * is overwritten.
-   * @param aDirectory The directory in which the file will be written (nsIFile).
-   * @param aFileName The name of the file to be written.
-   * @param aData The binary contents of the file.
-   */
-  writeBinaryFile : function(aDirectory, aFileName, aData) {
-    try {
-      let file = aDirectory.clone();
-      file.append(aFileName);
-
-      let stream =
-        Cc["@mozilla.org/network/safe-file-output-stream;1"].
-          createInstance(Ci.nsIFileOutputStream);
-      // Flags are: write, create, truncate
-      stream.init(file, 0x04 | 0x08 | 0x20, 0600, 0);
-
-      stream.write(aData, aData.length);
-      if (stream instanceof Ci.nsISafeOutputStream)
-        stream.finish();
-      else
-        stream.close();
-    }
-    catch (ex) {
-      Cu.reportError("Could not write binary file " + aFileName);
+        try {
+            let file = aDirectory.clone();
+            file.append(aFileName);
+
+            if (file.exists()) {
+                let fstream =
+                    Cc["@mozilla.org/network/file-input-stream;1"].
+                createInstance(Ci.nsIFileInputStream);
+                fstream.init(file, -1, 0, 0);
+
+                let cstream =
+                    Cc["@mozilla.org/intl/converter-input-stream;1"].
+                createInstance(Ci.nsIConverterInputStream);
+                cstream.init(fstream, "UTF-8", 0, 0);
+
+                let str = {};
+                // read the whole file
+                while (cstream.readString(-1, str))
+                    data += str.value;
+                cstream.close(); // this also closes fstream
+            }
+        } catch (ex) {
+            Cu.reportError("Could not read file " + aFileName);
+        }
+
+        return data;
+    },
+
+    /**
+     * Writes a text file in the given directory. If the file already exists it is
+     * overwritten.
+     * @param aDirectory The directory in which the file will be written (nsIFile).
+     * @param aFileName The name of the file to be written.
+     * @param aData The contents of the file.
+     */
+    writeFile: function(aDirectory, aFileName, aData) {
+        try {
+            let file = aDirectory.clone();
+            file.append(aFileName);
+
+            return OS.File.writeAtomic(file.path, aData, {
+                tmpPath: file.path + ".tmp"
+            });
+        } catch (ex) {
+            Cu.reportError("Could not write file " + aFileName);
+        }
+    },
+
+    /**
+     * Writes a binary file in the given directory. If the file already exists it
+     * is overwritten.
+     * @param aDirectory The directory in which the file will be written (nsIFile).
+     * @param aFileName The name of the file to be written.
+     * @param aData The binary contents of the file.
+     */
+    writeBinaryFile: function(aDirectory, aFileName, aData) {
+        try {
+            let file = aDirectory.clone();
+            file.append(aFileName);
+
+            OS.File.writeAtomic(file.path, aData, {
+                tmpPath: file.path + ".tmp"
+            });
+        } catch (ex) {
+            Cu.reportError("Could not write binary file " + aFileName);
+        }
     }
-  }
 };
 
-PersonaService._init();
+try {
+    PersonaService._init();
+} catch (e) {
+    Cu.reportError(e);
+}
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/personasplus.git



More information about the Pkg-mozext-commits mailing list