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