Bug 863246 - Content can only load resource:// URIs declared content-accessible in manifests r=billm,bz draft
authorChung-Sheng Fu <cfu@mozilla.com>
Thu, 08 Jun 2017 17:44:09 +0800
changeset 654062 83000448abf58b7956c2eb122604d7ab38ad0f7c
parent 653766 d10c97627b51a226e19d0fa801201897fe1932f6
child 654063 888f7ce8f9db809ad5abbe6340af74e9dfb1d73b
push id76471
push userbmo:cfu@mozilla.com
push dateMon, 28 Aug 2017 08:30:33 +0000
reviewersbillm, bz
bugs863246
milestone57.0a1
Bug 863246 - Content can only load resource:// URIs declared content-accessible in manifests r=billm,bz bz: caps/nsScriptSecurityManager.cpp billm: browser/extensions/activity-stream/jar.mn browser/extensions/onboarding/jar.mn chrome/RegistryMessageUtils.h chrome/nsChromeRegistry.h chrome/nsChromeRegistryChrome.cpp chrome/nsChromeRegistryContent.cpp netwerk/protocol/res/SubstitutingProtocolHandler.cpp netwerk/protocol/res/SubstitutingProtocolHandler.h netwerk/protocol/res/nsIResProtocolHandler.idl netwerk/protocol/res/nsISubstitutingProtocolHandler.idl netwerk/protocol/res/nsResProtocolHandler.cpp netwerk/protocol/res/nsResProtocolHandler.h xpcom/components/ManifestParser.cpp MozReview-Commit-ID: 1RXeNn7jdBf
browser/extensions/activity-stream/jar.mn
browser/extensions/onboarding/jar.mn
browser/extensions/shield-recipe-client/jar.mn
caps/nsScriptSecurityManager.cpp
chrome/RegistryMessageUtils.h
chrome/nsChromeRegistryChrome.cpp
chrome/nsChromeRegistryContent.cpp
netwerk/protocol/res/SubstitutingProtocolHandler.cpp
netwerk/protocol/res/SubstitutingProtocolHandler.h
netwerk/protocol/res/nsIResProtocolHandler.idl
netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
netwerk/protocol/res/nsResProtocolHandler.cpp
netwerk/protocol/res/nsResProtocolHandler.h
xpcom/components/ManifestParser.cpp
--- a/browser/extensions/activity-stream/jar.mn
+++ b/browser/extensions/activity-stream/jar.mn
@@ -1,14 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [features/activity-stream@mozilla.org] chrome.jar:
-% resource activity-stream %content/
+% resource activity-stream %content/ contentaccessible=yes
   content/lib/ (./lib/*)
   content/common/ (./common/*)
   content/vendor/Redux.jsm (./vendor/Redux.jsm)
   content/vendor/react.js (./vendor/react.js)
   content/vendor/react-dom.js (./vendor/react-dom.js)
   content/vendor/react-intl.js (./vendor/react-intl.js)
   content/vendor/redux.js (./vendor/redux.js)
   content/vendor/react-redux.js (./vendor/react-redux.js)
--- a/browser/extensions/onboarding/jar.mn
+++ b/browser/extensions/onboarding/jar.mn
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [features/onboarding@mozilla.org] chrome.jar:
-% resource onboarding %content/
+  # resource://onboarding/ is referenced in about:home and about:newtab,
+  # so make it content-accessible.
+% resource onboarding %content/ contentaccessible=yes
   content/ (content/*)
   # Package UITour-lib.js in here rather than under
   # /browser/components/uitour to avoid "unreferenced files" error when
   # Onboarding extension is not built.
   content/lib/UITour-lib.js (/browser/components/uitour/UITour-lib.js)
   content/modules/ (*.jsm)
--- a/browser/extensions/shield-recipe-client/jar.mn
+++ b/browser/extensions/shield-recipe-client/jar.mn
@@ -2,12 +2,12 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [features/shield-recipe-client@mozilla.org] chrome.jar:
 % resource shield-recipe-client %
   lib/ (./lib/*)
   data/ (./data/*)
   skin/  (skin/*)
-% resource shield-recipe-client-content %content/
+% resource shield-recipe-client-content %content/ contentaccessible=yes
   content/ (./content/*)
-% resource shield-recipe-client-vendor %vendor/
+% resource shield-recipe-client-vendor %vendor/ contentaccessible=yes
   vendor/ (./vendor/*)
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -48,16 +48,17 @@
 #include "nsIOService.h"
 #include "nsIContent.h"
 #include "nsDOMJSUtils.h"
 #include "nsAboutProtocolUtils.h"
 #include "nsIClassInfo.h"
 #include "nsIURIFixup.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIChromeRegistry.h"
+#include "nsIResProtocolHandler.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include <stdint.h>
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
@@ -910,20 +911,19 @@ nsScriptSecurityManager::CheckLoadURIFla
     // Check for chrome target URI
     bool hasFlags = false;
     rv = NS_URIChainHasFlags(aTargetBaseURI,
                              nsIProtocolHandler::URI_IS_UI_RESOURCE,
                              &hasFlags);
     NS_ENSURE_SUCCESS(rv, rv);
     if (hasFlags) {
         if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) {
-
-            // For now, don't change behavior for resource:// or moz-icon:// and
-            // just allow them.
-            if (!targetScheme.EqualsLiteral("chrome")) {
+            // For now, don't change behavior for moz-icon:// and just allow it.
+            if (!targetScheme.EqualsLiteral("chrome")
+                    && !targetScheme.EqualsLiteral("resource")) {
                 return NS_OK;
             }
 
             // Allow a URI_IS_UI_RESOURCE source to link to a URI_IS_UI_RESOURCE
             // target if ALLOW_CHROME is set.
             //
             // ALLOW_CHROME is a flag that we pass on all loads _except_ docshell
             // loads (since docshell loads run the loaded content with its origin
@@ -934,25 +934,61 @@ nsScriptSecurityManager::CheckLoadURIFla
             rv = NS_URIChainHasFlags(aSourceBaseURI,
                                      nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                      &sourceIsUIResource);
             NS_ENSURE_SUCCESS(rv, rv);
             if (sourceIsUIResource) {
                 return NS_OK;
             }
 
-            // Allow the load only if the chrome package is whitelisted.
-            nsCOMPtr<nsIXULChromeRegistry> reg(do_GetService(
-                                                 NS_CHROMEREGISTRY_CONTRACTID));
-            if (reg) {
+            if (targetScheme.EqualsLiteral("resource")) {
+                // Mochitests that need to load resource:// URIs not declared
+                // content-accessible in manifests should set the preference
+                // "security.all_resource_uri_content_accessible" true.
+                static bool sSecurityPrefCached = false;
+                static bool sAllResourceUriContentAccessible = false;
+                if (!sSecurityPrefCached) {
+                    sSecurityPrefCached = true;
+                    Preferences::AddBoolVarCache(
+                            &sAllResourceUriContentAccessible,
+                            "security.all_resource_uri_content_accessible",
+                            false);
+                }
+                if (sAllResourceUriContentAccessible) {
+                    return NS_OK;
+                }
+
+                nsCOMPtr<nsIProtocolHandler> ph;
+                rv = sIOService->GetProtocolHandler("resource", getter_AddRefs(ph));
+                NS_ENSURE_SUCCESS(rv, rv);
+                if (!ph) {
+                    return NS_ERROR_DOM_BAD_URI;
+                }
+
+                nsCOMPtr<nsIResProtocolHandler> rph = do_QueryInterface(ph);
+                if (!rph) {
+                    return NS_ERROR_DOM_BAD_URI;
+                }
+
                 bool accessAllowed = false;
-                reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed);
+                rph->AllowContentToAccess(aTargetBaseURI, &accessAllowed);
                 if (accessAllowed) {
                     return NS_OK;
                 }
+            } else {
+                // Allow the load only if the chrome package is whitelisted.
+                nsCOMPtr<nsIXULChromeRegistry> reg(
+                        do_GetService(NS_CHROMEREGISTRY_CONTRACTID));
+                if (reg) {
+                    bool accessAllowed = false;
+                    reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed);
+                    if (accessAllowed) {
+                        return NS_OK;
+                    }
+                }
             }
         }
 
         static bool sCanLoadChromeInContent = false;
         static bool sCachedCanLoadChromeInContentPref = false;
         if (!sCachedCanLoadChromeInContentPref) {
             sCachedCanLoadChromeInContentPref = true;
             mozilla::Preferences::AddBoolVarCache(&sCanLoadChromeInContent,
--- a/chrome/RegistryMessageUtils.h
+++ b/chrome/RegistryMessageUtils.h
@@ -37,22 +37,24 @@ struct ChromePackage
   }
 };
 
 struct SubstitutionMapping
 {
   nsCString scheme;
   nsCString path;
   SerializedURI resolvedURI;
+  uint32_t flags;
 
   bool operator ==(const SubstitutionMapping& rhs) const
   {
     return scheme.Equals(rhs.scheme) &&
            path.Equals(rhs.path) &&
-           resolvedURI == rhs.resolvedURI;
+           resolvedURI == rhs.resolvedURI &&
+           flags == rhs.flags;
   }
 };
 
 struct OverrideMapping
 {
   SerializedURI originalURI;
   SerializedURI overrideURI;
 
@@ -135,29 +137,33 @@ struct ParamTraits<SubstitutionMapping>
 {
   typedef SubstitutionMapping paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.scheme);
     WriteParam(aMsg, aParam.path);
     WriteParam(aMsg, aParam.resolvedURI);
+    WriteParam(aMsg, aParam.flags);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     nsCString scheme, path;
     SerializedURI resolvedURI;
+    uint32_t flags;
 
     if (ReadParam(aMsg, aIter, &scheme) &&
         ReadParam(aMsg, aIter, &path) &&
-        ReadParam(aMsg, aIter, &resolvedURI)) {
+        ReadParam(aMsg, aIter, &resolvedURI) &&
+        ReadParam(aMsg, aIter, &flags)) {
       aResult->scheme = scheme;
       aResult->path = path;
       aResult->resolvedURI = resolvedURI;
+      aResult->flags = flags;
       return true;
     }
     return false;
   }
 
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
     aLog->append(StringPrintf(L"[%s://%s, %s, %u]",
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -922,15 +922,23 @@ nsChromeRegistryChrome::ManifestResource
 
   if (!CanLoadResource(resolved)) {
     LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                           "Warning: cannot register non-local URI '%s' as a resource.",
                           uri);
     return;
   }
 
-  rv = rph->SetSubstitution(host, resolved);
+  // By default, Firefox resources are not content-accessible unless the
+  // manifests opts in.
+  bool contentAccessible = (flags & nsChromeRegistry::CONTENT_ACCESSIBLE);
+
+  uint32_t substitutionFlags = 0;
+  if (contentAccessible) {
+    substitutionFlags |= nsIResProtocolHandler::ALLOW_CONTENT_ACCESS;
+  }
+  rv = rph->SetSubstitutionWithFlags(host, resolved, substitutionFlags);
   if (NS_FAILED(rv)) {
     LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                           "Warning: cannot set substitution for '%s'.",
                           uri);
   }
 }
--- a/chrome/nsChromeRegistryContent.cpp
+++ b/chrome/nsChromeRegistryContent.cpp
@@ -109,17 +109,17 @@ nsChromeRegistryContent::RegisterSubstit
   if (aSubstitution.resolvedURI.spec.Length()) {
     rv = NS_NewURI(getter_AddRefs(resolvedURI),
                    aSubstitution.resolvedURI.spec,
                    nullptr, nullptr, io);
     if (NS_FAILED(rv))
       return;
   }
 
-  rv = sph->SetSubstitution(aSubstitution.path, resolvedURI);
+  rv = sph->SetSubstitutionWithFlags(aSubstitution.path, resolvedURI, aSubstitution.flags);
   if (NS_FAILED(rv))
     return;
 }
 
 void
 nsChromeRegistryContent::RegisterOverride(const OverrideMapping& aOverride)
 {
   nsCOMPtr<nsIIOService> io (do_GetIOService());
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -112,31 +112,32 @@ SubstitutingProtocolHandler::ConstructIn
 //
 // IPC marshalling.
 //
 
 nsresult
 SubstitutingProtocolHandler::CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aMappings)
 {
   for (auto iter = mSubstitutions.ConstIter(); !iter.Done(); iter.Next()) {
-    nsCOMPtr<nsIURI> uri = iter.Data();
+    SubstitutionEntry& entry = iter.Data();
+    nsCOMPtr<nsIURI> uri = entry.baseURI;
     SerializedURI serialized;
     if (uri) {
       nsresult rv = uri->GetSpec(serialized.spec);
       NS_ENSURE_SUCCESS(rv, rv);
     }
-    SubstitutionMapping substitution = { mScheme, nsCString(iter.Key()), serialized };
+    SubstitutionMapping substitution = { mScheme, nsCString(iter.Key()), serialized, entry.flags };
     aMappings.AppendElement(substitution);
   }
 
   return NS_OK;
 }
 
 nsresult
-SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
+SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI, uint32_t aFlags)
 {
   if (GeckoProcessType_Content == XRE_GetProcessType()) {
     return NS_OK;
   }
 
   nsTArray<ContentParent*> parents;
   ContentParent::GetAll(parents);
   if (!parents.Length()) {
@@ -145,16 +146,17 @@ SubstitutingProtocolHandler::SendSubstit
 
   SubstitutionMapping mapping;
   mapping.scheme = mScheme;
   mapping.path = aRoot;
   if (aBaseURI) {
     nsresult rv = aBaseURI->GetSpec(mapping.resolvedURI.spec);
     NS_ENSURE_SUCCESS(rv, rv);
   }
+  mapping.flags = aFlags;
 
   for (uint32_t i = 0; i < parents.Length(); i++) {
     Unused << parents[i]->SendRegisterChromeItem(mapping);
   }
 
   return NS_OK;
 }
 
@@ -288,61 +290,92 @@ SubstitutingProtocolHandler::AllowPort(i
 
 //----------------------------------------------------------------------------
 // nsISubstitutingProtocolHandler
 //----------------------------------------------------------------------------
 
 nsresult
 SubstitutingProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI)
 {
+  // Add-ons use this API but they should not be able to make anything
+  // content-accessible.
+  return SetSubstitutionWithFlags(root, baseURI, 0);
+}
+
+nsresult
+SubstitutingProtocolHandler::SetSubstitutionWithFlags(const nsACString& root, nsIURI *baseURI, uint32_t flags)
+{
   if (!baseURI) {
     mSubstitutions.Remove(root);
     NotifyObservers(root, baseURI);
-    return SendSubstitution(root, baseURI);
+    return SendSubstitution(root, baseURI, flags);
   }
 
   // If baseURI isn't a same-scheme URI, we can set the substitution immediately.
   nsAutoCString scheme;
   nsresult rv = baseURI->GetScheme(scheme);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!scheme.Equals(mScheme)) {
     if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")
         && !scheme.EqualsLiteral("app")) {
       NS_WARNING("Refusing to create substituting URI to non-file:// target");
       return NS_ERROR_INVALID_ARG;
     }
 
-    mSubstitutions.Put(root, baseURI);
+    SubstitutionEntry& entry = mSubstitutions.GetOrInsert(root);
+    entry.baseURI = baseURI;
+    entry.flags = flags;
     NotifyObservers(root, baseURI);
-    return SendSubstitution(root, baseURI);
+    return SendSubstitution(root, baseURI, flags);
   }
 
   // baseURI is a same-type substituting URI, let's resolve it first.
   nsAutoCString newBase;
   rv = ResolveURI(baseURI, newBase);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIURI> newBaseURI;
   rv = mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mSubstitutions.Put(root, newBaseURI);
+  SubstitutionEntry& entry = mSubstitutions.GetOrInsert(root);
+  entry.baseURI = newBaseURI;
+  entry.flags = flags;
   NotifyObservers(root, baseURI);
-  return SendSubstitution(root, newBaseURI);
+  return SendSubstitution(root, newBaseURI, flags);
 }
 
 nsresult
 SubstitutingProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
 {
   NS_ENSURE_ARG_POINTER(result);
 
-  if (mSubstitutions.Get(root, result))
+  SubstitutionEntry entry;
+  if (mSubstitutions.Get(root, &entry)) {
+    nsCOMPtr<nsIURI> baseURI = entry.baseURI;
+    baseURI.forget(result);
     return NS_OK;
+  }
 
-  return GetSubstitutionInternal(root, result);
+  uint32_t flags;
+  return GetSubstitutionInternal(root, result, &flags);
+}
+
+nsresult
+SubstitutingProtocolHandler::GetSubstitutionFlags(const nsACString& root, uint32_t* flags)
+{
+  *flags = 0;
+  SubstitutionEntry entry;
+  if (mSubstitutions.Get(root, &entry)) {
+    *flags = entry.flags;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIURI> baseURI;
+  return GetSubstitutionInternal(root, getter_AddRefs(baseURI), flags);
 }
 
 nsresult
 SubstitutingProtocolHandler::HasSubstitution(const nsACString& root, bool *result)
 {
   NS_ENSURE_ARG_POINTER(result);
   *result = HasSubstitution(root);
   return NS_OK;
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.h
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -4,19 +4,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef SubstitutingProtocolHandler_h___
 #define SubstitutingProtocolHandler_h___
 
 #include "nsISubstitutingProtocolHandler.h"
 
-#include "nsInterfaceHashtable.h"
 #include "nsIOService.h"
 #include "nsISubstitutionObserver.h"
+#include "nsDataHashtable.h"
 #include "nsStandardURL.h"
 #include "mozilla/chrome/RegistryMessageUtils.h"
 #include "mozilla/Maybe.h"
 
 class nsIIOService;
 
 namespace mozilla {
 namespace net {
@@ -39,23 +39,26 @@ public:
   bool HasSubstitution(const nsACString& aRoot) const { return mSubstitutions.Get(aRoot, nullptr); }
 
   MOZ_MUST_USE nsresult CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aResources);
 
 protected:
   virtual ~SubstitutingProtocolHandler() {}
   void ConstructInternal();
 
-  MOZ_MUST_USE nsresult SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI);
+  MOZ_MUST_USE nsresult SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI, uint32_t aFlags);
+
+  nsresult GetSubstitutionFlags(const nsACString& root, uint32_t* flags);
 
   // Override this in the subclass to try additional lookups after checking
   // mSubstitutions.
-  virtual MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult)
+  virtual MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult, uint32_t* aFlags)
   {
     *aResult = nullptr;
+    *aFlags = 0;
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Override this in the subclass to check for special case when resolving URIs
   // _before_ checking substitutions.
   virtual MOZ_MUST_USE bool ResolveSpecialCases(const nsACString& aHost,
                                                 const nsACString& aPath,
                                                 const nsACString& aPathname,
@@ -69,23 +72,38 @@ protected:
   virtual MOZ_MUST_USE nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result)
   {
     return NS_OK;
   }
 
   nsIIOService* IOService() { return mIOService; }
 
 private:
+  struct SubstitutionEntry
+  {
+    SubstitutionEntry()
+        : flags(0)
+    {
+    }
+
+    ~SubstitutionEntry()
+    {
+    }
+
+    nsCOMPtr<nsIURI> baseURI;
+    uint32_t flags;
+  };
+
   // Notifies all observers that a new substitution from |aRoot| to
   // |aBaseURI| has been set/installed for this protocol handler.
   void NotifyObservers(const nsACString& aRoot, nsIURI* aBaseURI);
 
   nsCString mScheme;
   Maybe<uint32_t> mFlags;
-  nsInterfaceHashtable<nsCStringHashKey,nsIURI> mSubstitutions;
+  nsDataHashtable<nsCStringHashKey, SubstitutionEntry> mSubstitutions;
   nsCOMPtr<nsIIOService> mIOService;
 
   // The list of observers added with AddObserver that will be
   // notified when substitutions are set or unset.
   nsTArray<nsCOMPtr<nsISubstitutionObserver>> mObservers;
 
   // In general, we expect the principal of a document loaded from a
   // substituting URI to be a codebase principal for that URI (rather than
--- a/netwerk/protocol/res/nsIResProtocolHandler.idl
+++ b/netwerk/protocol/res/nsIResProtocolHandler.idl
@@ -6,9 +6,10 @@
 #include "nsISubstitutingProtocolHandler.idl"
 
 /**
  * Protocol handler interface for the resource:// protocol
  */
 [scriptable, uuid(241d34ac-9ed5-46d7-910c-7a9d914aa0c5)]
 interface nsIResProtocolHandler : nsISubstitutingProtocolHandler
 {
+  boolean allowContentToAccess(in nsIURI url);
 };
--- a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -10,27 +10,37 @@ interface nsISubstitutionObserver;
 /**
  * Protocol handler superinterface for a protocol which performs substitutions
  * from URIs of its scheme to URIs of another scheme.
  */
 [scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)]
 interface nsISubstitutingProtocolHandler : nsIProtocolHandler
 {
   /**
+   * Content script may access files in this package.
+   */
+  const short ALLOW_CONTENT_ACCESS = 1;
+
+  /**
    * Sets the substitution for the root key:
    *   resource://root/path ==> baseURI.resolve(path)
    *
    * A null baseURI removes the specified substitution.
    *
    * A root key should always be lowercase; however, this may not be
    * enforced.
    */
   [must_use] void setSubstitution(in ACString root, in nsIURI baseURI);
 
   /**
+   * Same as setSubstitution, but with specific flags.
+   */
+  [must_use] void setSubstitutionWithFlags(in ACString root, in nsIURI baseURI, in uint32_t flags);
+
+  /**
    * Gets the substitution for the root key.
    *
    * @throws NS_ERROR_NOT_AVAILABLE if none exists.
    */
   [must_use] nsIURI getSubstitution(in ACString root);
 
   /**
    * Returns TRUE if the substitution exists and FALSE otherwise.
--- a/netwerk/protocol/res/nsResProtocolHandler.cpp
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -56,26 +56,46 @@ nsResProtocolHandler::Init()
 //----------------------------------------------------------------------------
 
 NS_IMPL_QUERY_INTERFACE(nsResProtocolHandler, nsIResProtocolHandler,
                         nsISubstitutingProtocolHandler, nsIProtocolHandler,
                         nsISupportsWeakReference)
 NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
 NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
 
+NS_IMETHODIMP
+nsResProtocolHandler::AllowContentToAccess(nsIURI *aURI, bool *aResult)
+{
+    *aResult = false;
+
+    nsAutoCString host;
+    nsresult rv = aURI->GetAsciiHost(host);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    uint32_t flags;
+    rv = GetSubstitutionFlags(host, &flags);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    *aResult = flags & nsISubstitutingProtocolHandler::ALLOW_CONTENT_ACCESS;
+    return NS_OK;
+}
+
 nsresult
-nsResProtocolHandler::GetSubstitutionInternal(const nsACString& root, nsIURI **result)
+nsResProtocolHandler::GetSubstitutionInternal(const nsACString& aRoot,
+                                              nsIURI** aResult,
+                                              uint32_t* aFlags)
 {
     nsAutoCString uri;
 
-    if (!ResolveSpecialCases(root, NS_LITERAL_CSTRING("/"), NS_LITERAL_CSTRING("/"), uri)) {
+    if (!ResolveSpecialCases(aRoot, NS_LITERAL_CSTRING("/"), NS_LITERAL_CSTRING("/"), uri)) {
         return NS_ERROR_NOT_AVAILABLE;
     }
 
-    return NS_NewURI(result, uri);
+    *aFlags = 0; // No content access.
+    return NS_NewURI(aResult, uri);
 }
 
 bool
 nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
                                           const nsACString& aPath,
                                           const nsACString& aPathname,
                                           nsACString& aResult)
 {
@@ -93,8 +113,19 @@ nsResProtocolHandler::ResolveSpecialCase
 nsresult
 nsResProtocolHandler::SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
 {
     MOZ_ASSERT(!aRoot.Equals(""));
     MOZ_ASSERT(!aRoot.Equals(kAPP));
     MOZ_ASSERT(!aRoot.Equals(kGRE));
     return SubstitutingProtocolHandler::SetSubstitution(aRoot, aBaseURI);
 }
+
+nsresult
+nsResProtocolHandler::SetSubstitutionWithFlags(const nsACString& aRoot,
+                                               nsIURI* aBaseURI,
+                                               uint32_t aFlags)
+{
+    MOZ_ASSERT(!aRoot.Equals(""));
+    MOZ_ASSERT(!aRoot.Equals(kAPP));
+    MOZ_ASSERT(!aRoot.Equals(kGRE));
+    return SubstitutingProtocolHandler::SetSubstitutionWithFlags(aRoot, aBaseURI, aFlags);
+}
--- a/netwerk/protocol/res/nsResProtocolHandler.h
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -29,16 +29,17 @@ public:
     nsResProtocolHandler()
       : SubstitutingProtocolHandler("resource", URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE,
                                     /* aEnforceFileOrJar = */ false)
     {}
 
     MOZ_MUST_USE nsresult Init();
 
     NS_IMETHOD SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI) override;
+    NS_IMETHOD SetSubstitutionWithFlags(const nsACString& aRoot, nsIURI* aBaseURI, uint32_t aFlags) override;
 
     NS_IMETHOD GetSubstitution(const nsACString& aRoot, nsIURI** aResult) override
     {
         return mozilla::SubstitutingProtocolHandler::GetSubstitution(aRoot, aResult);
     }
 
     NS_IMETHOD HasSubstitution(const nsACString& aRoot, bool* aResult) override
     {
@@ -56,17 +57,17 @@ public:
     }
 
     NS_IMETHOD RemoveObserver(nsISubstitutionObserver *aObserver) override
     {
         return mozilla::SubstitutingProtocolHandler::RemoveObserver(aObserver);
     }
 
 protected:
-    MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override;
+    MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult, uint32_t* aFlags) override;
     virtual ~nsResProtocolHandler() {}
 
     MOZ_MUST_USE bool ResolveSpecialCases(const nsACString& aHost,
                                           const nsACString& aPath,
                                           const nsACString& aPathname,
                                           nsACString& aResult) override;
 
 private:
--- a/xpcom/components/ManifestParser.cpp
+++ b/xpcom/components/ManifestParser.cpp
@@ -51,17 +51,17 @@ struct ManifestDirective
 
   // Some directives should only be delivered for APP or EXTENSION locations.
   bool componentonly;
 
   bool ischrome;
 
   bool allowbootstrap;
 
-  // The contentaccessible flags only apply to content directives.
+  // The contentaccessible flags only apply to content/resource directives.
   bool contentflags;
 
   // Function to handle this directive. This isn't a union because C++ still
   // hasn't learned how to initialize unions in a sane way.
   void (nsComponentManagerImpl::*mgrfunc)(
     nsComponentManagerImpl::ManifestProcessingContext& aCx,
     int aLineNo, char* const* aArgv);
   void (nsChromeRegistry::*regfunc)(
@@ -118,17 +118,17 @@ static const ManifestDirective kParsingT
   },
   {
     // NB: note that while skin manifests can use this, they are only allowed
     // to use it for chrome://../skin/ URLs
     "override",         2, false, false, true, true, false,
     nullptr, &nsChromeRegistry::ManifestOverride, nullptr
   },
   {
-    "resource",         2, false, true, true, true, false,
+    "resource",         2, false, true, true, true, true,
     nullptr, &nsChromeRegistry::ManifestResource, nullptr
   }
 };
 
 static const char kWhitespace[] = "\t ";
 
 static bool
 IsNewline(char aChar)