Bug 835698 - 'Pre-open() and send the fd for app process's application.zip'. r=jduell.
☠☠ backed out by d509e44cae3e ☠ ☠
authorBen Turner <bent.mozilla@gmail.com>
Sat, 09 Feb 2013 17:59:47 +0000
changeset 121806 1646e649878adb0b53954dd5dec31671dbe62cf4
parent 121805 274946fd76c49dc3e2d909f334d2d7acfc51f10a
child 121807 9e3d9776842f4446a962500bf666142b28e2aaa9
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersjduell
bugs835698
milestone21.0a1
Bug 835698 - 'Pre-open() and send the fd for app process's application.zip'. r=jduell.
dom/ipc/Makefile.in
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/ipc/nsICachedFileDescriptorListener.h
ipc/glue/FileDescriptor.cpp
ipc/glue/FileDescriptor.h
ipc/glue/FileDescriptorUtils.cpp
ipc/glue/FileDescriptorUtils.h
ipc/glue/Makefile.in
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
modules/libjar/nsJARProtocolHandler.cpp
modules/libjar/nsJARProtocolHandler.h
netwerk/ipc/Makefile.in
netwerk/ipc/RemoteOpenFileChild.cpp
netwerk/ipc/RemoteOpenFileChild.h
--- a/dom/ipc/Makefile.in
+++ b/dom/ipc/Makefile.in
@@ -17,17 +17,20 @@ EXPORT_LIBRARY = 1
 ifndef _MSC_VER
 FAIL_ON_WARNINGS := 1
 endif # !_MSC_VER
 
 ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 TEST_DIRS += tests
 endif
 
-EXPORTS = PCOMContentPermissionRequestChild.h
+EXPORTS = \
+  nsICachedFileDescriptorListener.h \
+  PCOMContentPermissionRequestChild.h \
+  $(NULL)
 
 EXPORTS_NAMESPACES = \
   mozilla \
   mozilla/dom \
   mozilla/dom/ipc \
   $(NULL)
 
 EXPORTS_mozilla = \
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -26,16 +26,17 @@ include "mozilla/layout/RenderFrameUtils
 
 using IPC::Principal;
 using gfxMatrix;
 using gfxRect;
 using gfxSize;
 using mozilla::layers::LayersBackend;
 using mozilla::layers::FrameMetrics;
 using mozilla::layout::ScrollingBehavior;
+using mozilla::void_t;
 using mozilla::WindowsHandle;
 using nscolor;
 using nsCompositionEvent;
 using nsIMEUpdatePreference;
 using nsIntPoint;
 using nsIntRect;
 using nsIntSize;
 using nsKeyEvent;
@@ -285,16 +286,18 @@ child:
      * |Show()| and |Move()| take IntSizes rather than Rects because
      * content processes always render to a virtual <0, 0> top-left
      * point.
      */
     Show(nsIntSize size);
 
     LoadURL(nsCString uri);
 
+    CacheFileDescriptor(nsString path, FileDescriptor fd);
+
     UpdateDimensions(nsRect rect, nsIntSize size, ScreenOrientation orientation) compress;
 
     UpdateFrame(FrameMetrics frame) compress;
 
     /**
      * Requests handling of a double tap. |point| is in CSS pixels, relative to
      * the scroll offset. This message is expected to round-trip back to
      * ZoomToRect() with a rect indicating where we should zoom to.
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -13,16 +13,17 @@
 #include "ContentChild.h"
 #include "IndexedDBChild.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/IntentionalCrash.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/PContentChild.h"
 #include "mozilla/dom/PContentDialogChild.h"
 #include "mozilla/ipc/DocumentRendererChild.h"
+#include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/PLayersChild.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "mozIApplication.h"
 #include "nsComponentManagerUtils.h"
@@ -32,16 +33,17 @@
 #include "nsEventListenerManager.h"
 #include <algorithm>
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 #include "mozilla/dom/Element.h"
 #include "nsIAppsService.h"
 #include "nsIBaseWindow.h"
+#include "nsICachedFileDescriptorListener.h"
 #include "nsIComponentManager.h"
 #include "nsIDocumentInlines.h"
 #include "nsIDOMClassInfo.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIDocShell.h"
@@ -116,16 +118,108 @@ ContentListener::HandleEvent(nsIDOMEvent
 
 class ContentDialogChild : public PContentDialogChild
 {
 public:
   virtual bool Recv__delete__(const InfallibleTArray<int>& aIntParams,
                               const InfallibleTArray<nsString>& aStringParams);
 };
 
+class TabChild::CachedFileDescriptorInfo
+{
+    struct PathOnlyComparatorHelper
+    {
+        bool Equals(const nsAutoPtr<CachedFileDescriptorInfo>& a,
+                    const CachedFileDescriptorInfo& b) const
+        {
+            return a->mPath == b.mPath;
+        }
+    };
+
+    struct PathAndCallbackComparatorHelper
+    {
+        bool Equals(const nsAutoPtr<CachedFileDescriptorInfo>& a,
+                    const CachedFileDescriptorInfo& b) const
+        {
+            return a->mPath == b.mPath &&
+                   a->mCallback == b.mCallback;
+        }
+    };
+
+public:
+    nsString mPath;
+    FileDescriptor mFileDescriptor;
+    nsCOMPtr<nsICachedFileDescriptorListener> mCallback;
+    bool mCanceled;
+
+    CachedFileDescriptorInfo(const nsAString& aPath)
+      : mPath(aPath), mCanceled(false)
+    { }
+
+    CachedFileDescriptorInfo(const nsAString& aPath,
+                             const FileDescriptor& aFileDescriptor)
+      : mPath(aPath), mFileDescriptor(aFileDescriptor), mCanceled(false)
+    { }
+
+    CachedFileDescriptorInfo(const nsAString& aPath,
+                             nsICachedFileDescriptorListener* aCallback)
+      : mPath(aPath), mCallback(aCallback), mCanceled(false)
+    { }
+
+    PathOnlyComparatorHelper PathOnlyComparator() const
+    {
+        return PathOnlyComparatorHelper();
+    }
+
+    PathAndCallbackComparatorHelper PathAndCallbackComparator() const
+    {
+        return PathAndCallbackComparatorHelper();
+    }
+
+    void FireCallback() const
+    {
+        mCallback->OnCachedFileDescriptor(mPath, mFileDescriptor);
+    }
+};
+
+class TabChild::CachedFileDescriptorCallbackRunnable : public nsRunnable
+{
+    typedef TabChild::CachedFileDescriptorInfo CachedFileDescriptorInfo;
+
+    nsAutoPtr<CachedFileDescriptorInfo> mInfo;
+
+public:
+    CachedFileDescriptorCallbackRunnable(CachedFileDescriptorInfo* aInfo)
+      : mInfo(aInfo)
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(aInfo);
+        MOZ_ASSERT(!aInfo->mPath.IsEmpty());
+        MOZ_ASSERT(aInfo->mCallback);
+    }
+
+    void Dispatch()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+
+        nsresult rv = NS_DispatchToCurrentThread(this);
+        NS_ENSURE_SUCCESS_VOID(rv);
+    }
+
+private:
+    NS_IMETHOD Run()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(mInfo);
+
+        mInfo->FireCallback();
+        return NS_OK;
+    }
+};
+
 StaticRefPtr<TabChild> sPreallocatedTab;
 
 /*static*/ void
 TabChild::PreloadSlowThings()
 {
     MOZ_ASSERT(!sPreallocatedTab);
 
     nsRefPtr<TabChild> tab(new TabChild(TabContext(), /* chromeFlags */ 0));
@@ -1112,16 +1206,140 @@ TabChild::RecvLoadURL(const nsCString& u
 
 #ifdef MOZ_CRASHREPORTER
     CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("URL"), uri);
 #endif
 
     return true;
 }
 
+bool
+TabChild::RecvCacheFileDescriptor(const nsString& aPath,
+                                  const FileDescriptor& aFileDescriptor)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!aPath.IsEmpty());
+
+    // aFileDescriptor may be invalid here, but the callback will choose how to
+    // handle it.
+
+    // First see if we already have a request for this path.
+    const CachedFileDescriptorInfo search(aPath);
+    uint32_t index =
+        mCachedFileDescriptorInfos.IndexOf(search, 0,
+                                           search.PathOnlyComparator());
+    if (index == mCachedFileDescriptorInfos.NoIndex) {
+        // We haven't had any requests for this path yet. Assume that we will
+        // in a little while and save the file descriptor here.
+        mCachedFileDescriptorInfos.AppendElement(
+            new CachedFileDescriptorInfo(aPath, aFileDescriptor));
+        return true;
+    }
+
+    nsAutoPtr<CachedFileDescriptorInfo>& info =
+        mCachedFileDescriptorInfos[index];
+
+    MOZ_ASSERT(info);
+    MOZ_ASSERT(info->mPath == aPath);
+    MOZ_ASSERT(!info->mFileDescriptor.IsValid());
+    MOZ_ASSERT(info->mCallback);
+
+    // If this callback has been canceled then we can simply close the file
+    // descriptor and forget about the callback.
+    if (info->mCanceled) {
+        // Only close if this is a valid file descriptor.
+        if (aFileDescriptor.IsValid()) {
+            nsRefPtr<CloseFileRunnable> runnable =
+                new CloseFileRunnable(aFileDescriptor);
+            runnable->Dispatch();
+        }
+    } else {
+        // Not canceled so fire the callback.
+        info->mFileDescriptor = aFileDescriptor;
+
+        // We don't need a runnable here because we should already be at the top
+        // of the event loop. Just fire immediately.
+        info->FireCallback();
+    }
+
+    mCachedFileDescriptorInfos.RemoveElementAt(index);
+    return true;
+}
+
+bool
+TabChild::GetCachedFileDescriptor(const nsAString& aPath,
+                                  nsICachedFileDescriptorListener* aCallback)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!aPath.IsEmpty());
+    MOZ_ASSERT(aCallback);
+
+    // First see if we've already received a cached file descriptor for this
+    // path.
+    const CachedFileDescriptorInfo search(aPath);
+    uint32_t index =
+        mCachedFileDescriptorInfos.IndexOf(search, 0,
+                                           search.PathOnlyComparator());
+    if (index == mCachedFileDescriptorInfos.NoIndex) {
+        // We haven't received a file descriptor for this path yet. Assume that
+        // we will in a little while and save the request here.
+        mCachedFileDescriptorInfos.AppendElement(
+            new CachedFileDescriptorInfo(aPath, aCallback));
+        return false;
+    }
+
+    nsAutoPtr<CachedFileDescriptorInfo>& info =
+        mCachedFileDescriptorInfos[index];
+
+    MOZ_ASSERT(info);
+    MOZ_ASSERT(info->mPath == aPath);
+    MOZ_ASSERT(!info->mCallback);
+    MOZ_ASSERT(!info->mCanceled);
+
+    info->mCallback = aCallback;
+
+    nsRefPtr<CachedFileDescriptorCallbackRunnable> runnable =
+        new CachedFileDescriptorCallbackRunnable(info.forget());
+    runnable->Dispatch();
+
+    mCachedFileDescriptorInfos.RemoveElementAt(index);
+    return true;
+}
+
+void
+TabChild::CancelCachedFileDescriptorCallback(
+                                     const nsAString& aPath,
+                                     nsICachedFileDescriptorListener* aCallback)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!aPath.IsEmpty());
+    MOZ_ASSERT(aCallback);
+
+    const CachedFileDescriptorInfo search(aPath, aCallback);
+    uint32_t index =
+        mCachedFileDescriptorInfos.IndexOf(search, 0,
+                                           search.PathAndCallbackComparator());
+    if (index == mCachedFileDescriptorInfos.NoIndex) {
+        // Nothing to do here.
+        return;
+    }
+
+    nsAutoPtr<CachedFileDescriptorInfo>& info =
+        mCachedFileDescriptorInfos[index];
+
+    MOZ_ASSERT(info);
+    MOZ_ASSERT(info->mPath == aPath);
+    MOZ_ASSERT(!info->mFileDescriptor.IsValid());
+    MOZ_ASSERT(info->mCallback == aCallback);
+    MOZ_ASSERT(!info->mCanceled);
+
+    // Set this flag so that we will close the file descriptor when it arrives.
+    info->mCanceled = true;
+}
+
 void
 TabChild::DoFakeShow()
 {
   RecvShow(nsIntSize(0, 0));
   mDidFakeShow = true;
 }
 
 bool
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -49,16 +49,17 @@
 #include "nsWeakReference.h"
 #include "nsITabChild.h"
 #include "mozilla/Attributes.h"
 #include "FrameMetrics.h"
 #include "ProcessUtils.h"
 #include "mozilla/dom/TabContext.h"
 
 struct gfxMatrix;
+class nsICachedFileDescriptorListener;
 
 namespace mozilla {
 namespace layout {
 class RenderFrameChild;
 }
 
 namespace dom {
 
@@ -191,16 +192,19 @@ public:
      */
     virtual bool DoSendSyncMessage(const nsAString& aMessage,
                                    const mozilla::dom::StructuredCloneData& aData,
                                    InfallibleTArray<nsString>* aJSONRetVal);
     virtual bool DoSendAsyncMessage(const nsAString& aMessage,
                                     const mozilla::dom::StructuredCloneData& aData);
 
     virtual bool RecvLoadURL(const nsCString& uri);
+    virtual bool RecvCacheFileDescriptor(const nsString& aPath,
+                                         const FileDescriptor& aFileDescriptor)
+                                         MOZ_OVERRIDE;
     virtual bool RecvShow(const nsIntSize& size);
     virtual bool RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size, const ScreenOrientation& orientation);
     virtual bool RecvUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics);
     virtual bool RecvHandleDoubleTap(const nsIntPoint& aPoint);
     virtual bool RecvHandleSingleTap(const nsIntPoint& aPoint);
     virtual bool RecvHandleLongTap(const nsIntPoint& aPoint);
     virtual bool RecvActivate();
     virtual bool RecvDeactivate();
@@ -312,16 +316,25 @@ public:
     /**
      * Get this object's app type.
      *
      * A TabChild's app type corresponds to the value of its frame element's
      * "mozapptype" attribute.
      */
     void GetAppType(nsAString& aAppType) const { aAppType = mAppType; }
 
+    // Returns true if the file descriptor was found in the cache, false
+    // otherwise.
+    bool GetCachedFileDescriptor(const nsAString& aPath,
+                                 nsICachedFileDescriptorListener* aCallback);
+
+    void CancelCachedFileDescriptorCallback(
+                                    const nsAString& aPath,
+                                    nsICachedFileDescriptorListener* aCallback);
+
 protected:
     virtual PRenderFrameChild* AllocPRenderFrame(ScrollingBehavior* aScrolling,
                                                  LayersBackend* aBackend,
                                                  int32_t* aMaxTextureSize,
                                                  uint64_t* aLayersId) MOZ_OVERRIDE;
     virtual bool DeallocPRenderFrame(PRenderFrameChild* aFrame) MOZ_OVERRIDE;
     virtual bool RecvDestroy() MOZ_OVERRIDE;
 
@@ -407,16 +420,19 @@ private:
 
     nsIDOMWindowUtils* GetDOMWindowUtils()
     {
         nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(mWebNav);
         nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
         return utils;
     }
 
+    class CachedFileDescriptorInfo;
+    class CachedFileDescriptorCallbackRunnable;
+
     nsCOMPtr<nsIWebNavigation> mWebNav;
     nsCOMPtr<nsIWidget> mWidget;
     nsCOMPtr<nsIURI> mLastURI;
     FrameMetrics mLastMetrics;
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<TabChildGlobal> mTabChildGlobal;
     uint32_t mChromeFlags;
     nsIntRect mOuterRect;
@@ -425,16 +441,19 @@ private:
     // point of the touchstart.
     nsIntPoint mGestureDownPoint;
     // The touch identifier of the active gesture.
     int32_t mActivePointerId;
     // A timer task that fires if the tap-hold timeout is exceeded by
     // the touch we're tracking.  That is, if touchend or a touchmove
     // that exceeds the gesture threshold doesn't happen.
     CancelableTask* mTapHoldTimer;
+    // At present only 1 of these is really expected.
+    nsAutoTArray<nsAutoPtr<CachedFileDescriptorInfo>, 1>
+        mCachedFileDescriptorInfos;
     float mOldViewportWidth;
     nscolor mLastBackgroundColor;
     ScrollingBehavior mScrolling;
     bool mDidFakeShow;
     bool mNotified;
     bool mContentDocumentIsDisplayed;
     bool mTriedBrowserInit;
     nsString mAppType;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -45,32 +45,143 @@
 #include "nsIWidget.h"
 #include "nsIWindowWatcher.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsPrintfCString.h"
 #include "nsSerializationHelper.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
+#include "private/pprio.h"
 #include "StructuredCloneUtils.h"
 #include "TabChild.h"
 #include <algorithm>
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::services;
 using namespace mozilla::widget;
 using namespace mozilla::dom::indexedDB;
 
 // The flags passed by the webProgress notifications are 16 bits shifted
 // from the ones registered by webProgressListeners.
 #define NOTIFY_FLAG_SHIFT 16
 
+class OpenFileAndSendFDRunnable : public nsRunnable
+{
+    const nsString mPath;
+    nsRefPtr<TabParent> mTabParent;
+    nsCOMPtr<nsIEventTarget> mEventTarget;
+    PRFileDesc* mFD;
+
+public:
+    OpenFileAndSendFDRunnable(const nsAString& aPath, TabParent* aTabParent)
+      : mPath(aPath), mTabParent(aTabParent), mFD(nullptr)
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(!aPath.IsEmpty());
+        MOZ_ASSERT(aTabParent);
+    }
+
+    void Dispatch()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+
+        mEventTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+        NS_ENSURE_TRUE_VOID(mEventTarget);
+
+        nsresult rv = mEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+        NS_ENSURE_SUCCESS_VOID(rv);
+    }
+
+private:
+    ~OpenFileAndSendFDRunnable()
+    {
+        MOZ_ASSERT(!mFD);
+    }
+
+    // This shouldn't be called directly except by the event loop. Use Dispatch
+    // to start the sequence.
+    NS_IMETHOD Run()
+    {
+        if (NS_IsMainThread()) {
+            SendResponse();
+        } else if (mFD) {
+            CloseFile();
+        } else {
+            OpenFile();
+        }
+
+        return NS_OK;
+    }
+
+    void SendResponse()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(mTabParent);
+        MOZ_ASSERT(mEventTarget);
+        MOZ_ASSERT(mFD);
+
+        nsRefPtr<TabParent> tabParent;
+        mTabParent.swap(tabParent);
+
+        FileDescriptor::PlatformHandleType handle =
+            FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFD));
+
+        mozilla::unused << tabParent->SendCacheFileDescriptor(mPath, handle);
+
+        nsCOMPtr<nsIEventTarget> eventTarget;
+        mEventTarget.swap(eventTarget);
+
+        if (NS_FAILED(eventTarget->Dispatch(this, NS_DISPATCH_NORMAL))) {
+            NS_WARNING("Failed to dispatch to stream transport service!");
+
+            // It's probably safer to take the main thread IO hit here rather
+            // than leak a file descriptor.
+            CloseFile();
+        }
+    }
+
+    void OpenFile()
+    {
+        MOZ_ASSERT(!NS_IsMainThread());
+        MOZ_ASSERT(!mFD);
+
+        nsCOMPtr<nsIFile> file;
+        nsresult rv = NS_NewLocalFile(mPath, false, getter_AddRefs(file));
+        NS_ENSURE_SUCCESS_VOID(rv);
+
+        PRFileDesc* fd;
+        rv = file->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+        NS_ENSURE_SUCCESS_VOID(rv);
+
+        mFD = fd;
+
+        if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
+            NS_WARNING("Failed to dispatch to main thread!");
+
+            CloseFile();
+        }
+    }
+
+    void CloseFile()
+    {
+        // It's possible for this to happen on the main thread if the dispatch
+        // to the stream service fails after we've already opened the file so
+        // we can't assert the thread we're running on.
+
+        MOZ_ASSERT(mFD);
+
+        PR_Close(mFD);
+        mFD = nullptr;
+    }
+};
+
 namespace mozilla {
 namespace dom {
 
 TabParent* sEventCapturer;
 
 TabParent *TabParent::mIMETabParent = nullptr;
 
 NS_IMPL_ISUPPORTS3(TabParent, nsITabParent, nsIAuthPromptProvider, nsISecureBrowserUI)
@@ -88,16 +199,17 @@ TabParent::TabParent(const TabContext& a
   , mRect(0, 0, 0, 0)
   , mDimensions(0, 0)
   , mOrientation(0)
   , mDPI(0)
   , mShown(false)
   , mUpdatedDimensions(false)
   , mMarkedDestroying(false)
   , mIsDestroyed(false)
+  , mAppPackageFileDescriptorSent(false)
 {
 }
 
 TabParent::~TabParent()
 {
 }
 
 void
@@ -225,33 +337,76 @@ TabParent::AnswerCreateWindow(PBrowserPa
 
     *retval = frameLoader->GetRemoteBrowser();
     return true;
 }
 
 void
 TabParent::LoadURL(nsIURI* aURI)
 {
+    MOZ_ASSERT(aURI);
+
     if (mIsDestroyed) {
-      return;
-    }
-    if (!mShown) {
-      nsAutoCString spec;
-      if (aURI) {
-        aURI->GetSpec(spec);
-      }
-      NS_WARNING(nsPrintfCString("TabParent::LoadURL(%s) called before "
-                                 "Show(). Ignoring LoadURL.\n", spec.get()).get());
-      return;
+        return;
     }
 
     nsCString spec;
     aURI->GetSpec(spec);
 
+    if (!mShown) {
+        NS_WARNING(nsPrintfCString("TabParent::LoadURL(%s) called before "
+                                   "Show(). Ignoring LoadURL.\n",
+                                   spec.get()).get());
+        return;
+    }
+
     unused << SendLoadURL(spec);
+
+    // If this app is a packaged app then we can speed startup by sending over
+    // the file descriptor for the "application.zip" file that it will
+    // invariably request. Only do this once.
+    if (!mAppPackageFileDescriptorSent) {
+        mAppPackageFileDescriptorSent = true;
+
+        nsCOMPtr<mozIApplication> app = GetOwnOrContainingApp();
+        if (app) {
+            nsString manifestURL;
+            nsresult rv = app->GetManifestURL(manifestURL);
+            NS_ENSURE_SUCCESS_VOID(rv);
+
+            if (StringBeginsWith(manifestURL, NS_LITERAL_STRING("app:"))) {
+                nsString basePath;
+                rv = app->GetBasePath(basePath);
+                NS_ENSURE_SUCCESS_VOID(rv);
+
+                nsString appId;
+                rv = app->GetId(appId);
+                NS_ENSURE_SUCCESS_VOID(rv);
+
+                nsCOMPtr<nsIFile> packageFile;
+                rv = NS_NewLocalFile(basePath, false,
+                                     getter_AddRefs(packageFile));
+                NS_ENSURE_SUCCESS_VOID(rv);
+
+                rv = packageFile->Append(appId);
+                NS_ENSURE_SUCCESS_VOID(rv);
+
+                rv = packageFile->Append(NS_LITERAL_STRING("application.zip"));
+                NS_ENSURE_SUCCESS_VOID(rv);
+
+                nsString path;
+                rv = packageFile->GetPath(path);
+                NS_ENSURE_SUCCESS_VOID(rv);
+
+                nsRefPtr<OpenFileAndSendFDRunnable> openFileRunnable =
+                    new OpenFileAndSendFDRunnable(path, this);
+                openFileRunnable->Dispatch();
+            }
+        }
+    }
 }
 
 void
 TabParent::Show(const nsIntSize& size)
 {
     // sigh
     mShown = true;
     mDimensions = size;
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -299,14 +299,16 @@ private:
     void MaybeForwardEventToRenderFrame(const nsInputEvent& aEvent,
                                         nsInputEvent* aOutEvent);
     // When true, we've initiated normal shutdown and notified our
     // managing PContent.
     bool mMarkedDestroying;
     // When true, the TabParent is invalid and we should not send IPC messages
     // anymore.
     bool mIsDestroyed;
+    // Whether we have already sent a FileDescriptor for the app package.
+    bool mAppPackageFileDescriptorSent;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/ipc/nsICachedFileDescriptorListener.h
@@ -0,0 +1,38 @@
+/* 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_dom_ipc_nsICachedFileDescriptorListener_h
+#define mozilla_dom_ipc_nsICachedFileDescriptorListener_h
+
+#include "nsISupports.h"
+
+#ifndef NS_NO_VTABLE
+#define NS_NO_VTABLE
+#endif
+
+#define NS_ICACHEDFILEDESCRIPTORLISTENER_IID \
+  {0x2cedaee0, 0x6ef2, 0x4f60, {0x9a, 0x6c, 0xdf, 0x4e, 0x4d, 0x65, 0x6a, 0xf7}}
+
+class nsAString;
+
+namespace mozilla {
+namespace ipc {
+class FileDescriptor;
+}
+}
+
+class NS_NO_VTABLE nsICachedFileDescriptorListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICACHEDFILEDESCRIPTORLISTENER_IID)
+
+  virtual void
+  OnCachedFileDescriptor(const nsAString& aPath,
+                         const mozilla::ipc::FileDescriptor& aFD) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICachedFileDescriptorListener,
+                              NS_ICACHEDFILEDESCRIPTORLISTENER_IID)
+
+#endif // mozilla_dom_ipc_nsICachedFileDescriptorListener_h
--- a/ipc/glue/FileDescriptor.cpp
+++ b/ipc/glue/FileDescriptor.cpp
@@ -37,17 +37,18 @@ FileDescriptor::FileDescriptor(PlatformH
   mHandleCreatedByOtherProcessWasUsed(false)
 {
   DuplicateInCurrentProcess(aHandle);
 }
 
 void
 FileDescriptor::DuplicateInCurrentProcess(PlatformHandleType aHandle)
 {
-  MOZ_ASSERT(!mHandleCreatedByOtherProcess);
+  MOZ_ASSERT_IF(mHandleCreatedByOtherProcess,
+                mHandleCreatedByOtherProcessWasUsed);
 
   if (IsValid(aHandle)) {
     PlatformHandleType newHandle;
 #ifdef XP_WIN
     if (DuplicateHandle(GetCurrentProcess(), aHandle, GetCurrentProcess(),
                         &newHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
 #else // XP_WIN
     if ((newHandle = dup(aHandle)) != INVALID_HANDLE) {
--- a/ipc/glue/FileDescriptor.h
+++ b/ipc/glue/FileDescriptor.h
@@ -48,17 +48,19 @@ public:
   // This should only ever be created by IPDL.
   struct IPDLPrivate
   {};
 
   FileDescriptor();
 
   FileDescriptor(const FileDescriptor& aOther)
   {
-    *this = aOther;
+    // Don't use operator= here because that will call
+    // CloseCurrentProcessHandle() on this (uninitialized) object.
+    Assign(aOther);
   }
 
   FileDescriptor(PlatformHandleType aHandle);
 
   FileDescriptor(const IPDLPrivate&, const PickleType& aPickle)
 #ifdef XP_WIN
   : mHandle(aPickle)
 #else
@@ -72,28 +74,17 @@ public:
   {
     CloseCurrentProcessHandle();
   }
 
   FileDescriptor&
   operator=(const FileDescriptor& aOther)
   {
     CloseCurrentProcessHandle();
-
-    if (aOther.mHandleCreatedByOtherProcess) {
-      mHandleCreatedByOtherProcess = true;
-      mHandleCreatedByOtherProcessWasUsed =
-        aOther.mHandleCreatedByOtherProcessWasUsed;
-      mHandle = aOther.PlatformHandle();
-    } else {
-      DuplicateInCurrentProcess(aOther.PlatformHandle());
-      mHandleCreatedByOtherProcess = false;
-      mHandleCreatedByOtherProcessWasUsed = false;
-    }
-
+    Assign(aOther);
     return *this;
   }
 
   // Performs platform-specific actions to duplicate mHandle in the other
   // process (e.g. dup() on POSIX, DuplicateHandle() on Windows). Returns a
   // pickled value that can be passed to the other process via IPC.
   PickleType
   ShareTo(const IPDLPrivate&, ProcessHandle aOtherProcess) const;
@@ -117,16 +108,31 @@ public:
 
   bool
   operator==(const FileDescriptor& aOther) const
   {
     return mHandle == aOther.mHandle;
   }
 
 private:
+  void
+  Assign(const FileDescriptor& aOther)
+  {
+    if (aOther.mHandleCreatedByOtherProcess) {
+      mHandleCreatedByOtherProcess = true;
+      mHandleCreatedByOtherProcessWasUsed =
+        aOther.mHandleCreatedByOtherProcessWasUsed;
+      mHandle = aOther.PlatformHandle();
+    } else {
+      DuplicateInCurrentProcess(aOther.PlatformHandle());
+      mHandleCreatedByOtherProcess = false;
+      mHandleCreatedByOtherProcessWasUsed = false;
+    }
+  }
+
   static bool
   IsValid(PlatformHandleType aHandle);
 
   void
   DuplicateInCurrentProcess(PlatformHandleType aHandle);
 
   void
   CloseCurrentProcessHandle();
new file mode 100644
--- /dev/null
+++ b/ipc/glue/FileDescriptorUtils.cpp
@@ -0,0 +1,81 @@
+/* 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 "FileDescriptorUtils.h"
+
+#include "nsIEventTarget.h"
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "prio.h"
+#include "private/pprio.h"
+
+using mozilla::ipc::CloseFileRunnable;
+
+#ifdef DEBUG
+
+CloseFileRunnable::CloseFileRunnable(const FileDescriptor& aFileDescriptor)
+: mFileDescriptor(aFileDescriptor)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aFileDescriptor.IsValid());
+}
+
+#endif // DEBUG
+
+CloseFileRunnable::~CloseFileRunnable()
+{
+  if (mFileDescriptor.IsValid()) {
+    // It's probably safer to take the main thread IO hit here rather than leak
+    // the file descriptor.
+    CloseFile();
+  }
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(CloseFileRunnable, nsIRunnable)
+
+void
+CloseFileRunnable::Dispatch()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIEventTarget> eventTarget =
+    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  NS_ENSURE_TRUE_VOID(eventTarget);
+
+  nsresult rv = eventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+void
+CloseFileRunnable::CloseFile()
+{
+  // It's possible for this to happen on the main thread if the dispatch to the
+  // stream service fails so we can't assert the thread on which we're running.
+
+  MOZ_ASSERT(mFileDescriptor.IsValid());
+
+  PRFileDesc* fd =
+    PR_ImportFile(PROsfd(mFileDescriptor.PlatformHandle()));
+  NS_WARN_IF_FALSE(fd, "Failed to import file handle!");
+
+  mFileDescriptor = FileDescriptor();
+
+  if (fd) {
+    PR_Close(fd);
+    fd = nullptr;
+  }
+}
+
+NS_IMETHODIMP
+CloseFileRunnable::Run()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  CloseFile();
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/ipc/glue/FileDescriptorUtils.h
@@ -0,0 +1,47 @@
+
+/* 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_ipc_FileDescriptorUtils_h
+#define mozilla_ipc_FileDescriptorUtils_h
+
+#include "mozilla/ipc/FileDescriptor.h"
+#include "nsIRunnable.h"
+
+namespace mozilla {
+namespace ipc {
+
+// When Dispatch() is called (from main thread) this class arranges to close the
+// provided FileDescriptor on one of the socket transport service threads (to
+// avoid main thread I/O).
+class CloseFileRunnable : public nsIRunnable
+{
+  typedef mozilla::ipc::FileDescriptor FileDescriptor;
+
+  FileDescriptor mFileDescriptor;
+
+public:
+  CloseFileRunnable(const FileDescriptor& aFileDescriptor)
+#ifdef DEBUG
+  ;
+#else
+  : mFileDescriptor(aFileDescriptor)
+  { }
+#endif
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+
+  void Dispatch();
+
+private:
+  ~CloseFileRunnable();
+
+  void CloseFile();
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_FileDescriptorUtils_h
--- a/ipc/glue/Makefile.in
+++ b/ipc/glue/Makefile.in
@@ -21,16 +21,17 @@ EXPORTS_NAMESPACES = ipc mozilla/ipc
 
 EXPORTS_ipc = IPCMessageUtils.h
 
 EXPORTS_mozilla/ipc = \
   AsyncChannel.h \
   BrowserProcessSubThread.h \
   CrossProcessMutex.h \
   FileDescriptor.h \
+  FileDescriptorUtils.h \
   GeckoChildProcessHost.h \
   InputStreamUtils.h \
   IOThreadChild.h \
   ProcessChild.h \
   ProtocolUtils.h \
   RPCChannel.h \
   SharedMemory.h \
   SharedMemoryBasic.h \
@@ -66,16 +67,17 @@ EXPORTS_mozilla/ipc += SharedMemoryBasic
 else
 EXPORTS_mozilla/ipc += SharedMemoryBasic_chromium.h
 endif #}
 
 CPPSRCS += \
   AsyncChannel.cpp \
   BrowserProcessSubThread.cpp \
   FileDescriptor.cpp \
+  FileDescriptorUtils.cpp \
   GeckoChildProcessHost.cpp \
   InputStreamUtils.cpp \
   MessagePump.cpp \
   ProcessChild.cpp \
   ProtocolUtils.cpp \
   RPCChannel.cpp \
   ScopedXREEmbed.cpp \
   SharedMemory.cpp \
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -13,17 +13,16 @@
 #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;
 
@@ -344,19 +343,19 @@ nsJARChannel::LookupFile()
     // 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) {
+    if (!mJarFile && !gJarHandler->IsMainProcess()) {
         nsAutoCString scheme;
-        nsresult rv = mJarBaseURI->GetScheme(scheme);
+        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) {
@@ -364,23 +363,30 @@ nsJARChannel::LookupFile()
                 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;
                 }
             }
 
+            mOpeningRemote = true;
+
+            if (gJarHandler->RemoteOpenFileInProgress(remoteFile, this)) {
+                // JarHandler will trigger OnRemoteFileOpen() after the first
+                // request for this file completes and we'll get a JAR cache
+                // hit.
+                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;
@@ -392,16 +398,48 @@ nsJARChannel::LookupFile()
                 jarURI->GetJAREntry(mInnerJarEntry);
             }
         }
     }
 
     return rv;
 }
 
+nsresult
+nsJARChannel::OpenLocalFile()
+{
+    MOZ_ASSERT(mIsPending);
+
+    // Local files are always considered safe.
+    mIsUnsafe = false;
+
+    nsRefPtr<nsJARInputThunk> input;
+    nsresult 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);
+    }
+
+    return rv;
+}
+
+void
+nsJARChannel::NotifyError(nsresult aError)
+{
+    MOZ_ASSERT(NS_FAILED(aError));
+
+    mStatus = aError;
+
+    OnStartRequest(nullptr, nullptr);
+    OnStopRequest(nullptr, nullptr, aError);
+}
+
 //-----------------------------------------------------------------------------
 // nsIRequest
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsJARChannel::GetName(nsACString &result)
 {
     return mJarURI->GetSpec(result);
@@ -744,27 +782,17 @@ nsJARChannel::AsyncOpen(nsIStreamListene
         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 if (mOpeningRemote) {
         // nothing to do: already asked parent to open file.
     } else {
-        // local files 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);
-        }
+        rv = OpenLocalFile();
     }
 
     if (NS_FAILED(rv)) {
         mIsPending = false;
         mListenerContext = nullptr;
         mListener = nullptr;
         return rv;
     }
@@ -897,56 +925,43 @@ nsJARChannel::OnDownloadComplete(nsIDown
             rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
             if (NS_SUCCEEDED(rv))
                 rv = mPump->AsyncRead(this, nullptr);
         }
         status = rv;
     }
 
     if (NS_FAILED(status)) {
-        mStatus = status;
-        OnStartRequest(nullptr, nullptr);
-        OnStopRequest(nullptr, nullptr, status);
+        NotifyError(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);
-        }
+    // NS_ERROR_ALREADY_OPENED here means we'll hit JAR cache in
+    // OpenLocalFile().
+    if (NS_SUCCEEDED(rv) || rv == NS_ERROR_ALREADY_OPENED) {
+        rv = OpenLocalFile();
     }
 
     if (NS_FAILED(rv)) {
-        mStatus = rv;
-        OnStartRequest(nullptr, nullptr);
-        OnStopRequest(nullptr, nullptr, mStatus);
+        NotifyError(rv);
     }
 
     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
@@ -46,16 +46,18 @@ public:
     nsJARChannel();
     virtual ~nsJARChannel();
 
     nsresult Init(nsIURI *uri);
 
 private:
     nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
     nsresult LookupFile();
+    nsresult OpenLocalFile();
+    void NotifyError(nsresult aError);
 
 #if defined(PR_LOGGING)
     nsCString                       mSpec;
 #endif
 
     bool                            mOpened;
 
     nsCOMPtr<nsIJARURI>             mJarURI;
--- a/modules/libjar/nsJARProtocolHandler.cpp
+++ b/modules/libjar/nsJARProtocolHandler.cpp
@@ -12,27 +12,37 @@
 #include "nsJARURI.h"
 #include "nsIURL.h"
 #include "nsJARChannel.h"
 #include "nsXPIDLString.h"
 #include "nsString.h"
 #include "nsNetCID.h"
 #include "nsIMIMEService.h"
 #include "nsMimeTypes.h"
+#include "nsIRemoteOpenFileListener.h"
+#include "nsIHashable.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
 
 static NS_DEFINE_CID(kZipReaderCacheCID, NS_ZIPREADERCACHE_CID);
 
 #define NS_JAR_CACHE_SIZE 32
 
 //-----------------------------------------------------------------------------
 
 nsJARProtocolHandler *gJarHandler = nullptr;
 
 nsJARProtocolHandler::nsJARProtocolHandler()
+: mIsMainProcess(XRE_GetProcessType() == GeckoProcessType_Default)
 {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!mIsMainProcess) {
+        mRemoteFileListeners.Init();
+    }
 }
 
 nsJARProtocolHandler::~nsJARProtocolHandler()
 {
 }
 
 nsresult
 nsJARProtocolHandler::Init()
@@ -50,16 +60,77 @@ nsIMIMEService *
 nsJARProtocolHandler::MimeService()
 {
     if (!mMimeService)
         mMimeService = do_GetService("@mozilla.org/mime;1");
 
     return mMimeService.get();
 }
 
+bool
+nsJARProtocolHandler::RemoteOpenFileInProgress(
+                                           nsIHashable *aRemoteFile,
+                                           nsIRemoteOpenFileListener *aListener)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(aRemoteFile);
+    MOZ_ASSERT(aListener);
+
+    if (IsMainProcess()) {
+        MOZ_NOT_REACHED("Shouldn't be called in the main process!");
+        return false;
+    }
+
+    RemoteFileListenerArray *listeners;
+    if (mRemoteFileListeners.Get(aRemoteFile, &listeners)) {
+        listeners->AppendElement(aListener);
+        return true;
+    }
+
+    // We deliberately don't put the listener in the new array since the first
+    // load is handled differently.
+    mRemoteFileListeners.Put(aRemoteFile, new RemoteFileListenerArray());
+    return false;
+}
+
+void
+nsJARProtocolHandler::RemoteOpenFileComplete(nsIHashable *aRemoteFile,
+                                             nsresult aStatus)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(aRemoteFile);
+
+    if (IsMainProcess()) {
+        MOZ_NOT_REACHED("Shouldn't be called in the main process!");
+        return;
+    }
+
+    RemoteFileListenerArray *tempListeners;
+    if (!mRemoteFileListeners.Get(aRemoteFile, &tempListeners)) {
+        return;
+    }
+
+    // Save the listeners in a stack array. The call to Remove() below will
+    // delete the tempListeners array.
+    RemoteFileListenerArray listeners;
+    tempListeners->SwapElements(listeners);
+
+    mRemoteFileListeners.Remove(aRemoteFile);
+
+    // Technically we must fail OnRemoteFileComplete() since OpenNSPRFileDesc()
+    // won't succeed here. We've trained nsJARChannel to recognize
+    // NS_ERROR_ALREADY_OPENED in this case as "proceed to JAR cache hit."
+    nsresult status = NS_SUCCEEDED(aStatus) ? NS_ERROR_ALREADY_OPENED : aStatus;
+
+    uint32_t count = listeners.Length();
+    for (uint32_t index = 0; index < count; index++) {
+        listeners[index]->OnRemoteFileOpenComplete(status);
+    }
+}
+
 NS_IMPL_THREADSAFE_ISUPPORTS3(nsJARProtocolHandler,
                               nsIJARProtocolHandler,
                               nsIProtocolHandler,
                               nsISupportsWeakReference)
 
 nsJARProtocolHandler*
 nsJARProtocolHandler::GetSingleton()
 {
--- a/modules/libjar/nsJARProtocolHandler.h
+++ b/modules/libjar/nsJARProtocolHandler.h
@@ -8,20 +8,28 @@
 
 #include "nsIJARProtocolHandler.h"
 #include "nsIProtocolHandler.h"
 #include "nsIJARURI.h"
 #include "nsIZipReader.h"
 #include "nsIMIMEService.h"
 #include "nsWeakReference.h"
 #include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+
+class nsIHashable;
+class nsIRemoteOpenFileListener;
 
 class nsJARProtocolHandler : public nsIJARProtocolHandler
                            , public nsSupportsWeakReference
 {
+    typedef nsAutoTArray<nsCOMPtr<nsIRemoteOpenFileListener>, 5>
+            RemoteFileListenerArray;
+
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIPROTOCOLHANDLER
     NS_DECL_NSIJARPROTOCOLHANDLER
 
     // nsJARProtocolHandler methods:
     nsJARProtocolHandler();
     virtual ~nsJARProtocolHandler();
@@ -29,19 +37,32 @@ public:
     static nsJARProtocolHandler *GetSingleton();
 
     nsresult Init();
 
     // returns non addref'ed pointer.  
     nsIMIMEService    *MimeService();
     nsIZipReaderCache *JarCache() { return mJARCache; }
 
+    bool IsMainProcess() const { return mIsMainProcess; }
+
+    bool RemoteOpenFileInProgress(nsIHashable *aRemoteFile,
+                                  nsIRemoteOpenFileListener *aListener);
+    void RemoteOpenFileComplete(nsIHashable *aRemoteFile, nsresult aStatus);
+
 protected:
     nsCOMPtr<nsIZipReaderCache> mJARCache;
     nsCOMPtr<nsIMIMEService> mMimeService;
+
+    // Holds lists of RemoteOpenFileChild (not including the 1st) that have
+    // requested the same file from parent.
+    nsClassHashtable<nsHashableHashKey, RemoteFileListenerArray>
+        mRemoteFileListeners;
+
+    bool mIsMainProcess;
 };
 
 extern nsJARProtocolHandler *gJarHandler;
 
 #define NS_JARPROTOCOLHANDLER_CLASSNAME \
     "nsJarProtocolHandler"
 #define NS_JARPROTOCOLHANDLER_CID                    \
 { /* 0xc7e410d4-0x85f2-11d3-9f63-006008a6efe9 */     \
--- a/netwerk/ipc/Makefile.in
+++ b/netwerk/ipc/Makefile.in
@@ -41,13 +41,14 @@ CPPSRCS =               \
   ChannelEventQueue.cpp \
   RemoteOpenFileParent.cpp \
   RemoteOpenFileChild.cpp  \
   $(NULL)
 
 LOCAL_INCLUDES +=                  \
   -I$(srcdir)/../protocol/http \
   -I$(srcdir)/../base/src          \
+  -I$(topsrcdir)/modules/libjar    \
   $(NULL)
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
--- a/netwerk/ipc/RemoteOpenFileChild.cpp
+++ b/netwerk/ipc/RemoteOpenFileChild.cpp
@@ -1,44 +1,94 @@
 /* -*- 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/RemoteOpenFileChild.h"
+
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/ipc/FileDescriptorUtils.h"
+#include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/NeckoChild.h"
-#include "mozilla/net/RemoteOpenFileChild.h"
+#include "nsThreadUtils.h"
+#include "nsJARProtocolHandler.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,
+//-----------------------------------------------------------------------------
+// Helper class to dispatch events async on windows/OSX
+//-----------------------------------------------------------------------------
+
+class CallsListenerInNewEvent : public nsRunnable
+{
+public:
+    CallsListenerInNewEvent(nsIRemoteOpenFileListener *aListener, nsresult aRv)
+      : mListener(aListener), mRV(aRv)
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(aListener);
+    }
+
+    void Dispatch()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+
+        nsresult rv = NS_DispatchToCurrentThread(this);
+        NS_ENSURE_SUCCESS_VOID(rv);
+    }
+
+private:
+    NS_IMETHOD Run()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(mListener);
+
+        mListener->OnRemoteFileOpenComplete(mRV);
+        return NS_OK;
+    }
+
+    nsCOMPtr<nsIRemoteOpenFileListener> mListener;
+    nsresult mRV;
+};
+
+//-----------------------------------------------------------------------------
+// RemoteOpenFileChild
+//-----------------------------------------------------------------------------
+
+NS_IMPL_THREADSAFE_ISUPPORTS3(RemoteOpenFileChild,
                               nsIFile,
-                              nsIHashable)
-
+                              nsIHashable,
+                              nsICachedFileDescriptorListener)
 
 RemoteOpenFileChild::RemoteOpenFileChild(const RemoteOpenFileChild& other)
-  : mNSPRFileDesc(other.mNSPRFileDesc)
+  : mTabChild(other.mTabChild)
+  , 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 (mListener) {
+    NotifyListener(NS_ERROR_UNEXPECTED);
+  }
+
   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);
   }
 }
 
@@ -97,118 +147,181 @@ RemoteOpenFileChild::AsyncRemoteFileOpen
   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 (MissingRequiredTabChild(tabChild, "remoteopenfile")) {
+  mTabChild = static_cast<TabChild*>(aTabChild);
+
+  if (MissingRequiredTabChild(mTabChild, "remoteopenfile")) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
   // Windows/OSX desktop builds skip remoting, and just open file in child
   // process when asked for NSPR handle
-  aListener->OnRemoteFileOpenComplete(NS_OK);
+  nsRefPtr<CallsListenerInNewEvent> runnable =
+    new CallsListenerInNewEvent(aListener, NS_OK);
+  runnable->Dispatch();
+
   mAsyncOpenCalled = true;
   return NS_OK;
 #else
+  nsString path;
+  if (NS_FAILED(mFile->GetPath(path))) {
+    MOZ_NOT_REACHED("Couldn't get path from file!");
+  }
+
+  if (mTabChild) {
+    if (mTabChild->GetCachedFileDescriptor(path, this)) {
+      // The file descriptor was found in the cache and OnCachedFileDescriptor()
+      // will be called with it.
+      return NS_OK;
+    }
+  }
+
   URIParams uri;
   SerializeURI(mURI, uri);
 
-  gNeckoChild->SendPRemoteOpenFileConstructor(this, uri, tabChild);
+  gNeckoChild->SendPRemoteOpenFileConstructor(this, uri, mTabChild);
 
   // 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
 }
 
+void
+RemoteOpenFileChild::OnCachedFileDescriptor(const nsAString& aPath,
+                                            const FileDescriptor& aFD)
+{
+#ifdef DEBUG
+  if (!aPath.IsEmpty()) {
+    MOZ_ASSERT(mFile);
+
+    nsString path;
+    if (NS_FAILED(mFile->GetPath(path))) {
+      MOZ_NOT_REACHED("Couldn't get path from file!");
+    }
+
+    MOZ_ASSERT(path == aPath, "Paths don't match!");
+  }
+#endif
+
+  HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvFileOpened */ false);
+}
+
+void
+RemoteOpenFileChild::HandleFileDescriptorAndNotifyListener(
+                                                      const FileDescriptor& aFD,
+                                                      bool aFromRecvFileOpened)
+{
+#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
+  MOZ_NOT_REACHED("OS X and Windows shouldn't be doing IPDL here");
+#else
+  if (!mListener) {
+    // We already notified our listener (either in response to a cached file
+    // descriptor callback or through the normal messaging mechanism). Close the
+    // file descriptor if it is valid.
+    if (aFD.IsValid()) {
+      nsRefPtr<CloseFileRunnable> runnable = new CloseFileRunnable(aFD);
+      runnable->Dispatch();
+    }
+    return;
+  }
+
+  MOZ_ASSERT(!mNSPRFileDesc);
+
+  nsRefPtr<TabChild> tabChild;
+  mTabChild.swap(tabChild);
+
+  // If there is a pending callback and we're being called from IPDL then we
+  // need to cancel it.
+  if (tabChild && aFromRecvFileOpened) {
+    nsString path;
+    if (NS_FAILED(mFile->GetPath(path))) {
+      MOZ_NOT_REACHED("Couldn't get path from file!");
+    }
+
+    tabChild->CancelCachedFileDescriptorCallback(path, this);
+  }
+
+  if (aFD.IsValid()) {
+    mNSPRFileDesc = PR_ImportFile(aFD.PlatformHandle());
+    if (!mNSPRFileDesc) {
+      NS_WARNING("Failed to import file handle!");
+    }
+  }
+
+  NotifyListener(mNSPRFileDesc ? NS_OK : NS_ERROR_FILE_NOT_FOUND);
+#endif
+}
+
+void
+RemoteOpenFileChild::NotifyListener(nsresult aResult)
+{
+  MOZ_ASSERT(mListener);
+  mListener->OnRemoteFileOpenComplete(aResult);
+  mListener = nullptr;     // release ref to listener
+
+  nsRefPtr<nsJARProtocolHandler> handler(gJarHandler);
+  NS_WARN_IF_FALSE(handler, "nsJARProtocolHandler is already gone!");
+
+  if (handler) {
+    handler->RemoteOpenFileComplete(this, aResult);
+  }
+}
 
 //-----------------------------------------------------------------------------
 // RemoteOpenFileChild::PRemoteOpenFileChild
 //-----------------------------------------------------------------------------
 
 bool
 RemoteOpenFileChild::RecvFileOpened(const FileDescriptor& aFD)
 {
 #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
-  NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here");
+  NS_NOTREACHED("OS X and Windows shouldn't be doing IPDL here");
 #else
-  if (!aFD.IsValid()) {
-    return RecvFileDidNotOpen();
-  }
-
-  MOZ_ASSERT(!mNSPRFileDesc);
-  mNSPRFileDesc = PR_AllocFileDesc(aFD.PlatformHandle(), PR_GetFileMethods());
-
-  MOZ_ASSERT(mListener);
-  mListener->OnRemoteFileOpenComplete(NS_OK);
-  mListener = nullptr;     // release ref to listener
+  HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvFileOpened */ true);
 
   // 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;
 }
 
 bool
 RemoteOpenFileChild::RecvFileDidNotOpen()
 {
 #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
-  NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here");
+  NS_NOTREACHED("OS X and Windows shouldn't be doing IPDL here");
 #else
-  MOZ_ASSERT(!mNSPRFileDesc);
-  printf_stderr("RemoteOpenFileChild: file was not opened!\n");
-
-  MOZ_ASSERT(mListener);
-  mListener->OnRemoteFileOpenComplete(NS_ERROR_FILE_NOT_FOUND);
-  mListener = nullptr;     // release ref to listener
+  HandleFileDescriptorAndNotifyListener(FileDescriptor(),
+                                        /* aFromRecvFileOpened */ true);
 
   // 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);
@@ -657,9 +770,8 @@ RemoteOpenFileChild::GetHashCode(uint32_
   if (hashable) {
     return hashable->GetHashCode(aResult);
   }
   return NS_ERROR_UNEXPECTED;
 }
 
 } // namespace net
 } // namespace mozilla
-
--- a/netwerk/ipc/RemoteOpenFileChild.h
+++ b/netwerk/ipc/RemoteOpenFileChild.h
@@ -4,20 +4,26 @@
  * 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 "nsICachedFileDescriptorListener.h"
 #include "nsILocalFile.h"
 #include "nsIRemoteOpenFileListener.h"
 
 namespace mozilla {
+
+namespace ipc {
+class FileDescriptor;
+}
+
 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.
  *
@@ -33,55 +39,75 @@ namespace net {
  *
  * This class should only be instantiated in a child process.
  *
  */
 class RemoteOpenFileChild MOZ_FINAL
   : public PRemoteOpenFileChild
   , public nsIFile
   , public nsIHashable
+  , public nsICachedFileDescriptorListener
 {
+  typedef mozilla::dom::TabChild TabChild;
+  typedef mozilla::ipc::FileDescriptor FileDescriptor;
+
 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);
+
+  void ReleaseIPDLReference()
+  {
+    Release();
+  }
+
 private:
   RemoteOpenFileChild(const RemoteOpenFileChild& other);
 
 protected:
+  void AddIPDLReference()
+  {
+    AddRef();
+  }
+
   virtual bool RecvFileOpened(const FileDescriptor&);
   virtual bool RecvFileDidNotOpen();
 
+  virtual void OnCachedFileDescriptor(const nsAString& aPath,
+                                      const FileDescriptor& aFD) MOZ_OVERRIDE;
+
+  void HandleFileDescriptorAndNotifyListener(const FileDescriptor&,
+                                             bool aFromRecvFileOpened);
+
+  void NotifyListener(nsresult aResult);
+
   // regular nsIFile object, that we forward most calls to.
   nsCOMPtr<nsIFile> mFile;
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIRemoteOpenFileListener> mListener;
+  nsRefPtr<TabChild> mTabChild;
   PRFileDesc* mNSPRFileDesc;
   bool mAsyncOpenCalled;
   bool mNSPROpenCalled;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // _RemoteOpenFileChild_h
-