|
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 |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 const ObservableObject = require("devtools/shared/observable-object"); |
|
6 const promise = require("devtools/toolkit/deprecated-sync-thenables"); |
|
7 const {Connection} = require("devtools/client/connection-manager"); |
|
8 |
|
9 const {Cu} = require("chrome"); |
|
10 const dbgClient = Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); |
|
11 const _knownWebappsStores = new WeakMap(); |
|
12 |
|
13 let WebappsStore; |
|
14 |
|
15 module.exports = WebappsStore = function(connection) { |
|
16 // If we already know about this connection, |
|
17 // let's re-use the existing store. |
|
18 if (_knownWebappsStores.has(connection)) { |
|
19 return _knownWebappsStores.get(connection); |
|
20 } |
|
21 |
|
22 _knownWebappsStores.set(connection, this); |
|
23 |
|
24 ObservableObject.call(this, {}); |
|
25 |
|
26 this._resetStore(); |
|
27 |
|
28 this.destroy = this.destroy.bind(this); |
|
29 this._onStatusChanged = this._onStatusChanged.bind(this); |
|
30 |
|
31 this._connection = connection; |
|
32 this._connection.once(Connection.Events.DESTROYED, this.destroy); |
|
33 this._connection.on(Connection.Events.STATUS_CHANGED, this._onStatusChanged); |
|
34 this._onStatusChanged(); |
|
35 return this; |
|
36 } |
|
37 |
|
38 WebappsStore.prototype = { |
|
39 destroy: function() { |
|
40 if (this._connection) { |
|
41 // While this.destroy is bound using .once() above, that event may not |
|
42 // have occurred when the WebappsStore client calls destroy, so we |
|
43 // manually remove it here. |
|
44 this._connection.off(Connection.Events.DESTROYED, this.destroy); |
|
45 this._connection.off(Connection.Events.STATUS_CHANGED, this._onStatusChanged); |
|
46 _knownWebappsStores.delete(this._connection); |
|
47 this._connection = null; |
|
48 } |
|
49 }, |
|
50 |
|
51 _resetStore: function() { |
|
52 this.object.all = []; // list of app objects |
|
53 this.object.running = []; // list of manifests |
|
54 }, |
|
55 |
|
56 _getAppFromManifest: function(manifest) { |
|
57 for (let app of this.object.all) { |
|
58 if (app.manifestURL == manifest) { |
|
59 return app; |
|
60 } |
|
61 } |
|
62 return null; |
|
63 }, |
|
64 |
|
65 _onStatusChanged: function() { |
|
66 if (this._connection.status == Connection.Status.CONNECTED) { |
|
67 this._listTabs(); |
|
68 } else { |
|
69 this._resetStore(); |
|
70 } |
|
71 }, |
|
72 |
|
73 _listTabs: function() { |
|
74 this._connection.client.listTabs((resp) => { |
|
75 this._webAppsActor = resp.webappsActor; |
|
76 this._feedStore(); |
|
77 }); |
|
78 }, |
|
79 |
|
80 _feedStore: function(deviceFront, webAppsActor) { |
|
81 this._listenToApps(); |
|
82 this._getAllApps() |
|
83 .then(this._getRunningApps.bind(this)) |
|
84 .then(this._getAppsIcons.bind(this)) |
|
85 }, |
|
86 |
|
87 _listenToApps: function() { |
|
88 let deferred = promise.defer(); |
|
89 let client = this._connection.client; |
|
90 |
|
91 let request = { |
|
92 to: this._webAppsActor, |
|
93 type: "watchApps" |
|
94 }; |
|
95 |
|
96 client.request(request, (res) => { |
|
97 if (res.error) { |
|
98 return deferred.reject(res.error); |
|
99 } |
|
100 |
|
101 client.addListener("appOpen", (type, { manifestURL }) => { |
|
102 this._onAppOpen(manifestURL); |
|
103 }); |
|
104 |
|
105 client.addListener("appClose", (type, { manifestURL }) => { |
|
106 this._onAppClose(manifestURL); |
|
107 }); |
|
108 |
|
109 client.addListener("appInstall", (type, { manifestURL }) => { |
|
110 this._onAppInstall(manifestURL); |
|
111 }); |
|
112 |
|
113 client.addListener("appUninstall", (type, { manifestURL }) => { |
|
114 this._onAppUninstall(manifestURL); |
|
115 }); |
|
116 |
|
117 return deferred.resolve(); |
|
118 }) |
|
119 return deferred.promise; |
|
120 }, |
|
121 |
|
122 _getAllApps: function() { |
|
123 let deferred = promise.defer(); |
|
124 let request = { |
|
125 to: this._webAppsActor, |
|
126 type: "getAll" |
|
127 }; |
|
128 |
|
129 this._connection.client.request(request, (res) => { |
|
130 if (res.error) { |
|
131 return deferred.reject(res.error); |
|
132 } |
|
133 let apps = res.apps; |
|
134 for (let a of apps) { |
|
135 a.running = false; |
|
136 } |
|
137 this.object.all = apps; |
|
138 return deferred.resolve(); |
|
139 }); |
|
140 return deferred.promise; |
|
141 }, |
|
142 |
|
143 _getRunningApps: function() { |
|
144 let deferred = promise.defer(); |
|
145 let request = { |
|
146 to: this._webAppsActor, |
|
147 type: "listRunningApps" |
|
148 }; |
|
149 |
|
150 this._connection.client.request(request, (res) => { |
|
151 if (res.error) { |
|
152 return deferred.reject(res.error); |
|
153 } |
|
154 |
|
155 let manifests = res.apps; |
|
156 this.object.running = manifests; |
|
157 |
|
158 for (let m of manifests) { |
|
159 let a = this._getAppFromManifest(m); |
|
160 if (a) { |
|
161 a.running = true; |
|
162 } else { |
|
163 return deferred.reject("Unexpected manifest: " + m); |
|
164 } |
|
165 } |
|
166 |
|
167 return deferred.resolve(); |
|
168 }); |
|
169 return deferred.promise; |
|
170 }, |
|
171 |
|
172 _getAppsIcons: function() { |
|
173 let deferred = promise.defer(); |
|
174 let allApps = this.object.all; |
|
175 |
|
176 let request = { |
|
177 to: this._webAppsActor, |
|
178 type: "getIconAsDataURL" |
|
179 }; |
|
180 |
|
181 let client = this._connection.client; |
|
182 |
|
183 let idx = 0; |
|
184 (function getIcon() { |
|
185 if (idx == allApps.length) { |
|
186 return deferred.resolve(); |
|
187 } |
|
188 let a = allApps[idx++]; |
|
189 request.manifestURL = a.manifestURL; |
|
190 return client.request(request, (res) => { |
|
191 if (res.error) { |
|
192 Cu.reportError(res.message || res.error); |
|
193 } |
|
194 |
|
195 if (res.url) { |
|
196 a.iconURL = res.url; |
|
197 } |
|
198 getIcon(); |
|
199 }); |
|
200 })(); |
|
201 |
|
202 return deferred.promise; |
|
203 }, |
|
204 |
|
205 _onAppOpen: function(manifest) { |
|
206 let a = this._getAppFromManifest(manifest); |
|
207 a.running = true; |
|
208 let running = this.object.running; |
|
209 if (running.indexOf(manifest) < 0) { |
|
210 this.object.running.push(manifest); |
|
211 } |
|
212 }, |
|
213 |
|
214 _onAppClose: function(manifest) { |
|
215 let a = this._getAppFromManifest(manifest); |
|
216 a.running = false; |
|
217 let running = this.object.running; |
|
218 this.object.running = running.filter((m) => { |
|
219 return m != manifest; |
|
220 }); |
|
221 }, |
|
222 |
|
223 _onAppInstall: function(manifest) { |
|
224 let client = this._connection.client; |
|
225 let request = { |
|
226 to: this._webAppsActor, |
|
227 type: "getApp", |
|
228 manifestURL: manifest |
|
229 }; |
|
230 |
|
231 client.request(request, (res) => { |
|
232 if (res.error) { |
|
233 if (res.error == "forbidden") { |
|
234 // We got a notification for an app we don't have access to. |
|
235 // Ignore. |
|
236 return; |
|
237 } |
|
238 Cu.reportError(res.message || res.error); |
|
239 return; |
|
240 } |
|
241 |
|
242 let app = res.app; |
|
243 app.running = false; |
|
244 |
|
245 let notFound = true; |
|
246 let proxifiedApp; |
|
247 for (let i = 0; i < this.object.all.length; i++) { |
|
248 let storedApp = this.object.all[i]; |
|
249 if (storedApp.manifestURL == app.manifestURL) { |
|
250 this.object.all[i] = app; |
|
251 proxifiedApp = this.object.all[i]; |
|
252 notFound = false; |
|
253 break; |
|
254 } |
|
255 } |
|
256 if (notFound) { |
|
257 this.object.all.push(app); |
|
258 proxifiedApp = this.object.all[this.object.all.length - 1]; |
|
259 } |
|
260 |
|
261 request.type = "getIconAsDataURL"; |
|
262 client.request(request, (res) => { |
|
263 if (res.url) { |
|
264 proxifiedApp.iconURL = res.url; |
|
265 } |
|
266 }); |
|
267 |
|
268 // This app may have been running while being installed, so check the list |
|
269 // of running apps again to get the right answer. |
|
270 this._getRunningApps(); |
|
271 }); |
|
272 }, |
|
273 |
|
274 _onAppUninstall: function(manifest) { |
|
275 this.object.all = this.object.all.filter((app) => { |
|
276 return (app.manifestURL != manifest); |
|
277 }); |
|
278 }, |
|
279 } |