gecko/browser/modules/WebappManager.jsm
Mike Conley 86cf179c70 Bug 1077168 - Notice when any content requesting Webapp permissions browses away, and remove the notification. r=felipe.
PopupNotifications are great at noticing when the top outer frame changes
location, and clears any pending notifications accordingly. It is, however,
not so great at noticing when subframes that have caused notifications
change location.

Now, when prompting for install / uninstall permissions, we add an
nsIWebProgressListener to the gBrowser that will remove the
notification if we hear that the requesting content has browsed
away.
2015-02-11 11:46:50 -05:00

340 lines
11 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = ["WebappManager"];
let Ci = Components.interfaces;
let Cc = Components.classes;
let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
"resource://gre/modules/NativeApp.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
"resource://gre/modules/WebappOSUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"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.sendAsyncMessage("Webapps:RegisterForMessages",
{ messages: ["Webapps:Install:Return:OK",
"Webapps:Install:Return:KO",
"Webapps:UpdateState"]});
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.sendAsyncMessage("Webapps:UnregisterForMessages",
["Webapps:Install:Return:OK",
"Webapps:Install:Return:KO",
"Webapps:UpdateState"]);
cpmm.removeMessageListener("Webapps:Install:Return:OK", this);
cpmm.removeMessageListener("Webapps:Install:Return:KO", this);
cpmm.removeMessageListener("Webapps:UpdateState", this);
},
receiveMessage: function(aMessage) {
let data = aMessage.data;
let manifestURL = data.manifestURL ||
(data.app && data.app.manifestURL) ||
data.manifest;
if (!this.installations[manifestURL]) {
return;
}
if (aMessage.name == "Webapps:UpdateState") {
if (data.error) {
this.installations[manifestURL].reject(data.error);
} else if (data.app.installState == "installed") {
this.installations[manifestURL].resolve();
}
} else if (aMessage.name == "Webapps:Install:Return:OK" &&
!data.isPackage) {
let manifest = new ManifestHelper(data.app.manifest,
data.app.origin,
data.app.manifestURL);
if (!manifest.appcache_path) {
this.installations[manifestURL].resolve();
}
} else if (aMessage.name == "Webapps:Install:Return:KO") {
this.installations[manifestURL].reject(data.error);
}
},
observe: function(aSubject, aTopic, aData) {
let data = JSON.parse(aData);
data.mm = aSubject;
let browser;
switch(aTopic) {
case "webapps-ask-install":
browser = this._getBrowserForId(data.topId);
if (browser) {
this.doInstall(data, browser);
}
break;
case "webapps-ask-uninstall":
browser = this._getBrowserForId(data.topId);
if (browser) {
this.doUninstall(data, browser);
}
break;
case "webapps-launch":
WebappOSUtils.launch(data);
break;
case "webapps-uninstall":
WebappOSUtils.uninstall(data);
break;
}
},
_getBrowserForId: function(aId) {
let windows = Services.wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) {
let window = windows.getNext();
let tabbrowser = window.gBrowser;
let foundBrowser = tabbrowser.getBrowserForOuterWindowID(aId);
if (foundBrowser) {
return foundBrowser;
}
}
let foundWindow = Services.wm.getOuterWindowWithId(aId);
if (foundWindow) {
return foundWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
}
return null;
},
doInstall: function(aData, aBrowser) {
let chromeDoc = aBrowser.ownerDocument;
let chromeWin = chromeDoc.defaultView;
let popupProgressContent =
chromeDoc.getElementById("webapps-install-progress-content");
let bundle = chromeWin.gNavigatorBundle;
let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
let notification;
let mainAction = {
label: bundle.getString("webapps.install"),
accessKey: bundle.getString("webapps.install.accesskey"),
callback: () => {
notification.remove();
notification = chromeWin.PopupNotifications.
show(aBrowser,
"webapps-install-progress",
bundle.getString("webapps.install.inprogress"),
"webapps-notification-icon");
let progressMeter = chromeDoc.createElement("progressmeter");
progressMeter.setAttribute("mode", "undetermined");
popupProgressContent.appendChild(progressMeter);
let manifestURL = aData.app.manifestURL;
let nativeApp = new NativeApp(aData.app, jsonManifest,
aData.app.categories);
this.installations[manifestURL] = Promise.defer();
this.installations[manifestURL].promise.then(() => {
notifyInstallSuccess(aData.app, nativeApp, bundle,
PrivateBrowsingUtils.isBrowserPrivate(aBrowser));
}, (error) => {
Cu.reportError("Error installing webapp: " + error);
}).then(() => {
popupProgressContent.removeChild(progressMeter);
delete this.installations[manifestURL];
if (Object.getOwnPropertyNames(this.installations).length == 0) {
notification.remove();
}
});
let localDir;
try {
localDir = nativeApp.createProfile();
} catch (ex) {
DOMApplicationRegistry.denyInstall(aData);
return;
}
DOMApplicationRegistry.confirmInstall(aData, localDir,
Task.async(function*(aApp, aManifest, aZipPath) {
try {
yield nativeApp.install(aApp, aManifest, aZipPath);
} catch (ex) {
Cu.reportError("Error installing webapp: " + ex);
throw ex;
}
})
);
}
};
let requestingURI = chromeWin.makeURI(aData.from);
let app = aData.app;
let manifest = new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
let host;
try {
host = requestingURI.host;
} catch(e) {
host = requestingURI.spec;
}
let message = bundle.getFormattedString("webapps.requestInstall",
[manifest.name, host], 2);
let eventCallback = null;
let gBrowser = chromeWin.gBrowser;
if (gBrowser) {
let windowID = aData.oid;
let listener = {
onLocationChange(webProgress) {
if (webProgress.DOMWindowID == windowID) {
notification.remove();
}
}
};
gBrowser.addProgressListener(listener);
eventCallback = (event) => {
if (event != "removed") {
return;
}
// The notification was removed, so we should
// remove our listener.
gBrowser.removeProgressListener(listener);
}
}
notification = chromeWin.PopupNotifications.show(aBrowser,
"webapps-install",
message,
"webapps-notification-icon",
mainAction, [],
eventCallback);
},
doUninstall: function(aData, aBrowser) {
let chromeDoc = aBrowser.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]);
let eventCallback = null;
let gBrowser = chromeWin.gBrowser;
if (gBrowser) {
let windowID = aData.oid;
let listener = {
onLocationChange(webProgress) {
if (webProgress.DOMWindowID == windowID) {
notification.remove();
}
}
};
gBrowser.addProgressListener(listener);
eventCallback = (event) => {
if (event != "removed") {
return;
}
// The notification was removed, so we should
// remove our listener.
gBrowser.removeProgressListener(listener);
}
}
notification = chromeWin.PopupNotifications.show(
aBrowser, "webapps-uninstall", message,
"webapps-notification-icon",
mainAction, [secondaryAction],
eventCallback);
}
}
function notifyInstallSuccess(aApp, aNativeApp, aBundle, aInPrivateBrowsing) {
let launcher = {
observe: function(aSubject, aTopic) {
if (aTopic == "alertclickcallback") {
WebappOSUtils.launch(aApp);
}
}
};
try {
let notifier = Cc["@mozilla.org/alerts-service;1"].
getService(Ci.nsIAlertsService);
notifier.showAlertNotification(aNativeApp.iconURI.spec,
aBundle.getString("webapps.install.success"),
aNativeApp.appNameAsFilename,
true, null, launcher, "", "", "", "", null,
aInPrivateBrowsing);
} catch (ex) {}
}