Bug 553494: Support undo uninstall for restartless and already disabled add-ons. r=Unfocused

This commit is contained in:
Dave Townsend 2010-07-26 12:42:06 -07:00
parent 07f85bbcb9
commit 8bc3aacf67
5 changed files with 1192 additions and 14 deletions

View File

@ -133,8 +133,8 @@ function shutdown() {
}
// Used by external callers to load a specific view into the manager
function loadView(url) {
gViewController.loadView(url);
function loadView(aViewId, aCallback) {
gViewController.loadView(aViewId, aCallback);
}
var gEventManager = {
@ -253,6 +253,7 @@ var gViewController = {
currentViewRequest: 0,
previousViewId: "",
viewObjects: {},
viewChangeCallback: null,
initialize: function() {
this.viewPort = document.getElementById("view-port");
@ -269,6 +270,8 @@ var gViewController = {
},
shutdown: function() {
if (this.currentViewObj)
this.currentViewObj.hide();
this.currentViewRequest = 0;
},
@ -282,7 +285,7 @@ var gViewController = {
return this.currentViewObj.node.hasAttribute("loading");
},
loadView: function(aViewId) {
loadView: function(aViewId, aCallback) {
if (aViewId == this.currentViewId)
return;
@ -314,6 +317,8 @@ var gViewController = {
this.currentViewId = aViewId;
this.currentViewObj = viewObj;
this.viewChangeCallback = aCallback;
this.viewPort.selectedPanel = this.currentViewObj.node;
this.viewPort.selectedPanel.setAttribute("loading", "true");
this.currentViewObj.show(view.param, ++this.currentViewRequest);
@ -321,6 +326,10 @@ var gViewController = {
notifyViewChanged: function() {
this.viewPort.selectedPanel.removeAttribute("loading");
if (this.viewChangeCallback)
this.viewChangeCallback();
var event = document.createEvent("Events");
event.initEvent("ViewChanged", true, true);
this.currentViewObj.node.dispatchEvent(event);
@ -527,7 +536,14 @@ var gViewController = {
return hasPermission(aAddon, "uninstall");
},
doCommand: function(aAddon) {
aAddon.uninstall();
if (gViewController.currentViewObj != gDetailView) {
aAddon.uninstall();
return;
}
gViewController.loadView(gViewController.previousViewId, function() {
gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall();
});
}
},
@ -1077,7 +1093,15 @@ var gSearchView = {
gViewController.updateCommands();
},
hide: function() { },
hide: function() {
var listitem = this._listBox.firstChild;
while (listitem) {
if (listitem.getAttribute("status") == "uninstalled" &&
!listitem.isPending("uninstall"))
listitem.mAddon.uninstall();
listitem = listitem.nextSibling;
}
},
getMatchScore: function(aObj, aQuery) {
var score = 0;
@ -1144,8 +1168,16 @@ var gSearchView = {
if (item)
return item.mAddon;
return null;
}
},
getListItemForID: function(aId) {
var listitem = this._listBox.firstChild;
while (listitem) {
if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
return listitem;
listitem = listitem.nextSibling;
}
}
};
@ -1213,6 +1245,14 @@ var gListView = {
hide: function() {
gEventManager.unregisterInstallListener(this);
var listitem = this._listBox.firstChild;
while (listitem) {
if (listitem.getAttribute("status") == "uninstalled" &&
!listitem.isPending("uninstall"))
listitem.mAddon.uninstall();
listitem = listitem.nextSibling;
}
},
showEmptyNotice: function(aShow) {
@ -1266,6 +1306,15 @@ var gListView = {
if (item)
return item.mAddon;
return null;
},
getListItemForID: function(aId) {
var listitem = this._listBox.firstChild;
while (listitem) {
if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
return listitem;
listitem = listitem.nextSibling;
}
}
};

View File

@ -711,6 +711,14 @@
]]></body>
</method>
<method name="opRequiresRestart">
<parameter name="aOperation"/>
<body><![CDATA[
var operation = AddonManager["OP_NEEDS_RESTART_" + aOperation.toUpperCase()];
return !!(this.mAddon.operationsRequiringRestart & operation);
]]></body>
</method>
<method name="isPending">
<parameter name="aAction"/>
<body><![CDATA[
@ -718,6 +726,12 @@
return !!(this.mAddon.pendingOperations & action);
]]></body>
</method>
<method name="onUninstalled">
<body><![CDATA[
this.parentNode.removeChild(this);
]]></body>
</method>
</implementation>
</binding>
@ -1031,8 +1045,18 @@
<method name="uninstall">
<body><![CDATA[
this.mAddon.uninstall();
]]></body>
// If uninstalling does not require a restart then just disable it
// and show the undo UI.
if (!this.opRequiresRestart("uninstall")) {
// This won't update any other add-on manager views (bug 582002)
this.setAttribute("wasDisabled", this.mAddon.userDisabled);
this.setAttribute("restartrequired", false);
this.setAttribute("status", "uninstalled");
this.mAddon.userDisabled = true;
} else {
this.mAddon.uninstall();
}
]]></body>
</method>
<method name="showPreferences">
@ -1175,9 +1199,17 @@
<method name="cancelUninstall">
<body><![CDATA[
// This assumes that disabling does not require a restart when
// uninstalling doesn't. Things will still work if not, the add-on
// will just still be active until finally getting uninstalled.
if (this.isPending("uninstall"))
this.mAddon.cancelUninstall();
else if (this.getAttribute("wasDisabled") != "true")
this.mAddon.userDisabled = false;
this.removeAttribute("restartrequired");
this.mAddon.userDisabled = false;
this.mAddon.cancelUninstall();
this.setAttribute("status", "installed");
]]></body>
</method>

View File

@ -56,6 +56,7 @@ _TEST_FILES = \
browser_searching.xml \
browser_searching_empty.xml \
browser_sorting.js \
browser_uninstalling.js \
browser_updatessl.js \
browser_updatessl.rdf \
browser_installssl.js \

File diff suppressed because it is too large Load Diff

View File

@ -282,6 +282,7 @@ MockProvider.prototype = {
*/
addAddon: function MP_addAddon(aAddon) {
this.addons.push(aAddon);
aAddon._provider = this;
if (!this.started)
return;
@ -292,6 +293,28 @@ MockProvider.prototype = {
null, requiresRestart)
},
/**
* Removes an add-on from the list of add-ons that this provider exposes to
* the AddonManager, dispatching the onUninstalled event in the process.
*
* @param aAddon
* The add-on to add
*/
removeAddon: function MP_removeAddon(aAddon) {
var pos = this.addons.indexOf(aAddon);
if (pos == -1) {
ok(false, "Tried to remove an add-on that wasn't registered with the mock provider");
return;
}
this.addons.splice(pos, 1);
if (!this.started)
return;
AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon);
},
/**
* Adds an add-on install to the list of installs that this provider exposes
* to the AddonManager, dispatching appropriate events in the process.
@ -588,12 +611,15 @@ function MockAddon(aId, aName, aType, aOperationsRequiringRestart) {
this.providesUpdatesSecurely = true;
this.blocklistState = 0;
this.appDisabled = false;
this.userDisabled = false;
this._userDisabled = false;
this.scope = AddonManager.SCOPE_PROFILE;
this.isActive = true;
this.creator = "";
this.pendingOperations = 0;
this.permissions = 0;
this.permissions = AddonManager.PERM_CAN_UNINSTALL |
AddonManager.PERM_CAN_ENABLE |
AddonManager.PERM_CAN_DISABLE |
AddonManager.PERM_CAN_UPGRADE;
this.operationsRequiringRestart = aOperationsRequiringRestart ||
(AddonManager.OP_NEEDS_RESTART_INSTALL |
AddonManager.OP_NEEDS_RESTART_UNINSTALL |
@ -602,6 +628,26 @@ function MockAddon(aId, aName, aType, aOperationsRequiringRestart) {
}
MockAddon.prototype = {
get shouldBeActive() {
return !this.appDisabled && !this._userDisabled;
},
get userDisabled() {
return this._userDisabled;
},
set userDisabled(val) {
if (val == this._userDisabled)
return val;
var currentActive = this.shouldBeActive;
this._userDisabled = val;
var newActive = this.shouldBeActive;
this._updateActiveState(currentActive, newActive);
return val;
},
isCompatibleWith: function(aAppVersion, aPlatformVersion) {
return true;
},
@ -611,11 +657,53 @@ MockAddon.prototype = {
},
uninstall: function() {
// To be implemented when needed
if (this.pendingOperations & AddonManager.PENDING_UNINSTALL)
throw new Error("Add-on is already pending uninstall");
var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL);
this.pendingOperations |= AddonManager.PENDING_UNINSTALL;
AddonManagerPrivate.callAddonListeners("onUninstalling", this, needsRestart);
if (!needsRestart) {
this.pendingOperations -= AddonManager.PENDING_UNINSTALL;
this._provider.removeAddon(this);
}
},
cancelUninstall: function() {
// To be implemented when needed
if (!(this.pendingOperations & AddonManager.PENDING_UNINSTALL))
throw new Error("Add-on is not pending uninstall");
this.pendingOperations -= AddonManager.PENDING_UNINSTALL;
AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
},
_updateActiveState: function(currentActive, newActive) {
if (currentActive == newActive)
return;
if (newActive == this.isActive) {
AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
}
else if (newActive) {
var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE);
this.pendingOperations |= AddonManager.PENDING_ENABLE;
AddonManagerPrivate.callAddonListeners("onEnabling", this, needsRestart);
if (!needsRestart) {
this.isActive = newActive;
this.pendingOperations -= AddonManager.PENDING_ENABLE;
AddonManagerPrivate.callAddonListeners("onEnabled", this);
}
}
else {
var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE);
this.pendingOperations |= AddonManager.PENDING_DISABLE;
AddonManagerPrivate.callAddonListeners("onDisabling", this, needsRestart);
if (!needsRestart) {
this.isActive = newActive;
this.pendingOperations -= AddonManager.PENDING_DISABLE;
AddonManagerPrivate.callAddonListeners("onDisabled", this);
}
}
}
};