1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/app-manager/webapps-store.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,279 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const ObservableObject = require("devtools/shared/observable-object"); 1.9 +const promise = require("devtools/toolkit/deprecated-sync-thenables"); 1.10 +const {Connection} = require("devtools/client/connection-manager"); 1.11 + 1.12 +const {Cu} = require("chrome"); 1.13 +const dbgClient = Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); 1.14 +const _knownWebappsStores = new WeakMap(); 1.15 + 1.16 +let WebappsStore; 1.17 + 1.18 +module.exports = WebappsStore = function(connection) { 1.19 + // If we already know about this connection, 1.20 + // let's re-use the existing store. 1.21 + if (_knownWebappsStores.has(connection)) { 1.22 + return _knownWebappsStores.get(connection); 1.23 + } 1.24 + 1.25 + _knownWebappsStores.set(connection, this); 1.26 + 1.27 + ObservableObject.call(this, {}); 1.28 + 1.29 + this._resetStore(); 1.30 + 1.31 + this.destroy = this.destroy.bind(this); 1.32 + this._onStatusChanged = this._onStatusChanged.bind(this); 1.33 + 1.34 + this._connection = connection; 1.35 + this._connection.once(Connection.Events.DESTROYED, this.destroy); 1.36 + this._connection.on(Connection.Events.STATUS_CHANGED, this._onStatusChanged); 1.37 + this._onStatusChanged(); 1.38 + return this; 1.39 +} 1.40 + 1.41 +WebappsStore.prototype = { 1.42 + destroy: function() { 1.43 + if (this._connection) { 1.44 + // While this.destroy is bound using .once() above, that event may not 1.45 + // have occurred when the WebappsStore client calls destroy, so we 1.46 + // manually remove it here. 1.47 + this._connection.off(Connection.Events.DESTROYED, this.destroy); 1.48 + this._connection.off(Connection.Events.STATUS_CHANGED, this._onStatusChanged); 1.49 + _knownWebappsStores.delete(this._connection); 1.50 + this._connection = null; 1.51 + } 1.52 + }, 1.53 + 1.54 + _resetStore: function() { 1.55 + this.object.all = []; // list of app objects 1.56 + this.object.running = []; // list of manifests 1.57 + }, 1.58 + 1.59 + _getAppFromManifest: function(manifest) { 1.60 + for (let app of this.object.all) { 1.61 + if (app.manifestURL == manifest) { 1.62 + return app; 1.63 + } 1.64 + } 1.65 + return null; 1.66 + }, 1.67 + 1.68 + _onStatusChanged: function() { 1.69 + if (this._connection.status == Connection.Status.CONNECTED) { 1.70 + this._listTabs(); 1.71 + } else { 1.72 + this._resetStore(); 1.73 + } 1.74 + }, 1.75 + 1.76 + _listTabs: function() { 1.77 + this._connection.client.listTabs((resp) => { 1.78 + this._webAppsActor = resp.webappsActor; 1.79 + this._feedStore(); 1.80 + }); 1.81 + }, 1.82 + 1.83 + _feedStore: function(deviceFront, webAppsActor) { 1.84 + this._listenToApps(); 1.85 + this._getAllApps() 1.86 + .then(this._getRunningApps.bind(this)) 1.87 + .then(this._getAppsIcons.bind(this)) 1.88 + }, 1.89 + 1.90 + _listenToApps: function() { 1.91 + let deferred = promise.defer(); 1.92 + let client = this._connection.client; 1.93 + 1.94 + let request = { 1.95 + to: this._webAppsActor, 1.96 + type: "watchApps" 1.97 + }; 1.98 + 1.99 + client.request(request, (res) => { 1.100 + if (res.error) { 1.101 + return deferred.reject(res.error); 1.102 + } 1.103 + 1.104 + client.addListener("appOpen", (type, { manifestURL }) => { 1.105 + this._onAppOpen(manifestURL); 1.106 + }); 1.107 + 1.108 + client.addListener("appClose", (type, { manifestURL }) => { 1.109 + this._onAppClose(manifestURL); 1.110 + }); 1.111 + 1.112 + client.addListener("appInstall", (type, { manifestURL }) => { 1.113 + this._onAppInstall(manifestURL); 1.114 + }); 1.115 + 1.116 + client.addListener("appUninstall", (type, { manifestURL }) => { 1.117 + this._onAppUninstall(manifestURL); 1.118 + }); 1.119 + 1.120 + return deferred.resolve(); 1.121 + }) 1.122 + return deferred.promise; 1.123 + }, 1.124 + 1.125 + _getAllApps: function() { 1.126 + let deferred = promise.defer(); 1.127 + let request = { 1.128 + to: this._webAppsActor, 1.129 + type: "getAll" 1.130 + }; 1.131 + 1.132 + this._connection.client.request(request, (res) => { 1.133 + if (res.error) { 1.134 + return deferred.reject(res.error); 1.135 + } 1.136 + let apps = res.apps; 1.137 + for (let a of apps) { 1.138 + a.running = false; 1.139 + } 1.140 + this.object.all = apps; 1.141 + return deferred.resolve(); 1.142 + }); 1.143 + return deferred.promise; 1.144 + }, 1.145 + 1.146 + _getRunningApps: function() { 1.147 + let deferred = promise.defer(); 1.148 + let request = { 1.149 + to: this._webAppsActor, 1.150 + type: "listRunningApps" 1.151 + }; 1.152 + 1.153 + this._connection.client.request(request, (res) => { 1.154 + if (res.error) { 1.155 + return deferred.reject(res.error); 1.156 + } 1.157 + 1.158 + let manifests = res.apps; 1.159 + this.object.running = manifests; 1.160 + 1.161 + for (let m of manifests) { 1.162 + let a = this._getAppFromManifest(m); 1.163 + if (a) { 1.164 + a.running = true; 1.165 + } else { 1.166 + return deferred.reject("Unexpected manifest: " + m); 1.167 + } 1.168 + } 1.169 + 1.170 + return deferred.resolve(); 1.171 + }); 1.172 + return deferred.promise; 1.173 + }, 1.174 + 1.175 + _getAppsIcons: function() { 1.176 + let deferred = promise.defer(); 1.177 + let allApps = this.object.all; 1.178 + 1.179 + let request = { 1.180 + to: this._webAppsActor, 1.181 + type: "getIconAsDataURL" 1.182 + }; 1.183 + 1.184 + let client = this._connection.client; 1.185 + 1.186 + let idx = 0; 1.187 + (function getIcon() { 1.188 + if (idx == allApps.length) { 1.189 + return deferred.resolve(); 1.190 + } 1.191 + let a = allApps[idx++]; 1.192 + request.manifestURL = a.manifestURL; 1.193 + return client.request(request, (res) => { 1.194 + if (res.error) { 1.195 + Cu.reportError(res.message || res.error); 1.196 + } 1.197 + 1.198 + if (res.url) { 1.199 + a.iconURL = res.url; 1.200 + } 1.201 + getIcon(); 1.202 + }); 1.203 + })(); 1.204 + 1.205 + return deferred.promise; 1.206 + }, 1.207 + 1.208 + _onAppOpen: function(manifest) { 1.209 + let a = this._getAppFromManifest(manifest); 1.210 + a.running = true; 1.211 + let running = this.object.running; 1.212 + if (running.indexOf(manifest) < 0) { 1.213 + this.object.running.push(manifest); 1.214 + } 1.215 + }, 1.216 + 1.217 + _onAppClose: function(manifest) { 1.218 + let a = this._getAppFromManifest(manifest); 1.219 + a.running = false; 1.220 + let running = this.object.running; 1.221 + this.object.running = running.filter((m) => { 1.222 + return m != manifest; 1.223 + }); 1.224 + }, 1.225 + 1.226 + _onAppInstall: function(manifest) { 1.227 + let client = this._connection.client; 1.228 + let request = { 1.229 + to: this._webAppsActor, 1.230 + type: "getApp", 1.231 + manifestURL: manifest 1.232 + }; 1.233 + 1.234 + client.request(request, (res) => { 1.235 + if (res.error) { 1.236 + if (res.error == "forbidden") { 1.237 + // We got a notification for an app we don't have access to. 1.238 + // Ignore. 1.239 + return; 1.240 + } 1.241 + Cu.reportError(res.message || res.error); 1.242 + return; 1.243 + } 1.244 + 1.245 + let app = res.app; 1.246 + app.running = false; 1.247 + 1.248 + let notFound = true; 1.249 + let proxifiedApp; 1.250 + for (let i = 0; i < this.object.all.length; i++) { 1.251 + let storedApp = this.object.all[i]; 1.252 + if (storedApp.manifestURL == app.manifestURL) { 1.253 + this.object.all[i] = app; 1.254 + proxifiedApp = this.object.all[i]; 1.255 + notFound = false; 1.256 + break; 1.257 + } 1.258 + } 1.259 + if (notFound) { 1.260 + this.object.all.push(app); 1.261 + proxifiedApp = this.object.all[this.object.all.length - 1]; 1.262 + } 1.263 + 1.264 + request.type = "getIconAsDataURL"; 1.265 + client.request(request, (res) => { 1.266 + if (res.url) { 1.267 + proxifiedApp.iconURL = res.url; 1.268 + } 1.269 + }); 1.270 + 1.271 + // This app may have been running while being installed, so check the list 1.272 + // of running apps again to get the right answer. 1.273 + this._getRunningApps(); 1.274 + }); 1.275 + }, 1.276 + 1.277 + _onAppUninstall: function(manifest) { 1.278 + this.object.all = this.object.all.filter((app) => { 1.279 + return (app.manifestURL != manifest); 1.280 + }); 1.281 + }, 1.282 +}