michael@0: const {Ci, Cc, Cu, Cr} = require("chrome"); michael@0: Cu.import("resource://gre/modules/osfile.jsm"); michael@0: const {Services} = Cu.import("resource://gre/modules/Services.jsm"); michael@0: const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); michael@0: const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); michael@0: michael@0: // XXX: bug 912476 make this module a real protocol.js front michael@0: // by converting webapps actor to protocol.js michael@0: michael@0: const PR_USEC_PER_MSEC = 1000; michael@0: const PR_RDWR = 0x04; michael@0: const PR_CREATE_FILE = 0x08; michael@0: const PR_TRUNCATE = 0x20; michael@0: michael@0: const CHUNK_SIZE = 10000; michael@0: michael@0: const appTargets = new Map(); michael@0: michael@0: function addDirToZip(writer, dir, basePath) { michael@0: let files = dir.directoryEntries; michael@0: michael@0: while (files.hasMoreElements()) { michael@0: let file = files.getNext().QueryInterface(Ci.nsIFile); michael@0: michael@0: if (file.isHidden() || michael@0: file.isSpecial() || michael@0: file.equals(writer.file)) michael@0: { michael@0: continue; michael@0: } michael@0: michael@0: if (file.isDirectory()) { michael@0: writer.addEntryDirectory(basePath + file.leafName + "/", michael@0: file.lastModifiedTime * PR_USEC_PER_MSEC, michael@0: true); michael@0: addDirToZip(writer, file, basePath + file.leafName + "/"); michael@0: } else { michael@0: writer.addEntryFile(basePath + file.leafName, michael@0: Ci.nsIZipWriter.COMPRESSION_DEFAULT, michael@0: file, michael@0: true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Convert an XPConnect result code to its name and message. michael@0: * We have to extract them from an exception per bug 637307 comment 5. michael@0: */ michael@0: function getResultTest(code) { michael@0: let regexp = michael@0: /^\[Exception... "(.*)" nsresult: "0x[0-9a-fA-F]* \((.*)\)" location: ".*" data: .*\]$/; michael@0: let ex = Cc["@mozilla.org/js/xpc/Exception;1"]. michael@0: createInstance(Ci.nsIXPCException); michael@0: ex.initialize(null, code, null, null, null, null); michael@0: let [, message, name] = regexp.exec(ex.toString()); michael@0: return { name: name, message: message }; michael@0: } michael@0: michael@0: function zipDirectory(zipFile, dirToArchive) { michael@0: let deferred = promise.defer(); michael@0: let writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter); michael@0: writer.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); michael@0: michael@0: this.addDirToZip(writer, dirToArchive, ""); michael@0: michael@0: writer.processQueue({ michael@0: onStartRequest: function onStartRequest(request, context) {}, michael@0: onStopRequest: (request, context, status) => { michael@0: if (status == Cr.NS_OK) { michael@0: writer.close(); michael@0: deferred.resolve(zipFile); michael@0: } michael@0: else { michael@0: let { name, message } = getResultText(status); michael@0: deferred.reject(name + ": " + message); michael@0: } michael@0: } michael@0: }, null); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function uploadPackage(client, webappsActor, packageFile) { michael@0: let deferred = promise.defer(); michael@0: michael@0: let request = { michael@0: to: webappsActor, michael@0: type: "uploadPackage" michael@0: }; michael@0: client.request(request, (res) => { michael@0: openFile(res.actor); michael@0: }); michael@0: michael@0: function openFile(actor) { michael@0: OS.File.open(packageFile.path) michael@0: .then(function (file) { michael@0: uploadChunk(actor, file); michael@0: }); michael@0: } michael@0: function uploadChunk(actor, file) { michael@0: file.read(CHUNK_SIZE) michael@0: .then(function (bytes) { michael@0: // To work around the fact that JSON.stringify translates the typed michael@0: // array to object, we are encoding the typed array here into a string michael@0: let chunk = String.fromCharCode.apply(null, bytes); michael@0: michael@0: let request = { michael@0: to: actor, michael@0: type: "chunk", michael@0: chunk: chunk michael@0: }; michael@0: client.request(request, (res) => { michael@0: if (bytes.length == CHUNK_SIZE) { michael@0: uploadChunk(actor, file); michael@0: } else { michael@0: file.close().then(function () { michael@0: endsUpload(actor); michael@0: }); michael@0: } michael@0: }); michael@0: }); michael@0: } michael@0: function endsUpload(actor) { michael@0: let request = { michael@0: to: actor, michael@0: type: "done" michael@0: }; michael@0: client.request(request, (res) => { michael@0: deferred.resolve(actor); michael@0: }); michael@0: } michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function removeServerTemporaryFile(client, fileActor) { michael@0: let request = { michael@0: to: fileActor, michael@0: type: "remove" michael@0: }; michael@0: client.request(request); michael@0: } michael@0: michael@0: function installPackaged(client, webappsActor, packagePath, appId) { michael@0: let deferred = promise.defer(); michael@0: let file = FileUtils.File(packagePath); michael@0: let packagePromise; michael@0: if (file.isDirectory()) { michael@0: let tmpZipFile = FileUtils.getDir("TmpD", [], true); michael@0: tmpZipFile.append("application.zip"); michael@0: tmpZipFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8)); michael@0: packagePromise = zipDirectory(tmpZipFile, file) michael@0: } else { michael@0: packagePromise = promise.resolve(file); michael@0: } michael@0: packagePromise.then((zipFile) => { michael@0: uploadPackage(client, webappsActor, zipFile) michael@0: .then((fileActor) => { michael@0: let request = { michael@0: to: webappsActor, michael@0: type: "install", michael@0: appId: appId, michael@0: upload: fileActor michael@0: }; michael@0: client.request(request, (res) => { michael@0: // If the install method immediatly fails, michael@0: // reject immediatly the installPackaged promise. michael@0: // Otherwise, wait for webappsEvent for completion michael@0: if (res.error) { michael@0: deferred.reject(res); michael@0: } michael@0: if ("error" in res) michael@0: deferred.reject({error: res.error, message: res.message}); michael@0: else michael@0: deferred.resolve({appId: res.appId}); michael@0: }); michael@0: // Ensure deleting the temporary package file, but only if that a temporary michael@0: // package created when we pass a directory as `packagePath` michael@0: if (zipFile != file) michael@0: zipFile.remove(false); michael@0: // In case of success or error, ensure deleting the temporary package file michael@0: // also created on the device, but only once install request is done michael@0: deferred.promise.then( michael@0: () => removeServerTemporaryFile(client, fileActor), michael@0: () => removeServerTemporaryFile(client, fileActor)); michael@0: }); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: exports.installPackaged = installPackaged; michael@0: michael@0: function installHosted(client, webappsActor, appId, metadata, manifest) { michael@0: let deferred = promise.defer(); michael@0: let request = { michael@0: to: webappsActor, michael@0: type: "install", michael@0: appId: appId, michael@0: metadata: metadata, michael@0: manifest: manifest michael@0: }; michael@0: client.request(request, (res) => { michael@0: if (res.error) { michael@0: deferred.reject(res); michael@0: } michael@0: if ("error" in res) michael@0: deferred.reject({error: res.error, message: res.message}); michael@0: else michael@0: deferred.resolve({appId: res.appId}); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: exports.installHosted = installHosted; michael@0: michael@0: function getTargetForApp(client, webappsActor, manifestURL) { michael@0: // Ensure always returning the exact same JS object for a target michael@0: // of the same app in order to show only one toolbox per app and michael@0: // avoid re-creating lot of objects twice. michael@0: let existingTarget = appTargets.get(manifestURL); michael@0: if (existingTarget) michael@0: return promise.resolve(existingTarget); michael@0: michael@0: let deferred = promise.defer(); michael@0: let request = { michael@0: to: webappsActor, michael@0: type: "getAppActor", michael@0: manifestURL: manifestURL, michael@0: } michael@0: client.request(request, (res) => { michael@0: if (res.error) { michael@0: deferred.reject(res.error); michael@0: } else { michael@0: let options = { michael@0: form: res.actor, michael@0: client: client, michael@0: chrome: false michael@0: }; michael@0: michael@0: devtools.TargetFactory.forRemoteTab(options).then((target) => { michael@0: target.isApp = true; michael@0: appTargets.set(manifestURL, target); michael@0: target.on("close", () => { michael@0: appTargets.delete(manifestURL); michael@0: }); michael@0: deferred.resolve(target) michael@0: }, (error) => { michael@0: deferred.reject(error); michael@0: }); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: exports.getTargetForApp = getTargetForApp; michael@0: michael@0: function reloadApp(client, webappsActor, manifestURL) { michael@0: let deferred = promise.defer(); michael@0: getTargetForApp(client, michael@0: webappsActor, michael@0: manifestURL). michael@0: then((target) => { michael@0: // Request the ContentActor to reload the app michael@0: let request = { michael@0: to: target.form.actor, michael@0: type: "reload", michael@0: manifestURL: manifestURL michael@0: }; michael@0: client.request(request, (res) => { michael@0: deferred.resolve(); michael@0: }); michael@0: }, () => { michael@0: deferred.reject("Not running"); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: exports.reloadApp = reloadApp; michael@0: michael@0: function launchApp(client, webappsActor, manifestURL) { michael@0: let deferred = promise.defer(); michael@0: let request = { michael@0: to: webappsActor, michael@0: type: "launch", michael@0: manifestURL: manifestURL michael@0: }; michael@0: client.request(request, (res) => { michael@0: if (res.error) { michael@0: deferred.reject(res.error); michael@0: } else { michael@0: deferred.resolve(res); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: exports.launchApp = launchApp; michael@0: michael@0: function closeApp(client, webappsActor, manifestURL) { michael@0: let deferred = promise.defer(); michael@0: let request = { michael@0: to: webappsActor, michael@0: type: "close", michael@0: manifestURL: manifestURL michael@0: }; michael@0: client.request(request, (res) => { michael@0: if (res.error) { michael@0: deferred.reject(res.error); michael@0: } else { michael@0: deferred.resolve(res); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: exports.closeApp = closeApp;