Bug 773891 - With AppsService changes
authorCarmen Jimenez Cabezas <mcjimenez@gfi.es>
Fri, 19 Oct 2012 12:43:17 +0200
changeset 111223 f0dc54155f65fe63e0392233f6c0c78416deb58d
parent 111222 e2594523cb92d7ea78eb45cc819b93facf664724
child 111224 40e9ab16d3b23f7e0bb173f78089bf7f754c3032
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
bugs773891
milestone19.0a1
Bug 773891 - With AppsService changes
build/automation.py.in
content/base/src/nsDocument.cpp
content/base/test/Makefile.in
content/base/test/chrome/Makefile.in
content/base/test/chrome/test_csp_bug773891.html
content/base/test/file_csp_bug773891.html
content/base/test/file_csp_bug773891.sjs
dom/apps/src/AppsService.js
dom/apps/src/AppsServiceChild.jsm
dom/apps/src/AppsUtils.jsm
dom/apps/src/Webapps.jsm
dom/interfaces/apps/mozIApplication.idl
dom/interfaces/apps/nsIAppsService.idl
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -299,21 +299,23 @@ class Automation(object):
   def setupTestApps(self, profileDir, apps):
     webappJSONTemplate = Template(""""$name": {
 "origin": "$origin",
 "installOrigin": "$origin",
 "receipt": null,
 "installTime": 132333986000,
 "manifestURL": "$manifestURL",
 "localId": $localId,
-"appStatus": $appStatus
+"appStatus": $appStatus,
+"csp": "$csp"
 }""")
 
     manifestTemplate = Template("""{
   "name": "$name",
+  "csp": "$csp",
   "description": "$description",
   "launch_path": "/",
   "developer": {
     "name": "Mozilla",
     "url": "https://mozilla.org/"
   },
   "permissions": [
   ],
@@ -525,63 +527,94 @@ user_pref("camino.use_system_proxy_setti
     # write the preferences
     prefsFile = open(profileDir + "/" + "user.js", "a")
     prefsFile.write("".join(prefs))
     prefsFile.close()
 
     apps = [
       {
         'name': 'http_example_org',
+        'csp': '',
         'origin': 'http://example.org',
         'manifestURL': 'http://example.org/manifest.webapp',
         'description': 'http://example.org App',
         'appStatus': _APP_STATUS_INSTALLED
       },
       {
         'name': 'https_example_com',
+        'csp': '',
         'origin': 'https://example.com',
         'manifestURL': 'https://example.com/manifest.webapp',
         'description': 'https://example.com App',
         'appStatus': _APP_STATUS_INSTALLED
       },
       {
         'name': 'http_test1_example_org',
+        'csp': '',
         'origin': 'http://test1.example.org',
         'manifestURL': 'http://test1.example.org/manifest.webapp',
         'description': 'http://test1.example.org App',
         'appStatus': _APP_STATUS_INSTALLED
       },
       {
         'name': 'http_test1_example_org_8000',
+        'csp': '',
         'origin': 'http://test1.example.org:8000',
         'manifestURL': 'http://test1.example.org:8000/manifest.webapp',
         'description': 'http://test1.example.org:8000 App',
         'appStatus': _APP_STATUS_INSTALLED
       },
       {
         'name': 'http_sub1_test1_example_org',
+        'csp': '',
         'origin': 'http://sub1.test1.example.org',
         'manifestURL': 'http://sub1.test1.example.org/manifest.webapp',
         'description': 'http://sub1.test1.example.org App',
         'appStatus': _APP_STATUS_INSTALLED
       },
       {
         'name': 'https_example_com_privileged',
+        'csp': '',
         'origin': 'https://example.com',
         'manifestURL': 'https://example.com/manifest_priv.webapp',
         'description': 'https://example.com Privileged App',
         'appStatus': _APP_STATUS_PRIVILEGED
       },
       {
         'name': 'https_example_com_certified',
+        'csp': '',
         'origin': 'https://example.com',
         'manifestURL': 'https://example.com/manifest_cert.webapp',
         'description': 'https://example.com Certified App',
         'appStatus': _APP_STATUS_CERTIFIED
       },
+      {
+        'name': 'https_example_csp_certified',
+        'csp': "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
+        'origin': 'https://example.com',
+        'manifestURL': 'https://example.com/manifest_csp_cert.webapp',
+        'description': 'https://example.com Certified App with manifest policy',
+        'appStatus': _APP_STATUS_CERTIFIED
+      }, 
+      {
+        'name': 'https_example_csp_installed',
+        'csp': "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
+        'origin': 'https://example.com',
+        'manifestURL': 'https://example.com/manifest_csp_inst.webapp',
+        'description': 'https://example.com Installed App with manifest policy',
+        'appStatus': _APP_STATUS_INSTALLED
+      }, 
+      {
+        'name': 'https_example_csp_privileged',
+        'csp': "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
+        'origin': 'https://example.com',
+        'manifestURL': 'https://example.com/manifest_csp_priv.webapp',
+        'description': 'https://example.com Privileged App with manifest policy',
+        'appStatus': _APP_STATUS_PRIVILEGED
+      }, 
     ];
     self.setupTestApps(profileDir, apps)
 
   def addCommonOptions(self, parser):
     "Adds command-line options which are common to mochitest and reftest."
 
     parser.add_option("--setpref",
                       action = "append", type = "string",
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -165,16 +165,17 @@
 #include "nsXULAppAPI.h"
 #include "nsDOMTouchEvent.h"
 
 #include "mozilla/Preferences.h"
 
 #include "imgILoader.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsSandboxFlags.h"
+#include "nsIAppsService.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::directionality;
 
 typedef nsTArray<Link*> LinkArray;
 
 // Reference to the document which requested DOM full-screen mode.
@@ -2271,16 +2272,32 @@ nsDocument::InitCSP(nsIChannel* aChannel
   nsIPrincipal* principal = NodePrincipal();
   uint16_t appStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
   bool unknownAppId;
   if (NS_SUCCEEDED(principal->GetUnknownAppId(&unknownAppId)) &&
       !unknownAppId &&
       NS_SUCCEEDED(principal->GetAppStatus(&appStatus))) {
     applyAppDefaultCSP = ( appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED ||
                            appStatus == nsIPrincipal::APP_STATUS_CERTIFIED);
+
+    // Bug 773981. Allow a per-app policy from the manifest.
+    // Just read the CSP from the manifest into cspHeaderValue.
+    // That way we don't have to change the rest of the function logic
+    if (applyAppDefaultCSP || appStatus == nsIPrincipal::APP_STATUS_INSTALLED) {
+      nsCOMPtr<nsIAppsService> appsService =
+        do_GetService(APPS_SERVICE_CONTRACTID);
+
+      if (appsService)  {
+        uint32_t appId;
+
+        if ( NS_SUCCEEDED(principal->GetAppId(&appId)) ) {
+          appsService->GetCSPByLocalId(appId, cspHeaderValue);
+        }
+      }
+    }
   }
 #ifdef PR_LOGGING
   else
     PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("Failed to get app status from principal"));
 #endif
 
   // If there's no CSP to apply go ahead and return early
   if (!applyAppDefaultCSP &&
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -368,16 +368,18 @@ MOCHITEST_FILES_B = \
 		file_CSP_inlinescript_main.html \
 		file_CSP_inlinescript_main.html^headers^ \
 		test_CSP_evalscript.html \
 		file_CSP_evalscript_main.html \
 		file_CSP_evalscript_main.html^headers^ \
 		file_CSP_evalscript_main.js \
 		file_csp_bug768029.html \
 		file_csp_bug768029.sjs \
+		file_csp_bug773891.html \
+		file_csp_bug773891.sjs \
 		test_bug540854.html \
 		bug540854.sjs \
 		test_bug548463.html \
 		test_bug545644.html \
 		test_bug545644.xhtml \
 		test_bug553896.xhtml \
 		test_bug515401.html \
 		test_bug541937.html \
--- a/content/base/test/chrome/Makefile.in
+++ b/content/base/test/chrome/Makefile.in
@@ -43,11 +43,12 @@ MOCHITEST_CHROME_FILES = \
     test_bug750096.html \
     test_bug752226-3.xul \
     test_bug752226-4.xul \
     test_bug682305.html \
     test_bug780199.xul \
     test_bug780529.xul \
     test_csp_bug768029.html \
     test_bug800386.xul \
+    test_csp_bug773891.html \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/base/test/chrome/test_csp_bug773891.html
@@ -0,0 +1,228 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=768029
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for CSP on trusted/certified and installed apps -- bug 773891</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=773891">Mozilla Bug 773891</a>
+<p id="display"></p>
+<div id="content">
+
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/** Test for Bug 773891 **/
+
+// Note: we don't have to inspect all the different operations of CSP,
+// we're just looking for specific differences in behavior that indicate
+// a default CSP got applied.
+const DEFAULT_CSP_PRIV = "default-src *; script-src *; style-src 'self' 'unsafe-inline'; object-src 'none'";
+const DEFAULT_CSP_CERT = "default-src *; script-src *; style-src 'self'; object-src 'none'";
+
+const MANIFEST_CSP_PRIV = "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'";
+const MANIFEST_CSP_INST = "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'";
+const MANIFEST_CSP_CERT = "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'";
+
+SimpleTest.waitForExplicitFinish();
+
+var gData = [
+
+  {
+    app: "https://example.com/manifest_csp_inst.webapp",
+    appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_INSTALLED,
+    csp: MANIFEST_CSP_INST,
+    origin: "https://example.com",
+    uri: "https://example.com/tests/content/base/test/file_csp_bug773891.html",
+    statusString: "installed app",
+    expectedTestResults: {
+      max_tests: 7, /* number of bools below plus one for the status check */
+      cross_origin: { img: true,  script: false, style: false },
+      same_origin:  { img: true,  script: true,  style: true  },
+    },
+  },
+  {
+    app: "https://example.com/manifest_csp_cert.webapp",
+    appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_CERTIFIED,
+    csp: MANIFEST_CSP_CERT,
+    origin: "https://example.com",
+    uri: "https://example.com/tests/content/base/test/file_csp_bug773891.html",
+    statusString: "certified app",
+    expectedTestResults: {
+      max_tests: 7, /* number of bools below plus one for the status check */
+      cross_origin: { img: true,  script: false, style: false },
+      same_origin:  { img: true,  script: true,  style: true  },
+    },
+  },
+  {
+    app: "https://example.com/manifest_csp_priv.webapp",
+    appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED,
+    csp: MANIFEST_CSP_PRIV,
+    origin: "https://example.com",
+    uri: "https://example.com/tests/content/base/test/file_csp_bug773891.html",
+    statusString: "privileged app",
+    expectedTestResults: {
+      max_tests: 7, /* number of bools below plus one for the status check */
+      cross_origin: { img: true,  script: false, style: false },
+      same_origin:  { img: true,  script: true,  style: true  },
+    },
+  },
+];
+
+// Observer for watching allowed loads and blocked attempts
+function ThingyListener(app, iframe) {
+  Services.obs.addObserver(this, "csp-on-violate-policy", false);
+  Services.obs.addObserver(this, "http-on-modify-request", false);
+  dump("added observers\n");
+  // keep track of which app ID this test is monitoring.
+  this._testData = app;
+  this._expectedResults = app.expectedTestResults;
+  this._resultsRecorded = { cross_origin: {}, same_origin: {}};
+  this._iframe = iframe;
+  this._countedTests = 0;
+}
+ThingyListener.prototype = {
+
+  observe: function(subject, topic, data) {
+    // make sure to only observe app-generated calls to the helper for this test.
+    var testpat = new RegExp("file_csp_bug773891\\.sjs");
+
+    // used to extract which kind of load this is (img, script, etc).
+    var typepat = new RegExp("type=([\\_a-z0-9]+)");
+
+    // used to identify whether it's cross-origin or same-origin loads
+    // (the applied CSP allows same-origin loads).
+    var originpat = new RegExp("origin=([\\_a-z0-9]+)");
+
+    if (topic === "http-on-modify-request") {
+      // Matching requests on this topic were allowed by the csp
+      var chan = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
+      var uri = chan.URI;
+      // ignore irrelevent URIs
+      if (!testpat.test(uri.asciiSpec)) return;
+
+      var loadType = typepat.exec(uri.asciiSpec)[1];
+      var originType = originpat.exec(uri.asciiSpec)[1];
+
+      // skip duplicate hits to this topic (potentially document loads
+      // may generate duplicate loads.
+      if (this._resultsRecorded[originType] &&
+          this._resultsRecorded[originType][loadType]) {
+        return;
+      }
+      var message = originType + " : " + loadType + " should be " +
+                    (this._expectedResults[originType][loadType] ? "allowed" : "blocked");
+      ok(this._expectedResults[originType][loadType] == true, message);
+      this._resultsRecorded[originType][loadType] = true;
+      this._countedTests++;
+    }
+    else if (topic === "csp-on-violate-policy") {
+      // Matching hits on this topic were blocked by the csp
+      var uri = subject.QueryInterface(Components.interfaces.nsIURI);
+      // ignore irrelevent URIs
+      if (!testpat.test(uri.asciiSpec)) return;
+
+      var loadType = typepat.exec(uri.asciiSpec)[1];
+      var originType = originpat.exec(uri.asciiSpec)[1];
+
+      // skip duplicate hits to this topic (potentially document loads
+      // may generate duplicate loads.
+      if (this._resultsRecorded[originType] &&
+          this._resultsRecorded[originType][loadType]) {
+        return;
+      }
+
+      var message = originType + " : " + loadType + " should be " +
+                    (this._expectedResults[originType][loadType] ? "allowed" : "blocked");
+      ok(this._expectedResults[originType][loadType] == false, message);
+      this._resultsRecorded[originType][loadType] = true;
+      this._countedTests++;
+    }
+    else {
+      // wrong topic!  Nothing to do.
+      return;
+    }
+
+    this._checkForFinish();
+  },
+
+  _checkForFinish: function() {
+    // check to see if there are load tests still pending.
+    // (All requests triggered by the app should hit one of the
+    // two observer topics.)
+    if (this._countedTests == this._expectedResults.max_tests) {
+      Services.obs.removeObserver(this, "csp-on-violate-policy");
+      Services.obs.removeObserver(this, "http-on-modify-request");
+      dump("removed observers\n");
+      checkedCount++;
+      if (checkedCount == checksTodo) {
+        SpecialPowers.removePermission("browser", "https://example.com");
+        SimpleTest.finish();
+      } else {
+        gTestRunner.next();
+      }
+    }
+  },
+
+  // verify the status of the app
+  checkAppStatus: function() {
+    var principal = this._iframe.contentDocument.nodePrincipal;
+    if (this._testData.app) {
+      is(principal.appStatus, this._testData.appStatus,
+         "iframe principal's app status doesn't match the expected app status.");
+      this._countedTests++;
+      this._checkForFinish();
+    }
+  }
+}
+
+var content = document.getElementById('content');
+var checkedCount = 0; // number of apps checked
+var checksTodo = gData.length;
+
+// quick check to make sure we can test apps:
+is('appStatus' in document.nodePrincipal, true,
+   'appStatus should be present in nsIPrincipal, if not the rest of this test will fail');
+
+function runTest() {
+  for (var i = 0; i < gData.length; i++) {
+    let data = gData[i];
+    var iframe = document.createElement('iframe');
+
+    // watch for successes and failures
+    var examiner = new ThingyListener(data, iframe);
+
+    iframe.setAttribute('mozapp', data.app);
+    iframe.setAttribute('mozbrowser', 'true');
+    iframe.addEventListener('load', examiner.checkAppStatus.bind(examiner));
+    iframe.src = data.uri;
+
+    content.appendChild(iframe);
+
+    yield;
+  }
+}
+
+var gTestRunner = runTest();
+
+// load the default CSP and pref it on
+SpecialPowers.addPermission("browser", true, "https://example.com");
+
+SpecialPowers.pushPrefEnv({'set': [["dom.mozBrowserFramesEnabled", true],
+                                   ["security.apps.privileged.CSP.default", DEFAULT_CSP_PRIV],
+                                   ["security.apps.certified.CSP.default", DEFAULT_CSP_CERT]]},
+                          function() {  gTestRunner.next(); });
+
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_csp_bug773891.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=773891
+-->
+<head>
+  <meta charset="utf-8">
+    <title>This is an app for csp testing</title>
+
+    <link rel="stylesheet" type="text/css"
+          href="file_csp_bug773891.sjs?type=style&origin=same_origin" />
+    <link rel="stylesheet" type="text/css"
+          href="http://example.com/tests/content/base/test/file_csp_bug773891.sjs?type=style&origin=cross_origin" />
+  </head>
+  <body>
+
+    <script src="file_csp_bug773891.sjs?type=script&origin=same_origin"></script>
+    <script src="http://example.com/tests/content/base/test/file_csp_bug773891.sjs?type=script&origin=cross_origin"></script>
+    <img src="file_csp_bug773891.sjs?type=img&origin=same_origin" />
+    <img src="http://example.com/tests/content/base/test/file_csp_bug773891.sjs?type=img&origin=cross_origin" />
+
+    Test for CSP applied to (simulated) app.
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_csp_bug773891.sjs
@@ -0,0 +1,29 @@
+function handleRequest(request, response) {
+
+  var query = {};
+
+  request.queryString.split('&').forEach(function(val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+  response.setHeader("Cache-Control", "no-cache", false);
+
+  if ("type" in query) {
+    switch (query.type) {
+      case "script":
+        response.setHeader("Content-Type", "application/javascript");
+        response.write("\n\ndocument.write('<pre>script loaded\\n</pre>');\n\n");
+        return;
+      case "style":
+        response.setHeader("Content-Type", "text/css");
+        response.write("\n\n.cspfoo { color:red; }\n\n");
+        return;
+      case "img":
+        response.setHeader("Content-Type", "image/png");
+        return;
+    }
+  }
+
+  response.setHeader("Content-Type", "text/plain");
+  response.write("ohnoes!");
+}
--- a/dom/apps/src/AppsService.js
+++ b/dom/apps/src/AppsService.js
@@ -22,16 +22,22 @@ function AppsService()
   let inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
                    .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
   debug("inParent: " + inParent);
   Cu.import(inParent ? "resource://gre/modules/Webapps.jsm" :
                        "resource://gre/modules/AppsServiceChild.jsm");
 }
 
 AppsService.prototype = {
+
+  getCSPByLocalId: function getCSPByLocalId(localId) {
+    debug("GetCSPByLocalId( " + localId + " )");
+    return DOMApplicationRegistry.getCSPByLocalId(localId);
+  },
+
   getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
     debug("GetAppByManifestURL( " + aManifestURL + " )");
     return DOMApplicationRegistry.getAppByManifestURL(aManifestURL);
   },
 
   getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
     debug("getAppLocalIdByManifestURL( " + aManifestURL + " )");
     return DOMApplicationRegistry.getAppLocalIdByManifestURL(aManifestURL);
--- a/dom/apps/src/AppsServiceChild.jsm
+++ b/dom/apps/src/AppsServiceChild.jsm
@@ -63,16 +63,21 @@ let DOMApplicationRegistry = {
     return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
   },
 
   getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
     debug("getAppLocalIdByManifestURL " + aManifestURL);
     return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
   },
 
+  getCSPByLocalId: function(aLocalId) {
+    debug("getCSPByLocalId:" + aLocalId);
+    return AppsUtils.getCSPByLocalId(this.webapps, aLocalId);
+  },
+
   getAppByLocalId: function getAppByLocalId(aLocalId) {
     debug("getAppByLocalId " + aLocalId);
     return AppsUtils.getAppByLocalId(this.webapps, aLocalId);
   },
 
   getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
     debug("getManifestURLByLocalId " + aLocalId);
     return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -20,16 +20,17 @@ function debug(s) {
   //dump("-*- AppsUtils.jsm: " + s + "\n");
 }
 
 let AppsUtils = {
   // Clones a app, without the manifest.
   cloneAppObject: function cloneAppObject(aApp) {
     return {
       name: aApp.name,
+      csp: aApp.csp,
       installOrigin: aApp.installOrigin,
       origin: aApp.origin,
       receipts: aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null,
       installTime: aApp.installTime,
       manifestURL: aApp.manifestURL,
       appStatus: aApp.appStatus,
       removable: aApp.removable,
       localId: aApp.localId,
@@ -85,16 +86,28 @@ let AppsUtils = {
       if (aApps[id].manifestURL == aManifestURL) {
         return aApps[id].localId;
       }
     }
 
     return Ci.nsIScriptSecurityManager.NO_APP_ID;
   },
 
+  getCSPByLocalId: function getCSPByLocalId(aApps, aLocalId) {
+    debug("getCSPByLocalId " + aLocalId);
+    for (let id in aApps) {
+      let app = aApps[id];
+      if (app.localId == aLocalId) {
+	  return ( app.csp || "" );
+      }
+    }
+
+    return "";
+  },
+
   getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
     debug("getAppByLocalId " + aLocalId);
     for (let id in aApps) {
       let app = aApps[id];
       if (app.localId == aLocalId) {
         return this.cloneAsMozIApplication(app);
       }
     }
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -134,23 +134,32 @@ let DOMApplicationRegistry = {
   notifyAppsRegistryReady: function notifyAppsRegistryReady() {
     Services.obs.notifyObservers(this, "webapps-registry-ready", null);
     this._saveApps();
   },
 
   // Registers all the activities and system messages.
   registerAppsHandlers: function registerAppsHandlers() {
     this.notifyAppsRegistryStart();
-#ifdef MOZ_SYS_MSG
     let ids = [];
     for (let id in this.webapps) {
       ids.push({ id: id });
     }
+#ifdef MOZ_SYS_MSG
     this._processManifestForIds(ids);
 #else
+    // Read the CSPs. If MOZ_SYS_MSG is defined this is done on
+    // _processManifestForIds so as to not reading the manifests
+    // twice
+    this._readManifests(ids, (function readCSPs(aResults) {
+      aResults.forEach(function registerManifest(aResult) {
+        this.webapps[aResult.id].csp = manifest.csp || "";
+      }, this);
+    }).bind(this));
+
     // Nothing else to do but notifying we're ready.
     this.notifyAppsRegistryReady();
 #endif
   },
 
   updatePermissionsForApp: function updatePermissionsForApp(aId) {
     // Install the permissions for this app, as if we were updating
     // to cleanup the old ones if needed.
@@ -414,16 +423,17 @@ let DOMApplicationRegistry = {
 
   _processManifestForIds: function(aIds) {
     this._readManifests(aIds, (function registerManifests(aResults) {
       let appsToRegister = [];
       aResults.forEach(function registerManifest(aResult) {
         let app = this.webapps[aResult.id];
         let manifest = aResult.manifest;
         app.name = manifest.name;
+        app.csp = manifest.csp || "";
         this._registerSystemMessages(manifest, app);
         appsToRegister.push({ manifest: manifest, app: app });
       }, this);
       this._registerActivitiesForApps(appsToRegister);
     }).bind(this));
   },
 #endif
 
@@ -879,16 +889,17 @@ let DOMApplicationRegistry = {
       } else {
         app.installState = "installed";
         app.downloadAvailable = false;
         app.downloading = false;
         app.readyToApplyDownload = false;
       }
 
       app.name = aManifest.name;
+      app.csp = aManifest.csp || "";
 
       // Update the registry.
       this.webapps[id] = app;
 
       this._saveApps((function() {
         // XXX Should we fire notifications ?
       }).bind(this));
 
@@ -1032,16 +1043,17 @@ let DOMApplicationRegistry = {
     } else {
       appObject.installState = "installed";
       appObject.downloadAvailable = false;
       appObject.downloading = false;
       appObject.readyToApplyDownload = false;
     }
 
     appObject.name = manifest.name;
+    appObject.csp = manifest.csp || "";
 
     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) {
       PermissionsInstaller.installPermissions(aData.app, isReinstall, (function() {
         this.uninstall(aData, aData.mm);
@@ -1501,16 +1513,21 @@ let DOMApplicationRegistry = {
     let app = AppsUtils.cloneAppObject(this.webapps[aId]);
     return app;
   },
 
   getAppByManifestURL: function(aManifestURL) {
     return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
   },
 
+  getCSPByLocalId: function(aLocalId) {
+    debug("getCSPByLocalId:" + aLocalId);
+    return AppsUtils.getCSPByLocalId(this.webapps, aLocalId);
+  },
+
   getAppByLocalId: function(aLocalId) {
     return AppsUtils.getAppByLocalId(this.webapps, aLocalId);
   },
 
   getManifestURLByLocalId: function(aLocalId) {
     return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
   },
 
--- a/dom/interfaces/apps/mozIApplication.idl
+++ b/dom/interfaces/apps/mozIApplication.idl
@@ -6,23 +6,26 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMApplicationRegistry.idl"
 
 /**
  * We expose Gecko-internal helpers related to "web apps" through this
  * sub-interface.
  */
-[scriptable, uuid(efe22f80-f973-11e1-8a00-b7888ac0d2b9)]
+[scriptable, uuid(8ac7827f-f982-40fb-be11-ba16dd665635)]
 interface mozIApplication: mozIDOMApplication
 {
   /* Return true if this app has |permission|. */
   boolean hasPermission(in string permission);
 
   /* Application status as defined in nsIPrincipal. */
   readonly attribute unsigned short appStatus;
 
   /* Returns the local id of the app (not the uuid used for sync). */
   readonly attribute unsigned long localId;
 
   /* Name copied from the manifest */
   readonly attribute DOMString name;
+
+  /* CSP copied from the manifest */
+  readonly attribute DOMString csp;
 };
--- a/dom/interfaces/apps/nsIAppsService.idl
+++ b/dom/interfaces/apps/nsIAppsService.idl
@@ -11,17 +11,17 @@ interface mozIApplication;
 #define APPS_SERVICE_CID { 0x05072afa, 0x92fe, 0x45bf, { 0xae, 0x22, 0x39, 0xb6, 0x9c, 0x11, 0x70, 0x58 } }
 #define APPS_SERVICE_CONTRACTID "@mozilla.org/AppsService;1"
 %}
 
 /*
  * This service allows accessing some DOMApplicationRegistry methods from
  * non-javascript code.
  */
-[scriptable, uuid(1f0ec00c-57c7-4ad2-a648-1359aa390360)]
+[scriptable, uuid(4a182c18-dbdf-4f9c-93a0-0f0cffb88ed0)]
 interface nsIAppsService : nsISupports
 {
   mozIDOMApplication getAppByManifestURL(in DOMString manifestURL);
 
   /**
    * Returns the |localId| of the app associated with the |manifestURL| passed
    * in parameter.
    * Returns nsIScriptSecurityManager::NO_APP_ID if |manifestURL| isn't a valid
@@ -40,9 +40,14 @@ interface nsIAppsService : nsISupports
   DOMString getManifestURLByLocalId(in unsigned long localId);
 
   /**
    * Returns the app that is related to the message.
    * This is a helper to not have to worry about what is the actual structure
    * of the message when listening to one.
    */
   mozIApplication getAppFromObserverMessage(in DOMString message);
+
+  /**
+   * Returns the CSP associated to this localId.
+   */
+  DOMString getCSPByLocalId(in unsigned long localId);
 };