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 500377 2da6504c901e9474566aa1663d907bd58bed9edb
parent 500376 0162bb225b88cfda9b702d88464d48fbf1f372ee
child 500378 4a22c1051967e4edef65c5c68305e72618eb2c0d
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1496581, 1494176
milestone64.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 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);