Bug 1049142: don't scan unpacked extensions if disabled; r=Unfocused

This commit is contained in:
Irving Reid 2014-09-16 12:48:15 -04:00
Родитель fb8d48718c
Коммит b6b6a6c4f5
11 изменённых файлов: 895 добавлений и 370 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -376,7 +376,7 @@ DBAddonInternal.prototype = new DBAddonInternalPrototype();
* Internal interface: find an addon from an already loaded addonDB
*/
function _findAddon(addonDB, aFilter) {
for (let [, addon] of addonDB) {
for (let addon of addonDB.values()) {
if (aFilter(addon)) {
return addon;
}
@ -388,14 +388,7 @@ function _findAddon(addonDB, aFilter) {
* 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;
return [for (addon of addonDB.values()) if (aFilter(addon)) addon];
}
this.XPIDatabase = {
@ -766,7 +759,7 @@ this.XPIDatabase = {
this.addonDB = new Map();
this.initialized = true;
if (XPIProvider.installStates && XPIProvider.installStates.length == 0) {
if (XPIStates.size == 0) {
// No extensions installed, so we're done
logger.debug("Rebuilding XPI database with no extensions");
return;
@ -780,7 +773,7 @@ this.XPIDatabase = {
if (aRebuildOnError) {
logger.warn("Rebuilding add-ons database from installed extensions.");
try {
XPIProvider.processFileChanges(XPIProvider.installStates, {}, false);
XPIProvider.processFileChanges({}, false);
}
catch (e) {
logger.error("Failed to rebuild XPI database from installed extensions", e);
@ -1042,26 +1035,6 @@ this.XPIDatabase = {
return Promise.resolve(0);
},
/**
* 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
* installed add-ons, occasionally a superset when an install location no
* longer exists. Only called from XPIProvider.processFileChanges, when
* the database should already be loaded.
*
* @return a Set of names of install locations
*/
getInstallLocations: function XPIDB_getInstallLocations() {
let locations = new Set();
if (!this.addonDB)
return locations;
for (let [, addon] of this.addonDB) {
locations.add(addon.location);
}
return locations;
},
/**
* Asynchronously list all addons that match the filter function
* @param aFilter
@ -1104,18 +1077,6 @@ this.XPIDatabase = {
});
},
/**
* Synchronously reads all the add-ons in a particular install location.
* Always called with the addon database already loaded.
*
* @param aLocation
* The name of the install location
* @return an array of DBAddonInternals
*/
getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) {
return _filterDB(this.addonDB, aAddon => (aAddon.location == aLocation));
},
/**
* Asynchronously gets an add-on with a particular ID in a particular
* install location.
@ -1215,10 +1176,9 @@ this.XPIDatabase = {
this.getAddonList(
aAddon => (aAddon.visible &&
(aAddon.pendingUninstall ||
// Logic here is tricky. If we're active but either
// disabled flag is set, we're pending disable; if we're not
// active and neither disabled flag is set, we're pending enable
(aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) &&
// Logic here is tricky. If we're active but disabled,
// we're pending disable; !active && !disabled, we're pending enable
(aAddon.active == aAddon.disabled)) &&
(!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))),
aCallback);
},
@ -1299,8 +1259,7 @@ this.XPIDatabase = {
aNewAddon.installDate = aOldAddon.installDate;
aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
aNewAddon.foreignInstall = aOldAddon.foreignInstall;
aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled &&
!aNewAddon.appDisabled && !aNewAddon.pendingUninstall);
aNewAddon.active = (aNewAddon.visible && !aNewAddon.disabled && !aNewAddon.pendingUninstall);
// addAddonMetadata does a saveChanges()
return this.addAddonMetadata(aNewAddon, aDescriptor);
@ -1401,9 +1360,7 @@ this.XPIDatabase = {
}
logger.debug("Updating add-on states");
for (let [, addon] of this.addonDB) {
let newActive = (addon.visible && !addon.userDisabled &&
!addon.softDisabled && !addon.appDisabled &&
!addon.pendingUninstall);
let newActive = (addon.visible && !addon.disabled && !addon.pendingUninstall);
if (newActive != addon.active) {
addon.active = newActive;
this.saveChanges();

Просмотреть файл

@ -652,7 +652,7 @@ function createInstallRDF(aData) {
/**
* Writes an install.rdf manifest into a directory using the properties passed
* in a JS object. The objects should contain a property for each property to
* appear in the RDFThe object may contain an array of objects with id,
* appear in the RDF. The object may contain an array of objects with id,
* minVersion and maxVersion in the targetApplications property to give target
* application compatibility.
*
@ -660,14 +660,22 @@ function createInstallRDF(aData) {
* The object holding data about the add-on
* @param aDir
* The directory to add the install.rdf to
* @param aId
* An optional string to override the default installation aId
* @param aExtraFile
* An optional dummy file to create in the directory
* @return An nsIFile for the directory in which the add-on is installed.
*/
function writeInstallRDFToDir(aData, aDir, aExtraFile) {
function writeInstallRDFToDir(aData, aDir, aId, aExtraFile) {
var id = aId ? aId : aData.id
var dir = aDir.clone();
dir.append(id);
var rdf = createInstallRDF(aData);
if (!aDir.exists())
aDir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
var file = aDir.clone();
if (!dir.exists())
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
var file = dir.clone();
file.append("install.rdf");
if (file.exists())
file.remove(true);
@ -680,17 +688,18 @@ function writeInstallRDFToDir(aData, aDir, aExtraFile) {
fos.close();
if (!aExtraFile)
return;
return dir;
file = aDir.clone();
file = dir.clone();
file.append(aExtraFile);
file.create(AM_Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
return dir;
}
/**
* Writes an install.rdf manifest into an extension using the properties passed
* in a JS object. The objects should contain a property for each property to
* appear in the RDFThe object may contain an array of objects with id,
* appear in the RDF. The object may contain an array of objects with id,
* minVersion and maxVersion in the targetApplications property to give target
* application compatibility.
*
@ -705,16 +714,34 @@ function writeInstallRDFToDir(aData, aDir, aExtraFile) {
* @return A file pointing to where the extension was installed
*/
function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
if (TEST_UNPACKED) {
return writeInstallRDFToDir(aData, aDir, aId, aExtraFile);
}
return writeInstallRDFToXPI(aData, aDir, aId, aExtraFile);
}
/**
* Writes an install.rdf manifest into a packed extension using the properties passed
* in a JS object. The objects should contain a property for each property to
* appear in the RDF. The object may contain an array of objects with id,
* minVersion and maxVersion in the targetApplications property to give target
* application compatibility.
*
* @param aData
* The object holding data about the add-on
* @param aDir
* The install directory to add the extension to
* @param aId
* An optional string to override the default installation aId
* @param aExtraFile
* An optional dummy file to create in the extension
* @return A file pointing to where the extension was installed
*/
function writeInstallRDFToXPI(aData, aDir, aId, aExtraFile) {
var id = aId ? aId : aData.id
var dir = aDir.clone();
if (TEST_UNPACKED) {
dir.append(id);
writeInstallRDFToDir(aData, dir, aExtraFile);
return dir;
}
if (!dir.exists())
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
dir.append(id + ".xpi");

Просмотреть файл

@ -0,0 +1,299 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Test that we only check manifest age for disabled extensions
Components.utils.import("resource://gre/modules/Promise.jsm");
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
const profileDir = gProfD.clone();
profileDir.append("extensions");
/* We want one add-on installed packed, and one installed unpacked
*/
function run_test() {
// Shut down the add-on manager after all tests run.
do_register_cleanup(promiseShutdownManager);
// Kick off the task-based tests...
run_next_test();
}
// Use bootstrap extensions so the changes will be immediate.
// A packed extension, to be enabled
writeInstallRDFToXPI({
id: "packed-enabled@tests.mozilla.org",
version: "1.0",
bootstrap: true,
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}],
name: "Packed, Enabled",
}, profileDir);
// Packed, will be disabled
writeInstallRDFToXPI({
id: "packed-disabled@tests.mozilla.org",
version: "1.0",
bootstrap: true,
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}],
name: "Packed, Disabled",
}, profileDir);
// Unpacked, enabled
writeInstallRDFToDir({
id: "unpacked-enabled@tests.mozilla.org",
version: "1.0",
bootstrap: true,
unpack: true,
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}],
name: "Unpacked, Enabled",
}, profileDir, null, "extraFile.js");
// Unpacked, disabled
writeInstallRDFToDir({
id: "unpacked-disabled@tests.mozilla.org",
version: "1.0",
bootstrap: true,
unpack: true,
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}],
name: "Unpacked, disabled",
}, profileDir, null, "extraFile.js");
// Keep track of the last time stamp we've used, so that we can keep moving
// it forward (if we touch two different files in the same add-on with the same
// timestamp we may not consider the change significant)
let lastTimestamp = Date.now();
/*
* Helper function to touch a file and then test whether we detect the change.
* @param XS The XPIState object.
* @param aPath File path to touch.
* @param aChange True if we should notice the change, False if we shouldn't.
*/
function checkChange(XS, aPath, aChange) {
do_check_true(aPath.exists());
lastTimestamp += 10000;
do_print("Touching file " + aPath.path + " with " + lastTimestamp);
aPath.lastModifiedTime = lastTimestamp;
do_check_eq(XS.getInstallState(), aChange);
// Save the pref so we don't detect this change again
XS.save();
}
// Get a reference to the XPIState (loaded by startupManager) so we can unit test it.
function getXS() {
let XPI = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
return XPI.XPIStates;
}
add_task(function* detect_touches() {
startupManager();
let [pe, pd, ue, ud] = yield promiseAddonsByIDs([
"packed-enabled@tests.mozilla.org",
"packed-disabled@tests.mozilla.org",
"unpacked-enabled@tests.mozilla.org",
"unpacked-disabled@tests.mozilla.org"
]);
do_print("Disable test add-ons");
pd.userDisabled = true;
ud.userDisabled = true;
let XS = getXS();
// Should be no changes detected here, because everything should start out up-to-date.
do_check_false(XS.getInstallState());
let states = XS.getLocation("app-profile");
// State should correctly reflect enabled/disabled
do_check_true(states.get("packed-enabled@tests.mozilla.org").enabled);
do_check_false(states.get("packed-disabled@tests.mozilla.org").enabled);
do_check_true(states.get("unpacked-enabled@tests.mozilla.org").enabled);
do_check_false(states.get("unpacked-disabled@tests.mozilla.org").enabled);
// Touch various files and make sure the change is detected.
// We notice that a packed XPI is touched for an enabled add-on.
let peFile = profileDir.clone();
peFile.append("packed-enabled@tests.mozilla.org.xpi");
checkChange(XS, peFile, true);
// We should notice the packed XPI change for a disabled add-on too.
let pdFile = profileDir.clone();
pdFile.append("packed-disabled@tests.mozilla.org.xpi");
checkChange(XS, pdFile, true);
// We notice changing install.rdf for an enabled unpacked add-on.
let ueDir = profileDir.clone();
ueDir.append("unpacked-enabled@tests.mozilla.org");
let manifest = ueDir.clone();
manifest.append("install.rdf");
checkChange(XS, manifest, true);
// We also notice changing another file for enabled unpacked add-on.
let otherFile = ueDir.clone();
otherFile.append("extraFile.js");
checkChange(XS, otherFile, true);
// We notice changing install.rdf for a *disabled* unpacked add-on.
let udDir = profileDir.clone();
udDir.append("unpacked-disabled@tests.mozilla.org");
manifest = udDir.clone();
manifest.append("install.rdf");
checkChange(XS, manifest, true);
// Finally, the case we actually care about...
// We *don't* notice changing another file for disabled unpacked add-on.
otherFile = udDir.clone();
otherFile.append("extraFile.js");
checkChange(XS, otherFile, false);
/*
* When we enable an unpacked add-on that was modified while it was
* disabled, we reflect the new timestamp in the add-on DB (otherwise, we'll
* think it changed on next restart).
*/
ud.userDisabled = false;
let xState = XS.getAddon("app-profile", ud.id);
do_check_true(xState.enabled);
do_check_eq(xState.scanTime, ud.updateDate.getTime());
});
/*
* Uninstalling bootstrap add-ons should immediately remove them from the
* extensions.xpiState preference.
*/
add_task(function* uninstall_bootstrap() {
let [pe, pd, ue, ud] = yield promiseAddonsByIDs([
"packed-enabled@tests.mozilla.org",
"packed-disabled@tests.mozilla.org",
"unpacked-enabled@tests.mozilla.org",
"unpacked-disabled@tests.mozilla.org"
]);
pe.uninstall();
let xpiState = Services.prefs.getCharPref("extensions.xpiState");
do_check_false(xpiState.contains("\"packed-enabled@tests.mozilla.org\""));
});
/*
* Installing a restartless add-on should immediately add it to XPIState
*/
add_task(function* install_bootstrap() {
let XS = getXS();
let installer = yield new Promise((resolve, reject) =>
AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_1"), resolve));
let promiseInstalled = new Promise((resolve, reject) => {
AddonManager.addInstallListener({
onInstallFailed: reject,
onInstallEnded: (install, newAddon) => resolve(newAddon)
});
});
installer.install();
let newAddon = yield promiseInstalled;
let xState = XS.getAddon("app-profile", newAddon.id);
do_check_true(!!xState);
do_check_true(xState.enabled);
do_check_eq(xState.scanTime, newAddon.updateDate.getTime());
newAddon.uninstall();
});
/*
* Installing an add-on that requires restart doesn't add to XPIState
* until after the restart; disable and enable happen immediately so that
* the next restart won't / will scan as necessary on the next restart,
* uninstalling it marks XPIState as disabled immediately
* and removes XPIState after restart.
*/
add_task(function* install_restart() {
let XS = getXS();
let installer = yield new Promise((resolve, reject) =>
AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_4"), resolve));
let promiseInstalled = new Promise((resolve, reject) => {
AddonManager.addInstallListener({
onInstallFailed: reject,
onInstallEnded: (install, newAddon) => resolve(newAddon)
});
});
installer.install();
let newAddon = yield promiseInstalled;
let newID = newAddon.id;
let xState = XS.getAddon("app-profile", newID);
do_check_false(xState);
// Now we restart the add-on manager, and we need to get the XPIState again
// because the add-on manager reloads it.
XS = null;
newAddon = null;
yield promiseRestartManager();
XS = getXS();
newAddon = yield promiseAddonByID(newID);
xState = XS.getAddon("app-profile", newID);
do_check_true(xState);
do_check_true(xState.enabled);
do_check_eq(xState.scanTime, newAddon.updateDate.getTime());
// Check that XPIState enabled flag is updated immediately,
// and doesn't change over restart.
newAddon.userDisabled = true;
do_check_false(xState.enabled);
XS = null;
newAddon = null;
yield promiseRestartManager();
XS = getXS();
xState = XS.getAddon("app-profile", newID);
do_check_true(xState);
do_check_false(xState.enabled);
newAddon = yield promiseAddonByID(newID);
newAddon.userDisabled = false;
do_check_true(xState.enabled);
XS = null;
newAddon = null;
yield promiseRestartManager();
XS = getXS();
xState = XS.getAddon("app-profile", newID);
do_check_true(xState);
do_check_true(xState.enabled);
// Uninstalling immediately marks XPIState disabled,
// removes state after restart.
newAddon = yield promiseAddonByID(newID);
newAddon.uninstall();
xState = XS.getAddon("app-profile", newID);
do_check_true(xState);
do_check_false(xState.enabled);
// Restart to finish uninstall.
XS = null;
newAddon = null;
yield promiseRestartManager();
XS = getXS();
xState = XS.getAddon("app-profile", newID);
do_check_false(xState);
});

Просмотреть файл

@ -126,11 +126,7 @@ function getUninstallNewVersion() {
}
function do_check_bootstrappedPref(aCallback) {
let data = "{}";
try {
// This is ok to fail, as the pref won't exist on a fresh profile.
data = Services.prefs.getCharPref("extensions.bootstrappedAddons");
} catch (e) {}
let data = Services.prefs.getCharPref("extensions.bootstrappedAddons");
data = JSON.parse(data);
AddonManager.getAddonsByTypes(["extension"], function(aAddons) {
@ -153,7 +149,7 @@ function do_check_bootstrappedPref(aCallback) {
}
do_check_eq(Object.keys(data).length, 0);
aCallback();
do_execute_soon(aCallback);
});
}
@ -169,7 +165,7 @@ function run_test() {
do_check_false(gExtensionsINI.exists());
do_check_bootstrappedPref(run_test_1);
run_test_1();
}
// Tests that installing doesn't require a restart
@ -853,7 +849,7 @@ function check_test_15() {
do_check_bootstrappedPref(function() {
restartManager();
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
do_check_neq(b1, null);
do_check_eq(b1.version, "2.0");
do_check_false(b1.appDisabled);
@ -865,7 +861,7 @@ function check_test_15() {
b1.uninstall();
run_test_16();
});
}));
});
});
}
@ -946,7 +942,7 @@ function run_test_17() {
// the existing one
function run_test_18() {
resetPrefs();
waitForPref("bootstraptest.startup_reason", function test_16_after_startup() {
waitForPref("bootstraptest.startup_reason", function test_18_after_startup() {
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
// Should have installed and started
do_check_eq(getInstalledVersion(), 2);

Просмотреть файл

@ -18,18 +18,18 @@ function run_test()
// Install test add-on
startupManager();
installAllFiles([do_get_addon(ADDON)], function() {
AddonManager.getAddonByID(ID, function(addon) {
AddonManager.getAddonByID(ID, callback_soon(function(addon) {
do_check_neq(addon, null);
do_check_eq(addon.name, "Test theme");
restartManager();
AddonManager.getAddonByID(ID, function(addon) {
AddonManager.getAddonByID(ID, callback_soon(function(addon) {
do_check_neq(addon, null);
do_check_eq(addon.optionsURL, null);
do_check_eq(addon.aboutURL, null);
do_execute_soon(do_test_finished);
});
});
}));
}));
});
}

Просмотреть файл

@ -103,22 +103,21 @@ function run_test() {
let stagedXPIs = profileDir.clone();
stagedXPIs.append("staged-xpis");
stagedXPIs.append("addon6@tests.mozilla.org");
stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let addon6 = do_get_addon("test_migrate6");
addon6.copyTo(stagedXPIs, "tmp.xpi");
stagedXPIs = stagedXPIs.parent;
stagedXPIs.append("addon7@tests.mozilla.org");
stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let addon7 = do_get_addon("test_migrate7");
addon7.copyTo(stagedXPIs, "tmp.xpi");
stagedXPIs = stagedXPIs.parent;
stagedXPIs.append("addon8@tests.mozilla.org");
stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let addon8 = do_get_addon("test_migrate8");
addon8.copyTo(stagedXPIs, "tmp.xpi");
stagedXPIs = stagedXPIs.parent;

Просмотреть файл

@ -171,7 +171,7 @@ function run_test_1() {
setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
writeInstallRDFForExtension(addon3, profileDir);
writeInstallRDFForExtension(addon4, profileDir);
writeInstallRDFForExtension(addon4, profileDir, "addon4@tests.mozilla.org");
writeInstallRDFForExtension(addon5, profileDir);
writeInstallRDFForExtension(addon6, profileDir);
writeInstallRDFForExtension(addon7, profileDir);

Просмотреть файл

@ -18,13 +18,6 @@ Components.utils.import("resource://testing-common/httpd.js");
const profileDir = gProfD.clone();
profileDir.append("extensions");
// Return a promise that resolves with an addon retrieved by
// AddonManager.getAddonByID()
function promiseGetAddon(aID) {
let p = Promise.defer();
AddonManager.getAddonByID(aID, p.resolve);
return p.promise;
}
function run_test() {
// Kick off the task-based tests...
@ -85,7 +78,7 @@ writeInstallRDFForExtension({
add_task(function cancel_during_check() {
startupManager();
let a1 = yield promiseGetAddon("addon1@tests.mozilla.org");
let a1 = yield promiseAddonByID("addon1@tests.mozilla.org");
do_check_neq(a1, null);
let listener = makeCancelListener();
@ -120,7 +113,7 @@ add_task(function shutdown_during_check() {
// Reset our HTTP listener
httpReceived = Promise.defer();
let a1 = yield promiseGetAddon("addon1@tests.mozilla.org");
let a1 = yield promiseAddonByID("addon1@tests.mozilla.org");
do_check_neq(a1, null);
let listener = makeCancelListener();

Просмотреть файл

@ -15,6 +15,8 @@ skip-if = os == "android"
[test_bad_json.js]
[test_badschema.js]
[test_blocklistchange.js]
# Times out during parallel runs on desktop
requesttimeoutfactor = 2
[test_blocklist_prefs.js]
[test_blocklist_metadata_filters.js]
# Bug 676992: test consistently hangs on Android
@ -210,6 +212,8 @@ skip-if = os == "android"
[test_migrate2.js]
[test_migrate3.js]
[test_migrate4.js]
# Times out during parallel runs on desktop
requesttimeoutfactor = 2
[test_migrate5.js]
[test_migrateAddonRepository.js]
[test_migrate_max_version.js]

Просмотреть файл

@ -8,8 +8,6 @@ support-files =
data/**
xpcshell-shared.ini
[include:xpcshell-shared.ini]
[test_addon_path_service.js]
[test_asyncBlocklistLoad.js]
[test_DeferredSave.js]
@ -18,3 +16,6 @@ support-files =
run-if = appname == "firefox"
[test_shutdown.js]
[test_XPIcancel.js]
[test_XPIStates.js]
[include:xpcshell-shared.ini]