Bug 1059194 - Trusted Hosted Apps, part 1: App installation & Run. r=fabrice a=bajaj
authorVlatko Markovic <vlatko.markovic@sonymobile.com>
Tue, 16 Sep 2014 13:14:02 -0700
changeset 224909 3ef45413d479c22627019ff8bcd1aaba075013c0
parent 224908 31fd219bf12bcf9c6cfa000220a25b96c5e5a3b3
child 224910 e7ad69c3b90c5b06d0c2f4e25c8b93db7753c927
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice, bajaj
bugs1059194
milestone34.0a2
Bug 1059194 - Trusted Hosted Apps, part 1: App installation & Run. r=fabrice a=bajaj
dom/apps/AppsUtils.jsm
dom/apps/PermissionsInstaller.jsm
dom/apps/PermissionsTable.jsm
dom/apps/Webapps.jsm
dom/messages/SystemMessagePermissionsChecker.jsm
--- a/dom/apps/AppsUtils.jsm
+++ b/dom/apps/AppsUtils.jsm
@@ -436,16 +436,17 @@ this.AppsUtils = {
    * @param object aManifest
    * @returns integer
    **/
   getAppManifestStatus: function getAppManifestStatus(aManifest) {
     let type = aManifest.type || "web";
 
     switch(type) {
     case "web":
+    case "trusted":
       return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
     case "privileged":
       return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED;
     case "certified":
       return Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
     default:
       throw new Error("Webapps.jsm: Undetermined app manifest type");
     }
--- a/dom/apps/PermissionsInstaller.jsm
+++ b/dom/apps/PermissionsInstaller.jsm
@@ -100,16 +100,19 @@ this.PermissionsInstaller = {
       case Ci.nsIPrincipal.APP_STATUS_CERTIFIED:
         appStatus = "certified";
         break;
       case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED:
         appStatus = "privileged";
         break;
       case Ci.nsIPrincipal.APP_STATUS_INSTALLED:
         appStatus = "app";
+        if (aApp.kind == "hosted-trusted") {
+          appStatus = "trusted";
+        }
         break;
       default:
         // Cannot determine app type, abort install by throwing an error.
         throw new Error("PermissionsInstaller.jsm: " +
                         "Cannot determine the app's status. Install cancelled.");
         break;
       }
 
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -30,398 +30,470 @@ const PROMPT_ACTION = Ci.nsIPermissionMa
 // Permissions Matrix: https://docs.google.com/spreadsheet/ccc?key=0Akyz_Bqjgf5pdENVekxYRjBTX0dCXzItMnRyUU1RQ0E#gid=0
 
 // Permissions that are implicit:
 // battery-status, network-information, vibration,
 // device-capabilities
 
 this.PermissionsTable =  { geolocation: {
                              app: PROMPT_ACTION,
+                             trusted: PROMPT_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: PROMPT_ACTION
                            },
                            "geolocation-noprompt": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION,
                              substitute: ["geolocation"]
                            },
                            camera: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION
                            },
                            alarms: {
                              app: ALLOW_ACTION,
+                             trusted: ALLOW_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "tcp-socket": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "udp-socket": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "network-events": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            contacts: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write", "create"]
                            },
                            "device-storage:apps": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read"]
                            },
                            "device-storage:crashes": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read"]
                            },
                            "device-storage:pictures": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write", "create"]
                            },
                            "device-storage:videos": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write", "create"]
                            },
                            "device-storage:music": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write", "create"]
                            },
                            "device-storage:sdcard": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write", "create"]
                            },
                            sms: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            telephony: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            browser: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            bluetooth: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            mobileconnection: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            mobilenetwork: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            power: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            push: {
                             app: ALLOW_ACTION,
+                            trusted: ALLOW_ACTION,
                             privileged: ALLOW_ACTION,
                             certified: ALLOW_ACTION
                            },
                            settings: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write"],
                              additional: ["indexedDB-chrome-settings", "settings-api"]
                            },
                            // This exists purely for tests, no app
                            // should ever use it. It can only be
                            // handed out by SpecialPowers.
                            "settings-clear": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: DENY_ACTION,
                              additional: ["indexedDB-chrome-settings", "settings-api"]
                            },
                            permissions: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            phonenumberservice: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            fmradio: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            attention: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "moz-attention": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION,
                              substitute: ["attention"]
                            },
                            "webapps-manage": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "backgroundservice": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "desktop-notification": {
                              app: ALLOW_ACTION,
+                             trusted: ALLOW_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "networkstats-manage": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "resourcestats-manage": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "wifi-manage": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "systemXHR": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "voicemail": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "deprecated-hwvideo": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "idle": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "time": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "embed-apps": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "embed-widgets": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "storage": {
                              app: ALLOW_ACTION,
+                             trusted: ALLOW_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION,
                              substitute: [
                                "indexedDB-unlimited",
                                "default-persistent-storage"
                              ]
                            },
                            "background-sensors": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            cellbroadcast: {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-channel-normal": {
                              app: ALLOW_ACTION,
+                             trusted: ALLOW_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-channel-content": {
                              app: ALLOW_ACTION,
+                             trusted: ALLOW_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-channel-notification": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-channel-alarm": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-channel-telephony": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "moz-audio-channel-telephony": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION,
                              substitute: ["audio-channel-telephony"]
                            },
                            "audio-channel-ringer": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "moz-audio-channel-ringer": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION,
                              substitute: ["audio-channel-ringer"]
                            },
                            "audio-channel-publicnotification": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "open-remote-window": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "input": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "input-manage": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "wappush": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-capture": {
                              app: PROMPT_ACTION,
+                             trusted: PROMPT_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "nfc": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write"]
                            },
                            "nfc-manager": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "nfc-hci-events": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "speaker-control": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "downloads": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "video-capture": {
                              app: PROMPT_ACTION,
+                             trusted: PROMPT_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "feature-detection": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "mobileid": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: PROMPT_ACTION
                            },
                            // This permission doesn't actually grant access to
                            // anything. It exists only to check the correctness
                            // of web prompt composed permissions in tests.
                            "test-permission": {
                              app: PROMPT_ACTION,
+                             trusted: PROMPT_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write", "create"]
                            },
                            "firefox-accounts": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "moz-firefox-accounts": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              substitute: ["firefox-accounts"]
                              },
                            "themeable": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "settings:wallpaper.image": {
                              app: DENY_ACTION,
+                             trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write"],
                              additional: ["settings-api"]
                            }
                          };
 
 /**
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -150,16 +150,17 @@ XPCOMUtils.defineLazyGetter(this, "updat
 // store even by error.
 const STORE_ID_PENDING_PREFIX = "#unknownID#";
 
 this.DOMApplicationRegistry = {
   // pseudo-constants for the different application kinds.
   get kPackaged()       "packaged",
   get kHosted()         "hosted",
   get kHostedAppcache() "hosted-appcache",
+  get kTrustedHosted()  "hosted-trusted",
 
   // Path to the webapps.json file where we store the registry data.
   appsFile: null,
   webapps: { },
   children: [ ],
   allAppsLaunchable: false,
   _updateHandlers: [ ],
   _pendingUninstalls: {},
@@ -366,23 +367,17 @@ this.DOMApplicationRegistry = {
         app.role = aResult.manifest.role || "";
 
         let localeManifest = new ManifestHelper(aResult.manifest, app.origin, app.manifestURL);
         this._saveWidgetsFullPath(localeManifest, app);
 
         if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
           app.redirects = this.sanitizeRedirects(aResult.redirects);
         }
-        if (app.origin.startsWith("app://")) {
-          app.kind = this.kPackaged;
-        } else {
-          // Hosted apps, can be appcached or not.
-          app.kind = aResult.manifest.appcache_path ? this.kHostedAppcache
-                                                    : this.kHosted;
-        }
+        app.kind = this.appKind(app, aResult.manifest);
       });
 
       // Nothing else to do but notifying we're ready.
       this.notifyAppsRegistryReady();
     }
   }),
 
   updateDataStoreForApp: Task.async(function*(aId) {
@@ -392,33 +387,49 @@ this.DOMApplicationRegistry = {
 
     // Create or Update the DataStore for this app
     let results = yield this._readManifests([{ id: aId }]);
     let app = this.webapps[aId];
     this.updateDataStore(app.localId, app.origin, app.manifestURL,
                          results[0].manifest, app.appStatus);
   }),
 
+  appKind: function(aApp, aManifest) {
+    if (aApp.origin.startsWith("app://")) {
+      return this.kPackaged;
+    } else {
+      // Hosted apps, can be appcached or not.
+      let kind = this.kHosted;
+      if (aManifest.type == "trusted") {
+        kind = this.kTrustedHosted;
+      } else if (aManifest.appcache_path) {
+        kind = this.kHostedAppcache;
+      }
+      return kind;
+    }
+  },
+
   updatePermissionsForApp: function(aId, aIsPreinstalled, aIsSystemUpdate) {
     if (!this.webapps[aId]) {
       return;
     }
 
     // Install the permissions for this app, as if we were updating
     // to cleanup the old ones if needed.
     // TODO It's not clear what this should do when there are multiple profiles.
     if (supportUseCurrentProfile()) {
       this._readManifests([{ id: aId }]).then((aResult) => {
         let data = aResult[0];
         PermissionsInstaller.installPermissions({
           manifest: data.manifest,
           manifestURL: this.webapps[aId].manifestURL,
           origin: this.webapps[aId].origin,
           isPreinstalled: aIsPreinstalled,
-          isSystemUpdate: aIsSystemUpdate
+          isSystemUpdate: aIsSystemUpdate,
+          kind: this.webapps[aId].kind
         }, true, function() {
           debug("Error installing permissions for " + aId);
         });
       });
     }
   },
 
   updateOfflineCacheForApp: function(aId) {
@@ -1011,23 +1022,17 @@ this.DOMApplicationRegistry = {
         app.name = manifest.name;
         app.csp = manifest.csp || "";
         app.role = localeManifest.role;
         this._saveWidgetsFullPath(localeManifest, app);
 
         if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
           app.redirects = this.sanitizeRedirects(manifest.redirects);
         }
-        if (app.origin.startsWith("app://")) {
-          app.kind = this.kPackaged;
-        } else {
-          // Hosted apps, can be appcached or not.
-          app.kind = aResult.manifest.appcache_path ? this.kHostedAppcache
-                                                    : this.kHosted;
-        }
+        app.kind = this.appKind(app, aResult.manifest);
         this._registerSystemMessages(manifest, app);
         this._registerInterAppConnections(manifest, app);
         appsToRegister.push({ manifest: manifest, app: app });
       });
       this._safeToClone.resolve();
       this._registerActivitiesForApps(appsToRegister, aRunUpdate);
     });
   },
@@ -1673,36 +1678,41 @@ this.DOMApplicationRegistry = {
     let appObject = AppsUtils.cloneAppObject(app);
     appObject.updateManifest = updateManifest;
     this.notifyUpdateHandlers(appObject, newManifest, appFile.path);
 
     if (supportUseCurrentProfile()) {
       PermissionsInstaller.installPermissions(
         { manifest: newManifest,
           origin: app.origin,
-          manifestURL: app.manifestURL },
+          manifestURL: app.manifestURL,
+          kind: app.kind },
         true);
     }
     this.updateDataStore(this.webapps[id].localId, app.origin,
                          app.manifestURL, newManifest);
     this.broadcastMessage("Webapps:UpdateState", {
       app: app,
       manifest: newManifest,
       id: app.id
     });
     this.broadcastMessage("Webapps:FireEvent", {
       eventType: "downloadapplied",
       manifestURL: app.manifestURL
     });
   }),
 
   startOfflineCacheDownload: function(aManifest, aApp, aProfileDir, aIsUpdate) {
-    if (aApp.kind !== this.kHostedAppcache) {
+    debug("startOfflineCacheDownload " + aApp.id + " " + aApp.kind);
+    if ((aApp.kind !== this.kHostedAppcache &&
+         aApp.kind !== this.kTrustedHosted) ||
+         !aManifest.appcache_path) {
       return;
     }
+    debug("startOfflineCacheDownload " + aManifest.appcache_path);
 
     // If the manifest has an appcache_path property, use it to populate the
     // appcache.
     let appcacheURI = Services.io.newURI(aManifest.fullAppcachePath(),
                                          null, null);
     let docURI = Services.io.newURI(aManifest.fullLaunchPath(), null, null);
 
     // We determine the app's 'installState' according to its previous
@@ -1815,25 +1825,30 @@ this.DOMApplicationRegistry = {
 
 #ifdef MOZ_WIDGET_GONK
     let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
     onlyCheckAppCache = (app.basePath == appDir.path);
 #endif
 
     if (onlyCheckAppCache) {
       // Bail out for packaged apps & hosted apps without appcache.
-      if (app.kind !== this.kHostedAppcache) {
+      if (aApp.kind !== this.kHostedAppcache &&
+          aApp.kind !== this.kTrustedHosted) {
         sendError("NOT_UPDATABLE");
         return;
       }
 
       // We need the manifest to get the appcache path.
       this._readManifests([{ id: id }]).then((aResult) => {
         debug("Checking only appcache for " + aData.manifestURL);
         let manifest = aResult[0].manifest;
+        if (!manifest.appcache_path) {
+          sendError("NOT_UPDATABLE");
+          return;
+        }
         // Check if the appcache is updatable, and send "downloadavailable" or
         // "downloadapplied".
         let updateObserver = {
           observe: function(aSubject, aTopic, aObsData) {
             debug("onlyCheckAppCache updateSvc.checkForUpdate return for " +
                   app.manifestURL + " - event is " + aTopic);
             if (aTopic == "offline-cache-update-available") {
               app.downloadAvailable = true;
@@ -2105,34 +2120,37 @@ this.DOMApplicationRegistry = {
       manifest =
         new ManifestHelper(aNewManifest, aApp.origin, aApp.manifestURL);
 
       if (supportUseCurrentProfile()) {
         // Update the permissions for this app.
         PermissionsInstaller.installPermissions({
           manifest: aApp.manifest,
           origin: aApp.origin,
-          manifestURL: aData.manifestURL
+          manifestURL: aData.manifestURL,
+          kind: aApp.kind
         }, true);
       }
 
       this.updateDataStore(this.webapps[aId].localId, aApp.origin,
                            aApp.manifestURL, aApp.manifest);
 
       aApp.name = aNewManifest.name;
       aApp.csp = manifest.csp || "";
       this._saveWidgetsFullPath(manifest, aApp);
       aApp.updateTime = Date.now();
     }
 
     // Update the registry.
     this.webapps[aId] = aApp;
     yield this._saveApps();
 
-    if (aApp.kind !== this.kHostedAppcache) {
+    if ((aApp.kind !== this.kHostedAppcache &&
+         aApp.kind !== this.kTrustedHosted) ||
+         !aApp.manifest.appcache_path) {
       this.broadcastMessage("Webapps:UpdateState", {
         app: aApp,
         manifest: aApp.manifest,
         id: aApp.id
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloadapplied",
         manifestURL: aApp.manifestURL,
@@ -2198,17 +2216,18 @@ this.DOMApplicationRegistry = {
         }
       }
     }
 
     // Hosted apps can't be trusted or certified, so just check that the
     // manifest doesn't ask for those.
     function checkAppStatus(aManifest) {
       let manifestStatus = aManifest.type || "web";
-      return manifestStatus === "web";
+      return manifestStatus === "web" ||
+             manifestStatus === "trusted";
     }
 
     let checkManifest = (function() {
       if (!app.manifest) {
         sendError("MANIFEST_PARSE_ERROR");
         return false;
       }
 
@@ -2490,29 +2509,35 @@ this.DOMApplicationRegistry = {
     return app;
   },
 
   _cloneApp: function(aData, aNewApp, aLocaleManifest, aManifest, aId, aLocalId) {
     let appObject = AppsUtils.cloneAppObject(aNewApp);
     appObject.appStatus =
       aNewApp.appStatus || Ci.nsIPrincipal.APP_STATUS_INSTALLED;
 
-    if (appObject.kind == this.kHostedAppcache) {
+    let usesAppcache = appObject.kind == this.kHostedAppcache;
+    if (appObject.kind == this.kTrustedHosted && aManifest.appcache_path) {
+      usesAppcache = true;
+    }
+
+    if (usesAppcache) {
       appObject.installState = "pending";
       appObject.downloadAvailable = true;
       appObject.downloading = true;
       appObject.downloadSize = 0;
       appObject.readyToApplyDownload = false;
     } else if (appObject.kind == this.kPackaged) {
       appObject.installState = "pending";
       appObject.downloadAvailable = true;
       appObject.downloading = true;
       appObject.downloadSize = aLocaleManifest.size;
       appObject.readyToApplyDownload = false;
-    } else if (appObject.kind == this.kHosted) {
+    } else if (appObject.kind == this.kHosted ||
+               appObject.kind == this.kTrustedHosted) {
       appObject.installState = "installed";
       appObject.downloadAvailable = false;
       appObject.downloading = false;
       appObject.readyToApplyDownload = false;
     } else {
       debug("Unknown app kind: " + appObject.kind);
       throw Error("Unknown app kind: " + appObject.kind);
     }
@@ -2644,35 +2669,32 @@ this.DOMApplicationRegistry = {
     let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest;
     yield this._writeManifestFile(id, aData.isPackage, jsonManifest);
 
     debug("app.origin: " + app.origin);
     let manifest =
       new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
 
     // Set the application kind.
-    if (aData.isPackage) {
-      app.kind = this.kPackaged;
-    } else {
-      app.kind = manifest.appcache_path ? this.kHostedAppcache : this.kHosted;
-    }
+    app.kind = this.appKind(app, manifest);
 
     let appObject = this._cloneApp(aData, app, manifest, jsonManifest, id, localId);
 
     this.webapps[id] = appObject;
 
     // For package apps, the permissions are not in the mini-manifest, so
     // don't update the permissions yet.
     if (!aData.isPackage) {
       if (supportUseCurrentProfile()) {
         PermissionsInstaller.installPermissions(
           {
             origin: appObject.origin,
             manifestURL: appObject.manifestURL,
-            manifest: jsonManifest
+            manifest: jsonManifest,
+            kind: appObject.kind
           },
           isReinstall,
           this.doUninstall.bind(this, aData, aData.mm)
         );
       }
 
       this.updateDataStore(this.webapps[id].localId,  this.webapps[id].origin,
                            this.webapps[id].manifestURL, jsonManifest);
@@ -2680,17 +2702,19 @@ this.DOMApplicationRegistry = {
 
     for each (let prop in ["installState", "downloadAvailable", "downloading",
                            "downloadSize", "readyToApplyDownload"]) {
       aData.app[prop] = appObject[prop];
     }
 
     let dontNeedNetwork = false;
 
-    if (appObject.kind == this.kHostedAppcache) {
+    if ((appObject.kind == this.kHostedAppcache ||
+        appObject.kind == this.kTrustedHosted) &&
+        manifest.appcache_path) {
       this.queuedDownload[app.manifestURL] = {
         manifest: manifest,
         app: appObject,
         profileDir: aProfileDir
       }
     } else if (appObject.kind == this.kPackaged) {
       // If it is a local app then it must been installed from a local file
       // instead of web.
@@ -2810,17 +2834,18 @@ this.DOMApplicationRegistry = {
     Services.obs.notifyObservers(null, "webapps-installed",
       JSON.stringify({ manifestURL: aNewApp.manifestURL }));
 
     if (supportUseCurrentProfile()) {
       // Update the permissions for this app.
       PermissionsInstaller.installPermissions({
         manifest: aManifest,
         origin: aNewApp.origin,
-        manifestURL: aNewApp.manifestURL
+        manifestURL: aNewApp.manifestURL,
+        kind: this.webapps[aId].kind
       }, true);
     }
 
     this.updateDataStore(this.webapps[aId].localId, aNewApp.origin,
                          aNewApp.manifestURL, aManifest);
 
     if (aInstallSuccessCallback) {
       try {
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -226,16 +226,19 @@ this.SystemMessagePermissionsChecker = {
     case Ci.nsIPrincipal.APP_STATUS_CERTIFIED:
       appStatus = "certified";
       break;
     case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED:
       appStatus = "privileged";
       break;
     case Ci.nsIPrincipal.APP_STATUS_INSTALLED:
       appStatus = "app";
+      if (aManifest.type == "trusted") {
+        appStatus = "trusted";
+      }
       break;
     default:
       throw new Error("SystemMessagePermissionsChecker.jsm: " +
                       "Cannot decide the app's status. Install cancelled.");
       break;
     }
 
     // It's ok here to not pass the origin to ManifestHelper since we only