|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = ["WebappManager"]; |
|
6 |
|
7 let Ci = Components.interfaces; |
|
8 let Cc = Components.classes; |
|
9 let Cu = Components.utils; |
|
10 |
|
11 Cu.import("resource://gre/modules/Services.jsm"); |
|
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
13 Cu.import("resource://gre/modules/Webapps.jsm"); |
|
14 Cu.import("resource://gre/modules/AppsUtils.jsm"); |
|
15 Cu.import("resource://gre/modules/Task.jsm"); |
|
16 Cu.import("resource://gre/modules/Promise.jsm"); |
|
17 |
|
18 XPCOMUtils.defineLazyModuleGetter(this, "NativeApp", |
|
19 "resource://gre/modules/NativeApp.jsm"); |
|
20 |
|
21 XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils", |
|
22 "resource://gre/modules/WebappOSUtils.jsm"); |
|
23 |
|
24 XPCOMUtils.defineLazyServiceGetter(this, "cpmm", |
|
25 "@mozilla.org/childprocessmessagemanager;1", |
|
26 "nsIMessageSender"); |
|
27 |
|
28 this.WebappManager = { |
|
29 // List of promises for in-progress installations |
|
30 installations: {}, |
|
31 |
|
32 init: function() { |
|
33 Services.obs.addObserver(this, "webapps-ask-install", false); |
|
34 Services.obs.addObserver(this, "webapps-launch", false); |
|
35 Services.obs.addObserver(this, "webapps-uninstall", false); |
|
36 cpmm.addMessageListener("Webapps:Install:Return:OK", this); |
|
37 cpmm.addMessageListener("Webapps:Install:Return:KO", this); |
|
38 cpmm.addMessageListener("Webapps:UpdateState", this); |
|
39 }, |
|
40 |
|
41 uninit: function() { |
|
42 Services.obs.removeObserver(this, "webapps-ask-install"); |
|
43 Services.obs.removeObserver(this, "webapps-launch"); |
|
44 Services.obs.removeObserver(this, "webapps-uninstall"); |
|
45 cpmm.removeMessageListener("Webapps:Install:Return:OK", this); |
|
46 cpmm.removeMessageListener("Webapps:Install:Return:KO", this); |
|
47 cpmm.removeMessageListener("Webapps:UpdateState", this); |
|
48 }, |
|
49 |
|
50 receiveMessage: function(aMessage) { |
|
51 let data = aMessage.data; |
|
52 |
|
53 let manifestURL = data.manifestURL || |
|
54 (data.app && data.app.manifestURL) || |
|
55 data.manifest; |
|
56 |
|
57 if (!this.installations[manifestURL]) { |
|
58 return; |
|
59 } |
|
60 |
|
61 if (aMessage.name == "Webapps:UpdateState") { |
|
62 if (data.error) { |
|
63 this.installations[manifestURL].reject(data.error); |
|
64 } else if (data.app.installState == "installed") { |
|
65 this.installations[manifestURL].resolve(); |
|
66 } |
|
67 } else if (aMessage.name == "Webapps:Install:Return:OK" && |
|
68 !data.isPackage) { |
|
69 let manifest = new ManifestHelper(data.app.manifest, data.app.origin); |
|
70 if (!manifest.appcache_path) { |
|
71 this.installations[manifestURL].resolve(); |
|
72 } |
|
73 } else if (aMessage.name == "Webapps:Install:Return:KO") { |
|
74 this.installations[manifestURL].reject(data.error); |
|
75 } |
|
76 }, |
|
77 |
|
78 observe: function(aSubject, aTopic, aData) { |
|
79 let data = JSON.parse(aData); |
|
80 data.mm = aSubject; |
|
81 |
|
82 switch(aTopic) { |
|
83 case "webapps-ask-install": |
|
84 let win = this._getWindowForId(data.oid); |
|
85 if (win && win.location.href == data.from) { |
|
86 this.doInstall(data, win); |
|
87 } |
|
88 break; |
|
89 case "webapps-launch": |
|
90 WebappOSUtils.launch(data); |
|
91 break; |
|
92 case "webapps-uninstall": |
|
93 WebappOSUtils.uninstall(data); |
|
94 break; |
|
95 } |
|
96 }, |
|
97 |
|
98 _getWindowForId: function(aId) { |
|
99 let someWindow = Services.wm.getMostRecentWindow(null); |
|
100 return someWindow && Services.wm.getOuterWindowWithId(aId); |
|
101 }, |
|
102 |
|
103 doInstall: function(aData, aWindow) { |
|
104 let browser = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
|
105 .getInterface(Ci.nsIWebNavigation) |
|
106 .QueryInterface(Ci.nsIDocShell) |
|
107 .chromeEventHandler; |
|
108 let chromeDoc = browser.ownerDocument; |
|
109 let chromeWin = chromeDoc.defaultView; |
|
110 let popupProgressContent = |
|
111 chromeDoc.getElementById("webapps-install-progress-content"); |
|
112 |
|
113 let bundle = chromeWin.gNavigatorBundle; |
|
114 |
|
115 let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest; |
|
116 |
|
117 let notification; |
|
118 |
|
119 let mainAction = { |
|
120 label: bundle.getString("webapps.install"), |
|
121 accessKey: bundle.getString("webapps.install.accesskey"), |
|
122 callback: () => { |
|
123 notification.remove(); |
|
124 |
|
125 notification = chromeWin.PopupNotifications. |
|
126 show(browser, |
|
127 "webapps-install-progress", |
|
128 bundle.getString("webapps.install.inprogress"), |
|
129 "webapps-notification-icon"); |
|
130 |
|
131 let progressMeter = chromeDoc.createElement("progressmeter"); |
|
132 progressMeter.setAttribute("mode", "undetermined"); |
|
133 popupProgressContent.appendChild(progressMeter); |
|
134 |
|
135 let manifestURL = aData.app.manifestURL; |
|
136 |
|
137 let cleanup = () => { |
|
138 popupProgressContent.removeChild(progressMeter); |
|
139 delete this.installations[manifestURL]; |
|
140 if (Object.getOwnPropertyNames(this.installations).length == 0) { |
|
141 notification.remove(); |
|
142 } |
|
143 }; |
|
144 |
|
145 this.installations[manifestURL] = Promise.defer(); |
|
146 this.installations[manifestURL].promise.then(null, (error) => { |
|
147 Cu.reportError("Error installing webapp: " + error); |
|
148 cleanup(); |
|
149 }); |
|
150 |
|
151 let nativeApp = new NativeApp(aData.app, jsonManifest, |
|
152 aData.app.categories); |
|
153 let localDir; |
|
154 try { |
|
155 localDir = nativeApp.createProfile(); |
|
156 } catch (ex) { |
|
157 Cu.reportError("Error installing webapp: " + ex); |
|
158 DOMApplicationRegistry.denyInstall(aData); |
|
159 cleanup(); |
|
160 return; |
|
161 } |
|
162 |
|
163 DOMApplicationRegistry.confirmInstall(aData, localDir, |
|
164 (aManifest, aZipPath) => Task.spawn((function*() { |
|
165 try { |
|
166 yield nativeApp.install(aManifest, aZipPath); |
|
167 yield this.installations[manifestURL].promise; |
|
168 notifyInstallSuccess(aData.app, nativeApp, bundle); |
|
169 } catch (ex) { |
|
170 Cu.reportError("Error installing webapp: " + ex); |
|
171 // TODO: Notify user that the installation has failed |
|
172 } finally { |
|
173 cleanup(); |
|
174 } |
|
175 }).bind(this)) |
|
176 ); |
|
177 } |
|
178 }; |
|
179 |
|
180 let requestingURI = chromeWin.makeURI(aData.from); |
|
181 let manifest = new ManifestHelper(jsonManifest, aData.app.origin); |
|
182 |
|
183 let host; |
|
184 try { |
|
185 host = requestingURI.host; |
|
186 } catch(e) { |
|
187 host = requestingURI.spec; |
|
188 } |
|
189 |
|
190 let message = bundle.getFormattedString("webapps.requestInstall", |
|
191 [manifest.name, host], 2); |
|
192 |
|
193 notification = chromeWin.PopupNotifications.show(browser, |
|
194 "webapps-install", |
|
195 message, |
|
196 "webapps-notification-icon", |
|
197 mainAction); |
|
198 |
|
199 } |
|
200 } |
|
201 |
|
202 function notifyInstallSuccess(aApp, aNativeApp, aBundle) { |
|
203 let launcher = { |
|
204 observe: function(aSubject, aTopic) { |
|
205 if (aTopic == "alertclickcallback") { |
|
206 WebappOSUtils.launch(aApp); |
|
207 } |
|
208 } |
|
209 }; |
|
210 |
|
211 try { |
|
212 let notifier = Cc["@mozilla.org/alerts-service;1"]. |
|
213 getService(Ci.nsIAlertsService); |
|
214 |
|
215 notifier.showAlertNotification(aNativeApp.iconURI.spec, |
|
216 aBundle.getString("webapps.install.success"), |
|
217 aNativeApp.appNameAsFilename, |
|
218 true, null, launcher); |
|
219 } catch (ex) {} |
|
220 } |