Bug 815523 - Remote the app: and jar: protocols. r=fabrice,mwu,jdm
authorJason Duell <jduell.mcbugs@gmail.com>
Sat, 22 Dec 2012 05:56:21 -0800
changeset 116889 108eb99a250db187c9bfa6c5f383f8375d261c9f
parent 116888 4ff2c4b415f8dfa9fd2be3908808268c87c2186e
child 116890 443df7602f6ef3f7736fc687c0e62a0dc15caf8b
push idunknown
push userunknown
push dateunknown
reviewersfabrice, mwu, jdm
bugs815523
milestone20.0a1
Bug 815523 - Remote the app: and jar: protocols. r=fabrice,mwu,jdm
dom/apps/src/AppsService.js
dom/apps/src/AppsServiceChild.jsm
dom/apps/src/AppsUtils.jsm
dom/apps/src/Webapps.jsm
dom/interfaces/apps/mozIApplication.idl
dom/interfaces/apps/nsIAppsService.idl
modules/libjar/nsIZipReader.idl
modules/libjar/nsJAR.cpp
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
modules/libjar/test/unit/head_ipc.js
modules/libjar/test/unit/test_jarchannel.js
modules/libjar/test/unit/test_jarchannel_e10s.js
modules/libjar/test/unit/xpcshell.ini
netwerk/ipc/Makefile.in
netwerk/ipc/NeckoChild.cpp
netwerk/ipc/NeckoChild.h
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/ipc/PRemoteOpenFile.ipdl
netwerk/ipc/RemoteOpenFileChild.cpp
netwerk/ipc/RemoteOpenFileChild.h
netwerk/ipc/RemoteOpenFileParent.cpp
netwerk/ipc/RemoteOpenFileParent.h
netwerk/ipc/ipdl.mk
netwerk/ipc/nsIRemoteOpenFileListener.idl
netwerk/protocol/app/AppProtocolHandler.js
--- a/dom/apps/src/AppsService.js
+++ b/dom/apps/src/AppsService.js
@@ -53,13 +53,23 @@ AppsService.prototype = {
     return DOMApplicationRegistry.getManifestURLByLocalId(aLocalId);
   },
 
   getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) {
     debug("getAppFromObserverMessage( " + aMessage + " )");
     return DOMApplicationRegistry.getAppFromObserverMessage(aMessage);
   },
 
+  getCoreAppsBasePath: function getCoreAppsBasePath() {
+    debug("getCoreAppsBasePath()");
+    return DOMApplicationRegistry.getCoreAppsBasePath();
+  },
+
+  getWebAppsBasePath: function getWebAppsBasePath() {
+    debug("getWebAppsBasePath()");
+    return DOMApplicationRegistry.getWebAppsBasePath();
+  },
+
   classID : APPS_SERVICE_CID,
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService])
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AppsService])
--- a/dom/apps/src/AppsServiceChild.jsm
+++ b/dom/apps/src/AppsServiceChild.jsm
@@ -81,12 +81,21 @@ this.DOMApplicationRegistry = {
   getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
     debug("getManifestURLByLocalId " + aLocalId);
     return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
   },
 
   getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) {
     debug("getAppFromObserverMessage " + aMessage);
     return AppsUtils.getAppFromObserverMessage(this.webapps. aMessage);
+  },
+  getCoreAppsBasePath: function getCoreAppsBasePath() {
+    debug("getCoreAppsBasePath() not yet supported on child!");
+    return null;
+  },
+
+  getWebAppsBasePath: function getWebAppsBasePath() {
+    debug("getWebAppsBasePath() not yet supported on child!");
+    return null;
   }
 }
 
 DOMApplicationRegistry.init();
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -33,17 +33,19 @@ this.AppsUtils = {
       csp: aApp.csp,
       installOrigin: aApp.installOrigin,
       origin: aApp.origin,
       receipts: aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null,
       installTime: aApp.installTime,
       manifestURL: aApp.manifestURL,
       appStatus: aApp.appStatus,
       removable: aApp.removable,
+      id: aApp.id,
       localId: aApp.localId,
+      basePath: aApp.basePath,
       progress: aApp.progress || 0.0,
       installState: aApp.installState || "installed",
       downloadAvailable: aApp.downloadAvailable,
       downloading: aApp.downloading,
       readyToApplyDownload: aApp.readyToApplyDownload,
       downloadSize: aApp.downloadSize || 0,
       lastUpdateCheck: aApp.lastUpdateCheck,
       updateTime: aApp.updateTime,
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -70,17 +70,17 @@ this.DOMApplicationRegistry = {
   children: [ ],
   allAppsLaunchable: false,
 
   init: function() {
     this.messages = ["Webapps:Install", "Webapps:Uninstall",
                      "Webapps:GetSelf", "Webapps:CheckInstalled",
                      "Webapps:GetInstalled", "Webapps:GetNotInstalled",
                      "Webapps:Launch", "Webapps:GetAll",
-                     "Webapps:InstallPackage", "Webapps:GetBasePath",
+                     "Webapps:InstallPackage", "Webapps:GetAppInfo",
                      "Webapps:GetList", "Webapps:RegisterForMessages",
                      "Webapps:UnregisterForMessages",
                      "Webapps:CancelDownload", "Webapps:CheckForUpdate",
                      "Webapps:Download", "Webapps:ApplyDownload",
                      "child-process-shutdown"];
 
     this.frameMessages = ["Webapps:ClearBrowserData"];
 
@@ -105,16 +105,19 @@ this.DOMApplicationRegistry = {
   loadCurrentRegistry: function loadCurrentRegistry(aNext) {
     let file = FileUtils.getFile(DIRECTORY_NAME, ["webapps", "webapps.json"], false);
     if (file && file.exists()) {
       this._loadJSONAsync(file, (function loadRegistry(aData) {
         if (aData) {
           this.webapps = aData;
           let appDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false);
           for (let id in this.webapps) {
+
+            this.webapps[id].id = id;
+
             // Make sure we have a localId
             if (this.webapps[id].localId === undefined) {
               this.webapps[id].localId = this._nextLocalId();
             }
 
             if (this.webapps[id].basePath === undefined) {
               this.webapps[id].basePath = appDir.path;
             }
@@ -210,17 +213,17 @@ this.DOMApplicationRegistry = {
   },
 
   // Installs a 3rd party packaged app.
   installPreinstalledPackage: function installPreinstalledPackage(aId) {
 #ifdef MOZ_WIDGET_GONK
     let app = this.webapps[aId];
     let baseDir;
     try {
-      baseDir = FileUtils.getDir("coreAppsDir", ["webapps", aId], true, true);
+      baseDir = FileUtils.getDir("coreAppsDir", ["webapps", aId], false);
     } catch(e) {
       // In ENG builds, we don't have apps in coreAppsDir.
       return;
     }
 
     let updateFile = baseDir.clone();
     updateFile.append("update.webapp");
     if (!updateFile.exists()) {
@@ -315,16 +318,18 @@ this.DOMApplicationRegistry = {
         // c
         for (let id in aData) {
           // Core apps have ids matching their domain name (eg: dialer.gaiamobile.org)
           // Use that property to check if they are new or not.
           if (!(id in this.webapps)) {
             this.webapps[id] = aData[id];
             this.webapps[id].basePath = appDir.path;
 
+            this.webapps[id].id = id;
+
             // Create a new localId.
             this.webapps[id].localId = this._nextLocalId();
 
             // Core apps are not removable.
             if (this.webapps[id].removable === undefined) {
               this.webapps[id].removable = false;
             }
           }
@@ -774,22 +779,23 @@ this.DOMApplicationRegistry = {
         if (msg.hasPrivileges)
           this.getAll(msg, mm);
         else
           mm.sendAsyncMessage("Webapps:GetAll:Return:KO", msg);
         break;
       case "Webapps:InstallPackage":
         this.doInstallPackage(msg, mm);
         break;
-      case "Webapps:GetBasePath":
+      case "Webapps:GetAppInfo":
         if (!this.webapps[msg.id]) {
           debug("No webapp for " + msg.id);
           return null;
         }
-        return this.webapps[msg.id].basePath;
+        return { "basePath":  this.webapps[msg.id].basePath + "/",
+                 "isCoreApp": !this.webapps[msg.id].removable };
         break;
       case "Webapps:RegisterForMessages":
         this.addMessageListener(msg, mm);
         break;
       case "Webapps:UnregisterForMessages":
         this.removeMessageListener(msg, mm);
         break;
       case "child-process-shutdown":
@@ -1474,19 +1480,19 @@ this.DOMApplicationRegistry = {
       isReinstall = true;
       let dir = this._getAppDir(id);
       try {
         dir.remove(true);
       } catch(e) {
       }
     } else {
       id = this.makeAppId();
-      app.id = id;
       localId = this._nextLocalId();
     }
+    app.id = id;
 
     let manifestName = "manifest.webapp";
     if (aData.isPackage) {
       // Override the origin with the correct id.
       app.origin = "app://" + id;
 
       // For packaged apps, keep the update manifest distinct from the app
       // manifest.
@@ -1501,16 +1507,17 @@ this.DOMApplicationRegistry = {
       appObject.appStatus = AppsUtils.getAppManifestStatus(app.manifest);
     }
 
     appObject.installTime = app.installTime = Date.now();
     appObject.lastUpdateCheck = app.lastUpdateCheck = Date.now();
     let appNote = JSON.stringify(appObject);
     appNote.id = id;
 
+    appObject.id = id;
     appObject.localId = localId;
     appObject.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true).path;
     let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
     dir.permissions = FileUtils.PERMS_DIRECTORY;
     let manFile = dir.clone();
     manFile.append(manifestName);
     let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest;
     this._writeFile(manFile, JSON.stringify(jsonManifest), function() { });
@@ -2184,16 +2191,24 @@ this.DOMApplicationRegistry = {
   getAppLocalIdByManifestURL: function(aManifestURL) {
     return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
   },
 
   getAppFromObserverMessage: function(aMessage) {
     return AppsUtils.getAppFromObserverMessage(this.webapps, aMessage);
   },
 
+  getCoreAppsBasePath: function() {
+    return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
+  },
+
+  getWebAppsBasePath: function getWebAppsBasePath() {
+    return FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false).path;
+  },
+
   getAllWithoutManifests: function(aCallback) {
     let result = {};
     for (let id in this.webapps) {
       let app = AppsUtils.cloneAppObject(this.webapps[id]);
       result[id] = app;
     }
     aCallback(result);
   },
--- a/dom/interfaces/apps/mozIApplication.idl
+++ b/dom/interfaces/apps/mozIApplication.idl
@@ -6,26 +6,32 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMApplicationRegistry.idl"
 
 /**
  * We expose Gecko-internal helpers related to "web apps" through this
  * sub-interface.
  */
-[scriptable, uuid(8ac7827f-f982-40fb-be11-ba16dd665635)]
+[scriptable, uuid(cfa75628-4d31-481f-b51e-fe0ce18fa98f)]
 interface mozIApplication: mozIDOMApplication
 {
   /* Return true if this app has |permission|. */
   boolean hasPermission(in string permission);
 
   /* Application status as defined in nsIPrincipal. */
   readonly attribute unsigned short appStatus;
 
-  /* Returns the local id of the app (not the uuid used for sync). */
+  /* Returns the uuid of the app. */
+  readonly attribute DOMString id;
+
+  /* Returns the local id of the app. */
   readonly attribute unsigned long localId;
 
+  /* Returns the base directory for the app */
+  readonly attribute DOMString basePath;
+
   /* Name copied from the manifest */
   readonly attribute DOMString name;
 
   /* CSP copied from the manifest */
   readonly attribute DOMString csp;
 };
--- a/dom/interfaces/apps/nsIAppsService.idl
+++ b/dom/interfaces/apps/nsIAppsService.idl
@@ -11,17 +11,17 @@ interface mozIApplication;
 #define APPS_SERVICE_CID { 0x05072afa, 0x92fe, 0x45bf, { 0xae, 0x22, 0x39, 0xb6, 0x9c, 0x11, 0x70, 0x58 } }
 #define APPS_SERVICE_CONTRACTID "@mozilla.org/AppsService;1"
 %}
 
 /*
  * This service allows accessing some DOMApplicationRegistry methods from
  * non-javascript code.
  */
-[scriptable, uuid(4a182c18-dbdf-4f9c-93a0-0f0cffb88ed0)]
+[scriptable, uuid(e65f9397-e191-4273-aa5f-f13c185ce63b)]
 interface nsIAppsService : nsISupports
 {
   mozIDOMApplication getAppByManifestURL(in DOMString manifestURL);
 
   /**
    * Returns the |localId| of the app associated with the |manifestURL| passed
    * in parameter.
    * Returns nsIScriptSecurityManager::NO_APP_ID if |manifestURL| isn't a valid
@@ -45,9 +45,19 @@ interface nsIAppsService : nsISupports
    * of the message when listening to one.
    */
   mozIApplication getAppFromObserverMessage(in DOMString message);
 
   /**
    * Returns the CSP associated to this localId.
    */
   DOMString getCSPByLocalId(in unsigned long localId);
+
+  /**
+   * Returns the basepath for core apps
+   */
+  DOMString getCoreAppsBasePath();
+
+  /**
+   * Returns the basepath for regular packaged apps
+   */
+  DOMString getWebAppsBasePath();
 };
--- a/modules/libjar/nsIZipReader.idl
+++ b/modules/libjar/nsIZipReader.idl
@@ -180,17 +180,17 @@ interface nsIZipReader : nsISupports
     nsICertificatePrincipal getCertificatePrincipal(in AUTF8String aEntryName);   
     
     readonly attribute uint32_t manifestEntriesCount;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIZipReaderCache
 
-[scriptable, uuid(72fc56e5-3e6e-4d11-8967-26ab96071032)]
+[scriptable, uuid(748050ac-3ab6-4472-bc2a-cb1564ac6a81)]
 interface nsIZipReaderCache : nsISupports
 {
     /**
      * Initializes a new zip reader cache. 
      * @param cacheSize - the number of released entries to maintain before
      *   beginning to throw some out (note that the number of outstanding
      *   entries can be much greater than this number -- this is the count
      *   for those otherwise unused entries)
@@ -206,16 +206,21 @@ interface nsIZipReaderCache : nsISupport
      * returned.
      *
      * @note If someone called close() on the shared nsIZipReader, this method 
      *       will return the closed zip reader.
      */
     nsIZipReader getZip(in nsIFile zipFile);
 
     /**
+     * returns true if this zipreader already has this file cached
+     */
+    bool isCached(in nsIFile zipFile);
+
+    /**
      * Returns a (possibly shared) nsIZipReader for a zip inside another zip
      *
      * See getZip
      */
     nsIZipReader getInnerZip(in nsIFile zipFile, in AUTF8String zipEntry);
 };
 
 ////////////////////////////////////////////////////////////////////////////////
--- a/modules/libjar/nsJAR.cpp
+++ b/modules/libjar/nsJAR.cpp
@@ -1058,16 +1058,37 @@ nsZipReaderCache::~nsZipReaderCache()
   printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
          mCacheSize, mZipCacheHits, mZipCacheLookups, 
          (float)mZipCacheHits / mZipCacheLookups, 
          mZipCacheFlushes, mZipSyncMisses);
 #endif
 }
 
 NS_IMETHODIMP
+nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult)
+{
+  NS_ENSURE_ARG_POINTER(zipFile);
+  nsresult rv;
+  nsCOMPtr<nsIZipReader> antiLockZipGrip;
+  MutexAutoLock lock(mLock);
+
+  nsAutoCString uri;
+  rv = zipFile->GetNativePath(uri);
+  if (NS_FAILED(rv))
+    return rv;
+
+  uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+  nsCStringKey key(uri);
+
+  *aResult = mZips.Exists(&key);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
 {
   NS_ENSURE_ARG_POINTER(zipFile);
   nsresult rv;
   nsCOMPtr<nsIZipReader> antiLockZipGrip;
   MutexAutoLock lock(mLock);
 
 #ifdef ZIP_CACHE_HIT_RATE
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -1,11 +1,11 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 "nsJAR.h"
 #include "nsJARChannel.h"
 #include "nsJARProtocolHandler.h"
 #include "nsMimeTypes.h"
 #include "nsNetUtil.h"
@@ -13,20 +13,24 @@
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsIViewSourceChannel.h"
 #include "nsChannelProperties.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsIFileURL.h"
+#include "nsXULAppAPI.h"
 
 #include "mozilla/Preferences.h"
+#include "mozilla/net/RemoteOpenFileChild.h"
+#include "nsITabChild.h"
 
 using namespace mozilla;
+using namespace mozilla::net;
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 // the entry for a directory will either be empty (in the case of the
 // top-level directory) or will end with a slash
 #define ENTRY_IS_DIRECTORY(_entry) \
   ((_entry).IsEmpty() || '/' == (_entry).Last())
 
@@ -183,16 +187,17 @@ nsJARInputThunk::IsNonBlocking(bool *non
 nsJARChannel::nsJARChannel()
     : mOpened(false)
     , mAppURI(nullptr)
     , mContentLength(-1)
     , mLoadFlags(LOAD_NORMAL)
     , mStatus(NS_OK)
     , mIsPending(false)
     , mIsUnsafe(true)
+    , mOpeningRemote(false)
 {
 #if defined(PR_LOGGING)
     if (!gJarProtocolLog)
         gJarProtocolLog = PR_NewLogModule("nsJarProtocol");
 #endif
 
     // hold an owning reference to the jar handler
     NS_ADDREF(gJarHandler);
@@ -200,23 +205,24 @@ nsJARChannel::nsJARChannel()
 
 nsJARChannel::~nsJARChannel()
 {
     // release owning reference to the jar handler
     nsJARProtocolHandler *handler = gJarHandler;
     NS_RELEASE(handler); // NULL parameter
 }
 
-NS_IMPL_ISUPPORTS_INHERITED6(nsJARChannel,
+NS_IMPL_ISUPPORTS_INHERITED7(nsJARChannel,
                              nsHashPropertyBag,
                              nsIRequest,
                              nsIChannel,
                              nsIStreamListener,
                              nsIRequestObserver,
                              nsIDownloadObserver,
+                             nsIRemoteOpenFileListener,
                              nsIJARChannel)
 
 nsresult 
 nsJARChannel::Init(nsIURI *uri)
 {
     nsresult rv;
     rv = nsHashPropertyBag::Init();
     if (NS_FAILED(rv))
@@ -258,27 +264,27 @@ nsJARChannel::CreateJarInput(nsIZipReade
     nsCOMPtr<nsIFile> clonedFile;
     nsresult rv = mJarFile->Clone(getter_AddRefs(clonedFile));
     if (NS_FAILED(rv))
         return rv;
 
     nsCOMPtr<nsIZipReader> reader;
     if (jarCache) {
         if (mInnerJarEntry.IsEmpty())
-            rv = jarCache->GetZip(mJarFile, getter_AddRefs(reader));
+            rv = jarCache->GetZip(clonedFile, getter_AddRefs(reader));
         else
-            rv = jarCache->GetInnerZip(mJarFile, mInnerJarEntry,
+            rv = jarCache->GetInnerZip(clonedFile, mInnerJarEntry,
                                        getter_AddRefs(reader));
     } else {
         // create an uncached jar reader
         nsCOMPtr<nsIZipReader> outerReader = do_CreateInstance(kZipReaderCID, &rv);
         if (NS_FAILED(rv))
             return rv;
 
-        rv = outerReader->Open(mJarFile);
+        rv = outerReader->Open(clonedFile);
         if (NS_FAILED(rv))
             return rv;
 
         if (mInnerJarEntry.IsEmpty())
             reader = outerReader;
         else {
             reader = do_CreateInstance(kZipReaderCID, &rv);
             if (NS_FAILED(rv))
@@ -329,16 +335,47 @@ nsJARChannel::LookupFile()
     NS_UnescapeURL(mJarEntry);
 
     // 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));
     }
+    // if we're in child process and have special "remoteopenfile:://" scheme,
+    // create special nsIFile that gets file handle from parent when opened.
+    if (!mJarFile && XRE_GetProcessType() != GeckoProcessType_Default) {
+        nsAutoCString scheme;
+        nsresult rv = mJarBaseURI->GetScheme(scheme);
+        if (NS_SUCCEEDED(rv) && scheme.EqualsLiteral("remoteopenfile")) {
+            nsRefPtr<RemoteOpenFileChild> remoteFile = new RemoteOpenFileChild();
+            rv = remoteFile->Init(mJarBaseURI);
+            NS_ENSURE_SUCCESS(rv, rv);
+            mJarFile = remoteFile;
+
+            nsIZipReaderCache *jarCache = gJarHandler->JarCache();
+            if (jarCache) {
+                bool cached = false;
+                rv = jarCache->IsCached(mJarFile, &cached);
+                if (NS_SUCCEEDED(rv) && cached) {
+                    // zipcache already has file mmapped: don't open on parent,
+                    // just return and proceed to cache hit in CreateJarInput()
+                    return NS_OK;
+                }
+            }
+
+            // Open file on parent: OnRemoteFileOpenComplete called when done
+            nsCOMPtr<nsITabChild> tabChild;
+            NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, tabChild);
+            rv = remoteFile->AsyncRemoteFileOpen(PR_RDONLY, this, tabChild.get());
+            NS_ENSURE_SUCCESS(rv, rv);
+
+            mOpeningRemote = true;
+        }
+    }
     // try to handle a nested jar
     if (!mJarFile) {
         nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(mJarBaseURI);
         if (jarURI) {
             nsCOMPtr<nsIFileURL> fileURL;
             nsCOMPtr<nsIURI> innerJarURI;
             rv = jarURI->GetJARFile(getter_AddRefs(innerJarURI));
             if (NS_SUCCEEDED(rv))
@@ -650,17 +687,17 @@ nsJARChannel::Open(nsIInputStream **stre
         return rv;
 
     // If mJarInput was not set by LookupFile, the JAR is a remote jar.
     if (!mJarFile) {
         NS_NOTREACHED("need sync downloader");
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
-    nsCOMPtr<nsJARInputThunk> input;
+    nsRefPtr<nsJARInputThunk> input;
     rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
     if (NS_FAILED(rv))
         return rv;
 
     input.forget(stream);
     mOpened = true;
     // local files are always considered safe
     mIsUnsafe = false;
@@ -697,22 +734,23 @@ nsJARChannel::AsyncOpen(nsIStreamListene
     if (!mJarFile) {
         // Not a local file...
         // kick off an async download of the base URI...
         rv = NS_NewDownloader(getter_AddRefs(mDownloader), this);
         if (NS_SUCCEEDED(rv))
             rv = NS_OpenURI(mDownloader, nullptr, mJarBaseURI, nullptr,
                             mLoadGroup, mCallbacks,
                             mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS));
-    }
-    else {
+    } else if (mOpeningRemote) {
+        // nothing to do: already asked parent to open file.
+    } else {
         // local files are always considered safe
         mIsUnsafe = false;
 
-        nsCOMPtr<nsJARInputThunk> input;
+        nsRefPtr<nsJARInputThunk> input;
         rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
         if (NS_SUCCEEDED(rv)) {
             // create input stream pump and call AsyncRead as a block
             rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
             if (NS_SUCCEEDED(rv))
                 rv = mPump->AsyncRead(this, nullptr);
         }
     }
@@ -840,17 +878,17 @@ nsJARChannel::OnDownloadComplete(nsIDown
         if (viewSource) {
             status = NS_ERROR_UNSAFE_CONTENT_TYPE;
         }
     }
 
     if (NS_SUCCEEDED(status)) {
         mJarFile = file;
 
-        nsCOMPtr<nsJARInputThunk> input;
+        nsRefPtr<nsJARInputThunk> input;
         rv = CreateJarInput(nullptr, getter_AddRefs(input));
         if (NS_SUCCEEDED(rv)) {
             // create input stream pump
             rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
             if (NS_SUCCEEDED(rv))
                 rv = mPump->AsyncRead(this, nullptr);
         }
         status = rv;
@@ -861,16 +899,48 @@ nsJARChannel::OnDownloadComplete(nsIDown
         OnStartRequest(nullptr, nullptr);
         OnStopRequest(nullptr, nullptr, status);
     }
 
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
+// nsIRemoteOpenFileListener
+//-----------------------------------------------------------------------------
+nsresult
+nsJARChannel::OnRemoteFileOpenComplete(nsresult aOpenStatus)
+{
+    nsresult rv = aOpenStatus;
+
+    if (NS_SUCCEEDED(rv)) {
+        // files on parent are always considered safe
+        mIsUnsafe = false;
+
+        nsRefPtr<nsJARInputThunk> input;
+        rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
+        if (NS_SUCCEEDED(rv)) {
+            // create input stream pump and call AsyncRead as a block
+            rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
+            if (NS_SUCCEEDED(rv))
+                rv = mPump->AsyncRead(this, nullptr);
+        }
+    }
+
+    if (NS_FAILED(rv)) {
+        mStatus = rv;
+        OnStartRequest(nullptr, nullptr);
+        OnStopRequest(nullptr, nullptr, mStatus);
+    }
+
+    return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
 // nsIStreamListener
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsJARChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx)
 {
     LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec.get()));
 
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -7,16 +7,17 @@
 #define nsJARChannel_h__
 
 #include "nsIJARChannel.h"
 #include "nsIJARURI.h"
 #include "nsIInputStreamPump.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIProgressEventSink.h"
 #include "nsIStreamListener.h"
+#include "nsIRemoteOpenFileListener.h"
 #include "nsIZipReader.h"
 #include "nsIDownloader.h"
 #include "nsILoadGroup.h"
 #include "nsHashPropertyBag.h"
 #include "nsIFile.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
@@ -24,26 +25,28 @@
 
 class nsJARInputThunk;
 
 //-----------------------------------------------------------------------------
 
 class nsJARChannel : public nsIJARChannel
                    , public nsIDownloadObserver
                    , public nsIStreamListener
+                   , public nsIRemoteOpenFileListener
                    , public nsHashPropertyBag
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIREQUEST
     NS_DECL_NSICHANNEL
     NS_DECL_NSIJARCHANNEL
     NS_DECL_NSIDOWNLOADOBSERVER
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
+    NS_DECL_NSIREMOTEOPENFILELISTENER
 
     nsJARChannel();
     virtual ~nsJARChannel();
 
     nsresult Init(nsIURI *uri);
 
 private:
     nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
@@ -71,16 +74,17 @@ private:
     /* mContentDisposition is uninitialized if mContentDispositionHeader is
      * empty */
     uint32_t                        mContentDisposition;
     int64_t                         mContentLength;
     uint32_t                        mLoadFlags;
     nsresult                        mStatus;
     bool                            mIsPending;
     bool                            mIsUnsafe;
+    bool                            mOpeningRemote;
 
     nsCOMPtr<nsIStreamListener>     mDownloader;
     nsCOMPtr<nsIInputStreamPump>    mPump;
     nsCOMPtr<nsIFile>               mJarFile;
     nsCOMPtr<nsIURI>                mJarBaseURI;
     nsCString                       mJarEntry;
     nsCString                       mInnerJarEntry;
 };
new file mode 100644
--- /dev/null
+++ b/modules/libjar/test/unit/head_ipc.js
@@ -0,0 +1,17 @@
+/*
+ * If we're running in e10s, determines whether we're in child directory or
+ * not.
+ */
+
+var inChild = false;
+var filePrefix = "";
+try {
+  inChild = Components.classes["@mozilla.org/xre/runtime;1"].
+              getService(Components.interfaces.nsIXULRuntime).processType
+              != Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+  if (inChild) {
+    // use "jar:remoteopenfile://" in child instead of "jar:file://"
+    filePrefix = "remoteopen";
+  }
+} 
+catch (e) { }
--- a/modules/libjar/test/unit/test_jarchannel.js
+++ b/modules/libjar/test/unit/test_jarchannel.js
@@ -22,17 +22,18 @@ const obs = Cc["@mozilla.org/observer-se
 
 const nsIBinaryInputStream = ctor("@mozilla.org/binaryinputstream;1",
                                "nsIBinaryInputStream",
                                "setInputStream"
                                );
 
 const fileBase = "test_bug637286.zip";
 const file = do_get_file("data/" + fileBase);
-const jarBase = "jar:" + ios.newFileURI(file).spec + "!";
+// on child we'll test with jar:remoteopenfile:// instead of jar:file://
+const jarBase = "jar:" + filePrefix + ios.newFileURI(file).spec + "!";
 const tmpDir = dirSvc.get("TmpD", Ci.nsIFile);
 
 function Listener(callback) {
     this._callback = callback;
 }
 Listener.prototype = {
     gotStartRequest: false,
     available: -1,
@@ -61,127 +62,141 @@ Listener.prototype = {
         this.gotStopRequest = true;
         if (this._callback) {
             this._callback.call(null, this);
         }
     }
 };
 
 /**
- * Basic reading test for synchronously opened jar channels
- */
-add_test(function testSync() {
-    var uri = jarBase + "/inner40.zip";
-    var chan = ios.newChannel(uri, null, null);
-    var stream = chan.open();
-    do_check_true(chan.contentLength > 0);
-    do_check_eq(stream.available(), chan.contentLength);
-    stream.close();
-    stream.close(); // should still not throw
-
-    run_next_test();
-});
-
-/**
  * Basic reading test for asynchronously opened jar channel
  */
-add_test(function testAsync() {
+function testAsync() {
     var uri = jarBase + "/inner40.zip";
     var chan = ios.newChannel(uri, null, null);
     do_check_true(chan.contentLength < 0);
     chan.asyncOpen(new Listener(function(l) {
         do_check_true(chan.contentLength > 0);
         do_check_true(l.gotStartRequest);
         do_check_true(l.gotStopRequest);
         do_check_eq(l.available, chan.contentLength);
 
         run_next_test();
     }), null);
-});
+}
+
+add_test(testAsync);
+// Run same test again so we test the codepath for a zipcache hit
+add_test(testAsync);
+
+
+// In e10s child processes we don't currently support 
+// 1) synchronously opening jar files on parent
+// 2) nested jar channels in e10s: (app:// doesn't use them).
+// 3) we can't do file lock checks on android, so skip those tests too.
+if (!inChild) {
 
-/**
- * Basic reading test for synchronously opened, nested jar channels
- */
-add_test(function testSyncNested() {
-    var uri = "jar:" + jarBase + "/inner40.zip!/foo";
-    var chan = ios.newChannel(uri, null, null);
-    var stream = chan.open();
-    do_check_true(chan.contentLength > 0);
-    do_check_eq(stream.available(), chan.contentLength);
-    stream.close();
-    stream.close(); // should still not throw
+  /**
+   * Basic reading test for synchronously opened jar channels
+   */
+  add_test(function testSync() {
+      var uri = jarBase + "/inner40.zip";
+      var chan = ios.newChannel(uri, null, null);
+      var stream = chan.open();
+      do_check_true(chan.contentLength > 0);
+      do_check_eq(stream.available(), chan.contentLength);
+      stream.close();
+      stream.close(); // should still not throw
 
-    run_next_test();
-});
+      run_next_test();
+  });
 
-/**
- * Basic reading test for asynchronously opened, nested jar channels
- */
-add_test(function testAsyncNested(next) {
-    var uri = "jar:" + jarBase + "/inner40.zip!/foo";
-    var chan = ios.newChannel(uri, null, null);
-    chan.asyncOpen(new Listener(function(l) {
-        do_check_true(chan.contentLength > 0);
-        do_check_true(l.gotStartRequest);
-        do_check_true(l.gotStopRequest);
-        do_check_eq(l.available, chan.contentLength);
+
+  /**
+   * Basic reading test for synchronously opened, nested jar channels
+   */
+  add_test(function testSyncNested() {
+      var uri = "jar:" + jarBase + "/inner40.zip!/foo";
+      var chan = ios.newChannel(uri, null, null);
+      var stream = chan.open();
+      do_check_true(chan.contentLength > 0);
+      do_check_eq(stream.available(), chan.contentLength);
+      stream.close();
+      stream.close(); // should still not throw
 
-        run_next_test();
-    }), null);
-});
+      run_next_test();
+  });
 
-/**
- * Verify that file locks are released when closing a synchronously
- * opened jar channel stream
- */
-add_test(function testSyncCloseUnlocks() {
-    var copy = tmpDir.clone();
-    copy.append(fileBase);
-    file.copyTo(copy.parent, copy.leafName);
+  /**
+   * Basic reading test for asynchronously opened, nested jar channels
+   */
+  add_test(function testAsyncNested(next) {
+      var uri = "jar:" + jarBase + "/inner40.zip!/foo";
+      var chan = ios.newChannel(uri, null, null);
+      chan.asyncOpen(new Listener(function(l) {
+          do_check_true(chan.contentLength > 0);
+          do_check_true(l.gotStartRequest);
+          do_check_true(l.gotStopRequest);
+          do_check_eq(l.available, chan.contentLength);
 
-    var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
-    var chan = ios.newChannel(uri, null, null);
-    var stream = chan.open();
-    do_check_true(chan.contentLength > 0);
-    stream.close();
+          run_next_test();
+      }), null);
+  });
 
-    // Drop any jar caches
-    obs.notifyObservers(null, "chrome-flush-caches", null);
+  /**
+   * Verify that file locks are released when closing a synchronously
+   * opened jar channel stream
+   */
+  add_test(function testSyncCloseUnlocks() {
+      var copy = tmpDir.clone();
+      copy.append(fileBase);
+      file.copyTo(copy.parent, copy.leafName);
 
-    try {
-        copy.remove(false);
-    }
-    catch (ex) {
-        do_throw(ex);
-    }
+      var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
+      var chan = ios.newChannel(uri, null, null);
+      var stream = chan.open();
+      do_check_true(chan.contentLength > 0);
+      stream.close();
 
-    run_next_test();
-});
+      // Drop any jar caches
+      obs.notifyObservers(null, "chrome-flush-caches", null);
+
+      try {
+          copy.remove(false);
+      }
+      catch (ex) {
+          do_throw(ex);
+      }
 
-/**
- * Verify that file locks are released when closing an asynchronously
- * opened jar channel stream
- */
-add_test(function testAsyncCloseUnlocks() {
-    var copy = tmpDir.clone();
-    copy.append(fileBase);
-    file.copyTo(copy.parent, copy.leafName);
+      run_next_test();
+  });
 
-    var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
-    var chan = ios.newChannel(uri, null, null);
-    chan.asyncOpen(new Listener(function (l) {
-        do_check_true(chan.contentLength > 0);
+  /**
+   * Verify that file locks are released when closing an asynchronously
+   * opened jar channel stream
+   */
+  add_test(function testAsyncCloseUnlocks() {
+      var copy = tmpDir.clone();
+      copy.append(fileBase);
+      file.copyTo(copy.parent, copy.leafName);
 
-        // Drop any jar caches
-        obs.notifyObservers(null, "chrome-flush-caches", null);
+      var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
+      var chan = ios.newChannel(uri, null, null);
+      chan.asyncOpen(new Listener(function (l) {
+          do_check_true(chan.contentLength > 0);
+
+          // Drop any jar caches
+          obs.notifyObservers(null, "chrome-flush-caches", null);
 
-        try {
-            copy.remove(false);
-        }
-        catch (ex) {
-            do_throw(ex);
-        }
+          try {
+              copy.remove(false);
+          }
+          catch (ex) {
+              do_throw(ex);
+          }
 
-        run_next_test();
-    }), null);
-});
+          run_next_test();
+      }), null);
+  });
+
+} // if !inChild
 
 function run_test() run_next_test();
new file mode 100644
--- /dev/null
+++ b/modules/libjar/test/unit/test_jarchannel_e10s.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+  run_test_in_child("../unit/test_jarchannel.js");
+}
--- a/modules/libjar/test/unit/xpcshell.ini
+++ b/modules/libjar/test/unit/xpcshell.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
-head = 
+head = head_ipc.js
 tail = 
 
 [test_jarchannel.js]
+[test_jarchannel_e10s.js]
+skip-if = os == "mac" || os == "windows"
 [test_bug278262.js]
 [test_bug333423.js]
 [test_bug336691.js]
 [test_bug370103.js]
 [test_bug379841.js]
 [test_bug407303.js]
 [test_bug453254.js]
 [test_bug458158.js]
--- a/netwerk/ipc/Makefile.in
+++ b/netwerk/ipc/Makefile.in
@@ -11,31 +11,40 @@ FAIL_ON_WARNINGS := 1
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = necko
 
 LIBRARY_NAME = neckoipc_s
 LIBXUL_LIBRARY = 1
 FORCE_STATIC_LIB = 1
 EXPORT_LIBRARY = 1
+XPIDL_MODULE   = necko_ipc
+
+XPIDLSRCS = \
+  nsIRemoteOpenFileListener.idl \
+  $(NULL)
 
 EXPORTS_NAMESPACES = mozilla/net
 
 EXPORTS_mozilla/net = \
   NeckoParent.h       \
   NeckoChild.h        \
   NeckoCommon.h       \
   NeckoMessageUtils.h \
   ChannelEventQueue.h \
+  RemoteOpenFileParent.h \
+  RemoteOpenFileChild.h \
   $(NULL)
 
 CPPSRCS =               \
   NeckoChild.cpp        \
   NeckoParent.cpp       \
   ChannelEventQueue.cpp \
+  RemoteOpenFileParent.cpp \
+  RemoteOpenFileChild.cpp  \
   $(NULL)
 
 LOCAL_INCLUDES +=                  \
   -I$(srcdir)/../protocol/http \
   -I$(srcdir)/../base/src          \
   $(NULL)
 
 include $(topsrcdir)/config/config.mk
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -8,16 +8,17 @@
 #include "nsHttp.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/net/HttpChannelChild.h"
 #include "mozilla/net/CookieServiceChild.h"
 #include "mozilla/net/WyciwygChannelChild.h"
 #include "mozilla/net/FTPChannelChild.h"
 #include "mozilla/net/WebSocketChannelChild.h"
+#include "mozilla/net/RemoteOpenFileChild.h"
 #include "mozilla/dom/network/TCPSocketChild.h"
 #include "mozilla/Preferences.h"
 
 using mozilla::dom::TCPSocketChild;
 
 namespace mozilla {
 namespace net {
 
@@ -167,10 +168,27 @@ NeckoChild::AllocPTCPSocket(const nsStri
 bool
 NeckoChild::DeallocPTCPSocket(PTCPSocketChild* child)
 {
   TCPSocketChild* p = static_cast<TCPSocketChild*>(child);
   p->ReleaseIPDLReference();
   return true;
 }
 
+PRemoteOpenFileChild*
+NeckoChild::AllocPRemoteOpenFile(const URIParams&, PBrowserChild*)
+{
+  // We don't allocate here: instead we always use IPDL constructor that takes
+  // an existing RemoteOpenFileChild
+  NS_NOTREACHED("AllocPRemoteOpenFile should not be called on child");
+  return nullptr;
+}
+
+bool
+NeckoChild::DeallocPRemoteOpenFile(PRemoteOpenFileChild* aChild)
+{
+  RemoteOpenFileChild *p = static_cast<RemoteOpenFileChild*>(aChild);
+  p->ReleaseIPDLReference();
+  return true;
+}
+
 }} // mozilla::net
 
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -38,16 +38,19 @@ protected:
   virtual PWebSocketChild* AllocPWebSocket(PBrowserChild*);
   virtual bool DeallocPWebSocket(PWebSocketChild*);
   virtual PTCPSocketChild* AllocPTCPSocket(const nsString& aHost,
                                            const uint16_t& aPort,
                                            const bool& useSSL,
                                            const nsString& aBinaryType,
                                            PBrowserChild* aBrowser);
   virtual bool DeallocPTCPSocket(PTCPSocketChild*);
+  virtual PRemoteOpenFileChild* AllocPRemoteOpenFile(const URIParams&,
+                                                     PBrowserChild*);
+  virtual bool DeallocPRemoteOpenFile(PRemoteOpenFileChild*);
 };
 
 /**
  * Reference to the PNecko Child protocol.
  * Null if this is not a content process.
  */
 extern PNeckoChild *gNeckoChild;
 
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -7,36 +7,55 @@
 
 #include "nsHttp.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/RemoteOpenFileParent.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/network/TCPSocketParent.h"
+#include "mozilla/ipc/URIUtils.h"
 #include "mozilla/Preferences.h"
 
 #include "nsHTMLDNSPrefetch.h"
+#include "nsIAppsService.h"
+#include "nsEscape.h"
 
 using mozilla::dom::TabParent;
 using mozilla::net::PTCPSocketParent;
 using mozilla::dom::TCPSocketParent;
 
 namespace mozilla {
 namespace net {
 
 static bool gDisableIPCSecurity = false;
 static const char kPrefDisableIPCSecurity[] = "network.disable.ipc.security";
 
 // C++ file contents
 NeckoParent::NeckoParent()
 {
   Preferences::AddBoolVarCache(&gDisableIPCSecurity, kPrefDisableIPCSecurity);
+
+  if (!gDisableIPCSecurity) {
+    // cache values for core/packaged apps basepaths
+    nsAutoString corePath, webPath;
+    nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+    if (appsService) {
+      appsService->GetCoreAppsBasePath(corePath);
+      appsService->GetWebAppsBasePath(webPath);
+    }
+    // corePath may be empty: we don't use it for all build types
+    MOZ_ASSERT(!webPath.IsEmpty());
+
+    LossyCopyUTF16toASCII(corePath, mCoreAppsBasePath);
+    LossyCopyUTF16toASCII(webPath, mWebAppsBasePath);
+  }
 }
 
 NeckoParent::~NeckoParent()
 {
 }
 
 PHttpChannelParent*
 NeckoParent::AllocPHttpChannel(PBrowserParent* browser,
@@ -144,16 +163,126 @@ NeckoParent::RecvPTCPSocketConstructor(P
 bool
 NeckoParent::DeallocPTCPSocket(PTCPSocketParent* actor)
 {
   TCPSocketParent* p = static_cast<TCPSocketParent*>(actor);
   p->Release();
   return true;
 }
 
+PRemoteOpenFileParent*
+NeckoParent::AllocPRemoteOpenFile(const URIParams& aURI,
+                                  PBrowserParent* aBrowser)
+{
+  nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+  nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
+  if (!fileURL) {
+    return nullptr;
+  }
+
+  // security checks
+  if (!gDisableIPCSecurity) {
+    if (!aBrowser) {
+      NS_WARNING("NeckoParent::AllocPRemoteOpenFile: "
+                 "FATAL error: missing TabParent: KILLING CHILD PROCESS\n");
+      return nullptr;
+    }
+    nsRefPtr<TabParent> tabParent = static_cast<TabParent*>(aBrowser);
+    uint32_t appId = tabParent->OwnOrContainingAppId();
+    nsCOMPtr<nsIAppsService> appsService =
+      do_GetService(APPS_SERVICE_CONTRACTID);
+    if (!appsService) {
+      return nullptr;
+    }
+    nsCOMPtr<mozIDOMApplication> domApp;
+    nsresult rv = appsService->GetAppByLocalId(appId, getter_AddRefs(domApp));
+    if (!domApp) {
+      return nullptr;
+    }
+    nsCOMPtr<mozIApplication> mozApp = do_QueryInterface(domApp);
+    if (!mozApp) {
+      return nullptr;
+    }
+    bool hasManage = false;
+    rv = mozApp->HasPermission("webapps-manage", &hasManage);
+    if (NS_FAILED(rv)) {
+      return nullptr;
+    }
+
+    nsAutoCString requestedPath;
+    fileURL->GetPath(requestedPath);
+    NS_UnescapeURL(requestedPath);
+
+    if (hasManage) {
+      // webapps-manage permission means allow reading any application.zip file
+      // in either the regular webapps directory, or the core apps directory (if
+      // we're using one).
+      NS_NAMED_LITERAL_CSTRING(appzip, "/application.zip");
+      nsAutoCString pathEnd;
+      requestedPath.Right(pathEnd, appzip.Length());
+      if (!pathEnd.Equals(appzip)) {
+        return nullptr;
+      }
+      nsAutoCString pathStart;
+      requestedPath.Left(pathStart, mWebAppsBasePath.Length());
+      if (!pathStart.Equals(mWebAppsBasePath)) {
+        if (mCoreAppsBasePath.IsEmpty()) {
+          return nullptr;
+        }
+        requestedPath.Left(pathStart, mCoreAppsBasePath.Length());
+        if (!pathStart.Equals(mCoreAppsBasePath)) {
+          return nullptr;
+        }
+      }
+      // Finally: make sure there are no "../" in URI.
+      // Note: not checking for symlinks (would cause I/O for each path
+      // component).  So it's up to us to avoid creating symlinks that could
+      // provide attack vectors.
+      if (PL_strnstr(requestedPath.BeginReading(), "/../",
+                     requestedPath.Length())) {
+        NS_WARNING("NeckoParent::AllocPRemoteOpenFile: "
+                   "FATAL error: requested file URI contains '/../' "
+                   "KILLING CHILD PROCESS\n");
+        return nullptr;
+      }
+    } else {
+      // regular packaged apps can only access their own application.zip file
+      nsAutoString basePath;
+      rv = mozApp->GetBasePath(basePath);
+      if (NS_FAILED(rv)) {
+        return nullptr;
+      }
+      nsAutoString uuid;
+      rv = mozApp->GetId(uuid);
+      if (NS_FAILED(rv)) {
+        return nullptr;
+      }
+      nsPrintfCString mustMatch("%s/%s/application.zip",
+                                NS_LossyConvertUTF16toASCII(basePath).get(),
+                                NS_LossyConvertUTF16toASCII(uuid).get());
+      if (!requestedPath.Equals(mustMatch)) {
+        NS_WARNING("NeckoParent::AllocPRemoteOpenFile: "
+                   "FATAL error: requesting file other than application.zip: "
+                   "KILLING CHILD PROCESS\n");
+        return nullptr;
+      }
+    }
+  }
+
+  RemoteOpenFileParent* parent = new RemoteOpenFileParent(fileURL);
+  return parent;
+}
+
+bool
+NeckoParent::DeallocPRemoteOpenFile(PRemoteOpenFileParent* actor)
+{
+  delete actor;
+  return true;
+}
+
 bool
 NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname,
                                  const uint16_t& flags)
 {
   nsHTMLDNSPrefetch::Prefetch(hostname, flags);
   return true;
 }
 
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -34,27 +34,35 @@ protected:
   virtual bool DeallocPFTPChannel(PFTPChannelParent*);
   virtual PWebSocketParent* AllocPWebSocket(PBrowserParent* browser);
   virtual bool DeallocPWebSocket(PWebSocketParent*);
   virtual PTCPSocketParent* AllocPTCPSocket(const nsString& aHost,
                                             const uint16_t& aPort,
                                             const bool& useSSL,
                                             const nsString& aBinaryType,
                                             PBrowserParent* aBrowser);
+  virtual PRemoteOpenFileParent* AllocPRemoteOpenFile(
+                                            const URIParams& fileuri,
+                                            PBrowserParent* browser);
+  virtual bool DeallocPRemoteOpenFile(PRemoteOpenFileParent* actor);
+
   virtual bool RecvPTCPSocketConstructor(PTCPSocketParent*,
                                          const nsString& aHost,
                                          const uint16_t& aPort,
                                          const bool& useSSL,
                                          const nsString& aBinaryType,
                                          PBrowserParent* aBrowser);
   virtual bool DeallocPTCPSocket(PTCPSocketParent*);
   virtual bool RecvHTMLDNSPrefetch(const nsString& hostname,
                                    const uint16_t& flags);
   virtual bool RecvCancelHTMLDNSPrefetch(const nsString& hostname,
                                          const uint16_t& flags,
                                          const nsresult& reason);
 
+private:
+  nsCString mCoreAppsBasePath;
+  nsCString mWebAppsBasePath;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_NeckoParent_h
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -8,16 +8,18 @@
 include protocol PContent;
 include protocol PHttpChannel;
 include protocol PCookieService;
 include protocol PBrowser;
 include protocol PWyciwygChannel;
 include protocol PFTPChannel;
 include protocol PWebSocket;
 include protocol PTCPSocket;
+include protocol PRemoteOpenFile;
+include URIParams;
 
 include "SerializedLoadContext.h";
 
 using IPC::SerializedLoadContext;
 
 namespace mozilla {
 namespace net {
 
@@ -27,28 +29,30 @@ sync protocol PNecko
 {
   manager PContent;
   manages PHttpChannel;
   manages PCookieService;
   manages PWyciwygChannel;
   manages PFTPChannel;
   manages PWebSocket;
   manages PTCPSocket;
+  manages PRemoteOpenFile;
 
 parent:
   __delete__();
 
   PCookieService();
   PHttpChannel(nullable PBrowser browser,
                SerializedLoadContext loadContext);
   PWyciwygChannel();
   PFTPChannel();
   PWebSocket(PBrowser browser);
   PTCPSocket(nsString host, uint16_t port, bool useSSL, nsString binaryType,
              nullable PBrowser browser);
+  PRemoteOpenFile(URIParams fileuri, nullable PBrowser browser);
 
   HTMLDNSPrefetch(nsString hostname, uint16_t flags);
   CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason);
 
 };
 
 
 } // namespace net
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/PRemoteOpenFile.ipdl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* 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 protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+/**
+ * Protocol to support RemoteOpenFile, an nsIFile that opens it's file handle on
+ * the parent instead of the child (since child lacks permission to do so).
+ */
+protocol PRemoteOpenFile
+{
+  manager PNecko;
+
+parent:
+  // Tell parent to open file. URI to open was passed and vetted for security in
+  // IPDL constructor: see NeckoParent::AllocPRemoteOpenFile()
+  AsyncOpenFile();
+
+  __delete__();
+
+child:
+  // success/failure code, and if NS_SUCCEEDED(rv), an open file descriptor
+  FileOpened(FileDescriptor fd, nsresult rv);
+};
+
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/RemoteOpenFileChild.cpp
@@ -0,0 +1,644 @@
+/* -*- 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 "mozilla/net/NeckoChild.h"
+#include "mozilla/net/RemoteOpenFileChild.h"
+#include "nsIRemoteOpenFileListener.h"
+#include "mozilla/ipc/URIUtils.h"
+
+// needed to alloc/free NSPR file descriptors
+#include "private/pprio.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(RemoteOpenFileChild,
+                              nsIFile,
+                              nsIHashable)
+
+
+RemoteOpenFileChild::RemoteOpenFileChild(const RemoteOpenFileChild& other)
+  : mNSPRFileDesc(other.mNSPRFileDesc)
+  , mAsyncOpenCalled(other.mAsyncOpenCalled)
+  , mNSPROpenCalled(other.mNSPROpenCalled)
+{
+  // Note: don't clone mListener or we'll have a refcount leak.
+  other.mURI->Clone(getter_AddRefs(mURI));
+  other.mFile->Clone(getter_AddRefs(mFile));
+}
+
+RemoteOpenFileChild::~RemoteOpenFileChild()
+{
+  if (mNSPRFileDesc) {
+    // If we handed out fd we shouldn't have pointer to it any more.
+    MOZ_ASSERT(!mNSPROpenCalled);
+    // PR_Close both closes the file and deallocates the PRFileDesc
+    PR_Close(mNSPRFileDesc);
+  }
+}
+
+nsresult
+RemoteOpenFileChild::Init(nsIURI* aRemoteOpenUri)
+{
+  if (!aRemoteOpenUri) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsAutoCString scheme;
+  nsresult rv = aRemoteOpenUri->GetScheme(scheme);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!scheme.EqualsLiteral("remoteopenfile")) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // scheme of URI is not file:// so this is not a nsIFileURL.  Convert to one.
+  nsCOMPtr<nsIURI> clonedURI;
+  rv = aRemoteOpenUri->Clone(getter_AddRefs(clonedURI));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  clonedURI->SetScheme(NS_LITERAL_CSTRING("file"));
+  nsAutoCString spec;
+  clonedURI->GetSpec(spec);
+
+  rv = NS_NewURI(getter_AddRefs(mURI), spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Get nsIFile
+  nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mURI);
+  if (!fileURL) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  rv = fileURL->GetFile(getter_AddRefs(mFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+RemoteOpenFileChild::AsyncRemoteFileOpen(int32_t aFlags,
+                                         nsIRemoteOpenFileListener* aListener,
+                                         nsITabChild* aTabChild)
+{
+  if (!mFile) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  if (!aListener) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (mAsyncOpenCalled) {
+    return NS_ERROR_ALREADY_OPENED;
+  }
+
+  if (aFlags != PR_RDONLY) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mozilla::dom::TabChild* tabChild = nullptr;
+  if (aTabChild) {
+    tabChild = static_cast<mozilla::dom::TabChild*>(aTabChild);
+  }
+
+#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
+  // we do nothing on these platforms: we'll just open file locally when asked
+  // for NSPR handle
+  mListener->OnRemoteFileOpenComplete(NS_OK);
+  mListener = nullptr;
+  mAsyncOpenCalled = true;
+  return NS_OK;
+
+#else
+  URIParams uri;
+  SerializeURI(mURI, uri);
+
+  gNeckoChild->SendPRemoteOpenFileConstructor(this, uri, tabChild);
+
+  // Can't seem to reply from within IPDL Parent constructor, so send open as
+  // separate message
+  SendAsyncOpenFile();
+
+  // The chrome process now has a logical ref to us until we call Send__delete
+  AddIPDLReference();
+
+  mListener = aListener;
+  mAsyncOpenCalled = true;
+  return NS_OK;
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// RemoteOpenFileChild::PRemoteOpenFileChild
+//-----------------------------------------------------------------------------
+
+bool
+RemoteOpenFileChild::RecvFileOpened(const FileDescriptor& aFD,
+                                    const nsresult& aRV)
+{
+#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
+  NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here");
+#else
+  if (NS_SUCCEEDED(aRV)) {
+    mNSPRFileDesc = PR_AllocFileDesc(aFD.PlatformHandle(), PR_GetFileMethods());
+  }
+
+  MOZ_ASSERT(mListener);
+
+  mListener->OnRemoteFileOpenComplete(aRV);
+
+  mListener = nullptr;     // release ref to listener
+
+  // This calls NeckoChild::DeallocPRemoteOpenFile(), which deletes |this| if
+  // IPDL holds the last reference.  Don't rely on |this| existing after here!
+  Send__delete__(this);
+
+#endif
+
+  return true;
+}
+
+void
+RemoteOpenFileChild::AddIPDLReference()
+{
+  AddRef();
+}
+
+void
+RemoteOpenFileChild::ReleaseIPDLReference()
+{
+  // if we haven't gotten fd from parent yet, we're not going to.
+  if (mListener) {
+    mListener->OnRemoteFileOpenComplete(NS_ERROR_UNEXPECTED);
+    mListener = nullptr;
+  }
+
+  Release();
+}
+
+//-----------------------------------------------------------------------------
+// RemoteOpenFileChild::nsIFile functions that we override logic for
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Clone(nsIFile **file)
+{
+  *file = new RemoteOpenFileChild(*this);
+  NS_ADDREF(*file);
+
+  // if we transferred ownership of file to clone, forget our pointer.
+  if (mNSPRFileDesc) {
+    mNSPRFileDesc = nullptr;
+  }
+
+  return NS_OK;
+}
+
+/* The main event: get file descriptor from parent process
+ */
+NS_IMETHODIMP
+RemoteOpenFileChild::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
+                                      PRFileDesc **aRetval)
+{
+#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
+  // Windows and OSX builds: just open nsIFile locally.
+  return mFile->OpenNSPRFileDesc(aFlags, aMode, aRetval);
+
+#else
+  if (aFlags != PR_RDONLY) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Unlike regular nsIFile we can't (easily) support multiple open()s.
+  if (mNSPROpenCalled) {
+    return NS_ERROR_ALREADY_OPENED;
+  }
+
+  if (!mNSPRFileDesc) {
+    // client skipped AsyncRemoteFileOpen() or didn't wait for result, or this
+    // object has been cloned
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // hand off ownership (i.e responsibility to PR_Close() file handle) to caller
+  *aRetval = mNSPRFileDesc;
+  mNSPRFileDesc = nullptr;
+  mNSPROpenCalled = true;
+
+  return NS_OK;
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// RemoteOpenFileChild::nsIFile functions that we delegate to underlying nsIFile
+//-----------------------------------------------------------------------------
+
+nsresult
+RemoteOpenFileChild::GetLeafName(nsAString &aLeafName)
+{
+  return mFile->GetLeafName(aLeafName);
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetNativeLeafName(nsACString &aLeafName)
+{
+  return mFile->GetNativeLeafName(aLeafName);
+}
+
+nsresult
+RemoteOpenFileChild::GetTarget(nsAString &_retval)
+{
+  return mFile->GetTarget(_retval);
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetNativeTarget(nsACString &_retval)
+{
+  return mFile->GetNativeTarget(_retval);
+}
+
+nsresult
+RemoteOpenFileChild::GetPath(nsAString &_retval)
+{
+  return mFile->GetPath(_retval);
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetNativePath(nsACString &_retval)
+{
+  return mFile->GetNativePath(_retval);
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Equals(nsIFile *inFile, bool *_retval)
+{
+  return mFile->Equals(inFile, _retval);
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Contains(nsIFile *inFile, bool recur, bool *_retval)
+{
+  return mFile->Contains(inFile, recur, _retval);
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetParent(nsIFile **aParent)
+{
+  return mFile->GetParent(aParent);
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetFollowLinks(bool *aFollowLinks)
+{
+  return mFile->GetFollowLinks(aFollowLinks);
+}
+
+//-----------------------------------------------------------------------------
+// RemoteOpenFileChild::nsIFile functions that are not currently supported
+//-----------------------------------------------------------------------------
+
+nsresult
+RemoteOpenFileChild::Append(const nsAString &node)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::AppendNative(const nsACString &fragment)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Normalize()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Create(uint32_t type, uint32_t permissions)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+RemoteOpenFileChild::SetLeafName(const nsAString &aLeafName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetNativeLeafName(const nsACString &aLeafName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+RemoteOpenFileChild::InitWithPath(const nsAString &filePath)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::InitWithNativePath(const nsACString &filePath)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::InitWithFile(nsIFile *aFile)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetFollowLinks(bool aFollowLinks)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult  
+RemoteOpenFileChild::AppendRelativePath(const nsAString &node)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::AppendRelativeNativePath(const nsACString &fragment)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetPersistentDescriptor(nsACString &aPersistentDescriptor)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetPersistentDescriptor(const nsACString &aPersistentDescriptor)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetRelativeDescriptor(nsIFile *fromFile, nsACString& _retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetRelativeDescriptor(nsIFile *fromFile,
+                                   const nsACString& relativeDesc)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+RemoteOpenFileChild::CopyTo(nsIFile *newParentDir, const nsAString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::CopyToNative(nsIFile *newParent, const nsACString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+RemoteOpenFileChild::CopyToFollowingLinks(nsIFile *newParentDir,
+                                  const nsAString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::CopyToFollowingLinksNative(nsIFile *newParent,
+                                        const nsACString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+RemoteOpenFileChild::MoveTo(nsIFile *newParentDir, const nsAString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::MoveToNative(nsIFile *newParent, const nsACString &newName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Remove(bool recursive)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetPermissions(uint32_t *aPermissions)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetPermissions(uint32_t aPermissions)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetPermissionsOfLink(uint32_t *aPermissionsOfLink)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetPermissionsOfLink(uint32_t aPermissions)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetLastModifiedTime(PRTime *aLastModTime)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetLastModifiedTime(PRTime aLastModTime)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetLastModifiedTimeOfLink(PRTime *aLastModTimeOfLink)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetFileSize(int64_t *aFileSize)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::SetFileSize(int64_t aFileSize)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetFileSizeOfLink(int64_t *aFileSize)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Exists(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::IsWritable(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::IsReadable(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::IsExecutable(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::IsHidden(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::IsDirectory(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::IsFile(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::IsSymlink(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::IsSpecial(bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::CreateUnique(uint32_t type, uint32_t attributes)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetDirectoryEntries(nsISimpleEnumerator **entries)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::OpenANSIFileDesc(const char *mode, FILE **_retval)
+{
+  // TODO: can implement using fdopen()?
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Load(PRLibrary **_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Reveal()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Launch()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// RemoteOpenFileChild::nsIHashable functions that we delegate to underlying nsIFile
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+RemoteOpenFileChild::Equals(nsIHashable* aOther, bool *aResult)
+{
+  nsCOMPtr<nsIHashable> hashable = do_QueryInterface(mFile);
+
+  MOZ_ASSERT(hashable);
+
+  if (hashable) {
+    return hashable->Equals(aOther, aResult);
+  }
+  return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+RemoteOpenFileChild::GetHashCode(uint32_t *aResult)
+{
+  nsCOMPtr<nsIHashable> hashable = do_QueryInterface(mFile);
+
+  MOZ_ASSERT(hashable);
+
+  if (hashable) {
+    return hashable->GetHashCode(aResult);
+  }
+  return NS_ERROR_UNEXPECTED;
+}
+
+} // namespace net
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/RemoteOpenFileChild.h
@@ -0,0 +1,86 @@
+/* -*- 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 _RemoteOpenFileChild_h
+#define _RemoteOpenFileChild_h
+
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/net/PRemoteOpenFileChild.h"
+#include "nsILocalFile.h"
+#include "nsIRemoteOpenFileListener.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ * RemoteOpenFileChild: a thin wrapper around regular nsIFile classes that does
+ * IPC to open a file handle on parent instead of child.  Used when we can't
+ * open file handle on child (don't have permission), but we don't want the
+ * overhead of shipping all I/O traffic across IPDL.  Example: JAR files.
+ *
+ * To open a file handle with this class, AsyncRemoteFileOpen() must be called
+ * first.  After the listener's OnRemoteFileOpenComplete() is called, if the
+ * result is NS_OK, nsIFile.OpenNSPRFileDesc() may be called--once--to get the
+ * file handle.
+ *
+ * Note that calling Clone() on this class results in the filehandle ownership
+ * being passed on to the new RemoteOpenFileChild. I.e. if
+ * OnRemoteFileOpenComplete is called and then Clone(), OpenNSPRFileDesc() will
+ * work in the cloned object, but not in the original.
+ *
+ * This class should only be instantiated in a child process.
+ *
+ */
+class RemoteOpenFileChild MOZ_FINAL
+  : public PRemoteOpenFileChild
+  , public nsIFile
+  , public nsIHashable
+{
+public:
+  RemoteOpenFileChild()
+    : mNSPRFileDesc(nullptr)
+    , mAsyncOpenCalled(false)
+    , mNSPROpenCalled(false)
+  {}
+
+  virtual ~RemoteOpenFileChild();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIFILE
+  NS_DECL_NSIHASHABLE
+
+  // URI must be scheme 'remoteopenfile://': otherwise looks like a file:// uri.
+  nsresult Init(nsIURI* aRemoteOpenUri);
+
+  void AddIPDLReference();
+  void ReleaseIPDLReference();
+
+  // Send message to parent to tell it to open file handle for file.
+  // TabChild is required, for IPC security.
+  // Note: currently only PR_RDONLY is supported for 'flags'
+  nsresult AsyncRemoteFileOpen(int32_t aFlags,
+                               nsIRemoteOpenFileListener* aListener,
+                               nsITabChild* aTabChild);
+private:
+  RemoteOpenFileChild(const RemoteOpenFileChild& other);
+
+protected:
+  virtual bool RecvFileOpened(const FileDescriptor&, const nsresult&);
+
+  // regular nsIFile object, that we forward most calls to.
+  nsCOMPtr<nsIFile> mFile;
+  nsCOMPtr<nsIURI> mURI;
+  nsCOMPtr<nsIRemoteOpenFileListener> mListener;
+  PRFileDesc* mNSPRFileDesc;
+  bool mAsyncOpenCalled;
+  bool mNSPROpenCalled;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // _RemoteOpenFileChild_h
+
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/RemoteOpenFileParent.cpp
@@ -0,0 +1,69 @@
+/* -*- 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 "mozilla/net/RemoteOpenFileParent.h"
+#include "mozilla/unused.h"
+#include "nsEscape.h"
+
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
+#include <fcntl.h>
+#endif
+
+namespace mozilla {
+namespace net {
+
+RemoteOpenFileParent::RemoteOpenFileParent(nsIFileURL *aURI)
+  : mURI(aURI)
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
+  , mFd(-1)
+#endif
+{}
+
+RemoteOpenFileParent::~RemoteOpenFileParent()
+{
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
+  if (mFd != -1) {
+    // close file handle now that other process has it open, else we'll leak
+    // file handles in parent process
+    close(mFd);
+  }
+#endif
+}
+
+bool
+RemoteOpenFileParent::RecvAsyncOpenFile()
+{
+#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
+  NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here");
+#else
+
+  // TODO: make this async!
+
+  nsAutoCString path;
+  nsresult rv = mURI->GetFilePath(path);
+  NS_UnescapeURL(path);
+  if (NS_SUCCEEDED(rv)) {
+    int fd = open(path.get(), O_RDONLY);
+    if (fd != -1) {
+      unused << SendFileOpened(FileDescriptor(fd), NS_OK);
+      // file handle needs to stay open until it's shared with child (and IPDL
+      // is async, so hasn't happened yet). Close in destructor.
+      mFd = fd;
+      return true;
+    }
+  }
+
+  // Note: sending an invalid file descriptor currently kills the child process:
+  // but that's ok for our use case (failing to open application.jar).
+  unused << SendFileOpened(FileDescriptor(mFd), NS_ERROR_NOT_AVAILABLE);
+#endif // OS_TYPE
+
+  return true;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/RemoteOpenFileParent.h
@@ -0,0 +1,38 @@
+/* -*- 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 mozilla_net_RemoteOpenFileParent_h
+#define mozilla_net_RemoteOpenFileParent_h
+
+#include "mozilla/net/PRemoteOpenFileParent.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "nsIFileURL.h"
+
+namespace mozilla {
+namespace net {
+
+class RemoteOpenFileParent : public PRemoteOpenFileParent
+{
+public:
+  RemoteOpenFileParent(nsIFileURL* aURI);
+
+ ~RemoteOpenFileParent();
+
+  virtual bool RecvAsyncOpenFile();
+
+private:
+  nsCOMPtr<nsIFileURL> mURI;
+
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
+  int mFd;
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_RemoteOpenFileParent_h
--- a/netwerk/ipc/ipdl.mk
+++ b/netwerk/ipc/ipdl.mk
@@ -1,8 +1,9 @@
 # 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/.
 
 IPDLSRCS =          \
   PNecko.ipdl       \
+  PRemoteOpenFile.ipdl \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/nsIRemoteOpenFileListener.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 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 "nsISupports.idl"
+
+/**
+ * nsIRemoteOpenFileListener: passed to RemoteOpenFileChild::AsyncRemoteFileOpen.
+ *
+ * Interface for notifying when the file has been opened and is available in
+ * child.
+ */
+[uuid(5c89208c-fe2b-4e04-9783-93bcf5c3b783)]
+interface nsIRemoteOpenFileListener : nsISupports
+{
+    /**
+     * Called when result of opening RemoteOpenFileChild:AsyncRemoteFileOpen()
+     * is available in child.
+     *
+     * @param aOpenStatus: nsresult from opening file in parent.  If NS_OK,
+     * then a following call to RemoteOpenFileChild::OpenNSPRFileDesc that
+     * passes the same flags as were passed to
+     * RemoteOpenFileChild::AsyncRemoteFileOpen is guaranteed to succeed.  If
+     * !NS_OK or if different flags were passed, the call will fail.
+     */
+    void onRemoteFileOpenComplete(in nsresult aOpenStatus);
+};
+
--- a/netwerk/protocol/app/AppProtocolHandler.js
+++ b/netwerk/protocol/app/AppProtocolHandler.js
@@ -11,38 +11,40 @@ const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsISyncMessageSender");
 
 function AppProtocolHandler() {
-  this._basePath = [];
+  this._appInfo = [];
+  this._runningInParent = Cc["@mozilla.org/xre/runtime;1"]
+                            .getService(Ci.nsIXULRuntime)
+                            .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
 }
 
 AppProtocolHandler.prototype = {
   classID: Components.ID("{b7ad6144-d344-4687-b2d0-b6b9dce1f07f}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
 
   scheme: "app",
   defaultPort: -1,
   // Don't allow loading from other protocols, and only from app:// if webapps is granted
   protocolFlags: Ci.nsIProtocolHandler.URI_NOAUTH |
                  Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
                  Ci.nsIProtocolHandler.URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM,
 
-  getBasePath: function app_phGetBasePath(aId) {
+  getAppInfo: function app_phGetAppInfo(aId) {
 
-    if (!this._basePath[aId]) {
-      this._basePath[aId] = cpmm.sendSyncMessage("Webapps:GetBasePath",
-                                                 { id: aId })[0] + "/";
+    if (!this._appInfo[aId]) {
+      let reply = cpmm.sendSyncMessage("Webapps:GetAppInfo", { id: aId });
+      this._appInfo[aId] = reply[0];
     }
-
-    return this._basePath[aId];
+    return this._appInfo[aId];
   },
 
   newURI: function app_phNewURI(aSpec, aOriginCharset, aBaseURI) {
     let uri = Cc["@mozilla.org/network/standard-url;1"]
               .createInstance(Ci.nsIStandardURL);
     uri.init(Ci.nsIStandardURL.URLTYPE_STANDARD, -1, aSpec, aOriginCharset,
              aBaseURI);
     return uri.QueryInterface(Ci.nsIURI);
@@ -57,17 +59,25 @@ AppProtocolHandler.prototype = {
     let appId = noScheme;
     let fileSpec = aURI.path;
 
     if (firstSlash) {
       appId = noScheme.substring(0, firstSlash);
     }
 
     // Build a jar channel and masquerade as an app:// URI.
-    let uri = "jar:file://" + this.getBasePath(appId) + appId + "/application.zip!" + fileSpec;
+    let appInfo = this.getAppInfo(appId);
+    let uri;
+    if (this._runningInParent || appInfo.isCoreApp) {
+      // In-parent and CoreApps can directly access files, so use jar:file://
+      uri = "jar:file://" + appInfo.basePath + appId + "/application.zip!" + fileSpec;
+    } else {
+      // non-CoreApps in child need to ask parent for file handle, use jar:ipcfile://
+      uri = "jar:remoteopenfile://" + appInfo.basePath + appId + "/application.zip!" + fileSpec;
+    }
     let channel = Services.io.newChannel(uri, null, null);
     channel.QueryInterface(Ci.nsIJARChannel).setAppURI(aURI);
     channel.QueryInterface(Ci.nsIChannel).originalURI = aURI;
 
     return channel;
   },
 
   allowPort: function app_phAllowPort(aPort, aScheme) {