Skip to content

Instantly share code, notes, and snippets.

@rhelmer
Created August 4, 2016 16:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rhelmer/fd0aa0e66e8be1eff2bdcb05413b1ac9 to your computer and use it in GitHub Desktop.
Save rhelmer/fd0aa0e66e8be1eff2bdcb05413b1ac9 to your computer and use it in GitHub Desktop.
diff --git a/toolkit/modules/GMPInstallManager.jsm b/toolkit/modules/GMPInstallManager.jsm
--- a/toolkit/modules/GMPInstallManager.jsm
+++ b/toolkit/modules/GMPInstallManager.jsm
@@ -99,17 +99,17 @@ GMPInstallManager.prototype = {
let certs = null;
if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE)) {
allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN, true);
if (GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
}
}
- ProductAddonChecker.getProductAddonList(url, allowNonBuiltIn, certs).then((addons) => {
+ ProductAddonChecker.getProductAddonList(url, allowNonBuiltIn, certs).then((addons, revision) => {
if (!addons) {
this._deferred.resolve([]);
}
else {
this._deferred.resolve(addons.map(a => new GMPAddon(a)));
}
delete this._deferred;
}, (ex) => {
diff --git a/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm b/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm
--- a/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm
@@ -139,30 +139,31 @@ function downloadXML(url, allowNonBuiltI
});
}
/**
* Parses a list of add-ons from a DOM document.
*
* @param document
* The DOM document to parse.
- * @return null if there is no <addons> element otherwise an array of the addons
- * listed.
+ * @return a tuple of {null, null} if there is no <addons> element,
+ * {array of the addons listed, -1} if there is no revision number, or
+ * {array of the addons listed, revision number of the addon set}.
*/
function parseXML(document) {
// Check that the root element is correct
if (document.documentElement.localName != "updates") {
throw new Error("got node name: " + document.documentElement.localName +
", expected: updates");
}
// Check if there are any addons elements in the updates element
let addons = document.querySelector("updates:root > addons");
if (!addons) {
- return null;
+ return [null, null];
}
let results = [];
let addonList = document.querySelectorAll("updates:root > addons > addon");
for (let addonElement of addonList) {
let addon = {};
for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) {
@@ -170,17 +171,25 @@ function parseXML(document) {
addon[name] = addonElement.getAttribute(name);
}
}
addon.size = Number(addon.size) || undefined;
results.push(addon);
}
- return results;
+ // Record the revision number for this set, if available.
+ let revision = -1;
+ let addonData = document.querySelector("updates:root > addons")
+ let revisionAttribute = "revision";
+ if (addonData.hasAttribute(revisionAttribute)) {
+ revision = addonData.getAttribute(revisionAttribute);
+ }
+
+ return [results, revision];
}
/**
* Downloads file from a URL using XHR.
*
* @param url
* The url to download from.
* @return a promise that resolves to the path of a temporary file or rejects
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -170,16 +170,24 @@ const RDFURI_INSTALL_MANIFEST_ROOT =
const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
const TOOLKIT_ID = "toolkit@mozilla.org";
const XPI_SIGNATURE_CHECK_PERIOD = 24 * 60 * 60;
XPCOMUtils.defineConstant(this, "DB_SCHEMA", 17);
+const SYSTEM_ADDON_SCHEMA_VERSION = 2;
+const EMPTY_SYSTEM_ADDON_SET = {
+ schema: SYSTEM_ADDON_SCHEMA_VERSION,
+ revision: -1,
+ addons: {}
+};
+
+
const NOTIFICATION_TOOLBOXPROCESS_LOADED = "ToolboxProcessLoaded";
// Properties that exist in the install manifest
const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL",
"updateKey", "optionsURL", "optionsType", "aboutURL",
"iconURL", "icon64URL"];
const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
@@ -3007,25 +3015,36 @@ this.XPIProvider = {
if (!url) {
yield systemAddonLocation.cleanDirectories();
return;
}
url = UpdateUtils.formatUpdateURL(url);
logger.info(`Starting system add-on update check from ${url}.`);
- let addonList = yield ProductAddonChecker.getProductAddonList(url);
+ let [addonList, revision] = yield ProductAddonChecker.getProductAddonList(url);
// If there was no list then do nothing.
if (!addonList) {
logger.info("No system add-ons list was returned.");
yield systemAddonLocation.cleanDirectories();
return;
}
+ // If there is a revision set and it is older than the recorded revision,
+ // do nothing.
+ let oldRevision = systemAddonLocation.getRevision();
+ if (revision && oldRevision) {
+ if (revision >= 0 && revision < oldRevision) {
+ logger.debug(`System add-on server revision too old, server has ${revision} and client has ${oldRevision}`);
+ return;
+ }
+ }
+ systemAddonLocation.setRevision(revision);
+
addonList = new Map(
addonList.map(spec => [spec.id, { spec, path: null, addon: null }]));
let getAddonsInLocation = (location) => {
return new Promise(resolve => {
XPIDatabase.getAddonsInLocation(location, resolve);
});
};
@@ -3122,17 +3141,17 @@ this.XPIProvider = {
if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) {
throw new Error("Rejecting updated system add-on set that either could not " +
"be downloaded or contained unusable add-ons.");
}
// Install into the install location
logger.info("Installing new system add-on set");
yield systemAddonLocation.installAddonSet(Array.from(addonList.values())
- .map(a => a.addon));
+ .map(a => a.addon), revision);
// Bug 1204156: Switch to the new system add-ons without requiring a restart
}
finally {
// Delete the temporary files
logger.info("Deleting temporary files");
for (let item of addonList.values()) {
// If this item downloaded delete the temporary file.
@@ -8275,39 +8294,50 @@ function SystemAddonInstallLocation(aNam
this._directory = aDirectory.clone();
this._directory.append(this._addonSet.directory);
logger.info("SystemAddonInstallLocation scanning directory " + this._directory.path);
}
else {
logger.info("SystemAddonInstallLocation directory is missing");
}
+ if (this._addonSet.revision) {
+ this._revision = this._addonSet.revision;
+ logger.info(`System add-on update revision is: ${this._revision}`);
+ } else {
+ logger.info("No System add-on update revision specified on server, setting to -1.");
+ this._revision = -1;
+ }
+
DirectoryInstallLocation.call(this, aName, this._directory, aScope);
this.locked = true;
}
SystemAddonInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype);
Object.assign(SystemAddonInstallLocation.prototype, {
/**
* Reads the current set of system add-ons
*/
_loadAddonSet: function() {
try {
let setStr = Preferences.get(PREF_SYSTEM_ADDON_SET, null);
if (setStr) {
let addonSet = JSON.parse(setStr);
- if ((typeof addonSet == "object") && addonSet.schema == 1)
- return addonSet;
+ if (typeof addonSet == "object") {
+ if (addonSet.schema == 1 || addonSet.schema == 2) {
+ return addonSet;
+ }
+ }
}
}
catch (e) {
logger.error("Malformed system add-on set, resetting.");
}
- return { schema: 1, addons: {} };
+ return EMPTY_SYSTEM_ADDON_SET;
},
/**
* Saves the current set of system add-ons
*/
_saveAddonSet: function(aAddonSet) {
Preferences.set(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet));
},
@@ -8323,16 +8353,25 @@ Object.assign(SystemAddonInstallLocation
for (let id of addons.keys()) {
if (!(id in this._addonSet.addons))
addons.delete(id);
}
return addons;
},
+ getRevision: function() {
+ return this._revision;
+ },
+
+ setRevision: function(revision) {
+ // TODO validate that it's a Number etc.
+ this._revision = revision;
+ },
+
/**
* Tests whether updated system add-ons are expected.
*/
isActive: function() {
return this._directory != null;
},
isValidAddon: function(aAddon) {
@@ -8376,17 +8415,17 @@ Object.assign(SystemAddonInstallLocation
return true;
},
/**
* Resets the add-on set so on the next startup the default set will be used.
*/
resetAddonSet: function() {
- this._saveAddonSet({ schema: 1, addons: {} });
+ this._saveAddonSet(EMPTY_SYSTEM_ADDON_SET);
},
/**
* Removes any directories not currently in use or pending use after a
* restart. Any errors that happen here don't really matter as we'll attempt
* to cleanup again next time.
*/
cleanDirectories: Task.async(function*() {
@@ -8441,17 +8480,17 @@ Object.assign(SystemAddonInstallLocation
iterator.close();
}
}),
/**
* Installs a new set of system add-ons into the location and updates the
* add-on set in prefs. We wait to switch state until a restart.
*/
- installAddonSet: Task.async(function*(aAddons) {
+ installAddonSet: Task.async(function*(aAddons, revision) {
// Make sure the base dir exists
yield OS.File.makeDir(this._baseDir.path, { ignoreExisting: true });
let newDir = this._baseDir.clone();
let uuidGen = Cc["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator);
newDir.append("blank");
@@ -8490,17 +8529,22 @@ Object.assign(SystemAddonInstallLocation
}
catch (e) {
logger.warn(`Failed to remove new system add-on directory ${newDir.path}.`, e);
}
throw e;
}
// All add-ons in position, create the new state and store it in prefs
- let state = { schema: 1, directory: newDir.leafName, addons: {} };
+ let state = {
+ schema: SYSTEM_ADDON_SCHEMA_VERSION,
+ revision: revision,
+ directory: newDir.leafName,
+ addons: {}
+ };
for (let addon of aAddons) {
state.addons[addon.id] = {
version: addon.version
}
}
this._saveAddonSet(state);
this._nextDir = newDir;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment