1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/apps/app-actor-front.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,311 @@ 1.4 +const {Ci, Cc, Cu, Cr} = require("chrome"); 1.5 +Cu.import("resource://gre/modules/osfile.jsm"); 1.6 +const {Services} = Cu.import("resource://gre/modules/Services.jsm"); 1.7 +const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm"); 1.8 +const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); 1.9 +const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.10 + 1.11 +// XXX: bug 912476 make this module a real protocol.js front 1.12 +// by converting webapps actor to protocol.js 1.13 + 1.14 +const PR_USEC_PER_MSEC = 1000; 1.15 +const PR_RDWR = 0x04; 1.16 +const PR_CREATE_FILE = 0x08; 1.17 +const PR_TRUNCATE = 0x20; 1.18 + 1.19 +const CHUNK_SIZE = 10000; 1.20 + 1.21 +const appTargets = new Map(); 1.22 + 1.23 +function addDirToZip(writer, dir, basePath) { 1.24 + let files = dir.directoryEntries; 1.25 + 1.26 + while (files.hasMoreElements()) { 1.27 + let file = files.getNext().QueryInterface(Ci.nsIFile); 1.28 + 1.29 + if (file.isHidden() || 1.30 + file.isSpecial() || 1.31 + file.equals(writer.file)) 1.32 + { 1.33 + continue; 1.34 + } 1.35 + 1.36 + if (file.isDirectory()) { 1.37 + writer.addEntryDirectory(basePath + file.leafName + "/", 1.38 + file.lastModifiedTime * PR_USEC_PER_MSEC, 1.39 + true); 1.40 + addDirToZip(writer, file, basePath + file.leafName + "/"); 1.41 + } else { 1.42 + writer.addEntryFile(basePath + file.leafName, 1.43 + Ci.nsIZipWriter.COMPRESSION_DEFAULT, 1.44 + file, 1.45 + true); 1.46 + } 1.47 + } 1.48 +} 1.49 + 1.50 +/** 1.51 + * Convert an XPConnect result code to its name and message. 1.52 + * We have to extract them from an exception per bug 637307 comment 5. 1.53 + */ 1.54 +function getResultTest(code) { 1.55 + let regexp = 1.56 + /^\[Exception... "(.*)" nsresult: "0x[0-9a-fA-F]* \((.*)\)" location: ".*" data: .*\]$/; 1.57 + let ex = Cc["@mozilla.org/js/xpc/Exception;1"]. 1.58 + createInstance(Ci.nsIXPCException); 1.59 + ex.initialize(null, code, null, null, null, null); 1.60 + let [, message, name] = regexp.exec(ex.toString()); 1.61 + return { name: name, message: message }; 1.62 +} 1.63 + 1.64 +function zipDirectory(zipFile, dirToArchive) { 1.65 + let deferred = promise.defer(); 1.66 + let writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter); 1.67 + writer.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); 1.68 + 1.69 + this.addDirToZip(writer, dirToArchive, ""); 1.70 + 1.71 + writer.processQueue({ 1.72 + onStartRequest: function onStartRequest(request, context) {}, 1.73 + onStopRequest: (request, context, status) => { 1.74 + if (status == Cr.NS_OK) { 1.75 + writer.close(); 1.76 + deferred.resolve(zipFile); 1.77 + } 1.78 + else { 1.79 + let { name, message } = getResultText(status); 1.80 + deferred.reject(name + ": " + message); 1.81 + } 1.82 + } 1.83 + }, null); 1.84 + 1.85 + return deferred.promise; 1.86 +} 1.87 + 1.88 +function uploadPackage(client, webappsActor, packageFile) { 1.89 + let deferred = promise.defer(); 1.90 + 1.91 + let request = { 1.92 + to: webappsActor, 1.93 + type: "uploadPackage" 1.94 + }; 1.95 + client.request(request, (res) => { 1.96 + openFile(res.actor); 1.97 + }); 1.98 + 1.99 + function openFile(actor) { 1.100 + OS.File.open(packageFile.path) 1.101 + .then(function (file) { 1.102 + uploadChunk(actor, file); 1.103 + }); 1.104 + } 1.105 + function uploadChunk(actor, file) { 1.106 + file.read(CHUNK_SIZE) 1.107 + .then(function (bytes) { 1.108 + // To work around the fact that JSON.stringify translates the typed 1.109 + // array to object, we are encoding the typed array here into a string 1.110 + let chunk = String.fromCharCode.apply(null, bytes); 1.111 + 1.112 + let request = { 1.113 + to: actor, 1.114 + type: "chunk", 1.115 + chunk: chunk 1.116 + }; 1.117 + client.request(request, (res) => { 1.118 + if (bytes.length == CHUNK_SIZE) { 1.119 + uploadChunk(actor, file); 1.120 + } else { 1.121 + file.close().then(function () { 1.122 + endsUpload(actor); 1.123 + }); 1.124 + } 1.125 + }); 1.126 + }); 1.127 + } 1.128 + function endsUpload(actor) { 1.129 + let request = { 1.130 + to: actor, 1.131 + type: "done" 1.132 + }; 1.133 + client.request(request, (res) => { 1.134 + deferred.resolve(actor); 1.135 + }); 1.136 + } 1.137 + return deferred.promise; 1.138 +} 1.139 + 1.140 +function removeServerTemporaryFile(client, fileActor) { 1.141 + let request = { 1.142 + to: fileActor, 1.143 + type: "remove" 1.144 + }; 1.145 + client.request(request); 1.146 +} 1.147 + 1.148 +function installPackaged(client, webappsActor, packagePath, appId) { 1.149 + let deferred = promise.defer(); 1.150 + let file = FileUtils.File(packagePath); 1.151 + let packagePromise; 1.152 + if (file.isDirectory()) { 1.153 + let tmpZipFile = FileUtils.getDir("TmpD", [], true); 1.154 + tmpZipFile.append("application.zip"); 1.155 + tmpZipFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8)); 1.156 + packagePromise = zipDirectory(tmpZipFile, file) 1.157 + } else { 1.158 + packagePromise = promise.resolve(file); 1.159 + } 1.160 + packagePromise.then((zipFile) => { 1.161 + uploadPackage(client, webappsActor, zipFile) 1.162 + .then((fileActor) => { 1.163 + let request = { 1.164 + to: webappsActor, 1.165 + type: "install", 1.166 + appId: appId, 1.167 + upload: fileActor 1.168 + }; 1.169 + client.request(request, (res) => { 1.170 + // If the install method immediatly fails, 1.171 + // reject immediatly the installPackaged promise. 1.172 + // Otherwise, wait for webappsEvent for completion 1.173 + if (res.error) { 1.174 + deferred.reject(res); 1.175 + } 1.176 + if ("error" in res) 1.177 + deferred.reject({error: res.error, message: res.message}); 1.178 + else 1.179 + deferred.resolve({appId: res.appId}); 1.180 + }); 1.181 + // Ensure deleting the temporary package file, but only if that a temporary 1.182 + // package created when we pass a directory as `packagePath` 1.183 + if (zipFile != file) 1.184 + zipFile.remove(false); 1.185 + // In case of success or error, ensure deleting the temporary package file 1.186 + // also created on the device, but only once install request is done 1.187 + deferred.promise.then( 1.188 + () => removeServerTemporaryFile(client, fileActor), 1.189 + () => removeServerTemporaryFile(client, fileActor)); 1.190 + }); 1.191 + }); 1.192 + return deferred.promise; 1.193 +} 1.194 +exports.installPackaged = installPackaged; 1.195 + 1.196 +function installHosted(client, webappsActor, appId, metadata, manifest) { 1.197 + let deferred = promise.defer(); 1.198 + let request = { 1.199 + to: webappsActor, 1.200 + type: "install", 1.201 + appId: appId, 1.202 + metadata: metadata, 1.203 + manifest: manifest 1.204 + }; 1.205 + client.request(request, (res) => { 1.206 + if (res.error) { 1.207 + deferred.reject(res); 1.208 + } 1.209 + if ("error" in res) 1.210 + deferred.reject({error: res.error, message: res.message}); 1.211 + else 1.212 + deferred.resolve({appId: res.appId}); 1.213 + }); 1.214 + return deferred.promise; 1.215 +} 1.216 +exports.installHosted = installHosted; 1.217 + 1.218 +function getTargetForApp(client, webappsActor, manifestURL) { 1.219 + // Ensure always returning the exact same JS object for a target 1.220 + // of the same app in order to show only one toolbox per app and 1.221 + // avoid re-creating lot of objects twice. 1.222 + let existingTarget = appTargets.get(manifestURL); 1.223 + if (existingTarget) 1.224 + return promise.resolve(existingTarget); 1.225 + 1.226 + let deferred = promise.defer(); 1.227 + let request = { 1.228 + to: webappsActor, 1.229 + type: "getAppActor", 1.230 + manifestURL: manifestURL, 1.231 + } 1.232 + client.request(request, (res) => { 1.233 + if (res.error) { 1.234 + deferred.reject(res.error); 1.235 + } else { 1.236 + let options = { 1.237 + form: res.actor, 1.238 + client: client, 1.239 + chrome: false 1.240 + }; 1.241 + 1.242 + devtools.TargetFactory.forRemoteTab(options).then((target) => { 1.243 + target.isApp = true; 1.244 + appTargets.set(manifestURL, target); 1.245 + target.on("close", () => { 1.246 + appTargets.delete(manifestURL); 1.247 + }); 1.248 + deferred.resolve(target) 1.249 + }, (error) => { 1.250 + deferred.reject(error); 1.251 + }); 1.252 + } 1.253 + }); 1.254 + return deferred.promise; 1.255 +} 1.256 +exports.getTargetForApp = getTargetForApp; 1.257 + 1.258 +function reloadApp(client, webappsActor, manifestURL) { 1.259 + let deferred = promise.defer(); 1.260 + getTargetForApp(client, 1.261 + webappsActor, 1.262 + manifestURL). 1.263 + then((target) => { 1.264 + // Request the ContentActor to reload the app 1.265 + let request = { 1.266 + to: target.form.actor, 1.267 + type: "reload", 1.268 + manifestURL: manifestURL 1.269 + }; 1.270 + client.request(request, (res) => { 1.271 + deferred.resolve(); 1.272 + }); 1.273 + }, () => { 1.274 + deferred.reject("Not running"); 1.275 + }); 1.276 + return deferred.promise; 1.277 +} 1.278 +exports.reloadApp = reloadApp; 1.279 + 1.280 +function launchApp(client, webappsActor, manifestURL) { 1.281 + let deferred = promise.defer(); 1.282 + let request = { 1.283 + to: webappsActor, 1.284 + type: "launch", 1.285 + manifestURL: manifestURL 1.286 + }; 1.287 + client.request(request, (res) => { 1.288 + if (res.error) { 1.289 + deferred.reject(res.error); 1.290 + } else { 1.291 + deferred.resolve(res); 1.292 + } 1.293 + }); 1.294 + return deferred.promise; 1.295 +} 1.296 +exports.launchApp = launchApp; 1.297 + 1.298 +function closeApp(client, webappsActor, manifestURL) { 1.299 + let deferred = promise.defer(); 1.300 + let request = { 1.301 + to: webappsActor, 1.302 + type: "close", 1.303 + manifestURL: manifestURL 1.304 + }; 1.305 + client.request(request, (res) => { 1.306 + if (res.error) { 1.307 + deferred.reject(res.error); 1.308 + } else { 1.309 + deferred.resolve(res); 1.310 + } 1.311 + }); 1.312 + return deferred.promise; 1.313 +} 1.314 +exports.closeApp = closeApp;