Bug 1397635 - Support for non-seekable stream in HTTP connection, r=bagder
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 08 Sep 2017 16:06:26 +0200
changeset 429258 c08a59cb75a34422a3fdeb287cbfa954df117be8
parent 429257 85b18b5a5e439fbf31eeb40f94b22a278836ef82
child 429259 6933c6396bda51a32059f7bbd24349ff9cda2eaf
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbagder
bugs1397635
milestone57.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 1397635 - Support for non-seekable stream in HTTP connection, r=bagder
netwerk/base/PartiallySeekableInputStream.cpp
netwerk/base/PartiallySeekableInputStream.h
netwerk/base/RequestContextService.cpp
netwerk/base/moz.build
netwerk/protocol/http/Http2Session.h
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/test/gtest/TestPartiallySeekableInputStream.cpp
netwerk/test/gtest/moz.build
new file mode 100644
--- /dev/null
+++ b/netwerk/base/PartiallySeekableInputStream.cpp
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "PartiallySeekableInputStream.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsISeekableStream.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(PartiallySeekableInputStream);
+NS_IMPL_RELEASE(PartiallySeekableInputStream);
+
+NS_INTERFACE_MAP_BEGIN(PartiallySeekableInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
+                                     mWeakCloneableInputStream)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
+                                     mWeakIPCSerializableInputStream)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream,
+                                     mWeakAsyncInputStream)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
+                                     mWeakAsyncInputStream)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+PartiallySeekableInputStream::PartiallySeekableInputStream(nsIInputStream* aInputStream,
+                                                           uint64_t aBufferSize)
+  : mInputStream(aInputStream)
+  , mWeakCloneableInputStream(nullptr)
+  , mWeakIPCSerializableInputStream(nullptr)
+  , mWeakAsyncInputStream(nullptr)
+  , mBufferSize(aBufferSize)
+  , mPos(0)
+  , mClosed(false)
+{
+  MOZ_ASSERT(aInputStream);
+
+#ifdef DEBUG
+  nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(aInputStream);
+  MOZ_ASSERT(!seekableStream);
+#endif
+
+  nsCOMPtr<nsICloneableInputStream> cloneableStream =
+    do_QueryInterface(aInputStream);
+  if (cloneableStream && SameCOMIdentity(aInputStream, cloneableStream)) {
+    mWeakCloneableInputStream = cloneableStream;
+  }
+
+  nsCOMPtr<nsIIPCSerializableInputStream> serializableStream =
+    do_QueryInterface(aInputStream);
+  if (serializableStream &&
+      SameCOMIdentity(aInputStream, serializableStream)) {
+    mWeakIPCSerializableInputStream = serializableStream;
+  }
+
+  nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+    do_QueryInterface(aInputStream);
+  if (asyncInputStream && SameCOMIdentity(aInputStream, asyncInputStream)) {
+    mWeakAsyncInputStream = asyncInputStream;
+  }
+}
+
+PartiallySeekableInputStream::~PartiallySeekableInputStream()
+{}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Close()
+{
+  mInputStream->Close();
+  mCachedBuffer.Clear();
+  mPos = 0;
+  mClosed = true;
+  return NS_OK;
+}
+
+// nsIInputStream interface
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Available(uint64_t* aLength)
+{
+  if (mClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  nsresult rv = mInputStream->Available(aLength);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mPos < mCachedBuffer.Length()) {
+    *aLength += mCachedBuffer.Length() - mPos;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Read(char* aBuffer, uint32_t aCount,
+                                   uint32_t* aReadCount)
+{
+  *aReadCount = 0;
+
+  if (mClosed) {
+    return NS_OK;
+  }
+
+  uint32_t byteRead = 0;
+
+  if (mPos < mCachedBuffer.Length()) {
+    // We are reading from the cached buffer.
+    byteRead = XPCOM_MIN(mCachedBuffer.Length() - mPos, (uint64_t)aCount);
+    memcpy(aBuffer, mCachedBuffer.Elements() + mPos, byteRead);
+    *aReadCount = byteRead;
+    mPos += byteRead;
+  }
+
+  if (byteRead < aCount) {
+    MOZ_ASSERT(mPos >= mCachedBuffer.Length());
+    MOZ_ASSERT_IF(mPos > mCachedBuffer.Length(),
+                  mCachedBuffer.Length() == mBufferSize);
+
+    // We can read from the stream.
+    uint32_t byteWritten;
+    nsresult rv = mInputStream->Read(aBuffer + byteRead, aCount - byteRead,
+                                     &byteWritten);
+    if (NS_WARN_IF(NS_FAILED(rv)) || byteWritten == 0) {
+      return rv;
+    }
+
+    *aReadCount += byteWritten;
+
+    // Maybe we have to cache something.
+    if (mPos < mBufferSize) {
+      uint32_t size = XPCOM_MIN(mPos + byteWritten, mBufferSize);
+      mCachedBuffer.SetLength(size);
+      memcpy(mCachedBuffer.Elements() + mPos, aBuffer + byteRead, size - mPos);
+    }
+
+    mPos += byteWritten;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+                                           uint32_t aCount, uint32_t *aResult)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+  return mInputStream->IsNonBlocking(aNonBlocking);
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::GetCloneable(bool* aCloneable)
+{
+  NS_ENSURE_STATE(mWeakCloneableInputStream);
+
+  *aCloneable = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Clone(nsIInputStream** aResult)
+{
+  NS_ENSURE_STATE(mWeakCloneableInputStream);
+
+  nsCOMPtr<nsIInputStream> clonedStream;
+  nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIInputStream> stream =
+    new PartiallySeekableInputStream(clonedStream, mBufferSize);
+
+  stream.forget(aResult);
+  return NS_OK;
+}
+
+// nsIAsyncInputStream interface
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::CloseWithStatus(nsresult aStatus)
+{
+  NS_ENSURE_STATE(mWeakAsyncInputStream);
+
+  return mWeakAsyncInputStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+                                        uint32_t aFlags,
+                                        uint32_t aRequestedCount,
+                                        nsIEventTarget* aEventTarget)
+{
+  if (mClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  NS_ENSURE_STATE(mWeakAsyncInputStream);
+
+  if (mAsyncWaitCallback && aCallback) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mAsyncWaitCallback = aCallback;
+
+  if (!mAsyncWaitCallback) {
+    return NS_OK;
+  }
+
+  return mWeakAsyncInputStream->AsyncWait(this, aFlags, aRequestedCount,
+                                          aEventTarget);
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+  MOZ_ASSERT(mWeakAsyncInputStream);
+  MOZ_ASSERT(mWeakAsyncInputStream == aStream);
+
+  // We have been canceled in the meanwhile.
+  if (!mAsyncWaitCallback) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIInputStreamCallback> callback = mAsyncWaitCallback;
+
+  mAsyncWaitCallback = nullptr;
+
+  return callback->OnInputStreamReady(this);
+}
+
+// nsIIPCSerializableInputStream
+
+void
+PartiallySeekableInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams,
+                                        FileDescriptorArray& aFileDescriptors)
+{
+  MOZ_ASSERT(mWeakIPCSerializableInputStream);
+  mozilla::ipc::InputStreamHelper::SerializeInputStream(mInputStream, aParams,
+                                                        aFileDescriptors);
+}
+
+bool
+PartiallySeekableInputStream::Deserialize(const mozilla::ipc::InputStreamParams& aParams,
+                                          const FileDescriptorArray& aFileDescriptors)
+{
+  MOZ_CRASH("This method should never be called!");
+  return false;
+}
+
+mozilla::Maybe<uint64_t>
+PartiallySeekableInputStream::ExpectedSerializedLength()
+{
+  if (!mWeakIPCSerializableInputStream) {
+    return mozilla::Nothing();
+  }
+
+  return mWeakIPCSerializableInputStream->ExpectedSerializedLength();
+}
+
+// nsISeekableStream
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+  if (mClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  int64_t offset;
+
+  switch (aWhence) {
+    case NS_SEEK_SET:
+      offset = aOffset;
+      break;
+    case NS_SEEK_CUR:
+      offset = mPos + aOffset;
+      break;
+    case NS_SEEK_END: {
+      return NS_ERROR_NOT_IMPLEMENTED;
+    }
+    default:
+      return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  if (offset < 0) {
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  if ((uint64_t)offset >= mCachedBuffer.Length() || mPos > mBufferSize) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  mPos = offset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Tell(int64_t *aResult)
+{
+  if (mClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  *aResult = mPos;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::SetEOF()
+{
+  return Close();
+}
+
+} // net namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/netwerk/base/PartiallySeekableInputStream.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 PartiallySeekableInputStream_h
+#define PartiallySeekableInputStream_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsICloneableInputStream.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsISeekableStream.h"
+
+namespace mozilla {
+namespace net {
+
+// A wrapper for making a stream seekable for the first |aBufferSize| bytes.
+
+class PartiallySeekableInputStream final : public nsISeekableStream
+                                         , public nsIAsyncInputStream
+                                         , public nsICloneableInputStream
+                                         , public nsIIPCSerializableInputStream
+                                         , public nsIInputStreamCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIINPUTSTREAM
+  NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSIASYNCINPUTSTREAM
+  NS_DECL_NSICLONEABLEINPUTSTREAM
+  NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+  NS_DECL_NSIINPUTSTREAMCALLBACK
+
+  explicit PartiallySeekableInputStream(nsIInputStream* aInputStream,
+                                        uint64_t aBufferSize = 4096);
+
+private:
+  ~PartiallySeekableInputStream();
+
+  nsCOMPtr<nsIInputStream> mInputStream;
+
+  // Raw pointers because these are just QI of mInputStream.
+  nsICloneableInputStream* mWeakCloneableInputStream;
+  nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream;
+  nsIAsyncInputStream* mWeakAsyncInputStream;
+
+  nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+  nsTArray<char> mCachedBuffer;
+
+  uint64_t mBufferSize;
+  uint64_t mPos;
+  bool mClosed;
+};
+
+} // net namespace
+} // mozilla namespace
+
+#endif // PartiallySeekableInputStream_h
--- a/netwerk/base/RequestContextService.cpp
+++ b/netwerk/base/RequestContextService.cpp
@@ -1,26 +1,31 @@
 /* -*- 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 "nsAutoPtr.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDocumentLoader.h"
 #include "nsIObserverService.h"
 #include "nsIXULRuntime.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "RequestContextService.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Services.h"
 #include "mozilla/TimeStamp.h"
 
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoCommon.h"
 #include "mozilla/net/PSpdyPush.h"
 
 #include "../protocol/http/nsHttpHandler.h"
 
 namespace mozilla {
 namespace net {
 
 LazyLogModule gRequestContextLog("RequestContext");
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -170,16 +170,17 @@ EXPORTS.mozilla += [
 
 EXPORTS.mozilla.net += [
     'CaptivePortalService.h',
     'ChannelDiverterChild.h',
     'ChannelDiverterParent.h',
     'Dashboard.h',
     'DashboardTypes.h',
     'MemoryDownloader.h',
+    'PartiallySeekableInputStream.h',
     'Predictor.h',
     'ReferrerPolicy.h',
     'SimpleChannelParent.h',
     'TCPFastOpen.h',
 ]
 
 UNIFIED_SOURCES += [
     'ArrayBufferInputStream.cpp',
@@ -236,16 +237,17 @@ UNIFIED_SOURCES += [
     'nsStreamTransportService.cpp',
     'nsSyncStreamListener.cpp',
     'nsTemporaryFileInputStream.cpp',
     'nsTransportUtils.cpp',
     'nsUDPSocket.cpp',
     'nsUnicharStreamLoader.cpp',
     'nsURLHelper.cpp',
     'nsURLParsers.cpp',
+    'PartiallySeekableInputStream.cpp',
     'PollableEvent.cpp',
     'Predictor.cpp',
     'ProxyAutoConfig.cpp',
     'RedirectChannelRegistrar.cpp',
     'RequestContextService.cpp',
     'SimpleBuffer.cpp',
     'SimpleChannel.cpp',
     'SimpleChannelParent.cpp',
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -7,16 +7,17 @@
 #define mozilla_net_Http2Session_h
 
 // HTTP/2 - RFC 7540
 // https://www.rfc-editor.org/rfc/rfc7540.txt
 
 #include "ASpdySession.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
 #include "nsAHttpConnection.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsDeque.h"
 #include "nsHashKeys.h"
 #include "nsHttpRequestHead.h"
 #include "nsICacheEntryOpenCallback.h"
 
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -52,16 +52,17 @@
 #include "NullPrincipal.h"
 #include "nsISSLSocketControl.h"
 #include "mozilla/Telemetry.h"
 #include "nsIURL.h"
 #include "nsIConsoleService.h"
 #include "mozilla/BinarySearch.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Move.h"
+#include "mozilla/net/PartiallySeekableInputStream.h"
 #include "nsIHttpHeaderVisitor.h"
 #include "nsIMIMEInputStream.h"
 #include "nsIXULRuntime.h"
 #include "nsICacheInfoChannel.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsHttpChannel.h"
 #include "nsRedirectHistoryEntry.h"
 
@@ -207,16 +208,18 @@ HttpBaseChannel::HttpBaseChannel()
   , mContentWindowId(0)
   , mTopLevelOuterContentWindowId(0)
   , mRequireCORSPreflight(false)
   , mReportCollector(new ConsoleReportCollector())
   , mAltDataLength(0)
   , mForceMainDocumentChannel(false)
   , mIsTrackingResource(false)
   , mLastRedirectFlags(0)
+  , mReqContentLength(0U)
+  , mReqContentLengthDetermined(false)
 {
   LOG(("Creating HttpBaseChannel @%p\n", this));
 
   // Subfields of unions cannot be targeted in an initializer list.
 #ifdef MOZ_VALGRIND
   // Zero the entire unions so that Valgrind doesn't complain when we send them
   // to another process.
   memset(&mSelfAddr, 0, sizeof(NetAddr));
@@ -1007,20 +1010,20 @@ HttpBaseChannel::CloneUploadStream(nsIIn
 
 
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsIUploadChannel2
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream *aStream,
-                                       const nsACString &aContentType,
-                                       int64_t aContentLength,
-                                       const nsACString &aMethod,
-                                       bool aStreamHasHeaders)
+                                         const nsACString &aContentType,
+                                         int64_t aContentLength,
+                                         const nsACString &aMethod,
+                                         bool aStreamHasHeaders)
 {
   // Ensure stream is set and method is valid
   NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
 
   {
     DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
     MOZ_ASSERT(!aStreamHasHeaders ||
                NS_FAILED(CallQueryInterface(aStream, getter_AddRefs(mimeStream.value))),
@@ -1050,16 +1053,28 @@ HttpBaseChannel::ExplicitSetUploadStream
       } else {
         SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), aContentType,
                          false);
       }
     }
   }
 
   mUploadStreamHasHeaders = aStreamHasHeaders;
+
+  // We already have the content length. We don't need to determinate it.
+  if (aContentLength > 0) {
+    mReqContentLength = aContentLength;
+    mReqContentLengthDetermined = true;
+  }
+
+  nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
+  if (!seekable) {
+    aStream = new PartiallySeekableInputStream(aStream);
+  }
+
   mUploadStream = aStream;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetUploadStreamHasHeaders(bool *hasHeaders)
 {
   NS_ENSURE_ARG(hasHeaders);
@@ -3406,18 +3421,19 @@ HttpBaseChannel::SetupReplacementChannel
   if (preserveMethod) {
     nsCOMPtr<nsIUploadChannel> uploadChannel =
       do_QueryInterface(httpChannel);
     nsCOMPtr<nsIUploadChannel2> uploadChannel2 =
       do_QueryInterface(httpChannel);
     if (mUploadStream && (uploadChannel2 || uploadChannel)) {
       // rewind upload stream
       nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
-      if (seekable)
-        seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+      MOZ_ASSERT(seekable);
+
+      seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
 
       // replicate original call to SetUploadStream...
       if (uploadChannel2) {
         nsAutoCString ctype;
         // If header is not present mRequestHead.HasHeaderValue will truncated
         // it.  But we want to end up with a void string, not an empty string,
         // because ExplicitSetUploadStream treats the former as "no header" and
         // the latter as "header with empty string value".
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -651,16 +651,19 @@ protected:
 
   uint64_t mChannelId;
 
   // If this channel was created as the result of a redirect, then this value
   // will reflect the redirect flags passed to the SetupReplacementChannel()
   // method.
   uint32_t mLastRedirectFlags;
 
+  uint64_t mReqContentLength;
+  bool mReqContentLengthDetermined;
+
   nsString mIntegrityMetadata;
 
   // Classified channel's matched information
   nsCString mMatchedList;
   nsCString mMatchedProvider;
   nsCString mMatchedPrefix;
 };
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -332,18 +332,16 @@ nsHttpChannel::nsHttpChannel()
     , mConcurrentCacheAccess(0)
     , mIsPartialRequest(0)
     , mHasAutoRedirectVetoNotifier(0)
     , mPinCacheContent(0)
     , mIsCorsPreflightDone(0)
     , mStronglyFramed(false)
     , mUsedNetwork(0)
     , mAuthConnectionRestartable(0)
-    , mReqContentLengthDetermined(0)
-    , mReqContentLength(0U)
     , mPushedStream(nullptr)
     , mLocalBlocklist(false)
     , mOnTailUnblock(nullptr)
     , mWarningReporter(nullptr)
     , mIsReadingFromCache(false)
     , mFirstResponseSource(RESPONSE_PENDING)
     , mOnCacheAvailableCalled(false)
     , mRaceCacheWithNetwork(false)
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -656,20 +656,16 @@ private:
     uint32_t                          mStronglyFramed : 1;
 
     // true if an HTTP transaction is created for the socket thread
     uint32_t                          mUsedNetwork : 1;
 
     // the next authentication request can be sent on a whole new connection
     uint32_t                          mAuthConnectionRestartable : 1;
 
-    uint32_t                          mReqContentLengthDetermined : 1;
-
-    uint64_t                          mReqContentLength;
-
     nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
 
     // Needed for accurate DNS timing
     RefPtr<nsDNSPrefetch>           mDNSPrefetch;
 
     Http2PushedStream                 *mPushedStream;
     // True if the channel's principal was found on a phishing, malware, or
     // tracking (if tracking protection is enabled) blocklist
new file mode 100644
--- /dev/null
+++ b/netwerk/test/gtest/TestPartiallySeekableInputStream.cpp
@@ -0,0 +1,225 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "nsIPipe.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "mozilla/net/PartiallySeekableInputStream.h"
+
+using mozilla::net::PartiallySeekableInputStream;
+
+class NonSeekableStream final : public nsIInputStream
+{
+  nsCOMPtr<nsIInputStream> mStream;
+
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  explicit NonSeekableStream(const nsACString& aBuffer)
+  {
+    NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+  }
+
+  NS_IMETHOD
+  Available(uint64_t* aLength) override
+  {
+    return mStream->Available(aLength);
+  }
+
+  NS_IMETHOD
+  Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override
+  {
+    return mStream->Read(aBuffer, aCount, aReadCount);
+  }
+
+  NS_IMETHOD
+  ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+               uint32_t aCount, uint32_t *aResult) override
+  {
+    return mStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+  }
+
+  NS_IMETHOD
+  Close() override
+  {
+    return mStream->Close();
+  }
+
+  NS_IMETHOD
+  IsNonBlocking(bool* aNonBlocking) override
+  {
+    return mStream->IsNonBlocking(aNonBlocking);
+  }
+
+private:
+  ~NonSeekableStream() {}
+};
+
+NS_IMPL_ISUPPORTS(NonSeekableStream, nsIInputStream)
+
+// Helper function for creating a non-seekable nsIInputStream + a
+// PartiallySeekableInputStream.
+PartiallySeekableInputStream*
+CreateStream(uint32_t aSize, uint64_t aStreamSize, nsCString& aBuffer)
+{
+  aBuffer.SetLength(aSize);
+  for (uint32_t i = 0; i < aSize; ++i) {
+    aBuffer.BeginWriting()[i] = i % 10;
+  }
+
+  RefPtr<NonSeekableStream> stream = new NonSeekableStream(aBuffer);
+  return new PartiallySeekableInputStream(stream, aStreamSize);
+}
+
+// Simple reading.
+TEST(TestPartiallySeekableInputStream, SimpleRead) {
+  const size_t kBufSize = 10;
+
+  nsCString buf;
+  RefPtr<PartiallySeekableInputStream> psi = CreateStream(kBufSize, 5, buf);
+
+  uint64_t length;
+  ASSERT_EQ(NS_OK, psi->Available(&length));
+  ASSERT_EQ((uint64_t)kBufSize, length);
+
+  char buf2[kBufSize];
+  uint32_t count;
+  ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+  ASSERT_EQ(count, buf.Length());
+  ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+
+  // At this point, after reading more than the buffer size, seek is not
+  // allowed.
+  ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED,
+            psi->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+  ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED,
+            psi->Seek(nsISeekableStream::NS_SEEK_END, 0));
+
+  ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED,
+            psi->Seek(nsISeekableStream::NS_SEEK_CUR, 0));
+
+  // Position is at the end of the stream.
+  int64_t pos;
+  ASSERT_EQ(NS_OK, psi->Tell(&pos));
+  ASSERT_EQ((int64_t)kBufSize, pos);
+}
+
+// Simple seek
+TEST(TestPartiallySeekableInputStream, SimpleSeek) {
+  const size_t kBufSize = 10;
+
+  nsCString buf;
+  RefPtr<PartiallySeekableInputStream> psi = CreateStream(kBufSize, 5, buf);
+
+  uint64_t length;
+  ASSERT_EQ(NS_OK, psi->Available(&length));
+  ASSERT_EQ((uint64_t)kBufSize, length);
+
+  uint32_t count;
+
+  {
+    char buf2[3];
+    ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+    ASSERT_EQ(count, sizeof(buf2));
+    ASSERT_TRUE(nsCString(buf.get(), sizeof(buf2)).Equals(nsCString(buf2, sizeof(buf2))));
+
+    int64_t pos;
+    ASSERT_EQ(NS_OK, psi->Tell(&pos));
+    ASSERT_EQ((int64_t)sizeof(buf2), pos);
+
+    uint64_t length;
+    ASSERT_EQ(NS_OK, psi->Available(&length));
+    ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2), length);
+  }
+
+  // Let's seek back to the beginning using NS_SEEK_SET
+  ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+  {
+    uint64_t length;
+    ASSERT_EQ(NS_OK, psi->Available(&length));
+    ASSERT_EQ((uint64_t)kBufSize, length);
+
+    char buf2[3];
+    ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+    ASSERT_EQ(count, sizeof(buf2));
+    ASSERT_TRUE(nsCString(buf.get(), sizeof(buf2)).Equals(nsCString(buf2, sizeof(buf2))));
+
+    int64_t pos;
+    ASSERT_EQ(NS_OK, psi->Tell(&pos));
+    ASSERT_EQ((int64_t)sizeof(buf2), pos);
+
+    ASSERT_EQ(NS_OK, psi->Available(&length));
+    ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2), length);
+  }
+
+  // Let's seek back of 2 bytes using NS_SEEK_CUR
+  ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_CUR, -2));
+
+  {
+    uint64_t length;
+    ASSERT_EQ(NS_OK, psi->Available(&length));
+    ASSERT_EQ((uint64_t)kBufSize - 1, length);
+
+    char buf2[3];
+    ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+    ASSERT_EQ(count, sizeof(buf2));
+    ASSERT_TRUE(nsCString(buf.get() + 1, sizeof(buf2)).Equals(nsCString(buf2, sizeof(buf2))));
+
+    int64_t pos;
+    ASSERT_EQ(NS_OK, psi->Tell(&pos));
+    ASSERT_EQ((int64_t)sizeof(buf2) + 1, pos);
+
+    ASSERT_EQ(NS_OK, psi->Available(&length));
+    ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2) - 1, length);
+  }
+
+  // Let's seek back to the beginning using NS_SEEK_SET
+  ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+  {
+    uint64_t length;
+    ASSERT_EQ(NS_OK, psi->Available(&length));
+    ASSERT_EQ((uint64_t)kBufSize, length);
+
+    char buf2[kBufSize];
+    ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+    ASSERT_EQ(count, buf.Length());
+    ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+  }
+}
+
+// Full in cache
+TEST(TestPartiallySeekableInputStream, FullCachedSeek) {
+  const size_t kBufSize = 10;
+
+  nsCString buf;
+  RefPtr<PartiallySeekableInputStream> psi = CreateStream(kBufSize, 4096, buf);
+
+  uint64_t length;
+  ASSERT_EQ(NS_OK, psi->Available(&length));
+  ASSERT_EQ((uint64_t)kBufSize, length);
+
+  char buf2[kBufSize];
+  uint32_t count;
+  ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+  ASSERT_EQ(count, buf.Length());
+  ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+
+  ASSERT_EQ(NS_OK, psi->Available(&length));
+  ASSERT_EQ((uint64_t)0, length);
+
+  ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+  ASSERT_EQ(NS_OK, psi->Available(&length));
+  ASSERT_EQ((uint64_t)kBufSize, length);
+
+  ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+  ASSERT_EQ(count, buf.Length());
+  ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+
+  ASSERT_EQ(NS_OK, psi->Available(&length));
+  ASSERT_EQ((uint64_t)0, length);
+}
--- a/netwerk/test/gtest/moz.build
+++ b/netwerk/test/gtest/moz.build
@@ -2,15 +2,16 @@
 # vim: set filetype=python:
 # 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/.
 
 UNIFIED_SOURCES += [
     'TestHeaders.cpp',
     'TestHttpAuthUtils.cpp',
+    'TestPartiallySeekableInputStream.cpp',
     'TestProtocolProxyService.cpp',
     'TestStandardURL.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul-gtest'