Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
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");
9 const {Cu} = require("chrome");
10 const dbgClient = Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
11 const _knownWebappsStores = new WeakMap();
13 let WebappsStore;
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 }
22 _knownWebappsStores.set(connection, this);
24 ObservableObject.call(this, {});
26 this._resetStore();
28 this.destroy = this.destroy.bind(this);
29 this._onStatusChanged = this._onStatusChanged.bind(this);
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 }
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 },
51 _resetStore: function() {
52 this.object.all = []; // list of app objects
53 this.object.running = []; // list of manifests
54 },
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 },
65 _onStatusChanged: function() {
66 if (this._connection.status == Connection.Status.CONNECTED) {
67 this._listTabs();
68 } else {
69 this._resetStore();
70 }
71 },
73 _listTabs: function() {
74 this._connection.client.listTabs((resp) => {
75 this._webAppsActor = resp.webappsActor;
76 this._feedStore();
77 });
78 },
80 _feedStore: function(deviceFront, webAppsActor) {
81 this._listenToApps();
82 this._getAllApps()
83 .then(this._getRunningApps.bind(this))
84 .then(this._getAppsIcons.bind(this))
85 },
87 _listenToApps: function() {
88 let deferred = promise.defer();
89 let client = this._connection.client;
91 let request = {
92 to: this._webAppsActor,
93 type: "watchApps"
94 };
96 client.request(request, (res) => {
97 if (res.error) {
98 return deferred.reject(res.error);
99 }
101 client.addListener("appOpen", (type, { manifestURL }) => {
102 this._onAppOpen(manifestURL);
103 });
105 client.addListener("appClose", (type, { manifestURL }) => {
106 this._onAppClose(manifestURL);
107 });
109 client.addListener("appInstall", (type, { manifestURL }) => {
110 this._onAppInstall(manifestURL);
111 });
113 client.addListener("appUninstall", (type, { manifestURL }) => {
114 this._onAppUninstall(manifestURL);
115 });
117 return deferred.resolve();
118 })
119 return deferred.promise;
120 },
122 _getAllApps: function() {
123 let deferred = promise.defer();
124 let request = {
125 to: this._webAppsActor,
126 type: "getAll"
127 };
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 },
143 _getRunningApps: function() {
144 let deferred = promise.defer();
145 let request = {
146 to: this._webAppsActor,
147 type: "listRunningApps"
148 };
150 this._connection.client.request(request, (res) => {
151 if (res.error) {
152 return deferred.reject(res.error);
153 }
155 let manifests = res.apps;
156 this.object.running = manifests;
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 }
167 return deferred.resolve();
168 });
169 return deferred.promise;
170 },
172 _getAppsIcons: function() {
173 let deferred = promise.defer();
174 let allApps = this.object.all;
176 let request = {
177 to: this._webAppsActor,
178 type: "getIconAsDataURL"
179 };
181 let client = this._connection.client;
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 }
195 if (res.url) {
196 a.iconURL = res.url;
197 }
198 getIcon();
199 });
200 })();
202 return deferred.promise;
203 },
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 },
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 },
223 _onAppInstall: function(manifest) {
224 let client = this._connection.client;
225 let request = {
226 to: this._webAppsActor,
227 type: "getApp",
228 manifestURL: manifest
229 };
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 }
242 let app = res.app;
243 app.running = false;
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 }
261 request.type = "getIconAsDataURL";
262 client.request(request, (res) => {
263 if (res.url) {
264 proxifiedApp.iconURL = res.url;
265 }
266 });
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 },
274 _onAppUninstall: function(manifest) {
275 this.object.all = this.object.all.filter((app) => {
276 return (app.manifestURL != manifest);
277 });
278 },
279 }