Bug 1373708 - make local jar I/O off-main-thread; r?aklotz draft
authorLiang-Heng Chen <xeonchen@gmail.com>
Thu, 22 Jun 2017 16:37:27 +0800
changeset 599445 eda19c526b44cde918158b21cbb8c286126b3c9c
parent 598703 dc33e00dad90346466fefaa158bc0d79a53668a9
child 634778 42de2334667b394dbfd996061c1ef51b221d9f57
push id65527
push userbmo:xeonchen@mozilla.com
push dateFri, 23 Jun 2017 05:48:09 +0000
reviewersaklotz
bugs1373708
milestone56.0a1
Bug 1373708 - make local jar I/O off-main-thread; r?aklotz MozReview-Commit-ID: J87ygHglR2
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -21,16 +21,17 @@
 #include "nsIPrincipal.h"
 #include "nsIFileURL.h"
 
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Preferences.h"
 #include "nsITabChild.h"
 #include "private/pprio.h"
 #include "nsInputStreamPump.h"
+#include "nsThreadUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 // the entry for a directory will either be empty (in the case of the
 // top-level directory) or will end with a slash
@@ -225,16 +226,22 @@ NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel
                             nsIThreadRetargetableRequest,
                             nsIThreadRetargetableStreamListener,
                             nsIJARChannel)
 
 nsresult
 nsJARChannel::Init(nsIURI *uri)
 {
     nsresult rv;
+
+    mWorker = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
     mJarURI = do_QueryInterface(uri, &rv);
     if (NS_FAILED(rv))
         return rv;
 
     mOriginalURI = mJarURI;
 
     // Prevent loading jar:javascript URIs (see bug 290982).
     nsCOMPtr<nsIURI> innerURI;
@@ -368,36 +375,174 @@ nsJARChannel::LookupFile(bool aAllowAsyn
             }
         }
     }
 
     return rv;
 }
 
 nsresult
+CreateLocalJarInput(nsIZipReaderCache* aJarCache,
+                    nsIFile* aFile,
+                    const nsACString& aInnerJarEntry,
+                    nsIJARURI* aJarURI,
+                    const nsACString& aJarEntry,
+                    nsJARInputThunk** aResultInput)
+{
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(aJarCache);
+    MOZ_ASSERT(aResultInput);
+
+    nsresult rv;
+
+    nsCOMPtr<nsIZipReader> reader;
+    if (aInnerJarEntry.IsEmpty()) {
+        rv = aJarCache->GetZip(aFile, getter_AddRefs(reader));
+    } else {
+        rv = aJarCache->GetInnerZip(aFile,
+                                    aInnerJarEntry,
+                                    getter_AddRefs(reader));
+    }
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+
+    RefPtr<nsJARInputThunk> input = new nsJARInputThunk(reader,
+                                                        aJarURI,
+                                                        aJarEntry,
+                                                        aJarCache != nullptr);
+    rv = input->Init();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+
+    input.forget(aResultInput);
+    return NS_OK;
+}
+
+nsresult
 nsJARChannel::OpenLocalFile()
 {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    MOZ_ASSERT(mWorker);
     MOZ_ASSERT(mIsPending);
+    MOZ_ASSERT(mJarFile);
 
     // Local files are always considered safe.
     mIsUnsafe = false;
 
-    RefPtr<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);
+    nsresult rv;
+
+    nsCOMPtr<nsIZipReaderCache> jarCache = gJarHandler->JarCache();
+    if (NS_WARN_IF(!jarCache)) {
+        return NS_ERROR_UNEXPECTED;
+    }
+
+    nsCOMPtr<nsIFile> clonedFile;
+    rv = mJarFile->Clone(getter_AddRefs(clonedFile));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+
+    // clone mJarURI
+    nsCOMPtr<nsIURI> clonedURI;
+    rv = mJarURI->Clone(getter_AddRefs(clonedURI));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+    nsCOMPtr<nsIJARURI> clonedJarURI = do_QueryInterface(clonedURI, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
     }
 
+    nsAutoCString jarEntry(mJarEntry);
+    nsAutoCString innerJarEntry(mInnerJarEntry);
+
+    RefPtr<nsJARChannel> self = this;
+    return mWorker->Dispatch(
+            NS_NewRunnableFunction([self,
+                                    jarCache,
+                                    clonedFile,
+                                    clonedJarURI,
+                                    jarEntry,
+                                    innerJarEntry] () {
+
+        RefPtr<nsJARInputThunk> input;
+        nsresult rv = CreateLocalJarInput(jarCache,
+                                          clonedFile,
+                                          innerJarEntry,
+                                          clonedJarURI,
+                                          jarEntry,
+                                          getter_AddRefs(input));
+
+        NS_ReleaseOnMainThread("nsJARChannel::clonedJarURI",
+                               nsCOMPtr<nsIJARURI>(clonedJarURI).forget());
+
+        nsCOMPtr<nsIRunnable> target;
+        if (NS_SUCCEEDED(rv)) {
+            target = NewRunnableMethod<RefPtr<nsJARInputThunk>>(
+                self, &nsJARChannel::ContinueOpenLocalFile, input);
+        } else {
+            target = NewRunnableMethod<nsresult>(
+                self, &nsJARChannel::OnOpenLocalFileComplete, rv);
+        }
+        NS_DispatchToMainThread(target);
+    }));
+}
+
+nsresult
+nsJARChannel::ContinueOpenLocalFile(nsJARInputThunk* aInput)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mIsPending);
+
+    // Make GetContentLength meaningful
+    mContentLength = aInput->GetContentLength();
+
+    nsresult rv;
+    // Create input stream pump and call AsyncRead as a block.
+    rv = NS_NewInputStreamPump(getter_AddRefs(mPump), aInput);
+    if (NS_SUCCEEDED(rv)) {
+        rv = mPump->AsyncRead(this, nullptr);
+        return OnOpenLocalFileComplete(rv);
+    }
     return rv;
 }
 
+nsresult
+nsJARChannel::OnOpenLocalFileComplete(nsresult aResult)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsresult rv = aResult;
+
+    if (NS_FAILED(rv)) {
+        if (mIsPending) {
+            NotifyError(rv);
+        }
+
+        mIsPending = false;
+        mListenerContext = nullptr;
+        mListener = nullptr;
+        mCallbacks = nullptr;
+        mProgressSink = nullptr;
+
+        return rv;
+    }
+
+    if (mLoadGroup) {
+        mLoadGroup->AddRequest(this, nullptr);
+    }
+
+    mOpened = true;
+
+    return NS_OK;
+}
+
 void
 nsJARChannel::NotifyError(nsresult aError)
 {
     MOZ_ASSERT(NS_FAILED(aError));
 
     mStatus = aError;
 
     OnStartRequest(nullptr, nullptr);
@@ -823,19 +968,23 @@ nsJARChannel::AsyncOpen(nsIStreamListene
             return rv;
         }
         if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
             rv = channel->AsyncOpen2(downloader);
         }
         else {
             rv = channel->AsyncOpen(downloader, nullptr);
         }
+
     }
     else {
         rv = OpenLocalFile();
+        if (NS_SUCCEEDED(rv)) {
+            return NS_OK;
+        }
     }
 
     if (NS_FAILED(rv)) {
         mIsPending = false;
         mListenerContext = nullptr;
         mListener = nullptr;
         mCallbacks = nullptr;
         mProgressSink = nullptr;
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsJARChannel_h__
 #define nsJARChannel_h__
 
 #include "mozilla/net/MemoryDownloader.h"
 #include "nsIJARChannel.h"
 #include "nsIJARURI.h"
+#include "nsIEventTarget.h"
 #include "nsIInputStreamPump.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIProgressEventSink.h"
 #include "nsIStreamListener.h"
 #include "nsIZipReader.h"
 #include "nsILoadGroup.h"
 #include "nsILoadInfo.h"
 #include "nsIThreadRetargetableRequest.h"
@@ -52,16 +53,18 @@ public:
     nsresult Init(nsIURI *uri);
 
 private:
     virtual ~nsJARChannel();
 
     nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
     nsresult LookupFile(bool aAllowAsync);
     nsresult OpenLocalFile();
+    nsresult ContinueOpenLocalFile(nsJARInputThunk* aInput);
+    nsresult OnOpenLocalFileComplete(nsresult aResult);
     void NotifyError(nsresult aError);
     void FireOnProgress(uint64_t aProgress);
     virtual void OnDownloadComplete(mozilla::net::MemoryDownloader* aDownloader,
                                     nsIRequest* aRequest,
                                     nsISupports* aCtxt,
                                     nsresult aStatus,
                                     mozilla::net::MemoryDownloader::Data aData)
         override;
@@ -97,13 +100,15 @@ private:
     // mRequest is only non-null during OnStartRequest, so we'll have a pointer
     // to the request if we get called back via RetargetDeliveryTo.
     nsCOMPtr<nsIRequest>            mRequest;
     nsCOMPtr<nsIFile>               mJarFile;
     nsCOMPtr<nsIURI>                mJarBaseURI;
     nsCString                       mJarEntry;
     nsCString                       mInnerJarEntry;
 
+    nsCOMPtr<nsIEventTarget>        mWorker;
+
     // True if this channel should not download any remote files.
     bool                            mBlockRemoteFiles;
 };
 
 #endif // nsJARChannel_h__