Bug 1000315 - Part 3: Uninstall prompting on Desktop and WebRT. r=myk
This commit is contained in:
@@ -444,6 +444,12 @@ webapps.install.accesskey = I
|
||||
webapps.requestInstall = Do you want to install "%1$S" from this site (%2$S)?
|
||||
webapps.install.success = Application Installed
|
||||
webapps.install.inprogress = Installation in progress
|
||||
webapps.uninstall = Uninstall
|
||||
webapps.uninstall.accesskey = U
|
||||
webapps.doNotUninstall = Don't Uninstall
|
||||
webapps.doNotUninstall.accesskey = D
|
||||
#LOCALIZATION NOTE (webapps.requestUninstall) %1$S is the web app name
|
||||
webapps.requestUninstall = Do you want to uninstall "%1$S"?
|
||||
|
||||
# LOCALIZATION NOTE (fullscreen.entered): displayed when we enter HTML5 fullscreen mode, %S is the domain name of the focused website (e.g. mozilla.com).
|
||||
fullscreen.entered=%S is now fullscreen.
|
||||
|
||||
@@ -31,6 +31,7 @@ this.WebappManager = {
|
||||
|
||||
init: function() {
|
||||
Services.obs.addObserver(this, "webapps-ask-install", false);
|
||||
Services.obs.addObserver(this, "webapps-ask-uninstall", false);
|
||||
Services.obs.addObserver(this, "webapps-launch", false);
|
||||
Services.obs.addObserver(this, "webapps-uninstall", false);
|
||||
cpmm.addMessageListener("Webapps:Install:Return:OK", this);
|
||||
@@ -40,6 +41,7 @@ this.WebappManager = {
|
||||
|
||||
uninit: function() {
|
||||
Services.obs.removeObserver(this, "webapps-ask-install");
|
||||
Services.obs.removeObserver(this, "webapps-ask-uninstall");
|
||||
Services.obs.removeObserver(this, "webapps-launch");
|
||||
Services.obs.removeObserver(this, "webapps-uninstall");
|
||||
cpmm.removeMessageListener("Webapps:Install:Return:OK", this);
|
||||
@@ -81,13 +83,20 @@ this.WebappManager = {
|
||||
let data = JSON.parse(aData);
|
||||
data.mm = aSubject;
|
||||
|
||||
let win;
|
||||
switch(aTopic) {
|
||||
case "webapps-ask-install":
|
||||
let win = this._getWindowForId(data.oid);
|
||||
win = this._getWindowForId(data.oid);
|
||||
if (win && win.location.href == data.from) {
|
||||
this.doInstall(data, win);
|
||||
}
|
||||
break;
|
||||
case "webapps-ask-uninstall":
|
||||
win = this._getWindowForId(data.windowId);
|
||||
if (win && win.location.href == data.from) {
|
||||
this.doUninstall(data, win);
|
||||
}
|
||||
break;
|
||||
case "webapps-launch":
|
||||
WebappOSUtils.launch(data);
|
||||
break;
|
||||
@@ -193,6 +202,49 @@ this.WebappManager = {
|
||||
"webapps-notification-icon",
|
||||
mainAction);
|
||||
|
||||
},
|
||||
|
||||
doUninstall: function(aData, aWindow) {
|
||||
let browser = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
let chromeDoc = browser.ownerDocument;
|
||||
let chromeWin = chromeDoc.defaultView;
|
||||
|
||||
let bundle = chromeWin.gNavigatorBundle;
|
||||
let jsonManifest = aData.app.manifest;
|
||||
|
||||
let notification;
|
||||
|
||||
let mainAction = {
|
||||
label: bundle.getString("webapps.uninstall"),
|
||||
accessKey: bundle.getString("webapps.uninstall.accesskey"),
|
||||
callback: () => {
|
||||
notification.remove();
|
||||
DOMApplicationRegistry.confirmUninstall(aData);
|
||||
}
|
||||
};
|
||||
|
||||
let secondaryAction = {
|
||||
label: bundle.getString("webapps.doNotUninstall"),
|
||||
accessKey: bundle.getString("webapps.doNotUninstall.accesskey"),
|
||||
callback: () => {
|
||||
notification.remove();
|
||||
DOMApplicationRegistry.denyUninstall(aData, "USER_DECLINED");
|
||||
}
|
||||
};
|
||||
|
||||
let manifest = new ManifestHelper(jsonManifest, aData.app.origin,
|
||||
aData.app.manifestURL);
|
||||
|
||||
let message = bundle.getFormattedString("webapps.requestUninstall",
|
||||
[manifest.name]);
|
||||
|
||||
notification = chromeWin.PopupNotifications.show(
|
||||
browser, "webapps-uninstall", message,
|
||||
"webapps-notification-icon",
|
||||
mainAction, [secondaryAction]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,7 @@ WebappsRegistry.prototype = {
|
||||
let mgmt = Cc["@mozilla.org/webapps/manager;1"]
|
||||
.createInstance(Ci.nsISupports);
|
||||
mgmt.wrappedJSObject.init(this._window);
|
||||
mgmt.wrappedJSObject._windowId = this._id;
|
||||
this._mgmt = mgmt.__DOM_IMPL__
|
||||
? mgmt.__DOM_IMPL__
|
||||
: this._window.DOMApplicationsManager._create(this._window, mgmt.wrappedJSObject);
|
||||
@@ -721,10 +722,15 @@ WebappsApplicationMgmt.prototype = {
|
||||
|
||||
uninstall: function(aApp) {
|
||||
let request = this.createRequest();
|
||||
cpmm.sendAsyncMessage("Webapps:Uninstall", { origin: aApp.origin,
|
||||
manifestURL: aApp.manifestURL,
|
||||
oid: this._id,
|
||||
requestID: this.getRequestId(request) });
|
||||
|
||||
cpmm.sendAsyncMessage("Webapps:Uninstall", {
|
||||
origin: aApp.origin,
|
||||
manifestURL: aApp.manifestURL,
|
||||
oid: this._id,
|
||||
from: this._window.location.href,
|
||||
windowId: this._windowId,
|
||||
requestID: this.getRequestId(request)
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ this.DOMApplicationRegistry = {
|
||||
children: [ ],
|
||||
allAppsLaunchable: false,
|
||||
_updateHandlers: [ ],
|
||||
_pendingUninstalls: {},
|
||||
|
||||
init: function() {
|
||||
this.messages = ["Webapps:Install", "Webapps:Uninstall",
|
||||
@@ -547,7 +548,7 @@ this.DOMApplicationRegistry = {
|
||||
if (this.webapps[id].manifestURL === httpsManifestURL) {
|
||||
debug("Found a http/https match: " + app.manifestURL + " / " +
|
||||
this.webapps[id].manifestURL);
|
||||
this.uninstall(app.manifestURL, function() {}, function() {});
|
||||
this.uninstall(app.manifestURL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2638,7 +2639,7 @@ this.DOMApplicationRegistry = {
|
||||
manifest: jsonManifest
|
||||
},
|
||||
isReinstall,
|
||||
this.uninstall.bind(this, aData, aData.mm)
|
||||
this.doUninstall.bind(this, aData, aData.mm)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3675,39 +3676,50 @@ this.DOMApplicationRegistry = {
|
||||
AppDownloadManager.remove(aNewApp.manifestURL);
|
||||
},
|
||||
|
||||
doUninstall: function(aData, aMm) {
|
||||
this.uninstall(aData.manifestURL,
|
||||
function onsuccess() {
|
||||
aMm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData);
|
||||
},
|
||||
function onfailure() {
|
||||
// Fall-through, fails to uninstall the desired app because:
|
||||
// - we cannot find the app to be uninstalled.
|
||||
// - the app to be uninstalled is not removable.
|
||||
aMm.sendAsyncMessage("Webapps:Uninstall:Return:KO", aData);
|
||||
doUninstall: Task.async(function*(aData, aMm) {
|
||||
// The yields here could get stuck forever, so we only hold
|
||||
// a weak reference to the message manager while yielding, to avoid
|
||||
// leaking the whole page associationed with the message manager.
|
||||
aMm = Cu.getWeakReference(aMm);
|
||||
|
||||
let response = "Webapps:Uninstall:Return:OK";
|
||||
|
||||
try {
|
||||
aData.app = yield this._getAppWithManifest(aData.manifestURL);
|
||||
|
||||
let prefName = "dom.mozApps.auto_confirm_uninstall";
|
||||
if (Services.prefs.prefHasUserValue(prefName) &&
|
||||
Services.prefs.getBoolPref(prefName)) {
|
||||
yield this._uninstallApp(aData.app);
|
||||
} else {
|
||||
yield this._promptForUninstall(aData);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
aData.error = error;
|
||||
response = "Webapps:Uninstall:Return:KO";
|
||||
}
|
||||
|
||||
if (aMm = aMm.get()) {
|
||||
aMm.sendAsyncMessage(response, aData);
|
||||
}
|
||||
}),
|
||||
|
||||
uninstall: function(aManifestURL) {
|
||||
return this._getAppWithManifest(aManifestURL)
|
||||
.then(this._uninstallApp.bind(this));
|
||||
},
|
||||
|
||||
uninstall: function(aManifestURL, aOnSuccess, aOnFailure) {
|
||||
debug("uninstall " + aManifestURL);
|
||||
|
||||
let app = this.getAppByManifestURL(aManifestURL);
|
||||
if (!app) {
|
||||
aOnFailure("NO_SUCH_APP");
|
||||
return;
|
||||
}
|
||||
let id = app.id;
|
||||
|
||||
if (!app.removable) {
|
||||
_uninstallApp: Task.async(function*(aApp) {
|
||||
if (!aApp.removable) {
|
||||
debug("Error: cannot uninstall a non-removable app.");
|
||||
aOnFailure("NON_REMOVABLE_APP");
|
||||
return;
|
||||
throw new Error("NON_REMOVABLE_APP");
|
||||
}
|
||||
|
||||
let id = aApp.id;
|
||||
|
||||
// Check if we are downloading something for this app, and cancel the
|
||||
// download if needed.
|
||||
this.cancelDownload(app.manifestURL);
|
||||
this.cancelDownload(aApp.manifestURL);
|
||||
|
||||
// Clean up the deprecated manifest cache if needed.
|
||||
if (id in this._manifestCache) {
|
||||
@@ -3715,18 +3727,13 @@ this.DOMApplicationRegistry = {
|
||||
}
|
||||
|
||||
// Clear private data first.
|
||||
this._clearPrivateData(app.localId, false);
|
||||
this._clearPrivateData(aApp.localId, false);
|
||||
|
||||
// Then notify observers.
|
||||
// We have to clone the app object as nsIDOMApplication objects are
|
||||
// stringified as an empty object. (see bug 830376)
|
||||
let appClone = AppsUtils.cloneAppObject(app);
|
||||
Services.obs.notifyObservers(null, "webapps-uninstall", JSON.stringify(appClone));
|
||||
Services.obs.notifyObservers(null, "webapps-uninstall", JSON.stringify(aApp));
|
||||
|
||||
if (supportSystemMessages()) {
|
||||
this._readManifests([{ id: id }]).then((aResult) => {
|
||||
this._unregisterActivities(aResult[0].manifest, app);
|
||||
});
|
||||
this._unregisterActivities(aApp.manifest, aApp);
|
||||
}
|
||||
|
||||
let dir = this._getAppDir(id);
|
||||
@@ -3736,18 +3743,47 @@ this.DOMApplicationRegistry = {
|
||||
|
||||
delete this.webapps[id];
|
||||
|
||||
this._saveApps().then(() => {
|
||||
this.broadcastMessage("Webapps:Uninstall:Broadcast:Return:OK", appClone);
|
||||
this.broadcastMessage("Webapps:RemoveApp", { id: id });
|
||||
try {
|
||||
if (aOnSuccess) {
|
||||
aOnSuccess();
|
||||
}
|
||||
} catch(ex) {
|
||||
Cu.reportError("DOMApplicationRegistry: Exception on app uninstall: " +
|
||||
ex + "\n" + ex.stack);
|
||||
}
|
||||
});
|
||||
yield this._saveApps();
|
||||
|
||||
this.broadcastMessage("Webapps:Uninstall:Broadcast:Return:OK", aApp);
|
||||
this.broadcastMessage("Webapps:RemoveApp", { id: id });
|
||||
|
||||
return aApp;
|
||||
}),
|
||||
|
||||
_promptForUninstall: function(aData) {
|
||||
let deferred = Promise.defer();
|
||||
this._pendingUninstalls[aData.requestID] = deferred;
|
||||
Services.obs.notifyObservers(null, "webapps-ask-uninstall",
|
||||
JSON.stringify(aData));
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
confirmUninstall: function(aData) {
|
||||
let pending = this._pendingUninstalls[aData.requestID];
|
||||
if (pending) {
|
||||
delete this._pendingUninstalls[aData.requestID];
|
||||
return this._uninstallApp(aData.app).then(() => {
|
||||
pending.resolve();
|
||||
return aData.app;
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error("PENDING_UNINSTALL_NOT_FOUND"));
|
||||
},
|
||||
|
||||
denyUninstall: function(aData, aReason = "ERROR_UNKNOWN_FAILURE") {
|
||||
// Fails to uninstall the desired app because:
|
||||
// - we cannot find the app to be uninstalled.
|
||||
// - the app to be uninstalled is not removable.
|
||||
// - the user declined the confirmation
|
||||
debug("Failed to uninstall app: " + aReason);
|
||||
let pending = this._pendingUninstalls[aData.requestID];
|
||||
if (pending) {
|
||||
delete this._pendingUninstalls[aData.requestID];
|
||||
pending.reject(new Error(aReason));
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error("PENDING_UNINSTALL_NOT_FOUND"));
|
||||
},
|
||||
|
||||
getSelf: function(aData, aMm) {
|
||||
@@ -4067,6 +4103,17 @@ this.DOMApplicationRegistry = {
|
||||
return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
|
||||
},
|
||||
|
||||
_getAppWithManifest: Task.async(function*(aManifestURL) {
|
||||
let app = this.getAppByManifestURL(aManifestURL);
|
||||
if (!app) {
|
||||
throw new Error("NO_SUCH_APP");
|
||||
}
|
||||
|
||||
app.manifest = ( yield this._readManifests([{ id: app.id }]) )[0].manifest;
|
||||
|
||||
return app;
|
||||
}),
|
||||
|
||||
getCSPByLocalId: function(aLocalId) {
|
||||
debug("getCSPByLocalId:" + aLocalId);
|
||||
return AppsUtils.getCSPByLocalId(this.webapps, aLocalId);
|
||||
|
||||
@@ -19,16 +19,23 @@ Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
||||
Cu.import("resource://webapprt/modules/WebappRT.jsm");
|
||||
|
||||
this.WebappManager = {
|
||||
observe: function(subject, topic, data) {
|
||||
data = JSON.parse(data);
|
||||
data.mm = subject;
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
let data = JSON.parse(aData);
|
||||
data.mm = aSubject;
|
||||
|
||||
switch (topic) {
|
||||
let chromeWin;
|
||||
switch (aTopic) {
|
||||
case "webapps-ask-install":
|
||||
let chromeWin = Services.wm.getOuterWindowWithId(data.oid);
|
||||
chromeWin = Services.wm.getOuterWindowWithId(data.oid);
|
||||
if (chromeWin)
|
||||
this.doInstall(data, chromeWin);
|
||||
break;
|
||||
case "webapps-ask-uninstall":
|
||||
chromeWin = Services.wm.getOuterWindowWithId(data.windowId);
|
||||
if (chromeWin) {
|
||||
this.doUninstall(data, chromeWin);
|
||||
}
|
||||
break;
|
||||
case "webapps-launch":
|
||||
WebappOSUtils.launch(data);
|
||||
break;
|
||||
@@ -75,7 +82,7 @@ this.WebappManager = {
|
||||
try {
|
||||
localDir = nativeApp.createProfile();
|
||||
} catch (ex) {
|
||||
DOMApplicationRegistry.denyInstall(aData);
|
||||
DOMApplicationRegistry.denyInstall(data);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,11 +96,43 @@ this.WebappManager = {
|
||||
}
|
||||
},
|
||||
|
||||
doUninstall: function(aData, aWindow) {
|
||||
let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
|
||||
let manifest = new ManifestHelper(jsonManifest, aData.app.origin,
|
||||
aData.app.manifestURL);
|
||||
let name = manifest.name;
|
||||
let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties");
|
||||
|
||||
let choice = Services.prompt.confirmEx(
|
||||
aWindow,
|
||||
bundle.formatStringFromName("webapps.uninstall.title", [name], 1),
|
||||
bundle.formatStringFromName("webapps.uninstall.description", [name], 1),
|
||||
// Set both buttons to strings with the cancel button being default
|
||||
Ci.nsIPromptService.BUTTON_POS_1_DEFAULT |
|
||||
Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_0 |
|
||||
Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_1,
|
||||
bundle.GetStringFromName("webapps.uninstall.uninstall"),
|
||||
bundle.GetStringFromName("webapps.uninstall.dontuninstall"),
|
||||
null,
|
||||
null,
|
||||
{});
|
||||
|
||||
// Perform the uninstall if the user allows it
|
||||
if (choice == 0) {
|
||||
DOMApplicationRegistry.confirmUninstall(aData).then((aApp) => {
|
||||
WebappOSUtils.uninstall(aApp);
|
||||
});
|
||||
} else {
|
||||
DOMApplicationRegistry.denyUninstall(aData);
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
|
||||
Services.obs.addObserver(WebappManager, "webapps-ask-install", false);
|
||||
Services.obs.addObserver(WebappManager, "webapps-ask-uninstall", false);
|
||||
Services.obs.addObserver(WebappManager, "webapps-launch", false);
|
||||
Services.obs.addObserver(WebappManager, "webapps-uninstall", false);
|
||||
Services.obs.addObserver(WebappManager, "webapps-update", false);
|
||||
|
||||
@@ -40,6 +40,14 @@ webapps.install.title=Install %S
|
||||
webapps.install.description=Do you want to install %S?
|
||||
webapps.install.install=Install App
|
||||
webapps.install.dontinstall=Don't Install
|
||||
# LOCALIZATION NOTE (webapps.uninstall.title): %S will be replaced with the name
|
||||
# of the webapp being uninstalled.
|
||||
webapps.uninstall.title=Uninstall %S
|
||||
# LOCALIZATION NOTE (webapps.uninstall.description): %S will be replaced with the
|
||||
# name of the webapp being uninstalled.
|
||||
webapps.uninstall.description=Do you want to uninstall %S?
|
||||
webapps.uninstall.uninstall=Uninstall App
|
||||
webapps.uninstall.dontuninstall=Don't Uninstall
|
||||
|
||||
paymentDialog.title=Payment
|
||||
paymentDialog.message=Which payment provider do you want to use?
|
||||
|
||||
Reference in New Issue
Block a user