toolkit/mozapps/extensions/LightweightThemeManager.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 this.EXPORTED_SYMBOLS = ["LightweightThemeManager"];
     9 const Cc = Components.classes;
    10 const Ci = Components.interfaces;
    12 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    13 Components.utils.import("resource://gre/modules/AddonManager.jsm");
    14 Components.utils.import("resource://gre/modules/Services.jsm");
    16 const ID_SUFFIX              = "@personas.mozilla.org";
    17 const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect";
    18 const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
    19 const PREF_EM_DSS_ENABLED    = "extensions.dss.enabled";
    20 const ADDON_TYPE             = "theme";
    22 const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
    24 const STRING_TYPE_NAME       = "type.%ID%.name";
    26 const DEFAULT_MAX_USED_THEMES_COUNT = 30;
    28 const MAX_PREVIEW_SECONDS = 30;
    30 const MANDATORY = ["id", "name", "headerURL"];
    31 const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL",
    32                   "previewURL", "author", "description", "homepageURL",
    33                   "updateURL", "version"];
    35 const PERSIST_ENABLED = true;
    36 const PERSIST_BYPASS_CACHE = false;
    37 const PERSIST_FILES = {
    38   headerURL: "lightweighttheme-header",
    39   footerURL: "lightweighttheme-footer"
    40 };
    42 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer",
    43   "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
    45 this.__defineGetter__("_prefs", function prefsGetter() {
    46   delete this._prefs;
    47   return this._prefs = Services.prefs.getBranch("lightweightThemes.");
    48 });
    50 this.__defineGetter__("_maxUsedThemes", function maxUsedThemesGetter() {
    51   delete this._maxUsedThemes;
    52   try {
    53     this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes");
    54   }
    55   catch (e) {
    56     this._maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT;
    57   }
    58   return this._maxUsedThemes;
    59 });
    61 this.__defineSetter__("_maxUsedThemes", function maxUsedThemesSetter(aVal) {
    62   delete this._maxUsedThemes;
    63   return this._maxUsedThemes = aVal;
    64 });
    66 // Holds the ID of the theme being enabled or disabled while sending out the
    67 // events so cached AddonWrapper instances can return correct values for
    68 // permissions and pendingOperations
    69 var _themeIDBeingEnabled = null;
    70 var _themeIDBeingDisabled = null;
    72 this.LightweightThemeManager = {
    73   get usedThemes () {
    74     try {
    75       return JSON.parse(_prefs.getComplexValue("usedThemes",
    76                                                Ci.nsISupportsString).data);
    77     } catch (e) {
    78       return [];
    79     }
    80   },
    82   get currentTheme () {
    83     try {
    84       if (_prefs.getBoolPref("isThemeSelected"))
    85         var data = this.usedThemes[0];
    86     } catch (e) {}
    88     return data || null;
    89   },
    91   get currentThemeForDisplay () {
    92     var data = this.currentTheme;
    94     if (data && PERSIST_ENABLED) {
    95       for (let key in PERSIST_FILES) {
    96         try {
    97           if (data[key] && _prefs.getBoolPref("persisted." + key))
    98             data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec
    99                         + "?" + data.id + ";" + _version(data);
   100         } catch (e) {}
   101       }
   102     }
   104     return data;
   105   },
   107   set currentTheme (aData) {
   108     return _setCurrentTheme(aData, false);
   109   },
   111   setLocalTheme: function LightweightThemeManager_setLocalTheme(aData) {
   112     _setCurrentTheme(aData, true);
   113   },
   115   getUsedTheme: function LightweightThemeManager_getUsedTheme(aId) {
   116     var usedThemes = this.usedThemes;
   117     for (let usedTheme of usedThemes) {
   118       if (usedTheme.id == aId)
   119         return usedTheme;
   120     }
   121     return null;
   122   },
   124   forgetUsedTheme: function LightweightThemeManager_forgetUsedTheme(aId) {
   125     let theme = this.getUsedTheme(aId);
   126     if (!theme)
   127       return;
   129     let wrapper = new AddonWrapper(theme);
   130     AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
   132     var currentTheme = this.currentTheme;
   133     if (currentTheme && currentTheme.id == aId) {
   134       this.themeChanged(null);
   135       AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false);
   136     }
   138     _updateUsedThemes(_usedThemesExceptId(aId));
   139     AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
   140   },
   142   previewTheme: function LightweightThemeManager_previewTheme(aData) {
   143     if (!aData)
   144       return;
   146     let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
   147     cancel.data = false;
   148     Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested",
   149                                  JSON.stringify(aData));
   150     if (cancel.data)
   151       return;
   153     if (_previewTimer)
   154       _previewTimer.cancel();
   155     else
   156       _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   157     _previewTimer.initWithCallback(_previewTimerCallback,
   158                                    MAX_PREVIEW_SECONDS * 1000,
   159                                    _previewTimer.TYPE_ONE_SHOT);
   161     _notifyWindows(aData);
   162   },
   164   resetPreview: function LightweightThemeManager_resetPreview() {
   165     if (_previewTimer) {
   166       _previewTimer.cancel();
   167       _previewTimer = null;
   168       _notifyWindows(this.currentThemeForDisplay);
   169     }
   170   },
   172   parseTheme: function LightweightThemeManager_parseTheme(aString, aBaseURI) {
   173     try {
   174       return _sanitizeTheme(JSON.parse(aString), aBaseURI, false);
   175     } catch (e) {
   176       return null;
   177     }
   178   },
   180   updateCurrentTheme: function LightweightThemeManager_updateCurrentTheme() {
   181     try {
   182       if (!_prefs.getBoolPref("update.enabled"))
   183         return;
   184     } catch (e) {
   185       return;
   186     }
   188     var theme = this.currentTheme;
   189     if (!theme || !theme.updateURL)
   190       return;
   192     var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
   193                 .createInstance(Ci.nsIXMLHttpRequest);
   195     req.mozBackgroundRequest = true;
   196     req.overrideMimeType("text/plain");
   197     req.open("GET", theme.updateURL, true);
   198     // Prevent the request from reading from the cache.
   199     req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
   200     // Prevent the request from writing to the cache.
   201     req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
   203     var self = this;
   204     req.addEventListener("load", function loadEventListener() {
   205       if (req.status != 200)
   206         return;
   208       let newData = self.parseTheme(req.responseText, theme.updateURL);
   209       if (!newData ||
   210           newData.id != theme.id ||
   211           _version(newData) == _version(theme))
   212         return;
   214       var currentTheme = self.currentTheme;
   215       if (currentTheme && currentTheme.id == theme.id)
   216         self.currentTheme = newData;
   217     }, false);
   219     req.send(null);
   220   },
   222   /**
   223    * Switches to a new lightweight theme.
   224    *
   225    * @param  aData
   226    *         The lightweight theme to switch to
   227    */
   228   themeChanged: function LightweightThemeManager_themeChanged(aData) {
   229     if (_previewTimer) {
   230       _previewTimer.cancel();
   231       _previewTimer = null;
   232     }
   234     if (aData) {
   235       let usedThemes = _usedThemesExceptId(aData.id);
   236       usedThemes.unshift(aData);
   237       _updateUsedThemes(usedThemes);
   238       if (PERSIST_ENABLED) {
   239         LightweightThemeImageOptimizer.purge();
   240         _persistImages(aData, function themeChanged_persistImages() {
   241           _notifyWindows(this.currentThemeForDisplay);
   242         }.bind(this));
   243       }
   244     }
   246     _prefs.setBoolPref("isThemeSelected", aData != null);
   247     _notifyWindows(aData);
   248     Services.obs.notifyObservers(null, "lightweight-theme-changed", null);
   249   },
   251   /**
   252    * Starts the Addons provider and enables the new lightweight theme if
   253    * necessary.
   254    */
   255   startup: function LightweightThemeManager_startup() {
   256     if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
   257       let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
   258       if (id)
   259         this.themeChanged(this.getUsedTheme(id));
   260       else
   261         this.themeChanged(null);
   262       Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
   263     }
   265     _prefs.addObserver("", _prefObserver, false);
   266   },
   268   /**
   269    * Shuts down the provider.
   270    */
   271   shutdown: function LightweightThemeManager_shutdown() {
   272     _prefs.removeObserver("", _prefObserver);
   273   },
   275   /**
   276    * Called when a new add-on has been enabled when only one add-on of that type
   277    * can be enabled.
   278    *
   279    * @param  aId
   280    *         The ID of the newly enabled add-on
   281    * @param  aType
   282    *         The type of the newly enabled add-on
   283    * @param  aPendingRestart
   284    *         true if the newly enabled add-on will only become enabled after a
   285    *         restart
   286    */
   287   addonChanged: function LightweightThemeManager_addonChanged(aId, aType, aPendingRestart) {
   288     if (aType != ADDON_TYPE)
   289       return;
   291     let id = _getInternalID(aId);
   292     let current = this.currentTheme;
   294     try {
   295       let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
   296       if (id == next && aPendingRestart)
   297         return;
   299       Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
   300       if (next) {
   301         AddonManagerPrivate.callAddonListeners("onOperationCancelled",
   302                                                new AddonWrapper(this.getUsedTheme(next)));
   303       }
   304       else {
   305         if (id == current.id) {
   306           AddonManagerPrivate.callAddonListeners("onOperationCancelled",
   307                                                  new AddonWrapper(current));
   308           return;
   309         }
   310       }
   311     }
   312     catch (e) {
   313     }
   315     if (current) {
   316       if (current.id == id)
   317         return;
   318       _themeIDBeingDisabled = current.id;
   319       let wrapper = new AddonWrapper(current);
   320       if (aPendingRestart) {
   321         Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, "");
   322         AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true);
   323       }
   324       else {
   325         AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false);
   326         this.themeChanged(null);
   327         AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
   328       }
   329       _themeIDBeingDisabled = null;
   330     }
   332     if (id) {
   333       let theme = this.getUsedTheme(id);
   334       _themeIDBeingEnabled = id;
   335       let wrapper = new AddonWrapper(theme);
   336       if (aPendingRestart) {
   337         AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true);
   338         Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id);
   340         // Flush the preferences to disk so they survive any crash
   341         Services.prefs.savePrefFile(null);
   342       }
   343       else {
   344         AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false);
   345         this.themeChanged(theme);
   346         AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
   347       }
   348       _themeIDBeingEnabled = null;
   349     }
   350   },
   352   /**
   353    * Called to get an Addon with a particular ID.
   354    *
   355    * @param  aId
   356    *         The ID of the add-on to retrieve
   357    * @param  aCallback
   358    *         A callback to pass the Addon to
   359    */
   360   getAddonByID: function LightweightThemeManager_getAddonByID(aId, aCallback) {
   361     let id = _getInternalID(aId);
   362     if (!id) {
   363       aCallback(null);
   364       return;
   365      }
   367     let theme = this.getUsedTheme(id);
   368     if (!theme) {
   369       aCallback(null);
   370       return;
   371     }
   373     aCallback(new AddonWrapper(theme));
   374   },
   376   /**
   377    * Called to get Addons of a particular type.
   378    *
   379    * @param  aTypes
   380    *         An array of types to fetch. Can be null to get all types.
   381    * @param  aCallback
   382    *         A callback to pass an array of Addons to
   383    */
   384   getAddonsByTypes: function LightweightThemeManager_getAddonsByTypes(aTypes, aCallback) {
   385     if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) {
   386       aCallback([]);
   387       return;
   388     }
   390     aCallback([new AddonWrapper(a) for each (a in this.usedThemes)]);
   391   },
   392 };
   394 /**
   395  * The AddonWrapper wraps lightweight theme to provide the data visible to
   396  * consumers of the AddonManager API.
   397  */
   398 function AddonWrapper(aTheme) {
   399   this.__defineGetter__("id", function AddonWrapper_idGetter() aTheme.id + ID_SUFFIX);
   400   this.__defineGetter__("type", function AddonWrapper_typeGetter() ADDON_TYPE);
   401   this.__defineGetter__("isActive", function AddonWrapper_isActiveGetter() {
   402     let current = LightweightThemeManager.currentTheme;
   403     if (current)
   404       return aTheme.id == current.id;
   405     return false;
   406   });
   408   this.__defineGetter__("name", function AddonWrapper_nameGetter() aTheme.name);
   409   this.__defineGetter__("version", function AddonWrapper_versionGetter() {
   410     return "version" in aTheme ? aTheme.version : "";
   411   });
   413   ["description", "homepageURL", "iconURL"].forEach(function(prop) {
   414     this.__defineGetter__(prop, function AddonWrapper_optionalPropGetter() {
   415       return prop in aTheme ? aTheme[prop] : null;
   416     });
   417   }, this);
   419   ["installDate", "updateDate"].forEach(function(prop) {
   420     this.__defineGetter__(prop, function AddonWrapper_datePropGetter() {
   421       return prop in aTheme ? new Date(aTheme[prop]) : null;
   422     });
   423   }, this);
   425   this.__defineGetter__("creator", function AddonWrapper_creatorGetter() {
   426     return new AddonManagerPrivate.AddonAuthor(aTheme.author);
   427   });
   429   this.__defineGetter__("screenshots", function AddonWrapper_screenshotsGetter() {
   430     let url = aTheme.previewURL;
   431     return [new AddonManagerPrivate.AddonScreenshot(url)];
   432   });
   434   this.__defineGetter__("pendingOperations",
   435                        function AddonWrapper_pendingOperationsGetter() {
   436     let pending = AddonManager.PENDING_NONE;
   437     if (this.isActive == this.userDisabled)
   438       pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE;
   439     return pending;
   440   });
   442   this.__defineGetter__("operationsRequiringRestart", 
   443                function AddonWrapper_operationsRequiringRestartGetter() {
   444     // If a non-default theme is in use then a restart will be required to
   445     // enable lightweight themes unless dynamic theme switching is enabled
   446     if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
   447       try {
   448         if (Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED))
   449           return AddonManager.OP_NEEDS_RESTART_NONE;
   450       }
   451       catch (e) {
   452       }
   453       return AddonManager.OP_NEEDS_RESTART_ENABLE;
   454     }
   456     return AddonManager.OP_NEEDS_RESTART_NONE;
   457   });
   459   this.__defineGetter__("size", function AddonWrapper_sizeGetter() {
   460     // The size changes depending on whether the theme is in use or not, this is
   461     // probably not worth exposing.
   462     return null;
   463   });
   465   this.__defineGetter__("permissions", function AddonWrapper_permissionsGetter() {
   466     let permissions = AddonManager.PERM_CAN_UNINSTALL;
   467     if (this.userDisabled)
   468       permissions |= AddonManager.PERM_CAN_ENABLE;
   469     else
   470       permissions |= AddonManager.PERM_CAN_DISABLE;
   471     return permissions;
   472   });
   474   this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() {
   475     if (_themeIDBeingEnabled == aTheme.id)
   476       return false;
   477     if (_themeIDBeingDisabled == aTheme.id)
   478       return true;
   480     try {
   481       let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
   482       return aTheme.id != toSelect;
   483     }
   484     catch (e) {
   485       let current = LightweightThemeManager.currentTheme;
   486       return !current || current.id != aTheme.id;
   487     }
   488   });
   490   this.__defineSetter__("userDisabled", function AddonWrapper_userDisabledSetter(val) {
   491     if (val == this.userDisabled)
   492       return val;
   494     if (val)
   495       LightweightThemeManager.currentTheme = null;
   496     else
   497       LightweightThemeManager.currentTheme = aTheme;
   499     return val;
   500   });
   502   this.uninstall = function AddonWrapper_uninstall() {
   503     LightweightThemeManager.forgetUsedTheme(aTheme.id);
   504   };
   506   this.cancelUninstall = function AddonWrapper_cancelUninstall() {
   507     throw new Error("Theme is not marked to be uninstalled");
   508   };
   510   this.findUpdates = function AddonWrapper_findUpdates(listener, reason, appVersion, platformVersion) {
   511     AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion);
   512   };
   513 }
   515 AddonWrapper.prototype = {
   516   // Lightweight themes are never disabled by the application
   517   get appDisabled() {
   518     return false;
   519   },
   521   // Lightweight themes are always compatible
   522   get isCompatible() {
   523     return true;
   524   },
   526   get isPlatformCompatible() {
   527     return true;
   528   },
   530   get scope() {
   531     return AddonManager.SCOPE_PROFILE;
   532   },
   534   get foreignInstall() {
   535     return false;
   536   },
   538   // Lightweight themes are always compatible
   539   isCompatibleWith: function AddonWrapper_isCompatibleWith(appVersion, platformVersion) {
   540     return true;
   541   },
   543   // Lightweight themes are always securely updated
   544   get providesUpdatesSecurely() {
   545     return true;
   546   },
   548   // Lightweight themes are never blocklisted
   549   get blocklistState() {
   550     return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   551   }
   552 };
   554 /**
   555  * Converts the ID used by the public AddonManager API to an lightweight theme
   556  * ID.
   557  *
   558  * @param   id
   559  *          The ID to be converted
   560  *
   561  * @return  the lightweight theme ID or null if the ID was not for a lightweight
   562  *          theme.
   563  */
   564 function _getInternalID(id) {
   565   if (!id)
   566     return null;
   567   let len = id.length - ID_SUFFIX.length;
   568   if (len > 0 && id.substring(len) == ID_SUFFIX)
   569     return id.substring(0, len);
   570   return null;
   571 }
   573 function _setCurrentTheme(aData, aLocal) {
   574   aData = _sanitizeTheme(aData, null, aLocal);
   576   let needsRestart = (ADDON_TYPE == "theme") &&
   577                      Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN);
   579   let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
   580   cancel.data = false;
   581   Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested",
   582                                JSON.stringify(aData));
   584   if (aData) {
   585     let theme = LightweightThemeManager.getUsedTheme(aData.id);
   586     let isInstall = !theme || theme.version != aData.version;
   587     if (isInstall) {
   588       aData.updateDate = Date.now();
   589       if (theme && "installDate" in theme)
   590         aData.installDate = theme.installDate;
   591       else
   592         aData.installDate = aData.updateDate;
   594       var oldWrapper = theme ? new AddonWrapper(theme) : null;
   595       var wrapper = new AddonWrapper(aData);
   596       AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
   597                                                wrapper, oldWrapper, false);
   598       AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false);
   599     }
   601     let current = LightweightThemeManager.currentTheme;
   602     let usedThemes = _usedThemesExceptId(aData.id);
   603     if (current && current.id != aData.id)
   604       usedThemes.splice(1, 0, aData);
   605     else
   606       usedThemes.unshift(aData);
   607     _updateUsedThemes(usedThemes);
   609     if (isInstall)
   610        AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
   611   }
   613   if (cancel.data)
   614     return null;
   616   AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null,
   617                                          ADDON_TYPE, needsRestart);
   619   return LightweightThemeManager.currentTheme;
   620 }
   622 function _sanitizeTheme(aData, aBaseURI, aLocal) {
   623   if (!aData || typeof aData != "object")
   624     return null;
   626   var resourceProtocols = ["http", "https"];
   627   if (aLocal)
   628     resourceProtocols.push("file");
   629   var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):");
   631   function sanitizeProperty(prop) {
   632     if (!(prop in aData))
   633       return null;
   634     if (typeof aData[prop] != "string")
   635       return null;
   636     let val = aData[prop].trim();
   637     if (!val)
   638       return null;
   640     if (!/URL$/.test(prop))
   641       return val;
   643     try {
   644       val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec;
   645       if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val))
   646         return val;
   647       return null;
   648     }
   649     catch (e) {
   650       return null;
   651     }
   652   }
   654   let result = {};
   655   for (let mandatoryProperty of MANDATORY) {
   656     let val = sanitizeProperty(mandatoryProperty);
   657     if (!val)
   658       throw Components.results.NS_ERROR_INVALID_ARG;
   659     result[mandatoryProperty] = val;
   660   }
   662   for (let optionalProperty of OPTIONAL) {
   663     let val = sanitizeProperty(optionalProperty);
   664     if (!val)
   665       continue;
   666     result[optionalProperty] = val;
   667   }
   669   return result;
   670 }
   672 function _usedThemesExceptId(aId)
   673   LightweightThemeManager.usedThemes.filter(
   674        function usedThemesExceptId_filterID(t) "id" in t && t.id != aId);
   676 function _version(aThemeData)
   677   aThemeData.version || "";
   679 function _makeURI(aURL, aBaseURI)
   680   Services.io.newURI(aURL, null, aBaseURI);
   682 function _updateUsedThemes(aList) {
   683   // Send uninstall events for all themes that need to be removed.
   684   while (aList.length > _maxUsedThemes) {
   685     let wrapper = new AddonWrapper(aList[aList.length - 1]);
   686     AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
   687     aList.pop();
   688     AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
   689   }
   691   var str = Cc["@mozilla.org/supports-string;1"]
   692               .createInstance(Ci.nsISupportsString);
   693   str.data = JSON.stringify(aList);
   694   _prefs.setComplexValue("usedThemes", Ci.nsISupportsString, str);
   696   Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null);
   697 }
   699 function _notifyWindows(aThemeData) {
   700   Services.obs.notifyObservers(null, "lightweight-theme-styling-update",
   701                                JSON.stringify(aThemeData));
   702 }
   704 var _previewTimer;
   705 var _previewTimerCallback = {
   706   notify: function _previewTimerCallback_notify() {
   707     LightweightThemeManager.resetPreview();
   708   }
   709 };
   711 /**
   712  * Called when any of the lightweightThemes preferences are changed.
   713  */
   714 function _prefObserver(aSubject, aTopic, aData) {
   715   switch (aData) {
   716     case "maxUsedThemes":
   717       try {
   718         _maxUsedThemes = _prefs.getIntPref(aData);
   719       }
   720       catch (e) {
   721         _maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT;
   722       }
   723       // Update the theme list to remove any themes over the number we keep
   724       _updateUsedThemes(LightweightThemeManager.usedThemes);
   725       break;
   726   }
   727 }
   729 function _persistImages(aData, aCallback) {
   730   function onSuccess(key) function () {
   731     let current = LightweightThemeManager.currentTheme;
   732     if (current && current.id == aData.id) {
   733       _prefs.setBoolPref("persisted." + key, true);
   734     }
   735     if (--numFilesToPersist == 0 && aCallback) {
   736       aCallback();
   737     }
   738   };
   740   let numFilesToPersist = 0;
   741   for (let key in PERSIST_FILES) {
   742     _prefs.setBoolPref("persisted." + key, false);
   743     if (aData[key]) {
   744       numFilesToPersist++;
   745       _persistImage(aData[key], PERSIST_FILES[key], onSuccess(key));
   746     }
   747   }
   748 }
   750 function _getLocalImageURI(localFileName) {
   751   var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
   752   localFile.append(localFileName);
   753   return Services.io.newFileURI(localFile);
   754 }
   756 function _persistImage(sourceURL, localFileName, successCallback) {
   757   if (/^file:/.test(sourceURL))
   758     return;
   760   var targetURI = _getLocalImageURI(localFileName);
   761   var sourceURI = _makeURI(sourceURL);
   763   var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
   764                   .createInstance(Ci.nsIWebBrowserPersist);
   766   persist.persistFlags =
   767     Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
   768     Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION |
   769     (PERSIST_BYPASS_CACHE ?
   770        Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE :
   771        Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE);
   773   persist.progressListener = new _persistProgressListener(successCallback);
   775   persist.saveURI(sourceURI, null, null, null, null, targetURI, null);
   776 }
   778 function _persistProgressListener(successCallback) {
   779   this.onLocationChange = function persistProgressListener_onLocationChange() {};
   780   this.onProgressChange = function persistProgressListener_onProgressChange() {};
   781   this.onStatusChange   = function persistProgressListener_onStatusChange() {};
   782   this.onSecurityChange = function persistProgressListener_onSecurityChange() {};
   783   this.onStateChange    = function persistProgressListener_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
   784     if (aRequest &&
   785         aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
   786         aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
   787       try {
   788         if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) {
   789           // success
   790           successCallback();
   791           return;
   792         }
   793       } catch (e) { }
   794       // failure
   795     }
   796   };
   797 }
   799 AddonManagerPrivate.registerProvider(LightweightThemeManager, [
   800   new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
   801                                     STRING_TYPE_NAME,
   802                                     AddonManager.VIEW_TYPE_LIST, 5000)
   803 ]);

mercurial