Bug 1333899 - Part 3: Avoid extra copies in nsDataChannel::OpenContentStream. r=bz
authorEric Rahm <erahm@mozilla.com>
Fri, 02 Feb 2018 12:59:38 -0800
changeset 402435 b68e9ed93b9feaa9e59f18957453d91c13b3ffb7
parent 402434 fc9b9ea20e7c40074a41c42a2242d6c34629c183
child 402436 1ed7050a69b4b881393a8e794338e4965875fc65
push id99569
push usererahm@mozilla.com
push dateMon, 05 Feb 2018 23:33:18 +0000
treeherdermozilla-inbound@b68e9ed93b9f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1333899
milestone60.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 1333899 - Part 3: Avoid extra copies in nsDataChannel::OpenContentStream. r=bz This switches over to cloning the URI without it's ref which will most likely avoid a copy. The new |ParsePathWithoutRef| is used, again to avoid needing a copy of the path substring.
netwerk/protocol/data/nsDataChannel.cpp
--- a/netwerk/protocol/data/nsDataChannel.cpp
+++ b/netwerk/protocol/data/nsDataChannel.cpp
@@ -12,77 +12,107 @@
 #include "nsDataHandler.h"
 #include "nsIPipe.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsEscape.h"
 
 using namespace mozilla;
 
+/**
+ * Helper for performing a fallible unescape.
+ *
+ * @param aStr The string to unescape.
+ * @param aBuffer Buffer to unescape into if necessary.
+ * @param rv Out: nsresult indicating success or failure of unescaping.
+ * @return Reference to the string containing the unescaped data.
+ */
+const nsACString& Unescape(const nsACString& aStr, nsACString& aBuffer,
+                           nsresult* rv)
+{
+    MOZ_ASSERT(rv);
+
+    bool appended = false;
+    *rv = NS_UnescapeURL(aStr.Data(), aStr.Length(), /* flags = */ 0,
+                         aBuffer, appended, mozilla::fallible);
+    if (NS_FAILED(*rv) || !appended) {
+        return aStr;
+    }
+
+    return aBuffer;
+}
+
 nsresult
 nsDataChannel::OpenContentStream(bool async, nsIInputStream **result,
                                  nsIChannel** channel)
 {
     NS_ENSURE_TRUE(URI(), NS_ERROR_NOT_INITIALIZED);
 
     nsresult rv;
 
-    nsAutoCString spec;
-    rv = URI()->GetAsciiSpec(spec);
-    if (NS_FAILED(rv)) return rv;
+    // In order to avoid potentially building up a new path including the
+    // ref portion of the URI, which we don't care about, we clone a version
+    // of the URI that does not have a ref and in most cases should share
+    // string buffers with the original URI.
+    nsCOMPtr<nsIURI> uri;
+    rv = URI()->CloneIgnoringRef(getter_AddRefs(uri));
+    if (NS_FAILED(rv))
+        return rv;
 
-    nsCString contentType, contentCharset, dataBuffer;
-    bool lBase64;
-    rv = nsDataHandler::ParseURI(spec, contentType, &contentCharset,
-                                 lBase64, &dataBuffer);
+    nsAutoCString path;
+    rv = uri->GetPathQueryRef(path);
     if (NS_FAILED(rv))
         return rv;
 
-    NS_UnescapeURL(dataBuffer);
+    nsCString contentType, contentCharset;
+    nsDependentCSubstring dataRange;
+    bool lBase64;
+    rv = nsDataHandler::ParsePathWithoutRef(path, contentType, &contentCharset,
+                                            lBase64, &dataRange);
+    if (NS_FAILED(rv))
+        return rv;
 
-    if (lBase64) {
+    // This will avoid a copy if nothing needs to be unescaped.
+    nsAutoCString unescapedBuffer;
+    const nsACString& data = Unescape(dataRange, unescapedBuffer, &rv);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
+    if (lBase64 && &data == &unescapedBuffer) {
         // Don't allow spaces in base64-encoded content. This is only
         // relevant for escaped spaces; other spaces are stripped in
-        // NewURI.
-        dataBuffer.StripWhitespace();
+        // NewURI. We know there were no escaped spaces if the data buffer
+        // wasn't used in |Unescape|.
+        unescapedBuffer.StripWhitespace();
     }
 
     nsCOMPtr<nsIInputStream> bufInStream;
     nsCOMPtr<nsIOutputStream> bufOutStream;
 
     // create an unbounded pipe.
     rv = NS_NewPipe(getter_AddRefs(bufInStream),
                     getter_AddRefs(bufOutStream),
                     nsIOService::gDefaultSegmentSize,
                     UINT32_MAX,
                     async, true);
     if (NS_FAILED(rv))
         return rv;
 
     uint32_t contentLen;
     if (lBase64) {
-        const uint32_t dataLen = dataBuffer.Length();
-        int32_t resultLen = 0;
-        if (dataLen >= 1 && dataBuffer[dataLen-1] == '=') {
-            if (dataLen >= 2 && dataBuffer[dataLen-2] == '=')
-                resultLen = dataLen-2;
-            else
-                resultLen = dataLen-1;
-        } else {
-            resultLen = dataLen;
-        }
-        resultLen = ((resultLen * 3) / 4);
+        nsAutoCString decodedData;
+        rv = Base64Decode(data, decodedData);
+        NS_ENSURE_SUCCESS(rv, rv);
 
-        nsAutoCString decodedData;
-        rv = Base64Decode(dataBuffer, decodedData);
-        NS_ENSURE_SUCCESS(rv, rv);
-        rv = bufOutStream->Write(decodedData.get(), resultLen, &contentLen);
+        rv = bufOutStream->Write(decodedData.get(), decodedData.Length(), &contentLen);
     } else {
-        rv = bufOutStream->Write(dataBuffer.get(), dataBuffer.Length(), &contentLen);
+        rv = bufOutStream->Write(data.Data(), data.Length(), &contentLen);
     }
+
     if (NS_FAILED(rv))
         return rv;
 
     SetContentType(contentType);
     SetContentCharset(contentCharset);
     mContentLength = contentLen;
 
     bufInStream.forget(result);