Bug 1161831 - Factor the sharable bits out of nsIResProtocolHandler. r=billm
authorBobby Holley <bobbyholley@gmail.com>
Thu, 16 Jul 2015 15:50:07 -0700
changeset 254011 7b078b5605a1bc51ba7fb87b5b81f46f9e8dfad1
parent 254010 cd49cae74a81362ffca63066f65a6bc345dbd2b3
child 254012 9b10ef08bfbe105055f128afd5a1023a6e754011
push idunknown
push userunknown
push dateunknown
reviewersbillm
bugs1161831
milestone42.0a1
Bug 1161831 - Factor the sharable bits out of nsIResProtocolHandler. r=billm
chrome/RegistryMessageUtils.h
chrome/nsChromeRegistryChrome.cpp
chrome/nsChromeRegistryContent.cpp
chrome/nsChromeRegistryContent.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/PContent.ipdl
netwerk/build/nsNetCID.h
netwerk/build/nsNetModule.cpp
netwerk/protocol/res/SubstitutingProtocolHandler.cpp
netwerk/protocol/res/SubstitutingProtocolHandler.h
netwerk/protocol/res/moz.build
netwerk/protocol/res/nsIResProtocolHandler.idl
netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
netwerk/protocol/res/nsResProtocolHandler.cpp
netwerk/protocol/res/nsResProtocolHandler.h
--- a/chrome/RegistryMessageUtils.h
+++ b/chrome/RegistryMessageUtils.h
@@ -34,24 +34,26 @@ struct ChromePackage
     return package.Equals(rhs.package) &&
            contentBaseURI == rhs.contentBaseURI &&
            localeBaseURI == rhs.localeBaseURI &&
            skinBaseURI == rhs.skinBaseURI &&
            flags == rhs.flags;
   }
 };
 
-struct ResourceMapping
+struct SubstitutionMapping
 {
-  nsCString resource;
+  nsCString scheme;
+  nsCString path;
   SerializedURI resolvedURI;
 
-  bool operator ==(const ResourceMapping& rhs) const
+  bool operator ==(const SubstitutionMapping& rhs) const
   {
-    return resource.Equals(rhs.resource) &&
+    return scheme.Equals(rhs.scheme) &&
+           path.Equals(rhs.path) &&
            resolvedURI == rhs.resolvedURI;
   }
 };
 
 struct OverrideMapping
 {
   SerializedURI originalURI;
   SerializedURI overrideURI;
@@ -129,43 +131,48 @@ struct ParamTraits<ChromePackage>
     aLog->append(StringPrintf(L"[%s, %s, %s, %s, %u]", aParam.package.get(),
                              aParam.contentBaseURI.spec.get(),
                              aParam.localeBaseURI.spec.get(),
                              aParam.skinBaseURI.spec.get(), aParam.flags));
   }
 };
 
 template <>
-struct ParamTraits<ResourceMapping>
+struct ParamTraits<SubstitutionMapping>
 {
-  typedef ResourceMapping paramType;
+  typedef SubstitutionMapping paramType;
   
   static void Write(Message* aMsg, const paramType& aParam)
   {
-    WriteParam(aMsg, aParam.resource);
+    WriteParam(aMsg, aParam.scheme);
+    WriteParam(aMsg, aParam.path);
     WriteParam(aMsg, aParam.resolvedURI);
   }
   
   static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
   {
-    nsCString resource;
+    nsCString scheme, path;
     SerializedURI resolvedURI;
     
-    if (ReadParam(aMsg, aIter, &resource) &&
+    if (ReadParam(aMsg, aIter, &scheme) &&
+        ReadParam(aMsg, aIter, &path) &&
         ReadParam(aMsg, aIter, &resolvedURI)) {
-      aResult->resource = resource;
+      aResult->scheme = scheme;
+      aResult->path = path;
       aResult->resolvedURI = resolvedURI;
       return true;
     }
     return false;
   }
 
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
-    aLog->append(StringPrintf(L"[%s, %s, %u]", aParam.resource.get(),
+    aLog->append(StringPrintf(L"[%s://%s, %s, %u]",
+                             aParam.scheme.get(),
+                             aParam.path.get(),
                              aParam.resolvedURI.spec.get()));
   }
 };
 
 template <>
 struct ParamTraits<OverrideMapping>
 {
   typedef OverrideMapping paramType;
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -440,17 +440,17 @@ struct EnumerationArgs
   const nsCString& selectedSkin;
 };
 
 void
 nsChromeRegistryChrome::SendRegisteredChrome(
     mozilla::dom::PContentParent* aParent)
 {
   InfallibleTArray<ChromePackage> packages;
-  InfallibleTArray<ResourceMapping> resources;
+  InfallibleTArray<SubstitutionMapping> resources;
   InfallibleTArray<OverrideMapping> overrides;
 
   EnumerationArgs args = {
     packages, mSelectedLocale, mSelectedSkin
   };
   mPackagesHash.EnumerateRead(CollectPackages, &args);
 
   // If we were passed a parent then a new child process has been created and
--- a/chrome/nsChromeRegistryContent.cpp
+++ b/chrome/nsChromeRegistryContent.cpp
@@ -12,17 +12,17 @@
 
 nsChromeRegistryContent::nsChromeRegistryContent()
 {
 }
 
 void
 nsChromeRegistryContent::RegisterRemoteChrome(
     const InfallibleTArray<ChromePackage>& aPackages,
-    const InfallibleTArray<ResourceMapping>& aResources,
+    const InfallibleTArray<SubstitutionMapping>& aSubstitutions,
     const InfallibleTArray<OverrideMapping>& aOverrides,
     const nsACString& aLocale,
     bool aReset)
 {
   MOZ_ASSERT(aReset || mLocale.IsEmpty(),
              "RegisterChrome twice?");
 
   if (aReset) {
@@ -31,19 +31,19 @@ nsChromeRegistryContent::RegisterRemoteC
     // XXX Can't clear resources.
   }
 
   for (uint32_t i = aPackages.Length(); i > 0; ) {
     --i;
     RegisterPackage(aPackages[i]);
   }
 
-  for (uint32_t i = aResources.Length(); i > 0; ) {
+  for (uint32_t i = aSubstitutions.Length(); i > 0; ) {
     --i;
-    RegisterResource(aResources[i]);
+    RegisterSubstitution(aSubstitutions[i]);
   }
 
   for (uint32_t i = aOverrides.Length(); i > 0; ) {
     --i;
     RegisterOverride(aOverrides[i]);
   }
 
   mLocale = aLocale;
@@ -89,42 +89,42 @@ nsChromeRegistryContent::RegisterPackage
   entry->contentBaseURI = content;
   entry->localeBaseURI = locale;
   entry->skinBaseURI = skin;
 
   mPackagesHash.Put(aPackage.package, entry);
 }
 
 void
-nsChromeRegistryContent::RegisterResource(const ResourceMapping& aResource)
+nsChromeRegistryContent::RegisterSubstitution(const SubstitutionMapping& aSubstitution)
 {
   nsCOMPtr<nsIIOService> io (do_GetIOService());
   if (!io)
     return;
 
   nsCOMPtr<nsIProtocolHandler> ph;
-  nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph));
+  nsresult rv = io->GetProtocolHandler(aSubstitution.scheme.get(), getter_AddRefs(ph));
   if (NS_FAILED(rv))
     return;
   
-  nsCOMPtr<nsIResProtocolHandler> rph (do_QueryInterface(ph));
-  if (!rph)
+  nsCOMPtr<nsISubstitutingProtocolHandler> sph (do_QueryInterface(ph));
+  if (!sph)
     return;
 
   nsCOMPtr<nsIURI> resolvedURI;
-  if (aResource.resolvedURI.spec.Length()) {
+  if (aSubstitution.resolvedURI.spec.Length()) {
     nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI),
-                            aResource.resolvedURI.spec,
-                            aResource.resolvedURI.charset.get(),
+                            aSubstitution.resolvedURI.spec,
+                            aSubstitution.resolvedURI.charset.get(),
                             nullptr, io);
     if (NS_FAILED(rv))
       return;
   }
 
-  rv = rph->SetSubstitution(aResource.resource, resolvedURI);
+  rv = sph->SetSubstitution(aSubstitution.path, resolvedURI);
   if (NS_FAILED(rv))
     return;
 }
 
 void
 nsChromeRegistryContent::RegisterOverride(const OverrideMapping& aOverride)
 {
   nsCOMPtr<nsIIOService> io (do_GetIOService());
--- a/chrome/nsChromeRegistryContent.h
+++ b/chrome/nsChromeRegistryContent.h
@@ -5,26 +5,26 @@
 
 #ifndef nsChromeRegistryContent_h
 #define nsChromeRegistryContent_h
 
 #include "nsChromeRegistry.h"
 #include "nsClassHashtable.h"
 
 struct ChromePackage;
-struct ResourceMapping;
+struct SubstitutionMapping;
 struct OverrideMapping;
 
 class nsChromeRegistryContent : public nsChromeRegistry
 {
  public:
   nsChromeRegistryContent();
 
   void RegisterRemoteChrome(const InfallibleTArray<ChromePackage>& aPackages,
-                            const InfallibleTArray<ResourceMapping>& aResources,
+                            const InfallibleTArray<SubstitutionMapping>& aResources,
                             const InfallibleTArray<OverrideMapping>& aOverrides,
                             const nsACString& aLocale,
                             bool aReset);
 
   NS_IMETHOD GetLocalesForPackage(const nsACString& aPackage,
                                   nsIUTF8StringEnumerator* *aResult) override;
   NS_IMETHOD CheckForNewChrome() override;
   NS_IMETHOD CheckForOSAccessibility() override;
@@ -36,17 +36,17 @@ class nsChromeRegistryContent : public n
                                nsACString& aLocale) override;
   NS_IMETHOD GetStyleOverlays(nsIURI *aChromeURL,
                               nsISimpleEnumerator **aResult) override;
   NS_IMETHOD GetXULOverlays(nsIURI *aChromeURL,
                             nsISimpleEnumerator **aResult) override;
 
   void RegisterPackage(const ChromePackage& aPackage);
   void RegisterOverride(const OverrideMapping& aOverride);
-  void RegisterResource(const ResourceMapping& aResource);
+  void RegisterSubstitution(const SubstitutionMapping& aResource);
 
  private:
   struct PackageEntry
   {
     PackageEntry() : flags(0) { }
     ~PackageEntry() { }
 
     nsCOMPtr<nsIURI> contentBaseURI;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1833,17 +1833,17 @@ ContentChild::DeallocPWebrtcGlobalChild(
 #else
     return false;
 #endif
 }
 
 
 bool
 ContentChild::RecvRegisterChrome(InfallibleTArray<ChromePackage>&& packages,
-                                 InfallibleTArray<ResourceMapping>&& resources,
+                                 InfallibleTArray<SubstitutionMapping>&& resources,
                                  InfallibleTArray<OverrideMapping>&& overrides,
                                  const nsCString& locale,
                                  const bool& reset)
 {
     nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
     nsChromeRegistryContent* chromeRegistry =
         static_cast<nsChromeRegistryContent*>(registrySvc.get());
     chromeRegistry->RegisterRemoteChrome(packages, resources, overrides,
@@ -1861,18 +1861,18 @@ ContentChild::RecvRegisterChromeItem(con
         case ChromeRegistryItem::TChromePackage:
             chromeRegistry->RegisterPackage(item.get_ChromePackage());
             break;
 
         case ChromeRegistryItem::TOverrideMapping:
             chromeRegistry->RegisterOverride(item.get_OverrideMapping());
             break;
 
-        case ChromeRegistryItem::TResourceMapping:
-            chromeRegistry->RegisterResource(item.get_ResourceMapping());
+        case ChromeRegistryItem::TSubstitutionMapping:
+            chromeRegistry->RegisterSubstitution(item.get_SubstitutionMapping());
             break;
 
         default:
             MOZ_ASSERT(false, "bad chrome item");
             return false;
     }
 
     return true;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -16,17 +16,17 @@
 #include "nsIObserver.h"
 #include "nsTHashtable.h"
 
 #include "nsWeakPtr.h"
 
 
 struct ChromePackage;
 class nsIObserver;
-struct ResourceMapping;
+struct SubstitutionMapping;
 struct OverrideMapping;
 class nsIDomainPolicy;
 
 namespace mozilla {
 class RemoteSpellcheckEngineChild;
 
 namespace ipc {
 class OptionalURIParams;
@@ -277,17 +277,17 @@ public:
                                  const IPC::Principal& aPrincipal) override;
     virtual bool DeallocPAsmJSCacheEntryChild(
                                     PAsmJSCacheEntryChild* aActor) override;
 
     virtual PSpeechSynthesisChild* AllocPSpeechSynthesisChild() override;
     virtual bool DeallocPSpeechSynthesisChild(PSpeechSynthesisChild* aActor) override;
 
     virtual bool RecvRegisterChrome(InfallibleTArray<ChromePackage>&& packages,
-                                    InfallibleTArray<ResourceMapping>&& resources,
+                                    InfallibleTArray<SubstitutionMapping>&& resources,
                                     InfallibleTArray<OverrideMapping>&& overrides,
                                     const nsCString& locale,
                                     const bool& reset) override;
     virtual bool RecvRegisterChromeItem(const ChromeRegistryItem& item) override;
 
     virtual mozilla::jsipc::PJavaScriptChild* AllocPJavaScriptChild() override;
     virtual bool DeallocPJavaScriptChild(mozilla::jsipc::PJavaScriptChild*) override;
     virtual PRemoteSpellcheckEngineChild* AllocPRemoteSpellcheckEngineChild() override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -66,17 +66,17 @@ include BrowserConfiguration;
 // XXX Remove this once bug 1069073 is fixed
 include "mozilla/dom/PContentBridgeParent.h";
 
 include "mozilla/dom/indexedDB/SerializationHelpers.h";
 
 using GeoPosition from "nsGeoPositionIPCSerialiser.h";
 
 using struct ChromePackage from "mozilla/chrome/RegistryMessageUtils.h";
-using struct ResourceMapping from "mozilla/chrome/RegistryMessageUtils.h";
+using struct SubstitutionMapping from "mozilla/chrome/RegistryMessageUtils.h";
 using struct OverrideMapping from "mozilla/chrome/RegistryMessageUtils.h";
 using base::ChildPrivileges from "base/process_util.h";
 using base::ProcessId from "base/process.h";
 using struct IPC::Permission from "mozilla/net/NeckoMessageUtils.h";
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using struct mozilla::null_t from "ipc/IPCMessageUtils.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using mozilla::dom::asmjscache::OpenMode from "mozilla/dom/asmjscache/AsmJSCache.h";
@@ -89,17 +89,17 @@ using mozilla::dom::TabId from "mozilla/
 using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
 using struct mozilla::OwningSerializedStructuredCloneBuffer from "ipc/IPCMessageUtils.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
-    ResourceMapping;
+    SubstitutionMapping;
 };
 
 namespace mozilla {
 namespace dom {
 
 struct FontListEntry {
     nsString  familyName;
     nsString  faceName;
@@ -516,17 +516,17 @@ child:
      * nsIMemoryInfoDumper.idl
      */
     PCycleCollectWithLogs(bool dumpAllTraces,
                           FileDescriptor gcLog,
                           FileDescriptor ccLog);
 
     PTestShell();
 
-    RegisterChrome(ChromePackage[] packages, ResourceMapping[] resources,
+    RegisterChrome(ChromePackage[] packages, SubstitutionMapping[] substitutions,
                    OverrideMapping[] overrides, nsCString locale, bool reset);
     RegisterChromeItem(ChromeRegistryItem item);
 
     async SetOffline(bool offline);
     async SetConnectivity(bool connectivity);
 
     async NotifyVisited(URIParams uri);
 
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -632,25 +632,23 @@
 #define NS_RESPROTOCOLHANDLER_CID                    \
 { /* e64f152a-9f07-11d3-8cda-0060b0fc14a3 */         \
     0xe64f152a,                                      \
     0x9f07,                                          \
     0x11d3,                                          \
     {0x8c, 0xda, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \
 }
 
-#define NS_RESURL_CID                    \
-{ /* ff8fe7ec-2f74-4408-b742-6b7a546029a8 */         \
-    0xff8fe7ec,                                      \
-    0x2f74,                                          \
-    0x4408,                                          \
-    {0xb7, 0x42, 0x6b, 0x7a, 0x54, 0x60, 0x29, 0xa8} \
+#define NS_SUBSTITUTINGURL_CID                       \
+{ 0xdea9657c,                                        \
+  0x18cf,                                            \
+  0x4984,                                            \
+  { 0xbd, 0xe9, 0xcc, 0xef, 0x5d, 0x8a, 0xb4, 0x73 } \
 }
 
-
 /******************************************************************************
  * netwerk/protocol/file/ classes
  */
 
 #define NS_FILEPROTOCOLHANDLER_CID                   \
 { /* fbc81170-1f69-11d3-9344-00104ba0fd40 */         \
     0xfbc81170,                                      \
     0x1f69,                                          \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -267,18 +267,22 @@ namespace net {
   NS_GENERIC_FACTORY_CONSTRUCTOR(PackagedAppService)
 } // namespace net
 } // namespace mozilla
 #include "AppProtocolHandler.h"
 
 #ifdef NECKO_PROTOCOL_res
 // resource
 #include "nsResProtocolHandler.h"
+#include "SubstitutingProtocolHandler.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsResProtocolHandler, Init)
-NS_GENERIC_FACTORY_CONSTRUCTOR(nsResURL)
+
+namespace mozilla {
+NS_GENERIC_FACTORY_CONSTRUCTOR(SubstitutingURL)
+} // namespace mozilla
 #endif
 
 #ifdef NECKO_PROTOCOL_device
 #include "nsDeviceProtocolHandler.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceProtocolHandler)
 #endif
 
 #ifdef NECKO_PROTOCOL_viewsource
@@ -751,17 +755,17 @@ NS_DEFINE_NAMED_CID(NS_HTTPAUTHMANAGER_C
 NS_DEFINE_NAMED_CID(NS_HTTPCHANNELAUTHPROVIDER_CID);
 NS_DEFINE_NAMED_CID(NS_HTTPACTIVITYDISTRIBUTOR_CID);
 #endif // !NECKO_PROTOCOL_http
 #ifdef NECKO_PROTOCOL_ftp
 NS_DEFINE_NAMED_CID(NS_FTPPROTOCOLHANDLER_CID);
 #endif
 #ifdef NECKO_PROTOCOL_res
 NS_DEFINE_NAMED_CID(NS_RESPROTOCOLHANDLER_CID);
-NS_DEFINE_NAMED_CID(NS_RESURL_CID);
+NS_DEFINE_NAMED_CID(NS_SUBSTITUTINGURL_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_ABOUTPROTOCOLHANDLER_CID);
 NS_DEFINE_NAMED_CID(NS_SAFEABOUTPROTOCOLHANDLER_CID);
 NS_DEFINE_NAMED_CID(NS_ABOUT_BLANK_MODULE_CID);
 NS_DEFINE_NAMED_CID(NS_NESTEDABOUTURI_CID);
 #ifdef NECKO_PROTOCOL_about
 #ifdef NS_BUILD_REFCNT_LOGGING
 NS_DEFINE_NAMED_CID(NS_ABOUT_BLOAT_MODULE_CID);
@@ -897,17 +901,17 @@ static const mozilla::Module::CIDEntry k
     { &kNS_HTTPCHANNELAUTHPROVIDER_CID, false, nullptr, mozilla::net::nsHttpChannelAuthProviderConstructor },
     { &kNS_HTTPACTIVITYDISTRIBUTOR_CID, false, nullptr, mozilla::net::nsHttpActivityDistributorConstructor },
 #endif // !NECKO_PROTOCOL_http
 #ifdef NECKO_PROTOCOL_ftp
     { &kNS_FTPPROTOCOLHANDLER_CID, false, nullptr, nsFtpProtocolHandlerConstructor },
 #endif
 #ifdef NECKO_PROTOCOL_res
     { &kNS_RESPROTOCOLHANDLER_CID, false, nullptr, nsResProtocolHandlerConstructor },
-    { &kNS_RESURL_CID, false, nullptr, nsResURLConstructor },
+    { &kNS_SUBSTITUTINGURL_CID, false, nullptr, mozilla::SubstitutingURLConstructor },
 #endif
     { &kNS_ABOUTPROTOCOLHANDLER_CID, false, nullptr, nsAboutProtocolHandlerConstructor },
     { &kNS_SAFEABOUTPROTOCOLHANDLER_CID, false, nullptr, nsSafeAboutProtocolHandlerConstructor },
     { &kNS_ABOUT_BLANK_MODULE_CID, false, nullptr, nsAboutBlank::Create },
     { &kNS_NESTEDABOUTURI_CID, false, nullptr, nsNestedAboutURIConstructor },
 #ifdef NECKO_PROTOCOL_about
 #ifdef NS_BUILD_REFCNT_LOGGING
     { &kNS_ABOUT_BLOAT_MODULE_CID, false, nullptr, nsAboutBloat::Create },
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/unused.h"
+
+#include "SubstitutingProtocolHandler.h"
+#include "nsIIOService.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+
+using mozilla::dom::ContentParent;
+
+namespace mozilla {
+
+// Log module for Substituting Protocol logging. We keep the pre-existing module
+// name of "nsResProtocol" to avoid disruption.
+static PRLogModuleInfo *gResLog;
+
+static NS_DEFINE_CID(kSubstitutingURLCID, NS_SUBSTITUTINGURL_CID);
+
+//---------------------------------------------------------------------------------
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile resolution
+//---------------------------------------------------------------------------------
+
+nsresult
+SubstitutingURL::EnsureFile()
+{
+  nsAutoCString ourScheme;
+  nsresult rv = GetScheme(ourScheme);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Get the handler associated with this scheme. It would be nice to just
+  // pass this in when constructing SubstitutingURLs, but we need a generic
+  // factory constructor.
+  nsCOMPtr<nsIIOService> io = do_GetIOService(&rv);
+  nsCOMPtr<nsIProtocolHandler> handler;
+  rv = io->GetProtocolHandler(ourScheme.get(), getter_AddRefs(handler));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsISubstitutingProtocolHandler> substHandler = do_QueryInterface(handler);
+  MOZ_ASSERT(substHandler);
+
+  nsAutoCString spec;
+  rv = substHandler->ResolveURI(this, spec);
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsAutoCString scheme;
+  rv = net_ExtractURLScheme(spec, nullptr, nullptr, &scheme);
+  if (NS_FAILED(rv))
+    return rv;
+
+  // Bug 585869:
+  // In most cases, the scheme is jar if it's not file.
+  // Regardless, net_GetFileFromURLSpec should be avoided
+  // when the scheme isn't file.
+  if (!scheme.EqualsLiteral("file"))
+    return NS_ERROR_NO_INTERFACE;
+
+  return net_GetFileFromURLSpec(spec, getter_AddRefs(mFile));
+}
+
+/* virtual */ nsStandardURL*
+SubstitutingURL::StartClone()
+{
+  SubstitutingURL *clone = new SubstitutingURL();
+  return clone;
+}
+
+NS_IMETHODIMP
+SubstitutingURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+  *aClassIDNoAlloc = kSubstitutingURLCID;
+  return NS_OK;
+}
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme, uint32_t aFlags)
+  : mScheme(aScheme)
+  , mFlags(aFlags)
+  , mSubstitutions(16)
+{
+  nsresult rv;
+  mIOService = do_GetIOService(&rv);
+  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOService);
+
+  if (!gResLog) {
+    gResLog = PR_NewLogModule("nsResProtocol");
+  }
+}
+
+//
+// IPC marshalling.
+//
+
+struct EnumerateSubstitutionArg
+{
+  EnumerateSubstitutionArg(nsCString& aScheme, nsTArray<SubstitutionMapping>& aMappings)
+    : mScheme(aScheme), mMappings(aMappings) {}
+  nsCString& mScheme;
+  nsTArray<SubstitutionMapping>& mMappings;
+};
+
+static PLDHashOperator
+EnumerateSubstitution(const nsACString& aKey,
+                      nsIURI* aURI,
+                      void* aArg)
+{
+  auto arg = static_cast<EnumerateSubstitutionArg*>(aArg);
+  SerializedURI uri;
+  if (aURI) {
+    aURI->GetSpec(uri.spec);
+    aURI->GetOriginCharset(uri.charset);
+  }
+
+  SubstitutionMapping substitution = { arg->mScheme, nsCString(aKey), uri };
+  arg->mMappings.AppendElement(substitution);
+  return (PLDHashOperator)PL_DHASH_NEXT;
+}
+
+void
+SubstitutingProtocolHandler::CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aMappings)
+{
+  EnumerateSubstitutionArg arg(mScheme, aMappings);
+  mSubstitutions.EnumerateRead(&EnumerateSubstitution, &arg);
+}
+
+void
+SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
+{
+  if (GeckoProcessType_Content == XRE_GetProcessType()) {
+    return;
+  }
+
+  nsTArray<ContentParent*> parents;
+  ContentParent::GetAll(parents);
+  if (!parents.Length()) {
+    return;
+  }
+
+  SubstitutionMapping mapping;
+  mapping.scheme = mScheme;
+  mapping.path = aRoot;
+  if (aBaseURI) {
+    aBaseURI->GetSpec(mapping.resolvedURI.spec);
+    aBaseURI->GetOriginCharset(mapping.resolvedURI.charset);
+  }
+
+  for (uint32_t i = 0; i < parents.Length(); i++) {
+    unused << parents[i]->SendRegisterChromeItem(mapping);
+  }
+}
+
+//----------------------------------------------------------------------------
+// nsIProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult
+SubstitutingProtocolHandler::GetScheme(nsACString &result)
+{
+  result = mScheme;
+  return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::GetDefaultPort(int32_t *result)
+{
+  *result = -1;
+  return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+  *result = mFlags;
+  return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::NewURI(const nsACString &aSpec,
+                                    const char *aCharset,
+                                    nsIURI *aBaseURI,
+                                    nsIURI **result)
+{
+  nsresult rv;
+
+  nsRefPtr<SubstitutingURL> url = new SubstitutingURL();
+  if (!url)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  // unescape any %2f and %2e to make sure nsStandardURL coalesces them.
+  // Later net_GetFileFromURLSpec() will do a full unescape and we want to
+  // treat them the same way the file system will. (bugs 380994, 394075)
+  nsAutoCString spec;
+  const char *src = aSpec.BeginReading();
+  const char *end = aSpec.EndReading();
+  const char *last = src;
+
+  spec.SetCapacity(aSpec.Length()+1);
+  for ( ; src < end; ++src) {
+    if (*src == '%' && (src < end-2) && *(src+1) == '2') {
+      char ch = '\0';
+      if (*(src+2) == 'f' || *(src+2) == 'F') {
+        ch = '/';
+      } else if (*(src+2) == 'e' || *(src+2) == 'E') {
+        ch = '.';
+      }
+
+      if (ch) {
+        if (last < src) {
+          spec.Append(last, src-last);
+        }
+        spec.Append(ch);
+        src += 2;
+        last = src+1; // src will be incremented by the loop
+      }
+    }
+  }
+  if (last < src)
+    spec.Append(last, src-last);
+
+  rv = url->Init(nsIStandardURL::URLTYPE_STANDARD, -1, spec, aCharset, aBaseURI);
+  if (NS_SUCCEEDED(rv)) {
+    url.forget(result);
+  }
+  return rv;
+}
+
+nsresult
+SubstitutingProtocolHandler::NewChannel2(nsIURI* uri,
+                                         nsILoadInfo* aLoadInfo,
+                                         nsIChannel** result)
+{
+  NS_ENSURE_ARG_POINTER(uri);
+  nsAutoCString spec;
+  nsresult rv = ResolveURI(uri, spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> newURI;
+  rv = NS_NewURI(getter_AddRefs(newURI), spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = NS_NewChannelInternal(result, newURI, aLoadInfo);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsLoadFlags loadFlags = 0;
+  (*result)->GetLoadFlags(&loadFlags);
+  (*result)->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
+  return (*result)->SetOriginalURI(uri);
+}
+
+nsresult
+SubstitutingProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+  return NewChannel2(uri, nullptr, result);
+}
+
+nsresult
+SubstitutingProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+  // don't override anything.
+  *_retval = false;
+  return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsISubstitutingProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult
+SubstitutingProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI)
+{
+  if (!baseURI) {
+    mSubstitutions.Remove(root);
+    SendSubstitution(root, baseURI);
+    return NS_OK;
+  }
+
+  // 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)) {
+    mSubstitutions.Put(root, baseURI);
+    SendSubstitution(root, baseURI);
+    return NS_OK;
+  }
+
+  // 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);
+  SendSubstitution(root, newBaseURI);
+  return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
+{
+  NS_ENSURE_ARG_POINTER(result);
+
+  if (mSubstitutions.Get(root, result))
+    return NS_OK;
+
+  return GetSubstitutionInternal(root, result);
+}
+
+nsresult
+SubstitutingProtocolHandler::HasSubstitution(const nsACString& root, bool *result)
+{
+  NS_ENSURE_ARG_POINTER(result);
+
+  *result = mSubstitutions.Get(root, nullptr);
+  return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::ResolveURI(nsIURI *uri, nsACString &result)
+{
+  nsresult rv;
+
+  nsAutoCString host;
+  nsAutoCString path;
+
+  rv = uri->GetAsciiHost(host);
+  if (NS_FAILED(rv)) return rv;
+
+  rv = uri->GetPath(path);
+  if (NS_FAILED(rv)) return rv;
+
+  // Unescape the path so we can perform some checks on it.
+  nsAutoCString unescapedPath(path);
+  NS_UnescapeURL(unescapedPath);
+
+  // Don't misinterpret the filepath as an absolute URI.
+  if (unescapedPath.FindChar(':') != -1)
+    return NS_ERROR_MALFORMED_URI;
+
+  if (unescapedPath.FindChar('\\') != -1)
+    return NS_ERROR_MALFORMED_URI;
+
+  const char *p = path.get() + 1; // path always starts with a slash
+  NS_ASSERTION(*(p-1) == '/', "Path did not begin with a slash!");
+
+  if (*p == '/')
+    return NS_ERROR_MALFORMED_URI;
+
+  nsCOMPtr<nsIURI> baseURI;
+  rv = GetSubstitution(host, getter_AddRefs(baseURI));
+  if (NS_FAILED(rv)) return rv;
+
+  rv = baseURI->Resolve(nsDependentCString(p, path.Length()-1), result);
+
+  if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) {
+    nsAutoCString spec;
+    uri->GetAsciiSpec(spec);
+    MOZ_LOG(gResLog, LogLevel::Debug, ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get()));
+  }
+  return rv;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef SubstitutingProtocolHandler_h___
+#define SubstitutingProtocolHandler_h___
+
+#include "nsISubstitutingProtocolHandler.h"
+
+#include "nsInterfaceHashtable.h"
+#include "nsIOService.h"
+#include "nsStandardURL.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+
+class nsIIOService;
+
+namespace mozilla {
+
+//
+// Base class for resource://-like substitution protocols.
+//
+// If you add a new protocol, make sure to change nsChromeRegistryChrome
+// to properly invoke CollectSubstitutions at the right time.
+class SubstitutingProtocolHandler
+{
+public:
+  SubstitutingProtocolHandler(const char* aScheme, uint32_t aFlags);
+
+  NS_INLINE_DECL_REFCOUNTING(SubstitutingProtocolHandler);
+  NS_DECL_NON_VIRTUAL_NSIPROTOCOLHANDLER;
+  NS_DECL_NON_VIRTUAL_NSISUBSTITUTINGPROTOCOLHANDLER;
+
+  void CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aResources);
+
+protected:
+  virtual ~SubstitutingProtocolHandler() {}
+
+  void SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI);
+
+  // Override this in the subclass to try additional lookups after checking
+  // mSubstitutions.
+  virtual nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult)
+  {
+    *aResult = nullptr;
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsIIOService* IOService() { return mIOService; }
+
+private:
+  nsCString mScheme;
+  uint32_t mFlags;
+  nsInterfaceHashtable<nsCStringHashKey,nsIURI> mSubstitutions;
+  nsCOMPtr<nsIIOService> mIOService;
+};
+
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile resolution
+class SubstitutingURL : public nsStandardURL
+{
+public:
+  SubstitutingURL() : nsStandardURL(true) {}
+  virtual nsStandardURL* StartClone();
+  virtual nsresult EnsureFile();
+  NS_IMETHOD GetClassIDNoAlloc(nsCID *aCID);
+};
+
+} // namespace mozilla
+
+#endif /* SubstitutingProtocolHandler_h___ */
--- a/netwerk/protocol/res/moz.build
+++ b/netwerk/protocol/res/moz.build
@@ -1,22 +1,24 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIResProtocolHandler.idl',
+    'nsISubstitutingProtocolHandler.idl',
 ]
 
 XPIDL_MODULE = 'necko_res'
 
 SOURCES += [
     'nsResProtocolHandler.cpp',
+    'SubstitutingProtocolHandler.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
--- a/netwerk/protocol/res/nsIResProtocolHandler.idl
+++ b/netwerk/protocol/res/nsIResProtocolHandler.idl
@@ -1,45 +1,14 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
-#include "nsIProtocolHandler.idl"
+#include "nsISubstitutingProtocolHandler.idl"
 
 /**
  * Protocol handler interface for the resource:// protocol
  */
-[scriptable, uuid(067ca872-e947-4bd6-8946-a479cb6ba5dd)]
-interface nsIResProtocolHandler : nsIProtocolHandler
+[scriptable, uuid(241d34ac-9ed5-46d7-910c-7a9d914aa0c5)]
+interface nsIResProtocolHandler : nsISubstitutingProtocolHandler
 {
-    /**
-     * 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.
-     */
-    void setSubstitution(in ACString root, in nsIURI baseURI);
-
-    /**
-     * Gets the substitution for the root key.
-     *
-     * @throws NS_ERROR_NOT_AVAILABLE if none exists.
-     */
-    nsIURI getSubstitution(in ACString root);
-
-    /**
-     * Returns TRUE if the substitution exists and FALSE otherwise.
-     */
-    boolean hasSubstitution(in ACString root);
-
-    /**
-     * Utility function to resolve a resource URI.  A resolved URI is not 
-     * guaranteed to reference a resource that exists (ie. opening a channel to
-     * the resolved URI may fail).
-     *
-     * @throws NS_ERROR_NOT_AVAILABLE if resURI.host() is an unknown root key.
-     */
-    AUTF8String resolveURI(in nsIURI resURI);
 };
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIProtocolHandler.idl"
+
+/**
+ * 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
+{
+  /**
+   * 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.
+   */
+  void setSubstitution(in ACString root, in nsIURI baseURI);
+
+  /**
+   * Gets the substitution for the root key.
+   *
+   * @throws NS_ERROR_NOT_AVAILABLE if none exists.
+   */
+  nsIURI getSubstitution(in ACString root);
+
+  /**
+   * Returns TRUE if the substitution exists and FALSE otherwise.
+   */
+  boolean hasSubstitution(in ACString root);
+
+  /**
+   * Utility function to resolve a substituted URI.  A resolved URI is not
+   * guaranteed to reference a resource that exists (ie. opening a channel to
+   * the resolved URI may fail).
+   *
+   * @throws NS_ERROR_NOT_AVAILABLE if resURI.host() is an unknown root key.
+   */
+  AUTF8String resolveURI(in nsIURI resURI);
+};
--- a/netwerk/protocol/res/nsResProtocolHandler.cpp
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -16,118 +16,23 @@
 #include "nsEscape.h"
 
 #include "mozilla/Omnijar.h"
 
 using mozilla::dom::ContentParent;
 using mozilla::LogLevel;
 using mozilla::unused;
 
-static NS_DEFINE_CID(kResURLCID, NS_RESURL_CID);
-
-static nsResProtocolHandler *gResHandler = nullptr;
-
-//
-// Log module for Resource Protocol logging...
-//
-// To enable logging (see prlog.h for full details):
-//
-//    set NSPR_LOG_MODULES=nsResProtocol:5
-//    set NSPR_LOG_FILE=log.txt
-//
-// this enables LogLevel::Debug level information and places all output in
-// the file log.txt
-//
-static PRLogModuleInfo *gResLog;
-
 #define kAPP           NS_LITERAL_CSTRING("app")
 #define kGRE           NS_LITERAL_CSTRING("gre")
 
-//----------------------------------------------------------------------------
-// nsResURL : overrides nsStandardURL::GetFile to provide nsIFile resolution
-//----------------------------------------------------------------------------
-
-nsresult
-nsResURL::EnsureFile()
-{
-    nsresult rv;
-
-    NS_ENSURE_TRUE(gResHandler, NS_ERROR_NOT_AVAILABLE);
-
-    nsAutoCString spec;
-    rv = gResHandler->ResolveURI(this, spec);
-    if (NS_FAILED(rv))
-        return rv;
-
-    nsAutoCString scheme;
-    rv = net_ExtractURLScheme(spec, nullptr, nullptr, &scheme);
-    if (NS_FAILED(rv))
-        return rv;
-
-    // Bug 585869:
-    // In most cases, the scheme is jar if it's not file.
-    // Regardless, net_GetFileFromURLSpec should be avoided
-    // when the scheme isn't file.
-    if (!scheme.EqualsLiteral("file"))
-        return NS_ERROR_NO_INTERFACE;
-
-    rv = net_GetFileFromURLSpec(spec, getter_AddRefs(mFile));
-#ifdef DEBUG_bsmedberg
-    if (NS_SUCCEEDED(rv)) {
-        bool exists = true;
-        mFile->Exists(&exists);
-        if (!exists) {
-            printf("resource %s doesn't exist!\n", spec.get());
-        }
-    }
-#endif
-
-    return rv;
-}
-
-/* virtual */ nsStandardURL*
-nsResURL::StartClone()
-{
-    nsResURL *clone = new nsResURL();
-    return clone;
-}
-
-NS_IMETHODIMP 
-nsResURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
-{
-    *aClassIDNoAlloc = kResURLCID;
-    return NS_OK;
-}
-
-//----------------------------------------------------------------------------
-// nsResProtocolHandler <public>
-//----------------------------------------------------------------------------
-
-nsResProtocolHandler::nsResProtocolHandler()
-    : mSubstitutions(16)
-{
-    gResLog = PR_NewLogModule("nsResProtocol");
-
-    NS_ASSERTION(!gResHandler, "res handler already created!");
-    gResHandler = this;
-}
-
-nsResProtocolHandler::~nsResProtocolHandler()
-{
-    gResHandler = nullptr;
-}
-
 nsresult
 nsResProtocolHandler::Init()
 {
     nsresult rv;
-
-    mIOService = do_GetIOService(&rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     nsAutoCString appURI, greURI;
     rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, appURI);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, greURI);
     NS_ENSURE_SUCCESS(rv, rv);
 
     //
     // make resource:/// point to the application directory or omnijar
@@ -160,300 +65,38 @@ nsResProtocolHandler::Init()
     // but once I finish multiple chrome registration I'm not sure that it is needed
 
     // XXX dveditz: resource://pchrome/ defeats profile directory salting
     // if web content can load it. Tread carefully.
 
     return rv;
 }
 
-static PLDHashOperator
-EnumerateSubstitution(const nsACString& aKey,
-                      nsIURI* aURI,
-                      void* aArg)
-{
-    nsTArray<ResourceMapping>* resources =
-            static_cast<nsTArray<ResourceMapping>*>(aArg);
-    SerializedURI uri;
-    if (aURI) {
-        aURI->GetSpec(uri.spec);
-        aURI->GetOriginCharset(uri.charset);
-    }
-
-    ResourceMapping resource = {
-        nsCString(aKey), uri
-    };
-    resources->AppendElement(resource);
-    return (PLDHashOperator)PL_DHASH_NEXT;
-}
-
-void
-nsResProtocolHandler::CollectSubstitutions(InfallibleTArray<ResourceMapping>& aResources)
-{
-    mSubstitutions.EnumerateRead(&EnumerateSubstitution, &aResources);
-}
-
 //----------------------------------------------------------------------------
 // nsResProtocolHandler::nsISupports
 //----------------------------------------------------------------------------
 
-NS_IMPL_ISUPPORTS(nsResProtocolHandler,
-                  nsIResProtocolHandler,
-                  nsIProtocolHandler,
-                  nsISupportsWeakReference)
-
-//----------------------------------------------------------------------------
-// nsResProtocolHandler::nsIProtocolHandler
-//----------------------------------------------------------------------------
-
-NS_IMETHODIMP
-nsResProtocolHandler::GetScheme(nsACString &result)
-{
-    result.AssignLiteral("resource");
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsResProtocolHandler::GetDefaultPort(int32_t *result)
-{
-    *result = -1;        // no port for res: URLs
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsResProtocolHandler::GetProtocolFlags(uint32_t *result)
-{
-    // XXXbz Is this really true for all resource: URIs?  Could we
-    // somehow give different flags to some of them?
-    *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE;
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsResProtocolHandler::NewURI(const nsACString &aSpec,
-                             const char *aCharset,
-                             nsIURI *aBaseURI,
-                             nsIURI **result)
-{
-    nsresult rv;
-
-    nsRefPtr<nsResURL> resURL = new nsResURL();
-    if (!resURL)
-        return NS_ERROR_OUT_OF_MEMORY;
-
-    // unescape any %2f and %2e to make sure nsStandardURL coalesces them.
-    // Later net_GetFileFromURLSpec() will do a full unescape and we want to
-    // treat them the same way the file system will. (bugs 380994, 394075)
-    nsAutoCString spec;
-    const char *src = aSpec.BeginReading();
-    const char *end = aSpec.EndReading();
-    const char *last = src;
-
-    spec.SetCapacity(aSpec.Length()+1);
-    for ( ; src < end; ++src) {
-        if (*src == '%' && (src < end-2) && *(src+1) == '2') {
-           char ch = '\0';
-           if (*(src+2) == 'f' || *(src+2) == 'F')
-             ch = '/';
-           else if (*(src+2) == 'e' || *(src+2) == 'E')
-             ch = '.';
-
-           if (ch) {
-             if (last < src)
-               spec.Append(last, src-last);
-             spec.Append(ch);
-             src += 2;
-             last = src+1; // src will be incremented by the loop
-           }
-        }
-    }
-    if (last < src)
-      spec.Append(last, src-last);
-
-    rv = resURL->Init(nsIStandardURL::URLTYPE_STANDARD, -1, spec, aCharset, aBaseURI);
-    if (NS_SUCCEEDED(rv)) {
-        resURL.forget(result);
-    }
-    return rv;
-}
-
-NS_IMETHODIMP
-nsResProtocolHandler::NewChannel2(nsIURI* uri,
-                                  nsILoadInfo* aLoadInfo,
-                                  nsIChannel** result)
-{
-    NS_ENSURE_ARG_POINTER(uri);
-    nsAutoCString spec;
-    nsresult rv = ResolveURI(uri, spec);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIURI> newURI;
-    rv = NS_NewURI(getter_AddRefs(newURI), spec);
-    NS_ENSURE_SUCCESS(rv, rv);
+NS_IMPL_QUERY_INTERFACE(nsResProtocolHandler, nsIResProtocolHandler,
+                        nsISubstitutingProtocolHandler, nsIProtocolHandler,
+                        nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
 
-    rv = NS_NewChannelInternal(result,
-                               newURI,
-                               aLoadInfo);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsLoadFlags loadFlags = 0;
-    (*result)->GetLoadFlags(&loadFlags);
-    (*result)->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
-    return (*result)->SetOriginalURI(uri);
-}
-
-NS_IMETHODIMP
-nsResProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
-{
-    return NewChannel2(uri, nullptr, result);
-}
-
-NS_IMETHODIMP 
-nsResProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
-{
-    // don't override anything.  
-    *_retval = false;
-    return NS_OK;
-}
-
-//----------------------------------------------------------------------------
-// nsResProtocolHandler::nsIResProtocolHandler
-//----------------------------------------------------------------------------
-
-static void
-SendResourceSubstitution(const nsACString& root, nsIURI* baseURI)
+nsresult
+nsResProtocolHandler::GetSubstitutionInternal(const nsACString& root, nsIURI **result)
 {
-    if (GeckoProcessType_Content == XRE_GetProcessType()) {
-        return;
-    }
-
-    ResourceMapping resourceMapping;
-    resourceMapping.resource = root;
-    if (baseURI) {
-        baseURI->GetSpec(resourceMapping.resolvedURI.spec);
-        baseURI->GetOriginCharset(resourceMapping.resolvedURI.charset);
-    }
-
-    nsTArray<ContentParent*> parents;
-    ContentParent::GetAll(parents);
-    if (!parents.Length()) {
-        return;
-    }
-
-    for (uint32_t i = 0; i < parents.Length(); i++) {
-        unused << parents[i]->SendRegisterChromeItem(resourceMapping);
-    }
-}
-
-NS_IMETHODIMP
-nsResProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI)
-{
-    if (!baseURI) {
-        mSubstitutions.Remove(root);
-        SendResourceSubstitution(root, baseURI);
-        return NS_OK;
-    }
-
-    // If baseURI isn't a resource URI, we can set the substitution immediately.
-    nsAutoCString scheme;
-    nsresult rv = baseURI->GetScheme(scheme);
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (!scheme.EqualsLiteral("resource")) {
-        mSubstitutions.Put(root, baseURI);
-        SendResourceSubstitution(root, baseURI);
-        return NS_OK;
-    }
-
-    // baseURI is a resource 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);
-    SendResourceSubstitution(root, newBaseURI);
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsResProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
-{
-    NS_ENSURE_ARG_POINTER(result);
-
-    if (mSubstitutions.Get(root, result))
-        return NS_OK;
-
     // try invoking the directory service for "resource:root"
 
     nsAutoCString key;
     key.AssignLiteral("resource:");
     key.Append(root);
 
     nsCOMPtr<nsIFile> file;
     nsresult rv = NS_GetSpecialDirectory(key.get(), getter_AddRefs(file));
     if (NS_FAILED(rv))
         return NS_ERROR_NOT_AVAILABLE;
         
-    rv = mIOService->NewFileURI(file, result);
+    rv = IOService()->NewFileURI(file, result);
     if (NS_FAILED(rv))
         return NS_ERROR_NOT_AVAILABLE;
 
     return NS_OK;
 }
-
-NS_IMETHODIMP
-nsResProtocolHandler::HasSubstitution(const nsACString& root, bool *result)
-{
-    NS_ENSURE_ARG_POINTER(result);
-
-    *result = mSubstitutions.Get(root, nullptr);
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsResProtocolHandler::ResolveURI(nsIURI *uri, nsACString &result)
-{
-    nsresult rv;
-
-    nsAutoCString host;
-    nsAutoCString path;
-
-    rv = uri->GetAsciiHost(host);
-    if (NS_FAILED(rv)) return rv;
-
-    rv = uri->GetPath(path);
-    if (NS_FAILED(rv)) return rv;
-
-    // Unescape the path so we can perform some checks on it.
-    nsAutoCString unescapedPath(path);
-    NS_UnescapeURL(unescapedPath);
-
-    // Don't misinterpret the filepath as an absolute URI.
-    if (unescapedPath.FindChar(':') != -1)
-        return NS_ERROR_MALFORMED_URI;
-
-    if (unescapedPath.FindChar('\\') != -1)
-        return NS_ERROR_MALFORMED_URI;
-
-    const char *p = path.get() + 1; // path always starts with a slash
-    NS_ASSERTION(*(p-1) == '/', "Path did not begin with a slash!");
-
-    if (*p == '/')
-        return NS_ERROR_MALFORMED_URI;
-
-    nsCOMPtr<nsIURI> baseURI;
-    rv = GetSubstitution(host, getter_AddRefs(baseURI));
-    if (NS_FAILED(rv)) return rv;
-
-    rv = baseURI->Resolve(nsDependentCString(p, path.Length()-1), result);
-
-    if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) {
-        nsAutoCString spec;
-        uri->GetAsciiSpec(spec);
-        MOZ_LOG(gResLog, LogLevel::Debug,
-               ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get()));
-    }
-    return rv;
-}
--- a/netwerk/protocol/res/nsResProtocolHandler.h
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -1,51 +1,39 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #ifndef nsResProtocolHandler_h___
 #define nsResProtocolHandler_h___
 
+#include "SubstitutingProtocolHandler.h"
+
 #include "nsIResProtocolHandler.h"
 #include "nsInterfaceHashtable.h"
 #include "nsWeakReference.h"
 #include "nsStandardURL.h"
 
-class nsIIOService;
-struct ResourceMapping;
-
-// nsResURL : overrides nsStandardURL::GetFile to provide nsIFile resolution
-class nsResURL : public nsStandardURL
+struct SubstitutionMapping;
+class nsResProtocolHandler final : public nsIResProtocolHandler,
+                                   public mozilla::SubstitutingProtocolHandler,
+                                   public nsSupportsWeakReference
 {
 public:
-    nsResURL() : nsStandardURL(true) {}
-    virtual nsStandardURL* StartClone();
-    virtual nsresult EnsureFile();
-    NS_IMETHOD GetClassIDNoAlloc(nsCID *aCID);
-};
-
-class nsResProtocolHandler final : public nsIResProtocolHandler, public nsSupportsWeakReference
-{
-public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIPROTOCOLHANDLER
+    NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIRESPROTOCOLHANDLER
 
-    nsResProtocolHandler();
+    NS_FORWARD_NSIPROTOCOLHANDLER(mozilla::SubstitutingProtocolHandler::)
+    NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(mozilla::SubstitutingProtocolHandler::)
+
+    nsResProtocolHandler()
+      : SubstitutingProtocolHandler("resource", URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE)
+    {}
 
     nsresult Init();
 
-    void CollectSubstitutions(InfallibleTArray<ResourceMapping>& aResources);
-
-private:
-    virtual ~nsResProtocolHandler();
-
-    nsresult Init(nsIFile *aOmniJar);
-    nsresult AddSpecialDir(const char* aSpecialDir, const nsACString& aSubstitution);
-    nsInterfaceHashtable<nsCStringHashKey,nsIURI> mSubstitutions;
-    nsCOMPtr<nsIIOService> mIOService;
-
-    friend class nsResURL;
+protected:
+    nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override;
+    virtual ~nsResProtocolHandler() {}
 };
 
 #endif /* nsResProtocolHandler_h___ */