diff options
author | Matt A. Tobin <email@mattatobin.com> | 2018-02-10 04:00:58 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2018-02-10 04:00:58 -0500 |
commit | deea787c2efbb9c89caec8d9efc023ffafe75613 (patch) | |
tree | 6dbe55f7d24e67ecdcc821b8c5492f6c17217852 /toolkit/mozapps/extensions/LightweightThemeManager.jsm | |
parent | 37d5300335d81cecbecc99812747a657588c63eb (diff) | |
download | UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.tar UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.tar.gz UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.tar.lz UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.tar.xz UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.zip |
Import Tycho's Add-on Manager
Diffstat (limited to 'toolkit/mozapps/extensions/LightweightThemeManager.jsm')
-rw-r--r-- | toolkit/mozapps/extensions/LightweightThemeManager.jsm | 804 |
1 files changed, 804 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/LightweightThemeManager.jsm b/toolkit/mozapps/extensions/LightweightThemeManager.jsm new file mode 100644 index 000000000..66761efe2 --- /dev/null +++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm @@ -0,0 +1,804 @@ +/* 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"); + +this.__defineGetter__("_prefs", function prefsGetter() { + delete this._prefs; + return this._prefs = Services.prefs.getBranch("lightweightThemes."); +}); + +this.__defineGetter__("_maxUsedThemes", function maxUsedThemesGetter() { + delete this._maxUsedThemes; + try { + this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes"); + } + catch (e) { + this._maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; + } + return this._maxUsedThemes; +}); + +this.__defineSetter__("_maxUsedThemes", function maxUsedThemesSetter(aVal) { + delete this._maxUsedThemes; + return this._maxUsedThemes = aVal; +}); + +// 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; + +this.LightweightThemeManager = { + get name() "LightweightThemeManager", + + get usedThemes () { + try { + return JSON.parse(_prefs.getComplexValue("usedThemes", + Ci.nsISupportsString).data); + } catch (e) { + return []; + } + }, + + get currentTheme () { + try { + if (_prefs.getBoolPref("isThemeSelected")) + var data = this.usedThemes[0]; + } catch (e) {} + + return data || null; + }, + + 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 LightweightThemeManager_setLocalTheme(aData) { + _setCurrentTheme(aData, true); + }, + + getUsedTheme: function LightweightThemeManager_getUsedTheme(aId) { + var usedThemes = this.usedThemes; + for (let usedTheme of usedThemes) { + if (usedTheme.id == aId) + return usedTheme; + } + return null; + }, + + forgetUsedTheme: function LightweightThemeManager_forgetUsedTheme(aId) { + let theme = this.getUsedTheme(aId); + if (!theme) + 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); + }, + + previewTheme: function LightweightThemeManager_previewTheme(aData) { + let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + cancel.data = false; + Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested", + JSON.stringify(aData)); + if (cancel.data) + return; + + 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 LightweightThemeManager_resetPreview() { + if (_previewTimer) { + _previewTimer.cancel(); + _previewTimer = null; + _notifyWindows(this.currentThemeForDisplay); + } + }, + + parseTheme: function LightweightThemeManager_parseTheme(aString, aBaseURI) { + try { + return _sanitizeTheme(JSON.parse(aString), aBaseURI, false); + } catch (e) { + return null; + } + }, + + updateCurrentTheme: function LightweightThemeManager_updateCurrentTheme() { + 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; + + var self = this; + req.addEventListener("load", function loadEventListener() { + if (req.status != 200) + return; + + let newData = self.parseTheme(req.responseText, theme.updateURL); + if (!newData || + newData.id != theme.id || + _version(newData) == _version(theme)) + return; + + var currentTheme = self.currentTheme; + if (currentTheme && currentTheme.id == theme.id) + self.currentTheme = newData; + }, false); + + req.send(null); + }, + + /** + * Switches to a new lightweight theme. + * + * @param aData + * The lightweight theme to switch to + */ + themeChanged: function LightweightThemeManager_themeChanged(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 themeChanged_persistImages() { + _notifyWindows(this.currentThemeForDisplay); + }.bind(this)); + } + } + + _prefs.setBoolPref("isThemeSelected", aData != null); + _notifyWindows(aData); + Services.obs.notifyObservers(null, "lightweight-theme-changed", null); + }, + + /** + * Starts the Addons provider and enables the new lightweight theme if + * necessary. + */ + startup: function LightweightThemeManager_startup() { + 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 LightweightThemeManager_shutdown() { + _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 LightweightThemeManager_addonChanged(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 LightweightThemeManager_getAddonByID(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 LightweightThemeManager_getAddonsByTypes(aTypes, aCallback) { + if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) { + aCallback([]); + return; + } + + aCallback([new AddonWrapper(a) for each (a in this.usedThemes)]); + }, +}; + +/** + * The AddonWrapper wraps lightweight theme to provide the data visible to + * consumers of the AddonManager API. + */ +function AddonWrapper(aTheme) { + this.__defineGetter__("id", function AddonWrapper_idGetter() aTheme.id + ID_SUFFIX); + this.__defineGetter__("type", function AddonWrapper_typeGetter() ADDON_TYPE); + this.__defineGetter__("isActive", function AddonWrapper_isActiveGetter() { + let current = LightweightThemeManager.currentTheme; + if (current) + return aTheme.id == current.id; + return false; + }); + + this.__defineGetter__("name", function AddonWrapper_nameGetter() aTheme.name); + this.__defineGetter__("version", function AddonWrapper_versionGetter() { + return "version" in aTheme ? aTheme.version : ""; + }); + + ["description", "homepageURL", "iconURL"].forEach(function(prop) { + this.__defineGetter__(prop, function AddonWrapper_optionalPropGetter() { + return prop in aTheme ? aTheme[prop] : null; + }); + }, this); + + ["installDate", "updateDate"].forEach(function(prop) { + this.__defineGetter__(prop, function AddonWrapper_datePropGetter() { + return prop in aTheme ? new Date(aTheme[prop]) : null; + }); + }, this); + + this.__defineGetter__("creator", function AddonWrapper_creatorGetter() { + return new AddonManagerPrivate.AddonAuthor(aTheme.author); + }); + + this.__defineGetter__("screenshots", function AddonWrapper_screenshotsGetter() { + let url = aTheme.previewURL; + return [new AddonManagerPrivate.AddonScreenshot(url)]; + }); + + this.__defineGetter__("pendingOperations", + function AddonWrapper_pendingOperationsGetter() { + let pending = AddonManager.PENDING_NONE; + if (this.isActive == this.userDisabled) + pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE; + return pending; + }); + + this.__defineGetter__("operationsRequiringRestart", + function AddonWrapper_operationsRequiringRestartGetter() { + // 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; + }); + + this.__defineGetter__("size", function AddonWrapper_sizeGetter() { + // The size changes depending on whether the theme is in use or not, this is + // probably not worth exposing. + return null; + }); + + this.__defineGetter__("permissions", function AddonWrapper_permissionsGetter() { + let permissions = AddonManager.PERM_CAN_UNINSTALL; + if (this.userDisabled) + permissions |= AddonManager.PERM_CAN_ENABLE; + else + permissions |= AddonManager.PERM_CAN_DISABLE; + return permissions; + }); + + this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() { + if (_themeIDBeingEnabled == aTheme.id) + return false; + if (_themeIDBeingDisabled == aTheme.id) + return true; + + try { + let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); + return aTheme.id != toSelect; + } + catch (e) { + let current = LightweightThemeManager.currentTheme; + return !current || current.id != aTheme.id; + } + }); + + this.__defineSetter__("userDisabled", function AddonWrapper_userDisabledSetter(val) { + if (val == this.userDisabled) + return val; + + if (val) + LightweightThemeManager.currentTheme = null; + else + LightweightThemeManager.currentTheme = aTheme; + + return val; + }); + + this.uninstall = function AddonWrapper_uninstall() { + LightweightThemeManager.forgetUsedTheme(aTheme.id); + }; + + this.cancelUninstall = function AddonWrapper_cancelUninstall() { + throw new Error("Theme is not marked to be uninstalled"); + }; + + this.findUpdates = function AddonWrapper_findUpdates(listener, reason, appVersion, platformVersion) { + AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion); + }; +} + +AddonWrapper.prototype = { + // 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; + }, + + // Lightweight themes are always compatible + isCompatibleWith: function AddonWrapper_isCompatibleWith(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; + } +}; + +/** + * 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) + LightweightThemeManager.usedThemes.filter( + function usedThemesExceptId_filterID(t) "id" in t && t.id != aId); + +function _version(aThemeData) + aThemeData.version || ""; + +function _makeURI(aURL, aBaseURI) + Services.io.newURI(aURL, null, aBaseURI); + +function _updateUsedThemes(aList) { + // 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 _previewTimerCallback_notify() { + 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) 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 persistProgressListener_onLocationChange() {}; + this.onProgressChange = function persistProgressListener_onProgressChange() {}; + this.onStatusChange = function persistProgressListener_onStatusChange() {}; + this.onSecurityChange = function persistProgressListener_onSecurityChange() {}; + this.onStateChange = function persistProgressListener_onStateChange(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) +]); |