Bug 1019054 - re-add uninstallation from about:apps context menu; r=mfinkle,marco

This commit is contained in:
Myk Melez 2014-06-10 15:52:47 -07:00
parent d0bb84f99d
commit 78814e0a21
7 changed files with 122 additions and 34 deletions

View File

@ -306,7 +306,7 @@ this.DOMApplicationRegistry = {
},
// Registers all the activities and system messages.
registerAppsHandlers: function(aRunUpdate) {
registerAppsHandlers: Task.async(function*(aRunUpdate) {
this.notifyAppsRegistryStart();
let ids = [];
for (let id in this.webapps) {
@ -318,40 +318,38 @@ this.DOMApplicationRegistry = {
// Read the CSPs and roles. If MOZ_SYS_MSG is defined this is done on
// _processManifestForIds so as to not reading the manifests
// twice
this._readManifests(ids).then((aResults) => {
aResults.forEach((aResult) => {
if (!aResult.manifest) {
// If we can't load the manifest, we probably have a corrupted
// registry. We delete the app since we can't do anything with it.
delete this.webapps[aResult.id];
return;
}
let app = this.webapps[aResult.id];
app.csp = aResult.manifest.csp || "";
app.role = aResult.manifest.role || "";
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
app.redirects = this.sanitizeRedirects(aResult.redirects);
}
});
let results = yield this._readManifests(ids);
results.forEach((aResult) => {
if (!aResult.manifest) {
// If we can't load the manifest, we probably have a corrupted
// registry. We delete the app since we can't do anything with it.
delete this.webapps[aResult.id];
return;
}
let app = this.webapps[aResult.id];
app.csp = aResult.manifest.csp || "";
app.role = aResult.manifest.role || "";
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
app.redirects = this.sanitizeRedirects(aResult.redirects);
}
});
// Nothing else to do but notifying we're ready.
this.notifyAppsRegistryReady();
}
},
}),
updateDataStoreForApp: function(aId) {
updateDataStoreForApp: Task.async(function*(aId) {
if (!this.webapps[aId]) {
return;
}
// Create or Update the DataStore for this app
this._readManifests([{ id: aId }]).then((aResult) => {
let app = this.webapps[aId];
this.updateDataStore(app.localId, app.origin, app.manifestURL,
aResult[0].manifest, app.appStatus);
});
},
let results = yield this._readManifests([{ id: aId }]);
let app = this.webapps[aId];
this.updateDataStore(app.localId, app.origin, app.manifestURL,
results[0].manifest, app.appStatus);
}),
updatePermissionsForApp: function(aId, aIsPreinstalled) {
if (!this.webapps[aId]) {
@ -609,10 +607,10 @@ this.DOMApplicationRegistry = {
// DataStores must be initialized at startup.
for (let id in this.webapps) {
this.updateDataStoreForApp(id);
yield this.updateDataStoreForApp(id);
}
this.registerAppsHandlers(runUpdate);
yield this.registerAppsHandlers(runUpdate);
}.bind(this)).then(null, Cu.reportError);
},
@ -1101,7 +1099,11 @@ this.DOMApplicationRegistry = {
this.getSelf(msg, mm);
break;
case "Webapps:Uninstall":
#ifdef MOZ_WIDGET_ANDROID
Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg));
#else
this.doUninstall(msg, mm);
#endif
break;
case "Webapps:Launch":
this.doLaunch(msg, mm);

View File

@ -31,6 +31,7 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
public class EventListener implements NativeEventListener {
@ -41,6 +42,7 @@ public class EventListener implements NativeEventListener {
EventDispatcher.getInstance().registerGeckoThreadListener(this,
"Webapps:Preinstall",
"Webapps:InstallApk",
"Webapps:UninstallApk",
"Webapps:Postinstall",
"Webapps:Open",
"Webapps:Uninstall",
@ -51,6 +53,7 @@ public class EventListener implements NativeEventListener {
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
"Webapps:Preinstall",
"Webapps:InstallApk",
"Webapps:UninstallApk",
"Webapps:Postinstall",
"Webapps:Open",
"Webapps:Uninstall",
@ -62,6 +65,8 @@ public class EventListener implements NativeEventListener {
try {
if (event.equals("Webapps:InstallApk")) {
installApk(GeckoAppShell.getGeckoInterface().getActivity(), message, callback);
} else if (event.equals("Webapps:UninstallApk")) {
uninstallApk(GeckoAppShell.getGeckoInterface().getActivity(), message);
} else if (event.equals("Webapps:Postinstall")) {
postInstallWebapp(message.getString("apkPackageName"), message.getString("origin"));
} else if (event.equals("Webapps:Open")) {
@ -193,6 +198,20 @@ public class EventListener implements NativeEventListener {
});
}
public static void uninstallApk(final Activity context, NativeJSObject message) {
String packageName = message.getString("apkPackageName");
Uri packageUri = Uri.parse("package:" + packageName);
Intent intent;
if (Build.VERSION.SDK_INT < 14) {
intent = new Intent(Intent.ACTION_DELETE, packageUri);
} else {
intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
}
context.startActivity(intent);
}
private static final int DEFAULT_VERSION_CODE = -1;
public static JSONObject getApkVersions(Activity context, String[] packageNames) {

View File

@ -47,6 +47,29 @@ function checkForUpdates(aEvent) {
WebappManager.checkForUpdates(true);
}
let ContextMenus = {
target: null,
init: function() {
document.addEventListener("contextmenu", this, false);
document.getElementById("uninstallLabel").addEventListener("click", this.uninstall.bind(this), false);
},
handleEvent: function(event) {
// store the target of context menu events so that we know which app to act on
this.target = event.target;
while (!this.target.hasAttribute("contextmenu")) {
this.target = this.target.parentNode;
}
},
uninstall: function() {
navigator.mozApps.mgmt.uninstall(this.target.app);
this.target = null;
}
};
function onLoad(aEvent) {
let elmts = document.querySelectorAll("[pref]");
for (let i = 0; i < elmts.length; i++) {
@ -59,6 +82,8 @@ function onLoad(aEvent) {
navigator.mozApps.mgmt.onuninstall = onUninstall;
updateList();
ContextMenus.init();
// XXX - Hack to fix bug 985867 for now
document.addEventListener("touchstart", function() { });
}
@ -84,6 +109,7 @@ function addApplication(aApp) {
let container = document.createElement("div");
container.className = "app list-item";
container.setAttribute("contextmenu", "appmenu");
container.setAttribute("id", "app-" + aApp.origin);
container.setAttribute("mozApp", aApp.origin);
container.setAttribute("title", manifest.name);

View File

@ -28,6 +28,11 @@
</head>
<body dir="&locale.dir;">
<menu type="context" id="appmenu">
<menuitem id="uninstallLabel" label="&aboutApps.uninstall;"></menuitem>
</menu>
<div class="header">
<div>&aboutApps.header;</div>
<div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL"/>

View File

@ -328,7 +328,7 @@ var BrowserApp = {
Services.obs.addObserver(this, "webapps-runtime-install-package", false);
Services.obs.addObserver(this, "webapps-ask-install", false);
Services.obs.addObserver(this, "webapps-launch", false);
Services.obs.addObserver(this, "webapps-uninstall", false);
Services.obs.addObserver(this, "webapps-runtime-uninstall", false);
Services.obs.addObserver(this, "Webapps:AutoInstall", false);
Services.obs.addObserver(this, "Webapps:Load", false);
Services.obs.addObserver(this, "Webapps:AutoUninstall", false);
@ -1631,8 +1631,8 @@ var BrowserApp = {
break;
}
case "webapps-uninstall": {
WebappManager.uninstall(JSON.parse(aData));
case "webapps-runtime-uninstall": {
WebappManager.uninstall(JSON.parse(aData), aSubject);
break;
}

View File

@ -7,5 +7,4 @@
<!ENTITY aboutApps.browseMarketplace "Browse the Firefox Marketplace">
<!ENTITY aboutApps.uninstall "Uninstall">
<!ENTITY aboutApps.addToHomescreen "Add to Home Screen">
<!ENTITY aboutApps.checkForUpdates "Check for Updates">

View File

@ -208,16 +208,53 @@ this.WebappManager = {
});
},
uninstall: function(aData) {
uninstall: Task.async(function*(aData, aMessageManager) {
debug("uninstall: " + aData.manifestURL);
yield DOMApplicationRegistry.registryReady;
if (this._testing) {
// We don't have to do anything, as the registry does all the work.
// Go directly to DOM. Do not uninstall APK, do not collect $200.
DOMApplicationRegistry.doUninstall(aData, aMessageManager);
return;
}
// TODO: uninstall the APK.
},
let app = DOMApplicationRegistry.getAppByManifestURL(aData.manifestURL);
if (!app) {
throw new Error("app not found in registry");
}
// If the APK is installed, then _getAPKVersions will return a version
// for it, so we can use that function to determine its install status.
let apkVersions = yield this._getAPKVersions([ app.apkPackageName ]);
if (app.apkPackageName in apkVersions) {
debug("APK is installed; requesting uninstallation");
sendMessageToJava({
type: "Webapps:UninstallApk",
apkPackageName: app.apkPackageName,
});
// We don't need to call DOMApplicationRegistry.doUninstall at this point,
// because the APK uninstall listener will call autoUninstall once the APK
// is uninstalled; and if the user cancels the APK uninstallation, then we
// shouldn't remove the app from the registry anyway.
// But we should tell the requesting document the result of their request.
// TODO: tell the requesting document if uninstallation succeeds or fails
// by storing weak references to the message/manager pair here and then
// using them in autoUninstall if they're still defined when it's called;
// and make EventListener.uninstallApk return an error when APK uninstall
// fails (which it should be able to detect reliably on Android 4+),
// which we observe here and use to notify the requester of failure.
} else {
// The APK isn't installed, but remove the app from the registry anyway,
// to ensure the user can always remove an app from the registry (and thus
// about:apps) even if it's out of sync with installed APKs.
debug("APK not installed; proceeding directly to removal from registry");
DOMApplicationRegistry.doUninstall(aData, aMessageManager);
}
}),
autoInstall: function(aData) {
debug("autoInstall " + aData.manifestURL);