toolkit/devtools/server/actors/webapps.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 let Cu = Components.utils;
michael@0 8 let Cc = Components.classes;
michael@0 9 let Ci = Components.interfaces;
michael@0 10 let CC = Components.Constructor;
michael@0 11
michael@0 12 Cu.import("resource://gre/modules/osfile.jsm");
michael@0 13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 14
michael@0 15 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
michael@0 16
michael@0 17 let promise;
michael@0 18
michael@0 19 function debug(aMsg) {
michael@0 20 /*
michael@0 21 Cc["@mozilla.org/consoleservice;1"]
michael@0 22 .getService(Ci.nsIConsoleService)
michael@0 23 .logStringMessage("--*-- WebappsActor : " + aMsg);
michael@0 24 */
michael@0 25 }
michael@0 26
michael@0 27 function PackageUploadActor(aPath, aFile) {
michael@0 28 this._path = aPath;
michael@0 29 this._file = aFile;
michael@0 30 this.size = 0;
michael@0 31 }
michael@0 32
michael@0 33 PackageUploadActor.prototype = {
michael@0 34 actorPrefix: "packageUploadActor",
michael@0 35
michael@0 36 /**
michael@0 37 * This method isn't exposed to the client.
michael@0 38 * It is meant to be called by server code, in order to get
michael@0 39 * access to the temporary file out of the actor ID.
michael@0 40 */
michael@0 41 getFilePath: function () {
michael@0 42 return this._path;
michael@0 43 },
michael@0 44
michael@0 45 /**
michael@0 46 * This method allows you to upload a piece of file.
michael@0 47 * It expects a chunk argument that is the a string to write to the file.
michael@0 48 */
michael@0 49 chunk: function (aRequest) {
michael@0 50 let chunk = aRequest.chunk;
michael@0 51 if (!chunk || chunk.length <= 0) {
michael@0 52 return {error: "parameterError",
michael@0 53 message: "Missing or invalid chunk argument"};
michael@0 54 }
michael@0 55 // Translate the string used to transfer the chunk over JSON
michael@0 56 // back to a typed array
michael@0 57 let data = new Uint8Array(chunk.length);
michael@0 58 for (let i = 0, l = chunk.length; i < l ; i++) {
michael@0 59 data[i] = chunk.charCodeAt(i);
michael@0 60 }
michael@0 61 return this._file.write(data)
michael@0 62 .then((written) => {
michael@0 63 this.size += written;
michael@0 64 return {
michael@0 65 written: written,
michael@0 66 size: this.size
michael@0 67 };
michael@0 68 });
michael@0 69 },
michael@0 70
michael@0 71 /**
michael@0 72 * This method needs to be called, when you are done uploading
michael@0 73 * chunks, before trying to access/use the temporary file.
michael@0 74 * Otherwise, the file may be partially written
michael@0 75 * and also be locked.
michael@0 76 */
michael@0 77 done: function (aRequest) {
michael@0 78 this._file.close();
michael@0 79 return {};
michael@0 80 },
michael@0 81
michael@0 82 /**
michael@0 83 * This method allows you to delete the temporary file,
michael@0 84 * when you are done using it.
michael@0 85 */
michael@0 86 remove: function (aRequest) {
michael@0 87 this._cleanupFile();
michael@0 88 return {};
michael@0 89 },
michael@0 90
michael@0 91 _cleanupFile: function () {
michael@0 92 try {
michael@0 93 this._file.close();
michael@0 94 } catch(e) {}
michael@0 95 try {
michael@0 96 OS.File.remove(this._path);
michael@0 97 } catch(e) {}
michael@0 98 }
michael@0 99 };
michael@0 100
michael@0 101 /**
michael@0 102 * The request types this actor can handle.
michael@0 103 */
michael@0 104 PackageUploadActor.prototype.requestTypes = {
michael@0 105 "chunk": PackageUploadActor.prototype.chunk,
michael@0 106 "done": PackageUploadActor.prototype.done,
michael@0 107 "remove": PackageUploadActor.prototype.remove
michael@0 108 };
michael@0 109
michael@0 110 /**
michael@0 111 * Creates a WebappsActor. WebappsActor provides remote access to
michael@0 112 * install apps.
michael@0 113 */
michael@0 114 function WebappsActor(aConnection) {
michael@0 115 debug("init");
michael@0 116 // Load actor dependencies lazily as this actor require extra environnement
michael@0 117 // preparation to work (like have a profile setup in xpcshell tests)
michael@0 118
michael@0 119 Cu.import("resource://gre/modules/Webapps.jsm");
michael@0 120 Cu.import("resource://gre/modules/AppsUtils.jsm");
michael@0 121 Cu.import("resource://gre/modules/FileUtils.jsm");
michael@0 122
michael@0 123 // Keep reference of already created app actors.
michael@0 124 // key: app frame message manager, value: ContentActor's grip() value
michael@0 125 this._appActorsMap = new Map();
michael@0 126
michael@0 127 this.conn = aConnection;
michael@0 128 this._uploads = [];
michael@0 129 this._actorPool = new ActorPool(this.conn);
michael@0 130 this.conn.addActorPool(this._actorPool);
michael@0 131 }
michael@0 132
michael@0 133 WebappsActor.prototype = {
michael@0 134 actorPrefix: "webapps",
michael@0 135
michael@0 136 disconnect: function () {
michael@0 137 // When we stop using this actor, we should ensure removing all files.
michael@0 138 for (let upload of this._uploads) {
michael@0 139 upload.remove();
michael@0 140 }
michael@0 141 this._uploads = null;
michael@0 142
michael@0 143 this.conn.removeActorPool(this._actorPool);
michael@0 144 this._actorPool = null;
michael@0 145 this.conn = null;
michael@0 146 },
michael@0 147
michael@0 148 _registerApp: function wa_actorRegisterApp(aDeferred, aApp, aId, aDir) {
michael@0 149 debug("registerApp");
michael@0 150 let reg = DOMApplicationRegistry;
michael@0 151 let self = this;
michael@0 152
michael@0 153 // Clean up the deprecated manifest cache if needed.
michael@0 154 if (aId in reg._manifestCache) {
michael@0 155 delete reg._manifestCache[aId];
michael@0 156 }
michael@0 157
michael@0 158 aApp.installTime = Date.now();
michael@0 159 aApp.installState = "installed";
michael@0 160 aApp.removable = true;
michael@0 161 aApp.id = aId;
michael@0 162 aApp.basePath = reg.getWebAppsBasePath();
michael@0 163 aApp.localId = (aId in reg.webapps) ? reg.webapps[aId].localId
michael@0 164 : reg._nextLocalId();
michael@0 165
michael@0 166 reg.webapps[aId] = aApp;
michael@0 167 reg.updatePermissionsForApp(aId);
michael@0 168
michael@0 169 reg._readManifests([{ id: aId }]).then((aResult) => {
michael@0 170 let manifest = aResult[0].manifest;
michael@0 171 aApp.name = manifest.name;
michael@0 172 reg.updateAppHandlers(null, manifest, aApp);
michael@0 173
michael@0 174 reg._saveApps().then(() => {
michael@0 175 aApp.manifest = manifest;
michael@0 176
michael@0 177 // Needed to evict manifest cache on content side
michael@0 178 // (has to be dispatched first, otherwise other messages like
michael@0 179 // Install:Return:OK are going to use old manifest version)
michael@0 180 reg.broadcastMessage("Webapps:UpdateState", {
michael@0 181 app: aApp,
michael@0 182 manifest: manifest,
michael@0 183 manifestURL: aApp.manifestURL
michael@0 184 });
michael@0 185 reg.broadcastMessage("Webapps:FireEvent", {
michael@0 186 eventType: ["downloadsuccess", "downloadapplied"],
michael@0 187 manifestURL: aApp.manifestURL
michael@0 188 });
michael@0 189 reg.broadcastMessage("Webapps:AddApp", { id: aId, app: aApp });
michael@0 190 reg.broadcastMessage("Webapps:Install:Return:OK", {
michael@0 191 app: aApp,
michael@0 192 oid: "foo",
michael@0 193 requestID: "bar"
michael@0 194 });
michael@0 195
michael@0 196 Services.obs.notifyObservers(null, "webapps-installed",
michael@0 197 JSON.stringify({ manifestURL: aApp.manifestURL }));
michael@0 198
michael@0 199 delete aApp.manifest;
michael@0 200 aDeferred.resolve({ appId: aId, path: aDir.path });
michael@0 201
michael@0 202 // We can't have appcache for packaged apps.
michael@0 203 if (!aApp.origin.startsWith("app://")) {
michael@0 204 reg.startOfflineCacheDownload(new ManifestHelper(manifest, aApp.origin));
michael@0 205 }
michael@0 206 });
michael@0 207 // Cleanup by removing the temporary directory.
michael@0 208 if (aDir.exists())
michael@0 209 aDir.remove(true);
michael@0 210 });
michael@0 211 },
michael@0 212
michael@0 213 _sendError: function wa_actorSendError(aDeferred, aMsg, aId) {
michael@0 214 debug("Sending error: " + aMsg);
michael@0 215 aDeferred.resolve({
michael@0 216 error: "installationFailed",
michael@0 217 message: aMsg,
michael@0 218 appId: aId
michael@0 219 });
michael@0 220 },
michael@0 221
michael@0 222 _getAppType: function wa_actorGetAppType(aType) {
michael@0 223 let type = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
michael@0 224
michael@0 225 if (aType) {
michael@0 226 type = aType == "privileged" ? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED
michael@0 227 : aType == "certified" ? Ci.nsIPrincipal.APP_STATUS_CERTIFIED
michael@0 228 : Ci.nsIPrincipal.APP_STATUS_INSTALLED;
michael@0 229 }
michael@0 230
michael@0 231 return type;
michael@0 232 },
michael@0 233
michael@0 234 uploadPackage: function () {
michael@0 235 debug("uploadPackage\n");
michael@0 236 let tmpDir = FileUtils.getDir("TmpD", ["file-upload"], true, false);
michael@0 237 if (!tmpDir.exists() || !tmpDir.isDirectory()) {
michael@0 238 return {error: "fileAccessError",
michael@0 239 message: "Unable to create temporary folder"};
michael@0 240 }
michael@0 241 let tmpFile = tmpDir;
michael@0 242 tmpFile.append("package.zip");
michael@0 243 tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
michael@0 244 if (!tmpFile.exists() || !tmpDir.isFile()) {
michael@0 245 return {error: "fileAccessError",
michael@0 246 message: "Unable to create temporary file"};
michael@0 247 }
michael@0 248
michael@0 249 return OS.File.open(tmpFile.path, { write: true, truncate: true })
michael@0 250 .then((file) => {
michael@0 251 let actor = new PackageUploadActor(tmpFile.path, file);
michael@0 252 this._actorPool.addActor(actor);
michael@0 253 this._uploads.push(actor);
michael@0 254 return { actor: actor.actorID };
michael@0 255 });
michael@0 256 },
michael@0 257
michael@0 258 installHostedApp: function wa_actorInstallHosted(aDir, aId, aReceipts,
michael@0 259 aManifest, aMetadata) {
michael@0 260 debug("installHostedApp");
michael@0 261 let self = this;
michael@0 262 let deferred = promise.defer();
michael@0 263
michael@0 264 function readManifest() {
michael@0 265 if (aManifest) {
michael@0 266 return promise.resolve(aManifest);
michael@0 267 } else {
michael@0 268 let manFile = OS.Path.join(aDir.path, "manifest.webapp");
michael@0 269 return AppsUtils.loadJSONAsync(manFile);
michael@0 270 }
michael@0 271 }
michael@0 272 function checkSideloading(aManifest) {
michael@0 273 return self._getAppType(aManifest.type);
michael@0 274 }
michael@0 275 function writeManifest(aAppType) {
michael@0 276 // Move manifest.webapp to the destination directory.
michael@0 277 // The destination directory for this app.
michael@0 278 let installDir = DOMApplicationRegistry._getAppDir(aId);
michael@0 279 if (aManifest) {
michael@0 280 let manFile = OS.Path.join(installDir.path, "manifest.webapp");
michael@0 281 return DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest)).then(() => {
michael@0 282 return aAppType;
michael@0 283 });
michael@0 284 } else {
michael@0 285 let manFile = aDir.clone();
michael@0 286 manFile.append("manifest.webapp");
michael@0 287 manFile.moveTo(installDir, "manifest.webapp");
michael@0 288 }
michael@0 289 return null;
michael@0 290 }
michael@0 291 function readMetadata(aAppType) {
michael@0 292 if (aMetadata) {
michael@0 293 return { metadata: aMetadata, appType: aAppType };
michael@0 294 }
michael@0 295 // Read the origin and manifest url from metadata.json
michael@0 296 let metaFile = OS.Path.join(aDir.path, "metadata.json");
michael@0 297 return AppsUtils.loadJSONAsync(metaFile).then((aMetadata) => {
michael@0 298 if (!aMetadata) {
michael@0 299 throw("Error parsing metadata.json.");
michael@0 300 }
michael@0 301 if (!aMetadata.origin) {
michael@0 302 throw("Missing 'origin' property in metadata.json");
michael@0 303 }
michael@0 304 return { metadata: aMetadata, appType: aAppType };
michael@0 305 });
michael@0 306 }
michael@0 307 let runnable = {
michael@0 308 run: function run() {
michael@0 309 try {
michael@0 310 readManifest().
michael@0 311 then(writeManifest).
michael@0 312 then(checkSideloading).
michael@0 313 then(readMetadata).
michael@0 314 then(function ({ metadata, appType }) {
michael@0 315 let origin = metadata.origin;
michael@0 316 let manifestURL = metadata.manifestURL ||
michael@0 317 origin + "/manifest.webapp";
michael@0 318 // Create a fake app object with the minimum set of properties we need.
michael@0 319 let app = {
michael@0 320 origin: origin,
michael@0 321 installOrigin: metadata.installOrigin || origin,
michael@0 322 manifestURL: manifestURL,
michael@0 323 appStatus: appType,
michael@0 324 receipts: aReceipts,
michael@0 325 };
michael@0 326
michael@0 327 self._registerApp(deferred, app, aId, aDir);
michael@0 328 }, function (error) {
michael@0 329 self._sendError(deferred, error, aId);
michael@0 330 });
michael@0 331 } catch(e) {
michael@0 332 // If anything goes wrong, just send it back.
michael@0 333 self._sendError(deferred, e.toString(), aId);
michael@0 334 }
michael@0 335 }
michael@0 336 }
michael@0 337
michael@0 338 Services.tm.currentThread.dispatch(runnable,
michael@0 339 Ci.nsIThread.DISPATCH_NORMAL);
michael@0 340 return deferred.promise;
michael@0 341 },
michael@0 342
michael@0 343 installPackagedApp: function wa_actorInstallPackaged(aDir, aId, aReceipts) {
michael@0 344 debug("installPackagedApp");
michael@0 345 let self = this;
michael@0 346 let deferred = promise.defer();
michael@0 347
michael@0 348 let runnable = {
michael@0 349 run: function run() {
michael@0 350 try {
michael@0 351 // Open the app zip package
michael@0 352 let zipFile = aDir.clone();
michael@0 353 zipFile.append("application.zip");
michael@0 354 let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
michael@0 355 .createInstance(Ci.nsIZipReader);
michael@0 356 zipReader.open(zipFile);
michael@0 357
michael@0 358 // Read app manifest `manifest.webapp` from `application.zip`
michael@0 359 let istream = zipReader.getInputStream("manifest.webapp");
michael@0 360 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 361 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 362 converter.charset = "UTF-8";
michael@0 363 let jsonString = converter.ConvertToUnicode(
michael@0 364 NetUtil.readInputStreamToString(istream, istream.available())
michael@0 365 );
michael@0 366
michael@0 367 let manifest;
michael@0 368 try {
michael@0 369 manifest = JSON.parse(jsonString);
michael@0 370 } catch(e) {
michael@0 371 self._sendError(deferred, "Error Parsing manifest.webapp: " + e, aId);
michael@0 372 }
michael@0 373
michael@0 374 let appType = self._getAppType(manifest.type);
michael@0 375
michael@0 376 // Privileged and certified packaged apps can setup a custom origin
michael@0 377 // via `origin` manifest property
michael@0 378 let id = aId;
michael@0 379 if (appType >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED &&
michael@0 380 manifest.origin !== undefined) {
michael@0 381 let uri;
michael@0 382 try {
michael@0 383 uri = Services.io.newURI(manifest.origin, null, null);
michael@0 384 } catch(e) {
michael@0 385 self._sendError(deferred, "Invalid origin in webapp's manifest", aId);
michael@0 386 }
michael@0 387
michael@0 388 if (uri.scheme != "app") {
michael@0 389 self._sendError(deferred, "Invalid origin in webapp's manifest", aId);
michael@0 390 }
michael@0 391 id = uri.prePath.substring(6);
michael@0 392 }
michael@0 393
michael@0 394 // Only after security checks are made and after final app id is computed
michael@0 395 // we can move application.zip to the destination directory, and
michael@0 396 // extract manifest.webapp there.
michael@0 397 let installDir = DOMApplicationRegistry._getAppDir(id);
michael@0 398 let manFile = installDir.clone();
michael@0 399 manFile.append("manifest.webapp");
michael@0 400 zipReader.extract("manifest.webapp", manFile);
michael@0 401 zipReader.close();
michael@0 402 zipFile.moveTo(installDir, "application.zip");
michael@0 403
michael@0 404 let origin = "app://" + id;
michael@0 405 let manifestURL = origin + "/manifest.webapp";
michael@0 406
michael@0 407 // Refresh application.zip content (e.g. reinstall app), as done here:
michael@0 408 // http://hg.mozilla.org/mozilla-central/annotate/aaefec5d34f8/dom/apps/src/Webapps.jsm#l1125
michael@0 409 // Do it in parent process for the simulator
michael@0 410 let jar = installDir.clone();
michael@0 411 jar.append("application.zip");
michael@0 412 Services.obs.notifyObservers(jar, "flush-cache-entry", null);
michael@0 413
michael@0 414 // And then in app content process
michael@0 415 // This function will be evaluated in the scope of the content process
michael@0 416 // frame script. That will flush the jar cache for this app and allow
michael@0 417 // loading fresh updated resources if we reload its document.
michael@0 418 let FlushFrameScript = function (path) {
michael@0 419 let jar = Components.classes["@mozilla.org/file/local;1"]
michael@0 420 .createInstance(Components.interfaces.nsILocalFile);
michael@0 421 jar.initWithPath(path);
michael@0 422 let obs = Components.classes["@mozilla.org/observer-service;1"]
michael@0 423 .getService(Components.interfaces.nsIObserverService);
michael@0 424 obs.notifyObservers(jar, "flush-cache-entry", null);
michael@0 425 };
michael@0 426 for each (let frame in self._appFrames()) {
michael@0 427 if (frame.getAttribute("mozapp") == manifestURL) {
michael@0 428 let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
michael@0 429 mm.loadFrameScript("data:," +
michael@0 430 encodeURIComponent("(" + FlushFrameScript.toString() + ")" +
michael@0 431 "('" + jar.path + "')"), false);
michael@0 432 }
michael@0 433 }
michael@0 434
michael@0 435 // Create a fake app object with the minimum set of properties we need.
michael@0 436 let app = {
michael@0 437 origin: origin,
michael@0 438 installOrigin: origin,
michael@0 439 manifestURL: manifestURL,
michael@0 440 appStatus: appType,
michael@0 441 receipts: aReceipts,
michael@0 442 }
michael@0 443
michael@0 444 self._registerApp(deferred, app, id, aDir);
michael@0 445 } catch(e) {
michael@0 446 // If anything goes wrong, just send it back.
michael@0 447 self._sendError(deferred, e.toString(), aId);
michael@0 448 }
michael@0 449 }
michael@0 450 }
michael@0 451
michael@0 452 Services.tm.currentThread.dispatch(runnable,
michael@0 453 Ci.nsIThread.DISPATCH_NORMAL);
michael@0 454 return deferred.promise;
michael@0 455 },
michael@0 456
michael@0 457 /**
michael@0 458 * @param appId : The id of the app we want to install. We will look for
michael@0 459 * the files for the app in $TMP/b2g/$appId :
michael@0 460 * For packaged apps: application.zip
michael@0 461 * For hosted apps: metadata.json and manifest.webapp
michael@0 462 */
michael@0 463 install: function wa_actorInstall(aRequest) {
michael@0 464 debug("install");
michael@0 465
michael@0 466 let appId = aRequest.appId;
michael@0 467 let reg = DOMApplicationRegistry;
michael@0 468 if (!appId) {
michael@0 469 appId = reg.makeAppId();
michael@0 470 }
michael@0 471
michael@0 472 // Check that we are not overriding a preinstalled application.
michael@0 473 if (appId in reg.webapps && reg.webapps[appId].removable === false) {
michael@0 474 return { error: "badParameterType",
michael@0 475 message: "The application " + appId + " can't be overriden."
michael@0 476 }
michael@0 477 }
michael@0 478
michael@0 479 let appDir = FileUtils.getDir("TmpD", ["b2g", appId], false, false);
michael@0 480
michael@0 481 if (aRequest.upload) {
michael@0 482 // Ensure creating the directory (recursively)
michael@0 483 appDir = FileUtils.getDir("TmpD", ["b2g", appId], true, false);
michael@0 484 let actor = this.conn.getActor(aRequest.upload);
michael@0 485 if (!actor) {
michael@0 486 return { error: "badParameter",
michael@0 487 message: "Unable to find upload actor '" + aRequest.upload
michael@0 488 + "'" };
michael@0 489 }
michael@0 490 let appFile = FileUtils.File(actor.getFilePath());
michael@0 491 if (!appFile.exists()) {
michael@0 492 return { error: "badParameter",
michael@0 493 message: "The uploaded file doesn't exist on device" };
michael@0 494 }
michael@0 495 appFile.moveTo(appDir, "application.zip");
michael@0 496 } else if ((!appDir || !appDir.exists()) &&
michael@0 497 !aRequest.manifest && !aRequest.metadata) {
michael@0 498 return { error: "badParameterType",
michael@0 499 message: "missing directory " + appDir.path
michael@0 500 };
michael@0 501 }
michael@0 502
michael@0 503 let testFile = appDir.clone();
michael@0 504 testFile.append("application.zip");
michael@0 505
michael@0 506 let receipts = (aRequest.receipts && Array.isArray(aRequest.receipts))
michael@0 507 ? aRequest.receipts
michael@0 508 : [];
michael@0 509
michael@0 510 if (testFile.exists()) {
michael@0 511 return this.installPackagedApp(appDir, appId, receipts);
michael@0 512 }
michael@0 513
michael@0 514 let manifest, metadata;
michael@0 515 let missing =
michael@0 516 ["manifest.webapp", "metadata.json"]
michael@0 517 .some(function(aName) {
michael@0 518 testFile = appDir.clone();
michael@0 519 testFile.append(aName);
michael@0 520 return !testFile.exists();
michael@0 521 });
michael@0 522 if (missing) {
michael@0 523 if (aRequest.manifest && aRequest.metadata &&
michael@0 524 aRequest.metadata.origin) {
michael@0 525 manifest = aRequest.manifest;
michael@0 526 metadata = aRequest.metadata;
michael@0 527 } else {
michael@0 528 try {
michael@0 529 appDir.remove(true);
michael@0 530 } catch(e) {}
michael@0 531 return { error: "badParameterType",
michael@0 532 message: "hosted app file and manifest/metadata fields " +
michael@0 533 "are missing"
michael@0 534 };
michael@0 535 }
michael@0 536 }
michael@0 537
michael@0 538 return this.installHostedApp(appDir, appId, receipts, manifest, metadata);
michael@0 539 },
michael@0 540
michael@0 541 getAll: function wa_actorGetAll(aRequest) {
michael@0 542 debug("getAll");
michael@0 543
michael@0 544 let deferred = promise.defer();
michael@0 545 let reg = DOMApplicationRegistry;
michael@0 546 reg.getAll(apps => {
michael@0 547 deferred.resolve({ apps: this._filterAllowedApps(apps) });
michael@0 548 });
michael@0 549
michael@0 550 return deferred.promise;
michael@0 551 },
michael@0 552
michael@0 553 getApp: function wa_actorGetApp(aRequest) {
michael@0 554 debug("getApp");
michael@0 555
michael@0 556 let manifestURL = aRequest.manifestURL;
michael@0 557 if (!manifestURL) {
michael@0 558 return { error: "missingParameter",
michael@0 559 message: "missing parameter manifestURL" };
michael@0 560 }
michael@0 561
michael@0 562 let reg = DOMApplicationRegistry;
michael@0 563 let app = reg.getAppByManifestURL(manifestURL);
michael@0 564 if (!app) {
michael@0 565 return { error: "appNotFound" };
michael@0 566 }
michael@0 567
michael@0 568 return this._isAppAllowedForURL(app.manifestURL).then(allowed => {
michael@0 569 if (!allowed) {
michael@0 570 return { error: "forbidden" };
michael@0 571 }
michael@0 572 return reg.getManifestFor(manifestURL).then(function (manifest) {
michael@0 573 app.manifest = manifest;
michael@0 574 return { app: app };
michael@0 575 });
michael@0 576 });
michael@0 577 },
michael@0 578
michael@0 579 _areCertifiedAppsAllowed: function wa__areCertifiedAppsAllowed() {
michael@0 580 let pref = "devtools.debugger.forbid-certified-apps";
michael@0 581 return !Services.prefs.getBoolPref(pref);
michael@0 582 },
michael@0 583
michael@0 584 _isAppAllowedForManifest: function wa__isAppAllowedForManifest(aManifest) {
michael@0 585 if (this._areCertifiedAppsAllowed()) {
michael@0 586 return true;
michael@0 587 }
michael@0 588 let type = this._getAppType(aManifest.type);
michael@0 589 return type !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
michael@0 590 },
michael@0 591
michael@0 592 _filterAllowedApps: function wa__filterAllowedApps(aApps) {
michael@0 593 return aApps.filter(app => this._isAppAllowedForManifest(app.manifest));
michael@0 594 },
michael@0 595
michael@0 596 _isAppAllowedForURL: function wa__isAppAllowedForURL(aManifestURL) {
michael@0 597 return this._findManifestByURL(aManifestURL).then(manifest => {
michael@0 598 return this._isAppAllowedForManifest(manifest);
michael@0 599 });
michael@0 600 },
michael@0 601
michael@0 602 uninstall: function wa_actorUninstall(aRequest) {
michael@0 603 debug("uninstall");
michael@0 604
michael@0 605 let manifestURL = aRequest.manifestURL;
michael@0 606 if (!manifestURL) {
michael@0 607 return { error: "missingParameter",
michael@0 608 message: "missing parameter manifestURL" };
michael@0 609 }
michael@0 610
michael@0 611 let deferred = promise.defer();
michael@0 612 let reg = DOMApplicationRegistry;
michael@0 613 reg.uninstall(
michael@0 614 manifestURL,
michael@0 615 function onsuccess() {
michael@0 616 deferred.resolve({});
michael@0 617 },
michael@0 618 function onfailure(reason) {
michael@0 619 deferred.resolve({ error: reason });
michael@0 620 }
michael@0 621 );
michael@0 622
michael@0 623 return deferred.promise;
michael@0 624 },
michael@0 625
michael@0 626 _findManifestByURL: function wa__findManifestByURL(aManifestURL) {
michael@0 627 let deferred = promise.defer();
michael@0 628
michael@0 629 let reg = DOMApplicationRegistry;
michael@0 630 let id = reg._appIdForManifestURL(aManifestURL);
michael@0 631
michael@0 632 reg._readManifests([{ id: id }]).then((aResults) => {
michael@0 633 deferred.resolve(aResults[0].manifest);
michael@0 634 });
michael@0 635
michael@0 636 return deferred.promise;
michael@0 637 },
michael@0 638
michael@0 639 getIconAsDataURL: function (aRequest) {
michael@0 640 debug("getIconAsDataURL");
michael@0 641
michael@0 642 let manifestURL = aRequest.manifestURL;
michael@0 643 if (!manifestURL) {
michael@0 644 return { error: "missingParameter",
michael@0 645 message: "missing parameter manifestURL" };
michael@0 646 }
michael@0 647
michael@0 648 let reg = DOMApplicationRegistry;
michael@0 649 let app = reg.getAppByManifestURL(manifestURL);
michael@0 650 if (!app) {
michael@0 651 return { error: "wrongParameter",
michael@0 652 message: "No application for " + manifestURL };
michael@0 653 }
michael@0 654
michael@0 655 let deferred = promise.defer();
michael@0 656
michael@0 657 this._findManifestByURL(manifestURL).then(jsonManifest => {
michael@0 658 let manifest = new ManifestHelper(jsonManifest, app.origin);
michael@0 659 let iconURL = manifest.iconURLForSize(aRequest.size || 128);
michael@0 660 if (!iconURL) {
michael@0 661 deferred.resolve({
michael@0 662 error: "noIcon",
michael@0 663 message: "This app has no icon"
michael@0 664 });
michael@0 665 return;
michael@0 666 }
michael@0 667
michael@0 668 // Download the URL as a blob
michael@0 669 // bug 899177: there is a bug with xhr and app:// and jar:// uris
michael@0 670 // that ends up forcing the content type to application/xml.
michael@0 671 let req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
michael@0 672 .createInstance(Ci.nsIXMLHttpRequest);
michael@0 673 req.open("GET", iconURL, false);
michael@0 674 req.responseType = "blob";
michael@0 675
michael@0 676 try {
michael@0 677 req.send(null);
michael@0 678 } catch(e) {
michael@0 679 deferred.resolve({
michael@0 680 error: "noIcon",
michael@0 681 message: "The icon file '" + iconURL + "' doesn't exist"
michael@0 682 });
michael@0 683 return;
michael@0 684 }
michael@0 685
michael@0 686 // Convert the blog to a base64 encoded data URI
michael@0 687 let reader = Cc["@mozilla.org/files/filereader;1"]
michael@0 688 .createInstance(Ci.nsIDOMFileReader);
michael@0 689 reader.onload = function () {
michael@0 690 deferred.resolve({
michael@0 691 url: reader.result
michael@0 692 });
michael@0 693 };
michael@0 694 reader.onerror = function () {
michael@0 695 deferred.resolve({
michael@0 696 error: reader.error.name,
michael@0 697 message: String(reader.error)
michael@0 698 });
michael@0 699 };
michael@0 700 reader.readAsDataURL(req.response);
michael@0 701 });
michael@0 702
michael@0 703 return deferred.promise;
michael@0 704 },
michael@0 705
michael@0 706 launch: function wa_actorLaunch(aRequest) {
michael@0 707 debug("launch");
michael@0 708
michael@0 709 let manifestURL = aRequest.manifestURL;
michael@0 710 if (!manifestURL) {
michael@0 711 return { error: "missingParameter",
michael@0 712 message: "missing parameter manifestURL" };
michael@0 713 }
michael@0 714
michael@0 715 let deferred = promise.defer();
michael@0 716
michael@0 717 DOMApplicationRegistry.launch(
michael@0 718 aRequest.manifestURL,
michael@0 719 aRequest.startPoint || "",
michael@0 720 Date.now(),
michael@0 721 function onsuccess() {
michael@0 722 deferred.resolve({});
michael@0 723 },
michael@0 724 function onfailure(reason) {
michael@0 725 deferred.resolve({ error: reason });
michael@0 726 });
michael@0 727
michael@0 728 return deferred.promise;
michael@0 729 },
michael@0 730
michael@0 731 close: function wa_actorLaunch(aRequest) {
michael@0 732 debug("close");
michael@0 733
michael@0 734 let manifestURL = aRequest.manifestURL;
michael@0 735 if (!manifestURL) {
michael@0 736 return { error: "missingParameter",
michael@0 737 message: "missing parameter manifestURL" };
michael@0 738 }
michael@0 739
michael@0 740 let reg = DOMApplicationRegistry;
michael@0 741 let app = reg.getAppByManifestURL(manifestURL);
michael@0 742 if (!app) {
michael@0 743 return { error: "missingParameter",
michael@0 744 message: "No application for " + manifestURL };
michael@0 745 }
michael@0 746
michael@0 747 reg.close(app);
michael@0 748
michael@0 749 return {};
michael@0 750 },
michael@0 751
michael@0 752 _appFrames: function () {
michael@0 753 // For now, we only support app frames on b2g
michael@0 754 if (Services.appinfo.ID != "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
michael@0 755 return;
michael@0 756 }
michael@0 757 // Register the system app
michael@0 758 let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
michael@0 759 let systemAppFrame = chromeWindow.shell.contentBrowser;
michael@0 760 yield systemAppFrame;
michael@0 761
michael@0 762 // Register apps hosted in the system app. i.e. the homescreen, all regular
michael@0 763 // apps and the keyboard.
michael@0 764 // Bookmark apps and other system app internal frames like captive portal
michael@0 765 // are also hosted in system app, but they are not using mozapp attribute.
michael@0 766 let frames = systemAppFrame.contentDocument.querySelectorAll("iframe[mozapp]");
michael@0 767 for (let i = 0; i < frames.length; i++) {
michael@0 768 yield frames[i];
michael@0 769 }
michael@0 770 },
michael@0 771
michael@0 772 listRunningApps: function (aRequest) {
michael@0 773 debug("listRunningApps\n");
michael@0 774
michael@0 775 let appPromises = [];
michael@0 776 let apps = [];
michael@0 777
michael@0 778 for each (let frame in this._appFrames()) {
michael@0 779 let manifestURL = frame.getAttribute("mozapp");
michael@0 780
michael@0 781 appPromises.push(this._isAppAllowedForURL(manifestURL).then(allowed => {
michael@0 782 if (allowed) {
michael@0 783 apps.push(manifestURL);
michael@0 784 }
michael@0 785 }));
michael@0 786 }
michael@0 787
michael@0 788 return promise.all(appPromises).then(() => {
michael@0 789 return { apps: apps };
michael@0 790 });
michael@0 791 },
michael@0 792
michael@0 793 getAppActor: function ({ manifestURL }) {
michael@0 794 debug("getAppActor\n");
michael@0 795
michael@0 796 let appFrame = null;
michael@0 797 for each (let frame in this._appFrames()) {
michael@0 798 if (frame.getAttribute("mozapp") == manifestURL) {
michael@0 799 appFrame = frame;
michael@0 800 break;
michael@0 801 }
michael@0 802 }
michael@0 803
michael@0 804 let notFoundError = {
michael@0 805 error: "appNotFound",
michael@0 806 message: "Unable to find any opened app whose manifest " +
michael@0 807 "is '" + manifestURL + "'"
michael@0 808 };
michael@0 809
michael@0 810 if (!appFrame) {
michael@0 811 return notFoundError;
michael@0 812 }
michael@0 813
michael@0 814 return this._isAppAllowedForURL(manifestURL).then(allowed => {
michael@0 815 if (!allowed) {
michael@0 816 return notFoundError;
michael@0 817 }
michael@0 818
michael@0 819 // Only create a new actor, if we haven't already
michael@0 820 // instanciated one for this connection.
michael@0 821 let map = this._appActorsMap;
michael@0 822 let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
michael@0 823 .frameLoader
michael@0 824 .messageManager;
michael@0 825 let actor = map.get(mm);
michael@0 826 if (!actor) {
michael@0 827 let onConnect = actor => {
michael@0 828 map.set(mm, actor);
michael@0 829 return { actor: actor };
michael@0 830 };
michael@0 831 let onDisconnect = mm => {
michael@0 832 map.delete(mm);
michael@0 833 };
michael@0 834 return DebuggerServer.connectToChild(this.conn, appFrame, onDisconnect)
michael@0 835 .then(onConnect);
michael@0 836 }
michael@0 837
michael@0 838 return { actor: actor };
michael@0 839 });
michael@0 840 },
michael@0 841
michael@0 842 watchApps: function () {
michael@0 843 this._openedApps = new Set();
michael@0 844 // For now, app open/close events are only implement on b2g
michael@0 845 if (Services.appinfo.ID == "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
michael@0 846 let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
michael@0 847 let systemAppFrame = chromeWindow.getContentWindow();
michael@0 848 systemAppFrame.addEventListener("appwillopen", this);
michael@0 849 systemAppFrame.addEventListener("appterminated", this);
michael@0 850 }
michael@0 851 Services.obs.addObserver(this, "webapps-installed", false);
michael@0 852 Services.obs.addObserver(this, "webapps-uninstall", false);
michael@0 853
michael@0 854 return {};
michael@0 855 },
michael@0 856
michael@0 857 unwatchApps: function () {
michael@0 858 this._openedApps = null;
michael@0 859 if (Services.appinfo.ID == "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
michael@0 860 let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
michael@0 861 let systemAppFrame = chromeWindow.getContentWindow();
michael@0 862 systemAppFrame.removeEventListener("appwillopen", this);
michael@0 863 systemAppFrame.removeEventListener("appterminated", this);
michael@0 864 }
michael@0 865 Services.obs.removeObserver(this, "webapps-installed", false);
michael@0 866 Services.obs.removeObserver(this, "webapps-uninstall", false);
michael@0 867
michael@0 868 return {};
michael@0 869 },
michael@0 870
michael@0 871 handleEvent: function (event) {
michael@0 872 let manifestURL;
michael@0 873 switch(event.type) {
michael@0 874 case "appwillopen":
michael@0 875 manifestURL = event.detail.manifestURL;
michael@0 876
michael@0 877 // Ignore the event if we already received an appwillopen for this app
michael@0 878 // (appwillopen is also fired when the app has been moved to background
michael@0 879 // and get back to foreground)
michael@0 880 if (this._openedApps.has(manifestURL)) {
michael@0 881 return;
michael@0 882 }
michael@0 883 this._openedApps.add(manifestURL);
michael@0 884
michael@0 885 this._isAppAllowedForURL(manifestURL).then(allowed => {
michael@0 886 if (allowed) {
michael@0 887 this.conn.send({ from: this.actorID,
michael@0 888 type: "appOpen",
michael@0 889 manifestURL: manifestURL
michael@0 890 });
michael@0 891 }
michael@0 892 });
michael@0 893
michael@0 894 break;
michael@0 895
michael@0 896 case "appterminated":
michael@0 897 manifestURL = event.detail.manifestURL;
michael@0 898 this._openedApps.delete(manifestURL);
michael@0 899
michael@0 900 this._isAppAllowedForURL(manifestURL).then(allowed => {
michael@0 901 if (allowed) {
michael@0 902 this.conn.send({ from: this.actorID,
michael@0 903 type: "appClose",
michael@0 904 manifestURL: manifestURL
michael@0 905 });
michael@0 906 }
michael@0 907 });
michael@0 908
michael@0 909 break;
michael@0 910 }
michael@0 911 },
michael@0 912
michael@0 913 observe: function (subject, topic, data) {
michael@0 914 let app = JSON.parse(data);
michael@0 915 if (topic == "webapps-installed") {
michael@0 916 this.conn.send({ from: this.actorID,
michael@0 917 type: "appInstall",
michael@0 918 manifestURL: app.manifestURL
michael@0 919 });
michael@0 920 } else if (topic == "webapps-uninstall") {
michael@0 921 this.conn.send({ from: this.actorID,
michael@0 922 type: "appUninstall",
michael@0 923 manifestURL: app.manifestURL
michael@0 924 });
michael@0 925 }
michael@0 926 }
michael@0 927 };
michael@0 928
michael@0 929 /**
michael@0 930 * The request types this actor can handle.
michael@0 931 */
michael@0 932 WebappsActor.prototype.requestTypes = {
michael@0 933 "install": WebappsActor.prototype.install,
michael@0 934 "uploadPackage": WebappsActor.prototype.uploadPackage,
michael@0 935 "getAll": WebappsActor.prototype.getAll,
michael@0 936 "getApp": WebappsActor.prototype.getApp,
michael@0 937 "launch": WebappsActor.prototype.launch,
michael@0 938 "close": WebappsActor.prototype.close,
michael@0 939 "uninstall": WebappsActor.prototype.uninstall,
michael@0 940 "listRunningApps": WebappsActor.prototype.listRunningApps,
michael@0 941 "getAppActor": WebappsActor.prototype.getAppActor,
michael@0 942 "watchApps": WebappsActor.prototype.watchApps,
michael@0 943 "unwatchApps": WebappsActor.prototype.unwatchApps,
michael@0 944 "getIconAsDataURL": WebappsActor.prototype.getIconAsDataURL
michael@0 945 };
michael@0 946
michael@0 947 DebuggerServer.addGlobalActor(WebappsActor, "webappsActor");

mercurial