Bug 1334550 - Part 1 - Proxy moz-extension protocol requests to the parent process; r=jimm, r=mayhemer, r=kris draft
authorHaik Aftandilian <haftandilian@mozilla.com>
Fri, 23 Jun 2017 17:10:54 -0700
changeset 600028 dabbed20a916640eda8353268366f705a6e7835e
parent 599416 7455c74d833a9db4e02be17eda14588c7ef0de76
child 600029 5310b1d7ea019098a4e0822f7999c84bb890c798
push id65664
push userhaftandilian@mozilla.com
push dateSat, 24 Jun 2017 00:11:32 +0000
reviewersjimm, mayhemer, kris
bugs1334550
milestone56.0a1
Bug 1334550 - Part 1 - Proxy moz-extension protocol requests to the parent process; r=jimm, r=mayhemer, r=kris Changes ExtensionProtocolHandler to use remote streams for moz-extension loads of file and JAR URI's to allow for filesystem read-access sandboxing. Adds messaging to PNecko to allow child processes to request an input stream or file descriptor for moz-extension URI's. Add ExtensionProtocolHandler singleton so that NeckoParent can call methods directly and ExtensionProtocolHandler::NewFD can use a new member variable |mFileOpenerThread| to open files. Adds FileDescriptorFile, a limited implementation of nsIFile that wraps a file descriptor, to be sideloaded into nsJARChannels so that extension JAR files can be read using a file descriptor without accessing the filesystem directly. MozReview-Commit-ID: 1pcnIpjz2yR
modules/libjar/nsIJARChannel.idl
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
modules/libpref/init/all.js
netwerk/build/nsNetModule.cpp
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/protocol/res/ExtensionProtocolHandler.cpp
netwerk/protocol/res/ExtensionProtocolHandler.h
netwerk/protocol/res/moz.build
xpcom/io/FileDescriptorFile.cpp
xpcom/io/FileDescriptorFile.h
xpcom/io/moz.build
--- a/modules/libjar/nsIJARChannel.idl
+++ b/modules/libjar/nsIJARChannel.idl
@@ -16,17 +16,20 @@ interface nsIJARChannel : nsIChannel
      * by the server for a remote JAR is not of an expected type).  Scripting,
      * redirects, and plugins should be disabled when loading from this
      * channel.
      */
     [infallible] readonly attribute boolean isUnsafe;
 
     /**
      * Returns the JAR file.  May be null if the jar is remote.
+     * Setting the JAR file is optional and overrides the JAR
+     * file used for local file JARs. Setting the JAR file after
+     * the channel has been opened is not permitted.
      */
-    readonly attribute nsIFile jarFile;
+    attribute nsIFile jarFile;
 
     /**
      * Returns the zip entry if the file is synchronously accessible.
      * This will work even without opening the channel.
      */
     readonly attribute nsIZipEntry zipEntry;
 };
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -341,16 +341,22 @@ nsJARChannel::LookupFile(bool aAllowAsyn
         return rv;
 
     // The name of the JAR entry must not contain URL-escaped characters:
     // we're moving from URL domain to a filename domain here. nsStandardURL
     // does basic escaping by default, which breaks reading zipped files which
     // have e.g. spaces in their filenames.
     NS_UnescapeURL(mJarEntry);
 
+    if (mJarFileOverride) {
+        mJarFile = mJarFileOverride;
+        LOG(("nsJARChannel::LookupFile [this=%p] Overriding mJarFile\n", this));
+        return NS_OK;
+    }
+
     // try to get a nsIFile directly from the url, which will often succeed.
     {
         nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI);
         if (fileURL)
             fileURL->GetFile(getter_AddRefs(mJarFile));
     }
 
     // try to handle a nested jar
@@ -880,16 +886,27 @@ nsJARChannel::GetIsUnsafe(bool *isUnsafe
 NS_IMETHODIMP
 nsJARChannel::GetJarFile(nsIFile **aFile)
 {
     NS_IF_ADDREF(*aFile = mJarFile);
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsJARChannel::SetJarFile(nsIFile *aFile)
+{
+    if (mOpened) {
+        return NS_ERROR_IN_PROGRESS;
+    }
+    mJarFileOverride = aFile;
+    return NS_OK;
+}
+
+
+NS_IMETHODIMP
 nsJARChannel::GetZipEntry(nsIZipEntry **aZipEntry)
 {
     nsresult rv = LookupFile(false);
     if (NS_FAILED(rv))
         return rv;
 
     if (!mJarFile)
         return NS_ERROR_NOT_AVAILABLE;
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -46,16 +46,18 @@ public:
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSITHREADRETARGETABLEREQUEST
     NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
 
     nsJARChannel();
 
     nsresult Init(nsIURI *uri);
 
+    void SetFile(nsIFile *file);
+
 private:
     virtual ~nsJARChannel();
 
     nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
     nsresult LookupFile(bool aAllowAsync);
     nsresult OpenLocalFile();
     void NotifyError(nsresult aError);
     void FireOnProgress(uint64_t aProgress);
@@ -93,16 +95,17 @@ private:
     bool                            mIsUnsafe;
 
     mozilla::net::MemoryDownloader::Data mTempMem;
     nsCOMPtr<nsIInputStreamPump>    mPump;
     // mRequest is only non-null during OnStartRequest, so we'll have a pointer
     // to the request if we get called back via RetargetDeliveryTo.
     nsCOMPtr<nsIRequest>            mRequest;
     nsCOMPtr<nsIFile>               mJarFile;
+    nsCOMPtr<nsIFile>               mJarFileOverride;
     nsCOMPtr<nsIURI>                mJarBaseURI;
     nsCString                       mJarEntry;
     nsCString                       mInnerJarEntry;
 
     // True if this channel should not download any remote files.
     bool                            mBlockRemoteFiles;
 };
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4858,16 +4858,18 @@ pref("extensions.allow-non-mpc-extension
 pref("extensions.webextensions.keepStorageOnUninstall", false);
 pref("extensions.webextensions.keepUuidOnUninstall", false);
 // Redirect basedomain used by identity api
 pref("extensions.webextensions.identity.redirectDomain", "extensions.allizom.org");
 // Whether or not webextension themes are supported.
 pref("extensions.webextensions.themes.enabled", false);
 pref("extensions.webextensions.themes.icons.enabled", false);
 pref("extensions.webextensions.remote", false);
+// Whether or not the moz-extension resource loads are remoted
+pref("extensions.webextensions.protocol.remote", true);
 
 pref("layers.popups.compositing.enabled", false);
 
 // Report Site Issue button
 pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
 #if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD)
 pref("extensions.webcompat-reporter.enabled", true);
 #else
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -305,17 +305,18 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Name
 #ifdef NECKO_PROTOCOL_res
 // resource
 #include "nsResProtocolHandler.h"
 #include "ExtensionProtocolHandler.h"
 #include "SubstitutingProtocolHandler.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsResProtocolHandler, Init)
 
 namespace mozilla {
-NS_GENERIC_FACTORY_CONSTRUCTOR(ExtensionProtocolHandler)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ExtensionProtocolHandler,
+    ExtensionProtocolHandler::GetSingleton)
 NS_GENERIC_FACTORY_CONSTRUCTOR(SubstitutingURL)
 } // namespace mozilla
 #endif
 
 #ifdef NECKO_PROTOCOL_device
 #include "nsDeviceProtocolHandler.h"
 typedef mozilla::net::nsDeviceProtocolHandler nsDeviceProtocolHandler;
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceProtocolHandler)
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -3,16 +3,18 @@
 
 /* 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 "necko-config.h"
 #include "nsHttp.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/ExtensionProtocolHandler.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/net/HttpChannelParent.h"
 #include "mozilla/net/CookieServiceParent.h"
 #include "mozilla/net/WyciwygChannelParent.h"
 #include "mozilla/net/FTPChannelParent.h"
 #include "mozilla/net/WebSocketChannelParent.h"
 #include "mozilla/net/WebSocketEventListenerParent.h"
 #include "mozilla/net/DataChannelParent.h"
@@ -33,16 +35,17 @@
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/network/TCPSocketParent.h"
 #include "mozilla/dom/network/TCPServerSocketParent.h"
 #include "mozilla/dom/network/UDPSocketParent.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/LoadContext.h"
+#include "mozilla/MozPromise.h"
 #include "nsPrintfCString.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsEscape.h"
 #include "SerializedLoadContext.h"
 #include "nsAuthInformationHolder.h"
 #include "nsIAuthPromptCallback.h"
 #include "ContentPrincipal.h"
 #include "nsINetworkPredictor.h"
@@ -57,18 +60,20 @@ using mozilla::dom::TabContext;
 using mozilla::dom::TabParent;
 using mozilla::net::PTCPSocketParent;
 using mozilla::dom::TCPSocketParent;
 using mozilla::net::PTCPServerSocketParent;
 using mozilla::dom::TCPServerSocketParent;
 using mozilla::net::PUDPSocketParent;
 using mozilla::dom::UDPSocketParent;
 using mozilla::dom::workers::ServiceWorkerManager;
+using mozilla::ipc::AutoIPCStream;
 using mozilla::ipc::OptionalPrincipalInfo;
 using mozilla::ipc::PrincipalInfo;
+using mozilla::ipc::LoadInfoArgsToLoadInfo;
 using IPC::SerializedLoadContext;
 
 namespace mozilla {
 namespace net {
 
 // C++ file contents
 NeckoParent::NeckoParent()
 {
@@ -950,10 +955,106 @@ NeckoParent::RecvNotifyCurrentTopLevelOu
 {
   if (NS_FAILED(NS_NotifyCurrentTopLevelOuterContentWindowId(aWindowId))) {
     NS_WARNING("NS_NotifyCurrentTopLevelOuterContentWindowId failed!");
   }
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+NeckoParent::RecvGetExtensionStream(const URIParams& aURI,
+                                    const LoadInfoArgs& aLoadInfo,
+                                    GetExtensionStreamResolver&& aResolve)
+{
+  nsCOMPtr<nsIURI> deserializedURI = DeserializeURI(aURI);
+  if (!deserializedURI) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  nsCOMPtr<nsILoadInfo> deserializedLoadInfo;
+  nsresult rv;
+  rv = LoadInfoArgsToLoadInfo(aLoadInfo, getter_AddRefs(deserializedLoadInfo));
+  if (NS_FAILED(rv)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  RefPtr<ExtensionProtocolHandler> ph(ExtensionProtocolHandler::GetSingleton());
+  MOZ_ASSERT(ph);
+
+  // Ask the ExtensionProtocolHandler to give us a new input stream for
+  // this URI. The request comes from an ExtensionProtocolHandler in the
+  // child process, but is not guaranteed to be a valid moz-extension URI,
+  // and not guaranteed to represent a resource that the child should be
+  // allowed to access. The ExtensionProtocolHandler is responsible for
+  // validating the request. Specifically, only URI's for local files that
+  // an extension is allowed to access via moz-extension URI's should be
+  // accepted.
+  AutoIPCStream autoStream;
+  nsCOMPtr<nsIInputStream> inputStream;
+  bool terminateSender = true;
+  auto inputStreamOrReason = ph->NewStream(deserializedURI,
+                                           deserializedLoadInfo,
+                                           &terminateSender);
+  if (inputStreamOrReason.isOk()) {
+    inputStream = inputStreamOrReason.unwrap();
+    ContentParent* contentParent = static_cast<ContentParent*>(Manager());
+    Unused << autoStream.Serialize(inputStream, contentParent);
+  }
+
+  // If NewStream failed, we send back an invalid stream to the child so
+  // it can handle the error. MozPromise rejection is reserved for channel
+  // errors/disconnects.
+  aResolve(autoStream.TakeOptionalValue());
+
+  if (terminateSender) {
+    return IPC_FAIL_NO_REASON(this);
+  } else {
+    return IPC_OK();
+  }
+}
+
+mozilla::ipc::IPCResult
+NeckoParent::RecvGetExtensionFD(const URIParams& aURI,
+                                const OptionalLoadInfoArgs& aLoadInfo,
+                                GetExtensionFDResolver&& aResolve)
+{
+  nsCOMPtr<nsIURI> deserializedURI = DeserializeURI(aURI);
+  if (!deserializedURI) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  nsCOMPtr<nsILoadInfo> deserializedLoadInfo;
+  nsresult rv;
+  rv = LoadInfoArgsToLoadInfo(aLoadInfo, getter_AddRefs(deserializedLoadInfo));
+  if (NS_FAILED(rv)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  RefPtr<ExtensionProtocolHandler> ph(ExtensionProtocolHandler::GetSingleton());
+  MOZ_ASSERT(ph);
+
+  // Ask the ExtensionProtocolHandler to give us a new input stream for
+  // this URI. The request comes from an ExtensionProtocolHandler in the
+  // child process, but is not guaranteed to be a valid moz-extension URI,
+  // and not guaranteed to represent a resource that the child should be
+  // allowed to access. The ExtensionProtocolHandler is responsible for
+  // validating the request. Specifically, only URI's for local files that
+  // an extension is allowed to access via moz-extension URI's should be
+  // accepted.
+  bool terminateSender = true;
+  auto result = ph->NewFD(deserializedURI, deserializedLoadInfo,
+                          &terminateSender, aResolve);
+
+  if (result.isErr() && terminateSender) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  if (result.isErr()) {
+    FileDescriptor invalidFD;
+    aResolve(invalidFD);
+  }
+
+  return IPC_OK();
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -231,14 +231,25 @@ protected:
                                                 const ipc::OptionalURIParams& aSourceURI,
                                                 const PredictorPredictReason& aReason,
                                                 const OriginAttributes& aOriginAttributes) override;
   virtual mozilla::ipc::IPCResult RecvPredReset() override;
 
   virtual mozilla::ipc::IPCResult RecvRemoveRequestContext(const uint64_t& rcid) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyCurrentTopLevelOuterContentWindowId(const uint64_t& aWindowId) override;
+
+  /* WebExtensions */
+  virtual mozilla::ipc::IPCResult
+    RecvGetExtensionStream(const URIParams& aURI,
+                           const LoadInfoArgs& aLoadInfo,
+                           GetExtensionStreamResolver&& aResolve) override;
+
+  virtual mozilla::ipc::IPCResult
+    RecvGetExtensionFD(const URIParams& aURI,
+                       const OptionalLoadInfoArgs& aLoadInfo,
+                       GetExtensionFDResolver&& aResolve) override;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_NeckoParent_h
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -23,16 +23,17 @@ include protocol PDataChannel;
 include protocol PTransportProvider;
 include protocol PChildToParentStream; //FIXME: bug #792908
 include protocol PParentToChildStream; //FIXME: bug #792908
 include protocol PStunAddrsRequest;
 include protocol PFileChannel;
 
 include protocol PRtspController;
 include protocol PRtspChannel;
+include IPCStream;
 include URIParams;
 include NeckoChannelParams;
 include PBrowserOrId;
 include protocol PAltDataOutputStream;
 
 using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
@@ -122,16 +123,24 @@ parent:
   async RemoveRequestContext(uint64_t rcid);
 
   async PAltDataOutputStream(nsCString type, PHttpChannel channel);
 
   async PStunAddrsRequest();
 
   prio(high) async NotifyCurrentTopLevelOuterContentWindowId(uint64_t windowId);
 
+  /**
+   * WebExtension-specific remote resource loading
+   */
+  async GetExtensionStream(URIParams uri, LoadInfoArgs loadInfo) returns
+                          (OptionalIPCStream stream);
+  async GetExtensionFD(URIParams uri, OptionalLoadInfoArgs loadInfo) returns
+                      (FileDescriptor fd);
+
 child:
   /*
    * Bring up the http auth prompt for a nested remote mozbrowser.
    * NestedFrameId is the id corresponding to the PBrowser.  It is the same id
    * that was passed to the PBrowserOrId param in to the PHttpChannel constructor
    */
   async AsyncAuthPromptForNestedFrame(TabId nestedFrameId, nsCString uri,
                                       nsString realm, uint64_t callbackId);
--- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -1,39 +1,359 @@
 /* -*- 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 "ExtensionProtocolHandler.h"
 
+#include "mozilla/AbstractThread.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIParams.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/RefPtr.h"
+
+#include "FileDescriptor.h"
+#include "FileDescriptorFile.h"
+#include "LoadInfo.h"
+#include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIJARChannel.h"
 #include "nsIURL.h"
 #include "nsIChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIJARURI.h"
 #include "nsIStreamListener.h"
+#include "nsIThread.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsIStreamConverterService.h"
 #include "nsNetUtil.h"
-#include "LoadInfo.h"
+#include "prio.h"
 #include "SimpleChannel.h"
 
+#if defined(XP_WIN)
+#include "nsILocalFileWin.h"
+#endif
+
+#define EXTENSION_SCHEME "moz-extension"
+using mozilla::ipc::FileDescriptor;
+using OptionalIPCStream = mozilla::ipc::OptionalIPCStream;
+
 namespace mozilla {
+
+template <>
+class MOZ_MUST_USE_TYPE GenericErrorResult<nsresult>
+{
+  nsresult mErrorValue;
+
+  template<typename V, typename E2> friend class Result;
+
+public:
+  explicit GenericErrorResult(nsresult aErrorValue) : mErrorValue(aErrorValue) {}
+
+  operator nsresult() { return mErrorValue; }
+};
+
 namespace net {
 
 using extensions::URLInfo;
 
+StaticRefPtr<ExtensionProtocolHandler> ExtensionProtocolHandler::sSingleton;
+
+static inline Result<Ok, nsresult>
+WrapNSResult(PRStatus aRv)
+{
+    if (aRv != PR_SUCCESS) {
+        return Err(NS_ERROR_FAILURE);
+    }
+    return Ok();
+}
+
+static inline Result<Ok, nsresult>
+WrapNSResult(nsresult aRv)
+{
+    if (NS_FAILED(aRv)) {
+        return Err(aRv);
+    }
+    return Ok();
+}
+
+#define NS_TRY(expr) MOZ_TRY(WrapNSResult(expr))
+
+/**
+ * Helper class used with SimpleChannel to asynchronously obtain an input
+ * stream or file descriptor from the parent for a remote moz-extension load
+ * from the child.
+ */
+class ExtensionStreamGetter : public RefCounted<ExtensionStreamGetter>
+{
+  public:
+    // To use when getting a remote input stream for a resource
+    // in an unpacked extension.
+    ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+      : mURI(aURI)
+      , mLoadInfo(aLoadInfo)
+      , mIsJarChannel(false)
+    {
+      MOZ_ASSERT(aURI);
+      MOZ_ASSERT(aLoadInfo);
+    }
+
+    // To use when getting an FD for a packed extension JAR file
+    // in order to load a resource.
+    ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+                          already_AddRefed<nsIJARChannel>&& aJarChannel,
+                          nsIFile* aJarFile)
+      : mURI(aURI)
+      , mLoadInfo(aLoadInfo)
+      , mJarChannel(Move(aJarChannel))
+      , mJarFile(aJarFile)
+      , mIsJarChannel(true)
+    {
+      MOZ_ASSERT(aURI);
+      MOZ_ASSERT(aLoadInfo);
+      MOZ_ASSERT(mJarChannel);
+      MOZ_ASSERT(aJarFile);
+    }
+
+    ~ExtensionStreamGetter() {}
+
+    // Get an input stream or file descriptor from the parent asynchronously.
+    Result<Ok, nsresult> GetAsync(nsIStreamListener* aListener,
+                                  nsIChannel* aChannel);
+
+    // Handle an input stream being returned from the parent
+    void OnStream(nsIInputStream* aStream);
+
+    // Handle file descriptor being returned from the parent
+    void OnFD(const FileDescriptor& aFD);
+
+    MOZ_DECLARE_REFCOUNTED_TYPENAME(ExtensionStreamGetter)
+
+  private:
+    nsCOMPtr<nsIURI> mURI;
+    nsCOMPtr<nsILoadInfo> mLoadInfo;
+    nsCOMPtr<nsIJARChannel> mJarChannel;
+    nsCOMPtr<nsIFile> mJarFile;
+    nsCOMPtr<nsIStreamListener> mListener;
+    nsCOMPtr<nsIChannel> mChannel;
+    bool mIsJarChannel;
+};
+
+class ExtensionJARFileOpener : public nsISupports
+{
+public:
+  ExtensionJARFileOpener(nsIFile* aFile,
+                         NeckoParent::GetExtensionFDResolver& aResolve) :
+    mFile(aFile),
+    mResolve(aResolve)
+  {
+    MOZ_ASSERT(aFile);
+    MOZ_ASSERT(aResolve);
+  }
+
+  NS_IMETHOD OpenFile()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    AutoFDClose prFileDesc;
+
+#if defined(XP_WIN)
+    nsresult rv;
+    nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(mFile, &rv);
+    MOZ_ASSERT(winFile);
+    if (NS_SUCCEEDED(rv)) {
+      rv = winFile->OpenNSPRFileDescShareDelete(PR_RDONLY, 0,
+                                                &prFileDesc.rwget());
+    }
+#else
+    nsresult rv = mFile->OpenNSPRFileDesc(PR_RDONLY, 0, &prFileDesc.rwget());
+#endif /* XP_WIN */
+
+    if (NS_SUCCEEDED(rv)) {
+      mFD = FileDescriptor(FileDescriptor::PlatformHandleType(
+                           PR_FileDesc2NativeHandle(prFileDesc)));
+    }
+
+    nsCOMPtr<nsIRunnable> event =
+      mozilla::NewRunnableMethod("ExtensionJarFileFDResolver",
+        this, &ExtensionJARFileOpener::SendBackFD);
+
+    rv = NS_DispatchToMainThread(event, nsIEventTarget::DISPATCH_NORMAL);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread");
+    return NS_OK;
+  }
+
+  NS_IMETHOD SendBackFD()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    mResolve(mFD);
+    return NS_OK;
+  }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+private:
+  virtual ~ExtensionJARFileOpener() {}
+
+  nsCOMPtr<nsIFile> mFile;
+  NeckoParent::GetExtensionFDResolver mResolve;
+  FileDescriptor mFD;
+};
+
+NS_IMPL_ISUPPORTS(ExtensionJARFileOpener, nsISupports)
+
+// The amount of time, in milliseconds, that the file opener thread will remain
+// allocated after it is used. This value chosen because to match other uses
+// of LazyIdleThread.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+// Request an FD or input stream from the parent.
+Result<Ok, nsresult>
+ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener,
+                                nsIChannel* aChannel)
+{
+  MOZ_ASSERT(IsNeckoChild());
+
+  mListener = aListener;
+  mChannel = aChannel;
+
+  // Serialize the URI to send to parent
+  mozilla::ipc::URIParams uri;
+  SerializeURI(mURI, uri);
+
+  // Serialize the LoadInfo to send to parent
+  OptionalLoadInfoArgs loadInfo;
+  NS_TRY(mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfo));
+
+  RefPtr<ExtensionStreamGetter> self = this;
+  if (mIsJarChannel) {
+    // Request an FD for this moz-extension URI
+    gNeckoChild->SendGetExtensionFD(uri, loadInfo)->Then(
+      AbstractThread::MainThread(),
+      __func__,
+      [self] (const FileDescriptor& fd) {
+        self->OnFD(fd);
+      },
+      [self] (const mozilla::ipc::PromiseRejectReason) {
+        self->OnFD(FileDescriptor());
+      }
+    );
+    return Ok();
+  }
+
+  // Request an input stream for this moz-extension URI
+  gNeckoChild->SendGetExtensionStream(uri, loadInfo)->Then(
+    AbstractThread::MainThread(),
+    __func__,
+    [self] (const OptionalIPCStream& stream) {
+      nsCOMPtr<nsIInputStream> inputStream;
+      if (stream.type() == OptionalIPCStream::OptionalIPCStream::TIPCStream) {
+        inputStream = ipc::DeserializeIPCStream(stream);
+      }
+      self->OnStream(inputStream);
+    },
+    [self] (const mozilla::ipc::PromiseRejectReason) {
+      self->OnStream(nullptr);
+    }
+  );
+  return Ok();
+}
+
+// Handle an input stream sent from the parent.
+void
+ExtensionStreamGetter::OnStream(nsIInputStream* aStream)
+{
+  MOZ_ASSERT(IsNeckoChild());
+  MOZ_ASSERT(mListener);
+
+  // We must keep an owning reference to the listener
+  // until we pass it on to AsyncRead.
+  nsCOMPtr<nsIStreamListener> listener = mListener.forget();
+
+  MOZ_ASSERT(mChannel);
+
+  if (!aStream) {
+    // The parent didn't send us back a stream.
+    listener->OnStartRequest(mChannel, nullptr);
+    listener->OnStopRequest(mChannel, nullptr, NS_ERROR_FILE_ACCESS_DENIED);
+    mChannel->Cancel(NS_BINDING_ABORTED);
+    return;
+  }
+
+  nsCOMPtr<nsIInputStreamPump> pump;
+  nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), aStream);
+  if (NS_FAILED(rv)) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+    return;
+  }
+
+  rv = pump->AsyncRead(listener, nullptr);
+  if (NS_FAILED(rv)) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+  }
+}
+
+// Handle an FD sent from the parent.
+void
+ExtensionStreamGetter::OnFD(const FileDescriptor& aFD)
+{
+  MOZ_ASSERT(IsNeckoChild());
+  MOZ_ASSERT(mListener);
+  MOZ_ASSERT(mChannel);
+
+  if (!aFD.IsValid()) {
+    OnStream(nullptr);
+    return;
+  }
+
+  // We must keep an owning reference to the listener
+  // until we pass it on to AsyncOpen2.
+  nsCOMPtr<nsIStreamListener> listener = mListener.forget();
+
+  RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile);
+  mJarChannel->SetJarFile(fdFile);
+  nsresult rv = mJarChannel->AsyncOpen2(listener);
+  if (NS_FAILED(rv)) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+  }
+}
+
 NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler, nsISubstitutingProtocolHandler,
                         nsIProtocolHandler, nsIProtocolHandlerWithDynamicFlags,
                         nsISupportsWeakReference)
 NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
 NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
 
+already_AddRefed<ExtensionProtocolHandler>
+ExtensionProtocolHandler::GetSingleton()
+{
+  if (!sSingleton) {
+    sSingleton = new ExtensionProtocolHandler();
+    ClearOnShutdown(&sSingleton);
+  }
+  return do_AddRef(sSingleton.get());
+}
+
+ExtensionProtocolHandler::ExtensionProtocolHandler()
+  : SubstitutingProtocolHandler(EXTENSION_SCHEME)
+{
+  mUseRemoteFileChannels = IsNeckoChild() &&
+    Preferences::GetBool("extensions.webextensions.protocol.remote");
+}
+
 static inline ExtensionPolicyService&
 EPS()
 {
   return ExtensionPolicyService::GetSingleton();
 }
 
 nsresult
 ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags)
@@ -72,36 +392,68 @@ ExtensionProtocolHandler::ResolveSpecial
   if (aPathname.EqualsLiteral("/_generated_background_page.html")) {
     Unused << EPS().GetGeneratedBackgroundPageUrl(aHost, aResult);
     return !aResult.IsEmpty();
   }
 
   return false;
 }
 
-static inline Result<Ok, nsresult>
-WrapNSResult(nsresult aRv)
+// For file or JAR URI's, substitute in a remote channel.
+Result<Ok, nsresult>
+ExtensionProtocolHandler::SubstituteRemoteChannel(nsIURI* aURI,
+                                                  nsILoadInfo* aLoadInfo,
+                                                  nsIChannel** aRetVal)
 {
-  if (NS_FAILED(aRv)) {
-    return Err(aRv);
+  MOZ_ASSERT(IsNeckoChild());
+  NS_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
+  NS_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+
+  nsAutoCString unResolvedSpec;
+  NS_TRY(aURI->GetSpec(unResolvedSpec));
+
+  nsAutoCString resolvedSpec;
+  NS_TRY(ResolveURI(aURI, resolvedSpec));
+
+  // Use the target URI scheme to determine if this is a packed or unpacked
+  // extension URI. For unpacked extensions, we'll request an input stream
+  // from the parent. For a packed extension, we'll request a file descriptor
+  // for the JAR file.
+  nsAutoCString scheme;
+  NS_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+  if (scheme.EqualsLiteral("file")) {
+    // Unpacked extension
+    SubstituteRemoteFileChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
+    return Ok();
   }
+
+  if (scheme.EqualsLiteral("jar")) {
+    // Packed extension
+    return SubstituteRemoteJarChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
+  }
+
+  // Only unpacked resource files and JAR files are remoted.
+  // No other moz-extension loads should be reading from the filesystem.
   return Ok();
 }
 
-#define NS_TRY(expr) MOZ_TRY(WrapNSResult(expr))
-
 nsresult
 ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
                                             nsILoadInfo* aLoadInfo,
                                             nsIChannel** result)
 {
   nsresult rv;
   nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (mUseRemoteFileChannels) {
+    MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, result));
+  }
+
   nsAutoCString ext;
   rv = url->GetFileExtension(ext);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!ext.LowerCaseEqualsLiteral("css")) {
     return NS_OK;
   }
 
@@ -141,12 +493,247 @@ ExtensionProtocolHandler::SubstituteChan
     (*result)->SetLoadInfo(loadInfo);
   }
 
   channel.swap(*result);
 
   return NS_OK;
 }
 
+Result<nsCOMPtr<nsIInputStream>, nsresult>
+ExtensionProtocolHandler::NewStream(nsIURI* aChildURI,
+                                    nsILoadInfo* aChildLoadInfo,
+                                    bool* aTerminateSender)
+{
+  MOZ_ASSERT(!IsNeckoChild());
+  NS_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
+  NS_TRY(aChildLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+  NS_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
+
+  *aTerminateSender = true;
+  nsresult rv;
+
+  // We should never receive a URI that isn't for a moz-extension because
+  // these requests ordinarily come from the child's ExtensionProtocolHandler.
+  // Ensure this request is for a moz-extension URI. A rogue child process
+  // could send us any URI.
+  bool isExtScheme = false;
+  if (NS_FAILED(aChildURI->SchemeIs(EXTENSION_SCHEME, &isExtScheme)) ||
+      !isExtScheme) {
+    return Err(NS_ERROR_UNKNOWN_PROTOCOL);
+  }
+
+  // For errors after this point, we want to propagate the error to
+  // the child, but we don't force the child to be terminated because
+  // the error is likely to be due to a bug in the extension.
+  *aTerminateSender = false;
+
+  /*
+   * Make sure there is a substitution installed for the host found
+   * in the child's request URI and make sure the host resolves to
+   * a directory.
+   */
+
+  nsAutoCString host;
+  NS_TRY(aChildURI->GetAsciiHost(host));
+
+  // Lookup the directory this host string resolves to
+  nsCOMPtr<nsIURI> baseURI;
+  NS_TRY(GetSubstitution(host, getter_AddRefs(baseURI)));
+
+  // The result should be a file URL for the extension base dir
+  nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(baseURI, &rv);
+  NS_TRY(rv);
+
+  nsCOMPtr<nsIFile> extensionDir;
+  NS_TRY(fileURL->GetFile(getter_AddRefs(extensionDir)));
+
+  bool isDirectory = false;
+  NS_TRY(extensionDir->IsDirectory(&isDirectory));
+  if (!isDirectory) {
+    // The host should map to a directory for unpacked extensions
+    return Err(NS_ERROR_FILE_NOT_DIRECTORY);
+  }
+
+  /*
+   * Now get a channel for the resolved child URI and make sure the
+   * channel is a file channel.
+   */
+
+  nsCOMPtr<nsIPrincipal> childPrincipal;
+  NS_TRY(aChildLoadInfo->GetLoadingPrincipal(getter_AddRefs(childPrincipal)));
+  if (nsContentUtils::IsSystemPrincipal(childPrincipal)) {
+    return Err(NS_ERROR_FILE_ACCESS_DENIED);
+  }
+
+  nsCOMPtr<nsIChannel> channel;
+  NS_TRY(NS_NewChannelInternal(getter_AddRefs(channel),
+                               aChildURI,
+                               aChildLoadInfo));
+
+  // Channel should be a file channel. It should never be a JAR
+  // channel because we only request remote streams for unpacked
+  // extension resource loads where the URI resolves to a file.
+  nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel, &rv);
+  NS_TRY(rv);
+
+  nsCOMPtr<nsIFile> requestedFile;
+  NS_TRY(fileChannel->GetFile(getter_AddRefs(requestedFile)));
+
+  /*
+   * Make sure the file we resolved to is within the extension directory.
+   */
+
+  // Normalize paths for sane comparisons. nsIFile::Contains depends on
+  // it for reliable subpath checks.
+  NS_TRY(extensionDir->Normalize());
+  NS_TRY(requestedFile->Normalize());
+
+  bool isResourceFromExtensionDir = false;
+  NS_TRY(extensionDir->Contains(requestedFile, &isResourceFromExtensionDir));
+  if (!isResourceFromExtensionDir) {
+    return Err(NS_ERROR_FILE_ACCESS_DENIED);
+  }
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  NS_TRY(NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+                                    requestedFile,
+                                    PR_RDONLY,
+                                    -1,
+                                    nsIFileInputStream::DEFER_OPEN));
+
+  return inputStream;
+}
+
+Result<Ok, nsresult>
+ExtensionProtocolHandler::NewFD(nsIURI* aChildURI,
+                                nsILoadInfo* aChildLoadInfo,
+                                bool* aTerminateSender,
+                                NeckoParent::GetExtensionFDResolver& aResolve)
+{
+  MOZ_ASSERT(!IsNeckoChild());
+  NS_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
+  NS_TRY(aChildLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+  NS_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
+
+  *aTerminateSender = true;
+  nsresult rv;
+
+  // Ensure this is a moz-extension URI
+  bool isExtScheme = false;
+  if (NS_FAILED(aChildURI->SchemeIs(EXTENSION_SCHEME, &isExtScheme)) ||
+      !isExtScheme) {
+    return Err(NS_ERROR_UNKNOWN_PROTOCOL);
+  }
+
+  // For errors after this point, we want to propagate the error to
+  // the child, but we don't force the child to be terminated.
+  *aTerminateSender = false;
+
+  nsAutoCString host;
+  NS_TRY(aChildURI->GetAsciiHost(host));
+
+  // We expect the host string to map to a JAR file because the URI
+  // should refer to a web accessible resource for an enabled extension.
+  nsCOMPtr<nsIURI> subURI;
+  NS_TRY(GetSubstitution(host, getter_AddRefs(subURI)));
+
+  nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(subURI, &rv);
+  NS_TRY(rv);
+
+  nsCOMPtr<nsIURI> innerFileURI;
+  NS_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+  nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+  NS_TRY(rv);
+
+  nsCOMPtr<nsIFile> jarFile;
+  NS_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+  if (!mFileOpenerThread) {
+    mFileOpenerThread =
+      new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+                         NS_LITERAL_CSTRING("ExtensionProtocolHandler"));
+  }
+
+  RefPtr<ExtensionJARFileOpener> fileOpener =
+    new ExtensionJARFileOpener(jarFile, aResolve);
+
+  nsCOMPtr<nsIRunnable> event =
+    mozilla::NewRunnableMethod("ExtensionJarFileOpener",
+        fileOpener, &ExtensionJARFileOpener::OpenFile);
+
+  NS_TRY(mFileOpenerThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL));
+
+  return Ok();
+}
+
+static void
+NewSimpleChannel(nsIURI* aURI,
+                 nsILoadInfo* aLoadinfo,
+                 ExtensionStreamGetter* aStreamGetter,
+                 nsIChannel** aRetVal)
+{
+  nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+    aURI, aLoadinfo, aStreamGetter,
+    [] (nsIStreamListener* listener, nsIChannel* channel,
+        ExtensionStreamGetter* getter) -> RequestOrReason {
+      MOZ_TRY(getter->GetAsync(listener, channel));
+      return RequestOrReason(nullptr);
+
+    });
+  channel.swap(*aRetVal);
+}
+
+void
+ExtensionProtocolHandler::SubstituteRemoteFileChannel(nsIURI* aURI,
+                                                      nsILoadInfo* aLoadinfo,
+                                                      nsACString& aResolvedFileSpec,
+                                                      nsIChannel** aRetVal)
+{
+  MOZ_ASSERT(IsNeckoChild());
+
+  RefPtr<ExtensionStreamGetter> streamGetter =
+    new ExtensionStreamGetter(aURI, aLoadinfo);
+
+  NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
+}
+
+Result<Ok, nsresult>
+ExtensionProtocolHandler::SubstituteRemoteJarChannel(nsIURI* aURI,
+                                                     nsILoadInfo* aLoadinfo,
+                                                     nsACString& aResolvedSpec,
+                                                     nsIChannel** aRetVal)
+{
+  MOZ_ASSERT(IsNeckoChild());
+  nsresult rv;
+
+  // Build a JAR URI for this jar:file:// URI and use it to extract the
+  // inner file URI.
+  nsCOMPtr<nsIURI> uri;
+  NS_TRY(NS_NewURI(getter_AddRefs(uri), aResolvedSpec));
+
+  nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
+  NS_TRY(rv);
+
+  nsCOMPtr<nsIURI> innerFileURI;
+  NS_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+  nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+  NS_TRY(rv);
+
+  nsCOMPtr<nsIFile> jarFile;
+  NS_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+  nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(*aRetVal, &rv);
+  NS_TRY(rv);
+
+  RefPtr<ExtensionStreamGetter> streamGetter =
+    new ExtensionStreamGetter(aURI, aLoadinfo, jarChannel.forget(), jarFile);
+
+  NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
+  return Ok();
+}
+
 #undef NS_TRY
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/res/ExtensionProtocolHandler.h
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.h
@@ -1,48 +1,167 @@
 /* -*- 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 ExtensionProtocolHandler_h___
 #define ExtensionProtocolHandler_h___
 
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/LazyIdleThread.h"
 #include "SubstitutingProtocolHandler.h"
-#include "nsWeakReference.h"
 
 namespace mozilla {
 namespace net {
 
 class ExtensionProtocolHandler final : public nsISubstitutingProtocolHandler,
                                        public nsIProtocolHandlerWithDynamicFlags,
                                        public SubstitutingProtocolHandler,
                                        public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
   NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::)
   NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::)
 
-  ExtensionProtocolHandler() : SubstitutingProtocolHandler("moz-extension") {}
+  static already_AddRefed<ExtensionProtocolHandler> GetSingleton();
+
+  /**
+   * To be called in the parent process to obtain an input stream for a
+   * a web accessible resource from an unpacked WebExtension dir.
+   *
+   * @param aChildURI a moz-extension URI sent from the child that refers
+   *        to a web accessible resource file in an enabled unpacked extension
+   * @param aChildLoadInfo the loadinfo for the request sent from the child
+   * @param aTerminateSender out param set to true when the params are invalid
+   *        and indicate the child should be terminated. If |aChildURI| is
+   *        not a moz-extension URI, the child is in an invalid state and
+   *        should be terminated.
+   * @return NS_OK with |aTerminateSender| set to false on success. On
+   *         failure, returns an error and sets |aTerminateSender| to indicate
+   *         whether or not the child process should be terminated.
+   *         A moz-extension URI from the child that doesn't resolve to a
+   *         resource file within the extension could be the result of a bug
+   *         in the extension and doesn't result in |aTerminateSender| being
+   *         set to true.
+   */
+  Result<nsCOMPtr<nsIInputStream>, nsresult> NewStream(nsIURI* aChildURI,
+                                                       nsILoadInfo* aChildLoadInfo,
+                                                       bool* aTerminateSender);
+
+  /**
+   * To be called in the parent process to obtain a file descriptor for an
+   * enabled WebExtension JAR file.
+   *
+   * @param aChildURI a moz-extension URI sent from the child that refers
+   *        to a web accessible resource file in an enabled unpacked extension
+   * @param aChildLoadInfo the loadinfo for the request sent from the child
+   * @param aTerminateSender out param set to true when the params are invalid
+   *        and indicate the child should be terminated. If |aChildURI| is
+   *        not a moz-extension URI, the child is in an invalid state and
+   *        should be terminated.
+   * @param aPromise a promise that will be resolved asynchronously when the
+   *        file descriptor is available.
+   * @return NS_OK with |aTerminateSender| set to false on success. On
+   *         failure, returns an error and sets |aTerminateSender| to indicate
+   *         whether or not the child process should be terminated.
+   *         A moz-extension URI from the child that doesn't resolve to an
+   *         enabled WebExtension JAR could be the result of a bug in the
+   *         extension and doesn't result in |aTerminateSender| being
+   *         set to true.
+   */
+  Result<Ok, nsresult> NewFD(nsIURI* aChildURI,
+                             nsILoadInfo* aChildLoadInfo,
+                             bool* aTerminateSender,
+                             NeckoParent::GetExtensionFDResolver& aResolve);
 
 protected:
   ~ExtensionProtocolHandler() {}
 
+private:
+  explicit ExtensionProtocolHandler();
+
   MOZ_MUST_USE bool ResolveSpecialCases(const nsACString& aHost,
                                         const nsACString& aPath,
                                         const nsACString& aPathname,
                                         nsACString& aResult) override;
 
   // |result| is an inout param.  On entry to this function, *result
   // is expected to be non-null and already addrefed.  This function
   // may release the object stored in *result on entry and write
   // a new pointer to an already addrefed channel to *result.
   virtual MOZ_MUST_USE nsresult SubstituteChannel(nsIURI* uri,
                                                   nsILoadInfo* aLoadInfo,
                                                   nsIChannel** result) override;
+
+  /**
+   * For moz-extension URI's that resolve to file or JAR URI's, replaces
+   * the provided channel with a channel that will proxy the load to the
+   * parent process. For moz-extension URI's that resolve to other types
+   * of URI's (not file or JAR), the provide channel is not replaced and
+   * NS_OK is returned.
+   *
+   * @param aURI the moz-extension URI
+   * @param aLoadInfo the loadinfo for the request
+   * @param aRetVal in/out channel param referring to the channel that
+   *        might need to be substituted with a remote channel.
+   * @return NS_OK if the channel does not need to be substituted or
+   *         or the replacement channel was created successfully.
+   *         Otherwise returns an error.
+   */
+  Result<Ok, nsresult> SubstituteRemoteChannel(nsIURI* aURI,
+                                               nsILoadInfo* aLoadInfo,
+                                               nsIChannel** aRetVal);
+
+  /**
+   * Replaces a file channel with a remote file channel for loading a
+   * web accessible resource for an unpacked extension from the parent.
+   *
+   * @param aURI the moz-extension URI
+   * @param aLoadInfo the loadinfo for the request
+   * @param aResolvedFileSpec the resolved URI spec for the file.
+   * @param aRetVal in/out param referring to the new remote channel.
+   *        The reference to the input param file channel is dropped and
+   *        replaced with a reference to a new channel that remotes
+   *        the file access. The new channel encapsulates a request to
+   *        the parent for an IPCStream for the file.
+   */
+  void SubstituteRemoteFileChannel(nsIURI* aURI,
+                                   nsILoadInfo* aLoadinfo,
+                                   nsACString& aResolvedFileSpec,
+                                   nsIChannel** aRetVal);
+
+  /**
+   * Replaces a JAR channel with a remote JAR channel for loading a
+   * an extension JAR file from the parent.
+   *
+   * @param aURI the moz-extension URI
+   * @param aLoadInfo the loadinfo for the request
+   * @param aResolvedFileSpec the resolved URI spec for the file.
+   * @param aRetVal in/out param referring to the new remote channel.
+   *        The input param JAR channel is replaced with a new channel
+   *        that remotes the JAR file access. The new channel encapsulates
+   *        a request to the parent for the JAR file FD.
+   */
+  Result<Ok, nsresult> SubstituteRemoteJarChannel(nsIURI* aURI,
+                                                  nsILoadInfo* aLoadinfo,
+                                                  nsACString& aResolvedSpec,
+                                                  nsIChannel** aRetVal);
+
+  // Used for opening JAR files off the main thread when we just need to
+  // obtain a file descriptor to send back to the child.
+  RefPtr<mozilla::LazyIdleThread> mFileOpenerThread;
+
+  // To allow parent IPDL actors to invoke methods on this handler when
+  // handling moz-extension requests from the child.
+  static StaticRefPtr<ExtensionProtocolHandler> sSingleton;
+
+  // Set to true when this instance of the handler must proxy loads of
+  // extension web-accessible resources to the parent process.
+  bool mUseRemoteFileChannels;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif /* ExtensionProtocolHandler_h___ */
--- a/netwerk/protocol/res/moz.build
+++ b/netwerk/protocol/res/moz.build
@@ -6,16 +6,21 @@
 
 XPIDL_SOURCES += [
     'nsIResProtocolHandler.idl',
     'nsISubstitutingProtocolHandler.idl',
 ]
 
 XPIDL_MODULE = 'necko_res'
 
+EXPORTS.mozilla.net += [
+    'ExtensionProtocolHandler.h',
+    'SubstitutingProtocolHandler.h',
+]
+
 UNIFIED_SOURCES += [
     'ExtensionProtocolHandler.cpp',
     'nsResProtocolHandler.cpp',
     'SubstitutingProtocolHandler.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
new file mode 100644
--- /dev/null
+++ b/xpcom/io/FileDescriptorFile.cpp
@@ -0,0 +1,486 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et 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 "FileDescriptorFile.h"
+
+#include "mozilla/ipc/FileDescriptorUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "private/pprio.h"
+#include "SerializedLoadContext.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(FileDescriptorFile, nsIFile)
+
+LazyLogModule gFDFileLog("FDFile");
+#undef DBG
+#define DBG(...) MOZ_LOG(gFDFileLog, LogLevel::Debug, (__VA_ARGS__))
+
+FileDescriptorFile::FileDescriptorFile(const FileDescriptor& aFD,
+                                       nsIFile* aFile)
+{
+  MOZ_ASSERT(aFD.IsValid());
+  auto platformHandle = aFD.ClonePlatformHandle();
+  mFD = FileDescriptor(platformHandle.get());
+  mFile = aFile;
+}
+
+FileDescriptorFile::FileDescriptorFile(const FileDescriptorFile& aOther)
+{
+  auto platformHandle = aOther.mFD.ClonePlatformHandle();
+  mFD = FileDescriptor(platformHandle.get());
+  aOther.mFile->Clone(getter_AddRefs(mFile));
+}
+
+//-----------------------------------------------------------------------------
+// FileDescriptorFile::nsIFile functions that we override logic for
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FileDescriptorFile::Clone(nsIFile **aFileOut)
+{
+  RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(*this);
+  fdFile.forget(aFileOut);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
+                                     PRFileDesc **aRetval)
+{
+  // Remove optional OS_READAHEAD flag so we test against PR_RDONLY
+  aFlags &= ~nsIFile::OS_READAHEAD;
+
+  // Remove optional/deprecated DELETE_ON_CLOSE flag
+  aFlags &= ~nsIFile::DELETE_ON_CLOSE;
+
+  // All other flags require write access to the file and
+  // this implementation only provides read access.
+  if (aFlags != PR_RDONLY) {
+    DBG("OpenNSPRFileDesc flags error (%" PRIu32 ")\n", aFlags);
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!mFD.IsValid()) {
+    DBG("OpenNSPRFileDesc error: no file descriptor\n");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  auto platformHandle = mFD.ClonePlatformHandle();
+  *aRetval = PR_ImportFile(PROsfd(platformHandle.release()));
+
+  if (!*aRetval) {
+    DBG("OpenNSPRFileDesc Clone failure\n");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FileDescriptorFile::nsIFile functions that we delegate to underlying nsIFile
+//-----------------------------------------------------------------------------
+
+nsresult
+FileDescriptorFile::GetLeafName(nsAString &aLeafName)
+{
+  return mFile->GetLeafName(aLeafName);
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetNativeLeafName(nsACString &aLeafName)
+{
+  return mFile->GetNativeLeafName(aLeafName);
+}
+
+nsresult
+FileDescriptorFile::GetTarget(nsAString &_retval)
+{
+  return mFile->GetTarget(_retval);
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetNativeTarget(nsACString &_retval)
+{
+  return mFile->GetNativeTarget(_retval);
+}
+
+nsresult
+FileDescriptorFile::GetPath(nsAString &_retval)
+{
+  return mFile->GetPath(_retval);
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetNativePath(nsACString &_retval)
+{
+  return mFile->GetNativePath(_retval);
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Equals(nsIFile *inFile, bool *_retval)
+{
+  return mFile->Equals(inFile, _retval);
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Contains(nsIFile *inFile, bool *_retval)
+{
+  return mFile->Contains(inFile, _retval);
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetParent(nsIFile **aParent)
+{
+  return mFile->GetParent(aParent);
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetFollowLinks(bool *aFollowLinks)
+{
+  return mFile->GetFollowLinks(aFollowLinks);
+}
+
+//-----------------------------------------------------------------------------
+// FileDescriptorFile::nsIFile functions that are not currently supported
+//-----------------------------------------------------------------------------
+
+nsresult
+FileDescriptorFile::Append(const nsAString &node)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::AppendNative(const nsACString &fragment)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Normalize()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Create(uint32_t type, uint32_t permissions)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+FileDescriptorFile::SetLeafName(const nsAString &aLeafName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetNativeLeafName(const nsACString &aLeafName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+FileDescriptorFile::InitWithPath(const nsAString &filePath)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::InitWithNativePath(const nsACString &filePath)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::InitWithFile(nsIFile *aFile)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetFollowLinks(bool aFollowLinks)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+FileDescriptorFile::AppendRelativePath(const nsAString &node)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::AppendRelativeNativePath(const nsACString &fragment)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetRelativeDescriptor(nsIFile *fromFile, nsACString& _retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetRelativeDescriptor(nsIFile *fromFile,
+                                   const nsACString& relativeDesc)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetRelativePath(nsIFile *fromFile, nsACString& _retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetRelativePath(nsIFile *fromFile,
+                                     const nsACString& relativePath)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+FileDescriptorFile::CopyTo(nsIFile *newParentDir, const nsAString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::CopyToNative(nsIFile *newParent, const nsACString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+FileDescriptorFile::CopyToFollowingLinks(nsIFile *newParentDir,
+                                  const nsAString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::CopyToFollowingLinksNative(nsIFile *newParent,
+                                        const nsACString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+FileDescriptorFile::MoveTo(nsIFile *newParentDir, const nsAString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::MoveToNative(nsIFile *newParent, const nsACString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::RenameTo(nsIFile *newParentDir, const nsAString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::RenameToNative(nsIFile *newParentDir, const nsACString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Remove(bool recursive)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetPermissions(uint32_t *aPermissions)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetPermissions(uint32_t aPermissions)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetPermissionsOfLink(uint32_t *aPermissionsOfLink)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetPermissionsOfLink(uint32_t aPermissions)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetLastModifiedTime(PRTime *aLastModTime)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetLastModifiedTime(PRTime aLastModTime)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetLastModifiedTimeOfLink(PRTime *aLastModTimeOfLink)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetFileSize(int64_t *aFileSize)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::SetFileSize(int64_t aFileSize)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetFileSizeOfLink(int64_t *aFileSize)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Exists(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::IsWritable(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::IsReadable(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::IsExecutable(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::IsHidden(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::IsDirectory(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::IsFile(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::IsSymlink(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::IsSpecial(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::CreateUnique(uint32_t type, uint32_t attributes)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetDirectoryEntries(nsISimpleEnumerator **entries)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::OpenANSIFileDesc(const char *mode, FILE **_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Load(PRLibrary **_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Reveal()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorFile::Launch()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/io/FileDescriptorFile.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et 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 _FileDescriptorFile_h
+#define _FileDescriptorFile_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsIURI.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ * A limited implementation of nsIFile that wraps a FileDescriptor object
+ * allowing the file to be read from. Added to allow a child process to use
+ * an nsIFile object for a file it does not have access to on the filesystem
+ * but has been provided a FileDescriptor for from the parent. Many nsIFile
+ * methods are not implemented and this is not intended to be a general
+ * purpose file implementation.
+ */
+class FileDescriptorFile final : public nsIFile
+{
+  typedef mozilla::ipc::FileDescriptor FileDescriptor;
+
+public:
+  FileDescriptorFile(const FileDescriptor& aFD, nsIFile* aFile);
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIFILE
+
+private:
+  ~FileDescriptorFile()
+  {}
+
+  FileDescriptorFile(const FileDescriptorFile& other);
+
+  // regular nsIFile object, that we forward most calls to.
+  nsCOMPtr<nsIFile> mFile;
+  FileDescriptor mFD;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // _FileDescriptorFile_h
--- a/xpcom/io/moz.build
+++ b/xpcom/io/moz.build
@@ -56,16 +56,17 @@ else:
     EXPORTS += ['nsLocalFileUnix.h']
     SOURCES += [
         'nsLocalFileUnix.cpp',
     ]
 
 XPIDL_MODULE = 'xpcom_io'
 
 EXPORTS += [
+    'FileDescriptorFile.h',
     'nsAnonymousTemporaryFile.h',
     'nsAppDirectoryServiceDefs.h',
     'nsDirectoryService.h',
     'nsDirectoryServiceAtomList.h',
     'nsDirectoryServiceDefs.h',
     'nsDirectoryServiceUtils.h',
     'nsEscape.h',
     'nsLinebreakConverter.h',
@@ -87,16 +88,17 @@ EXPORTS.mozilla += [
     'SnappyCompressOutputStream.h',
     'SnappyFrameUtils.h',
     'SnappyUncompressInputStream.h',
 ]
 
 UNIFIED_SOURCES += [
     'Base64.cpp',
     'crc32c.c',
+    'FileDescriptorFile.cpp',
     'nsAnonymousTemporaryFile.cpp',
     'nsAppFileLocationProvider.cpp',
     'nsBinaryStream.cpp',
     'nsDirectoryService.cpp',
     'nsEscape.cpp',
     'nsInputStreamTee.cpp',
     'nsIOUtil.cpp',
     'nsLinebreakConverter.cpp',