Backed out changeset 40c48d65e382 (bug 853388)

This commit is contained in:
Tim Taubert 2013-08-09 04:20:03 +02:00
parent 7f5ce688b5
commit d9d1ab46ea
3 changed files with 196 additions and 312 deletions

View File

@ -3218,8 +3218,9 @@ var XPIProvider = {
// If the database needs to be updated then open it and then update it // If the database needs to be updated then open it and then update it
// from the filesystem // from the filesystem
if (updateDatabase || hasPendingChanges) { if (updateDatabase || hasPendingChanges) {
XPIDatabase.syncLoadDB(false);
try { try {
XPIDatabase.openConnection(false, true);
extensionListChanged = this.processFileChanges(state, manifests, extensionListChanged = this.processFileChanges(state, manifests,
aAppChanged, aAppChanged,
aOldAppVersion, aOldAppVersion,

View File

@ -20,8 +20,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
"resource://gre/modules/DeferredSave.jsm"); "resource://gre/modules/DeferredSave.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm"); "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
["LOG", "WARN", "ERROR"].forEach(function(aName) { ["LOG", "WARN", "ERROR"].forEach(function(aName) {
Object.defineProperty(this, aName, { Object.defineProperty(this, aName, {
@ -160,20 +158,6 @@ function getRepositoryAddon(aAddon, aCallback) {
AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
} }
/**
* Wrap an API-supplied function in an exception handler to make it safe to call
*/
function safeCallback(aCallback) {
return function(...aArgs) {
try {
aCallback.apply(null, aArgs);
}
catch(ex) {
WARN("XPI Database callback failed", ex);
}
}
}
/** /**
* A helper method to asynchronously call a function on an array * A helper method to asynchronously call a function on an array
* of objects, calling a callback when function(x) has been gathered * of objects, calling a callback when function(x) has been gathered
@ -391,32 +375,6 @@ DBAddonInternal.prototype = {
DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; DBAddonInternal.prototype.__proto__ = AddonInternal.prototype;
/**
* Internal interface: find an addon from an already loaded addonDB
*/
function _findAddon(addonDB, aFilter) {
for (let [, addon] of addonDB) {
if (aFilter(addon)) {
return addon;
}
}
return null;
}
/**
* Internal interface to get a filtered list of addons from a loaded addonDB
*/
function _filterDB(addonDB, aFilter) {
let addonList = [];
for (let [, addon] of addonDB) {
if (aFilter(addon)) {
addonList.push(addon);
}
}
return addonList;
}
this.XPIDatabase = { this.XPIDatabase = {
// true if the database connection has been opened // true if the database connection has been opened
initialized: false, initialized: false,
@ -452,12 +410,6 @@ this.XPIDatabase = {
return; return;
} }
if (!this._deferredSave) {
this._deferredSave = new DeferredSave(this.jsonFile.path,
() => JSON.stringify(this),
ASYNC_SAVE_DELAY_MS);
}
let promise = this._deferredSave.saveChanges(); let promise = this._deferredSave.saveChanges();
if (!this._schemaVersionSet) { if (!this._schemaVersionSet) {
this._schemaVersionSet = true; this._schemaVersionSet = true;
@ -477,8 +429,8 @@ this.XPIDatabase = {
}, },
flush: function() { flush: function() {
// handle the "in memory only" and "saveChanges never called" cases // handle the "in memory only" case
if (!this._deferredSave) { if (this.lockedDatabase) {
let done = Promise.defer(); let done = Promise.defer();
done.resolve(0); done.resolve(0);
return done.promise; return done.promise;
@ -487,17 +439,19 @@ this.XPIDatabase = {
return this._deferredSave.flush(); return this._deferredSave.flush();
}, },
get _deferredSave() {
delete this._deferredSave;
return this._deferredSave =
new DeferredSave(this.jsonFile.path, () => JSON.stringify(this),
ASYNC_SAVE_DELAY_MS);
},
/** /**
* Converts the current internal state of the XPI addon database to JSON * Converts the current internal state of the XPI addon database to JSON
*/ */
toJSON: function() { toJSON: function() {
if (!this.addonDB) {
// We never loaded the database?
throw new Error("Attempt to save database without loading it first");
}
let addons = []; let addons = [];
for (let [, addon] of this.addonDB) { for (let [key, addon] of this.addonDB) {
addons.push(addon); addons.push(addon);
} }
let toSave = { let toSave = {
@ -535,7 +489,7 @@ this.XPIDatabase = {
}, },
/** /**
* Synchronously opens and reads the database file, upgrading from old * Opens and reads the database file, upgrading from old
* databases or making a new DB if needed. * databases or making a new DB if needed.
* *
* The possibilities, in order of priority, are: * The possibilities, in order of priority, are:
@ -552,8 +506,10 @@ this.XPIDatabase = {
* from the install locations if the database needs to be rebuilt. * from the install locations if the database needs to be rebuilt.
* (if false, caller is XPIProvider.checkForChanges() which will rebuild) * (if false, caller is XPIProvider.checkForChanges() which will rebuild)
*/ */
syncLoadDB: function XPIDB_syncLoadDB(aRebuildOnError) { openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) {
// XXX TELEMETRY report synchronous opens (startup time) vs. delayed opens // XXX TELEMETRY report opens with aRebuildOnError true (which implies delayed open)
// vs. aRebuildOnError false (DB loaded during startup)
delete this.addonDB;
this.migrateData = null; this.migrateData = null;
let fstream = null; let fstream = null;
let data = ""; let data = "";
@ -574,10 +530,42 @@ this.XPIDatabase = {
data += str.value; data += str.value;
} while (read != 0); } while (read != 0);
} }
this.parseDB(data, aRebuildOnError); // dump("Loaded JSON:\n" + data + "\n");
let inputAddons = JSON.parse(data);
// Now do some sanity checks on our JSON db
if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) {
// Content of JSON file is bad, need to rebuild from scratch
ERROR("bad JSON file contents");
this.rebuildDatabase(aRebuildOnError);
}
if (inputAddons.schemaVersion != DB_SCHEMA) {
// Handle mismatched JSON schema version. For now, we assume
// compatibility for JSON data, though we throw away any fields we
// don't know about
// XXX preserve unknown fields during save/restore
LOG("JSON schema mismatch: expected " + DB_SCHEMA +
", actual " + inputAddons.schemaVersion);
}
// If we got here, we probably have good data
// Make AddonInternal instances from the loaded data and save them
let addonDB = new Map();
inputAddons.addons.forEach(function(loadedAddon) {
let newAddon = new DBAddonInternal(loadedAddon);
addonDB.set(newAddon._key, newAddon);
});
this.addonDB = addonDB;
LOG("Successfully read XPI database");
this.initialized = true;
} }
catch(e) { catch(e) {
ERROR("Failed to load XPI JSON data from profile", e); // If we catch and log a SyntaxError from the JSON
// parser, the xpcshell test harness fails the test for us: bug 870828
if (e.name == "SyntaxError") {
ERROR("Syntax error parsing saved XPI JSON data");
}
else {
ERROR("Failed to load XPI JSON data from profile", e);
}
this.rebuildDatabase(aRebuildOnError); this.rebuildDatabase(aRebuildOnError);
} }
finally { finally {
@ -587,151 +575,46 @@ this.XPIDatabase = {
} }
catch (e) { catch (e) {
if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
this.upgradeDB(aRebuildOnError); try {
let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA);
if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) {
// we should have an older SQLITE database
this.migrateData = this.getMigrateDataFromSQLITE();
}
// else we've upgraded before but the JSON file is gone, fall through
// and rebuild from scratch
}
catch(e) {
// No schema version pref means either a really old upgrade (RDF) or
// a new profile
this.migrateData = this.getMigrateDataFromRDF();
}
this.rebuildDatabase(aRebuildOnError);
} }
else { else {
this.rebuildUnreadableDB(e, aRebuildOnError); WARN("Extensions database " + this.jsonFile.path +
" exists but is not readable; rebuilding in memory", e);
// XXX open question - if we can overwrite at save time, should we, or should we
// leave the locked database in case we can recover from it next time we start up?
// The old code made one attempt to remove the locked file before it rebuilt in memory
this.lockedDatabase = true;
// XXX TELEMETRY report when this happens?
this.rebuildDatabase(aRebuildOnError);
} }
} }
finally { finally {
if (fstream) if (fstream)
fstream.close(); fstream.close();
} }
// If an async load was also in progress, resolve that promise with our DB;
// otherwise create a resolved promise
if (this._dbPromise)
this._dbPromise.resolve(this.addonDB);
else
this._dbPromise = Promise.resolve(this.addonDB);
},
/** return;
* Parse loaded data, reconstructing the database if the loaded data is not valid
* @param aRebuildOnError // XXX what about aForceOpen? Appears to handle the case of "don't open DB file if there aren't any extensions"?
* If true, synchronously reconstruct the database from installed add-ons if (!aForceOpen && !this.dbfileExists) {
*/ this.connection = null;
parseDB: function(aData, aRebuildOnError) { return;
try {
// dump("Loaded JSON:\n" + aData + "\n");
let inputAddons = JSON.parse(aData);
// Now do some sanity checks on our JSON db
if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) {
// Content of JSON file is bad, need to rebuild from scratch
ERROR("bad JSON file contents");
this.rebuildDatabase(aRebuildOnError);
return;
}
if (inputAddons.schemaVersion != DB_SCHEMA) {
// Handle mismatched JSON schema version. For now, we assume
// compatibility for JSON data, though we throw away any fields we
// don't know about
// XXX preserve unknown fields during save/restore
LOG("JSON schema mismatch: expected " + DB_SCHEMA +
", actual " + inputAddons.schemaVersion);
}
// If we got here, we probably have good data
// Make AddonInternal instances from the loaded data and save them
let addonDB = new Map();
inputAddons.addons.forEach(function(loadedAddon) {
let newAddon = new DBAddonInternal(loadedAddon);
addonDB.set(newAddon._key, newAddon);
});
this.addonDB = addonDB;
LOG("Successfully read XPI database");
this.initialized = true;
} }
catch(e) {
// If we catch and log a SyntaxError from the JSON
// parser, the xpcshell test harness fails the test for us: bug 870828
if (e.name == "SyntaxError") {
ERROR("Syntax error parsing saved XPI JSON data");
}
else {
ERROR("Failed to load XPI JSON data from profile", e);
}
this.rebuildDatabase(aRebuildOnError);
}
},
/**
* Upgrade database from earlier (sqlite or RDF) version if available
*/
upgradeDB: function(aRebuildOnError) {
try {
let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA);
if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) {
// we should have an older SQLITE database
this.migrateData = this.getMigrateDataFromSQLITE();
}
// else we've upgraded before but the JSON file is gone, fall through
// and rebuild from scratch
}
catch(e) {
// No schema version pref means either a really old upgrade (RDF) or
// a new profile
this.migrateData = this.getMigrateDataFromRDF();
}
this.rebuildDatabase(aRebuildOnError);
},
/**
* Reconstruct when the DB file exists but is unreadable
* (for example because read permission is denied
*/
rebuildUnreadableDB: function(aError, aRebuildOnError) {
WARN("Extensions database " + this.jsonFile.path +
" exists but is not readable; rebuilding in memory", aError);
// XXX open question - if we can overwrite at save time, should we, or should we
// leave the locked database in case we can recover from it next time we start up?
// The old code made one attempt to remove the locked file before it rebuilt in memory
this.lockedDatabase = true;
// XXX TELEMETRY report when this happens?
this.rebuildDatabase(aRebuildOnError);
},
/**
* Open and read the XPI database asynchronously, upgrading if
* necessary. If any DB load operation fails, we need to
* synchronously rebuild the DB from the installed extensions.
*
* @return Promise<Map> resolves to the Map of loaded JSON data stored
* in this.addonDB; never rejects.
*/
asyncLoadDB: function XPIDB_asyncLoadDB(aDBCallback) {
// Already started (and possibly finished) loading
if (this._dbPromise) {
return this._dbPromise;
}
LOG("Starting async load of XPI database " + this.jsonFile.path);
return this._dbPromise = OS.File.read(this.jsonFile.path).then(
byteArray => {
if (this._addonDB) {
LOG("Synchronous load completed while waiting for async load");
return this.addonDB;
}
LOG("Finished async read of XPI database, parsing...");
let decoder = new TextDecoder();
let data = decoder.decode(byteArray);
this.parseDB(data, true);
return this.addonDB;
})
.then(null,
error => {
if (this._addonDB) {
LOG("Synchronous load completed while waiting for async load");
return this.addonDB;
}
if (error.becauseNoSuchFile) {
this.upgradeDB(true);
}
else {
// it's there but unreadable
this.rebuildUnreadableDB(error, true);
}
return this.addonDB;
});
}, },
/** /**
@ -766,13 +649,21 @@ this.XPIDatabase = {
} }
}, },
/**
* Lazy getter for the addons database
*/
get addonDB() {
this.openConnection(true);
return this.addonDB;
},
/** /**
* Gets the list of file descriptors of active extension directories or XPI * Gets the list of file descriptors of active extension directories or XPI
* files from the add-ons list. This must be loaded from disk since the * files from the add-ons list. This must be loaded from disk since the
* directory service gives no easy way to get both directly. This list doesn't * directory service gives no easy way to get both directly. This list doesn't
* include themes as preferences already say which theme is currently active * include themes as preferences already say which theme is currently active
* *
* @return an array of persistent descriptors for the directories * @return an array of persisitent descriptors for the directories
*/ */
getActiveBundles: function XPIDB_getActiveBundles() { getActiveBundles: function XPIDB_getActiveBundles() {
let bundles = []; let bundles = [];
@ -991,11 +882,27 @@ this.XPIDatabase = {
return 0; return 0;
}) })
.then(count => { .then(count => {
// Clear out the cached addons data loaded from JSON // Clear out the cached addons data loaded from JSON and recreate
// the getter to allow database re-loads during testing.
delete this.addonDB; delete this.addonDB;
delete this._dbPromise; Object.defineProperty(this, "addonDB", {
get: function addonsGetter() {
this.openConnection(true);
return this.addonDB;
},
configurable: true
});
// same for the deferred save // same for the deferred save
delete this._deferredSave; delete this._deferredSave;
Object.defineProperty(this, "_deferredSave", {
set: function deferredSaveGetter() {
delete this._deferredSave;
return this._deferredSave =
new DeferredSave(this.jsonFile.path, this.formJSON.bind(this),
ASYNC_SAVE_DELAY_MS);
},
configurable: true
});
// re-enable the schema version setter // re-enable the schema version setter
delete this._schemaVersionSet; delete this._schemaVersionSet;
@ -1013,8 +920,7 @@ this.XPIDatabase = {
* Return a list of all install locations known about by the database. This * Return a list of all install locations known about by the database. This
* is often a a subset of the total install locations when not all have * is often a a subset of the total install locations when not all have
* installed add-ons, occasionally a superset when an install location no * installed add-ons, occasionally a superset when an install location no
* longer exists. Only called from XPIProvider.processFileChanges, when * longer exists.
* the database should already be loaded.
* *
* @return a Set of names of install locations * @return a Set of names of install locations
*/ */
@ -1030,62 +936,61 @@ this.XPIDatabase = {
}, },
/** /**
* Asynchronously list all addons that match the filter function * List all addons that match the filter function
* @param aFilter * @param aFilter
* Function that takes an addon instance and returns * Function that takes an addon instance and returns
* true if that addon should be included in the selected array * true if that addon should be included in the selected array
* @param aCallback * @return an array of DBAddonInternals
* Called back with an array of addons matching aFilter
* or an empty array if none match
*/ */
getAddonList: function(aFilter, aCallback) { _listAddons: function XPIDB_listAddons(aFilter) {
this.asyncLoadDB().then( if (!this.addonDB)
addonDB => { return [];
let addonList = _filterDB(addonDB, aFilter);
asyncMap(addonList, getRepositoryAddon, safeCallback(aCallback)); let addonList = [];
}) for (let [key, addon] of this.addonDB) {
.then(null, if (aFilter(addon)) {
error => { addonList.push(addon);
ERROR("getAddonList failed", e); }
safeCallback(aCallback)([]); }
});
return addonList;
}, },
/** /**
* (Possibly asynchronously) get the first addon that matches the filter function * Find the first addon that matches the filter function
* @param aFilter * @param aFilter
* Function that takes an addon instance and returns * Function that takes an addon instance and returns
* true if that addon should be selected * true if that addon should be selected
* @param aCallback * @return The first DBAddonInternal for which the filter returns true
* Called back with the addon, or null if no matching addon is found
*/ */
getAddon: function(aFilter, aCallback) { _findAddon: function XPIDB_findAddon(aFilter) {
return this.asyncLoadDB().then( if (!this.addonDB)
addonDB => { return null;
getRepositoryAddon(_findAddon(addonDB, aFilter), safeCallback(aCallback));
}) for (let [key, addon] of this.addonDB) {
.then(null, if (aFilter(addon)) {
error => { return addon;
ERROR("getAddon failed", e); }
safeCallback(aCallback)(null); }
});
return null;
}, },
/** /**
* Synchronously reads all the add-ons in a particular install location. * Synchronously reads all the add-ons in a particular install location.
* Always called with the addon database already loaded.
* *
* @param aLocation * @param aLocation
* The name of the install location * The name of the install location
* @return an array of DBAddonInternals * @return an array of DBAddonInternals
*/ */
getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) { getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) {
return _filterDB(this.addonDB, aAddon => (aAddon.location == aLocation)); return this._listAddons(function inLocation(aAddon) {return (aAddon.location == aLocation);});
}, },
/** /**
* Asynchronously gets an add-on with a particular ID in a particular * Asynchronously gets an add-on with a particular ID in a particular
* install location. * install location.
* XXX IRVING sync for now
* *
* @param aId * @param aId
* The ID of the add-on to retrieve * The ID of the add-on to retrieve
@ -1095,13 +1000,12 @@ this.XPIDatabase = {
* A callback to pass the DBAddonInternal to * A callback to pass the DBAddonInternal to
*/ */
getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) { getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
this.asyncLoadDB().then( getRepositoryAddon(this.addonDB.get(aLocation + ":" + aId), aCallback);
addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId),
safeCallback(aCallback)));
}, },
/** /**
* Asynchronously gets the add-on with the specified ID that is visible. * Asynchronously gets the add-on with an ID that is visible.
* XXX IRVING sync
* *
* @param aId * @param aId
* The ID of the add-on to retrieve * The ID of the add-on to retrieve
@ -1109,12 +1013,13 @@ this.XPIDatabase = {
* A callback to pass the DBAddonInternal to * A callback to pass the DBAddonInternal to
*/ */
getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) { getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) {
this.getAddon(aAddon => ((aAddon.id == aId) && aAddon.visible), let addon = this._findAddon(function visibleID(aAddon) {return ((aAddon.id == aId) && aAddon.visible)});
aCallback); getRepositoryAddon(addon, aCallback);
}, },
/** /**
* Asynchronously gets the visible add-ons, optionally restricting by type. * Asynchronously gets the visible add-ons, optionally restricting by type.
* XXX IRVING sync
* *
* @param aTypes * @param aTypes
* An array of types to include or null to include all types * An array of types to include or null to include all types
@ -1122,10 +1027,10 @@ this.XPIDatabase = {
* A callback to pass the array of DBAddonInternals to * A callback to pass the array of DBAddonInternals to
*/ */
getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) { getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) {
this.getAddonList(aAddon => (aAddon.visible && let addons = this._listAddons(function visibleType(aAddon) {
(!aTypes || (aTypes.length == 0) || return (aAddon.visible && (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1)))
(aTypes.indexOf(aAddon.type) > -1))), });
aCallback); asyncMap(addons, getRepositoryAddon, aCallback);
}, },
/** /**
@ -1136,14 +1041,7 @@ this.XPIDatabase = {
* @return an array of DBAddonInternals * @return an array of DBAddonInternals
*/ */
getAddonsByType: function XPIDB_getAddonsByType(aType) { getAddonsByType: function XPIDB_getAddonsByType(aType) {
if (!this.addonDB) { return this._listAddons(function byType(aAddon) { return aAddon.type == aType; });
// jank-tastic! Must synchronously load DB if the theme switches from
// an XPI theme to a lightweight theme before the DB has loaded,
// because we're called from sync XPIProvider.addonChanged
WARN("Synchronous load of XPI database due to getAddonsByType(" + aType + ")");
this.syncLoadDB(true);
}
return _filterDB(this.addonDB, aAddon => (aAddon.type == aType));
}, },
/** /**
@ -1154,20 +1052,14 @@ this.XPIDatabase = {
* @return a DBAddonInternal * @return a DBAddonInternal
*/ */
getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) { getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) {
if (!this.addonDB) { return this._findAddon(function visibleInternalName(aAddon) {
// This may be called when the DB hasn't otherwise been loaded return (aAddon.visible && (aAddon.internalName == aInternalName));
// XXX TELEMETRY });
WARN("Synchronous load of XPI database due to getVisibleAddonForInternalName");
this.syncLoadDB(true);
}
return _findAddon(this.addonDB,
aAddon => aAddon.visible &&
(aAddon.internalName == aInternalName));
}, },
/** /**
* Asynchronously gets all add-ons with pending operations. * Asynchronously gets all add-ons with pending operations.
* XXX IRVING sync
* *
* @param aTypes * @param aTypes
* The types of add-ons to retrieve or null to get all types * The types of add-ons to retrieve or null to get all types
@ -1177,19 +1069,21 @@ this.XPIDatabase = {
getVisibleAddonsWithPendingOperations: getVisibleAddonsWithPendingOperations:
function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) { function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
this.getAddonList( let addons = this._listAddons(function visibleType(aAddon) {
aAddon => (aAddon.visible && return (aAddon.visible &&
(aAddon.pendingUninstall || (aAddon.pendingUninstall ||
// Logic here is tricky. If we're active but either // Logic here is tricky. If we're active but either
// disabled flag is set, we're pending disable; if we're not // disabled flag is set, we're pending disable; if we're not
// active and neither disabled flag is set, we're pending enable // active and neither disabled flag is set, we're pending enable
(aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) && (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) &&
(!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))), (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1)))
aCallback); });
asyncMap(addons, getRepositoryAddon, aCallback);
}, },
/** /**
* Asynchronously get an add-on by its Sync GUID. * Asynchronously get an add-on by its Sync GUID.
* XXX IRVING sync
* *
* @param aGUID * @param aGUID
* Sync GUID of add-on to fetch * Sync GUID of add-on to fetch
@ -1199,23 +1093,17 @@ this.XPIDatabase = {
* *
*/ */
getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) { getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) {
this.getAddon(aAddon => aAddon.syncGUID == aGUID, let addon = this._findAddon(function bySyncGUID(aAddon) { return aAddon.syncGUID == aGUID; });
aCallback); getRepositoryAddon(addon, aCallback);
}, },
/** /**
* Synchronously gets all add-ons in the database. * Synchronously gets all add-ons in the database.
* This is only called from the preference observer for the default
* compatibility version preference, so we can return an empty list if
* we haven't loaded the database yet.
* *
* @return an array of DBAddonInternals * @return an array of DBAddonInternals
*/ */
getAddons: function XPIDB_getAddons() { getAddons: function XPIDB_getAddons() {
if (!this.addonDB) { return this._listAddons(function(aAddon) {return true;});
return [];
}
return _filterDB(this.addonDB, aAddon => true);
}, },
/** /**
@ -1228,10 +1116,12 @@ this.XPIDatabase = {
* @return The DBAddonInternal that was added to the database * @return The DBAddonInternal that was added to the database
*/ */
addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) { addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) {
if (!this.addonDB) { // If there is no DB yet then forcibly create one
// XXX telemetry. Should never happen on platforms that have a default theme // XXX IRVING I don't think this will work as expected because the addonDB
this.syncLoadDB(false); // getter will kick in. Might not matter because of the way the new DB
} // creates itself.
if (!this.addonDB)
this.openConnection(false, true);
let newAddon = new DBAddonInternal(aAddon); let newAddon = new DBAddonInternal(aAddon);
newAddon.descriptor = aDescriptor; newAddon.descriptor = aDescriptor;
@ -1292,7 +1182,7 @@ this.XPIDatabase = {
*/ */
makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
LOG("Make addon " + aAddon._key + " visible"); LOG("Make addon " + aAddon._key + " visible");
for (let [, otherAddon] of this.addonDB) { for (let [key, otherAddon] of this.addonDB) {
if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
LOG("Hide addon " + otherAddon._key); LOG("Hide addon " + otherAddon._key);
otherAddon.visible = false; otherAddon.visible = false;
@ -1319,7 +1209,6 @@ this.XPIDatabase = {
/** /**
* Synchronously sets the Sync GUID for an add-on. * Synchronously sets the Sync GUID for an add-on.
* Only called when the database is already loaded.
* *
* @param aAddon * @param aAddon
* The DBAddonInternal being updated * The DBAddonInternal being updated
@ -1332,7 +1221,7 @@ this.XPIDatabase = {
function excludeSyncGUID(otherAddon) { function excludeSyncGUID(otherAddon) {
return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID); return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID);
} }
let otherAddon = _findAddon(this.addonDB, excludeSyncGUID); let otherAddon = this._findAddon(excludeSyncGUID);
if (otherAddon) { if (otherAddon) {
throw new Error("Addon sync GUID conflict for addon " + aAddon._key + throw new Error("Addon sync GUID conflict for addon " + aAddon._key +
": " + otherAddon._key + " already has GUID " + aGUID); ": " + otherAddon._key + " already has GUID " + aGUID);
@ -1375,7 +1264,7 @@ this.XPIDatabase = {
// XXX IRVING this may get called during XPI-utils shutdown // XXX IRVING this may get called during XPI-utils shutdown
// XXX need to make sure PREF_PENDING_OPERATIONS handling is clean // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean
LOG("Updating add-on states"); LOG("Updating add-on states");
for (let [, addon] of this.addonDB) { for (let [key, addon] of this.addonDB) {
let newActive = (addon.visible && !addon.userDisabled && let newActive = (addon.visible && !addon.userDisabled &&
!addon.softDisabled && !addon.appDisabled && !addon.softDisabled && !addon.appDisabled &&
!addon.pendingUninstall); !addon.pendingUninstall);
@ -1390,10 +1279,6 @@ this.XPIDatabase = {
* Writes out the XPI add-ons list for the platform to read. * Writes out the XPI add-ons list for the platform to read.
*/ */
writeAddonsList: function XPIDB_writeAddonsList() { writeAddonsList: function XPIDB_writeAddonsList() {
if (!this.addonDB) {
// Unusual condition, force the DB to load
this.syncLoadDB(true);
}
Services.appinfo.invalidateCachesOnRestart(); Services.appinfo.invalidateCachesOnRestart();
let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
@ -1403,9 +1288,9 @@ this.XPIDatabase = {
let count = 0; let count = 0;
let fullCount = 0; let fullCount = 0;
let activeAddons = _filterDB( let activeAddons = this._listAddons(function active(aAddon) {
this.addonDB, return aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme");
aAddon => aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme")); });
for (let row of activeAddons) { for (let row of activeAddons) {
text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
@ -1425,13 +1310,12 @@ this.XPIDatabase = {
let themes = []; let themes = [];
if (dssEnabled) { if (dssEnabled) {
themes = _filterDB(this.addonDB, aAddon => aAddon.type == "theme"); themes = this._listAddons(function isTheme(aAddon){ return aAddon.type == "theme"; });
} }
else { else {
let activeTheme = _findAddon( let activeTheme = this._findAddon(function isSelected(aAddon) {
this.addonDB, return ((aAddon.type == "theme") && (aAddon.internalName == XPIProvider.selectedSkin));
aAddon => (aAddon.type == "theme") && });
(aAddon.internalName == XPIProvider.selectedSkin));
if (activeTheme) { if (activeTheme) {
themes.push(activeTheme); themes.push(activeTheme);
} }

View File

@ -45,26 +45,25 @@ function run_test() {
let internal_ids = {}; let internal_ids = {};
let a = ["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"]; [["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"]
stmt.params.id = a[0]; ].forEach(function(a) {
stmt.params.location = a[1]; stmt.params.id = a[0];
stmt.params.version = a[2]; stmt.params.location = a[1];
stmt.params.active = a[3]; stmt.params.version = a[2];
stmt.params.userDisabled = a[4]; stmt.params.active = a[3];
stmt.params.installDate = a[5]; stmt.params.userDisabled = a[4];
stmt.execute(); stmt.params.installDate = a[5];
internal_ids[a[0]] = db.lastInsertRowID; stmt.execute();
internal_ids[a[0]] = db.lastInsertRowID;
});
stmt.finalize(); stmt.finalize();
db.schemaVersion = 14; db.schemaVersion = 15;
Services.prefs.setIntPref("extensions.databaseSchema", 14); Services.prefs.setIntPref("extensions.databaseSchema", 14);
db.close(); db.close();
startupManager(); startupManager();
run_next_test();
}
add_test(function before_rebuild() {
AddonManager.getAddonByID("addon1@tests.mozilla.org", AddonManager.getAddonByID("addon1@tests.mozilla.org",
function check_before_rebuild (a1) { function check_before_rebuild (a1) {
// First check that it migrated OK once // First check that it migrated OK once
@ -78,7 +77,7 @@ add_test(function before_rebuild() {
run_next_test(); run_next_test();
}); });
}); }
// now shut down, remove the JSON database, // now shut down, remove the JSON database,
// start up again, and make sure the data didn't migrate this time // start up again, and make sure the data didn't migrate this time