Bug 1042881 - ManifestHelper() must resolve uris against the manifest url r=myk
authorFabrice Desré <fabrice@mozilla.com>
Wed, 30 Jul 2014 14:00:15 -0700
changeset 218529 5a84a1907654185e04a58a2d40654dd7c57b12bc
parent 218528 68f3aa5d9568e8ba16ba390e9b7739f0007d403d
child 218530 3177b83dc26c1f30a3f848ef017d6dec8757be96
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)
reviewersmyk
bugs1042881
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1042881 - ManifestHelper() must resolve uris against the manifest url r=myk
b2g/chrome/content/shell.js
b2g/components/AlertsHelper.jsm
browser/modules/WebappManager.jsm
dom/activities/src/ActivitiesService.jsm
dom/apps/src/AppsUtils.jsm
dom/apps/src/PermissionsInstaller.jsm
dom/apps/src/Webapps.jsm
dom/messages/SystemMessagePermissionsChecker.jsm
mobile/android/chrome/content/WebappRT.js
mobile/android/chrome/content/aboutApps.js
mobile/android/modules/WebappManager.jsm
toolkit/devtools/server/actors/webapps.js
toolkit/webapps/NativeApp.jsm
webapprt/WebappManager.jsm
webapprt/WebappRT.jsm
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -797,17 +797,18 @@ var WebappsHelper = {
     json.mm = subject;
 
     switch(topic) {
       case "webapps-launch":
         DOMApplicationRegistry.getManifestFor(json.manifestURL).then((aManifest) => {
           if (!aManifest)
             return;
 
-          let manifest = new ManifestHelper(aManifest, json.origin);
+          let manifest = new ManifestHelper(aManifest, json.origin,
+                                            json.manifestURL);
           let payload = {
             timestamp: json.timestamp,
             url: manifest.fullLaunchPath(json.startPoint),
             manifestURL: json.manifestURL
           };
           shell.sendCustomEvent("webapps-launch", payload);
         });
         break;
--- a/b2g/components/AlertsHelper.jsm
+++ b/b2g/components/AlertsHelper.jsm
@@ -162,30 +162,30 @@ let AlertsHelper = {
   registerListener: function(alertId, cookie, alertListener) {
     this._listeners[alertId] = { observer: alertListener, cookie: cookie };
   },
 
   registerAppListener: function(uid, listener) {
     this._listeners[uid] = listener;
 
     appsService.getManifestFor(listener.manifestURL).then((manifest) => {
-      let helper = new ManifestHelper(manifest, listener.manifestURL);
+      let app = appsService.getAppByManifestURL(listener.manifestURL);
+      let helper = new ManifestHelper(manifest, app.origin, app.manifestURL);
       let getNotificationURLFor = function(messages) {
         if (!messages) {
           return null;
         }
 
         for (let i = 0; i < messages.length; i++) {
           let message = messages[i];
           if (message === kNotificationSystemMessageName) {
             return helper.fullLaunchPath();
           } else if (typeof message === "object" &&
                      kNotificationSystemMessageName in message) {
-            return helper.resolveFromOrigin(
-              message[kNotificationSystemMessageName]);
+            return helper.resolveURL(message[kNotificationSystemMessageName]);
           }
         }
 
         // No message found...
         return null;
       }
 
       listener.target = getNotificationURLFor(manifest.messages);
@@ -215,17 +215,18 @@ let AlertsHelper = {
     if (!manifestURL || !manifestURL.length) {
       send(null, null);
       return;
     }
 
     // If we have a manifest URL, get the icon and title from the manifest
     // to prevent spoofing.
     appsService.getManifestFor(manifestURL).then((manifest) => {
-      let helper = new ManifestHelper(manifest, manifestURL);
+      let app = appsService.getAppByManifestURL(manifestURL);
+      let helper = new ManifestHelper(manifest, app.origin, manifestURL);
       send(helper.name, helper.iconURLForSize(kNotificationIconSize));
     });
   },
 
   showAlertNotification: function(aMessage) {
     let data = aMessage.data;
     let currentListener = this._listeners[data.name];
     if (currentListener && currentListener.observer) {
--- a/browser/modules/WebappManager.jsm
+++ b/browser/modules/WebappManager.jsm
@@ -61,17 +61,19 @@ this.WebappManager = {
     if (aMessage.name == "Webapps:UpdateState") {
       if (data.error) {
         this.installations[manifestURL].reject(data.error);
       } else if (data.app.installState == "installed") {
         this.installations[manifestURL].resolve();
       }
     } else if (aMessage.name == "Webapps:Install:Return:OK" &&
                !data.isPackage) {
-      let manifest = new ManifestHelper(data.app.manifest, data.app.origin);
+      let manifest = new ManifestHelper(data.app.manifest,
+                                        data.app.origin,
+                                        data.app.manifestURL);
       if (!manifest.appcache_path) {
         this.installations[manifestURL].resolve();
       }
     } else if (aMessage.name == "Webapps:Install:Return:KO") {
       this.installations[manifestURL].reject(data.error);
     }
   },
 
@@ -167,17 +169,18 @@ this.WebappManager = {
               throw ex;
             }
           })
         );
       }
     };
 
     let requestingURI = chromeWin.makeURI(aData.from);
-    let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
+    let app = aData.app;
+    let manifest = new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
 
     let host;
     try {
       host = requestingURI.host;
     } catch(e) {
       host = requestingURI.spec;
     }
 
--- a/dom/activities/src/ActivitiesService.jsm
+++ b/dom/activities/src/ActivitiesService.jsm
@@ -289,17 +289,20 @@ let Activities = {
         let ids = aResults.options.map((aItem) => {
           return { id: reg._appIdForManifestURL(aItem.manifest) }
         });
 
         reg._readManifests(ids).then((aManifests) => {
           let results = [];
           aManifests.forEach((aManifest, i) => {
             let manifestURL = aResults.options[i].manifest;
-            let helper = new ManifestHelper(aManifest.manifest, manifestURL);
+            // Not passing the origin is fine here since we only need
+            // helper.name which doesn't rely on url resolution.
+            let helper =
+              new ManifestHelper(aManifest.manifest, manifestURL, manifestURL);
             results.push({
               manifestURL: manifestURL,
               iconURL: aResults.options[i].icon,
               appName: helper.name
             });
           });
 
           // Now fire success with the array of choices.
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -19,17 +19,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
   "resource://gre/modules/WebappOSUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 
 // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
 
-this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
+this.EXPORTED_SYMBOLS =
+  ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
 
 function debug(s) {
   //dump("-*- AppsUtils.jsm: " + s + "\n");
 }
 
 this.isAbsoluteURI = function(aURI) {
   let foo = Services.io.newURI("http://foo", null, null);
   let bar = Services.io.newURI("http://bar", null, null);
@@ -368,17 +369,18 @@ this.AppsUtils = {
    * For now, only ensure app can't rename itself.
    */
   ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) {
     // Ensure that app name can't be updated
     aNewManifest.name = aApp.name;
 
     // Nor through localized names
     if ('locales' in aNewManifest) {
-      let defaultName = new ManifestHelper(aOldManifest, aApp.origin).name;
+      let defaultName =
+        new ManifestHelper(aOldManifest, aApp.origin, aApp.manifestURL).name;
       for (let locale in aNewManifest.locales) {
         let entry = aNewManifest.locales[locale];
         if (!entry.name) {
           continue;
         }
         // In case previous manifest didn't had a name,
         // we use the default app name
         let localizedName = defaultName;
@@ -588,21 +590,35 @@ this.AppsUtils = {
     // Convert the binary hash data to a hex string.
     return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
   }
 }
 
 /**
  * Helper object to access manifest information with locale support
  */
-this.ManifestHelper = function(aManifest, aOrigin) {
-  this._origin = Services.io.newURI(aOrigin, null, null);
+this.ManifestHelper = function(aManifest, aOrigin, aManifestURL) {
+  // If the app is packaged, we resolve uris against the origin.
+  // If it's not, against the manifest url.
+
+  if (!aOrigin || !aManifestURL) {
+    throw Error("ManifestHelper needs both origin and manifestURL");
+  }
+
+  this._baseURI = Services.io.newURI(
+    aOrigin.startsWith("app://") ? aOrigin : aManifestURL, null, null);
+
+  // We keep the manifest url in all cases since we need it to
+  // resolve the package path for packaged apps.
+  this._manifestURL = Services.io.newURI(aManifestURL, null, null);
+
   this._manifest = aManifest;
-  let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
-                                                          .QueryInterface(Ci.nsIToolkitChromeRegistry);
+  let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"]
+                 .getService(Ci.nsIXULChromeRegistry)
+                 .QueryInterface(Ci.nsIToolkitChromeRegistry);
   let locale = chrome.getSelectedLocale("global").toLowerCase();
   this._localeRoot = this._manifest;
 
   if (this._manifest.locales && this._manifest.locales[locale]) {
     this._localeRoot = this._manifest.locales[locale];
   }
   else if (this._manifest.locales) {
     // try with the language part of the locale ("en" for en-GB) only
@@ -681,71 +697,71 @@ ManifestHelper.prototype = {
     let iconSizes = Object.keys(icons);
     if (iconSizes.length == 0) {
       return null;
     }
 
     iconSizes.sort((a, b) => a - b);
     let biggestIconSize = iconSizes.pop();
     let biggestIcon = icons[biggestIconSize];
-    let biggestIconURL = this._origin.resolve(biggestIcon);
+    let biggestIconURL = this._baseURI.resolve(biggestIcon);
 
     return biggestIconURL;
   },
 
   iconURLForSize: function(aSize) {
     let icons = this._localeProp("icons");
     if (!icons)
       return null;
     let dist = 100000;
     let icon = null;
     for (let size in icons) {
       let iSize = parseInt(size);
       if (Math.abs(iSize - aSize) < dist) {
-        icon = this._origin.resolve(icons[size]);
+        icon = this._baseURI.resolve(icons[size]);
         dist = Math.abs(iSize - aSize);
       }
     }
     return icon;
   },
 
   fullLaunchPath: function(aStartPoint) {
     // If no start point is specified, we use the root launch path.
     // In all error cases, we just return null.
     if ((aStartPoint || "") === "") {
-      return this._origin.resolve(this._localeProp("launch_path") || "");
+      return this._baseURI.resolve(this._localeProp("launch_path") || "");
     }
 
     // Search for the l10n entry_points property.
     let entryPoints = this._localeProp("entry_points");
     if (!entryPoints) {
       return null;
     }
 
     if (entryPoints[aStartPoint]) {
-      return this._origin.resolve(entryPoints[aStartPoint].launch_path || "");
+      return this._baseURI.resolve(entryPoints[aStartPoint].launch_path || "");
     }
 
     return null;
   },
 
-  resolveFromOrigin: function(aURI) {
+  resolveURL: function(aURI) {
     // This should be enforced higher up, but check it here just in case.
     if (isAbsoluteURI(aURI)) {
-      throw new Error("Webapps.jsm: non-relative URI passed to resolveFromOrigin");
+      throw new Error("Webapps.jsm: non-relative URI passed to resolve");
     }
-    return this._origin.resolve(aURI);
+    return this._baseURI.resolve(aURI);
   },
 
   fullAppcachePath: function() {
     let appcachePath = this._localeProp("appcache_path");
-    return this._origin.resolve(appcachePath ? appcachePath : "");
+    return this._baseURI.resolve(appcachePath ? appcachePath : "");
   },
 
   fullPackagePath: function() {
     let packagePath = this._localeProp("package_path");
-    return this._origin.resolve(packagePath ? packagePath : "");
+    return this._manifestURL.resolve(packagePath ? packagePath : "");
   },
 
   get role() {
     return this._manifest.role || "";
   }
 }
--- a/dom/apps/src/PermissionsInstaller.jsm
+++ b/dom/apps/src/PermissionsInstaller.jsm
@@ -54,17 +54,18 @@ this.PermissionsInstaller = {
    *        Indicates the app was just re-installed
    * @param function aOnError
    *        A function called if an error occurs
    * @returns void
    **/
   installPermissions: function installPermissions(aApp, aIsReinstall, aOnError,
                                                   aIsSystemUpdate) {
     try {
-      let newManifest = new ManifestHelper(aApp.manifest, aApp.origin);
+      let newManifest =
+        new ManifestHelper(aApp.manifest, aApp.origin, aApp.manifestURL);
       if (!newManifest.permissions && !aIsReinstall) {
         return;
       }
 
       if (aIsReinstall) {
         // Compare the original permissions against the new permissions
         // Remove any deprecated Permissions
 
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -384,17 +384,18 @@ this.DOMApplicationRegistry = {
         });
       });
     }
   },
 
   updateOfflineCacheForApp: function(aId) {
     let app = this.webapps[aId];
     this._readManifests([{ id: aId }]).then((aResult) => {
-      let manifest = new ManifestHelper(aResult[0].manifest, app.origin);
+      let manifest =
+        new ManifestHelper(aResult[0].manifest, app.origin, app.manifestURL);
       OfflineCacheInstaller.installCache({
         cachePath: app.cachePath,
         appId: aId,
         origin: Services.io.newURI(app.origin, null, null),
         localId: app.localId,
         appcache_path: manifest.fullAppcachePath()
       });
     });
@@ -670,47 +671,47 @@ this.DOMApplicationRegistry = {
       root = aManifest.entry_points[aEntryPoint];
     }
 
     if (!root.messages || !Array.isArray(root.messages) ||
         root.messages.length == 0) {
       return;
     }
 
-    let manifest = new ManifestHelper(aManifest, aApp.origin);
+    let manifest = new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
     let launchPathURI = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint), null, null);
     let manifestURI = Services.io.newURI(aApp.manifestURL, null, null);
     root.messages.forEach(function registerPages(aMessage) {
       let handlerPageURI = launchPathURI;
       let messageName;
       if (typeof(aMessage) === "object" && Object.keys(aMessage).length === 1) {
         messageName = Object.keys(aMessage)[0];
         let handlerPath = aMessage[messageName];
         // Resolve the handler path from origin. If |handler_path| is absent,
         // simply skip.
         let fullHandlerPath;
         try {
           if (handlerPath && handlerPath.trim()) {
-            fullHandlerPath = manifest.resolveFromOrigin(handlerPath);
+            fullHandlerPath = manifest.resolveURL(handlerPath);
           } else {
             throw new Error("Empty or blank handler path.");
           }
         } catch(e) {
           debug("system message handler path (" + handlerPath + ") is " +
                 "invalid, skipping. Error is: " + e);
           return;
         }
         handlerPageURI = Services.io.newURI(fullHandlerPath, null, null);
       } else {
         messageName = aMessage;
       }
 
       if (SystemMessagePermissionsChecker
             .isSystemMessagePermittedToRegister(messageName,
-                                                aApp.origin,
+                                                aApp.manifestURL,
                                                 aManifest)) {
         msgmgr.registerPage(messageName, handlerPageURI, manifestURI);
       }
     });
   },
 
   // |aEntryPoint| is either the entry_point name or the null in which case we
   // use the root of the manifest.
@@ -728,44 +729,44 @@ this.DOMApplicationRegistry = {
       return;
     }
 
     if ((typeof connections) !== "object") {
       debug("|connections| is not an object. Skipping: " + connections);
       return;
     }
 
-    let manifest = new ManifestHelper(aManifest, aApp.origin);
+    let manifest = new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
     let launchPathURI = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint),
                                            null, null);
     let manifestURI = Services.io.newURI(aApp.manifestURL, null, null);
 
     for (let keyword in connections) {
       let connection = connections[keyword];
 
       // Resolve the handler path from origin. If |handler_path| is absent,
       // use |launch_path| as default.
       let fullHandlerPath;
       let handlerPath = connection.handler_path;
       if (handlerPath) {
         try {
-          fullHandlerPath = manifest.resolveFromOrigin(handlerPath);
+          fullHandlerPath = manifest.resolveURL(handlerPath);
         } catch(e) {
           debug("Connection's handler path is invalid. Skipping: keyword: " +
                 keyword + " handler_path: " + handlerPath);
           continue;
         }
       }
       let handlerPageURI = fullHandlerPath
                            ? Services.io.newURI(fullHandlerPath, null, null)
                            : launchPathURI;
 
       if (SystemMessagePermissionsChecker
             .isSystemMessagePermittedToRegister("connection",
-                                                aApp.origin,
+                                                aApp.manifestURL,
                                                 aManifest)) {
         msgmgr.registerPage("connection", handlerPageURI, manifestURI);
       }
 
       interAppCommService.
         registerConnection(keyword,
                            handlerPageURI,
                            manifestURI,
@@ -807,26 +808,26 @@ this.DOMApplicationRegistry = {
     if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
       root = aManifest.entry_points[aEntryPoint];
     }
 
     if (!root.activities) {
       return activitiesToRegister;
     }
 
-    let manifest = new ManifestHelper(aManifest, aApp.origin);
+    let manifest = new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
     for (let activity in root.activities) {
       let description = root.activities[activity];
       let href = description.href;
       if (!href) {
         href = manifest.launch_path;
       }
 
       try {
-        href = manifest.resolveFromOrigin(href);
+        href = manifest.resolveURL(href);
       } catch (e) {
         debug("Activity href (" + href + ") is invalid, skipping. " +
               "Error is: " + e);
         continue;
       }
 
       // Make a copy of the description object since we don't want to modify
       // the manifest itself, but need to register with a resolved URI.
@@ -846,17 +847,17 @@ this.DOMApplicationRegistry = {
                                     "description": newDesc });
       }
 
       let launchPathURI = Services.io.newURI(href, null, null);
       let manifestURI = Services.io.newURI(aApp.manifestURL, null, null);
 
       if (SystemMessagePermissionsChecker
             .isSystemMessagePermittedToRegister("activity",
-                                                aApp.origin,
+                                                aApp.manifestURL,
                                                 aManifest)) {
         msgmgr.registerPage("activity", launchPathURI, manifestURI);
       }
     }
     return activitiesToRegister;
   },
 
   // |aAppsToRegister| contains an array of apps to be registered, where
@@ -956,17 +957,18 @@ this.DOMApplicationRegistry = {
         let manifest = aResult.manifest;
         if (!manifest) {
           // If we can't load the manifest, we probably have a corrupted
           // registry. We delete the app since we can't do anything with it.
           delete this.webapps[aResult.id];
           return;
         }
 
-        let localeManifest = new ManifestHelper(manifest, app.origin);
+        let localeManifest =
+          new ManifestHelper(manifest, app.origin, app.manifestURL);
 
         app.name = manifest.name;
         app.csp = manifest.csp || "";
         app.role = localeManifest.role;
         if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
           app.redirects = this.sanitizeRedirects(manifest.redirects);
         }
         this._registerSystemMessages(manifest, app);
@@ -1458,17 +1460,18 @@ this.DOMApplicationRegistry = {
                                  true);
 
     if (!file.exists()) {
       // This is a hosted app, let's check if it has an appcache
       // and download it.
       let results = yield this._readManifests([{ id: id }]);
 
       let jsonManifest = results[0].manifest;
-      let manifest = new ManifestHelper(jsonManifest, app.origin);
+      let manifest =
+        new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
 
       if (manifest.appcache_path) {
         debug("appcache found");
         this.startOfflineCacheDownload(manifest, app, null, isUpdate);
       } else {
         // Hosted app with no appcache, nothing to do, but we fire a
         // downloaded event.
         debug("No appcache found, sending 'downloaded' for " + aManifestURL);
@@ -1492,17 +1495,17 @@ this.DOMApplicationRegistry = {
 
     let json = yield AppsUtils.loadJSONAsync(file.path);
     if (!json) {
       debug("startDownload: No update manifest found at " + file.path + " " +
             aManifestURL);
       throw new Error("MISSING_UPDATE_MANIFEST");
     }
 
-    let manifest = new ManifestHelper(json, app.manifestURL);
+    let manifest = new ManifestHelper(json, app.origin, app.manifestURL);
     let newApp = {
       manifestURL: aManifestURL,
       origin: app.origin,
       installOrigin: app.installOrigin,
       downloadSize: app.downloadSize
     };
 
     let newManifest, newId;
@@ -1803,17 +1806,18 @@ this.DOMApplicationRegistry = {
                   requestID: aData.requestID
                 });
               });
             } else {
               sendError("NOT_UPDATABLE");
             }
           }
         };
-        let helper = new ManifestHelper(manifest, aData.manifestURL);
+        let helper =
+          new ManifestHelper(manifest, aData.origin, aData.manifestURL);
         debug("onlyCheckAppCache - launch updateSvc.checkForUpdate for " +
               helper.fullAppcachePath());
         updateSvc.checkForUpdate(Services.io.newURI(helper.fullAppcachePath(), null, null),
                                  app.localId, false, updateObserver);
       });
       return;
     }
 
@@ -1985,17 +1989,18 @@ this.DOMApplicationRegistry = {
   updatePackagedApp: Task.async(function*(aData, aId, aApp, aNewManifest) {
     debug("updatePackagedApp");
 
     // Store the new update manifest.
     let dir = this._getAppDir(aId).path;
     let manFile = OS.Path.join(dir, "staged-update.webapp");
     yield this._writeFile(manFile, JSON.stringify(aNewManifest));
 
-    let manifest = new ManifestHelper(aNewManifest, aApp.manifestURL);
+    let manifest =
+      new ManifestHelper(aNewManifest, aApp.origin, aApp.manifestURL);
     // A package is available: set downloadAvailable to fire the matching
     // event.
     aApp.downloadAvailable = true;
     aApp.downloadSize = manifest.size;
     aApp.updateManifest = aNewManifest;
     yield this._saveApps();
 
     this.broadcastMessage("Webapps:UpdateState", {
@@ -2031,17 +2036,18 @@ this.DOMApplicationRegistry = {
 
       this.notifyUpdateHandlers(AppsUtils.cloneAppObject(aApp), aNewManifest);
 
       // Store the new manifest.
       let dir = this._getAppDir(aId).path;
       let manFile = OS.Path.join(dir, "manifest.webapp");
       yield this._writeFile(manFile, JSON.stringify(aNewManifest));
 
-      manifest = new ManifestHelper(aNewManifest, aApp.origin);
+      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
         }, true);
@@ -2050,17 +2056,18 @@ this.DOMApplicationRegistry = {
       this.updateDataStore(this.webapps[aId].localId, aApp.origin,
                            aApp.manifestURL, aApp.manifest);
 
       aApp.name = aNewManifest.name;
       aApp.csp = manifest.csp || "";
       aApp.role = manifest.role || "";
       aApp.updateTime = Date.now();
     } else {
-      manifest = new ManifestHelper(aOldManifest, aApp.origin);
+      manifest =
+        new ManifestHelper(aOldManifest, aApp.origin, aApp.manifestURL);
     }
 
     // Update the registry.
     this.webapps[aId] = aApp;
     yield this._saveApps();
 
     if (!manifest.appcache_path) {
       this.broadcastMessage("Webapps:UpdateState", {
@@ -2559,17 +2566,18 @@ this.DOMApplicationRegistry = {
     }
 
     let app = this._setupApp(aData, id);
 
     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);
+    let manifest =
+      new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
 
     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) {
@@ -2613,17 +2621,17 @@ this.DOMApplicationRegistry = {
       if (aData.app.localInstallPath) {
         dontNeedNetwork = true;
         jsonManifest.package_path = "file://" + aData.app.localInstallPath;
       }   
 #endif
 
       // origin for install apps is meaningless here, since it's app:// and this
       // can't be used to resolve package paths.
-      manifest = new ManifestHelper(jsonManifest, app.manifestURL);
+      manifest = new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
 
       this.queuedPackageDownload[app.manifestURL] = {
         manifest: manifest,
         app: appObject,
         callback: aInstallSuccessCallback
       };
     }
 
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -165,28 +165,30 @@ this.SystemMessagePermissionsChecker = {
     return object
   },
 
   /**
    * Check if the system message is permitted to be registered for the given
    * app at start-up based on the permissions claimed in the app's manifest.
    * @param string aSysMsgName
    *        The system messsage name.
-   * @param string aOrigin
-   *        The app's origin.
+   * @param string aManifestURL
+   *        The app's manifest URL.
    * @param object aManifest
    *        The app's manifest.
    * @returns bool
    *        Is permitted or not.
    **/
   isSystemMessagePermittedToRegister:
-    function isSystemMessagePermittedToRegister(aSysMsgName, aOrigin, aManifest) {
+    function isSystemMessagePermittedToRegister(aSysMsgName,
+                                                aManifestURL,
+                                                aManifest) {
     debug("isSystemMessagePermittedToRegister(): " +
           "aSysMsgName: " + aSysMsgName + ", " +
-          "aOrigin: " + aOrigin + ", " +
+          "aManifestURL: " + aManifestURL + ", " +
           "aManifest: " + JSON.stringify(aManifest));
 
     let permNames = this.getSystemMessagePermissions(aSysMsgName);
     if (permNames === null) {
       return false;
     }
 
     // Check to see if the 'webapp' is app/privileged/certified.
@@ -202,30 +204,32 @@ this.SystemMessagePermissionsChecker = {
       appStatus = "app";
       break;
     default:
       throw new Error("SystemMessagePermissionsChecker.jsm: " +
                       "Cannot decide the app's status. Install cancelled.");
       break;
     }
 
-    let newManifest = new ManifestHelper(aManifest, aOrigin);
+    // It's ok here to not pass the origin to ManifestHelper since we only
+    // need the permission property and that doesn't depend on uri resolution.
+    let newManifest = new ManifestHelper(aManifest, aManifestURL, aManifestURL);
 
     for (let permName in permNames) {
       // The app doesn't claim valid permissions for this sytem message.
       if (!newManifest.permissions || !newManifest.permissions[permName]) {
         debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " +
-              "Please add the permission for app: '" + aOrigin + "'.");
+              "Please add the permission for app: '" + aManifestURL + "'.");
         return false;
       }
       let permValue = PermissionsTable[permName][appStatus];
       if (permValue != Ci.nsIPermissionManager.PROMPT_ACTION &&
           permValue != Ci.nsIPermissionManager.ALLOW_ACTION) {
         debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " +
-              "Please add the permission for app: '" + aOrigin + "'.");
+              "Please add the permission for app: '" + aManifestURL + "'.");
         return false;
       }
 
       // Compare the expanded permission names between the ones in
       // app's manifest and the ones needed for system message.
       let expandedPermNames =
         expandPermissions(permName,
                           newManifest.permissions[permName].access);
--- a/mobile/android/chrome/content/WebappRT.js
+++ b/mobile/android/chrome/content/WebappRT.js
@@ -78,17 +78,17 @@ let WebappRT = {
   },
 
   getManifestFor: function (aUrl, aCallback) {
     let request = navigator.mozApps.mgmt.getAll();
     request.onsuccess = function() {
       let apps = request.result;
       for (let i = 0; i < apps.length; i++) {
         let app = apps[i];
-        let manifest = new ManifestHelper(app.manifest, app.origin);
+        let manifest = new ManifestHelper(app.manifest, app.origin, app.manifestURL);
 
         // if this is a path to the manifest, or the launch path, then we have a hit.
         if (app.manifestURL == aUrl || manifest.fullLaunchPath() == aUrl) {
           aCallback(manifest, app);
           return;
         }
       }
 
--- a/mobile/android/chrome/content/aboutApps.js
+++ b/mobile/android/chrome/content/aboutApps.js
@@ -100,17 +100,17 @@ function updateList() {
       addApplication(request.result[i]);
     if (request.result.length)
       document.getElementById("main-container").classList.remove("hidden");
   }
 }
 
 function addApplication(aApp) {
   let list = document.getElementById("appgrid");
-  let manifest = new ManifestHelper(aApp.manifest, aApp.origin);
+  let manifest = new ManifestHelper(aApp.manifest, aApp.origin, aApp.manifestURL);
 
   let container = document.createElement("div");
   container.className = "app list-item";
   container.setAttribute("contextmenu", "appmenu");
   container.setAttribute("id", "app-" + aApp.manifestURL);
   container.setAttribute("title", manifest.name);
 
   let img = document.createElement("img");
--- a/mobile/android/modules/WebappManager.jsm
+++ b/mobile/android/modules/WebappManager.jsm
@@ -174,32 +174,33 @@ this.WebappManager = {
   askInstall: function(aData) {
     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
     file.initWithPath(aData.profilePath);
 
     this._deleteAppcachePath(aData.app.manifest);
 
     DOMApplicationRegistry.registryReady.then(() => {
       DOMApplicationRegistry.confirmInstall(aData, file, (function(aApp, aManifest) {
-        this._postInstall(aData.profilePath, aManifest, aData.app.origin, aData.app.apkPackageName);
+        this._postInstall(aData.profilePath, aManifest, aData.app.origin,
+                          aData.app.apkPackageName, aData.app.manifestURL);
       }).bind(this));
     });
   },
 
-  _postInstall: function(aProfilePath, aNewManifest, aOrigin, aApkPackageName) {
+  _postInstall: function(aProfilePath, aNewManifest, aOrigin, aApkPackageName, aManifestURL) {
     // aOrigin may now point to the app: url that hosts this app.
     sendMessageToJava({
       type: "Webapps:Postinstall",
       apkPackageName: aApkPackageName,
       origin: aOrigin,
     });
 
     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
     file.initWithPath(aProfilePath);
-    let localeManifest = new ManifestHelper(aNewManifest, aOrigin);
+    let localeManifest = new ManifestHelper(aNewManifest, aOrigin, aManifestUrl);
     this.writeDefaultPrefs(file, localeManifest);
   },
 
   launch: function({ apkPackageName }) {
     debug("launch: " + apkPackageName);
 
     sendMessageToJava({
       type: "Webapps:Launch",
@@ -328,17 +329,17 @@ this.WebappManager = {
     if (aData.type == "hosted") {
       this._deleteAppcachePath(aData.manifest);
       let oldManifest = yield DOMApplicationRegistry.getManifestFor(aData.manifestURL);
       yield DOMApplicationRegistry.updateHostedApp(aData, aOldApp.id, aOldApp, oldManifest, aData.manifest);
     } else {
       yield this._autoUpdatePackagedApp(aData, aOldApp);
     }
 
-    this._postInstall(aData.profilePath, aData.manifest, aOldApp.origin, aOldApp.apkPackageName);
+    this._postInstall(aData.profilePath, aData.manifest, aOldApp.origin, aOldApp.apkPackageName, aOldApp.manifestURL);
   }).bind(this)); },
 
   _autoUpdatePackagedApp: Task.async(function*(aData, aOldApp) {
     debug("_autoUpdatePackagedApp: " + aData.manifestURL);
 
     if (aData.updateManifest && aData.zipFilePath) {
       aData.updateManifest.package_path = aData.zipFilePath;
     }
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -278,17 +278,18 @@ WebappsActor.prototype = {
         Services.obs.notifyObservers(null, "webapps-installed",
           JSON.stringify({ manifestURL: aApp.manifestURL }));
 
         delete aApp.manifest;
         aDeferred.resolve({ appId: aId, path: aDir.path });
 
         // We can't have appcache for packaged apps.
         if (!aApp.origin.startsWith("app://")) {
-          reg.startOfflineCacheDownload(new ManifestHelper(manifest, aApp.origin));
+          reg.startOfflineCacheDownload(
+            new ManifestHelper(manifest, aApp.origin, aApp.manifestURL));
         }
       });
       // Cleanup by removing the temporary directory.
       if (aDir.exists())
         aDir.remove(true);
     });
   },
 
@@ -742,17 +743,17 @@ WebappsActor.prototype = {
     if (!app) {
       return { error: "wrongParameter",
                message: "No application for " + manifestURL };
     }
 
     let deferred = promise.defer();
 
     this._findManifestByURL(manifestURL).then(jsonManifest => {
-      let manifest = new ManifestHelper(jsonManifest, app.origin);
+      let manifest = new ManifestHelper(jsonManifest, app.origin, manifestURL);
       let iconURL = manifest.iconURLForSize(aRequest.size || 128);
       if (!iconURL) {
         deferred.resolve({
           error: "noIcon",
           message: "This app has no icon"
         });
         return;
       }
--- a/toolkit/webapps/NativeApp.jsm
+++ b/toolkit/webapps/NativeApp.jsm
@@ -52,17 +52,18 @@ const TMP_DIR = OS.Constants.Path.tmpDir
  *
  */
 function CommonNativeApp(aApp, aManifest, aCategories, aRegistryDir) {
   // Set the name property of the app object, otherwise
   // WebappOSUtils::getUniqueName won't work.
   aApp.name = aManifest.name;
   this.uniqueName = WebappOSUtils.getUniqueName(aApp);
 
-  let localeManifest = new ManifestHelper(aManifest, aApp.origin);
+  let localeManifest =
+    new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
 
   this.appLocalizedName = localeManifest.name;
   this.appNameAsFilename = stripStringForFilename(aApp.name);
 
   if (aApp.updateManifest) {
     this.isPackaged = true;
   }
 
@@ -94,17 +95,17 @@ CommonNativeApp.prototype = {
   /**
    * This function reads and parses the data from the app
    * manifest and stores it in the NativeApp object.
    *
    * @param aManifest {Object} the manifest data provided by the web app
    *
    */
   _setData: function(aApp, aManifest) {
-    let manifest = new ManifestHelper(aManifest, aApp.origin);
+    let manifest = new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
     let origin = Services.io.newURI(aApp.origin, null, null);
 
     this.iconURI = Services.io.newURI(manifest.biggestIconURL || DEFAULT_ICON_URL,
                                       null, null);
 
     if (manifest.developer) {
       if (manifest.developer.name) {
         let devName = manifest.developer.name.substr(0, 128);
--- a/webapprt/WebappManager.jsm
+++ b/webapprt/WebappManager.jsm
@@ -42,17 +42,18 @@ this.WebappManager = {
     let nativeApp = new NativeApp(aApp, aManifest,
                                   WebappRT.config.app.categories,
                                   WebappRT.config.registryDir);
     nativeApp.prepareUpdate(aApp, aManifest, aZipPath);
   },
 
   doInstall: function(data, window) {
     let jsonManifest = data.isPackage ? data.app.updateManifest : data.app.manifest;
-    let manifest = new ManifestHelper(jsonManifest, data.app.origin);
+    let manifest =
+      new ManifestHelper(jsonManifest, data.app.origin, data.app.manifestURL);
     let name = manifest.name;
     let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties");
 
     let choice = Services.prompt.confirmEx(
       window,
       bundle.formatStringFromName("webapps.install.title", [name], 1),
       bundle.formatStringFromName("webapps.install.description", [name], 1),
       // Set both buttons to strings with the cancel button being default
--- a/webapprt/WebappRT.jsm
+++ b/webapprt/WebappRT.jsm
@@ -43,17 +43,18 @@ this.WebappRT = {
   },
 
   get launchURI() {
     return this.localeManifest.fullLaunchPath();
   },
 
   get localeManifest() {
     return new ManifestHelper(this.config.app.manifest,
-                              this.config.app.origin);
+                              this.config.app.origin,
+                              this.config.app.manifestURL);
   },
 
   get appID() {
     let manifestURL = this.config.app.manifestURL;
     if (!manifestURL) {
       return Ci.nsIScriptSecurityManager.NO_APP_ID;
     }