From a7824cb26466cf6ba90258eda0edd78828d87ec6 Mon Sep 17 00:00:00 2001 From: "dtownsend@oxymoronical.com" Date: Thu, 27 Mar 2008 13:06:41 -0700 Subject: [PATCH] Bug 421396: Extension installation fails with "item is null" in nsExtensionManager.js. r=robstrong --- .../extensions/src/nsExtensionManager.js.in | 20 +- .../unit/data/test_bug421396/chrome.manifest | 1 + .../test/unit/data/test_bug421396/install.rdf | 22 ++ .../extensions/test/unit/test_bug421396.js | 255 ++++++++++++++++++ 4 files changed, 291 insertions(+), 7 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/unit/data/test_bug421396/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/unit/data/test_bug421396/install.rdf create mode 100644 toolkit/mozapps/extensions/test/unit/test_bug421396.js diff --git a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in index 4e71eab830a0..8378d8fd28fb 100644 --- a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in +++ b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in @@ -4856,13 +4856,19 @@ ExtensionManager.prototype = { catch (e) { ERROR("_finalizeUninstall: failed to remove directory for item: " + id + " at Install Location: " + installLocation.name + ", rolling back uninstall"); - // Removal of the files failed, reset the uninstalled flag and rewrite - // the install manifests so this item's components are registered. - // Clear the op flag from the Startup Cache - StartupCache.put(installLocation, id, OP_NONE, true); - var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type")) - this._updateManifests(restartRequired); - return; + var manifest = installLocation.getItemFile(id, "FILE_INSTALL_MANIFEST"); + // If there is no manifest then either the rollback failed, or there was + // no manifest in the first place. Either way this item is now invalid + // and we shouldn't try to re-install it. + if (manifest.exists()) { + // Removal of the files failed, reset the uninstalled flag and rewrite + // the install manifests so this item's components are registered. + // Clear the op flag from the Startup Cache + StartupCache.put(installLocation, id, OP_NONE, true); + var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type")) + this._updateManifests(restartRequired); + return; + } } } else if (installLocation.name == KEY_APP_PROFILE || diff --git a/toolkit/mozapps/extensions/test/unit/data/test_bug421396/chrome.manifest b/toolkit/mozapps/extensions/test/unit/data/test_bug421396/chrome.manifest new file mode 100644 index 000000000000..dc7f95abd69c --- /dev/null +++ b/toolkit/mozapps/extensions/test/unit/data/test_bug421396/chrome.manifest @@ -0,0 +1 @@ +# blank file to stop the EM attempting to create one anyway \ No newline at end of file diff --git a/toolkit/mozapps/extensions/test/unit/data/test_bug421396/install.rdf b/toolkit/mozapps/extensions/test/unit/data/test_bug421396/install.rdf new file mode 100644 index 000000000000..a785025bea19 --- /dev/null +++ b/toolkit/mozapps/extensions/test/unit/data/test_bug421396/install.rdf @@ -0,0 +1,22 @@ + + + + + + bug421396@tests.mozilla.org + 1.0 + + + + xpcshell@tests.mozilla.org + 1 + 1 + + + + + bug421396 test + + + diff --git a/toolkit/mozapps/extensions/test/unit/test_bug421396.js b/toolkit/mozapps/extensions/test/unit/test_bug421396.js new file mode 100644 index 000000000000..87d6c49ff880 --- /dev/null +++ b/toolkit/mozapps/extensions/test/unit/test_bug421396.js @@ -0,0 +1,255 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Dave Townsend . + * + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +const TESTID = "bug421396@tests.mozilla.org"; +var INSTALLED = true; + +// This creates a fake directory that cannot be modified. Nothing exists inside +// the directory. +function RestrictedPath(source, isVisible) { + this.source = source; + this.isVisible = isVisible; +} + +// This doesn't implement all of nsIFile and nsILocalFile, just enough to keep +// the EM happy. +RestrictedPath.prototype = { + // A real nsIFile that this shadows + source: null, + // If this file is visible or not. Only the main directory for the addon is visible. + isVisible: null, + + append: function(node) { + this.source.append(node); + this.isVisible = false; + }, + + normalize: function() { + this.source.normalize(); + }, + + create: function(type, permissions) { + if (this.isVisible) + throw Components.errors.NS_ERROR_FILE_ALREADY_EXISTS; + throw Components.errors.NS_ERROR_FILE_ACCESS_DENIED; + }, + + get leafName() { + return this.source.leafName; + }, + + copyTo: function(parentdir, name) { + throw Components.errors.NS_ERROR_FILE_ACCESS_DENIED; + }, + + copyToFollowingLinks: function(parentdir, name) { + throw Components.errors.NS_ERROR_FILE_ACCESS_DENIED; + }, + + moveTo: function(parentdir, name) { + throw Components.errors.NS_ERROR_FILE_ACCESS_DENIED; + }, + + remove: function(recursive) { + if (this.isVisible) + throw Components.errors.NS_ERROR_FILE_ACCESS_DENIED; + throw Components.errors.NS_ERROR_FILE_NOT_FOUND; + }, + + get lastModifiedTime() { + if (this.isVisible) + return Date.now(); + throw Components.errors.NS_ERROR_FILE_NOT_FOUND; + }, + + get lastModifiedTimeOfLink() { + return this.lastModifiedTime; + }, + + get path() { return this.source.path; }, + exists: function() { return this.isVisible; }, + isDirectory: function() { return this.isVisible; }, + isFile: function() { return !this.isVisible; }, + + clone: function() { + return new RestrictedPath(this.source.clone(), this.isVisible); + }, + + get parent() { + return new RestrictedPath(this.source.parent, false); + }, + + getRelativeDescriptor: function(basedir) { + return this.source.getRelativeDescriptor(basedir); + }, + + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIFile) + || iid.equals(Components.interfaces.nsILocalFile) + || iid.equals(Components.interfaces.nsISupports)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +}; + +function DirectoryEnumerator(files) { + this.files = files; + this.pos = 0; +} + +DirectoryEnumerator.prototype = { + pos: null, + files: null, + + get nextFile() { + if (this.pos < this.files.length) + return this.files[this.pos++]; + return null; + }, + + close: function() { + } +}; + +var InstallLocation = { + name: "test-location", + location: null, + restricted: null, + canAccess: null, + priority: Components.interfaces.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL, + + get itemLocations() { + return new DirectoryEnumerator([ this.getItemLocation(TESTID) ]); + }, + + getItemLocation: function(id) { + if (id == TESTID) { + var file = do_get_file("toolkit/mozapps/extensions/test/unit/data/test_bug421396"); + if (INSTALLED) + return file; + // If we are simulating after the extension is "removed" then return the + // empty undeletable directory. + return new RestrictedPath(file, true); + } + var file = do_get_file("toolkit/mozapps/extensions/test"); + file.append("INVALIDNAME"); + file.append(id); + return file; + }, + + getIDForLocation: function(file) { + if (file.leafName == "test_bug421396") + return TESTID; + return file.leafName; + }, + + getItemFile: function(id, filePath) { + var itemLocation = this.getItemLocation(id).clone(); + var parts = filePath.split("/"); + for (var i = 0; i < parts.length; ++i) + itemLocation.append(parts[i]); + return itemLocation; + }, + + itemIsManagedIndependently: function(id) { + return false; + }, + + stageFile: function(file, id) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + getStageFile: function(id) { + return null; + }, + + removeFile: function(file) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIInstallLocation) + || iid.equals(Components.interfaces.nsISupports)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +} + +var InstallLocationFactory = { + createInstance: function (outer, iid) { + if (outer != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return InstallLocation.QueryInterface(iid); + } +}; + +const IL_CID = Components.ID("{6bdd8320-57c7-4f19-81ad-c32fdfc2b423}"); +const IL_CONTRACT = "@tests.mozilla.org/installlocation;1"; +var registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); +registrar.registerFactory(IL_CID, "Test Install Location", + IL_CONTRACT, InstallLocationFactory); + +var categoryManager = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); +categoryManager.addCategoryEntry("extension-install-locations", "test-location", + IL_CONTRACT, false, true); + +function run_test() +{ + // EM needs to be running. + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); + startupEM(); + // Addon in the fake location should be installed now. + do_check_neq(gEM.getItemForID(TESTID), null); + + // Mark the add-on as uninstalled and restart ot pickup the change. + INSTALLED = false; + restartEM(); + + do_check_eq(gEM.getItemForID(TESTID), null); + // Test install something. + gEM.installItemFromFile(do_get_addon("test_bug397778"), NS_INSTALL_LOCATION_APPPROFILE); + do_check_neq(gEM.getItemForID("bug397778@tests.mozilla.org"), null); + restartEM(); + + do_check_eq(gEM.getItemForID(TESTID), null); + do_check_neq(gEM.getItemForID("bug397778@tests.mozilla.org"), null); +} +