Bug 1043143: Step 3: Convert nsJARChannel from temporary files to temporary memory. r=honzab
authorJed Davis <jld@mozilla.com>
Tue, 10 Mar 2015 17:00:01 -0700
changeset 261758 3c316731c1705dcbbacf47856d8cb04080c0c2b5
parent 261757 66d2a14f0fb05be7d16df47631b7b52648ce2820
child 261759 a0f18594c0de7b4319218e2ddbe7366ab064d955
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs1043143
milestone39.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 1043143: Step 3: Convert nsJARChannel from temporary files to temporary memory. r=honzab This also requires changing XMLHttpRequest so that it doesn't crash if memory-mapping would be applicable but the channel has no associated nsIFile; a regression test for that case is added. Bonus fix: nsJARChannel::AsyncOpen now checks whether the inner channel's AsyncOpen failed.
dom/base/nsXMLHttpRequest.cpp
modules/libjar/nsIJARChannel.idl
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
modules/libjar/test/mochitest/mochitest.ini
modules/libjar/test/mochitest/test_bug1034143_mapped.html
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -2039,21 +2039,25 @@ nsXMLHttpRequest::OnStartRequest(nsIRequ
           } else if (scheme.LowerCaseEqualsLiteral("jar")) {
             nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri);
             if (jarURI) {
               jarURI->GetJAREntry(file);
             }
           }
           nsCOMPtr<nsIFile> jarFile;
           jarChannel->GetJarFile(getter_AddRefs(jarFile));
-          rv = mArrayBufferBuilder.mapToFileInPackage(file, jarFile);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
+          if (!jarFile) {
             mIsMappedArrayBuffer = false;
           } else {
-            channel->SetContentType(NS_LITERAL_CSTRING("application/mem-mapped"));
+            rv = mArrayBufferBuilder.mapToFileInPackage(file, jarFile);
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+              mIsMappedArrayBuffer = false;
+            } else {
+              channel->SetContentType(NS_LITERAL_CSTRING("application/mem-mapped"));
+            }
           }
         }
       }
     }
     // If memory mapping failed, mIsMappedArrayBuffer would be set to false,
     // and we want it fallback to the malloc way.
     if (!mIsMappedArrayBuffer) {
       int64_t contentLength;
--- a/modules/libjar/nsIJARChannel.idl
+++ b/modules/libjar/nsIJARChannel.idl
@@ -20,17 +20,17 @@ interface nsIJARChannel : nsIChannel
     [infallible] readonly attribute boolean isUnsafe;
 
     /**
      * Forces the uri to be a app:// uri.
      */
     void setAppURI(in nsIURI uri);
 
     /**
-     * Returns the JAR file.
+     * Returns the JAR file.  May be null if the jar is remote.
      */
     readonly attribute nsIFile jarFile;
 
     /**
      * Returns the zip entry if the file is synchronously accessible.
      * This will work even without opening the channel.
      */
     readonly attribute nsIZipEntry zipEntry;
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -229,17 +229,16 @@ nsJARChannel::~nsJARChannel()
 }
 
 NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel,
                             nsHashPropertyBag,
                             nsIRequest,
                             nsIChannel,
                             nsIStreamListener,
                             nsIRequestObserver,
-                            nsIDownloadObserver,
                             nsIRemoteOpenFileListener,
                             nsIThreadRetargetableRequest,
                             nsIThreadRetargetableStreamListener,
                             nsIJARChannel)
 
 nsresult 
 nsJARChannel::Init(nsIURI *uri)
 {
@@ -269,38 +268,48 @@ nsJARChannel::Init(nsIURI *uri)
 #endif
     return rv;
 }
 
 nsresult
 nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resultInput)
 {
     MOZ_ASSERT(resultInput);
+    MOZ_ASSERT(mJarFile || mTempMem);
 
     // important to pass a clone of the file since the nsIFile impl is not
     // necessarily MT-safe
     nsCOMPtr<nsIFile> clonedFile;
-    nsresult rv = mJarFile->Clone(getter_AddRefs(clonedFile));
-    if (NS_FAILED(rv))
-        return rv;
+    nsresult rv = NS_OK;
+    if (mJarFile) {
+        rv = mJarFile->Clone(getter_AddRefs(clonedFile));
+        if (NS_FAILED(rv))
+            return rv;
+    }
 
     nsCOMPtr<nsIZipReader> reader;
     if (jarCache) {
+        MOZ_ASSERT(mJarFile);
         if (mInnerJarEntry.IsEmpty())
             rv = jarCache->GetZip(clonedFile, getter_AddRefs(reader));
         else
             rv = jarCache->GetInnerZip(clonedFile, mInnerJarEntry,
                                        getter_AddRefs(reader));
     } else {
         // create an uncached jar reader
         nsCOMPtr<nsIZipReader> outerReader = do_CreateInstance(kZipReaderCID, &rv);
         if (NS_FAILED(rv))
             return rv;
 
-        rv = outerReader->Open(clonedFile);
+        if (mJarFile) {
+            rv = outerReader->Open(clonedFile);
+        } else {
+            rv = outerReader->OpenMemory(mTempMem->Elements(),
+                                         mTempMem->Length());
+        }
         if (NS_FAILED(rv))
             return rv;
 
         if (mInnerJarEntry.IsEmpty())
             reader = outerReader;
         else {
             reader = do_CreateInstance(kZipReaderCID, &rv);
             if (NS_FAILED(rv))
@@ -861,36 +870,34 @@ nsJARChannel::AsyncOpen(nsIStreamListene
     mListenerContext = ctx;
     mIsPending = true;
 
     nsCOMPtr<nsIChannel> channel;
 
     if (!mJarFile) {
         // Not a local file...
         // kick off an async download of the base URI...
-        rv = NS_NewDownloader(getter_AddRefs(mDownloader), this);
-        if (NS_SUCCEEDED(rv)) {
-            // Since we might not have a loadinfo on all channels yet
-            // we have to provide default arguments in case mLoadInfo is null;
-            uint32_t loadFlags =
-              mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS);
-            rv = NS_NewChannelInternal(getter_AddRefs(channel),
-                                       mJarBaseURI,
-                                       mLoadInfo,
-                                       mLoadGroup,
-                                       mCallbacks,
-                                       loadFlags);
-            if (NS_FAILED(rv)) {
-              mIsPending = false;
-              mListenerContext = nullptr;
-              mListener = nullptr;
-              return rv;
-            }
-            channel->AsyncOpen(mDownloader, nullptr);
+        nsCOMPtr<nsIStreamListener> downloader = new MemoryDownloader(this);
+        // Since we might not have a loadinfo on all channels yet
+        // we have to provide default arguments in case mLoadInfo is null;
+        uint32_t loadFlags =
+            mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS);
+        rv = NS_NewChannelInternal(getter_AddRefs(channel),
+                                   mJarBaseURI,
+                                   mLoadInfo,
+                                   mLoadGroup,
+                                   mCallbacks,
+                                   loadFlags);
+        if (NS_FAILED(rv)) {
+            mIsPending = false;
+            mListenerContext = nullptr;
+            mListener = nullptr;
+            return rv;
         }
+        rv = channel->AsyncOpen(downloader, nullptr);
     } else if (mOpeningRemote) {
         // nothing to do: already asked parent to open file.
     } else {
         rv = OpenLocalFile();
     }
 
     if (NS_FAILED(rv)) {
         mIsPending = false;
@@ -959,25 +966,25 @@ nsJARChannel::GetZipEntry(nsIZipEntry **
 NS_IMETHODIMP
 nsJARChannel::EnsureChildFd()
 {
     mEnsureChildFd = true;
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// nsIDownloadObserver
+// mozilla::net::MemoryDownloader::IObserver
 //-----------------------------------------------------------------------------
 
-NS_IMETHODIMP
-nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
+void
+nsJARChannel::OnDownloadComplete(MemoryDownloader* aDownloader,
                                  nsIRequest    *request,
                                  nsISupports   *context,
                                  nsresult       status,
-                                 nsIFile       *file)
+                                 MemoryDownloader::Data aData)
 {
     nsresult rv;
 
     nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
     if (channel) {
         uint32_t loadFlags;
         channel->GetLoadFlags(&loadFlags);
         if (loadFlags & LOAD_REPLACE) {
@@ -1045,34 +1052,32 @@ nsJARChannel::OnDownloadComplete(nsIDown
         // Refuse to unpack view-source: jars even if open-unsafe-types is set.
         nsCOMPtr<nsIViewSourceChannel> viewSource = do_QueryInterface(channel);
         if (viewSource) {
             status = NS_ERROR_UNSAFE_CONTENT_TYPE;
         }
     }
 
     if (NS_SUCCEEDED(status)) {
-        mJarFile = file;
+        mTempMem = Move(aData);
 
         nsRefPtr<nsJARInputThunk> input;
         rv = CreateJarInput(nullptr, getter_AddRefs(input));
         if (NS_SUCCEEDED(rv)) {
             // create input stream pump
             rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
             if (NS_SUCCEEDED(rv))
                 rv = mPump->AsyncRead(this, nullptr);
         }
         status = rv;
     }
 
     if (NS_FAILED(status)) {
         NotifyError(status);
     }
-
-    return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsIRemoteOpenFileListener
 //-----------------------------------------------------------------------------
 nsresult
 nsJARChannel::OnRemoteFileOpenComplete(nsresult aOpenStatus)
 {
@@ -1147,17 +1152,16 @@ nsJARChannel::OnStopRequest(nsIRequest *
         mListenerContext = 0;
     }
 
     if (mLoadGroup)
         mLoadGroup->RemoveRequest(this, nullptr, status);
 
     mPump = 0;
     mIsPending = false;
-    mDownloader = 0; // this may delete the underlying jar file
 
     // Drop notification callbacks to prevent cycles.
     mCallbacks = 0;
     mProgressSink = 0;
 
     #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
     #else
     if (mEnsureChildFd) {
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -1,54 +1,53 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 nsJARChannel_h__
 #define nsJARChannel_h__
 
+#include "mozilla/net/MemoryDownloader.h"
 #include "nsIJARChannel.h"
 #include "nsIJARURI.h"
 #include "nsIInputStreamPump.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIProgressEventSink.h"
 #include "nsIStreamListener.h"
 #include "nsIRemoteOpenFileListener.h"
 #include "nsIZipReader.h"
-#include "nsIDownloader.h"
 #include "nsILoadGroup.h"
 #include "nsILoadInfo.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsIThreadRetargetableStreamListener.h"
 #include "nsHashPropertyBag.h"
 #include "nsIFile.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "prlog.h"
 
 class nsJARInputThunk;
 
 //-----------------------------------------------------------------------------
 
 class nsJARChannel MOZ_FINAL : public nsIJARChannel
-                             , public nsIDownloadObserver
+                             , public mozilla::net::MemoryDownloader::IObserver
                              , public nsIStreamListener
                              , public nsIRemoteOpenFileListener
                              , public nsIThreadRetargetableRequest
-                             , public           nsIThreadRetargetableStreamListener
+                             , public nsIThreadRetargetableStreamListener
                              , public nsHashPropertyBag
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIREQUEST
     NS_DECL_NSICHANNEL
     NS_DECL_NSIJARCHANNEL
-    NS_DECL_NSIDOWNLOADOBSERVER
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIREMOTEOPENFILELISTENER
     NS_DECL_NSITHREADRETARGETABLEREQUEST
     NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
 
     nsJARChannel();
 
@@ -58,16 +57,22 @@ private:
     virtual ~nsJARChannel();
 
     nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
     nsresult LookupFile(bool aAllowAsync);
     nsresult OpenLocalFile();
     void NotifyError(nsresult aError);
     void FireOnProgress(uint64_t aProgress);
     nsresult SetRemoteNSPRFileDesc(PRFileDesc *fd);
+    virtual void OnDownloadComplete(mozilla::net::MemoryDownloader* aDownloader,
+                                    nsIRequest* aRequest,
+                                    nsISupports* aCtxt,
+                                    nsresult aStatus,
+                                    mozilla::net::MemoryDownloader::Data aData)
+        MOZ_OVERRIDE;
 
 #if defined(PR_LOGGING)
     nsCString                       mSpec;
 #endif
 
     bool                            mOpened;
 
     nsCOMPtr<nsIJARURI>             mJarURI;
@@ -90,17 +95,17 @@ private:
     int64_t                         mContentLength;
     uint32_t                        mLoadFlags;
     nsresult                        mStatus;
     bool                            mIsPending;
     bool                            mIsUnsafe;
     bool                            mOpeningRemote;
     bool                            mEnsureChildFd;
 
-    nsCOMPtr<nsIStreamListener>     mDownloader;
+    mozilla::net::MemoryDownloader::Data mTempMem;
     nsCOMPtr<nsIInputStreamPump>    mPump;
     // 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;
--- a/modules/libjar/test/mochitest/mochitest.ini
+++ b/modules/libjar/test/mochitest/mochitest.ini
@@ -1,8 +1,10 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 support-files =
   bug403331.zip
   bug403331.zip^headers^
   openredirect.sjs
 
 [test_bug403331.html]
+[test_bug1034143_mapped.html]
+run-if = os == 'linux'
new file mode 100644
--- /dev/null
+++ b/modules/libjar/test/mochitest/test_bug1034143_mapped.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1034143
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 945152</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1034143">Mozilla Bug 1034143</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+// Ensure that XMLHttpRequest's memory-mapping code can handle a case
+// where the nsIJARChannel's jarFile property is null, but which is
+// otherwise eligible for bug 945152's memory-mapping optimization.
+
+function runTest() {
+  const jarURL = "jar:http://example.org/tests/dom/base/test/file_bug945152.jar!/data_1.txt";
+  let xhr = new XMLHttpRequest({ mozAnon: true, mozSystem: true });
+  xhr.open("GET", jarURL);
+  xhr.onerror = function onerror(e) {
+    ok(false, "JAR XHR failed: " + e.status);
+    SimpleTest.finish();
+  };
+  xhr.onload = function onload(e) {
+    ok(xhr.status == 200, "Status is 200");
+    let ct = xhr.getResponseHeader("Content-Type");
+    ok(ct.indexOf("mem-mapped") == -1, "Data is not memory-mapped");
+    SimpleTest.finish();
+  };
+  xhr.responseType = 'arraybuffer';
+  xhr.send();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+  SpecialPowers.pushPrefEnv({"set": [["dom.mapped_arraybuffer.enabled", true]]}, function() {
+    SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest);
+  });
+});
+</script>
+</pre>
+</body>
+</html>