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 id24076
push userryanvm@gmail.com
push dateSun, 23 Dec 2012 20:50:19 +0000
treeherdermozilla-central@4f74d77d6d8b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice, mwu, jdm
bugs815523
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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) {