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 id29087
push usercbook@mozilla.com
push dateWed, 22 Jul 2015 12:01:23 +0000
treeherdermozilla-central@e7434cafdf2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1161831
milestone42.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 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___ */