Bug 1496581 - Split nsISeekableStream in 2 classes: nsISeekableStream and nsITellableStream, f=mayhemer, r=froydnj
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 18 Oct 2018 13:35:35 +0200
changeset 490223 2da6504c901e9474566aa1663d907bd58bed9edb
parent 490222 0162bb225b88cfda9b702d88464d48fbf1f372ee
child 490224 4a22c1051967e4edef65c5c68305e72618eb2c0d
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersfroydnj
bugs1496581, 1494176
milestone64.0a1
Bug 1496581 - Split nsISeekableStream in 2 classes: nsISeekableStream and nsITellableStream, f=mayhemer, r=froydnj In the current code there are 3 main issues: 1. nsFileStream is not really thread-safe. There is nothing to protect the internal members and we see crashes. 2. nsPipeInputStream doesn't implement ::Seek() method and that caused issues in devtools when a nsHttpChannel sends POST data using a pipe. In order to fix this, bug 1494176 added a check in nsHttpChannel: if the stream doesn't implement ::Seek(), let's clone it. This was an hack around nsPipeInputStream, and it's bad. 3. When nsHttpChannel sends POST data using a file stream, nsFileStream does I/O on main-thread because of the issue 2. Plus, ::Seek() is called on the main-thread causing issue 1. Note that nsPipeInputStream implements only ::Tell(), of the nsISeekableStream methods. It doesn't implement ::Seek() and it doesn't implement ::SetEOF(). With this patch I want to fix point 2 and point 3 (and consequentially issue 1 - but we need a separate fix for it - follow up). The patch does: 1. it splits nsISeekableStream in 2 interfaces: nsITellableStream and nsISeekableStream. 2. nsPipeInputStream implements only nsITellableStream. Doing this, we don't need the ::Seek() check for point 2 in nsHttpChannel: a simple QI check is enough. 3. Because we don't call ::Seek() in nsHttpChannel, nsFileStream doesn't do I/O on the main-thread, and we don't crash doing so.
dom/file/MemoryBlobImpl.h
netwerk/base/PartiallySeekableInputStream.cpp
netwerk/base/PartiallySeekableInputStream.h
netwerk/base/ThrottleQueue.cpp
netwerk/base/nsBufferedStreams.cpp
netwerk/base/nsBufferedStreams.h
netwerk/base/nsFileStreams.cpp
netwerk/base/nsFileStreams.h
netwerk/base/nsInputStreamPump.cpp
netwerk/base/nsMIMEInputStream.cpp
netwerk/cache2/CacheFileInputStream.cpp
netwerk/cache2/CacheFileInputStream.h
netwerk/cache2/CacheFileOutputStream.cpp
netwerk/cache2/CacheFileOutputStream.h
netwerk/protocol/http/HttpBaseChannel.cpp
xpcom/io/InputStreamLengthWrapper.cpp
xpcom/io/InputStreamLengthWrapper.h
xpcom/io/NonBlockingAsyncInputStream.cpp
xpcom/io/NonBlockingAsyncInputStream.h
xpcom/io/SlicedInputStream.cpp
xpcom/io/SlicedInputStream.h
xpcom/io/moz.build
xpcom/io/nsISeekableStream.idl
xpcom/io/nsITellableStream.idl
xpcom/io/nsMultiplexInputStream.cpp
xpcom/io/nsPipe3.cpp
xpcom/io/nsStorageStream.cpp
xpcom/io/nsStringStream.cpp
xpcom/io/nsStringStream.h
xpcom/tests/gtest/TestPipes.cpp
--- a/dom/file/MemoryBlobImpl.h
+++ b/dom/file/MemoryBlobImpl.h
@@ -120,16 +120,17 @@ public:
                            uint32_t aLength,
                            nsIInputStream** _retval);
 
     NS_DECL_THREADSAFE_ISUPPORTS
 
     // These are mandatory.
     NS_FORWARD_NSIINPUTSTREAM(mStream->)
     NS_FORWARD_NSISEEKABLESTREAM(mSeekableStream->)
+    NS_FORWARD_NSITELLABLESTREAM(mSeekableStream->)
     NS_FORWARD_NSICLONEABLEINPUTSTREAM(mCloneableInputStream->)
 
     // This is optional. We use a conditional QI to keep it from being called
     // if the underlying stream doesn't support it.
     NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(mSerializableInputStream->)
 
   private:
     ~DataOwnerAdapter() {}
--- a/netwerk/base/PartiallySeekableInputStream.cpp
+++ b/netwerk/base/PartiallySeekableInputStream.cpp
@@ -12,16 +12,17 @@ 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(nsITellableStream)
   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)
--- a/netwerk/base/PartiallySeekableInputStream.h
+++ b/netwerk/base/PartiallySeekableInputStream.h
@@ -29,16 +29,17 @@ class PartiallySeekableInputStream final
                                          , public nsIInputStreamLength
                                          , public nsIAsyncInputStreamLength
                                          , public nsIInputStreamLengthCallback
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIINPUTSTREAMLENGTH
   NS_DECL_NSIASYNCINPUTSTREAMLENGTH
   NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
 
--- a/netwerk/base/ThrottleQueue.cpp
+++ b/netwerk/base/ThrottleQueue.cpp
@@ -21,33 +21,35 @@ class ThrottleInputStream final
 {
 public:
 
   ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
 
   void AllowInput();
 
 private:
 
   ~ThrottleInputStream();
 
   nsCOMPtr<nsIInputStream> mStream;
   RefPtr<ThrottleQueue> mQueue;
   nsresult mClosedStatus;
 
   nsCOMPtr<nsIInputStreamCallback> mCallback;
   nsCOMPtr<nsIEventTarget> mEventTarget;
 };
 
-NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream, nsISeekableStream)
+NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream,
+                  nsITellableStream, nsISeekableStream)
 
 ThrottleInputStream::ThrottleInputStream(nsIInputStream *aStream, ThrottleQueue* aQueue)
   : mStream(aStream)
   , mQueue(aQueue)
   , mClosedStatus(NS_OK)
 {
   MOZ_ASSERT(aQueue != nullptr);
 }
@@ -155,17 +157,17 @@ ThrottleInputStream::Seek(int32_t aWhenc
 
 NS_IMETHODIMP
 ThrottleInputStream::Tell(int64_t* aResult)
 {
   if (NS_FAILED(mClosedStatus)) {
     return mClosedStatus;
   }
 
-  nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+  nsCOMPtr<nsITellableStream> sstream = do_QueryInterface(mStream);
   if (!sstream) {
     return NS_ERROR_FAILURE;
   }
 
   return sstream->Tell(aResult);
 }
 
 NS_IMETHODIMP
--- a/netwerk/base/nsBufferedStreams.cpp
+++ b/netwerk/base/nsBufferedStreams.cpp
@@ -58,17 +58,17 @@ nsBufferedStream::nsBufferedStream()
 {
 }
 
 nsBufferedStream::~nsBufferedStream()
 {
     Close();
 }
 
-NS_IMPL_ISUPPORTS(nsBufferedStream, nsISeekableStream)
+NS_IMPL_ISUPPORTS(nsBufferedStream, nsITellableStream, nsISeekableStream)
 
 nsresult
 nsBufferedStream::Init(nsISupports* stream, uint32_t bufferSize)
 {
     NS_ASSERTION(stream, "need to supply a stream");
     NS_ASSERTION(mStream == nullptr, "already inited");
     mStream = stream;
     NS_IF_ADDREF(mStream);
@@ -300,16 +300,17 @@ NS_INTERFACE_MAP_BEGIN(nsBufferedInputSt
     NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback, mIsAsyncInputStreamLength)
     NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream)
 NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
 
 NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream,
                             nsIInputStream,
                             nsIBufferedInputStream,
                             nsISeekableStream,
+                            nsITellableStream,
                             nsIStreamBufferAccess)
 
 nsBufferedInputStream::nsBufferedInputStream()
    : nsBufferedStream()
    , mMutex("nsBufferedInputStream::mMutex")
    , mIsIPCSerializable(true)
    , mIsAsyncInputStream(false)
    , mIsCloneableInputStream(false)
--- a/netwerk/base/nsBufferedStreams.h
+++ b/netwerk/base/nsBufferedStreams.h
@@ -21,16 +21,17 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsBufferedStream : public nsISeekableStream
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSISEEKABLESTREAM
+    NS_DECL_NSITELLABLESTREAM
 
     nsBufferedStream();
 
     nsresult Close();
 
 protected:
     virtual ~nsBufferedStream();
 
--- a/netwerk/base/nsFileStreams.cpp
+++ b/netwerk/base/nsFileStreams.cpp
@@ -56,16 +56,17 @@ nsFileStreamBase::~nsFileStreamBase()
     // We don't want to try to rewrind the stream when shutting down.
     mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
 
     Close();
 }
 
 NS_IMPL_ISUPPORTS(nsFileStreamBase,
                   nsISeekableStream,
+                  nsITellableStream,
                   nsIFileMetadata)
 
 NS_IMETHODIMP
 nsFileStreamBase::Seek(int32_t whence, int64_t offset)
 {
     nsresult rv = DoPendingOpen();
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -437,16 +438,17 @@ NS_INTERFACE_MAP_BEGIN(nsFileInputStream
     NS_IMPL_QUERY_CLASSINFO(nsFileInputStream)
     NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable())
 NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
 
 NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream,
                             nsIInputStream,
                             nsIFileInputStream,
                             nsISeekableStream,
+                            nsITellableStream,
                             nsILineInputStream)
 
 nsresult
 nsFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
 {
     NS_ENSURE_NO_AGGREGATION(aOuter);
 
     RefPtr<nsFileInputStream> stream = new nsFileInputStream();
--- a/netwerk/base/nsFileStreams.h
+++ b/netwerk/base/nsFileStreams.h
@@ -26,16 +26,17 @@
 class nsFileStreamBase : public nsISeekableStream,
                          public nsIFileMetadata
 {
 public:
     // Record refcount changes to ensure that streams are destroyed on
     // consistent threads when recording/replaying.
     NS_DECL_THREADSAFE_ISUPPORTS_WITH_RECORDING(mozilla::recordreplay::Behavior::Preserve)
     NS_DECL_NSISEEKABLESTREAM
+    NS_DECL_NSITELLABLESTREAM
     NS_DECL_NSIFILEMETADATA
 
     nsFileStreamBase();
 
 protected:
     virtual ~nsFileStreamBase();
 
     nsresult Close();
--- a/netwerk/base/nsInputStreamPump.cpp
+++ b/netwerk/base/nsInputStreamPump.cpp
@@ -569,20 +569,20 @@ nsInputStreamPump::OnStateTransfer()
         //       however, many do not... mailnews... stream converters...
         //       cough, cough.  the input stream pump is fairly tolerant
         //       in this regard; however, if an ODA does not consume any
         //       data from the stream, then we could potentially end up in
         //       an infinite loop.  we do our best here to try to catch
         //       such an error.  (see bug 189672)
 
         // in most cases this QI will succeed (mAsyncStream is almost always
-        // a nsPipeInputStream, which implements nsISeekableStream::Tell).
+        // a nsPipeInputStream, which implements nsITellableStream::Tell).
         int64_t offsetBefore;
-        nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mAsyncStream);
-        if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
+        nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mAsyncStream);
+        if (tellable && NS_FAILED(tellable->Tell(&offsetBefore))) {
             MOZ_ASSERT_UNREACHABLE("Tell failed on readable stream");
             offsetBefore = 0;
         }
 
         uint32_t odaAvail =
             avail > UINT32_MAX ?
             UINT32_MAX : uint32_t(avail);
 
@@ -597,21 +597,21 @@ nsInputStreamPump::OnStateTransfer()
             rv = mListener->OnDataAvailable(this, mListenerContext,
                                             mAsyncStream, mStreamOffset,
                                             odaAvail);
         }
 
         // don't enter this code if ODA failed or called Cancel
         if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(mStatus)) {
             // test to see if this ODA failed to consume data
-            if (seekable) {
+            if (tellable) {
                 // NOTE: if Tell fails, which can happen if the stream is
                 // now closed, then we assume that everything was read.
                 int64_t offsetAfter;
-                if (NS_FAILED(seekable->Tell(&offsetAfter)))
+                if (NS_FAILED(tellable->Tell(&offsetAfter)))
                     offsetAfter = offsetBefore + odaAvail;
                 if (offsetAfter > offsetBefore)
                     mStreamOffset += (offsetAfter - offsetBefore);
                 else if (mSuspendCount == 0) {
                     //
                     // possible infinite loop if we continue pumping data!
                     //
                     // NOTE: although not allowed by nsIStreamListener, we
--- a/netwerk/base/nsMIMEInputStream.cpp
+++ b/netwerk/base/nsMIMEInputStream.cpp
@@ -42,16 +42,17 @@ class nsMIMEInputStream : public nsIMIME
 
 public:
     nsMIMEInputStream();
 
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIINPUTSTREAM
     NS_DECL_NSIMIMEINPUTSTREAM
     NS_DECL_NSISEEKABLESTREAM
+    NS_DECL_NSITELLABLESTREAM
     NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
     NS_DECL_NSIASYNCINPUTSTREAM
     NS_DECL_NSIINPUTSTREAMCALLBACK
     NS_DECL_NSIINPUTSTREAMLENGTH
     NS_DECL_NSIASYNCINPUTSTREAMLENGTH
     NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
     NS_DECL_NSICLONEABLEINPUTSTREAM
 
@@ -93,16 +94,17 @@ NS_IMPL_RELEASE(nsMIMEInputStream)
 
 NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE,
                   NS_MIMEINPUTSTREAM_CID)
 
 NS_INTERFACE_MAP_BEGIN(nsMIMEInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIMIMEInputStream)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream, nsIMIMEInputStream)
   NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+  NS_INTERFACE_MAP_ENTRY(nsITellableStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
                                      IsIPCSerializable())
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream,
                                      IsAsyncInputStream())
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
                                      IsAsyncInputStream())
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMIMEInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength,
@@ -115,17 +117,18 @@ NS_INTERFACE_MAP_BEGIN(nsMIMEInputStream
                                      IsCloneableInputStream())
   NS_IMPL_QUERY_CLASSINFO(nsMIMEInputStream)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream,
                             nsIMIMEInputStream,
                             nsIAsyncInputStream,
                             nsIInputStream,
-                            nsISeekableStream)
+                            nsISeekableStream,
+                            nsITellableStream)
 
 nsMIMEInputStream::nsMIMEInputStream()
   : mStartedReading(false)
   , mMutex("nsMIMEInputStream::mMutex")
 {
 }
 
 NS_IMETHODIMP
@@ -307,23 +310,25 @@ nsMIMEInputStream::OnInputStreamReady(ns
 
         callback.swap(mAsyncWaitCallback);
   }
 
   MOZ_ASSERT(callback);
   return callback->OnInputStreamReady(this);
 }
 
-// nsISeekableStream
+// nsITellableStream
 NS_IMETHODIMP nsMIMEInputStream::Tell(int64_t *_retval)
 {
     INITSTREAMS;
-    nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+    nsCOMPtr<nsITellableStream> stream = do_QueryInterface(mStream);
     return stream->Tell(_retval);
 }
+
+// nsISeekableStream
 NS_IMETHODIMP nsMIMEInputStream::SetEOF(void) {
     INITSTREAMS;
     nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
     return stream->SetEOF();
 }
 
 
 /**
--- a/netwerk/cache2/CacheFileInputStream.cpp
+++ b/netwerk/cache2/CacheFileInputStream.cpp
@@ -33,16 +33,17 @@ CacheFileInputStream::Release()
 
   return count;
 }
 
 NS_INTERFACE_MAP_BEGIN(CacheFileInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
   NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+  NS_INTERFACE_MAP_ENTRY(nsITellableStream)
   NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
 NS_INTERFACE_MAP_END
 
 CacheFileInputStream::CacheFileInputStream(CacheFile *aFile,
                                            nsISupports *aEntry,
                                            bool aAlternativeData)
   : mFile(aFile)
@@ -384,16 +385,24 @@ CacheFileInputStream::Seek(int32_t whenc
   mPos = newPos;
   EnsureCorrectChunk(false);
 
   LOG(("CacheFileInputStream::Seek() [this=%p, pos=%" PRId64 "]", this, mPos));
   return NS_OK;
 }
 
 NS_IMETHODIMP
+CacheFileInputStream::SetEOF()
+{
+  MOZ_ASSERT(false, "Don't call SetEOF on cache input stream");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsITellableStream
+NS_IMETHODIMP
 CacheFileInputStream::Tell(int64_t *_retval)
 {
   CacheFileAutoLock lock(mFile);
 
   if (mClosed) {
     LOG(("CacheFileInputStream::Tell() - Stream is closed. [this=%p]", this));
     return NS_BASE_STREAM_CLOSED;
   }
@@ -403,23 +412,16 @@ CacheFileInputStream::Tell(int64_t *_ret
   if (mAlternativeData) {
     *_retval -= mFile->mAltDataOffset;
   }
 
   LOG(("CacheFileInputStream::Tell() [this=%p, retval=%" PRId64 "]", this, *_retval));
   return NS_OK;
 }
 
-NS_IMETHODIMP
-CacheFileInputStream::SetEOF()
-{
-  MOZ_ASSERT(false, "Don't call SetEOF on cache input stream");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
 // CacheFileChunkListener
 nsresult
 CacheFileInputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
 {
   MOZ_CRASH("CacheFileInputStream::OnChunkRead should not be called!");
   return NS_ERROR_UNEXPECTED;
 }
 
--- a/netwerk/cache2/CacheFileInputStream.h
+++ b/netwerk/cache2/CacheFileInputStream.h
@@ -19,16 +19,17 @@ class CacheFile;
 class CacheFileInputStream : public nsIAsyncInputStream
                            , public nsISeekableStream
                            , public CacheFileChunkListener
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
 
 public:
   explicit CacheFileInputStream(CacheFile *aFile, nsISupports *aEntry,
                                 bool aAlternativeData);
 
   NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
--- a/netwerk/cache2/CacheFileOutputStream.cpp
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -35,16 +35,17 @@ CacheFileOutputStream::Release()
 
   return count;
 }
 
 NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream)
   NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream)
   NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+  NS_INTERFACE_MAP_ENTRY(nsITellableStream)
   NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
 NS_INTERFACE_MAP_END
 
 CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile,
                                              CacheOutputCloseListener *aCloseListener,
                                              bool aAlternativeData)
   : mFile(aFile)
@@ -284,16 +285,27 @@ CacheFileOutputStream::Seek(int32_t when
   mPos = newPos;
   EnsureCorrectChunk(true);
 
   LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%" PRId64 "]", this, mPos));
   return NS_OK;
 }
 
 NS_IMETHODIMP
+CacheFileOutputStream::SetEOF()
+{
+  MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented");
+  // Right now we don't use SetEOF(). If we ever need this method, we need
+  // to think about what to do with input streams that already points beyond
+  // new EOF.
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsITellableStream
+NS_IMETHODIMP
 CacheFileOutputStream::Tell(int64_t *_retval)
 {
   CacheFileAutoLock lock(mFile);
 
   if (mClosed) {
     LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this));
     return NS_BASE_STREAM_CLOSED;
   }
@@ -303,26 +315,16 @@ CacheFileOutputStream::Tell(int64_t *_re
   if (mAlternativeData) {
     *_retval -= mFile->mAltDataOffset;
   }
 
   LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%" PRId64 "]", this, *_retval));
   return NS_OK;
 }
 
-NS_IMETHODIMP
-CacheFileOutputStream::SetEOF()
-{
-  MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented");
-  // Right now we don't use SetEOF(). If we ever need this method, we need
-  // to think about what to do with input streams that already points beyond
-  // new EOF.
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
 // CacheFileChunkListener
 nsresult
 CacheFileOutputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
 {
   MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!");
   return NS_ERROR_UNEXPECTED;
 }
 
--- a/netwerk/cache2/CacheFileOutputStream.h
+++ b/netwerk/cache2/CacheFileOutputStream.h
@@ -21,16 +21,17 @@ class CacheOutputCloseListener;
 class CacheFileOutputStream : public nsIAsyncOutputStream
                             , public nsISeekableStream
                             , public CacheFileChunkListener
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOUTPUTSTREAM
   NS_DECL_NSIASYNCOUTPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
 
 public:
   CacheFileOutputStream(CacheFile *aFile,
                         CacheOutputCloseListener *aCloseListener,
                         bool aAlternativeData);
 
   NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) override;
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -915,24 +915,20 @@ HttpBaseChannel::EnsureUploadStreamIsClo
   NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED);
 
   // We can immediately exec the callback if we don't have an upload stream.
   if (!mUploadStream) {
     aCallback->Run();
     return NS_OK;
   }
 
-  // Some nsSeekableStreams do not implement ::Seek() (see nsPipeInputStream).
-  // In this case, we must clone the uploadStream into a memory stream in order
-  // to have it seekable.  If the CloneUploadStream() will succeed, then
-  // synchronously invoke the callback to indicate we're already cloneable.
+  // Upload nsIInputStream must be cloneable and seekable in order to be
+  // processed by devtools network inspector.
   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
-  if (seekable &&
-      NS_SUCCEEDED(seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0)) &&
-      NS_InputStreamIsCloneable(mUploadStream)) {
+  if (seekable && NS_InputStreamIsCloneable(mUploadStream)) {
     aCallback->Run();
     return NS_OK;
   }
 
   nsCOMPtr<nsIStorageStream> storageStream;
   nsresult rv = NS_NewStorageStream(4096, UINT32_MAX,
                                     getter_AddRefs(storageStream));
   NS_ENSURE_SUCCESS(rv, rv);
--- a/xpcom/io/InputStreamLengthWrapper.cpp
+++ b/xpcom/io/InputStreamLengthWrapper.cpp
@@ -19,16 +19,18 @@ NS_IMPL_RELEASE(InputStreamLengthWrapper
 NS_INTERFACE_MAP_BEGIN(InputStreamLengthWrapper)
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
                                      mWeakCloneableInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
                                      mWeakIPCSerializableInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream,
                                      mWeakSeekableInputStream || !mInputStream)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream,
+                                     mWeakTellableInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream,
                                      mWeakAsyncInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
                                      mWeakAsyncInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
 NS_INTERFACE_MAP_END
 
@@ -58,31 +60,33 @@ InputStreamLengthWrapper::MaybeWrap(alre
   return inputStream.forget();
 }
 
 InputStreamLengthWrapper::InputStreamLengthWrapper(already_AddRefed<nsIInputStream> aInputStream,
                                                    int64_t aLength)
   : mWeakCloneableInputStream(nullptr)
   , mWeakIPCSerializableInputStream(nullptr)
   , mWeakSeekableInputStream(nullptr)
+  , mWeakTellableInputStream(nullptr)
   , mWeakAsyncInputStream(nullptr)
   , mLength(aLength)
   , mConsumed(false)
   , mMutex("InputStreamLengthWrapper::mMutex")
 {
   MOZ_ASSERT(mLength >= 0);
 
   nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream);
   SetSourceStream(inputStream.forget());
 }
 
 InputStreamLengthWrapper::InputStreamLengthWrapper()
   : mWeakCloneableInputStream(nullptr)
   , mWeakIPCSerializableInputStream(nullptr)
   , mWeakSeekableInputStream(nullptr)
+  , mWeakTellableInputStream(nullptr)
   , mWeakAsyncInputStream(nullptr)
   , mLength(-1)
   , mConsumed(false)
   , mMutex("InputStreamLengthWrapper::mMutex")
 {}
 
 InputStreamLengthWrapper::~InputStreamLengthWrapper() = default;
 
@@ -107,16 +111,22 @@ InputStreamLengthWrapper::SetSourceStrea
   }
 
   nsCOMPtr<nsISeekableStream> seekableStream =
     do_QueryInterface(mInputStream);
   if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) {
     mWeakSeekableInputStream = seekableStream;
   }
 
+  nsCOMPtr<nsITellableStream> tellableStream =
+    do_QueryInterface(mInputStream);
+  if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) {
+    mWeakTellableInputStream = tellableStream;
+  }
+
   nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
     do_QueryInterface(mInputStream);
   if (asyncInputStream && SameCOMIdentity(mInputStream, asyncInputStream)) {
     mWeakAsyncInputStream = asyncInputStream;
   }
 }
 
 // nsIInputStream interface
@@ -326,34 +336,36 @@ InputStreamLengthWrapper::Seek(int32_t a
   NS_ENSURE_STATE(mInputStream);
   NS_ENSURE_STATE(mWeakSeekableInputStream);
 
   mConsumed = true;
   return mWeakSeekableInputStream->Seek(aWhence, aOffset);
 }
 
 NS_IMETHODIMP
-InputStreamLengthWrapper::Tell(int64_t *aResult)
-{
-  NS_ENSURE_STATE(mInputStream);
-  NS_ENSURE_STATE(mWeakSeekableInputStream);
-
-  return mWeakSeekableInputStream->Tell(aResult);
-}
-
-NS_IMETHODIMP
 InputStreamLengthWrapper::SetEOF()
 {
   NS_ENSURE_STATE(mInputStream);
   NS_ENSURE_STATE(mWeakSeekableInputStream);
 
   mConsumed = true;
   return mWeakSeekableInputStream->SetEOF();
 }
 
+// nsITellableStream
+
+NS_IMETHODIMP
+InputStreamLengthWrapper::Tell(int64_t *aResult)
+{
+  NS_ENSURE_STATE(mInputStream);
+  NS_ENSURE_STATE(mWeakTellableInputStream);
+
+  return mWeakTellableInputStream->Tell(aResult);
+}
+
 // nsIInputStreamLength
 
 NS_IMETHODIMP
 InputStreamLengthWrapper::Length(int64_t* aLength)
 {
   NS_ENSURE_STATE(mInputStream);
   *aLength = mLength;
   return NS_OK;
--- a/xpcom/io/InputStreamLengthWrapper.h
+++ b/xpcom/io/InputStreamLengthWrapper.h
@@ -31,16 +31,17 @@ class InputStreamLengthWrapper final : p
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIINPUTSTREAMLENGTH
 
   // This method creates a InputStreamLengthWrapper around aInputStream if
   // this doesn't implement nsIInputStreamLength or
   // nsIInputStreamAsyncLength interface, but it implements
   // nsIAsyncInputStream. For this kind of streams,
   // InputStreamLengthHelper is not able to retrieve the length. This
@@ -64,16 +65,17 @@ private:
   SetSourceStream(already_AddRefed<nsIInputStream> aInputStream);
 
   nsCOMPtr<nsIInputStream> mInputStream;
 
   // Raw pointers because these are just QI of mInputStream.
   nsICloneableInputStream* mWeakCloneableInputStream;
   nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream;
   nsISeekableStream* mWeakSeekableInputStream;
+  nsITellableStream* mWeakTellableInputStream;
   nsIAsyncInputStream* mWeakAsyncInputStream;
 
   int64_t mLength;
   bool mConsumed;
 
   mozilla::Mutex mMutex;
 
   // This is used for AsyncWait and it's protected by mutex.
--- a/xpcom/io/NonBlockingAsyncInputStream.cpp
+++ b/xpcom/io/NonBlockingAsyncInputStream.cpp
@@ -46,16 +46,18 @@ NS_INTERFACE_MAP_BEGIN(NonBlockingAsyncI
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
                                      mWeakCloneableInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
                                      mWeakIPCSerializableInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream,
                                      mWeakSeekableInputStream)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream,
+                                     mWeakTellableInputStream)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
 NS_INTERFACE_MAP_END
 
 /* static */ nsresult
 NonBlockingAsyncInputStream::Create(already_AddRefed<nsIInputStream> aInputStream,
                                     nsIAsyncInputStream** aResult)
 {
   MOZ_DIAGNOSTIC_ASSERT(aResult);
@@ -83,16 +85,17 @@ NonBlockingAsyncInputStream::Create(alre
   return NS_OK;
 }
 
 NonBlockingAsyncInputStream::NonBlockingAsyncInputStream(already_AddRefed<nsIInputStream> aInputStream)
   : mInputStream(std::move(aInputStream))
   , mWeakCloneableInputStream(nullptr)
   , mWeakIPCSerializableInputStream(nullptr)
   , mWeakSeekableInputStream(nullptr)
+  , mWeakTellableInputStream(nullptr)
   , mLock("NonBlockingAsyncInputStream::mLock")
   , mClosed(false)
 {
   MOZ_ASSERT(mInputStream);
 
   nsCOMPtr<nsICloneableInputStream> cloneableStream =
     do_QueryInterface(mInputStream);
   if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) {
@@ -106,16 +109,22 @@ NonBlockingAsyncInputStream::NonBlocking
     mWeakIPCSerializableInputStream = serializableStream;
   }
 
   nsCOMPtr<nsISeekableStream> seekableStream =
     do_QueryInterface(mInputStream);
   if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) {
     mWeakSeekableInputStream = seekableStream;
   }
+
+  nsCOMPtr<nsITellableStream> tellableStream =
+    do_QueryInterface(mInputStream);
+  if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) {
+    mWeakTellableInputStream = tellableStream;
+  }
 }
 
 NonBlockingAsyncInputStream::~NonBlockingAsyncInputStream()
 {}
 
 NS_IMETHODIMP
 NonBlockingAsyncInputStream::Close()
 {
@@ -346,29 +355,31 @@ NonBlockingAsyncInputStream::ExpectedSer
 NS_IMETHODIMP
 NonBlockingAsyncInputStream::Seek(int32_t aWhence, int64_t aOffset)
 {
   NS_ENSURE_STATE(mWeakSeekableInputStream);
   return mWeakSeekableInputStream->Seek(aWhence, aOffset);
 }
 
 NS_IMETHODIMP
-NonBlockingAsyncInputStream::Tell(int64_t* aResult)
-{
-  NS_ENSURE_STATE(mWeakSeekableInputStream);
-  return mWeakSeekableInputStream->Tell(aResult);
-}
-
-NS_IMETHODIMP
 NonBlockingAsyncInputStream::SetEOF()
 {
   NS_ENSURE_STATE(mWeakSeekableInputStream);
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+// nsITellableStream
+
+NS_IMETHODIMP
+NonBlockingAsyncInputStream::Tell(int64_t* aResult)
+{
+  NS_ENSURE_STATE(mWeakTellableInputStream);
+  return mWeakTellableInputStream->Tell(aResult);
+}
+
 void
 NonBlockingAsyncInputStream::RunAsyncWaitCallback(NonBlockingAsyncInputStream::AsyncWaitRunnable* aRunnable,
                                                   already_AddRefed<nsIInputStreamCallback> aCallback)
 {
   nsCOMPtr<nsIInputStreamCallback> callback = std::move(aCallback);
 
   {
     MutexAutoLock lock(mLock);
--- a/xpcom/io/NonBlockingAsyncInputStream.h
+++ b/xpcom/io/NonBlockingAsyncInputStream.h
@@ -29,16 +29,17 @@ class NonBlockingAsyncInputStream final 
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
 
   // |aInputStream| must be a non-blocking, non-async inputSteam.
   static nsresult
   Create(already_AddRefed<nsIInputStream> aInputStream,
          nsIAsyncInputStream** aAsyncInputStream);
 
 private:
   explicit NonBlockingAsyncInputStream(already_AddRefed<nsIInputStream> aInputStream);
@@ -51,16 +52,17 @@ private:
                        already_AddRefed<nsIInputStreamCallback> aCallback);
 
   nsCOMPtr<nsIInputStream> mInputStream;
 
   // Raw pointers because these are just QI of mInputStream.
   nsICloneableInputStream* MOZ_NON_OWNING_REF mWeakCloneableInputStream;
   nsIIPCSerializableInputStream* MOZ_NON_OWNING_REF mWeakIPCSerializableInputStream;
   nsISeekableStream* MOZ_NON_OWNING_REF mWeakSeekableInputStream;
+  nsITellableStream* MOZ_NON_OWNING_REF mWeakTellableInputStream;
 
   Mutex mLock;
 
   struct WaitClosureOnly
   {
     WaitClosureOnly(AsyncWaitRunnable* aRunnable, nsIEventTarget* aEventTarget);
 
     RefPtr<AsyncWaitRunnable> mRunnable;
--- a/xpcom/io/SlicedInputStream.cpp
+++ b/xpcom/io/SlicedInputStream.cpp
@@ -21,16 +21,18 @@ NS_IMPL_RELEASE(SlicedInputStream);
 NS_INTERFACE_MAP_BEGIN(SlicedInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
                                      mWeakCloneableInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
                                      mWeakIPCSerializableInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream,
                                      mWeakSeekableInputStream || !mInputStream)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream,
+                                     mWeakTellableInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream,
                                      mWeakAsyncInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
                                      mWeakAsyncInputStream || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength,
                                      mWeakInputStreamLength || !mInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength,
                                      mWeakAsyncInputStreamLength || !mInputStream)
@@ -39,16 +41,17 @@ NS_INTERFACE_MAP_BEGIN(SlicedInputStream
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
 NS_INTERFACE_MAP_END
 
 SlicedInputStream::SlicedInputStream(already_AddRefed<nsIInputStream> aInputStream,
                                      uint64_t aStart, uint64_t aLength)
   : mWeakCloneableInputStream(nullptr)
   , mWeakIPCSerializableInputStream(nullptr)
   , mWeakSeekableInputStream(nullptr)
+  , mWeakTellableInputStream(nullptr)
   , mWeakAsyncInputStream(nullptr)
   , mWeakInputStreamLength(nullptr)
   , mWeakAsyncInputStreamLength(nullptr)
   , mStart(aStart)
   , mLength(aLength)
   , mCurPos(0)
   , mClosed(false)
   , mAsyncWaitFlags(0)
@@ -58,16 +61,17 @@ SlicedInputStream::SlicedInputStream(alr
   nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream);
   SetSourceStream(inputStream.forget());
 }
 
 SlicedInputStream::SlicedInputStream()
   : mWeakCloneableInputStream(nullptr)
   , mWeakIPCSerializableInputStream(nullptr)
   , mWeakSeekableInputStream(nullptr)
+  , mWeakTellableInputStream(nullptr)
   , mWeakAsyncInputStream(nullptr)
   , mStart(0)
   , mLength(0)
   , mCurPos(0)
   , mClosed(false)
   , mAsyncWaitFlags(0)
   , mAsyncWaitRequestedCount(0)
   , mMutex("SlicedInputStream::mMutex")
@@ -97,16 +101,22 @@ SlicedInputStream::SetSourceStream(alrea
   }
 
   nsCOMPtr<nsISeekableStream> seekableStream =
     do_QueryInterface(mInputStream);
   if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) {
     mWeakSeekableInputStream = seekableStream;
   }
 
+  nsCOMPtr<nsITellableStream> tellableStream =
+    do_QueryInterface(mInputStream);
+  if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) {
+    mWeakTellableInputStream = tellableStream;
+  }
+
   nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
     do_QueryInterface(mInputStream);
   if (asyncInputStream && SameCOMIdentity(mInputStream, asyncInputStream)) {
     mWeakAsyncInputStream = asyncInputStream;
   }
 
   nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(mInputStream);
   if (streamLength &&
@@ -539,24 +549,36 @@ SlicedInputStream::Seek(int32_t aWhence,
     return rv;
   }
 
   mCurPos = offset;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SlicedInputStream::Tell(int64_t *aResult)
+SlicedInputStream::SetEOF()
 {
   NS_ENSURE_STATE(mInputStream);
   NS_ENSURE_STATE(mWeakSeekableInputStream);
 
+  mClosed = true;
+  return mWeakSeekableInputStream->SetEOF();
+}
+
+// nsITellableStream
+
+NS_IMETHODIMP
+SlicedInputStream::Tell(int64_t *aResult)
+{
+  NS_ENSURE_STATE(mInputStream);
+  NS_ENSURE_STATE(mWeakTellableInputStream);
+
   int64_t tell = 0;
 
-  nsresult rv = mWeakSeekableInputStream->Tell(&tell);
+  nsresult rv = mWeakTellableInputStream->Tell(&tell);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (tell < (int64_t)mStart) {
     *aResult = 0;
     return NS_OK;
   }
@@ -564,26 +586,16 @@ SlicedInputStream::Tell(int64_t *aResult
   *aResult = tell - mStart;
   if (*aResult > (int64_t)mLength) {
     *aResult = mLength;
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-SlicedInputStream::SetEOF()
-{
-  NS_ENSURE_STATE(mInputStream);
-  NS_ENSURE_STATE(mWeakSeekableInputStream);
-
-  mClosed = true;
-  return mWeakSeekableInputStream->SetEOF();
-}
-
 // nsIInputStreamLength
 
 NS_IMETHODIMP
 SlicedInputStream::Length(int64_t* aLength)
 {
   NS_ENSURE_STATE(mInputStream);
   NS_ENSURE_STATE(mWeakInputStreamLength);
 
--- a/xpcom/io/SlicedInputStream.h
+++ b/xpcom/io/SlicedInputStream.h
@@ -31,16 +31,17 @@ class SlicedInputStream final : public n
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIINPUTSTREAMLENGTH
   NS_DECL_NSIASYNCINPUTSTREAMLENGTH
   NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
 
   // Create an input stream whose data comes from a slice of aInputStream.  The
   // slice begins at aStart bytes beyond aInputStream's current position, and
   // extends for a maximum of aLength bytes.  If aInputStream contains fewer
@@ -68,16 +69,17 @@ private:
   AdjustRange(uint64_t aRange);
 
   nsCOMPtr<nsIInputStream> mInputStream;
 
   // Raw pointers because these are just QI of mInputStream.
   nsICloneableInputStream* mWeakCloneableInputStream;
   nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream;
   nsISeekableStream* mWeakSeekableInputStream;
+  nsITellableStream* mWeakTellableInputStream;
   nsIAsyncInputStream* mWeakAsyncInputStream;
   nsIInputStreamLength* mWeakInputStreamLength;
   nsIAsyncInputStreamLength* mWeakAsyncInputStreamLength;
 
   uint64_t mStart;
   uint64_t mLength;
   uint64_t mCurPos;
 
--- a/xpcom/io/moz.build
+++ b/xpcom/io/moz.build
@@ -28,16 +28,17 @@ XPIDL_SOURCES += [
     'nsIPipe.idl',
     'nsISafeOutputStream.idl',
     'nsIScriptableBase64Encoder.idl',
     'nsIScriptableInputStream.idl',
     'nsISeekableStream.idl',
     'nsIStorageStream.idl',
     'nsIStreamBufferAccess.idl',
     'nsIStringStream.idl',
+    'nsITellableStream.idl',
     'nsIUnicharInputStream.idl',
     'nsIUnicharLineInputStream.idl',
     'nsIUnicharOutputStream.idl',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     XPIDL_SOURCES += [
         'nsILocalFileMac.idl',
--- a/xpcom/io/nsISeekableStream.idl
+++ b/xpcom/io/nsISeekableStream.idl
@@ -1,25 +1,27 @@
 /* -*- 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/. */
 
 
+#include "nsITellableStream.idl"
+
 /*
  * nsISeekableStream
  *
  * Note that a stream might not implement all methods (e.g., a readonly stream 
  * won't implement setEOF)
  */
 
 #include "nsISupports.idl"
 
 [scriptable, uuid(8429d350-1040-4661-8b71-f2a6ba455980)]
-interface nsISeekableStream : nsISupports
+interface nsISeekableStream : nsITellableStream
 {
     /* 
      * Sets the stream pointer to the value of the 'offset' parameter 
      */
     const int32_t NS_SEEK_SET = 0;
 
     /*  
      * Sets the stream pointer to its current location plus the value 
@@ -48,27 +50,16 @@ interface nsISeekableStream : nsISupport
      *                 implementing stream.  A negative value causes seeking in 
      *                 the reverse direction.
      *
      *   @throws NS_BASE_STREAM_CLOSED if called on a closed stream.
      */
     void seek(in long whence, in long long offset);
 
     /**
-     *  tell
-     *
-     *  This method reports the current offset, in bytes, from the start of the 
-     *  stream. 
-     *
-     *   @throws NS_BASE_STREAM_CLOSED if called on a closed stream.
-     */
-    long long tell();
-
-
-    /**
      *  setEOF
      *
      *  This method truncates the stream at the current offset.
      *
      *   @throws NS_BASE_STREAM_CLOSED if called on a closed stream.
      */
     void setEOF();
 };
new file mode 100644
--- /dev/null
+++ b/xpcom/io/nsITellableStream.idl
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+
+/*
+ * nsITellableStream
+ *
+ * This class is separate from nsISeekableStream in order to let streams to
+ * implement ::Tell() without implementing the whole nsISeekableStream
+ * interface. Callers can QI the stream to know what is implemented. This is
+ * mainly done for nsPipeInputStream.
+ *
+ *
+ * Implementing this interface, streams are able to expose the current offset
+ * via ::tell().
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(ee942946-4538-45d2-bf05-ffdbf5932621)]
+interface nsITellableStream : nsISupports
+{
+    /**
+     *  tell
+     *
+     *  This method reports the current offset, in bytes, from the start of the
+     *  stream.
+     *
+     *   @throws NS_BASE_STREAM_CLOSED if called on a closed stream.
+     */
+    long long tell();
+};
--- a/xpcom/io/nsMultiplexInputStream.cpp
+++ b/xpcom/io/nsMultiplexInputStream.cpp
@@ -51,16 +51,17 @@ class nsMultiplexInputStream final
 {
 public:
   nsMultiplexInputStream();
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIMULTIPLEXINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIINPUTSTREAMLENGTH
   NS_DECL_NSIASYNCINPUTSTREAMLENGTH
 
   // This is used for nsIAsyncInputStream::AsyncWait
@@ -72,25 +73,28 @@ public:
 
   struct StreamData
   {
     void Initialize(nsIInputStream* aStream, bool aBuffered)
     {
       mStream = aStream;
       mAsyncStream = do_QueryInterface(aStream);
       mSeekableStream = do_QueryInterface(aStream);
+      mTellableStream = do_QueryInterface(aStream);
       mBuffered = aBuffered;
     }
 
     nsCOMPtr<nsIInputStream> mStream;
 
     // This can be null.
     nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
     // This can be null.
     nsCOMPtr<nsISeekableStream> mSeekableStream;
+    // This can be null.
+    nsCOMPtr<nsITellableStream> mTellableStream;
 
     // True if the stream is wrapped with nsIBufferedInputStream.
     bool mBuffered;
   };
 
   Mutex& GetLock()
   {
     return mLock;
@@ -99,34 +103,35 @@ public:
 private:
   ~nsMultiplexInputStream()
   {
   }
 
   nsresult
   AsyncWaitInternal();
 
-  // This method updates mSeekableStreams, mIPCSerializableStreams,
-  // mCloneableStreams and mAsyncInputStreams values.
+  // This method updates mSeekableStreams, mTellableStreams,
+  // mIPCSerializableStreams, mCloneableStreams and mAsyncInputStreams values.
   void UpdateQIMap(StreamData& aStream, int32_t aCount);
 
   struct MOZ_STACK_CLASS ReadSegmentsState
   {
     nsCOMPtr<nsIInputStream> mThisStream;
     uint32_t mOffset;
     nsWriteSegmentFun mWriter;
     void* mClosure;
     bool mDone;
   };
 
   static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
                             const char* aFromRawSegment, uint32_t aToOffset,
                             uint32_t aCount, uint32_t* aWriteCount);
 
   bool IsSeekable() const;
+  bool IsTellable() const;
   bool IsIPCSerializable() const;
   bool IsCloneable() const;
   bool IsAsyncInputStream() const;
   bool IsInputStreamLength() const;
   bool IsAsyncInputStreamLength() const;
 
   Mutex mLock; // Protects access to all data members.
 
@@ -140,16 +145,17 @@ private:
   uint32_t mAsyncWaitRequestedCount;
   nsCOMPtr<nsIEventTarget> mAsyncWaitEventTarget;
   nsCOMPtr<nsIInputStreamLengthCallback> mAsyncWaitLengthCallback;
 
   class AsyncWaitLengthHelper;
   RefPtr<AsyncWaitLengthHelper> mAsyncWaitLengthHelper;
 
   uint32_t mSeekableStreams;
+  uint32_t mTellableStreams;
   uint32_t mIPCSerializableStreams;
   uint32_t mCloneableStreams;
   uint32_t mAsyncInputStreams;
   uint32_t mInputStreamLengths;
   uint32_t mAsyncInputStreamLengths;
 };
 
 NS_IMPL_ADDREF(nsMultiplexInputStream)
@@ -157,16 +163,17 @@ NS_IMPL_RELEASE(nsMultiplexInputStream)
 
 NS_IMPL_CLASSINFO(nsMultiplexInputStream, nullptr, nsIClassInfo::THREADSAFE,
                   NS_MULTIPLEXINPUTSTREAM_CID)
 
 NS_INTERFACE_MAP_BEGIN(nsMultiplexInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIMultiplexInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, IsSeekable())
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, IsTellable())
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
                                      IsIPCSerializable())
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
                                      IsCloneable())
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream,
                                      IsAsyncInputStream())
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
                                      IsAsyncInputStream())
@@ -176,17 +183,18 @@ NS_INTERFACE_MAP_BEGIN(nsMultiplexInputS
                                      IsAsyncInputStreamLength())
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMultiplexInputStream)
   NS_IMPL_QUERY_CLASSINFO(nsMultiplexInputStream)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CI_INTERFACE_GETTER(nsMultiplexInputStream,
                             nsIMultiplexInputStream,
                             nsIInputStream,
-                            nsISeekableStream)
+                            nsISeekableStream,
+                            nsITellableStream)
 
 static nsresult
 AvailableMaybeSeek(nsMultiplexInputStream::StreamData& aStream,
                    uint64_t* aResult)
 {
   nsresult rv = aStream.mStream->Available(aResult);
   if (rv == NS_BASE_STREAM_CLOSED) {
     // Blindly seek to the current position if Available() returns
@@ -200,40 +208,42 @@ AvailableMaybeSeek(nsMultiplexInputStrea
         rv = aStream.mStream->Available(aResult);
       }
     }
   }
   return rv;
 }
 
 static nsresult
-TellMaybeSeek(nsISeekableStream* aSeekable, int64_t* aResult)
+TellMaybeSeek(nsITellableStream* aTellable, nsISeekableStream* aSeekable,
+              int64_t* aResult)
 {
-  nsresult rv = aSeekable->Tell(aResult);
-  if (rv == NS_BASE_STREAM_CLOSED) {
+  nsresult rv = aTellable->Tell(aResult);
+  if (rv == NS_BASE_STREAM_CLOSED && aSeekable) {
     // Blindly seek to the current position if Tell() returns
     // NS_BASE_STREAM_CLOSED.
     // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag,
     // Seek() could reopen the file if REOPEN_ON_REWIND flag is set.
     nsresult rvSeek = aSeekable->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
     if (NS_SUCCEEDED(rvSeek)) {
-      rv = aSeekable->Tell(aResult);
+      rv = aTellable->Tell(aResult);
     }
   }
   return rv;
 }
 
 nsMultiplexInputStream::nsMultiplexInputStream()
   : mLock("nsMultiplexInputStream lock")
   , mCurrentStream(0)
   , mStartedReadingCurrent(false)
   , mStatus(NS_OK)
   , mAsyncWaitFlags(0)
   , mAsyncWaitRequestedCount(0)
   , mSeekableStreams(0)
+  , mTellableStreams(0)
   , mIPCSerializableStreams(0)
   , mCloneableStreams(0)
   , mAsyncInputStreams(0)
   , mInputStreamLengths(0)
   , mAsyncInputStreamLengths(0)
 {}
 
 NS_IMETHODIMP
@@ -574,16 +584,18 @@ nsMultiplexInputStream::Seek(int32_t aWh
       mCurrentStream = 0;
     }
     for (uint32_t i = 0; i < mStreams.Length(); ++i) {
       nsCOMPtr<nsISeekableStream> stream = mStreams[i].mSeekableStream;
       if (!stream) {
         return NS_ERROR_FAILURE;
       }
 
+      MOZ_ASSERT(mStreams[i].mTellableStream);
+
       // See if all remaining streams should be rewound
       if (remaining == 0) {
         if (i < oldCurrentStream ||
             (i == oldCurrentStream && oldStartedReadingCurrent)) {
           rv = stream->Seek(NS_SEEK_SET, 0);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
@@ -594,17 +606,17 @@ nsMultiplexInputStream::Seek(int32_t aWh
       }
 
       // Get position in current stream
       int64_t streamPos;
       if (i > oldCurrentStream ||
           (i == oldCurrentStream && !oldStartedReadingCurrent)) {
         streamPos = 0;
       } else {
-        rv = TellMaybeSeek(stream, &streamPos);
+        rv = TellMaybeSeek(mStreams[i].mTellableStream, stream, &streamPos);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
       }
 
       // See if we need to seek current stream forward or backward
       if (remaining < streamPos) {
         rv = stream->Seek(NS_SEEK_SET, remaining);
@@ -677,17 +689,18 @@ nsMultiplexInputStream::Seek(int32_t aWh
 
     return NS_OK;
   }
 
   if (aWhence == NS_SEEK_CUR && aOffset < 0) {
     int64_t remaining = -aOffset;
     for (uint32_t i = mCurrentStream; remaining && i != (uint32_t)-1; --i) {
       int64_t pos;
-      rv = TellMaybeSeek(mStreams[i].mSeekableStream, &pos);
+      rv = TellMaybeSeek(mStreams[i].mTellableStream,
+                         mStreams[i].mSeekableStream, &pos);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       int64_t seek = XPCOM_MIN(pos, remaining);
 
       rv = mStreams[i].mSeekableStream->Seek(NS_SEEK_CUR, -seek);
       if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -756,17 +769,17 @@ nsMultiplexInputStream::Seek(int32_t aWh
         remaining = 0;
       } else if (DeprecatedAbs(remaining) > streamPos) {
         if (i > oldCurrentStream ||
             (i == oldCurrentStream && !oldStartedReadingCurrent)) {
           // We're already at start so no need to seek this stream
           remaining += streamPos;
         } else {
           int64_t avail;
-          rv = TellMaybeSeek(stream, &avail);
+          rv = TellMaybeSeek(mStreams[i].mTellableStream, stream, &avail);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
 
           int64_t newPos = streamPos + XPCOM_MIN(avail, DeprecatedAbs(remaining));
 
           rv = stream->Seek(NS_SEEK_END, -newPos);
           if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -800,22 +813,23 @@ nsMultiplexInputStream::Tell(int64_t* aR
     return mStatus;
   }
 
   nsresult rv;
   int64_t ret64 = 0;
   uint32_t i, last;
   last = mStartedReadingCurrent ? mCurrentStream + 1 : mCurrentStream;
   for (i = 0; i < last; ++i) {
-    if (NS_WARN_IF(!mStreams[i].mSeekableStream)) {
+    if (NS_WARN_IF(!mStreams[i].mTellableStream)) {
       return NS_ERROR_NO_INTERFACE;
     }
 
     int64_t pos;
-    rv = TellMaybeSeek(mStreams[i].mSeekableStream, &pos);
+    rv = TellMaybeSeek(mStreams[i].mTellableStream, mStreams[i].mSeekableStream,
+                       &pos);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     ret64 += pos;
   }
   *aResult =  ret64;
 
   return NS_OK;
@@ -1510,32 +1524,39 @@ nsMultiplexInputStream::AsyncWaitComplet
   }
 
 void
 nsMultiplexInputStream::UpdateQIMap(StreamData& aStream, int32_t aCount)
 {
   MOZ_ASSERT(aCount == -1 || aCount == 1);
 
   MAYBE_UPDATE_VALUE_REAL(mSeekableStreams, aStream.mSeekableStream)
+  MAYBE_UPDATE_VALUE_REAL(mTellableStreams, aStream.mTellableStream)
   MAYBE_UPDATE_VALUE(mIPCSerializableStreams, nsIIPCSerializableInputStream)
   MAYBE_UPDATE_VALUE(mCloneableStreams, nsICloneableInputStream)
   MAYBE_UPDATE_VALUE_REAL(mAsyncInputStreams, aStream.mAsyncStream)
   MAYBE_UPDATE_VALUE(mInputStreamLengths, nsIInputStreamLength)
   MAYBE_UPDATE_VALUE(mAsyncInputStreamLengths, nsIAsyncInputStreamLength)
 }
 
 #undef MAYBE_UPDATE_VALUE
 
 bool
 nsMultiplexInputStream::IsSeekable() const
 {
   return mStreams.Length() == mSeekableStreams;
 }
 
 bool
+nsMultiplexInputStream::IsTellable() const
+{
+  return mStreams.Length() == mTellableStreams;
+}
+
+bool
 nsMultiplexInputStream::IsIPCSerializable() const
 {
   return mStreams.Length() == mIPCSerializableStreams;
 }
 
 bool
 nsMultiplexInputStream::IsCloneable() const
 {
--- a/xpcom/io/nsPipe3.cpp
+++ b/xpcom/io/nsPipe3.cpp
@@ -7,17 +7,17 @@
 #include <algorithm>
 #include "mozilla/Attributes.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "nsIBufferedStreams.h"
 #include "nsICloneableInputStream.h"
 #include "nsIPipe.h"
 #include "nsIEventTarget.h"
-#include "nsISeekableStream.h"
+#include "nsITellableStream.h"
 #include "mozilla/RefPtr.h"
 #include "nsSegmentedBuffer.h"
 #include "nsStreamUtils.h"
 #include "nsCOMPtr.h"
 #include "nsCRT.h"
 #include "mozilla/Logging.h"
 #include "nsIClassInfoImpl.h"
 #include "nsAlgorithm.h"
@@ -137,30 +137,30 @@ struct nsPipeReadState
   bool     mNeedDrain;
 };
 
 //-----------------------------------------------------------------------------
 
 // an input end of a pipe (maintained as a list of refs within the pipe)
 class nsPipeInputStream final
   : public nsIAsyncInputStream
-  , public nsISeekableStream
+  , public nsITellableStream
   , public nsISearchableInputStream
   , public nsICloneableInputStream
   , public nsIClassInfo
   , public nsIBufferedInputStream
 {
 public:
   // Pipe input streams preserve their refcount changes when record/replaying,
   // as otherwise the thread which destroys the stream may vary between
   // recording and replaying.
   NS_DECL_THREADSAFE_ISUPPORTS_WITH_RECORDING(recordreplay::Behavior::Preserve)
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
-  NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
   NS_DECL_NSISEARCHABLEINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
   NS_DECL_NSICLASSINFO
   NS_DECL_NSIBUFFEREDINPUTSTREAM
 
   explicit nsPipeInputStream(nsPipe* aPipe)
     : mPipe(aPipe)
     , mLogicalOffset(0)
@@ -1245,32 +1245,32 @@ nsPipeEvents::~nsPipeEvents()
 //-----------------------------------------------------------------------------
 
 NS_IMPL_ADDREF(nsPipeInputStream);
 NS_IMPL_RELEASE(nsPipeInputStream);
 
 NS_INTERFACE_TABLE_HEAD(nsPipeInputStream)
   NS_INTERFACE_TABLE_BEGIN
     NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIAsyncInputStream)
-    NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISeekableStream)
+    NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsITellableStream)
     NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISearchableInputStream)
     NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsICloneableInputStream)
     NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIBufferedInputStream)
     NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIClassInfo)
     NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsIInputStream,
                                        nsIAsyncInputStream)
     NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsISupports,
                                        nsIAsyncInputStream)
   NS_INTERFACE_TABLE_END
 NS_INTERFACE_TABLE_TAIL
 
 NS_IMPL_CI_INTERFACE_GETTER(nsPipeInputStream,
                             nsIInputStream,
                             nsIAsyncInputStream,
-                            nsISeekableStream,
+                            nsITellableStream,
                             nsISearchableInputStream,
                             nsICloneableInputStream,
                             nsIBufferedInputStream)
 
 NS_IMPL_THREADSAFE_CI(nsPipeInputStream)
 
 NS_IMETHODIMP
 nsPipeInputStream::Init(nsIInputStream*, uint32_t)
@@ -1521,42 +1521,29 @@ nsPipeInputStream::AsyncWait(nsIInputStr
       mCallback = aCallback;
       mCallbackFlags = aFlags;
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsPipeInputStream::Seek(int32_t aWhence, int64_t aOffset)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
 nsPipeInputStream::Tell(int64_t* aOffset)
 {
   ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
 
   // return error if closed
   if (!mReadState.mAvailable && NS_FAILED(Status(mon))) {
     return Status(mon);
   }
 
   *aOffset = mLogicalOffset;
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsPipeInputStream::SetEOF()
-{
-  MOZ_ASSERT_UNREACHABLE("nsPipeInputStream::SetEOF");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
 static bool strings_equal(bool aIgnoreCase,
                           const char* aS1, const char* aS2, uint32_t aLen)
 {
   return aIgnoreCase
     ? !nsCRT::strncasecmp(aS1, aS2, aLen) : !strncmp(aS1, aS2, aLen);
 }
 
 NS_IMETHODIMP
--- a/xpcom/io/nsStorageStream.cpp
+++ b/xpcom/io/nsStorageStream.cpp
@@ -344,16 +344,17 @@ public:
       mSegmentSize(aSegmentSize), mLogicalCursor(0),
       mStatus(NS_OK)
   {
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
 
 private:
   ~nsStorageInputStream()
   {
   }
 
@@ -379,16 +380,17 @@ private:
   {
     return aPosition & (mSegmentSize - 1);
   }
 };
 
 NS_IMPL_ISUPPORTS(nsStorageInputStream,
                   nsIInputStream,
                   nsISeekableStream,
+                  nsITellableStream,
                   nsIIPCSerializableInputStream,
                   nsICloneableInputStream)
 
 NS_IMETHODIMP
 nsStorageStream::NewInputStream(int32_t aStartingOffset,
                                 nsIInputStream** aInputStream)
 {
   if (NS_WARN_IF(!mSegmentedBuffer)) {
--- a/xpcom/io/nsStringStream.cpp
+++ b/xpcom/io/nsStringStream.cpp
@@ -39,16 +39,17 @@ class nsStringInputStream final
   , public nsIIPCSerializableInputStream
   , public nsICloneableInputStream
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSISTRINGINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
+  NS_DECL_NSITELLABLESTREAM
   NS_DECL_NSISUPPORTSPRIMITIVE
   NS_DECL_NSISUPPORTSCSTRING
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
 
   nsStringInputStream()
     : mOffset(0)
   {
@@ -104,23 +105,25 @@ NS_IMPL_RELEASE(nsStringInputStream)
 
 NS_IMPL_CLASSINFO(nsStringInputStream, nullptr, nsIClassInfo::THREADSAFE,
                   NS_STRINGINPUTSTREAM_CID)
 NS_IMPL_QUERY_INTERFACE_CI(nsStringInputStream,
                            nsIStringInputStream,
                            nsIInputStream,
                            nsISupportsCString,
                            nsISeekableStream,
+                           nsITellableStream,
                            nsIIPCSerializableInputStream,
                            nsICloneableInputStream)
 NS_IMPL_CI_INTERFACE_GETTER(nsStringInputStream,
                             nsIStringInputStream,
                             nsIInputStream,
                             nsISupportsCString,
                             nsISeekableStream,
+                            nsITellableStream,
                             nsICloneableInputStream)
 
 /////////
 // nsISupportsCString implementation
 /////////
 
 NS_IMETHODIMP
 nsStringInputStream::GetType(uint16_t* aType)
@@ -318,37 +321,41 @@ nsStringInputStream::Seek(int32_t aWhenc
     return NS_ERROR_INVALID_ARG;
   }
 
   mOffset = (uint32_t)newPos;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsStringInputStream::SetEOF()
+{
+  if (Closed()) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  mOffset = Length();
+  return NS_OK;
+}
+
+/////////
+// nsITellableStream implementation
+/////////
+
+NS_IMETHODIMP
 nsStringInputStream::Tell(int64_t* aOutWhere)
 {
   if (Closed()) {
     return NS_BASE_STREAM_CLOSED;
   }
 
   *aOutWhere = mOffset;
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsStringInputStream::SetEOF()
-{
-  if (Closed()) {
-    return NS_BASE_STREAM_CLOSED;
-  }
-
-  mOffset = Length();
-  return NS_OK;
-}
-
 /////////
 // nsIIPCSerializableInputStream implementation
 /////////
 
 void
 nsStringInputStream::Serialize(InputStreamParams& aParams,
                                FileDescriptorArray& /* aFDs */)
 {
--- a/xpcom/io/nsStringStream.h
+++ b/xpcom/io/nsStringStream.h
@@ -11,30 +11,31 @@
 #include "nsString.h"
 #include "nsMemory.h"
 
 /**
  * Implements:
  *   nsIStringInputStream
  *   nsIInputStream
  *   nsISeekableStream
+ *   nsITellableStream
  *   nsISupportsCString
  */
 #define NS_STRINGINPUTSTREAM_CONTRACTID "@mozilla.org/io/string-input-stream;1"
 #define NS_STRINGINPUTSTREAM_CID                     \
 { /* 0abb0835-5000-4790-af28-61b3ba17c295 */         \
     0x0abb0835,                                      \
     0x5000,                                          \
     0x4790,                                          \
     {0xaf, 0x28, 0x61, 0xb3, 0xba, 0x17, 0xc2, 0x95} \
 }
 
 /**
  * Factory method to get an nsInputStream from a byte buffer.  Result will
- * implement nsIStringInputStream and nsISeekableStream.
+ * implement nsIStringInputStream, nsITellableStream and nsISeekableStream.
  *
  * If aAssignment is NS_ASSIGNMENT_COPY, then the resulting stream holds a copy
  * of the given buffer (aStringToRead), and the caller is free to discard
  * aStringToRead after this function returns.
  *
  * If aAssignment is NS_ASSIGNMENT_DEPEND, then the resulting stream refers
  * directly to the given buffer (aStringToRead), so the caller must ensure that
  * the buffer remains valid for the lifetime of the stream object.  Use with
@@ -49,17 +50,17 @@
  */
 extern nsresult
 NS_NewByteInputStream(nsIInputStream** aStreamResult,
                       const char* aStringToRead, int32_t aLength = -1,
                       nsAssignmentType aAssignment = NS_ASSIGNMENT_DEPEND);
 
 /**
  * Factory method to get an nsInputStream from an nsACString.  Result will
- * implement nsIStringInputStream and nsISeekableStream.
+ * implement nsIStringInputStream, nsTellableStream and nsISeekableStream.
  */
 extern nsresult
 NS_NewCStringInputStream(nsIInputStream** aStreamResult,
                          const nsACString& aStringToRead);
 extern nsresult
 NS_NewCStringInputStream(nsIInputStream** aStreamResult,
                          nsCString&& aStringToRead);
 
--- a/xpcom/tests/gtest/TestPipes.cpp
+++ b/xpcom/tests/gtest/TestPipes.cpp
@@ -14,17 +14,17 @@
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIBufferedStreams.h"
 #include "nsIClassInfo.h"
 #include "nsICloneableInputStream.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsIPipe.h"
-#include "nsISeekableStream.h"
+#include "nsITellableStream.h"
 #include "nsIThread.h"
 #include "nsIRunnable.h"
 #include "nsStreamUtils.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "prinrval.h"
 
 using namespace mozilla;
@@ -1073,17 +1073,17 @@ TEST(Pipes, Interfaces)
   nsCOMPtr<nsIOutputStream> writer;
 
   nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer));
   ASSERT_TRUE(NS_SUCCEEDED(rv));
 
   nsCOMPtr<nsIAsyncInputStream> readerType1 = do_QueryInterface(reader);
   ASSERT_TRUE(readerType1);
 
-  nsCOMPtr<nsISeekableStream> readerType2 = do_QueryInterface(reader);
+  nsCOMPtr<nsITellableStream> readerType2 = do_QueryInterface(reader);
   ASSERT_TRUE(readerType2);
 
   nsCOMPtr<nsISearchableInputStream> readerType3 = do_QueryInterface(reader);
   ASSERT_TRUE(readerType3);
 
   nsCOMPtr<nsICloneableInputStream> readerType4 = do_QueryInterface(reader);
   ASSERT_TRUE(readerType4);