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: "use strict"; michael@0: michael@0: let Cu = Components.utils; michael@0: let Cc = Components.classes; michael@0: let Ci = Components.interfaces; michael@0: let CC = Components.Constructor; michael@0: michael@0: Cu.import("resource://gre/modules/osfile.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); michael@0: michael@0: let promise; michael@0: michael@0: function debug(aMsg) { michael@0: /* michael@0: Cc["@mozilla.org/consoleservice;1"] michael@0: .getService(Ci.nsIConsoleService) michael@0: .logStringMessage("--*-- WebappsActor : " + aMsg); michael@0: */ michael@0: } michael@0: michael@0: function PackageUploadActor(aPath, aFile) { michael@0: this._path = aPath; michael@0: this._file = aFile; michael@0: this.size = 0; michael@0: } michael@0: michael@0: PackageUploadActor.prototype = { michael@0: actorPrefix: "packageUploadActor", michael@0: michael@0: /** michael@0: * This method isn't exposed to the client. michael@0: * It is meant to be called by server code, in order to get michael@0: * access to the temporary file out of the actor ID. michael@0: */ michael@0: getFilePath: function () { michael@0: return this._path; michael@0: }, michael@0: michael@0: /** michael@0: * This method allows you to upload a piece of file. michael@0: * It expects a chunk argument that is the a string to write to the file. michael@0: */ michael@0: chunk: function (aRequest) { michael@0: let chunk = aRequest.chunk; michael@0: if (!chunk || chunk.length <= 0) { michael@0: return {error: "parameterError", michael@0: message: "Missing or invalid chunk argument"}; michael@0: } michael@0: // Translate the string used to transfer the chunk over JSON michael@0: // back to a typed array michael@0: let data = new Uint8Array(chunk.length); michael@0: for (let i = 0, l = chunk.length; i < l ; i++) { michael@0: data[i] = chunk.charCodeAt(i); michael@0: } michael@0: return this._file.write(data) michael@0: .then((written) => { michael@0: this.size += written; michael@0: return { michael@0: written: written, michael@0: size: this.size michael@0: }; michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * This method needs to be called, when you are done uploading michael@0: * chunks, before trying to access/use the temporary file. michael@0: * Otherwise, the file may be partially written michael@0: * and also be locked. michael@0: */ michael@0: done: function (aRequest) { michael@0: this._file.close(); michael@0: return {}; michael@0: }, michael@0: michael@0: /** michael@0: * This method allows you to delete the temporary file, michael@0: * when you are done using it. michael@0: */ michael@0: remove: function (aRequest) { michael@0: this._cleanupFile(); michael@0: return {}; michael@0: }, michael@0: michael@0: _cleanupFile: function () { michael@0: try { michael@0: this._file.close(); michael@0: } catch(e) {} michael@0: try { michael@0: OS.File.remove(this._path); michael@0: } catch(e) {} michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The request types this actor can handle. michael@0: */ michael@0: PackageUploadActor.prototype.requestTypes = { michael@0: "chunk": PackageUploadActor.prototype.chunk, michael@0: "done": PackageUploadActor.prototype.done, michael@0: "remove": PackageUploadActor.prototype.remove michael@0: }; michael@0: michael@0: /** michael@0: * Creates a WebappsActor. WebappsActor provides remote access to michael@0: * install apps. michael@0: */ michael@0: function WebappsActor(aConnection) { michael@0: debug("init"); michael@0: // Load actor dependencies lazily as this actor require extra environnement michael@0: // preparation to work (like have a profile setup in xpcshell tests) michael@0: michael@0: Cu.import("resource://gre/modules/Webapps.jsm"); michael@0: Cu.import("resource://gre/modules/AppsUtils.jsm"); michael@0: Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: michael@0: // Keep reference of already created app actors. michael@0: // key: app frame message manager, value: ContentActor's grip() value michael@0: this._appActorsMap = new Map(); michael@0: michael@0: this.conn = aConnection; michael@0: this._uploads = []; michael@0: this._actorPool = new ActorPool(this.conn); michael@0: this.conn.addActorPool(this._actorPool); michael@0: } michael@0: michael@0: WebappsActor.prototype = { michael@0: actorPrefix: "webapps", michael@0: michael@0: disconnect: function () { michael@0: // When we stop using this actor, we should ensure removing all files. michael@0: for (let upload of this._uploads) { michael@0: upload.remove(); michael@0: } michael@0: this._uploads = null; michael@0: michael@0: this.conn.removeActorPool(this._actorPool); michael@0: this._actorPool = null; michael@0: this.conn = null; michael@0: }, michael@0: michael@0: _registerApp: function wa_actorRegisterApp(aDeferred, aApp, aId, aDir) { michael@0: debug("registerApp"); michael@0: let reg = DOMApplicationRegistry; michael@0: let self = this; michael@0: michael@0: // Clean up the deprecated manifest cache if needed. michael@0: if (aId in reg._manifestCache) { michael@0: delete reg._manifestCache[aId]; michael@0: } michael@0: michael@0: aApp.installTime = Date.now(); michael@0: aApp.installState = "installed"; michael@0: aApp.removable = true; michael@0: aApp.id = aId; michael@0: aApp.basePath = reg.getWebAppsBasePath(); michael@0: aApp.localId = (aId in reg.webapps) ? reg.webapps[aId].localId michael@0: : reg._nextLocalId(); michael@0: michael@0: reg.webapps[aId] = aApp; michael@0: reg.updatePermissionsForApp(aId); michael@0: michael@0: reg._readManifests([{ id: aId }]).then((aResult) => { michael@0: let manifest = aResult[0].manifest; michael@0: aApp.name = manifest.name; michael@0: reg.updateAppHandlers(null, manifest, aApp); michael@0: michael@0: reg._saveApps().then(() => { michael@0: aApp.manifest = manifest; michael@0: michael@0: // Needed to evict manifest cache on content side michael@0: // (has to be dispatched first, otherwise other messages like michael@0: // Install:Return:OK are going to use old manifest version) michael@0: reg.broadcastMessage("Webapps:UpdateState", { michael@0: app: aApp, michael@0: manifest: manifest, michael@0: manifestURL: aApp.manifestURL michael@0: }); michael@0: reg.broadcastMessage("Webapps:FireEvent", { michael@0: eventType: ["downloadsuccess", "downloadapplied"], michael@0: manifestURL: aApp.manifestURL michael@0: }); michael@0: reg.broadcastMessage("Webapps:AddApp", { id: aId, app: aApp }); michael@0: reg.broadcastMessage("Webapps:Install:Return:OK", { michael@0: app: aApp, michael@0: oid: "foo", michael@0: requestID: "bar" michael@0: }); michael@0: michael@0: Services.obs.notifyObservers(null, "webapps-installed", michael@0: JSON.stringify({ manifestURL: aApp.manifestURL })); michael@0: michael@0: delete aApp.manifest; michael@0: aDeferred.resolve({ appId: aId, path: aDir.path }); michael@0: michael@0: // We can't have appcache for packaged apps. michael@0: if (!aApp.origin.startsWith("app://")) { michael@0: reg.startOfflineCacheDownload(new ManifestHelper(manifest, aApp.origin)); michael@0: } michael@0: }); michael@0: // Cleanup by removing the temporary directory. michael@0: if (aDir.exists()) michael@0: aDir.remove(true); michael@0: }); michael@0: }, michael@0: michael@0: _sendError: function wa_actorSendError(aDeferred, aMsg, aId) { michael@0: debug("Sending error: " + aMsg); michael@0: aDeferred.resolve({ michael@0: error: "installationFailed", michael@0: message: aMsg, michael@0: appId: aId michael@0: }); michael@0: }, michael@0: michael@0: _getAppType: function wa_actorGetAppType(aType) { michael@0: let type = Ci.nsIPrincipal.APP_STATUS_INSTALLED; michael@0: michael@0: if (aType) { michael@0: type = aType == "privileged" ? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED michael@0: : aType == "certified" ? Ci.nsIPrincipal.APP_STATUS_CERTIFIED michael@0: : Ci.nsIPrincipal.APP_STATUS_INSTALLED; michael@0: } michael@0: michael@0: return type; michael@0: }, michael@0: michael@0: uploadPackage: function () { michael@0: debug("uploadPackage\n"); michael@0: let tmpDir = FileUtils.getDir("TmpD", ["file-upload"], true, false); michael@0: if (!tmpDir.exists() || !tmpDir.isDirectory()) { michael@0: return {error: "fileAccessError", michael@0: message: "Unable to create temporary folder"}; michael@0: } michael@0: let tmpFile = tmpDir; michael@0: tmpFile.append("package.zip"); michael@0: tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8)); michael@0: if (!tmpFile.exists() || !tmpDir.isFile()) { michael@0: return {error: "fileAccessError", michael@0: message: "Unable to create temporary file"}; michael@0: } michael@0: michael@0: return OS.File.open(tmpFile.path, { write: true, truncate: true }) michael@0: .then((file) => { michael@0: let actor = new PackageUploadActor(tmpFile.path, file); michael@0: this._actorPool.addActor(actor); michael@0: this._uploads.push(actor); michael@0: return { actor: actor.actorID }; michael@0: }); michael@0: }, michael@0: michael@0: installHostedApp: function wa_actorInstallHosted(aDir, aId, aReceipts, michael@0: aManifest, aMetadata) { michael@0: debug("installHostedApp"); michael@0: let self = this; michael@0: let deferred = promise.defer(); michael@0: michael@0: function readManifest() { michael@0: if (aManifest) { michael@0: return promise.resolve(aManifest); michael@0: } else { michael@0: let manFile = OS.Path.join(aDir.path, "manifest.webapp"); michael@0: return AppsUtils.loadJSONAsync(manFile); michael@0: } michael@0: } michael@0: function checkSideloading(aManifest) { michael@0: return self._getAppType(aManifest.type); michael@0: } michael@0: function writeManifest(aAppType) { michael@0: // Move manifest.webapp to the destination directory. michael@0: // The destination directory for this app. michael@0: let installDir = DOMApplicationRegistry._getAppDir(aId); michael@0: if (aManifest) { michael@0: let manFile = OS.Path.join(installDir.path, "manifest.webapp"); michael@0: return DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest)).then(() => { michael@0: return aAppType; michael@0: }); michael@0: } else { michael@0: let manFile = aDir.clone(); michael@0: manFile.append("manifest.webapp"); michael@0: manFile.moveTo(installDir, "manifest.webapp"); michael@0: } michael@0: return null; michael@0: } michael@0: function readMetadata(aAppType) { michael@0: if (aMetadata) { michael@0: return { metadata: aMetadata, appType: aAppType }; michael@0: } michael@0: // Read the origin and manifest url from metadata.json michael@0: let metaFile = OS.Path.join(aDir.path, "metadata.json"); michael@0: return AppsUtils.loadJSONAsync(metaFile).then((aMetadata) => { michael@0: if (!aMetadata) { michael@0: throw("Error parsing metadata.json."); michael@0: } michael@0: if (!aMetadata.origin) { michael@0: throw("Missing 'origin' property in metadata.json"); michael@0: } michael@0: return { metadata: aMetadata, appType: aAppType }; michael@0: }); michael@0: } michael@0: let runnable = { michael@0: run: function run() { michael@0: try { michael@0: readManifest(). michael@0: then(writeManifest). michael@0: then(checkSideloading). michael@0: then(readMetadata). michael@0: then(function ({ metadata, appType }) { michael@0: let origin = metadata.origin; michael@0: let manifestURL = metadata.manifestURL || michael@0: origin + "/manifest.webapp"; michael@0: // Create a fake app object with the minimum set of properties we need. michael@0: let app = { michael@0: origin: origin, michael@0: installOrigin: metadata.installOrigin || origin, michael@0: manifestURL: manifestURL, michael@0: appStatus: appType, michael@0: receipts: aReceipts, michael@0: }; michael@0: michael@0: self._registerApp(deferred, app, aId, aDir); michael@0: }, function (error) { michael@0: self._sendError(deferred, error, aId); michael@0: }); michael@0: } catch(e) { michael@0: // If anything goes wrong, just send it back. michael@0: self._sendError(deferred, e.toString(), aId); michael@0: } michael@0: } michael@0: } michael@0: michael@0: Services.tm.currentThread.dispatch(runnable, michael@0: Ci.nsIThread.DISPATCH_NORMAL); michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: installPackagedApp: function wa_actorInstallPackaged(aDir, aId, aReceipts) { michael@0: debug("installPackagedApp"); michael@0: let self = this; michael@0: let deferred = promise.defer(); michael@0: michael@0: let runnable = { michael@0: run: function run() { michael@0: try { michael@0: // Open the app zip package michael@0: let zipFile = aDir.clone(); michael@0: zipFile.append("application.zip"); michael@0: let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"] michael@0: .createInstance(Ci.nsIZipReader); michael@0: zipReader.open(zipFile); michael@0: michael@0: // Read app manifest `manifest.webapp` from `application.zip` michael@0: let istream = zipReader.getInputStream("manifest.webapp"); michael@0: let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: converter.charset = "UTF-8"; michael@0: let jsonString = converter.ConvertToUnicode( michael@0: NetUtil.readInputStreamToString(istream, istream.available()) michael@0: ); michael@0: michael@0: let manifest; michael@0: try { michael@0: manifest = JSON.parse(jsonString); michael@0: } catch(e) { michael@0: self._sendError(deferred, "Error Parsing manifest.webapp: " + e, aId); michael@0: } michael@0: michael@0: let appType = self._getAppType(manifest.type); michael@0: michael@0: // Privileged and certified packaged apps can setup a custom origin michael@0: // via `origin` manifest property michael@0: let id = aId; michael@0: if (appType >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED && michael@0: manifest.origin !== undefined) { michael@0: let uri; michael@0: try { michael@0: uri = Services.io.newURI(manifest.origin, null, null); michael@0: } catch(e) { michael@0: self._sendError(deferred, "Invalid origin in webapp's manifest", aId); michael@0: } michael@0: michael@0: if (uri.scheme != "app") { michael@0: self._sendError(deferred, "Invalid origin in webapp's manifest", aId); michael@0: } michael@0: id = uri.prePath.substring(6); michael@0: } michael@0: michael@0: // Only after security checks are made and after final app id is computed michael@0: // we can move application.zip to the destination directory, and michael@0: // extract manifest.webapp there. michael@0: let installDir = DOMApplicationRegistry._getAppDir(id); michael@0: let manFile = installDir.clone(); michael@0: manFile.append("manifest.webapp"); michael@0: zipReader.extract("manifest.webapp", manFile); michael@0: zipReader.close(); michael@0: zipFile.moveTo(installDir, "application.zip"); michael@0: michael@0: let origin = "app://" + id; michael@0: let manifestURL = origin + "/manifest.webapp"; michael@0: michael@0: // Refresh application.zip content (e.g. reinstall app), as done here: michael@0: // http://hg.mozilla.org/mozilla-central/annotate/aaefec5d34f8/dom/apps/src/Webapps.jsm#l1125 michael@0: // Do it in parent process for the simulator michael@0: let jar = installDir.clone(); michael@0: jar.append("application.zip"); michael@0: Services.obs.notifyObservers(jar, "flush-cache-entry", null); michael@0: michael@0: // And then in app content process michael@0: // This function will be evaluated in the scope of the content process michael@0: // frame script. That will flush the jar cache for this app and allow michael@0: // loading fresh updated resources if we reload its document. michael@0: let FlushFrameScript = function (path) { michael@0: let jar = Components.classes["@mozilla.org/file/local;1"] michael@0: .createInstance(Components.interfaces.nsILocalFile); michael@0: jar.initWithPath(path); michael@0: let obs = Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService); michael@0: obs.notifyObservers(jar, "flush-cache-entry", null); michael@0: }; michael@0: for each (let frame in self._appFrames()) { michael@0: if (frame.getAttribute("mozapp") == manifestURL) { michael@0: let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; michael@0: mm.loadFrameScript("data:," + michael@0: encodeURIComponent("(" + FlushFrameScript.toString() + ")" + michael@0: "('" + jar.path + "')"), false); michael@0: } michael@0: } michael@0: michael@0: // Create a fake app object with the minimum set of properties we need. michael@0: let app = { michael@0: origin: origin, michael@0: installOrigin: origin, michael@0: manifestURL: manifestURL, michael@0: appStatus: appType, michael@0: receipts: aReceipts, michael@0: } michael@0: michael@0: self._registerApp(deferred, app, id, aDir); michael@0: } catch(e) { michael@0: // If anything goes wrong, just send it back. michael@0: self._sendError(deferred, e.toString(), aId); michael@0: } michael@0: } michael@0: } michael@0: michael@0: Services.tm.currentThread.dispatch(runnable, michael@0: Ci.nsIThread.DISPATCH_NORMAL); michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: /** michael@0: * @param appId : The id of the app we want to install. We will look for michael@0: * the files for the app in $TMP/b2g/$appId : michael@0: * For packaged apps: application.zip michael@0: * For hosted apps: metadata.json and manifest.webapp michael@0: */ michael@0: install: function wa_actorInstall(aRequest) { michael@0: debug("install"); michael@0: michael@0: let appId = aRequest.appId; michael@0: let reg = DOMApplicationRegistry; michael@0: if (!appId) { michael@0: appId = reg.makeAppId(); michael@0: } michael@0: michael@0: // Check that we are not overriding a preinstalled application. michael@0: if (appId in reg.webapps && reg.webapps[appId].removable === false) { michael@0: return { error: "badParameterType", michael@0: message: "The application " + appId + " can't be overriden." michael@0: } michael@0: } michael@0: michael@0: let appDir = FileUtils.getDir("TmpD", ["b2g", appId], false, false); michael@0: michael@0: if (aRequest.upload) { michael@0: // Ensure creating the directory (recursively) michael@0: appDir = FileUtils.getDir("TmpD", ["b2g", appId], true, false); michael@0: let actor = this.conn.getActor(aRequest.upload); michael@0: if (!actor) { michael@0: return { error: "badParameter", michael@0: message: "Unable to find upload actor '" + aRequest.upload michael@0: + "'" }; michael@0: } michael@0: let appFile = FileUtils.File(actor.getFilePath()); michael@0: if (!appFile.exists()) { michael@0: return { error: "badParameter", michael@0: message: "The uploaded file doesn't exist on device" }; michael@0: } michael@0: appFile.moveTo(appDir, "application.zip"); michael@0: } else if ((!appDir || !appDir.exists()) && michael@0: !aRequest.manifest && !aRequest.metadata) { michael@0: return { error: "badParameterType", michael@0: message: "missing directory " + appDir.path michael@0: }; michael@0: } michael@0: michael@0: let testFile = appDir.clone(); michael@0: testFile.append("application.zip"); michael@0: michael@0: let receipts = (aRequest.receipts && Array.isArray(aRequest.receipts)) michael@0: ? aRequest.receipts michael@0: : []; michael@0: michael@0: if (testFile.exists()) { michael@0: return this.installPackagedApp(appDir, appId, receipts); michael@0: } michael@0: michael@0: let manifest, metadata; michael@0: let missing = michael@0: ["manifest.webapp", "metadata.json"] michael@0: .some(function(aName) { michael@0: testFile = appDir.clone(); michael@0: testFile.append(aName); michael@0: return !testFile.exists(); michael@0: }); michael@0: if (missing) { michael@0: if (aRequest.manifest && aRequest.metadata && michael@0: aRequest.metadata.origin) { michael@0: manifest = aRequest.manifest; michael@0: metadata = aRequest.metadata; michael@0: } else { michael@0: try { michael@0: appDir.remove(true); michael@0: } catch(e) {} michael@0: return { error: "badParameterType", michael@0: message: "hosted app file and manifest/metadata fields " + michael@0: "are missing" michael@0: }; michael@0: } michael@0: } michael@0: michael@0: return this.installHostedApp(appDir, appId, receipts, manifest, metadata); michael@0: }, michael@0: michael@0: getAll: function wa_actorGetAll(aRequest) { michael@0: debug("getAll"); michael@0: michael@0: let deferred = promise.defer(); michael@0: let reg = DOMApplicationRegistry; michael@0: reg.getAll(apps => { michael@0: deferred.resolve({ apps: this._filterAllowedApps(apps) }); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: getApp: function wa_actorGetApp(aRequest) { michael@0: debug("getApp"); michael@0: michael@0: let manifestURL = aRequest.manifestURL; michael@0: if (!manifestURL) { michael@0: return { error: "missingParameter", michael@0: message: "missing parameter manifestURL" }; michael@0: } michael@0: michael@0: let reg = DOMApplicationRegistry; michael@0: let app = reg.getAppByManifestURL(manifestURL); michael@0: if (!app) { michael@0: return { error: "appNotFound" }; michael@0: } michael@0: michael@0: return this._isAppAllowedForURL(app.manifestURL).then(allowed => { michael@0: if (!allowed) { michael@0: return { error: "forbidden" }; michael@0: } michael@0: return reg.getManifestFor(manifestURL).then(function (manifest) { michael@0: app.manifest = manifest; michael@0: return { app: app }; michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: _areCertifiedAppsAllowed: function wa__areCertifiedAppsAllowed() { michael@0: let pref = "devtools.debugger.forbid-certified-apps"; michael@0: return !Services.prefs.getBoolPref(pref); michael@0: }, michael@0: michael@0: _isAppAllowedForManifest: function wa__isAppAllowedForManifest(aManifest) { michael@0: if (this._areCertifiedAppsAllowed()) { michael@0: return true; michael@0: } michael@0: let type = this._getAppType(aManifest.type); michael@0: return type !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED; michael@0: }, michael@0: michael@0: _filterAllowedApps: function wa__filterAllowedApps(aApps) { michael@0: return aApps.filter(app => this._isAppAllowedForManifest(app.manifest)); michael@0: }, michael@0: michael@0: _isAppAllowedForURL: function wa__isAppAllowedForURL(aManifestURL) { michael@0: return this._findManifestByURL(aManifestURL).then(manifest => { michael@0: return this._isAppAllowedForManifest(manifest); michael@0: }); michael@0: }, michael@0: michael@0: uninstall: function wa_actorUninstall(aRequest) { michael@0: debug("uninstall"); michael@0: michael@0: let manifestURL = aRequest.manifestURL; michael@0: if (!manifestURL) { michael@0: return { error: "missingParameter", michael@0: message: "missing parameter manifestURL" }; michael@0: } michael@0: michael@0: let deferred = promise.defer(); michael@0: let reg = DOMApplicationRegistry; michael@0: reg.uninstall( michael@0: manifestURL, michael@0: function onsuccess() { michael@0: deferred.resolve({}); michael@0: }, michael@0: function onfailure(reason) { michael@0: deferred.resolve({ error: reason }); michael@0: } michael@0: ); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: _findManifestByURL: function wa__findManifestByURL(aManifestURL) { michael@0: let deferred = promise.defer(); michael@0: michael@0: let reg = DOMApplicationRegistry; michael@0: let id = reg._appIdForManifestURL(aManifestURL); michael@0: michael@0: reg._readManifests([{ id: id }]).then((aResults) => { michael@0: deferred.resolve(aResults[0].manifest); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: getIconAsDataURL: function (aRequest) { michael@0: debug("getIconAsDataURL"); michael@0: michael@0: let manifestURL = aRequest.manifestURL; michael@0: if (!manifestURL) { michael@0: return { error: "missingParameter", michael@0: message: "missing parameter manifestURL" }; michael@0: } michael@0: michael@0: let reg = DOMApplicationRegistry; michael@0: let app = reg.getAppByManifestURL(manifestURL); michael@0: if (!app) { michael@0: return { error: "wrongParameter", michael@0: message: "No application for " + manifestURL }; michael@0: } michael@0: michael@0: let deferred = promise.defer(); michael@0: michael@0: this._findManifestByURL(manifestURL).then(jsonManifest => { michael@0: let manifest = new ManifestHelper(jsonManifest, app.origin); michael@0: let iconURL = manifest.iconURLForSize(aRequest.size || 128); michael@0: if (!iconURL) { michael@0: deferred.resolve({ michael@0: error: "noIcon", michael@0: message: "This app has no icon" michael@0: }); michael@0: return; michael@0: } michael@0: michael@0: // Download the URL as a blob michael@0: // bug 899177: there is a bug with xhr and app:// and jar:// uris michael@0: // that ends up forcing the content type to application/xml. michael@0: let req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'] michael@0: .createInstance(Ci.nsIXMLHttpRequest); michael@0: req.open("GET", iconURL, false); michael@0: req.responseType = "blob"; michael@0: michael@0: try { michael@0: req.send(null); michael@0: } catch(e) { michael@0: deferred.resolve({ michael@0: error: "noIcon", michael@0: message: "The icon file '" + iconURL + "' doesn't exist" michael@0: }); michael@0: return; michael@0: } michael@0: michael@0: // Convert the blog to a base64 encoded data URI michael@0: let reader = Cc["@mozilla.org/files/filereader;1"] michael@0: .createInstance(Ci.nsIDOMFileReader); michael@0: reader.onload = function () { michael@0: deferred.resolve({ michael@0: url: reader.result michael@0: }); michael@0: }; michael@0: reader.onerror = function () { michael@0: deferred.resolve({ michael@0: error: reader.error.name, michael@0: message: String(reader.error) michael@0: }); michael@0: }; michael@0: reader.readAsDataURL(req.response); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: launch: function wa_actorLaunch(aRequest) { michael@0: debug("launch"); michael@0: michael@0: let manifestURL = aRequest.manifestURL; michael@0: if (!manifestURL) { michael@0: return { error: "missingParameter", michael@0: message: "missing parameter manifestURL" }; michael@0: } michael@0: michael@0: let deferred = promise.defer(); michael@0: michael@0: DOMApplicationRegistry.launch( michael@0: aRequest.manifestURL, michael@0: aRequest.startPoint || "", michael@0: Date.now(), michael@0: function onsuccess() { michael@0: deferred.resolve({}); michael@0: }, michael@0: function onfailure(reason) { michael@0: deferred.resolve({ error: reason }); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: close: function wa_actorLaunch(aRequest) { michael@0: debug("close"); michael@0: michael@0: let manifestURL = aRequest.manifestURL; michael@0: if (!manifestURL) { michael@0: return { error: "missingParameter", michael@0: message: "missing parameter manifestURL" }; michael@0: } michael@0: michael@0: let reg = DOMApplicationRegistry; michael@0: let app = reg.getAppByManifestURL(manifestURL); michael@0: if (!app) { michael@0: return { error: "missingParameter", michael@0: message: "No application for " + manifestURL }; michael@0: } michael@0: michael@0: reg.close(app); michael@0: michael@0: return {}; michael@0: }, michael@0: michael@0: _appFrames: function () { michael@0: // For now, we only support app frames on b2g michael@0: if (Services.appinfo.ID != "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") { michael@0: return; michael@0: } michael@0: // Register the system app michael@0: let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser'); michael@0: let systemAppFrame = chromeWindow.shell.contentBrowser; michael@0: yield systemAppFrame; michael@0: michael@0: // Register apps hosted in the system app. i.e. the homescreen, all regular michael@0: // apps and the keyboard. michael@0: // Bookmark apps and other system app internal frames like captive portal michael@0: // are also hosted in system app, but they are not using mozapp attribute. michael@0: let frames = systemAppFrame.contentDocument.querySelectorAll("iframe[mozapp]"); michael@0: for (let i = 0; i < frames.length; i++) { michael@0: yield frames[i]; michael@0: } michael@0: }, michael@0: michael@0: listRunningApps: function (aRequest) { michael@0: debug("listRunningApps\n"); michael@0: michael@0: let appPromises = []; michael@0: let apps = []; michael@0: michael@0: for each (let frame in this._appFrames()) { michael@0: let manifestURL = frame.getAttribute("mozapp"); michael@0: michael@0: appPromises.push(this._isAppAllowedForURL(manifestURL).then(allowed => { michael@0: if (allowed) { michael@0: apps.push(manifestURL); michael@0: } michael@0: })); michael@0: } michael@0: michael@0: return promise.all(appPromises).then(() => { michael@0: return { apps: apps }; michael@0: }); michael@0: }, michael@0: michael@0: getAppActor: function ({ manifestURL }) { michael@0: debug("getAppActor\n"); michael@0: michael@0: let appFrame = null; michael@0: for each (let frame in this._appFrames()) { michael@0: if (frame.getAttribute("mozapp") == manifestURL) { michael@0: appFrame = frame; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: let notFoundError = { michael@0: error: "appNotFound", michael@0: message: "Unable to find any opened app whose manifest " + michael@0: "is '" + manifestURL + "'" michael@0: }; michael@0: michael@0: if (!appFrame) { michael@0: return notFoundError; michael@0: } michael@0: michael@0: return this._isAppAllowedForURL(manifestURL).then(allowed => { michael@0: if (!allowed) { michael@0: return notFoundError; michael@0: } michael@0: michael@0: // Only create a new actor, if we haven't already michael@0: // instanciated one for this connection. michael@0: let map = this._appActorsMap; michael@0: let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner) michael@0: .frameLoader michael@0: .messageManager; michael@0: let actor = map.get(mm); michael@0: if (!actor) { michael@0: let onConnect = actor => { michael@0: map.set(mm, actor); michael@0: return { actor: actor }; michael@0: }; michael@0: let onDisconnect = mm => { michael@0: map.delete(mm); michael@0: }; michael@0: return DebuggerServer.connectToChild(this.conn, appFrame, onDisconnect) michael@0: .then(onConnect); michael@0: } michael@0: michael@0: return { actor: actor }; michael@0: }); michael@0: }, michael@0: michael@0: watchApps: function () { michael@0: this._openedApps = new Set(); michael@0: // For now, app open/close events are only implement on b2g michael@0: if (Services.appinfo.ID == "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") { michael@0: let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser'); michael@0: let systemAppFrame = chromeWindow.getContentWindow(); michael@0: systemAppFrame.addEventListener("appwillopen", this); michael@0: systemAppFrame.addEventListener("appterminated", this); michael@0: } michael@0: Services.obs.addObserver(this, "webapps-installed", false); michael@0: Services.obs.addObserver(this, "webapps-uninstall", false); michael@0: michael@0: return {}; michael@0: }, michael@0: michael@0: unwatchApps: function () { michael@0: this._openedApps = null; michael@0: if (Services.appinfo.ID == "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") { michael@0: let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser'); michael@0: let systemAppFrame = chromeWindow.getContentWindow(); michael@0: systemAppFrame.removeEventListener("appwillopen", this); michael@0: systemAppFrame.removeEventListener("appterminated", this); michael@0: } michael@0: Services.obs.removeObserver(this, "webapps-installed", false); michael@0: Services.obs.removeObserver(this, "webapps-uninstall", false); michael@0: michael@0: return {}; michael@0: }, michael@0: michael@0: handleEvent: function (event) { michael@0: let manifestURL; michael@0: switch(event.type) { michael@0: case "appwillopen": michael@0: manifestURL = event.detail.manifestURL; michael@0: michael@0: // Ignore the event if we already received an appwillopen for this app michael@0: // (appwillopen is also fired when the app has been moved to background michael@0: // and get back to foreground) michael@0: if (this._openedApps.has(manifestURL)) { michael@0: return; michael@0: } michael@0: this._openedApps.add(manifestURL); michael@0: michael@0: this._isAppAllowedForURL(manifestURL).then(allowed => { michael@0: if (allowed) { michael@0: this.conn.send({ from: this.actorID, michael@0: type: "appOpen", michael@0: manifestURL: manifestURL michael@0: }); michael@0: } michael@0: }); michael@0: michael@0: break; michael@0: michael@0: case "appterminated": michael@0: manifestURL = event.detail.manifestURL; michael@0: this._openedApps.delete(manifestURL); michael@0: michael@0: this._isAppAllowedForURL(manifestURL).then(allowed => { michael@0: if (allowed) { michael@0: this.conn.send({ from: this.actorID, michael@0: type: "appClose", michael@0: manifestURL: manifestURL michael@0: }); michael@0: } michael@0: }); michael@0: michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: observe: function (subject, topic, data) { michael@0: let app = JSON.parse(data); michael@0: if (topic == "webapps-installed") { michael@0: this.conn.send({ from: this.actorID, michael@0: type: "appInstall", michael@0: manifestURL: app.manifestURL michael@0: }); michael@0: } else if (topic == "webapps-uninstall") { michael@0: this.conn.send({ from: this.actorID, michael@0: type: "appUninstall", michael@0: manifestURL: app.manifestURL michael@0: }); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The request types this actor can handle. michael@0: */ michael@0: WebappsActor.prototype.requestTypes = { michael@0: "install": WebappsActor.prototype.install, michael@0: "uploadPackage": WebappsActor.prototype.uploadPackage, michael@0: "getAll": WebappsActor.prototype.getAll, michael@0: "getApp": WebappsActor.prototype.getApp, michael@0: "launch": WebappsActor.prototype.launch, michael@0: "close": WebappsActor.prototype.close, michael@0: "uninstall": WebappsActor.prototype.uninstall, michael@0: "listRunningApps": WebappsActor.prototype.listRunningApps, michael@0: "getAppActor": WebappsActor.prototype.getAppActor, michael@0: "watchApps": WebappsActor.prototype.watchApps, michael@0: "unwatchApps": WebappsActor.prototype.unwatchApps, michael@0: "getIconAsDataURL": WebappsActor.prototype.getIconAsDataURL michael@0: }; michael@0: michael@0: DebuggerServer.addGlobalActor(WebappsActor, "webappsActor");