michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const ObservableObject = require("devtools/shared/observable-object"); michael@0: const promise = require("devtools/toolkit/deprecated-sync-thenables"); michael@0: const {Connection} = require("devtools/client/connection-manager"); michael@0: michael@0: const {Cu} = require("chrome"); michael@0: const dbgClient = Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); michael@0: const _knownWebappsStores = new WeakMap(); michael@0: michael@0: let WebappsStore; michael@0: michael@0: module.exports = WebappsStore = function(connection) { michael@0: // If we already know about this connection, michael@0: // let's re-use the existing store. michael@0: if (_knownWebappsStores.has(connection)) { michael@0: return _knownWebappsStores.get(connection); michael@0: } michael@0: michael@0: _knownWebappsStores.set(connection, this); michael@0: michael@0: ObservableObject.call(this, {}); michael@0: michael@0: this._resetStore(); michael@0: michael@0: this.destroy = this.destroy.bind(this); michael@0: this._onStatusChanged = this._onStatusChanged.bind(this); michael@0: michael@0: this._connection = connection; michael@0: this._connection.once(Connection.Events.DESTROYED, this.destroy); michael@0: this._connection.on(Connection.Events.STATUS_CHANGED, this._onStatusChanged); michael@0: this._onStatusChanged(); michael@0: return this; michael@0: } michael@0: michael@0: WebappsStore.prototype = { michael@0: destroy: function() { michael@0: if (this._connection) { michael@0: // While this.destroy is bound using .once() above, that event may not michael@0: // have occurred when the WebappsStore client calls destroy, so we michael@0: // manually remove it here. michael@0: this._connection.off(Connection.Events.DESTROYED, this.destroy); michael@0: this._connection.off(Connection.Events.STATUS_CHANGED, this._onStatusChanged); michael@0: _knownWebappsStores.delete(this._connection); michael@0: this._connection = null; michael@0: } michael@0: }, michael@0: michael@0: _resetStore: function() { michael@0: this.object.all = []; // list of app objects michael@0: this.object.running = []; // list of manifests michael@0: }, michael@0: michael@0: _getAppFromManifest: function(manifest) { michael@0: for (let app of this.object.all) { michael@0: if (app.manifestURL == manifest) { michael@0: return app; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: _onStatusChanged: function() { michael@0: if (this._connection.status == Connection.Status.CONNECTED) { michael@0: this._listTabs(); michael@0: } else { michael@0: this._resetStore(); michael@0: } michael@0: }, michael@0: michael@0: _listTabs: function() { michael@0: this._connection.client.listTabs((resp) => { michael@0: this._webAppsActor = resp.webappsActor; michael@0: this._feedStore(); michael@0: }); michael@0: }, michael@0: michael@0: _feedStore: function(deviceFront, webAppsActor) { michael@0: this._listenToApps(); michael@0: this._getAllApps() michael@0: .then(this._getRunningApps.bind(this)) michael@0: .then(this._getAppsIcons.bind(this)) michael@0: }, michael@0: michael@0: _listenToApps: function() { michael@0: let deferred = promise.defer(); michael@0: let client = this._connection.client; michael@0: michael@0: let request = { michael@0: to: this._webAppsActor, michael@0: type: "watchApps" michael@0: }; michael@0: michael@0: client.request(request, (res) => { michael@0: if (res.error) { michael@0: return deferred.reject(res.error); michael@0: } michael@0: michael@0: client.addListener("appOpen", (type, { manifestURL }) => { michael@0: this._onAppOpen(manifestURL); michael@0: }); michael@0: michael@0: client.addListener("appClose", (type, { manifestURL }) => { michael@0: this._onAppClose(manifestURL); michael@0: }); michael@0: michael@0: client.addListener("appInstall", (type, { manifestURL }) => { michael@0: this._onAppInstall(manifestURL); michael@0: }); michael@0: michael@0: client.addListener("appUninstall", (type, { manifestURL }) => { michael@0: this._onAppUninstall(manifestURL); michael@0: }); michael@0: michael@0: return deferred.resolve(); michael@0: }) michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: _getAllApps: function() { michael@0: let deferred = promise.defer(); michael@0: let request = { michael@0: to: this._webAppsActor, michael@0: type: "getAll" michael@0: }; michael@0: michael@0: this._connection.client.request(request, (res) => { michael@0: if (res.error) { michael@0: return deferred.reject(res.error); michael@0: } michael@0: let apps = res.apps; michael@0: for (let a of apps) { michael@0: a.running = false; michael@0: } michael@0: this.object.all = apps; michael@0: return deferred.resolve(); michael@0: }); michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: _getRunningApps: function() { michael@0: let deferred = promise.defer(); michael@0: let request = { michael@0: to: this._webAppsActor, michael@0: type: "listRunningApps" michael@0: }; michael@0: michael@0: this._connection.client.request(request, (res) => { michael@0: if (res.error) { michael@0: return deferred.reject(res.error); michael@0: } michael@0: michael@0: let manifests = res.apps; michael@0: this.object.running = manifests; michael@0: michael@0: for (let m of manifests) { michael@0: let a = this._getAppFromManifest(m); michael@0: if (a) { michael@0: a.running = true; michael@0: } else { michael@0: return deferred.reject("Unexpected manifest: " + m); michael@0: } michael@0: } michael@0: michael@0: return deferred.resolve(); michael@0: }); michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: _getAppsIcons: function() { michael@0: let deferred = promise.defer(); michael@0: let allApps = this.object.all; michael@0: michael@0: let request = { michael@0: to: this._webAppsActor, michael@0: type: "getIconAsDataURL" michael@0: }; michael@0: michael@0: let client = this._connection.client; michael@0: michael@0: let idx = 0; michael@0: (function getIcon() { michael@0: if (idx == allApps.length) { michael@0: return deferred.resolve(); michael@0: } michael@0: let a = allApps[idx++]; michael@0: request.manifestURL = a.manifestURL; michael@0: return client.request(request, (res) => { michael@0: if (res.error) { michael@0: Cu.reportError(res.message || res.error); michael@0: } michael@0: michael@0: if (res.url) { michael@0: a.iconURL = res.url; michael@0: } michael@0: getIcon(); michael@0: }); michael@0: })(); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: _onAppOpen: function(manifest) { michael@0: let a = this._getAppFromManifest(manifest); michael@0: a.running = true; michael@0: let running = this.object.running; michael@0: if (running.indexOf(manifest) < 0) { michael@0: this.object.running.push(manifest); michael@0: } michael@0: }, michael@0: michael@0: _onAppClose: function(manifest) { michael@0: let a = this._getAppFromManifest(manifest); michael@0: a.running = false; michael@0: let running = this.object.running; michael@0: this.object.running = running.filter((m) => { michael@0: return m != manifest; michael@0: }); michael@0: }, michael@0: michael@0: _onAppInstall: function(manifest) { michael@0: let client = this._connection.client; michael@0: let request = { michael@0: to: this._webAppsActor, michael@0: type: "getApp", michael@0: manifestURL: manifest michael@0: }; michael@0: michael@0: client.request(request, (res) => { michael@0: if (res.error) { michael@0: if (res.error == "forbidden") { michael@0: // We got a notification for an app we don't have access to. michael@0: // Ignore. michael@0: return; michael@0: } michael@0: Cu.reportError(res.message || res.error); michael@0: return; michael@0: } michael@0: michael@0: let app = res.app; michael@0: app.running = false; michael@0: michael@0: let notFound = true; michael@0: let proxifiedApp; michael@0: for (let i = 0; i < this.object.all.length; i++) { michael@0: let storedApp = this.object.all[i]; michael@0: if (storedApp.manifestURL == app.manifestURL) { michael@0: this.object.all[i] = app; michael@0: proxifiedApp = this.object.all[i]; michael@0: notFound = false; michael@0: break; michael@0: } michael@0: } michael@0: if (notFound) { michael@0: this.object.all.push(app); michael@0: proxifiedApp = this.object.all[this.object.all.length - 1]; michael@0: } michael@0: michael@0: request.type = "getIconAsDataURL"; michael@0: client.request(request, (res) => { michael@0: if (res.url) { michael@0: proxifiedApp.iconURL = res.url; michael@0: } michael@0: }); michael@0: michael@0: // This app may have been running while being installed, so check the list michael@0: // of running apps again to get the right answer. michael@0: this._getRunningApps(); michael@0: }); michael@0: }, michael@0: michael@0: _onAppUninstall: function(manifest) { michael@0: this.object.all = this.object.all.filter((app) => { michael@0: return (app.manifestURL != manifest); michael@0: }); michael@0: }, michael@0: }