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 131318 1646e649878adb0b53954dd5dec31671dbe62cf4
parent 131317 274946fd76c49dc3e2d909f334d2d7acfc51f10a
child 131319 9e3d9776842f4446a962500bf666142b28e2aaa9
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs835698
milestone21.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 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
-