diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 863b3d3c1195..f0790abdbf4e 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -782,22 +782,53 @@ var XPIProvider = { this.installLocations = []; this.installLocationsByName = {}; - // These must be in order of priority for processFileChanges etc. to work - [ - [KEY_APP_GLOBAL, KEY_APPDIR, [DIR_EXTENSIONS], true], - [KEY_APP_SYSTEM_LOCAL, "XRESysLExtPD", [Services.appinfo.ID], true], - [KEY_APP_SYSTEM_SHARE, "XRESysSExtPD", [Services.appinfo.ID], true], - [KEY_APP_SYSTEM_USER, "XREUSysExt", [Services.appinfo.ID], true], - [KEY_APP_PROFILE, KEY_PROFILEDIR, [DIR_EXTENSIONS], false] - ].forEach(function([name, key, paths, locked]) { + function addDirectoryInstallLocation(name, key, paths, locked) { try { - let dir = FileUtils.getDir(key, paths); - let location = new DirectoryInstallLocation(name, dir, locked); - this.installLocations.push(location); - this.installLocationsByName[location.name] = location; + var dir = FileUtils.getDir(key, paths); } - catch (e) { } - }, this); + catch (e) { + // Some directories aren't defined on some platforms, ignore them + LOG("Skipping unavailable install location " + name); + return; + } + + try { + var location = new DirectoryInstallLocation(name, dir, locked); + } + catch (e) { + WARN("Failed to add directory install location " + name + " " + e); + return; + } + + XPIProvider.installLocations.push(location); + XPIProvider.installLocationsByName[location.name] = location; + } + + function addRegistryInstallLocation(name, rootkey) { + try { + var location = new WinRegInstallLocation(name, rootkey); + } + catch (e) { + WARN("Failed to add registry install location " + name + " " + e); + return; + } + + XPIProvider.installLocations.push(location); + XPIProvider.installLocationsByName[location.name] = location; + } + + let hasRegistry = ("nsIWindowsRegKey" in Ci); + + // These must be in order of priority for processFileChanges etc. to work + if (hasRegistry) + addRegistryInstallLocation("winreg-app-global", Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE); + addDirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL, "XRESysLExtPD", [Services.appinfo.ID], true); + addDirectoryInstallLocation(KEY_APP_SYSTEM_SHARE, "XRESysSExtPD", [Services.appinfo.ID], true); + addDirectoryInstallLocation(KEY_APP_GLOBAL, KEY_APPDIR, [DIR_EXTENSIONS], true); + if (hasRegistry) + addRegistryInstallLocation("winreg-app-user", Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER); + addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt", [Services.appinfo.ID], true); + addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR, [DIR_EXTENSIONS], false); this.defaultSkin = Prefs.getDefaultCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, "classic/1.0"); @@ -1092,7 +1123,10 @@ var XPIProvider = { catch (e) { WARN("Add-on is invalid: " + e); XPIDatabase.removeAddonMetadata(oldAddon); - installLocation.uninstallAddon(oldAddon.id); + if (!installLocation.locked) + installLocation.uninstallAddon(oldAddon.id); + else + WARN("Could not uninstall invalid item from locked install location"); // If this was an active add-on then we must force a restart if (oldAddon.active) { if (oldAddon.type == "bootstrapped") @@ -1277,9 +1311,12 @@ var XPIProvider = { catch (e) { WARN("Add-on is invalid: " + e); - // Remove the invalid add-on from the install location, no restart will - // be necessary - installLocation.uninstallAddon(id); + // Remove the invalid add-on from the install location if the install + // location isn't locked, no restart will be necessary + if (!installLocation.locked) + installLocation.uninstallAddon(id); + else + WARN("Could not uninstall invalid item from locked install location"); return false; } @@ -4582,11 +4619,13 @@ function WinRegInstallLocation(name, rootKey) { // cases, we just leave ourselves in the empty state. try { key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ); - this._readAddons(key); } - catch (e) { } - if (key) - key.close(); + catch (e) { + return; + } + + this._readAddons(key); + key.close(); } WinRegInstallLocation.prototype = { @@ -4635,6 +4674,9 @@ WinRegInstallLocation.prototype = { this._IDToDirMap[id] = dir; this._DirToIDMap[dir.path] = id; } + else { + WARN("Ignoring missing add-on in " + dir.path); + } } }, diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 9343906cb1ba..e8149ffda3b8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -49,7 +49,7 @@ function createAppInfo(id, name, version, platformVersion) { AM_Ci.nsICrashReporter, AM_Ci.nsISupports]) }; - + var XULAppInfoFactory = { createInstance: function (outer, iid) { if (outer != null) @@ -528,6 +528,116 @@ function ensure_test_completed() { if (gExpectedInstalls) do_check_eq(gExpectedInstalls.length, 0); } + +if ("nsIWindowsRegKey" in AM_Ci) { + var MockRegistry = { + LOCAL_MACHINE: {}, + CURRENT_USER: {}, + + setValue: function(root, path, name, value) { + switch (root) { + case AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE: + var rootKey = MockRegistry.LOCAL_MACHINE; + break + case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER: + rootKey = MockRegistry.CURRENT_USER; + break + } + + if (!(path in rootKey)) { + rootKey[path] = []; + } + else { + for (let i = 0; i < rootKey[path].length; i++) { + if (rootKey[path][i].name == name) { + if (value === null) + rootKey[path].splice(i, 1); + else + rootKey[path][i].value = value; + return; + } + } + } + + if (value === null) + return; + + rootKey[path].push({ + name: name, + value: value + }); + } + }; + + /** + * This is a mock nsIWindowsRegistry implementation. It only implements the + * methods that the extension manager requires. + */ + function MockWindowsRegKey() { + } + + MockWindowsRegKey.prototype = { + values: null, + + // --- Overridden nsISupports interface functions --- + QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIWindowsRegKey]), + + // --- Overridden nsIWindowsRegKey interface functions --- + open: function(aRootKey, aRelPath, aMode) { + switch (aRootKey) { + case AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE: + var rootKey = MockRegistry.LOCAL_MACHINE; + break + case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER: + rootKey = MockRegistry.CURRENT_USER; + break + } + + if (!(aRelPath in rootKey)) + rootKey[aRelPath] = []; + this.values = rootKey[aRelPath]; + }, + + close: function() { + this.values = null; + }, + + get valueCount() { + if (!this.values) + throw Components.results.NS_ERROR_FAILURE; + return this.values.length; + }, + + getValueName: function(aIndex) { + if (!this.values || aIndex >= this.values.length) + throw Components.results.NS_ERROR_FAILURE; + return this.values[aIndex].name; + }, + + readStringValue: function(aName) { + for (let i = 0; i < this.values.length; i++) { + if (this.values[i].name == aName) + return this.values[i].value; + } + } + }; + + var WinRegFactory = { + createInstance: function(aOuter, aIid) { + if (aOuter != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + + var key = new MockWindowsRegKey(); + return key.QueryInterface(aIid); + } + }; + + var registrar = Components.manager.QueryInterface(AM_Ci.nsIComponentRegistrar); + registrar.registerFactory(Components.ID("{0478de5b-0f38-4edb-851d-4c99f1ed8eba}"), + "Mock Windows Registry Implementation", + "@mozilla.org/windows-registry-key;1", WinRegFactory); +} + // Get the profile directory for tests to use. const gProfD = do_get_profile().QueryInterface(AM_Ci.nsILocalFile); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_registry.js b/toolkit/mozapps/extensions/test/xpcshell/test_registry.js new file mode 100644 index 000000000000..e575767c4f3e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_registry.js @@ -0,0 +1,147 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that extensions installed through the registry work as expected +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var addon2 = { + id: "addon2@tests.mozilla.org", + version: "2.0", + name: "Test 2", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] +}; + +const addon1Dir = gProfD.clone(); +addon1Dir.append("addon1"); +writeInstallRDFToDir(addon1, addon1Dir); +const addon2Dir = gProfD.clone(); +addon2Dir.append("addon2"); +writeInstallRDFToDir(addon2, addon2Dir); + +function run_test() { + // This test only works where there is a registry. + if (!("nsIWindowsRegKey" in AM_Ci)) + return; + + do_test_pending(); + + run_test_1(); +} + +// Tests whether basic registry install works +function run_test_1() { + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon1@tests.mozilla.org", addon1Dir.path); + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon2@tests.mozilla.org", addon2Dir.path); + + startupManager(1); + + AddonManager.getAddons(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org"], function([a1, a2]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(hasFlag(a1.permissions, AddonManager.PERM_CAN_UNINSTALL)); + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_false(hasFlag(a2.permissions, AddonManager.PERM_CAN_UNINSTALL)); + + run_test_2(); + }); +} + +// Tests whether uninstalling from the registry works +function run_test_2() { + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon1@tests.mozilla.org", null); + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon2@tests.mozilla.org", null); + + restartManager(1); + + AddonManager.getAddons(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org"], function([a1, a2]) { + do_check_eq(a1, null); + do_check_eq(a2, null); + + run_test_3(); + }); +} + +// Checks that the ID in the registry must match that in the install manifest +function run_test_3() { + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon1@tests.mozilla.org", addon2Dir.path); + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon2@tests.mozilla.org", addon1Dir.path); + + restartManager(0); + + AddonManager.getAddons(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org"], function([a1, a2]) { + do_check_eq(a1, null); + do_check_eq(a2, null); + + // Restarting with bad items in the registry should not force an EM restart + restartManager(0); + + run_test_4(); + }); +} + +// Tests whether an extension's ID can change without its directory changing +function run_test_4() { + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon1@tests.mozilla.org", null); + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon2@tests.mozilla.org", null); + + restartManager(0); + + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon1@tests.mozilla.org", addon1Dir.path); + restartManager(1); + + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon1@tests.mozilla.org", null); + MockRegistry.setValue(AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "SOFTWARE\\Mozilla\\XPCShell\\Extensions", + "addon2@tests.mozilla.org", addon1Dir.path); + writeInstallRDFToDir(addon2, addon1Dir); + + restartManager(1); + + AddonManager.getAddons(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org"], function([a1, a2]) { + do_check_eq(a1, null); + do_check_neq(a2, null); + + do_test_finished(); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index ced6f4e9c3b6..017b8c9eaa23 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -120,6 +120,9 @@ function run_test_1() { dest = profileDir.clone(); dest.append("addon2@tests.mozilla.org"); writeInstallRDFToDir(addon2, dest); + // Attempt to make this look like it was added some time in the past so + // the change in run_test_2 makes the last modified time change. + dest.lastModifiedTime -= 5000; dest = profileDir.clone(); dest.append("addon3@tests.mozilla.org"); writeInstallRDFToDir(addon3, dest);