gecko-dev/toolkit/mozapps/extensions/LightweightThemeManager.jsm
Dave Townsend f946cdabc4 Bug 1226386: Remove use of non-standard __defineGetter__ and __defineSetter__. r=rhelmer
Moved these mostly onto the prototype. We couldn't do this before without making
the target of the wrapper a property of the wrappers and we don't want to expose
that but now WeakMaps allow us to get the target without exposing it.

Once change with this approach is that when the test suite shuts down the
add-ons manager it kills the map and so wrappers cease to function. A couple of
tests were relying on accessing wrapper properties after that but that would
have likely been unsafe anyway.

--HG--
extra : commitid : 6OI6dLyM45D
extra : rebase_source : c8a53360ce582186dcdc5cbf0dfc2b5057881eac
2015-11-20 10:55:13 -08:00

905 lines
25 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["LightweightThemeManager"];
const Cc = Components.classes;
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/AddonManager.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
const ID_SUFFIX = "@personas.mozilla.org";
const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect";
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
const ADDON_TYPE = "theme";
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const DEFAULT_MAX_USED_THEMES_COUNT = 30;
const MAX_PREVIEW_SECONDS = 30;
const MANDATORY = ["id", "name", "headerURL"];
const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL",
"previewURL", "author", "description", "homepageURL",
"updateURL", "version"];
const PERSIST_ENABLED = true;
const PERSIST_BYPASS_CACHE = false;
const PERSIST_FILES = {
headerURL: "lightweighttheme-header",
footerURL: "lightweighttheme-footer"
};
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer",
"resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
XPCOMUtils.defineLazyGetter(this, "_prefs", () => {
return Services.prefs.getBranch("lightweightThemes.");
});
Object.defineProperty(this, "_maxUsedThemes", {
get: function() {
delete this._maxUsedThemes;
try {
this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes");
}
catch (e) {
this._maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT;
}
return this._maxUsedThemes;
},
set: function(val) {
delete this._maxUsedThemes;
return this._maxUsedThemes = val;
},
configurable: true,
});
// Holds the ID of the theme being enabled or disabled while sending out the
// events so cached AddonWrapper instances can return correct values for
// permissions and pendingOperations
var _themeIDBeingEnabled = null;
var _themeIDBeingDisabled = null;
// Convert from the old storage format (in which the order of usedThemes
// was combined with isThemeSelected to determine which theme was selected)
// to the new one (where a selectedThemeID determines which theme is selected).
(function() {
let wasThemeSelected = false;
try {
wasThemeSelected = _prefs.getBoolPref("isThemeSelected");
} catch(e) { }
if (wasThemeSelected) {
_prefs.clearUserPref("isThemeSelected");
let themes = [];
try {
themes = JSON.parse(_prefs.getComplexValue("usedThemes",
Ci.nsISupportsString).data);
} catch (e) { }
if (Array.isArray(themes) && themes[0]) {
_prefs.setCharPref("selectedThemeID", themes[0].id);
}
}
})();
this.LightweightThemeManager = {
get name() {
return "LightweightThemeManager";
},
// Themes that can be added for an application. They can't be removed, and
// will always show up at the top of the list.
_builtInThemes: new Map(),
get usedThemes () {
let themes = [];
try {
themes = JSON.parse(_prefs.getComplexValue("usedThemes",
Ci.nsISupportsString).data);
} catch (e) { }
themes.push(...this._builtInThemes.values());
return themes;
},
get currentTheme () {
let selectedThemeID = null;
try {
selectedThemeID = _prefs.getCharPref("selectedThemeID");
} catch (e) {}
let data = null;
if (selectedThemeID) {
data = this.getUsedTheme(selectedThemeID);
}
return data;
},
get currentThemeForDisplay () {
var data = this.currentTheme;
if (data && PERSIST_ENABLED) {
for (let key in PERSIST_FILES) {
try {
if (data[key] && _prefs.getBoolPref("persisted." + key))
data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec
+ "?" + data.id + ";" + _version(data);
} catch (e) {}
}
}
return data;
},
set currentTheme (aData) {
return _setCurrentTheme(aData, false);
},
setLocalTheme: function(aData) {
_setCurrentTheme(aData, true);
},
getUsedTheme: function(aId) {
var usedThemes = this.usedThemes;
for (let usedTheme of usedThemes) {
if (usedTheme.id == aId)
return usedTheme;
}
return null;
},
forgetUsedTheme: function(aId) {
let theme = this.getUsedTheme(aId);
if (!theme || LightweightThemeManager._builtInThemes.has(theme.id))
return;
let wrapper = new AddonWrapper(theme);
AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
var currentTheme = this.currentTheme;
if (currentTheme && currentTheme.id == aId) {
this.themeChanged(null);
AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false);
}
_updateUsedThemes(_usedThemesExceptId(aId));
AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
},
addBuiltInTheme: function(theme) {
if (!theme || !theme.id || this.usedThemes.some(t => t.id == theme.id)) {
throw new Error("Trying to add invalid builtIn theme");
}
this._builtInThemes.set(theme.id, theme);
},
forgetBuiltInTheme: function(id) {
if (!this._builtInThemes.has(id)) {
let currentTheme = this.currentTheme;
if (currentTheme && currentTheme.id == id) {
this.currentTheme = null;
}
}
return this._builtInThemes.delete(id);
},
clearBuiltInThemes: function() {
for (let id of this._builtInThemes.keys()) {
this.forgetBuiltInTheme(id);
}
},
previewTheme: function(aData) {
let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
cancel.data = false;
Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested",
JSON.stringify(aData));
if (cancel.data)
return;
if (_previewTimer)
_previewTimer.cancel();
else
_previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
_previewTimer.initWithCallback(_previewTimerCallback,
MAX_PREVIEW_SECONDS * 1000,
_previewTimer.TYPE_ONE_SHOT);
_notifyWindows(aData);
},
resetPreview: function() {
if (_previewTimer) {
_previewTimer.cancel();
_previewTimer = null;
_notifyWindows(this.currentThemeForDisplay);
}
},
parseTheme: function(aString, aBaseURI) {
try {
return _sanitizeTheme(JSON.parse(aString), aBaseURI, false);
} catch (e) {
return null;
}
},
updateCurrentTheme: function() {
try {
if (!_prefs.getBoolPref("update.enabled"))
return;
} catch (e) {
return;
}
var theme = this.currentTheme;
if (!theme || !theme.updateURL)
return;
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
req.mozBackgroundRequest = true;
req.overrideMimeType("text/plain");
req.open("GET", theme.updateURL, true);
// Prevent the request from reading from the cache.
req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// Prevent the request from writing to the cache.
req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
req.addEventListener("load", () => {
if (req.status != 200)
return;
let newData = this.parseTheme(req.responseText, theme.updateURL);
if (!newData ||
newData.id != theme.id ||
_version(newData) == _version(theme))
return;
var currentTheme = this.currentTheme;
if (currentTheme && currentTheme.id == theme.id)
this.currentTheme = newData;
}, false);
req.send(null);
},
/**
* Switches to a new lightweight theme.
*
* @param aData
* The lightweight theme to switch to
*/
themeChanged: function(aData) {
if (_previewTimer) {
_previewTimer.cancel();
_previewTimer = null;
}
if (aData) {
let usedThemes = _usedThemesExceptId(aData.id);
usedThemes.unshift(aData);
_updateUsedThemes(usedThemes);
if (PERSIST_ENABLED) {
LightweightThemeImageOptimizer.purge();
_persistImages(aData, function() {
_notifyWindows(this.currentThemeForDisplay);
}.bind(this));
}
}
if (aData)
_prefs.setCharPref("selectedThemeID", aData.id);
else
_prefs.setCharPref("selectedThemeID", "");
_notifyWindows(aData);
Services.obs.notifyObservers(null, "lightweight-theme-changed", null);
},
/**
* Starts the Addons provider and enables the new lightweight theme if
* necessary.
*/
startup: function() {
if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
if (id)
this.themeChanged(this.getUsedTheme(id));
else
this.themeChanged(null);
Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
}
_prefs.addObserver("", _prefObserver, false);
},
/**
* Shuts down the provider.
*/
shutdown: function() {
_prefs.removeObserver("", _prefObserver);
},
/**
* Called when a new add-on has been enabled when only one add-on of that type
* can be enabled.
*
* @param aId
* The ID of the newly enabled add-on
* @param aType
* The type of the newly enabled add-on
* @param aPendingRestart
* true if the newly enabled add-on will only become enabled after a
* restart
*/
addonChanged: function(aId, aType, aPendingRestart) {
if (aType != ADDON_TYPE)
return;
let id = _getInternalID(aId);
let current = this.currentTheme;
try {
let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
if (id == next && aPendingRestart)
return;
Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
if (next) {
AddonManagerPrivate.callAddonListeners("onOperationCancelled",
new AddonWrapper(this.getUsedTheme(next)));
}
else {
if (id == current.id) {
AddonManagerPrivate.callAddonListeners("onOperationCancelled",
new AddonWrapper(current));
return;
}
}
}
catch (e) {
}
if (current) {
if (current.id == id)
return;
_themeIDBeingDisabled = current.id;
let wrapper = new AddonWrapper(current);
if (aPendingRestart) {
Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, "");
AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true);
}
else {
AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false);
this.themeChanged(null);
AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
}
_themeIDBeingDisabled = null;
}
if (id) {
let theme = this.getUsedTheme(id);
_themeIDBeingEnabled = id;
let wrapper = new AddonWrapper(theme);
if (aPendingRestart) {
AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true);
Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id);
// Flush the preferences to disk so they survive any crash
Services.prefs.savePrefFile(null);
}
else {
AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false);
this.themeChanged(theme);
AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
}
_themeIDBeingEnabled = null;
}
},
/**
* Called to get an Addon with a particular ID.
*
* @param aId
* The ID of the add-on to retrieve
* @param aCallback
* A callback to pass the Addon to
*/
getAddonByID: function(aId, aCallback) {
let id = _getInternalID(aId);
if (!id) {
aCallback(null);
return;
}
let theme = this.getUsedTheme(id);
if (!theme) {
aCallback(null);
return;
}
aCallback(new AddonWrapper(theme));
},
/**
* Called to get Addons of a particular type.
*
* @param aTypes
* An array of types to fetch. Can be null to get all types.
* @param aCallback
* A callback to pass an array of Addons to
*/
getAddonsByTypes: function(aTypes, aCallback) {
if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) {
aCallback([]);
return;
}
aCallback(this.usedThemes.map(a => new AddonWrapper(a)));
},
};
const wrapperMap = new WeakMap();
let themeFor = wrapper => wrapperMap.get(wrapper);
/**
* The AddonWrapper wraps lightweight theme to provide the data visible to
* consumers of the AddonManager API.
*/
function AddonWrapper(aTheme) {
wrapperMap.set(this, aTheme);
}
AddonWrapper.prototype = {
get id() {
return themeFor(this).id + ID_SUFFIX;
},
get type() {
return ADDON_TYPE;
},
get isActive() {
let current = LightweightThemeManager.currentTheme;
if (current)
return themeFor(this).id == current.id;
return false;
},
get name() {
return themeFor(this).name;
},
get version() {
let theme = themeFor(this);
return "version" in theme ? theme.version : "";
},
get creator() {
let theme = themeFor(this);
return "author" in theme ? new AddonManagerPrivate.AddonAuthor(theme.author) : null;
},
get screenshots() {
let url = themeFor(this).previewURL;
return [new AddonManagerPrivate.AddonScreenshot(url)];
},
get pendingOperations() {
let pending = AddonManager.PENDING_NONE;
if (this.isActive == this.userDisabled)
pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE;
return pending;
},
get operationsRequiringRestart() {
// If a non-default theme is in use then a restart will be required to
// enable lightweight themes unless dynamic theme switching is enabled
if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
try {
if (Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED))
return AddonManager.OP_NEEDS_RESTART_NONE;
}
catch (e) {
}
return AddonManager.OP_NEEDS_RESTART_ENABLE;
}
return AddonManager.OP_NEEDS_RESTART_NONE;
},
get size() {
// The size changes depending on whether the theme is in use or not, this is
// probably not worth exposing.
return null;
},
get permissions() {
let permissions = 0;
// Do not allow uninstall of builtIn themes.
if (!LightweightThemeManager._builtInThemes.has(themeFor(this).id))
permissions = AddonManager.PERM_CAN_UNINSTALL;
if (this.userDisabled)
permissions |= AddonManager.PERM_CAN_ENABLE;
else
permissions |= AddonManager.PERM_CAN_DISABLE;
return permissions;
},
get userDisabled() {
let id = themeFor(this).id;
if (_themeIDBeingEnabled == id)
return false;
if (_themeIDBeingDisabled == id)
return true;
try {
let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
return id != toSelect;
}
catch (e) {
let current = LightweightThemeManager.currentTheme;
return !current || current.id != id;
}
},
set userDisabled(val) {
if (val == this.userDisabled)
return val;
if (val)
LightweightThemeManager.currentTheme = null;
else
LightweightThemeManager.currentTheme = themeFor(this);
return val;
},
// Lightweight themes are never disabled by the application
get appDisabled() {
return false;
},
// Lightweight themes are always compatible
get isCompatible() {
return true;
},
get isPlatformCompatible() {
return true;
},
get scope() {
return AddonManager.SCOPE_PROFILE;
},
get foreignInstall() {
return false;
},
uninstall: function() {
LightweightThemeManager.forgetUsedTheme(themeFor(this).id);
},
cancelUninstall: function() {
throw new Error("Theme is not marked to be uninstalled");
},
findUpdates: function(listener, reason, appVersion, platformVersion) {
AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion);
},
// Lightweight themes are always compatible
isCompatibleWith: function(appVersion, platformVersion) {
return true;
},
// Lightweight themes are always securely updated
get providesUpdatesSecurely() {
return true;
},
// Lightweight themes are never blocklisted
get blocklistState() {
return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
}
};
["description", "homepageURL", "iconURL"].forEach(function(prop) {
Object.defineProperty(AddonWrapper.prototype, prop, {
get: function() {
let theme = themeFor(this);
return prop in theme ? theme[prop] : null;
},
enumarable: true,
});
});
["installDate", "updateDate"].forEach(function(prop) {
Object.defineProperty(AddonWrapper.prototype, prop, {
get: function() {
let theme = themeFor(this);
return prop in theme ? new Date(theme[prop]) : null;
},
enumarable: true,
});
});
/**
* Converts the ID used by the public AddonManager API to an lightweight theme
* ID.
*
* @param id
* The ID to be converted
*
* @return the lightweight theme ID or null if the ID was not for a lightweight
* theme.
*/
function _getInternalID(id) {
if (!id)
return null;
let len = id.length - ID_SUFFIX.length;
if (len > 0 && id.substring(len) == ID_SUFFIX)
return id.substring(0, len);
return null;
}
function _setCurrentTheme(aData, aLocal) {
aData = _sanitizeTheme(aData, null, aLocal);
let needsRestart = (ADDON_TYPE == "theme") &&
Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN);
let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
cancel.data = false;
Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested",
JSON.stringify(aData));
if (aData) {
let theme = LightweightThemeManager.getUsedTheme(aData.id);
let isInstall = !theme || theme.version != aData.version;
if (isInstall) {
aData.updateDate = Date.now();
if (theme && "installDate" in theme)
aData.installDate = theme.installDate;
else
aData.installDate = aData.updateDate;
var oldWrapper = theme ? new AddonWrapper(theme) : null;
var wrapper = new AddonWrapper(aData);
AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
wrapper, oldWrapper, false);
AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false);
}
let current = LightweightThemeManager.currentTheme;
let usedThemes = _usedThemesExceptId(aData.id);
if (current && current.id != aData.id)
usedThemes.splice(1, 0, aData);
else
usedThemes.unshift(aData);
_updateUsedThemes(usedThemes);
if (isInstall)
AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
}
if (cancel.data)
return null;
AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null,
ADDON_TYPE, needsRestart);
return LightweightThemeManager.currentTheme;
}
function _sanitizeTheme(aData, aBaseURI, aLocal) {
if (!aData || typeof aData != "object")
return null;
var resourceProtocols = ["http", "https", "resource"];
if (aLocal)
resourceProtocols.push("file");
var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):");
function sanitizeProperty(prop) {
if (!(prop in aData))
return null;
if (typeof aData[prop] != "string")
return null;
let val = aData[prop].trim();
if (!val)
return null;
if (!/URL$/.test(prop))
return val;
try {
val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec;
if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val))
return val;
return null;
}
catch (e) {
return null;
}
}
let result = {};
for (let mandatoryProperty of MANDATORY) {
let val = sanitizeProperty(mandatoryProperty);
if (!val)
throw Components.results.NS_ERROR_INVALID_ARG;
result[mandatoryProperty] = val;
}
for (let optionalProperty of OPTIONAL) {
let val = sanitizeProperty(optionalProperty);
if (!val)
continue;
result[optionalProperty] = val;
}
return result;
}
function _usedThemesExceptId(aId) {
return LightweightThemeManager.usedThemes.filter(function(t) {
return "id" in t && t.id != aId;
});
}
function _version(aThemeData) {
return aThemeData.version || "";
}
function _makeURI(aURL, aBaseURI) {
return Services.io.newURI(aURL, null, aBaseURI);
}
function _updateUsedThemes(aList) {
// Remove app-specific themes before saving them to the usedThemes pref.
aList = aList.filter(theme => !LightweightThemeManager._builtInThemes.has(theme.id));
// Send uninstall events for all themes that need to be removed.
while (aList.length > _maxUsedThemes) {
let wrapper = new AddonWrapper(aList[aList.length - 1]);
AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
aList.pop();
AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
}
var str = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
str.data = JSON.stringify(aList);
_prefs.setComplexValue("usedThemes", Ci.nsISupportsString, str);
Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null);
}
function _notifyWindows(aThemeData) {
Services.obs.notifyObservers(null, "lightweight-theme-styling-update",
JSON.stringify(aThemeData));
}
var _previewTimer;
var _previewTimerCallback = {
notify: function() {
LightweightThemeManager.resetPreview();
}
};
/**
* Called when any of the lightweightThemes preferences are changed.
*/
function _prefObserver(aSubject, aTopic, aData) {
switch (aData) {
case "maxUsedThemes":
try {
_maxUsedThemes = _prefs.getIntPref(aData);
}
catch (e) {
_maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT;
}
// Update the theme list to remove any themes over the number we keep
_updateUsedThemes(LightweightThemeManager.usedThemes);
break;
}
}
function _persistImages(aData, aCallback) {
function onSuccess(key) {
return function () {
let current = LightweightThemeManager.currentTheme;
if (current && current.id == aData.id) {
_prefs.setBoolPref("persisted." + key, true);
}
if (--numFilesToPersist == 0 && aCallback) {
aCallback();
}
};
}
let numFilesToPersist = 0;
for (let key in PERSIST_FILES) {
_prefs.setBoolPref("persisted." + key, false);
if (aData[key]) {
numFilesToPersist++;
_persistImage(aData[key], PERSIST_FILES[key], onSuccess(key));
}
}
}
function _getLocalImageURI(localFileName) {
var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
localFile.append(localFileName);
return Services.io.newFileURI(localFile);
}
function _persistImage(sourceURL, localFileName, successCallback) {
if (/^(file|resource):/.test(sourceURL))
return;
var targetURI = _getLocalImageURI(localFileName);
var sourceURI = _makeURI(sourceURL);
var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
.createInstance(Ci.nsIWebBrowserPersist);
persist.persistFlags =
Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION |
(PERSIST_BYPASS_CACHE ?
Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE :
Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE);
persist.progressListener = new _persistProgressListener(successCallback);
persist.saveURI(sourceURI, null,
null, Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
null, null, targetURI, null);
}
function _persistProgressListener(successCallback) {
this.onLocationChange = function() {};
this.onProgressChange = function() {};
this.onStatusChange = function() {};
this.onSecurityChange = function() {};
this.onStateChange = function(aWebProgress, aRequest, aStateFlags, aStatus) {
if (aRequest &&
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
try {
if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) {
// success
successCallback();
return;
}
} catch (e) { }
// failure
}
};
}
AddonManagerPrivate.registerProvider(LightweightThemeManager, [
new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 5000)
]);