author | Ted Clancy (:tedders1) <tclancy@mozilla.com> |
Wed, 11 Jun 2014 14:23:18 -0700 | |
changeset 201385 | 8e2086458237c7030fab5eaae310b1e7e7e0eb60 |
parent 201384 | 87faeece51b80d07ea1285adef102962ec7b0248 |
child 201386 | 1a78ae9f8b9e162af78fd63c9cf62cca3fa184f9 |
push id | 27368 |
push user | ryanvm@gmail.com |
push date | Mon, 25 Aug 2014 20:14:11 +0000 |
treeherder | mozilla-central@fdea170bd819 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | myk |
bugs | 1000315 |
milestone | 34.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -439,16 +439,22 @@ dataReportingNotification.button.accessK # Webapps notification popup webapps.install = Install webapps.install.accesskey = I #LOCALIZATION NOTE (webapps.requestInstall) %1$S is the web app name, %2$S is the site from which the web app is installed 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. # LOCALIZATION NOTE (fullscreen.rememberDecision): displayed when we enter HTML5 fullscreen mode, %S is the domain name of the focused website (e.g. mozilla.com). fullscreen.rememberDecision=Remember decision for %S # LOCALIZATION NOTE (fullscreenButton.tooltip): %S is the keyboard shortcut for full screen fullscreenButton.tooltip=Display the window in full screen (%S)
--- a/browser/modules/WebappManager.jsm +++ b/browser/modules/WebappManager.jsm @@ -26,25 +26,27 @@ XPCOMUtils.defineLazyServiceGetter(this, "nsIMessageSender"); this.WebappManager = { // List of promises for in-progress installations installations: {}, 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); cpmm.addMessageListener("Webapps:Install:Return:KO", this); cpmm.addMessageListener("Webapps:UpdateState", this); }, 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); cpmm.removeMessageListener("Webapps:Install:Return:KO", this); cpmm.removeMessageListener("Webapps:UpdateState", this); }, receiveMessage: function(aMessage) { @@ -76,23 +78,30 @@ this.WebappManager = { this.installations[manifestURL].reject(data.error); } }, observe: function(aSubject, aTopic, aData) { 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; case "webapps-uninstall": WebappOSUtils.uninstall(data); break; } }, @@ -188,16 +197,59 @@ this.WebappManager = { [manifest.name, host], 2); notification = chromeWin.PopupNotifications.show(browser, "webapps-install", message, "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]); } } function notifyInstallSuccess(aApp, aNativeApp, aBundle) { let launcher = { observe: function(aSubject, aTopic) { if (aTopic == "alertclickcallback") { WebappOSUtils.launch(aApp);
--- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -210,16 +210,17 @@ WebappsRegistry.prototype = { if (!this.hasMgmtPrivilege) { return null; } if (!this._mgmt) { 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); } return this._mgmt; }, uninit: function() { @@ -716,20 +717,25 @@ WebappsApplicationMgmt.prototype = { } cpmm.sendAsyncMessage("Webapps:ApplyDownload", { manifestURL: aApp.manifestURL }); }, 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; }, getAll: function() { let request = this.createRequest(); let window = this._window; DOMApplicationRegistry.getAll((aApps) => { Services.DOMRequest.fireSuccessAsync(request,
--- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -157,16 +157,17 @@ this.DOMApplicationRegistry = { get kHostedAppcache() "hosted-appcache", // Path to the webapps.json file where we store the registry data. appsFile: null, webapps: { }, children: [ ], allAppsLaunchable: false, _updateHandlers: [ ], + _pendingUninstalls: {}, init: function() { this.messages = ["Webapps:Install", "Webapps:Uninstall", "Webapps:GetSelf", "Webapps:CheckInstalled", "Webapps:GetInstalled", "Webapps:GetNotInstalled", "Webapps:Launch", "Webapps:InstallPackage", "Webapps:GetList", "Webapps:RegisterForMessages", @@ -542,17 +543,17 @@ this.DOMApplicationRegistry = { "https://" + app.manifestURL.substring("http://".length); // This will uninstall the http apps and remove any data hold by this // app. Bug 948105 tracks data migration from http to https apps. for (let id in this.webapps) { 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; } } #endif }, // Implements the core of bug 787439 // if at first run, go through these steps: @@ -2633,17 +2634,17 @@ this.DOMApplicationRegistry = { if (supportUseCurrentProfile()) { PermissionsInstaller.installPermissions( { origin: appObject.origin, manifestURL: appObject.manifestURL, manifest: jsonManifest }, isReinstall, - this.uninstall.bind(this, aData, aData.mm) + this.doUninstall.bind(this, aData, aData.mm) ); } this.updateDataStore(this.webapps[id].localId, this.webapps[id].origin, this.webapps[id].manifestURL, jsonManifest); } for each (let prop in ["installState", "downloadAvailable", "downloading", @@ -3670,89 +3671,124 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", manifestURL: aNewApp.manifestURL }); }); 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; + _uninstallApp: Task.async(function*(aApp) { + if (!aApp.removable) { + debug("Error: cannot uninstall a non-removable app."); + throw new Error("NON_REMOVABLE_APP"); } - let id = app.id; - - if (!app.removable) { - debug("Error: cannot uninstall a non-removable app."); - aOnFailure("NON_REMOVABLE_APP"); - return; - } + + 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) { delete this._manifestCache[id]; } // 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); try { dir.remove(true); } catch (e) {} 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) { aData.apps = []; if (aData.appId == Ci.nsIScriptSecurityManager.NO_APP_ID || aData.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) { aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData); @@ -4062,16 +4098,27 @@ this.DOMApplicationRegistry = { return aResult[0].manifest; }); }, getAppByManifestURL: function(aManifestURL) { 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); }, getAppLocalIdByStoreId: function(aStoreId) { debug("getAppLocalIdByStoreId:" + aStoreId); return AppsUtils.getAppLocalIdByStoreId(this.webapps, aStoreId);
--- a/webapprt/WebappManager.jsm +++ b/webapprt/WebappManager.jsm @@ -14,26 +14,33 @@ Cu.import("resource://gre/modules/XPCOMU Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Webapps.jsm"); Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import("resource://gre/modules/NativeApp.jsm"); 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; case "webapps-uninstall": WebappOSUtils.uninstall(data); break; } }, @@ -70,32 +77,64 @@ this.WebappManager = { if (choice == 0) { let nativeApp = new NativeApp(data.app, jsonManifest, data.app.categories, WebappRT.config.registryDir); let localDir; try { localDir = nativeApp.createProfile(); } catch (ex) { - DOMApplicationRegistry.denyInstall(aData); + DOMApplicationRegistry.denyInstall(data); return; } DOMApplicationRegistry.confirmInstall(data, localDir, Task.async(function*(aApp, aManifest, aZipPath) { yield nativeApp.install(aApp, aManifest, aZipPath); }) ); } else { DOMApplicationRegistry.denyInstall(data); } }, + 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); DOMApplicationRegistry.registerUpdateHandler(WebappManager.update);
--- a/webapprt/locales/en-US/webapprt/webapp.properties +++ b/webapprt/locales/en-US/webapprt/webapp.properties @@ -35,11 +35,19 @@ desktop-notification.remember=Remember m # LOCALIZATION NOTE (webapps.install.title): %S will be replaced with the name # of the webapp being installed. webapps.install.title=Install %S # LOCALIZATION NOTE (webapps.install.description): %S will be replaced with the # name of the webapp being installed. 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?