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.

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

mercurial