Bug 1000315 - Part 3: Uninstall prompting on Desktop and WebRT. r=myk

This commit is contained in:
Ted Clancy (:tedders1)
2014-06-11 14:23:18 -07:00
parent 53beddee63
commit 1e4cac2b61
6 changed files with 216 additions and 58 deletions

View File

@@ -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.

View File

@@ -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]);
}
}

View File

@@ -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;
},

View File

@@ -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);

View File

@@ -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);

View File

@@ -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?