Bug 773891 - With AppsService changes
authorCarmen Jimenez Cabezas <mcjimenez@gfi.es>
Fri, 19 Oct 2012 12:43:17 +0200
changeset 119026 f0dc54155f65fe63e0392233f6c0c78416deb58d
parent 119025 e2594523cb92d7ea78eb45cc819b93facf664724
child 119027 40e9ab16d3b23f7e0bb173f78089bf7f754c3032
push id1997
push userakeybl@mozilla.com
push dateMon, 07 Jan 2013 21:25:26 +0000
treeherdermozilla-beta@4baf45cdcf21 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs773891
milestone19.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 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);
 };