Bug 1448476 - Cache entry corruption after writing the alternate data. r=honzab
authorMichal Novotny <michal.novotny>
Wed, 25 Apr 2018 07:01:00 +0300
changeset 416053 db2555234bfcc8c415f4307583031ebc62545e13
parent 416052 f212d89048ecdd6f15006f368322a2d2e156432b
child 416054 f846639066aa8f7e5aff7fa363a450885fc887b4
push id33915
push userncsoregi@mozilla.com
push dateFri, 27 Apr 2018 21:53:44 +0000
treeherdermozilla-central@8b2c1fc3d6c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs1448476
milestone61.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 1448476 - Cache entry corruption after writing the alternate data. r=honzab When writing to alt-data output stream fails for whatever reason, we now try to truncate alternative data and keep the original data instead of dooming the whole entry. The patch also changes how is the predicted size passed to the cache. Instead of a dedicated method it's now an argument of openOutputStream and openAlternativeOutputStream methods which fail in case the entry would exceed the allowed limit.
browser/base/content/test/sanitize/browser_sanitizeDialog.js
browser/components/places/tests/unit/test_clearHistory_shutdown.js
dom/script/ScriptLoader.cpp
netwerk/base/nsICacheInfoChannel.idl
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheEntry.h
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileChunk.cpp
netwerk/cache2/CacheFileOutputStream.cpp
netwerk/cache2/OldWrappers.cpp
netwerk/cache2/OldWrappers.h
netwerk/cache2/nsICacheEntry.idl
netwerk/ipc/NeckoChild.cpp
netwerk/ipc/NeckoChild.h
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/InterceptedHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
netwerk/test/unit/head_cache2.js
netwerk/test/unit/test_alt-data_cross_process.js
netwerk/test/unit/test_alt-data_overwrite.js
netwerk/test/unit/test_alt-data_simple.js
netwerk/test/unit/test_alt-data_stream.js
netwerk/test/unit/test_alt-data_too_big.js
netwerk/test/unit/test_bug248970_cache.js
netwerk/test/unit/test_bug482601.js
netwerk/test/unit/test_bug654926.js
netwerk/test/unit/test_bug654926_doom_and_read.js
netwerk/test/unit/test_bug654926_test_seek.js
netwerk/test/unit/test_cache-entry-id.js
netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
netwerk/test/unit/test_compressappend.js
netwerk/test/unit/test_doomentry.js
netwerk/test/unit/test_http2.js
netwerk/test/unit/xpcshell.ini
netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
--- a/browser/base/content/test/sanitize/browser_sanitizeDialog.js
+++ b/browser/base/content/test/sanitize/browser_sanitizeDialog.js
@@ -586,17 +586,17 @@ add_task(async function test_offline_cac
     // Offline cache visit happens synchronously, since it's forwarded to the old code
     is(size, 0, "offline application cache entries evicted");
   };
 
   var cacheListener = {
     onCacheEntryCheck() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
     onCacheEntryAvailable(entry, isnew, unused, status) {
       is(status, Cr.NS_OK);
-      var stream = entry.openOutputStream(0);
+      var stream = entry.openOutputStream(0, -1);
       var content = "content";
       stream.write(content, content.length);
       stream.close();
       entry.close();
       wh.open();
     }
   };
 
--- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -132,17 +132,17 @@ function storeCache(aURL, aContent) {
       onCacheEntryCheck(entry, appcache) {
         return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
       },
 
       onCacheEntryAvailable(entry, isnew, appcache, status) {
         Assert.equal(status, Cr.NS_OK);
 
         entry.setMetaDataElement("servertype", "0");
-        var os = entry.openOutputStream(0);
+        var os = entry.openOutputStream(0, -1);
 
         var written = os.write(aContent, aContent.length);
         if (written != aContent.length) {
           do_throw("os.write has not written all data!\n" +
                    "  Expected: " + written + "\n" +
                    "  Actual: " + aContent.length + "\n");
         }
         os.close();
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2484,16 +2484,17 @@ ScriptLoader::EncodeRequestBytecode(JSCo
     return;
   }
 
   // Open the output stream to the cache entry alternate data storage. This
   // might fail if the stream is already open by another request, in which
   // case, we just ignore the current one.
   nsCOMPtr<nsIOutputStream> output;
   rv = aRequest->mCacheInfo->OpenAlternativeOutputStream(nsContentUtils::JSBytecodeMimeType(),
+                                                         aRequest->mScriptBytecode.length(),
                                                          getter_AddRefs(output));
   if (NS_FAILED(rv)) {
     LOG(("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output = %p)",
          aRequest, unsigned(rv), output.get()));
     AccumulateCategorical(LABELS_DOM_SCRIPT_ENCODING_STATUS::OpenFailure);
     return;
   }
   MOZ_ASSERT(output);
--- a/netwerk/base/nsICacheInfoChannel.idl
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -101,11 +101,19 @@ interface nsICacheInfoChannel : nsISuppo
 
   /**
    * Opens and returns an output stream that a consumer may use to save an
    * alternate representation of the data.
    * Must be called after the OnStopRequest that delivered the real data.
    * The consumer may choose to replace the saved alt representation.
    * Opening the output stream will fail if there are any open input streams
    * reading the already saved alt representation.
+   *
+   * @param type
+   *        type of the alternative data representation
+   * @param predictedSize
+   *        Predicted size of the data that will be written. It's used to decide
+   *        whether the resulting entry would exceed size limit, in which case
+   *        an error is thrown. If the size isn't known in advance, -1 should be
+   *        passed.
    */
-  nsIOutputStream openAlternativeOutputStream(in ACString type);
+  nsIOutputStream openAlternativeOutputStream(in ACString type, in long long predictedSize);
 };
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -232,17 +232,16 @@ CacheEntry::CacheEntry(const nsACString&
 , mSecurityInfoLoaded(false)
 , mPreventCallbacks(false)
 , mHasData(false)
 , mPinned(aPin)
 , mPinningKnown(false)
 , mState(NOTLOADED)
 , mRegistration(NEVERREGISTERED)
 , mWriter(nullptr)
-, mPredictedDataSize(0)
 , mUseCount(0)
 , mCacheEntryId(GetNextId())
 {
   LOG(("CacheEntry::CacheEntry [this=%p]", this));
 
   mService = CacheStorageService::Self();
 
   CacheStorageService::Self()->RecordMemoryOnlyEntry(
@@ -1230,26 +1229,31 @@ nsresult CacheEntry::OpenInputStreamInte
     rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   stream.forget(_retval);
   return NS_OK;
 }
 
-nsresult CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval)
+nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize, nsIOutputStream * *_retval)
 {
   LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
 
   nsresult rv;
 
   mozilla::MutexAutoLock lock(mLock);
 
   MOZ_ASSERT(mState > EMPTY);
 
+  if (mFile->EntryWouldExceedLimit(0, predictedSize, false)) {
+    LOG(("  entry would exceed size limit"));
+    return NS_ERROR_FILE_TOO_BIG;
+  }
+
   if (mOutputStream && !mIsDoomed) {
     LOG(("  giving phantom output stream"));
     mOutputStream.forget(_retval);
   }
   else {
     rv = OpenOutputStreamInternal(offset, _retval);
     if (NS_FAILED(rv)) return rv;
   }
@@ -1259,30 +1263,35 @@ nsresult CacheEntry::OpenOutputStream(in
     mState = READY;
 
   // Invoke any pending readers now.
   InvokeCallbacks();
 
   return NS_OK;
 }
 
-nsresult CacheEntry::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+nsresult CacheEntry::OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval)
 {
   LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
        PromiseFlatCString(type).get()));
 
   nsresult rv;
 
   mozilla::MutexAutoLock lock(mLock);
 
   if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
     LOG(("  entry not in state to write alt-data"));
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) {
+    LOG(("  entry would exceed size limit"));
+    return NS_ERROR_FILE_TOO_BIG;
+  }
+
   nsCOMPtr<nsIOutputStream> stream;
   rv = mFile->OpenAlternativeOutputStream(nullptr,
                                           PromiseFlatCString(type).get(),
                                           getter_AddRefs(stream));
   NS_ENSURE_SUCCESS(rv, rv);
 
   stream.swap(*_retval);
   return NS_OK;
@@ -1328,35 +1337,16 @@ nsresult CacheEntry::OpenOutputStreamInt
 
   // Prevent opening output stream again.
   mHasData = true;
 
   stream.swap(*_retval);
   return NS_OK;
 }
 
-nsresult CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
-{
-  *aPredictedDataSize = mPredictedDataSize;
-  return NS_OK;
-}
-nsresult CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
-{
-  mPredictedDataSize = aPredictedDataSize;
-
-  if (!mSkipSizeCheck && CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
-    LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
-    AsyncDoom(nullptr);
-
-    return NS_ERROR_FILE_TOO_BIG;
-  }
-
-  return NS_OK;
-}
-
 nsresult CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
 {
   {
     mozilla::MutexAutoLock lock(mLock);
     if (mSecurityInfoLoaded) {
       NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
       return NS_OK;
     }
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -74,33 +74,31 @@ public:
   nsresult GetExpirationTime(uint32_t *aExpirationTime);
   nsresult SetExpirationTime(uint32_t expirationTime);
   nsresult GetOnStartTime(uint64_t *aOnStartTime);
   nsresult GetOnStopTime(uint64_t *aOnStopTime);
   nsresult SetNetworkTimes(uint64_t onStartTime, uint64_t onStopTime);
   nsresult ForceValidFor(uint32_t aSecondsToTheFuture);
   nsresult GetIsForcedValid(bool *aIsForcedValid);
   nsresult OpenInputStream(int64_t offset, nsIInputStream * *_retval);
-  nsresult OpenOutputStream(int64_t offset, nsIOutputStream * *_retval);
-  nsresult GetPredictedDataSize(int64_t *aPredictedDataSize);
-  nsresult SetPredictedDataSize(int64_t aPredictedDataSize);
+  nsresult OpenOutputStream(int64_t offset, int64_t predictedSize, nsIOutputStream * *_retval);
   nsresult GetSecurityInfo(nsISupports * *aSecurityInfo);
   nsresult SetSecurityInfo(nsISupports *aSecurityInfo);
   nsresult GetStorageDataSize(uint32_t *aStorageDataSize);
   nsresult AsyncDoom(nsICacheEntryDoomCallback *listener);
   nsresult GetMetaDataElement(const char * key, char * *_retval);
   nsresult SetMetaDataElement(const char * key, const char * value);
   nsresult VisitMetaData(nsICacheEntryMetaDataVisitor *visitor);
   nsresult MetaDataReady(void);
   nsresult SetValid(void);
   nsresult GetDiskStorageSizeInKB(uint32_t *aDiskStorageSizeInKB);
   nsresult Recreate(bool aMemoryOnly, nsICacheEntry * *_retval);
   nsresult GetDataSize(int64_t *aDataSize);
   nsresult GetAltDataSize(int64_t *aAltDataSize);
-  nsresult OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval);
+  nsresult OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval);
   nsresult OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval);
   nsresult GetLoadContextInfo(nsILoadContextInfo * *aLoadContextInfo);
   nsresult Close(void);
   nsresult MarkValid(void);
   nsresult MaybeMarkValid(void);
   nsresult HasWriteAccess(bool aWriteAllowed, bool *_retval);
 
 public:
@@ -414,17 +412,16 @@ private:
     Ops() : mFlags(0) { }
     uint32_t Grab() { uint32_t flags = mFlags; mFlags = 0; return flags; }
     bool Set(uint32_t aFlags) { if (mFlags & aFlags) return false; mFlags |= aFlags; return true; }
   private:
     uint32_t mFlags;
   } mBackgroundOperations;
 
   nsCOMPtr<nsISupports> mSecurityInfo;
-  int64_t mPredictedDataSize;
   mozilla::TimeStamp mLoadStart;
   uint32_t mUseCount;
 
   const uint64_t mCacheEntryId;
 };
 
 
 class CacheEntryHandle final : public nsICacheEntry
@@ -445,33 +442,31 @@ public:
   NS_IMETHOD GetExpirationTime(uint32_t *aExpirationTime) override { return mEntry->GetExpirationTime(aExpirationTime); }
   NS_IMETHOD SetExpirationTime(uint32_t expirationTime) override { return mEntry->SetExpirationTime(expirationTime); }
   NS_IMETHOD GetOnStartTime(uint64_t *aOnStartTime) override { return mEntry->GetOnStartTime(aOnStartTime); }
   NS_IMETHOD GetOnStopTime(uint64_t *aOnStopTime) override { return mEntry->GetOnStopTime(aOnStopTime); }
   NS_IMETHOD SetNetworkTimes(uint64_t onStartTime, uint64_t onStopTime) override { return mEntry->SetNetworkTimes(onStartTime, onStopTime); }
   NS_IMETHOD ForceValidFor(uint32_t aSecondsToTheFuture) override { return mEntry->ForceValidFor(aSecondsToTheFuture); }
   NS_IMETHOD GetIsForcedValid(bool *aIsForcedValid) override { return mEntry->GetIsForcedValid(aIsForcedValid); }
   NS_IMETHOD OpenInputStream(int64_t offset, nsIInputStream * *_retval) override { return mEntry->OpenInputStream(offset, _retval); }
-  NS_IMETHOD OpenOutputStream(int64_t offset, nsIOutputStream * *_retval) override { return mEntry->OpenOutputStream(offset, _retval); }
-  NS_IMETHOD GetPredictedDataSize(int64_t *aPredictedDataSize) override { return mEntry->GetPredictedDataSize(aPredictedDataSize); }
-  NS_IMETHOD SetPredictedDataSize(int64_t aPredictedDataSize) override { return mEntry->SetPredictedDataSize(aPredictedDataSize); }
+  NS_IMETHOD OpenOutputStream(int64_t offset, int64_t predictedSize, nsIOutputStream * *_retval) override { return mEntry->OpenOutputStream(offset, predictedSize, _retval); }
   NS_IMETHOD GetSecurityInfo(nsISupports * *aSecurityInfo) override { return mEntry->GetSecurityInfo(aSecurityInfo); }
   NS_IMETHOD SetSecurityInfo(nsISupports *aSecurityInfo) override { return mEntry->SetSecurityInfo(aSecurityInfo); }
   NS_IMETHOD GetStorageDataSize(uint32_t *aStorageDataSize) override { return mEntry->GetStorageDataSize(aStorageDataSize); }
   NS_IMETHOD AsyncDoom(nsICacheEntryDoomCallback *listener) override { return mEntry->AsyncDoom(listener); }
   NS_IMETHOD GetMetaDataElement(const char * key, char * *_retval) override { return mEntry->GetMetaDataElement(key, _retval); }
   NS_IMETHOD SetMetaDataElement(const char * key, const char * value) override { return mEntry->SetMetaDataElement(key, value); }
   NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor *visitor) override { return mEntry->VisitMetaData(visitor); }
   NS_IMETHOD MetaDataReady(void) override { return mEntry->MetaDataReady(); }
   NS_IMETHOD SetValid(void) override { return mEntry->SetValid(); }
   NS_IMETHOD GetDiskStorageSizeInKB(uint32_t *aDiskStorageSizeInKB) override { return mEntry->GetDiskStorageSizeInKB(aDiskStorageSizeInKB); }
   NS_IMETHOD Recreate(bool aMemoryOnly, nsICacheEntry * *_retval) override { return mEntry->Recreate(aMemoryOnly, _retval); }
   NS_IMETHOD GetDataSize(int64_t *aDataSize) override { return mEntry->GetDataSize(aDataSize); }
   NS_IMETHOD GetAltDataSize(int64_t *aAltDataSize) override { return mEntry->GetAltDataSize(aAltDataSize); }
-  NS_IMETHOD OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval) override { return mEntry->OpenAlternativeOutputStream(type, _retval); }
+  NS_IMETHOD OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval) override { return mEntry->OpenAlternativeOutputStream(type, predictedSize, _retval); }
   NS_IMETHOD OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval) override { return mEntry->OpenAlternativeInputStream(type, _retval); }
   NS_IMETHOD GetLoadContextInfo(nsILoadContextInfo * *aLoadContextInfo) override { return mEntry->GetLoadContextInfo(aLoadContextInfo); }
   NS_IMETHOD Close(void) override { return mEntry->Close(); }
   NS_IMETHOD MarkValid(void) override { return mEntry->MarkValid(); }
   NS_IMETHOD MaybeMarkValid(void) override { return mEntry->MaybeMarkValid(); }
   NS_IMETHOD HasWriteAccess(bool aWriteAllowed, bool *_retval) override { return mEntry->HasWriteAccess(aWriteAllowed, _retval); }
 
   // Specific implementation:
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -2057,16 +2057,23 @@ CacheFile::Truncate(int64_t aOffset)
       if (!mChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
         return NS_ERROR_UNEXPECTED;
       }
 
       LOG(("CacheFile::Truncate() - New last chunk %p got from preloader.",
            chunk.get()));
     }
 
+    rv = chunk->GetStatus();
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFile::Truncate() - New last chunk is failed [status=0x%08"
+           PRIx32 "]", static_cast<uint32_t>(rv)));
+      return rv;
+    }
+
     rv = chunk->Truncate(bytesInNewLastChunk);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // If the chunk is ready set the new hash now. If it's still being loaded
     // CacheChunk::Truncate() made the chunk dirty and the hash will be updated
     // in OnChunkWritten().
@@ -2141,16 +2148,18 @@ CacheFile::RemoveInput(CacheFileInputStr
   return NS_OK;
 }
 
 nsresult
 CacheFile::RemoveOutput(CacheFileOutputStream *aOutput, nsresult aStatus)
 {
   AssertOwnsLock();
 
+  nsresult rv;
+
   LOG(("CacheFile::RemoveOutput() [this=%p, output=%p, status=0x%08" PRIx32 "]", this,
        aOutput, static_cast<uint32_t>(aStatus)));
 
   if (mOutput != aOutput) {
     LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring"
          " call [this=%p]", this));
     return NS_OK;
   }
@@ -2162,17 +2171,43 @@ CacheFile::RemoveOutput(CacheFileOutputS
 
   if (!mMemoryOnly)
     WriteMetadataIfNeededLocked();
 
   // Make sure the CacheFile status is set to a failure when the output stream
   // is closed with a fatal error.  This way we propagate correctly and w/o any
   // windows the failure state of this entry to end consumers.
   if (NS_SUCCEEDED(mStatus) && NS_FAILED(aStatus) && aStatus != NS_BASE_STREAM_CLOSED) {
-    mStatus = aStatus;
+    if (aOutput->IsAlternativeData()) {
+      MOZ_ASSERT(mAltDataOffset != -1);
+      // If there is no alt-data input stream truncate only alt-data, otherwise
+      // doom the entry.
+      bool altDataInputExists = false;
+      for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+        if (mInputs[i]->IsAlternativeData()) {
+          altDataInputExists = true;
+          break;
+        }
+      }
+      if (altDataInputExists) {
+        SetError(aStatus);
+      } else {
+        rv = Truncate(mAltDataOffset);
+        if (NS_FAILED(rv)) {
+          LOG(("CacheFile::RemoveOutput() - Truncating alt-data failed "
+               "[rv=0x%08" PRIx32 "]", static_cast<uint32_t>(rv)));
+          SetError(aStatus);
+        } else {
+          SetAltMetadata(nullptr);
+          mAltDataOffset = -1;
+        }
+      }
+    } else {
+      SetError(aStatus);
+    }
   }
 
   // Notify close listener as the last action
   aOutput->NotifyCloseListener();
 
   Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_OUTPUT_STREAM_STATUS,
                         StatusToTelemetryEnum(aStatus));
 
@@ -2378,16 +2413,36 @@ CacheFile::IsWriteInProgress()
            mOpeningFile ||
            mOutput ||
            mChunks.Count();
 
   return result;
 }
 
 bool
+CacheFile::EntryWouldExceedLimit(int64_t aOffset, int64_t aSize, bool aIsAltData)
+{
+  if (mSkipSizeCheck || aSize < 0) {
+    return false;
+  }
+
+  int64_t totalSize = aOffset + aSize;
+  if (aIsAltData) {
+    totalSize += (mAltDataOffset == -1) ? mDataSize : mAltDataOffset;
+  }
+
+  if (CacheObserver::EntryIsTooBig(totalSize, !mMemoryOnly)) {
+    return true;
+  }
+
+  return false;
+}
+
+
+bool
 CacheFile::IsDirty()
 {
   return mDataIsDirty || mMetadata->IsDirty();
 }
 
 void
 CacheFile::WriteMetadataIfNeeded()
 {
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -113,16 +113,17 @@ public:
   // i.e. delivered to the consumer.
   nsresult OnFetched();
 
   bool DataSize(int64_t* aSize);
   void Key(nsACString& aKey) { aKey = mKey; }
   bool IsDoomed();
   bool IsPinned() const { return mPinned; }
   bool IsWriteInProgress();
+  bool EntryWouldExceedLimit(int64_t aOffset, int64_t aSize, bool aIsAltData);
 
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 private:
   friend class CacheFileIOManager;
   friend class CacheFileChunk;
--- a/netwerk/cache2/CacheFileChunk.cpp
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -775,18 +775,16 @@ CacheFileChunk::IsDirty() const
   AssertOwnsLock();
 
   return mIsDirty;
 }
 
 nsresult
 CacheFileChunk::GetStatus()
 {
-  AssertOwnsLock();
-
   return mStatus;
 }
 
 void
 CacheFileChunk::SetError(nsresult aStatus)
 {
   LOG(("CacheFileChunk::SetError() [this=%p, status=0x%08" PRIx32 "]",
        this, static_cast<uint32_t>(aStatus)));
--- a/netwerk/cache2/CacheFileOutputStream.cpp
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -94,32 +94,28 @@ CacheFileOutputStream::Write(const char 
   if (mClosed) {
     LOG(("CacheFileOutputStream::Write() - Stream is closed. [this=%p, "
          "status=0x%08" PRIx32"]", this, static_cast<uint32_t>(mStatus)));
 
     return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
   }
 
   if (!mFile->mSkipSizeCheck && CacheObserver::EntryIsTooBig(mPos + aCount, !mFile->mMemoryOnly)) {
-    LOG(("CacheFileOutputStream::Write() - Entry is too big, failing and "
-         "dooming the entry. [this=%p]", this));
+    LOG(("CacheFileOutputStream::Write() - Entry is too big. [this=%p]", this));
 
-    mFile->DoomLocked(nullptr);
     CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
     return NS_ERROR_FILE_TOO_BIG;
   }
 
   // We use 64-bit offset when accessing the file, unfortunately we use 32-bit
   // metadata offset, so we cannot handle data bigger than 4GB.
   if (mPos + aCount > PR_UINT32_MAX) {
-    LOG(("CacheFileOutputStream::Write() - Entry's size exceeds 4GB while it "
-         "isn't too big according to CacheObserver::EntryIsTooBig(). Failing "
-         "and dooming the entry. [this=%p]", this));
+    LOG(("CacheFileOutputStream::Write() - Entry's size exceeds 4GB. [this=%p]",
+         this));
 
-    mFile->DoomLocked(nullptr);
     CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
     return NS_ERROR_FILE_TOO_BIG;
   }
 
   *_retval = aCount;
 
   while (aCount) {
     EnsureCorrectChunk(false);
@@ -367,16 +363,25 @@ void CacheFileOutputStream::NotifyCloseL
 }
 
 void
 CacheFileOutputStream::ReleaseChunk()
 {
   LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]",
        this, mChunk->Index()));
 
+  // If the chunk didn't write any data we need to remove hash for this chunk
+  // that was added when the chunk was created in CacheFile::GetChunkLocked.
+  if (mChunk->DataSize() == 0) {
+    // It must be due to a failure, we don't create a new chunk when we don't
+    // have data to write.
+    MOZ_ASSERT(NS_FAILED(mChunk->GetStatus()));
+    mFile->mMetadata->RemoveHash(mChunk->Index());
+  }
+
   mFile->ReleaseOutsideLock(mChunk.forget());
 }
 
 void
 CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly)
 {
   mFile->AssertOwnsLock();
 
--- a/netwerk/cache2/OldWrappers.cpp
+++ b/netwerk/cache2/OldWrappers.cpp
@@ -443,16 +443,17 @@ NS_IMETHODIMP _OldCacheEntryWrapper::Ope
                                                      nsIInputStream * *_retval)
 {
   if (offset > PR_UINT32_MAX)
     return NS_ERROR_INVALID_ARG;
 
   return OpenInputStream(uint32_t(offset), _retval);
 }
 NS_IMETHODIMP _OldCacheEntryWrapper::OpenOutputStream(int64_t offset,
+                                                      int64_t predictedSize,
                                                       nsIOutputStream * *_retval)
 {
   if (offset > PR_UINT32_MAX)
     return NS_ERROR_INVALID_ARG;
 
   return OpenOutputStream(uint32_t(offset), _retval);
 }
 
--- a/netwerk/cache2/OldWrappers.h
+++ b/netwerk/cache2/OldWrappers.h
@@ -40,35 +40,25 @@ public:
     return !mOldDesc ? NS_ERROR_NULL_POINTER :
                        mOldDesc->OpenInputStream(offset, _retval);
   }
   nsresult OpenOutputStream(uint32_t offset, nsIOutputStream * *_retval)
   {
     return !mOldDesc ? NS_ERROR_NULL_POINTER :
                        mOldDesc->OpenOutputStream(offset, _retval);
   }
-  NS_IMETHOD OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval) override
+  NS_IMETHOD OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   NS_IMETHOD OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
-  NS_IMETHOD GetPredictedDataSize(int64_t *aPredictedDataSize) override
-  {
-    return !mOldDesc ? NS_ERROR_NULL_POINTER :
-                       mOldDesc->GetPredictedDataSize(aPredictedDataSize);
-  }
-  NS_IMETHOD SetPredictedDataSize(int64_t aPredictedDataSize) override
-  {
-    return !mOldDesc ? NS_ERROR_NULL_POINTER :
-                       mOldDesc->SetPredictedDataSize(aPredictedDataSize);
-  }
   NS_IMETHOD GetSecurityInfo(nsISupports * *aSecurityInfo) override
   {
     return !mOldDesc ? NS_ERROR_NULL_POINTER :
                        mOldDesc->GetSecurityInfo(aSecurityInfo);
   }
   NS_IMETHOD SetSecurityInfo(nsISupports *aSecurityInfo) override
   {
     return !mOldDesc ? NS_ERROR_NULL_POINTER :
@@ -166,17 +156,17 @@ public:
   NS_IMETHOD GetIsForcedValid(bool *aIsForcedValid) override;
   NS_IMETHOD ForceValidFor(uint32_t aSecondsToTheFuture) override;
   NS_IMETHOD SetValid() override { return NS_OK; }
   NS_IMETHOD MetaDataReady() override { return NS_OK; }
   NS_IMETHOD Recreate(bool, nsICacheEntry**) override;
   NS_IMETHOD GetDataSize(int64_t *size) override;
   NS_IMETHOD GetAltDataSize(int64_t *size) override;
   NS_IMETHOD OpenInputStream(int64_t offset, nsIInputStream * *_retval) override;
-  NS_IMETHOD OpenOutputStream(int64_t offset, nsIOutputStream * *_retval) override;
+  NS_IMETHOD OpenOutputStream(int64_t offset, int64_t predictedSize, nsIOutputStream * *_retval) override;
   NS_IMETHOD MaybeMarkValid() override;
   NS_IMETHOD HasWriteAccess(bool aWriteOnly, bool *aWriteAccess) override;
   NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor*) override;
 
   explicit _OldCacheEntryWrapper(nsICacheEntryDescriptor* desc);
   explicit _OldCacheEntryWrapper(nsICacheEntryInfo* info);
 
 private:
--- a/netwerk/cache2/nsICacheEntry.idl
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -123,28 +123,25 @@ interface nsICacheEntry : nsISupports
    * MAY implement nsISeekableStream.
    *
    * If opening an output stream to existing cached data, the data will be
    * truncated to the specified offset.
    *
    * @param offset
    *        write starting from this offset into the cached data.  an offset
    *        beyond the end of the stream has undefined consequences.
+   * @param predictedSize
+   *        Predicted size of the data that will be written. It's used to decide
+   *        whether the resulting entry would exceed size limit, in which case
+   *        an error is thrown. If the size isn't known in advance, -1 should be
+   *        passed.
    *
    * @return blocking, buffered output stream.
    */
-  nsIOutputStream openOutputStream(in long long offset);
-
-  /**
-    * Stores the Content-Length specified in the HTTP header for this
-    * entry. Checked before we write to the cache entry, to prevent ever
-    * taking up space in the cache for an entry that we know up front
-    * is going to have to be evicted anyway. See bug 588507.
-    */
-  attribute int64_t predictedDataSize;
+  nsIOutputStream openOutputStream(in long long offset, in long long predictedSize);
 
   /**
    * Get/set security info on the cache entry for this descriptor.
    */
   attribute nsISupports securityInfo;
 
   /**
    * Get the size of the cache entry data, as stored. This may differ
@@ -240,24 +237,33 @@ interface nsICacheEntry : nsISupports
                               content or alt data).
   *    - NS_ERROR_NOT_AVAILABLE if alt data does not exist.
   */
   readonly attribute long long altDataSize;
 
   /**
    * Opens and returns an output stream that a consumer may use to save an
    * alternate representation of the data.
+   *
+   * @param type
+   *        type of the alternative data representation
+   * @param predictedSize
+   *        Predicted size of the data that will be written. It's used to decide
+   *        whether the resulting entry would exceed size limit, in which case
+   *        an error is thrown. If the size isn't known in advance, -1 should be
+   *        passed.
+   *
    * @throws
    *    - NS_ERROR_NOT_AVAILABLE if the real data hasn't been written.
    *    - NS_ERROR_IN_PROGRESS when the writing regular content or alt-data to
    *      the cache entry is still in progress.
    *
    * If there is alt-data already saved, it will be overwritten.
    */
-  nsIOutputStream openAlternativeOutputStream(in ACString type);
+  nsIOutputStream openAlternativeOutputStream(in ACString type, in long long predictedSize);
 
   /**
    * Opens and returns an input stream that can be used to read the alternative
    * representation previously saved in the cache.
    * If this call is made while writing alt-data is still in progress, it is
    * still possible to read content from the input stream as it's being written.
    * @throws
    *    - NS_ERROR_NOT_AVAILABLE if the alt-data representation doesn't exist at
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -109,16 +109,17 @@ NeckoChild::DeallocPStunAddrsRequestChil
   p->ReleaseIPDLReference();
 #endif
   return true;
 }
 
 PAltDataOutputStreamChild*
 NeckoChild::AllocPAltDataOutputStreamChild(
         const nsCString& type,
+        const int64_t& predictedSize,
         PHttpChannelChild* channel)
 {
   // We don't allocate here: see HttpChannelChild::OpenAlternativeOutputStream()
   NS_NOTREACHED("AllocPAltDataOutputStreamChild should not be called");
   return nullptr;
 }
 
 bool
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -29,17 +29,17 @@ protected:
     AllocPHttpChannelChild(const PBrowserOrId&, const SerializedLoadContext&,
                            const HttpChannelCreationArgs& aOpenArgs) override;
   virtual bool DeallocPHttpChannelChild(PHttpChannelChild*) override;
 
   virtual PStunAddrsRequestChild* AllocPStunAddrsRequestChild() override;
   virtual bool
     DeallocPStunAddrsRequestChild(PStunAddrsRequestChild* aActor) override;
 
-  virtual PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild(const nsCString& type, PHttpChannelChild* channel) override;
+  virtual PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild(const nsCString& type, const int64_t& predictedSize, PHttpChannelChild* channel) override;
   virtual bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor) override;
 
   virtual PCookieServiceChild* AllocPCookieServiceChild() override;
   virtual bool DeallocPCookieServiceChild(PCookieServiceChild*) override;
   virtual PWyciwygChannelChild* AllocPWyciwygChannelChild() override;
   virtual bool DeallocPWyciwygChannelChild(PWyciwygChannelChild*) override;
   virtual PFTPChannelChild*
     AllocPFTPChannelChild(const PBrowserOrId& aBrowser,
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -350,21 +350,22 @@ NeckoParent::DeallocPStunAddrsRequestPar
   p->Release();
 #endif
   return true;
 }
 
 PAltDataOutputStreamParent*
 NeckoParent::AllocPAltDataOutputStreamParent(
         const nsCString& type,
+        const int64_t& predictedSize,
         PHttpChannelParent* channel)
 {
   HttpChannelParent* chan = static_cast<HttpChannelParent*>(channel);
   nsCOMPtr<nsIOutputStream> stream;
-  nsresult rv = chan->OpenAlternativeOutputStream(type, getter_AddRefs(stream));
+  nsresult rv = chan->OpenAlternativeOutputStream(type, predictedSize, getter_AddRefs(stream));
   AltDataOutputStreamParent* parent = new AltDataOutputStreamParent(stream);
   parent->AddRef();
   // If the return value was not NS_OK, the error code will be sent
   // asynchronously to the child, after receiving the first message.
   parent->SetError(rv);
   return parent;
 }
 
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -104,17 +104,17 @@ protected:
       const HttpChannelCreationArgs& aOpenArgs) override;
   virtual bool DeallocPHttpChannelParent(PHttpChannelParent*) override;
 
   virtual PStunAddrsRequestParent* AllocPStunAddrsRequestParent() override;
   virtual bool
     DeallocPStunAddrsRequestParent(PStunAddrsRequestParent* aActor) override;
 
   virtual PAltDataOutputStreamParent* AllocPAltDataOutputStreamParent(
-    const nsCString& type, PHttpChannelParent* channel) override;
+    const nsCString& type, const int64_t& predictedSize, PHttpChannelParent* channel) override;
   virtual bool DeallocPAltDataOutputStreamParent(
     PAltDataOutputStreamParent* aActor) override;
 
   virtual bool DeallocPCookieServiceParent(PCookieServiceParent*) override;
   virtual PWyciwygChannelParent* AllocPWyciwygChannelParent() override;
   virtual bool DeallocPWyciwygChannelParent(PWyciwygChannelParent*) override;
   virtual PFTPChannelParent*
     AllocPFTPChannelParent(const PBrowserOrId& aBrowser,
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -116,17 +116,17 @@ parent:
   async OnAuthAvailable(uint64_t callbackId, nsString user,
                         nsString password, nsString domain);
   async OnAuthCancelled(uint64_t callbackId, bool userCancel);
 
   async RequestContextLoadBegin(uint64_t rcid);
   async RequestContextAfterDOMContentLoaded(uint64_t rcid);
   async RemoveRequestContext(uint64_t rcid);
 
-  async PAltDataOutputStream(nsCString type, PHttpChannel channel);
+  async PAltDataOutputStream(nsCString type, int64_t predictedSize, PHttpChannel channel);
 
   async PStunAddrsRequest();
 
   /**
    * WebExtension-specific remote resource loading
    */
   async GetExtensionStream(URIParams uri) returns (nsIInputStream stream);
   async GetExtensionFD(URIParams uri) returns (FileDescriptor fd);
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -3096,22 +3096,22 @@ HttpChannelChild::GetAlternativeDataType
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   aType = mAvailableCachedAltDataType;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-HttpChannelChild::OpenAlternativeOutputStream(const nsACString & aType, nsIOutputStream * *_retval)
+HttpChannelChild::OpenAlternativeOutputStream(const nsACString & aType, int64_t aPredictedSize, nsIOutputStream * *_retval)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
 
   if (mSynthesizedCacheInfo) {
-    return mSynthesizedCacheInfo->OpenAlternativeOutputStream(aType, _retval);
+    return mSynthesizedCacheInfo->OpenAlternativeOutputStream(aType, aPredictedSize, _retval);
   }
 
   if (!mIPCOpen) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   if (static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
@@ -3121,16 +3121,17 @@ HttpChannelChild::OpenAlternativeOutputS
 
   RefPtr<AltDataOutputStreamChild> stream = new AltDataOutputStreamChild();
   stream->AddIPDLReference();
 
   gNeckoChild->SetEventTargetForActor(stream, neckoTarget);
 
   if (!gNeckoChild->SendPAltDataOutputStreamConstructor(stream,
                                                         nsCString(aType),
+                                                        aPredictedSize,
                                                         this)) {
     return NS_ERROR_FAILURE;
   }
 
   stream.forget(_retval);
   return NS_OK;
 }
 
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -2247,24 +2247,24 @@ HttpChannelParent::NotifyDiversionFailed
   // DoSendDeleteSelf will need channel Id to remove the strong reference in
   // BackgroundChannelRegistrar if channel pairing is aborted.
   // Thus we need to keep mChannel until DoSendDeleteSelf is done.
   mParentListener = nullptr;
   mChannel = nullptr;
 }
 
 nsresult
-HttpChannelParent::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+HttpChannelParent::OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval)
 {
   // We need to make sure the child does not call SendDocumentChannelCleanup()
   // before opening the altOutputStream, because that clears mCacheEntry.
   if (!mCacheEntry) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-  nsresult rv = mCacheEntry->OpenAlternativeOutputStream(type, _retval);
+  nsresult rv = mCacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
   if (NS_SUCCEEDED(rv)) {
     mCacheEntry->SetMetaDataElement("alt-data-from-child", "1");
   }
   return rv;
 }
 
 NS_IMETHODIMP
 HttpChannelParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -97,16 +97,17 @@ public:
   // Forwarded to nsHttpChannel::SetApplyConversion.
   void SetApplyConversion(bool aApplyConversion) {
     if (mChannel) {
       mChannel->SetApplyConversion(aApplyConversion);
     }
   }
 
   MOZ_MUST_USE nsresult OpenAlternativeOutputStream(const nsACString & type,
+                                                    int64_t predictedSize,
                                                     nsIOutputStream * *_retval);
 
   // Callbacks for each asynchronous tasks required in AsyncOpen
   // procedure, will call InvokeAsyncOpen when all the expected
   // tasks is finished successfully or when any failure happened.
   // @see mAsyncOpenBarrier.
   void TryInvokeAsyncOpen(nsresult aRv);
 
--- a/netwerk/protocol/http/InterceptedHttpChannel.cpp
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
@@ -1313,20 +1313,20 @@ InterceptedHttpChannel::GetAlternativeDa
 {
   if (mSynthesizedCacheInfo) {
     return mSynthesizedCacheInfo->GetAlternativeDataType(aType);
   }
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
-InterceptedHttpChannel::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+InterceptedHttpChannel::OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval)
 {
   if (mSynthesizedCacheInfo) {
-    return mSynthesizedCacheInfo->OpenAlternativeOutputStream(type, _retval);
+    return mSynthesizedCacheInfo->OpenAlternativeOutputStream(type, predictedSize, _retval);
   }
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
 InterceptedHttpChannel::GetCacheKey(uint32_t* key)
 {
   if (mSynthesizedCacheInfo) {
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1568,29 +1568,16 @@ nsHttpChannel::CallOnStartRequest()
                 }
             }
         }
     }
 
     if (mResponseHead && !mResponseHead->HasContentCharset())
         mResponseHead->SetContentCharset(mContentCharsetHint);
 
-    if (mResponseHead && mCacheEntry) {
-        // If we have a cache entry, set its predicted size to TotalEntitySize to
-        // avoid caching an entry that will exceed the max size limit.
-        rv = mCacheEntry->SetPredictedDataSize(
-            mResponseHead->TotalEntitySize());
-        if (NS_ERROR_FILE_TOO_BIG == rv) {
-          // Don't throw the entry away, we will need it later.
-          LOG(("  entry too big"));
-        } else {
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
-    }
-
     LOG(("  calling mListener->OnStartRequest [this=%p, listener=%p]\n", this, mListener.get()));
 
     // About to call OnStartRequest, dismiss the guard object.
     onStartGuard.release();
 
     if (mListener) {
         MOZ_ASSERT(!mOnStartRequestCalled,
                    "We should not call OsStartRequest twice");
@@ -5423,25 +5410,34 @@ nsHttpChannel::InstallCacheListener(int6
 
     LOG(("Trading cache input stream for output stream [channel=%p]", this));
 
     // We must close the input stream first because cache entries do not
     // correctly handle having an output stream and input streams open at
     // the same time.
     mCacheInputStream.CloseAndRelease();
 
+    int64_t predictedSize = mResponseHead->TotalEntitySize();
+    if (predictedSize != -1) {
+        predictedSize -= offset;
+    }
+
     nsCOMPtr<nsIOutputStream> out;
-    rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
+    rv = mCacheEntry->OpenOutputStream(offset, predictedSize, getter_AddRefs(out));
     if (rv == NS_ERROR_NOT_AVAILABLE) {
         LOG(("  entry doomed, not writing it [channel=%p]", this));
         // Entry is already doomed.
         // This may happen when expiration time is set to past and the entry
         // has been removed by the background eviction logic.
         return NS_OK;
     }
+    if (rv == NS_ERROR_FILE_TOO_BIG) {
+        LOG(("  entry would exceed max allowed size, not writing it [channel=%p]", this));
+        return NS_OK;
+    }
     if (NS_FAILED(rv)) return rv;
 
     if (mCacheOnlyMetadata) {
         LOG(("Not storing content, cacheOnlyMetadata set"));
         // We must open and then close the output stream of the cache entry.
         // This way we indicate the content has been written (despite with zero
         // length) and the entry is now in the ready state with "having data".
 
@@ -5476,17 +5472,17 @@ nsHttpChannel::InstallOfflineCacheListen
 
     LOG(("Preparing to write data into the offline cache [uri=%s]\n",
          mSpec.get()));
 
     MOZ_ASSERT(mOfflineCacheEntry);
     MOZ_ASSERT(mListener);
 
     nsCOMPtr<nsIOutputStream> out;
-    rv = mOfflineCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
+    rv = mOfflineCacheEntry->OpenOutputStream(offset, -1, getter_AddRefs(out));
     if (NS_FAILED(rv)) return rv;
 
     nsCOMPtr<nsIStreamListenerTee> tee =
         do_CreateInstance(kStreamListenerTeeCID, &rv);
     if (NS_FAILED(rv)) return rv;
 
     rv = tee->Init(mListener, out, nullptr);
     if (NS_FAILED(rv)) return rv;
@@ -7913,25 +7909,25 @@ nsHttpChannel::GetAlternativeDataType(ns
     if (!mAfterOnStartRequestBegun) {
         return NS_ERROR_NOT_AVAILABLE;
     }
     aType = mAvailableCachedAltDataType;
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsHttpChannel::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+nsHttpChannel::OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval)
 {
     // OnStopRequest will clear mCacheEntry, but we may use mAltDataCacheEntry
     // if the consumer called PreferAlternativeDataType()
     nsCOMPtr<nsICacheEntry> cacheEntry = mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
     if (!cacheEntry) {
         return NS_ERROR_NOT_AVAILABLE;
     }
-    nsresult rv = cacheEntry->OpenAlternativeOutputStream(type, _retval);
+    nsresult rv = cacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
     if (NS_SUCCEEDED(rv)) {
         // Clear this metadata flag in case it exists.
         // The caller of this method may set it again.
         cacheEntry->SetMetaDataElement("alt-data-from-child", nullptr);
     }
     return rv;
 }
 
--- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
@@ -447,17 +447,17 @@ nsWyciwygChannel::WriteToCacheEntry(cons
   if (mNeedToWriteCharset) {
     WriteCharsetAndSourceToCache(mCharsetSource, mCharset);
     mNeedToWriteCharset = false;
   }
 
   uint32_t out;
   if (!mCacheOutputStream) {
     // Get the outputstream from the cache entry.
-    rv = mCacheEntry->OpenOutputStream(0, getter_AddRefs(mCacheOutputStream));
+    rv = mCacheEntry->OpenOutputStream(0, -1, getter_AddRefs(mCacheOutputStream));
     if (NS_FAILED(rv)) return rv;
 
     // Write out a Byte Order Mark, so that we'll know if the data is
     // BE or LE when we go to read it.
     char16_t bom = 0xFEFF;
     rv = mCacheOutputStream->Write((char *)&bom, sizeof(bom), &out);
     if (NS_FAILED(rv)) return rv;
   }
--- a/netwerk/test/unit/head_cache2.js
+++ b/netwerk/test/unit/head_cache2.js
@@ -193,34 +193,34 @@ OpenCallback.prototype =
             self.goon(entry);
 
           return;
         }
         executeSoon(function() { // emulate more network latency
           if (self.behavior & DOOMED) {
             LOG_C2(self, "checking doom state");
             try {
-              var os = entry.openOutputStream(0);
+              var os = entry.openOutputStream(0, -1);
               // Unfortunately, in the undetermined state we cannot even check whether the entry
               // is actually doomed or not.
               os.close();
               Assert.ok(!!(self.behavior & MAYBE_NEW));
             } catch (ex) {
               Assert.ok(true);
             }
             if (self.behavior & WAITFORWRITE)
               self.goon(entry);
             return;
           }
 
           var offset = (self.behavior & PARTIAL)
             ? entry.dataSize
             : 0;
           LOG_C2(self, "openOutputStream @ " + offset);
-          var os = entry.openOutputStream(offset);
+          var os = entry.openOutputStream(offset, -1);
           LOG_C2(self, "writing data");
           var wrt = os.write(self.workingData, self.workingData.length);
           Assert.equal(wrt, self.workingData.length);
           os.close();
           if (self.behavior & WAITFORWRITE)
             self.goon(entry);
 
           entry.close();
--- a/netwerk/test/unit/test_alt-data_cross_process.js
+++ b/netwerk/test/unit/test_alt-data_cross_process.js
@@ -94,17 +94,17 @@ function readServerContent(request, buff
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   Assert.equal(buffer, responseContent);
   Assert.equal(cc.alternativeDataType, "");
   check_has_alt_data_in_index(false);
 
   executeSoon(() => {
-    var os = cc.openAlternativeOutputStream(altContentType);
+    var os = cc.openAlternativeOutputStream(altContentType, altContent.length);
     os.write(altContent, altContent.length);
     os.close();
 
     executeSoon(flushAndOpenAltChannel);
   });
 }
 
 function flushAndOpenAltChannel()
--- a/netwerk/test/unit/test_alt-data_overwrite.js
+++ b/netwerk/test/unit/test_alt-data_overwrite.js
@@ -74,17 +74,17 @@ function run_test()
 function readServerContent(request, buffer)
 {
   let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   Assert.equal(buffer, responseContent);
   Assert.equal(cc.alternativeDataType, "");
 
   executeSoon(() => {
-    let os = cc.openAlternativeOutputStream(altContentType);
+    let os = cc.openAlternativeOutputStream(altContentType, altContent.length);
     os.write(altContent, altContent.length);
     os.close();
 
     executeSoon(flushAndOpenAltChannel);
   });
 }
 
 function flushAndOpenAltChannel()
@@ -122,17 +122,17 @@ function readServerContent2(request, buf
   Cu.forceShrinkingGC();
   let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   Assert.equal(fromCache || servedNotModified, true);
   Assert.equal(buffer, responseContent);
   Assert.equal(cc.alternativeDataType, "");
 
   executeSoon(() => {
-    let os = cc.openAlternativeOutputStream(altContentType);
+    let os = cc.openAlternativeOutputStream(altContentType, altContent.length);
     os.write(altContent, altContent.length);
     os.close();
 
     executeSoon(flushAndOpenAltChannel2);
   });
 }
 
 function flushAndOpenAltChannel2()
@@ -160,17 +160,17 @@ function readAltContent2(request, buffer
 
   Assert.equal(servedNotModified || fromCache, true);
   Assert.equal(cc.alternativeDataType, altContentType);
   Assert.equal(buffer, altContent);
 
   executeSoon(() => {
     Cu.forceShrinkingGC();
     info("writing other content\n");
-    let os = cc.openAlternativeOutputStream(altContentType2);
+    let os = cc.openAlternativeOutputStream(altContentType2, altContent2.length);
     os.write(altContent2, altContent2.length);
     os.close();
 
     executeSoon(flushAndOpenAltChannel3);
   });
 }
 
 function flushAndOpenAltChannel3()
--- a/netwerk/test/unit/test_alt-data_simple.js
+++ b/netwerk/test/unit/test_alt-data_simple.js
@@ -100,17 +100,17 @@ function readServerContent(request, buff
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   Assert.equal(buffer, responseContent);
   Assert.equal(cc.alternativeDataType, "");
   check_has_alt_data_in_index(false);
 
   executeSoon(() => {
-    var os = cc.openAlternativeOutputStream(altContentType);
+    var os = cc.openAlternativeOutputStream(altContentType, altContent.length);
     os.write(altContent, altContent.length);
     os.close();
 
     executeSoon(flushAndOpenAltChannel);
   });
 }
 
 // needs to be rooted
--- a/netwerk/test/unit/test_alt-data_stream.js
+++ b/netwerk/test/unit/test_alt-data_stream.js
@@ -72,17 +72,17 @@ var os;
 function readServerContent(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   Assert.equal(buffer, responseContent);
   Assert.equal(cc.alternativeDataType, "");
 
   executeSoon(() => {
-    os = cc.openAlternativeOutputStream(altContentType);
+    os = cc.openAlternativeOutputStream(altContentType, altContent.length);
     // Write a quarter of the alt data content
     os.write(altContent, firstChunkSize);
 
     executeSoon(openAltChannel);
   });
 }
 
 function openAltChannel()
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_too_big.js
@@ -0,0 +1,100 @@
+/**
+ * Test for handling too big alternative data
+ *
+ *  - first we try to open an output stream for too big alt-data which must fail
+ *    and leave original data intact
+ *
+ *  - then we open the output stream without passing predicted data size which
+ *    succeeds but writing must fail later at the size limit and the original
+ *    data must be kept
+ */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var data = "data    ";
+var altData = "alt-data"
+
+function run_test()
+{
+  do_get_profile();
+
+  // Expand both data to 1MB
+  for (i = 0; i < 17; i++) {
+    data += data;
+    altData += altData;
+  }
+
+  // Set the limit so that the data fits but alt-data doesn't.
+  Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1800);
+
+  write_data();
+
+  do_test_pending();
+}
+
+function write_data()
+{
+  asyncOpenCacheEntry("http://data/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null, function (status, entry) {
+    Assert.equal(status, Cr.NS_OK);
+
+    var os = entry.openOutputStream(0, -1);
+    var written = os.write(data, data.length);
+    Assert.equal(written, data.length);
+    os.close();
+
+    open_big_altdata_output(entry);
+  });
+}
+
+function open_big_altdata_output(entry)
+{
+  try {
+    var os = entry.openAlternativeOutputStream("text/binary", altData.length);
+  } catch (e) {
+    Assert.equal(e.result, Cr.NS_ERROR_FILE_TOO_BIG);
+  }
+  entry.close();
+
+  check_entry(write_big_altdata);
+}
+
+function write_big_altdata()
+{
+  asyncOpenCacheEntry("http://data/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null, function (status, entry) {
+    Assert.equal(status, Cr.NS_OK);
+
+    var os = entry.openAlternativeOutputStream("text/binary", -1);
+    try {
+      os.write(altData, altData.length);
+    } catch (e) {
+      Assert.equal(e.result, Cr.NS_ERROR_FILE_TOO_BIG);
+    }
+    os.close();
+    entry.close();
+
+    check_entry(do_test_finished);
+  });
+}
+
+function check_entry(cb)
+{
+  asyncOpenCacheEntry("http://data/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null, function (status, entry) {
+    Assert.equal(status, Cr.NS_OK);
+
+    var is = null;
+    try {
+      is = entry.openAlternativeInputStream("text/binary");
+    } catch (e) {
+      Assert.equal(e.result, Cr.NS_ERROR_NOT_AVAILABLE);
+    }
+
+    is = entry.openInputStream(0);
+    pumpReadStream(is, function(read) {
+      Assert.equal(read.length, data.length);
+      is.close();
+      entry.close();
+
+      executeSoon(cb);
+    });
+  });
+}
--- a/netwerk/test/unit/test_bug248970_cache.js
+++ b/netwerk/test/unit/test_bug248970_cache.js
@@ -50,17 +50,17 @@ function store_entries(cb)
                       Services.loadContextInfo.custom(false,
                         {privateBrowsingId : entries[store_idx][3] ? 0 : 1}),
                       store_data,
                       appCache);
 }
 
 var store_data = function(status, entry) {
   Assert.equal(status, Cr.NS_OK);
-  var os = entry.openOutputStream(0);
+  var os = entry.openOutputStream(0, entries[store_idx][1].length);
 
   var written = os.write(entries[store_idx][1], entries[store_idx][1].length);
   if (written != entries[store_idx][1].length) {
     do_throw("os.write has not written all data!\n" +
              "  Expected: " + entries[store_idx][1].length  + "\n" +
              "  Actual: " + written + "\n");
   }
   os.close();
--- a/netwerk/test/unit/test_bug482601.js
+++ b/netwerk/test/unit/test_bug482601.js
@@ -84,17 +84,17 @@ function makeChan(url) {
                 .QueryInterface(Ci.nsIHttpChannel);
 }
 
 function storeCache(aCacheEntry, aResponseHeads, aContent) {
   aCacheEntry.setMetaDataElement("request-method", "GET");
   aCacheEntry.setMetaDataElement("response-head", aResponseHeads);
   aCacheEntry.setMetaDataElement("charset", "ISO-8859-1");
 
-  var oStream = aCacheEntry.openOutputStream(0);
+  var oStream = aCacheEntry.openOutputStream(0, aContent.length);
   var written = oStream.write(aContent, aContent.length);
   if (written != aContent.length) {
     do_throw("oStream.write has not written all data!\n" +
              "  Expected: " + written  + "\n" +
              "  Actual: " + aContent.length + "\n");
   }
   oStream.close();
   aCacheEntry.close();
--- a/netwerk/test/unit/test_bug654926.js
+++ b/netwerk/test/unit/test_bug654926.js
@@ -24,18 +24,18 @@ function write_and_check(str, data, len)
              "  Expected: " + len  + "\n" +
              "  Actual: " + written + "\n");
   }
 }
 
 function write_datafile(status, entry)
 {
   Assert.equal(status, Cr.NS_OK);
-  var os = entry.openOutputStream(0);
   var data = gen_1MiB();
+  var os = entry.openOutputStream(0, data.length);
 
   // write 2MiB
   var i;
   for (i=0 ; i<2 ; i++)
     write_and_check(os, data, data.length);
 
   os.close();
   entry.close();
@@ -47,17 +47,17 @@ function write_datafile(status, entry)
   asyncOpenCacheEntry("http://data/",
                       "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
                       append_datafile);
 }
 
 function append_datafile(status, entry)
 {
   Assert.equal(status, Cr.NS_OK);
-  var os = entry.openOutputStream(entry.dataSize);
+  var os = entry.openOutputStream(entry.dataSize, -1);
   var data = gen_1MiB();
 
   // append 1MiB
   try {
     write_and_check(os, data, data.length);
     do_throw();
   }
   catch (ex) { }
--- a/netwerk/test/unit/test_bug654926_doom_and_read.js
+++ b/netwerk/test/unit/test_bug654926_doom_and_read.js
@@ -22,36 +22,36 @@ function make_input_stream_scriptable(in
                 createInstance(Ci.nsIScriptableInputStream);
   wrapper.init(input);
   return wrapper;
 }
 
 function write_datafile(status, entry)
 {
   Assert.equal(status, Cr.NS_OK);
-  var os = entry.openOutputStream(0);
   var data = gen_1MiB();
+  var os = entry.openOutputStream(0, data.length);
 
   write_and_check(os, data, data.length);
 
   os.close();
   entry.close();
 
   // open, doom, append, read
   asyncOpenCacheEntry("http://data/",
                       "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
                       test_read_after_doom);
 
 }
 
 function test_read_after_doom(status, entry)
 {
   Assert.equal(status, Cr.NS_OK);
-  var os = entry.openOutputStream(entry.dataSize);
   var data = gen_1MiB();
+  var os = entry.openOutputStream(entry.dataSize, data.length);
 
   entry.asyncDoom(null);
   write_and_check(os, data, data.length);
 
   os.close();
 
   var is = entry.openInputStream(0);
   pumpReadStream(is, function(read) {
--- a/netwerk/test/unit/test_bug654926_test_seek.js
+++ b/netwerk/test/unit/test_bug654926_test_seek.js
@@ -15,34 +15,34 @@ function write_and_check(str, data, len)
              "  Expected: " + len  + "\n" +
              "  Actual: " + written + "\n");
   }
 }
 
 function write_datafile(status, entry)
 {
   Assert.equal(status, Cr.NS_OK);
-  var os = entry.openOutputStream(0);
   var data = gen_1MiB();
+  var os = entry.openOutputStream(0, data.length);
 
   write_and_check(os, data, data.length);
 
   os.close();
   entry.close();
 
   // try to open the entry for appending
   asyncOpenCacheEntry("http://data/",
                       "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
                       open_for_readwrite);
 }
 
 function open_for_readwrite(status, entry)
 {
   Assert.equal(status, Cr.NS_OK);
-  var os = entry.openOutputStream(entry.dataSize);
+  var os = entry.openOutputStream(entry.dataSize, -1);
 
   // Opening the entry for appending data calls nsDiskCacheStreamIO::Seek()
   // which initializes mFD. If no data is written then mBufDirty is false and
   // mFD won't be closed in nsDiskCacheStreamIO::Flush().
 
   os.close();
   entry.close();
 
--- a/netwerk/test/unit/test_cache-entry-id.js
+++ b/netwerk/test/unit/test_cache-entry-id.js
@@ -76,17 +76,17 @@ function check(response, content, prefer
   Assert.ok(!cacheEntryIdChecker || cacheEntryIdChecker(response.cacheEntryId));
 
   return response;
 }
 
 function writeAltData(request)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
-  var os = cc.openAlternativeOutputStream(altContentType);
+  var os = cc.openAlternativeOutputStream(altContentType, altContent.length);
   os.write(altContent, altContent.length);
   os.close();
   gc(); // We need to do a GC pass to ensure the cache entry has been freed.
 
   return new Promise(resolve => {
     if (isParentProcess()) {
       Services.cache2.QueryInterface(Ci.nsICacheTesting)
               .flush(resolve);
--- a/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
+++ b/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
@@ -20,24 +20,24 @@ function run_test()
                      getService(Ci.nsIPrefBranch);
 
   // set max chunks memory so that only one full chunk fits within the limit
   prefBranch.setIntPref("browser.cache.disk.max_chunks_memory_usage", 300);
 
   asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
     function(status, entry) {
       Assert.equal(status, Cr.NS_OK);
-      oStr = entry.openOutputStream(0);
       var data = gen_200k();
+      oStr = entry.openOutputStream(0, data.length);
       Assert.equal(data.length, oStr.write(data, data.length));
 
       asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
         function(status, entry) {
           Assert.equal(status, Cr.NS_OK);
-          var oStr2 = entry.openOutputStream(0);
+          var oStr2 = entry.openOutputStream(0, data.length);
           do_check_throws_nsIException(() => oStr2.write(data, data.length), 'NS_ERROR_OUT_OF_MEMORY');
           finish_cache2_test();
         }
       );
     }
   );
 
   do_test_pending();
--- a/netwerk/test/unit/test_compressappend.js
+++ b/netwerk/test/unit/test_compressappend.js
@@ -30,28 +30,28 @@ TestAppend.prototype = {
                         "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
                         this.writeData.bind(this));
   },
 
   writeData: function(status, entry) {
     Assert.equal(status, Cr.NS_OK);
     if (this._compress)
       entry.setMetaDataElement("uncompressed-len", "0");
-    var os = entry.openOutputStream(0);
+    var os = entry.openOutputStream(0, 5);
     write_and_check(os, "12345", 5);
     os.close();
     entry.close();
     asyncOpenCacheEntry("http://data/",
                         "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
                         this.appendData.bind(this));
   },
 
   appendData: function(status, entry) {
     Assert.equal(status, Cr.NS_OK);
-    var os = entry.openOutputStream(entry.storageDataSize);
+    var os = entry.openOutputStream(entry.storageDataSize, 5);
     write_and_check(os, "abcde", 5);
     os.close();
     entry.close();
 
     asyncOpenCacheEntry("http://data/",
                         "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
                         this.checkData.bind(this));
   },
--- a/netwerk/test/unit/test_doomentry.js
+++ b/netwerk/test/unit/test_doomentry.js
@@ -25,17 +25,17 @@ function write_and_check(str, data, len)
              "  Expected: " + len  + "\n" +
              "  Actual: " + written + "\n");
   }
 }
 
 function write_entry()
 {
   asyncOpenCacheEntry("http://testentry/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null, function(status, entry) {
-    write_entry_cont(entry, entry.openOutputStream(0));
+    write_entry_cont(entry, entry.openOutputStream(0, -1));
   });
 }
 
 function write_entry_cont(entry, ostream)
 {
   var data = "testdata";
   write_and_check(ostream, data, data.length);
   ostream.close();
@@ -48,17 +48,17 @@ function check_doom1(status)
   Assert.equal(status, Cr.NS_OK);
   doom("http://nonexistententry/", check_doom2);
 }
 
 function check_doom2(status)
 {
   Assert.equal(status, Cr.NS_ERROR_NOT_AVAILABLE);
   asyncOpenCacheEntry("http://testentry/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null, function(status, entry) {
-    write_entry2(entry, entry.openOutputStream(0));
+    write_entry2(entry, entry.openOutputStream(0, -1));
   });
 }
 
 var gEntry;
 var gOstream;
 function write_entry2(entry, ostream)
 {
   // write some data and doom the entry while it is active
--- a/netwerk/test/unit/test_http2.js
+++ b/netwerk/test/unit/test_http2.js
@@ -1031,17 +1031,17 @@ Http2DiskCachePushListener.onStopRequest
     chan.loadGroup = loadGroup;
     chan.asyncOpen2(listener);
 };
 
 function continue_test_http2_disk_cache_push(status, entry, appCache) {
   // TODO - store stuff in cache entry, then open an h2 channel that will push
   // this, once that completes, open a channel for the cache entry we made and
   // ensure it came from disk cache, not the push cache.
-  var outputStream = entry.openOutputStream(0);
+  var outputStream = entry.openOutputStream(0, -1);
   outputStream.write(DISK_CACHE_DATA, DISK_CACHE_DATA.length);
 
   // Now we open our URL that will push data for the URL above
   var chan = makeChan("https://localhost:" + serverPort + "/pushindisk");
   var listener = new Http2DiskCachePushListener();
   chan.loadGroup = loadGroup;
   chan.asyncOpen2(listener);
 }
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -388,16 +388,17 @@ skip-if = os == "android"
 [test_dns_disable_ipv6.js]
 [test_bug1195415.js]
 [test_cookie_blacklist.js]
 [test_getHost.js]
 [test_bug412457.js]
 [test_bug464591.js]
 [test_alt-data_simple.js]
 [test_alt-data_stream.js]
+[test_alt-data_too_big.js]
 [test_alt-data_overwrite.js]
 [test_cache-control_request.js]
 [test_bug1279246.js]
 [test_throttlequeue.js]
 [test_throttlechannel.js]
 [test_throttling.js]
 [test_separate_connections.js]
 [test_trackingProtection_annotateChannels.js]
--- a/netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
+++ b/netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
@@ -46,17 +46,17 @@ function readTextData(request, buffer)
   // Since we are in a different process from what that generated the alt-data,
   // we should receive the original data, not processed content.
   Assert.equal(cc.alternativeDataType, "");
   Assert.equal(buffer, "response body");
 
   // Now let's generate some alt-data in the parent, and make sure we can get it
   var altContent = "altContentParentGenerated";
   executeSoon(() => {
-    var os = cc.openAlternativeOutputStream("text/parent-binary");
+    var os = cc.openAlternativeOutputStream("text/parent-binary", altContent.length);
     os.write(altContent, altContent.length);
     os.close();
 
     executeSoon(() => {
       Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver2);
     });
   });
 }