Bug 913807 - HTTP cache v2: file I/O, off by default, r=honzab
authorMichal Novotny <michal.novotny@gmail.com>
Fri, 20 Sep 2013 11:11:26 +0200
changeset 161941 4d66cd80b6be17120ae9aabc93dbcb6ed9c161db
parent 161940 0c91d9aa9476a9c2c6eb01e6142267d7987977e2
child 161942 289fcfb608d6c1ce6dc86df359179870362e43af
push idunknown
push userunknown
push dateunknown
reviewershonzab
bugs913807
milestone27.0a1
Bug 913807 - HTTP cache v2: file I/O, off by default, r=honzab
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileChunk.cpp
netwerk/cache2/CacheFileChunk.h
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheFileInputStream.cpp
netwerk/cache2/CacheFileInputStream.h
netwerk/cache2/CacheFileMetadata.cpp
netwerk/cache2/CacheFileMetadata.h
netwerk/cache2/CacheFileOutputStream.cpp
netwerk/cache2/CacheFileOutputStream.h
netwerk/cache2/CacheHashUtils.cpp
netwerk/cache2/CacheHashUtils.h
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFile.cpp
@@ -0,0 +1,1634 @@
+/* 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 "CacheLog.h"
+#include "CacheFile.h"
+
+#include "CacheFileChunk.h"
+#include "CacheFileInputStream.h"
+#include "CacheFileOutputStream.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include <algorithm>
+#include "nsComponentManagerUtils.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+#define kMetadataWriteDelay        5000
+
+class NotifyCacheFileListenerEvent : public nsRunnable {
+public:
+  NotifyCacheFileListenerEvent(CacheFileListener *aCallback,
+                               nsresult aResult,
+                               bool aIsNew)
+    : mCallback(aCallback)
+    , mRV(aResult)
+    , mIsNew(aIsNew)
+  {
+    LOG(("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() "
+         "[this=%p]", this));
+    MOZ_COUNT_CTOR(NotifyCacheFileListenerEvent);
+  }
+
+  ~NotifyCacheFileListenerEvent()
+  {
+    LOG(("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() "
+         "[this=%p]", this));
+    MOZ_COUNT_DTOR(NotifyCacheFileListenerEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this));
+
+    mCallback->OnFileReady(mRV, mIsNew);
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<CacheFileListener> mCallback;
+  nsresult                    mRV;
+  bool                        mIsNew;
+};
+
+class NotifyChunkListenerEvent : public nsRunnable {
+public:
+  NotifyChunkListenerEvent(CacheFileChunkListener *aCallback,
+                           nsresult aResult,
+                           uint32_t aChunkIdx,
+                           CacheFileChunk *aChunk)
+    : mCallback(aCallback)
+    , mRV(aResult)
+    , mChunkIdx(aChunkIdx)
+    , mChunk(aChunk)
+  {
+    LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]",
+         this));
+    MOZ_COUNT_CTOR(NotifyChunkListenerEvent);
+  }
+
+  ~NotifyChunkListenerEvent()
+  {
+    LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]",
+         this));
+    MOZ_COUNT_DTOR(NotifyChunkListenerEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this));
+
+    mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk);
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<CacheFileChunkListener> mCallback;
+  nsresult                         mRV;
+  uint32_t                         mChunkIdx;
+  nsRefPtr<CacheFileChunk>         mChunk;
+};
+
+class MetadataWriteTimer : public nsITimerCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
+
+  MetadataWriteTimer(CacheFile *aFile);
+  nsresult Fire();
+  nsresult Cancel();
+  bool     ShouldFireNew();
+
+protected:
+  virtual ~MetadataWriteTimer();
+
+  nsCOMPtr<nsIWeakReference> mFile;
+  nsCOMPtr<nsITimer>         mTimer;
+  nsCOMPtr<nsIEventTarget>   mTarget;
+  PRIntervalTime             mFireTime;
+};
+
+NS_IMPL_ADDREF(MetadataWriteTimer)
+NS_IMPL_RELEASE(MetadataWriteTimer)
+
+NS_INTERFACE_MAP_BEGIN(MetadataWriteTimer)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+MetadataWriteTimer::MetadataWriteTimer(CacheFile *aFile)
+  : mFireTime(0)
+{
+  LOG(("MetadataWriteTimer::MetadataWriteTimer() [this=%p, file=%p]",
+       this, aFile));
+  MOZ_COUNT_CTOR(MetadataWriteTimer);
+
+  mFile = do_GetWeakReference(static_cast<CacheFileChunkListener *>(aFile));
+  mTarget = NS_GetCurrentThread();
+}
+
+MetadataWriteTimer::~MetadataWriteTimer()
+{
+  LOG(("MetadataWriteTimer::~MetadataWriteTimer() [this=%p]", this));
+  MOZ_COUNT_DTOR(MetadataWriteTimer);
+
+  NS_ProxyRelease(mTarget, mTimer.forget().get());
+  NS_ProxyRelease(mTarget, mFile.forget().get());
+}
+
+NS_IMETHODIMP
+MetadataWriteTimer::Notify(nsITimer *aTimer)
+{
+  LOG(("MetadataWriteTimer::Notify() [this=%p, timer=%p]", this, aTimer));
+
+  MOZ_ASSERT(aTimer == mTimer);
+
+  nsCOMPtr<nsISupports> supp = do_QueryReferent(mFile);
+  if (!supp)
+    return NS_OK;
+
+  CacheFile *file = static_cast<CacheFile *>(
+                      static_cast<CacheFileChunkListener *>(supp.get()));
+
+  CacheFileAutoLock lock(file);
+
+  if (file->mTimer != this)
+    return NS_OK;
+
+  if (file->mMemoryOnly)
+    return NS_OK;
+
+  file->WriteMetadataIfNeeded();
+  file->mTimer = nullptr;
+
+  return NS_OK;
+}
+
+nsresult
+MetadataWriteTimer::Fire()
+{
+  LOG(("MetadataWriteTimer::Fire() [this=%p]", this));
+
+  nsresult rv;
+
+#ifdef DEBUG
+  bool onCurrentThread = false;
+  rv = mTarget->IsOnCurrentThread(&onCurrentThread);
+  MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrentThread);
+#endif
+
+  mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mTimer->InitWithCallback(this, kMetadataWriteDelay,
+                                nsITimer::TYPE_ONE_SHOT);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mFireTime = PR_IntervalNow();
+
+  return NS_OK;
+}
+
+nsresult
+MetadataWriteTimer::Cancel()
+{
+  LOG(("MetadataWriteTimer::Cancel() [this=%p]", this));
+  return mTimer->Cancel();
+}
+
+bool
+MetadataWriteTimer::ShouldFireNew()
+{
+  uint32_t delta = PR_IntervalToMilliseconds(PR_IntervalNow() - mFireTime);
+
+  if (delta > kMetadataWriteDelay / 2) {
+    LOG(("MetadataWriteTimer::ShouldFireNew() - returning true [this=%p]",
+         this));
+    return true;
+  }
+
+  LOG(("MetadataWriteTimer::ShouldFireNew() - returning false [this=%p]",
+       this));
+  return false;
+}
+
+class DoomFileHelper : public CacheFileIOListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  DoomFileHelper(CacheFileListener *aListener)
+    : mListener(aListener)
+  {
+    MOZ_COUNT_CTOR(DoomFileHelper);
+  }
+
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
+  {
+    MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult)
+  {
+    MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
+  {
+    MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
+  {
+    mListener->OnFileDoomed(aResult);
+    return NS_OK;
+  }
+
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
+  {
+    MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+private:
+  virtual ~DoomFileHelper()
+  {
+    MOZ_COUNT_DTOR(DoomFileHelper);
+  }
+
+  nsCOMPtr<CacheFileListener>  mListener;
+};
+
+NS_IMPL_ISUPPORTS1(DoomFileHelper, CacheFileIOListener)
+
+
+// We try to write metadata when the last reference to CacheFile is released.
+// We call WriteMetadataIfNeeded() under the lock from CacheFile::Release() and
+// if writing fails synchronously the listener is released and lock in
+// CacheFile::Release() is re-entered. This helper class ensures that the
+// listener is released outside the lock in case of synchronous failure.
+class MetadataListenerHelper : public CacheFileMetadataListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  MetadataListenerHelper(CacheFile *aFile)
+    : mFile(aFile)
+  {
+    MOZ_COUNT_CTOR(MetadataListenerHelper);
+    mListener = static_cast<CacheFileMetadataListener *>(aFile);
+  }
+
+  NS_IMETHOD OnMetadataRead(nsresult aResult)
+  {
+    MOZ_CRASH("MetadataListenerHelper::OnMetadataRead should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_IMETHOD OnMetadataWritten(nsresult aResult)
+  {
+    mFile = nullptr;
+    return mListener->OnMetadataWritten(aResult);
+  }
+
+private:
+  virtual ~MetadataListenerHelper()
+  {
+    MOZ_COUNT_DTOR(MetadataListenerHelper);
+    if (mFile) {
+      mFile->ReleaseOutsideLock(mListener.forget().get());
+    }
+  }
+
+  CacheFile*                           mFile;
+  nsCOMPtr<CacheFileMetadataListener>  mListener;
+};
+
+NS_IMPL_ISUPPORTS1(MetadataListenerHelper, CacheFileMetadataListener)
+
+
+NS_IMPL_ADDREF(CacheFile)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFile::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFile");
+
+  MOZ_ASSERT(count != 0, "Unexpected");
+
+  if (count == 1) {
+    bool deleteFile = false;
+
+    // Not using CacheFileAutoLock since it hard-refers the file
+    // and in it's destructor reenters this method.
+    Lock();
+
+    if (mMemoryOnly) {
+      deleteFile = true;
+    }
+    else if (mMetadata) {
+      WriteMetadataIfNeeded();
+      if (mWritingMetadata) {
+        MOZ_ASSERT(mRefCnt > 1);
+      } else {
+        if (mRefCnt == 1)
+          deleteFile = true;
+      }
+    }
+
+    Unlock();
+
+    if (!deleteFile) {
+      return count;
+    }
+
+    NS_LOG_RELEASE(this, 0, "CacheFile");
+    delete (this);
+    return 0;
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFile)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,
+                                   mozilla::net::CacheFileChunkListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFile::CacheFile()
+  : mLock("CacheFile.mLock")
+  , mOpeningFile(false)
+  , mReady(false)
+  , mMemoryOnly(false)
+  , mDataAccessed(false)
+  , mDataIsDirty(false)
+  , mWritingMetadata(false)
+  , mStatus(NS_OK)
+  , mDataSize(-1)
+  , mOutput(nullptr)
+{
+  LOG(("CacheFile::CacheFile() [this=%p]", this));
+
+  NS_ADDREF(this);
+  MOZ_COUNT_CTOR(CacheFile);
+}
+
+CacheFile::~CacheFile()
+{
+  LOG(("CacheFile::~CacheFile() [this=%p]", this));
+
+  MOZ_COUNT_DTOR(CacheFile);
+}
+
+nsresult
+CacheFile::Init(const nsACString &aKey,
+                bool aCreateNew,
+                bool aMemoryOnly,
+                bool aPriority,
+                bool aKeyIsHash,
+                CacheFileListener *aCallback)
+{
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(!mHandle);
+
+  nsresult rv;
+
+  mKey = aKey;
+  mMemoryOnly = aMemoryOnly;
+  mKeyIsHash = aKeyIsHash;
+
+  LOG(("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, "
+       "listener=%p]", this, mKey.get(), aCreateNew, aMemoryOnly, aCallback));
+
+  if (mMemoryOnly) {
+    MOZ_ASSERT(!aCallback);
+
+    MOZ_ASSERT(!mKeyIsHash);
+    mMetadata = new CacheFileMetadata(mKey);
+    mReady = true;
+    mDataSize = mMetadata->Offset();
+    return NS_OK;
+  }
+  else {
+    uint32_t flags;
+    if (aCreateNew) {
+      MOZ_ASSERT(!aCallback);
+      flags = CacheFileIOManager::CREATE_NEW;
+
+      // make sure we can use this entry immediately
+      MOZ_ASSERT(!mKeyIsHash);
+      mMetadata = new CacheFileMetadata(mKey);
+      mReady = true;
+      mDataSize = mMetadata->Offset();
+    }
+    else
+      flags = CacheFileIOManager::CREATE;
+
+    if (aPriority)
+      flags |= CacheFileIOManager::PRIORITY;
+    if (aKeyIsHash)
+      flags |= CacheFileIOManager::NOHASH;
+
+    mOpeningFile = true;
+    mListener = aCallback;
+    rv = CacheFileIOManager::OpenFile(mKey, flags, this);
+    if (NS_FAILED(rv)) {
+      mListener = nullptr;
+      mOpeningFile = false;
+
+      if (aCreateNew) {
+        NS_WARNING("Forcing memory-only entry since OpenFile failed");
+        LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
+             "synchronously. We can continue in memory-only mode since "
+             "aCreateNew == true. [this=%p]", this));
+
+        mMemoryOnly = true;
+      }
+      else if (rv == NS_ERROR_NOT_INITIALIZED) {
+        NS_WARNING("Forcing memory-only entry since CacheIOManager isn't "
+                   "initialized.");
+        LOG(("CacheFile::Init() - CacheFileIOManager isn't initialized, "
+             "initializing entry as memory-only. [this=%p]", this));
+
+        mMemoryOnly = true;
+        MOZ_ASSERT(!mKeyIsHash);
+        mMetadata = new CacheFileMetadata(mKey);
+        mReady = true;
+        mDataSize = mMetadata->Offset();
+
+        nsRefPtr<NotifyCacheFileListenerEvent> ev;
+        ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true);
+        rv = NS_DispatchToCurrentThread(ev);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      else {
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
+{
+  CacheFileAutoLock lock(this);
+
+  nsresult rv;
+
+  uint32_t index = aChunk->Index();
+
+  LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08x, chunk=%p, idx=%d]",
+       this, aResult, aChunk, index));
+
+  // TODO handle ERROR state
+
+  if (HaveChunkListeners(index)) {
+    rv = NotifyChunkListeners(index, aResult, aChunk);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
+{
+  CacheFileAutoLock lock(this);
+
+  nsresult rv;
+
+  LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08x, chunk=%p, idx=%d]",
+       this, aResult, aChunk, aChunk->Index()));
+
+  MOZ_ASSERT(!mMemoryOnly);
+
+  // TODO handle ERROR state
+
+  if (NS_FAILED(aResult)) {
+    // TODO ??? doom entry
+    // TODO mark this chunk as memory only, since it wasn't written to disk and
+    // therefore cannot be released from memory
+    // LOG
+  }
+
+  if (NS_SUCCEEDED(aResult) && !aChunk->IsDirty()) {
+    // update hash value in metadata
+    mMetadata->SetHash(aChunk->Index(), aChunk->Hash());
+  }
+
+  // notify listeners if there is any
+  if (HaveChunkListeners(aChunk->Index())) {
+    // don't release the chunk since there are some listeners queued
+    rv = NotifyChunkListeners(aChunk->Index(), NS_OK, aChunk);
+    if (NS_SUCCEEDED(rv)) {
+      MOZ_ASSERT(aChunk->mRefCnt != 2);
+      return NS_OK;
+    }
+  }
+
+  if (aChunk->mRefCnt != 2) {
+    LOG(("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p,"
+         " refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
+
+    return NS_OK;
+  }
+
+  LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, chunk=%p]",
+       this, aChunk));
+
+  aChunk->mRemovingChunk = true;
+  ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
+                       aChunk->mFile.forget().get()));
+  mCachedChunks.Put(aChunk->Index(), aChunk);
+  mChunks.Remove(aChunk->Index());
+  WriteMetadataIfNeeded();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                            CacheFileChunk *aChunk)
+{
+  MOZ_CRASH("CacheFile::OnChunkAvailable should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OnChunkUpdated(CacheFileChunk *aChunk)
+{
+  MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
+{
+  nsresult rv;
+
+  nsCOMPtr<CacheFileListener> listener;
+  bool isNew = false;
+  nsresult retval = NS_OK;
+
+  {
+    CacheFileAutoLock lock(this);
+
+    MOZ_ASSERT(mOpeningFile);
+    MOZ_ASSERT((NS_SUCCEEDED(aResult) && aHandle) ||
+               (NS_FAILED(aResult) && !aHandle));
+    MOZ_ASSERT((mListener && !mMetadata) || // !createNew
+               (!mListener && mMetadata));  // createNew
+    MOZ_ASSERT(!mMemoryOnly || mMetadata); // memory-only was set on new entry
+
+    LOG(("CacheFile::OnFileOpened() [this=%p, rv=0x%08x, handle=%p]",
+         this, aResult, aHandle));
+
+    mOpeningFile = false;
+
+    if (mMemoryOnly) {
+      // We can be here only in case the entry was initilized as createNew and
+      // SetMemoryOnly() was called.
+
+      // Just don't store the handle into mHandle and exit
+      return NS_OK;
+    }
+    else if (NS_FAILED(aResult)) {
+      if (mMetadata) {
+        // This entry was initialized as createNew, just switch to memory-only
+        // mode.
+        NS_WARNING("Forcing memory-only entry since OpenFile failed");
+        LOG(("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() "
+             "failed asynchronously. We can continue in memory-only mode since "
+             "aCreateNew == true. [this=%p]", this));
+
+        mMemoryOnly = true;
+        return NS_OK;
+      }
+      else if (aResult == NS_ERROR_FILE_INVALID_PATH) {
+        // CacheFileIOManager doesn't have mCacheDirectory, switch to
+        // memory-only mode.
+        NS_WARNING("Forcing memory-only entry since CacheFileIOManager doesn't "
+                   "have mCacheDirectory.");
+        LOG(("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have "
+             "mCacheDirectory, initializing entry as memory-only. [this=%p]",
+             this));
+
+        mMemoryOnly = true;
+        MOZ_ASSERT(!mKeyIsHash);
+        mMetadata = new CacheFileMetadata(mKey);
+        mReady = true;
+        mDataSize = mMetadata->Offset();
+
+        isNew = true;
+        retval = NS_OK;
+      }
+      else {
+        // CacheFileIOManager::OpenFile() failed for another reason.
+        isNew = false;
+        retval = aResult;
+      }
+
+      mListener.swap(listener);
+    }
+    else {
+      mHandle = aHandle;
+
+      if (mMetadata) {
+        // The entry was initialized as createNew, don't try to read metadata.
+        mMetadata->SetHandle(mHandle);
+
+        // Write all cached chunks, otherwise thay may stay unwritten.
+        mCachedChunks.Enumerate(&CacheFile::WriteAllCachedChunks, this);
+
+        return NS_OK;
+      }
+    }
+  }
+
+  if (listener) {
+    listener->OnFileReady(retval, isNew);
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(NS_SUCCEEDED(aResult));
+  MOZ_ASSERT(!mMetadata);
+  MOZ_ASSERT(mListener);
+
+  mMetadata = new CacheFileMetadata(mHandle, mKey, mKeyIsHash);
+
+  rv = mMetadata->ReadMetadata(this);
+  if (NS_FAILED(rv)) {
+    mListener.swap(listener);
+    listener->OnFileReady(rv, false);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                         nsresult aResult)
+{
+  MOZ_CRASH("CacheFile::OnDataWritten should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
+{
+  MOZ_CRASH("CacheFile::OnDataRead should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OnMetadataRead(nsresult aResult)
+{
+  MOZ_ASSERT(mListener);
+
+  LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult));
+
+  bool isNew = false;
+  if (NS_SUCCEEDED(aResult)) {
+    mReady = true;
+    mDataSize = mMetadata->Offset();
+    if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
+      isNew = true;
+      mMetadata->MarkDirty();
+    }
+  }
+
+  nsCOMPtr<CacheFileListener> listener;
+  mListener.swap(listener);
+  listener->OnFileReady(aResult, isNew);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnMetadataWritten(nsresult aResult)
+{
+  CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::OnMetadataWritten() [this=%p, rv=0x%08x]", this, aResult));
+
+  MOZ_ASSERT(mWritingMetadata);
+  mWritingMetadata = false;
+
+  MOZ_ASSERT(!mMemoryOnly);
+  MOZ_ASSERT(!mOpeningFile);
+
+  if (NS_FAILED(aResult)) {
+    // TODO close streams with an error ???
+  }
+
+  if (mOutput || mInputs.Length() || mChunks.Count())
+    return NS_OK;
+
+  if (IsDirty())
+    WriteMetadataIfNeeded();
+
+  if (!mWritingMetadata) {
+    LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]",
+         this));
+    CacheFileIOManager::ReleaseNSPRHandle(mHandle);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
+{
+  nsCOMPtr<CacheFileListener> listener;
+
+  {
+    CacheFileAutoLock lock(this);
+
+    MOZ_ASSERT(mListener);
+
+    LOG(("CacheFile::OnFileDoomed() [this=%p, rv=0x%08x, handle=%p]",
+         this, aResult, aHandle));
+
+    mListener.swap(listener);
+  }
+
+  listener->OnFileDoomed(aResult);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFile::OnEOFSet should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OpenInputStream(nsIInputStream **_retval)
+{
+  CacheFileAutoLock lock(this);
+
+  MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+  if (!mReady) {
+    LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]",
+         this));
+
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  CacheFileInputStream *input = new CacheFileInputStream(this);
+
+  LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]",
+       input, this));
+
+  mInputs.AppendElement(input);
+  NS_ADDREF(input);
+
+  mDataAccessed = true;
+  NS_ADDREF(*_retval = input);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OpenOutputStream(nsIOutputStream **_retval)
+{
+  CacheFileAutoLock lock(this);
+
+  MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+  if (!mReady) {
+    LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]",
+         this));
+
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (mOutput) {
+    LOG(("CacheFile::OpenOutputStream() - We already have output stream %p "
+         "[this=%p]", mOutput, this));
+
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mOutput = new CacheFileOutputStream(this);
+
+  LOG(("CacheFile::OpenOutputStream() - Creating new output stream %p "
+       "[this=%p]", mOutput, this));
+
+  mDataAccessed = true;
+  NS_ADDREF(*_retval = mOutput);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::SetMemoryOnly()
+{
+  LOG(("CacheFile::SetMemoryOnly() aMemoryOnly=%d [this=%p]",
+       mMemoryOnly, this));
+
+  if (mMemoryOnly)
+    return NS_OK;
+
+  MOZ_ASSERT(mReady);
+
+  if (!mReady) {
+    LOG(("CacheFile::SetMemoryOnly() - CacheFile is not ready [this=%p]",
+         this));
+
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (mDataAccessed) {
+    LOG(("CacheFile::SetMemoryOnly() - Data was already accessed [this=%p]", this));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // TODO what to do when this isn't a new entry and has an existing metadata???
+  mMemoryOnly = true;
+  return NS_OK;
+}
+
+nsresult
+CacheFile::Doom(CacheFileListener *aCallback)
+{
+  CacheFileAutoLock lock(this);
+
+  MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+  LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback));
+
+  nsresult rv;
+
+  if (mMemoryOnly) {
+    // TODO what exactly to do here?
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<CacheFileIOListener> listener;
+  if (aCallback)
+    listener = new DoomFileHelper(aCallback);
+
+  if (mHandle) {
+    rv = CacheFileIOManager::DoomFile(mHandle, listener);
+  } else {
+    rv = CacheFileIOManager::DoomFileByKey(mKey, listener);
+  }
+
+  return rv;
+}
+
+nsresult
+CacheFile::ThrowMemoryCachedData()
+{
+  CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this));
+
+  if (mOpeningFile) {
+    // mayhemer, note: we shouldn't get here, since CacheEntry prevents loading
+    // entries from being purged.
+
+    LOG(("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
+         "entry is still opening the file [this=%p]", this));
+
+    return NS_ERROR_ABORT;
+  }
+
+  mCachedChunks.Clear();
+  return NS_OK;
+}
+
+nsresult
+CacheFile::GetElement(const char *aKey, const char **_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  *_retval = mMetadata->GetElement(aKey);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::SetElement(const char *aKey, const char *aValue)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  PostWriteTimer();
+  return mMetadata->SetElement(aKey, aValue);
+}
+
+nsresult
+CacheFile::ElementsSize(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+
+  if (!mMetadata)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  *_retval = mMetadata->ElementsSize();
+  return NS_OK;
+}
+
+nsresult
+CacheFile::SetExpirationTime(uint32_t aExpirationTime)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  PostWriteTimer();
+  return mMetadata->SetExpirationTime(aExpirationTime);
+}
+
+nsresult
+CacheFile::GetExpirationTime(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->GetExpirationTime(_retval);
+}
+
+nsresult
+CacheFile::SetLastModified(uint32_t aLastModified)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  PostWriteTimer();
+  return mMetadata->SetLastModified(aLastModified);
+}
+
+nsresult
+CacheFile::GetLastModified(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->GetLastModified(_retval);
+}
+
+nsresult
+CacheFile::GetLastFetched(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->GetLastFetched(_retval);
+}
+
+nsresult
+CacheFile::GetFetchCount(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->GetFetchCount(_retval);
+}
+
+void
+CacheFile::Lock()
+{
+  mLock.Lock();
+}
+
+void
+CacheFile::Unlock()
+{
+  nsTArray<nsISupports*> objs;
+  objs.SwapElements(mObjsToRelease);
+
+  mLock.Unlock();
+
+  for (uint32_t i = 0; i < objs.Length(); i++)
+    objs[i]->Release();
+}
+
+void
+CacheFile::AssertOwnsLock()
+{
+  mLock.AssertCurrentThreadOwns();
+}
+
+void
+CacheFile::ReleaseOutsideLock(nsISupports *aObject)
+{
+  AssertOwnsLock();
+
+  mObjsToRelease.AppendElement(aObject);
+}
+
+nsresult
+CacheFile::GetChunk(uint32_t aIndex, bool aWriter,
+                    CacheFileChunkListener *aCallback, CacheFileChunk **_retval)
+{
+  CacheFileAutoLock lock(this);
+  return GetChunkLocked(aIndex, aWriter, aCallback, _retval);
+}
+
+nsresult
+CacheFile::GetChunkLocked(uint32_t aIndex, bool aWriter,
+                          CacheFileChunkListener *aCallback,
+                          CacheFileChunk **_retval)
+{
+  AssertOwnsLock();
+
+  LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%d, writer=%d, listener=%p]",
+       this, aIndex, aWriter, aCallback));
+
+  MOZ_ASSERT(mReady);
+  MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+  MOZ_ASSERT((aWriter && !aCallback) || (!aWriter && aCallback));
+
+  nsresult rv;
+
+  nsRefPtr<CacheFileChunk> chunk;
+  if (mChunks.Get(aIndex, getter_AddRefs(chunk))) {
+    LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]",
+         chunk.get(), this));
+
+    if (chunk->IsReady() || aWriter) {
+      chunk.swap(*_retval);
+    }
+    else {
+      rv = QueueChunkListener(aIndex, aCallback);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
+  }
+
+  if (mCachedChunks.Get(aIndex, getter_AddRefs(chunk))) {
+    LOG(("CacheFile::GetChunkLocked() - Reusing cached chunk %p [this=%p]",
+         chunk.get(), this));
+
+    mChunks.Put(aIndex, chunk);
+    mCachedChunks.Remove(aIndex);
+    chunk->mFile = this;
+    chunk->mRemovingChunk = false;
+
+    MOZ_ASSERT(chunk->IsReady());
+
+    chunk.swap(*_retval);
+    return NS_OK;
+  }
+
+  int64_t off = aIndex * kChunkSize;
+
+  if (off < mDataSize) {
+    // We cannot be here if this is memory only entry since the chunk must exist
+    MOZ_ASSERT(!mMemoryOnly);
+
+    chunk = new CacheFileChunk(this, aIndex);
+    mChunks.Put(aIndex, chunk);
+
+    LOG(("CacheFile::GetChunkLocked() - Reading newly created chunk %p from "
+         "the disk [this=%p]", chunk.get(), this));
+
+    // Read the chunk from the disk
+    rv = chunk->Read(mHandle, std::min(static_cast<uint32_t>(mDataSize - off),
+                     static_cast<uint32_t>(kChunkSize)),
+                     mMetadata->GetHash(aIndex), this);
+    if (NS_FAILED(rv)) {
+      chunk->mRemovingChunk = true;
+      ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
+                           chunk->mFile.forget().get()));
+      mChunks.Remove(aIndex);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    if (aWriter) {
+      chunk.swap(*_retval);
+    }
+    else {
+      rv = QueueChunkListener(aIndex, aCallback);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
+  }
+  else if (off == mDataSize) {
+    if (aWriter) {
+      // this listener is going to write to the chunk
+      chunk = new CacheFileChunk(this, aIndex);
+      mChunks.Put(aIndex, chunk);
+
+      LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]",
+           chunk.get(), this));
+
+      chunk->InitNew(this);
+      mMetadata->SetHash(aIndex, chunk->Hash());
+
+      if (HaveChunkListeners(aIndex)) {
+        rv = NotifyChunkListeners(aIndex, NS_OK, chunk);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      chunk.swap(*_retval);
+      return NS_OK;
+    }
+  }
+  else {
+    if (aWriter) {
+      // this chunk was requested by writer, but we need to fill the gap first
+
+      // Fill with zero the last chunk if it is incomplete
+      if (mDataSize % kChunkSize) {
+        rv = PadChunkWithZeroes(mDataSize / kChunkSize);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        MOZ_ASSERT(!(mDataSize % kChunkSize));
+      }
+
+      uint32_t startChunk = mDataSize / kChunkSize;
+
+      if (mMemoryOnly) {
+        // We need to create all missing CacheFileChunks if this is memory-only
+        // entry
+        for (uint32_t i = startChunk ; i < aIndex ; i++) {
+          rv = PadChunkWithZeroes(i);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+      }
+      else {
+        // We don't need to create CacheFileChunk for other empty chunks unless
+        // there is some input stream waiting for this chunk.
+
+        if (startChunk != aIndex) {
+          // Make sure the file contains zeroes at the end of the file
+          rv = CacheFileIOManager::TruncateSeekSetEOF(mHandle,
+                                                      startChunk * kChunkSize,
+                                                      aIndex * kChunkSize,
+                                                      nullptr);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+
+        for (uint32_t i = startChunk ; i < aIndex ; i++) {
+          if (HaveChunkListeners(i)) {
+            rv = PadChunkWithZeroes(i);
+            NS_ENSURE_SUCCESS(rv, rv);
+          }
+          else {
+            mMetadata->SetHash(i, kEmptyChunkHash);
+            mDataSize = (i + 1) * kChunkSize;
+          }
+        }
+      }
+
+      MOZ_ASSERT(mDataSize == off);
+      rv = GetChunkLocked(aIndex, true, nullptr, getter_AddRefs(chunk));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      chunk.swap(*_retval);
+      return NS_OK;
+    }
+  }
+
+  if (mOutput) {
+    // the chunk doesn't exist but mOutput may create it
+    rv = QueueChunkListener(aIndex, aCallback);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::RemoveChunk(CacheFileChunk *aChunk)
+{
+  nsresult rv;
+
+  // Avoid lock reentrancy by increasing the RefCnt
+  nsRefPtr<CacheFileChunk> chunk = aChunk;
+
+  {
+    CacheFileAutoLock lock(this);
+
+    LOG(("CacheFile::RemoveChunk() [this=%p, chunk=%p, idx=%d]",
+         this, aChunk, aChunk->Index()));
+
+    MOZ_ASSERT(mReady);
+    MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+    if (aChunk->mRefCnt != 2) {
+      LOG(("CacheFile::RemoveChunk() - Chunk is still used [this=%p, chunk=%p, "
+           "refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
+
+      // somebody got the reference before the lock was acquired
+      return NS_OK;
+    }
+
+#ifdef DEBUG
+    {
+      // We can be here iff the chunk is in the hash table
+      nsRefPtr<CacheFileChunk> chunkCheck;
+      mChunks.Get(chunk->Index(), getter_AddRefs(chunkCheck));
+      MOZ_ASSERT(chunkCheck == chunk);
+
+      // We also shouldn't have any queued listener for this chunk
+      ChunkListeners *listeners;
+      mChunkListeners.Get(chunk->Index(), &listeners);
+      MOZ_ASSERT(!listeners);
+    }
+#endif
+
+    if (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) {
+      LOG(("CacheFile::RemoveChunk() - Writing dirty chunk to the disk "
+           "[this=%p]", this));
+
+      mDataIsDirty = true;
+
+      rv = chunk->Write(mHandle, this);
+      if (NS_FAILED(rv)) {
+        // TODO ??? doom entry
+        // TODO mark this chunk as memory only, since it wasn't written to disk
+        // and therefore cannot be released from memory
+        // LOG
+      }
+      else {
+        // Chunk will be removed in OnChunkWritten if it is still unused
+
+        // chunk needs to be released under the lock to be able to rely on
+        // CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten()
+        chunk = nullptr;
+        return NS_OK;
+      }
+    }
+
+    LOG(("CacheFile::RemoveChunk() - Caching unused chunk [this=%p, chunk=%p]",
+         this, chunk.get()));
+
+    chunk->mRemovingChunk = true;
+    ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
+                         chunk->mFile.forget().get()));
+    mCachedChunks.Put(chunk->Index(), chunk);
+    mChunks.Remove(chunk->Index());
+    if (!mMemoryOnly)
+      WriteMetadataIfNeeded();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::RemoveInput(CacheFileInputStream *aInput)
+{
+  CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::RemoveInput() [this=%p, input=%p]", this, aInput));
+
+  DebugOnly<bool> found;
+  found = mInputs.RemoveElement(aInput);
+  MOZ_ASSERT(found);
+
+  ReleaseOutsideLock(static_cast<nsIInputStream*>(aInput));
+
+  if (!mMemoryOnly)
+    WriteMetadataIfNeeded();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::RemoveOutput(CacheFileOutputStream *aOutput)
+{
+  AssertOwnsLock();
+
+  LOG(("CacheFile::RemoveOutput() [this=%p, output=%p]", this, aOutput));
+
+  if (mOutput != aOutput) {
+    LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring"
+         " call [this=%p]", this));
+    return NS_OK;
+  }
+
+  mOutput = nullptr;
+
+  // Cancel all queued chunk and update listeners that cannot be satisfied
+  NotifyListenersAboutOutputRemoval();
+
+  if (!mMemoryOnly)
+    WriteMetadataIfNeeded();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::NotifyChunkListener(CacheFileChunkListener *aCallback,
+                               nsIEventTarget *aTarget,
+                               nsresult aResult,
+                               uint32_t aChunkIdx,
+                               CacheFileChunk *aChunk)
+{
+  LOG(("CacheFile::NotifyChunkListener() [this=%p, listener=%p, target=%p, "
+       "rv=0x%08x, idx=%d, chunk=%p]", this, aCallback, aTarget, aResult,
+       aChunkIdx, aChunk));
+
+  nsresult rv;
+  nsRefPtr<NotifyChunkListenerEvent> ev;
+  ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk);
+  if (aTarget)
+    rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+  else
+    rv = NS_DispatchToCurrentThread(ev);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::QueueChunkListener(uint32_t aIndex,
+                              CacheFileChunkListener *aCallback)
+{
+  LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%d, listener=%p]",
+       this, aIndex, aCallback));
+
+  AssertOwnsLock();
+
+  MOZ_ASSERT(aCallback);
+
+  ChunkListenerItem *item = new ChunkListenerItem();
+  item->mTarget = NS_GetCurrentThread();
+  item->mCallback = aCallback;
+
+  ChunkListeners *listeners;
+  if (!mChunkListeners.Get(aIndex, &listeners)) {
+    listeners = new ChunkListeners();
+    mChunkListeners.Put(aIndex, listeners);
+  }
+
+  listeners->mItems.AppendElement(item);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
+                                CacheFileChunk *aChunk)
+{
+  LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%d, rv=0x%08x, "
+       "chunk=%p]", this, aIndex, aResult, aChunk));
+
+  AssertOwnsLock();
+
+  nsresult rv, rv2;
+
+  ChunkListeners *listeners;
+  mChunkListeners.Get(aIndex, &listeners);
+  MOZ_ASSERT(listeners);
+
+  rv = NS_OK;
+  for (uint32_t i = 0 ; i < listeners->mItems.Length() ; i++) {
+    ChunkListenerItem *item = listeners->mItems[i];
+    rv2 = NotifyChunkListener(item->mCallback, item->mTarget, aResult, aIndex,
+                              aChunk);
+    if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
+      rv = rv2;
+    delete item;
+  }
+
+  mChunkListeners.Remove(aIndex);
+
+  return rv;
+}
+
+bool
+CacheFile::HaveChunkListeners(uint32_t aIndex)
+{
+  ChunkListeners *listeners;
+  mChunkListeners.Get(aIndex, &listeners);
+  return !!listeners;
+}
+
+void
+CacheFile::NotifyListenersAboutOutputRemoval()
+{
+  LOG(("CacheFile::NotifyListenersAboutOutputRemoval() [this=%p]", this));
+
+  AssertOwnsLock();
+
+  // First fail all chunk listeners that wait for non-existent chunk
+  mChunkListeners.Enumerate(&CacheFile::FailListenersIfNonExistentChunk,
+                            this);
+
+  // Fail all update listeners
+  mChunks.Enumerate(&CacheFile::FailUpdateListeners, this);
+}
+
+bool
+CacheFile::DataSize(int64_t* aSize)
+{
+  CacheFileAutoLock lock(this);
+
+  if (mOutput)
+    return false;
+
+  *aSize = mDataSize;
+  return true;
+}
+
+bool
+CacheFile::IsDirty()
+{
+  return mDataIsDirty || mMetadata->IsDirty();
+}
+
+void
+CacheFile::WriteMetadataIfNeeded()
+{
+  LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this));
+
+  nsresult rv;
+
+  AssertOwnsLock();
+  MOZ_ASSERT(!mMemoryOnly);
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  if (NS_FAILED(mStatus))
+    return;
+
+  if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() ||
+      mWritingMetadata || mOpeningFile)
+    return;
+
+  LOG(("CacheFile::WriteMetadataIfNeeded() - Writing metadata [this=%p]",
+       this));
+
+  nsRefPtr<MetadataListenerHelper> mlh = new MetadataListenerHelper(this);
+
+  rv = mMetadata->WriteMetadata(mDataSize, mlh);
+  if (NS_SUCCEEDED(rv)) {
+    mWritingMetadata = true;
+    mDataIsDirty = false;
+  }
+  else {
+    LOG(("CacheFile::WriteMetadataIfNeeded() - Writing synchronously failed "
+         "[this=%p]", this));
+    // TODO: close streams with error
+    if (NS_SUCCEEDED(mStatus))
+      mStatus = rv;
+  }
+}
+
+void
+CacheFile::PostWriteTimer()
+{
+  LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
+
+  nsresult rv;
+
+  AssertOwnsLock();
+
+  if (mTimer) {
+    if (mTimer->ShouldFireNew()) {
+      LOG(("CacheFile::PostWriteTimer() - Canceling old timer [this=%p]",
+           this));
+      mTimer->Cancel();
+      mTimer = nullptr;
+    }
+    else {
+      LOG(("CacheFile::PostWriteTimer() - Keeping old timer [this=%p]", this));
+      return;
+    }
+  }
+
+  mTimer = new MetadataWriteTimer(this);
+
+  rv = mTimer->Fire();
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFile::PostWriteTimer() - Firing timer failed with error 0x%08x "
+         "[this=%p]", rv, this));
+  }
+}
+
+PLDHashOperator
+CacheFile::WriteAllCachedChunks(const uint32_t& aIdx,
+                                nsRefPtr<CacheFileChunk>& aChunk,
+                                void* aClosure)
+{
+  CacheFile *file = static_cast<CacheFile*>(aClosure);
+
+  LOG(("CacheFile::WriteAllCachedChunks() [this=%p, idx=%d, chunk=%p]",
+       file, aIdx, aChunk.get()));
+
+  file->mChunks.Put(aIdx, aChunk);
+  aChunk->mFile = file;
+  aChunk->mRemovingChunk = false;
+
+  MOZ_ASSERT(aChunk->IsReady());
+
+  NS_ADDREF(aChunk);
+  file->ReleaseOutsideLock(aChunk);
+
+  return PL_DHASH_REMOVE;
+}
+
+PLDHashOperator
+CacheFile::FailListenersIfNonExistentChunk(
+  const uint32_t& aIdx,
+  nsAutoPtr<ChunkListeners>& aListeners,
+  void* aClosure)
+{
+  CacheFile *file = static_cast<CacheFile*>(aClosure);
+
+  LOG(("CacheFile::FailListenersIfNonExistentChunk() [this=%p, idx=%d]",
+       file, aIdx));
+
+  nsRefPtr<CacheFileChunk> chunk;
+  file->mChunks.Get(aIdx, getter_AddRefs(chunk));
+  if (chunk) {
+    MOZ_ASSERT(!chunk->IsReady());
+    return PL_DHASH_NEXT;
+  }
+
+  for (uint32_t i = 0 ; i < aListeners->mItems.Length() ; i++) {
+    ChunkListenerItem *item = aListeners->mItems[i];
+    file->NotifyChunkListener(item->mCallback, item->mTarget,
+                              NS_ERROR_NOT_AVAILABLE, aIdx, nullptr);
+    delete item;
+  }
+
+  return PL_DHASH_REMOVE;
+}
+
+PLDHashOperator
+CacheFile::FailUpdateListeners(
+  const uint32_t& aIdx,
+  nsRefPtr<CacheFileChunk>& aChunk,
+  void* aClosure)
+{
+#ifdef PR_LOGGING
+  CacheFile *file = static_cast<CacheFile*>(aClosure);
+#endif
+
+  LOG(("CacheFile::FailUpdateListeners() [this=%p, idx=%d]",
+       file, aIdx));
+
+  if (aChunk->IsReady()) {
+    aChunk->NotifyUpdateListeners();
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+CacheFile::PadChunkWithZeroes(uint32_t aChunkIdx)
+{
+  AssertOwnsLock();
+
+  // This method is used to pad last incomplete chunk with zeroes or create
+  // a new chunk full of zeroes
+  MOZ_ASSERT(mDataSize / kChunkSize == aChunkIdx);
+
+  nsresult rv;
+  nsRefPtr<CacheFileChunk> chunk;
+  rv = GetChunkLocked(aChunkIdx, true, nullptr, getter_AddRefs(chunk));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  LOG(("CacheFile::PadChunkWithZeroes() - Zeroing hole in chunk %d, range %d-%d"
+       " [this=%p]", aChunkIdx, chunk->DataSize(), kChunkSize - 1, this));
+
+  chunk->EnsureBufSize(kChunkSize);
+  memset(chunk->BufForWriting() + chunk->DataSize(), 0, kChunkSize - chunk->DataSize());
+
+  chunk->UpdateDataSize(chunk->DataSize(), kChunkSize - chunk->DataSize(),
+                        false);
+
+  ReleaseOutsideLock(chunk.forget().get());
+
+  return NS_OK;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFile.h
@@ -0,0 +1,217 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFile__h__
+#define CacheFile__h__
+
+#include "CacheFileChunk.h"
+#include "nsWeakReference.h"
+#include "CacheFileIOManager.h"
+#include "CacheFileMetadata.h"
+#include "nsRefPtrHashtable.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Mutex.h"
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla {
+namespace net {
+
+class CacheFileInputStream;
+class CacheFileOutputStream;
+class MetadataWriteTimer;
+
+#define CACHEFILELISTENER_IID \
+{ /* 95e7f284-84ba-48f9-b1fc-3a7336b4c33c */       \
+  0x95e7f284,                                      \
+  0x84ba,                                          \
+  0x48f9,                                          \
+  {0xb1, 0xfc, 0x3a, 0x73, 0x36, 0xb4, 0xc3, 0x3c} \
+}
+
+class CacheFileListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILELISTENER_IID)
+
+  NS_IMETHOD OnFileReady(nsresult aResult, bool aIsNew) = 0;
+  NS_IMETHOD OnFileDoomed(nsresult aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileListener, CACHEFILELISTENER_IID)
+
+
+class CacheFile : public CacheFileChunkListener
+                , public CacheFileIOListener
+                , public CacheFileMetadataListener
+                , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheFile();
+
+  nsresult Init(const nsACString &aKey,
+                bool aCreateNew,
+                bool aMemoryOnly,
+                bool aPriority,
+                bool aKeyIsHash,
+                CacheFileListener *aCallback);
+
+  NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                              CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk);
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult);
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult);
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult);
+
+  NS_IMETHOD OnMetadataRead(nsresult aResult);
+  NS_IMETHOD OnMetadataWritten(nsresult aResult);
+
+  NS_IMETHOD OpenInputStream(nsIInputStream **_retval);
+  NS_IMETHOD OpenOutputStream(nsIOutputStream **_retval);
+  NS_IMETHOD SetMemoryOnly();
+  NS_IMETHOD Doom(CacheFileListener *aCallback);
+
+  nsresult   ThrowMemoryCachedData();
+
+  // metadata forwarders
+  nsresult GetElement(const char *aKey, const char **_retval);
+  nsresult SetElement(const char *aKey, const char *aValue);
+  nsresult ElementsSize(uint32_t *_retval);
+  nsresult SetExpirationTime(uint32_t aExpirationTime);
+  nsresult GetExpirationTime(uint32_t *_retval);
+  nsresult SetLastModified(uint32_t aLastModified);
+  nsresult GetLastModified(uint32_t *_retval);
+  nsresult GetLastFetched(uint32_t *_retval);
+  nsresult GetFetchCount(uint32_t *_retval);
+
+  bool DataSize(int64_t* aSize);
+  void Key(nsACString& aKey) { aKey = mKey; }
+
+private:
+  friend class CacheFileChunk;
+  friend class CacheFileInputStream;
+  friend class CacheFileOutputStream;
+  friend class CacheFileAutoLock;
+  friend class MetadataWriteTimer;
+  friend class MetadataListenerHelper;
+
+  virtual ~CacheFile();
+
+  void     Lock();
+  void     Unlock();
+  void     AssertOwnsLock();
+  void     ReleaseOutsideLock(nsISupports *aObject);
+
+  nsresult GetChunk(uint32_t aIndex, bool aWriter,
+                    CacheFileChunkListener *aCallback,
+                    CacheFileChunk **_retval);
+  nsresult GetChunkLocked(uint32_t aIndex, bool aWriter,
+                          CacheFileChunkListener *aCallback,
+                          CacheFileChunk **_retval);
+  nsresult RemoveChunk(CacheFileChunk *aChunk);
+
+  nsresult RemoveInput(CacheFileInputStream *aInput);
+  nsresult RemoveOutput(CacheFileOutputStream *aOutput);
+  nsresult NotifyChunkListener(CacheFileChunkListener *aCallback,
+                               nsIEventTarget *aTarget,
+                               nsresult aResult,
+                               uint32_t aChunkIdx,
+                               CacheFileChunk *aChunk);
+  nsresult QueueChunkListener(uint32_t aIndex,
+                              CacheFileChunkListener *aCallback);
+  nsresult NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
+                                CacheFileChunk *aChunk);
+  bool     HaveChunkListeners(uint32_t aIndex);
+  void     NotifyListenersAboutOutputRemoval();
+
+  bool IsDirty();
+  void WriteMetadataIfNeeded();
+  void PostWriteTimer();
+
+  static PLDHashOperator WriteAllCachedChunks(const uint32_t& aIdx,
+                                              nsRefPtr<CacheFileChunk>& aChunk,
+                                              void* aClosure);
+
+  static PLDHashOperator FailListenersIfNonExistentChunk(
+                           const uint32_t& aIdx,
+                           nsAutoPtr<mozilla::net::ChunkListeners>& aListeners,
+                           void* aClosure);
+
+  static PLDHashOperator FailUpdateListeners(const uint32_t& aIdx,
+                                             nsRefPtr<CacheFileChunk>& aChunk,
+                                             void* aClosure);
+
+  nsresult PadChunkWithZeroes(uint32_t aChunkIdx);
+
+  mozilla::Mutex mLock;
+  bool           mOpeningFile;
+  bool           mReady;
+  bool           mMemoryOnly;
+  bool           mDataAccessed;
+  bool           mDataIsDirty;
+  bool           mWritingMetadata;
+  bool           mKeyIsHash;
+  nsresult       mStatus;
+  int64_t        mDataSize;
+  nsCString      mKey;
+
+  nsRefPtr<CacheFileHandle>    mHandle;
+  nsRefPtr<CacheFileMetadata>  mMetadata;
+  nsCOMPtr<CacheFileListener>  mListener;
+  nsRefPtr<MetadataWriteTimer> mTimer;
+
+  nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks;
+  nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners;
+  nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks;
+
+  nsTArray<CacheFileInputStream*> mInputs;
+  CacheFileOutputStream          *mOutput;
+
+  nsTArray<nsISupports*>          mObjsToRelease;
+};
+
+class CacheFileAutoLock {
+public:
+  CacheFileAutoLock(CacheFile *aFile)
+    : mFile(aFile)
+    , mLocked(true)
+  {
+    mFile->Lock();
+  }
+  ~CacheFileAutoLock()
+  {
+    if (mLocked)
+      mFile->Unlock();
+  }
+  void Lock()
+  {
+    MOZ_ASSERT(!mLocked);
+    mFile->Lock();
+    mLocked = true;
+  }
+  void Unlock()
+  {
+    MOZ_ASSERT(mLocked);
+    mFile->Unlock();
+    mLocked = false;
+  }
+
+private:
+  nsRefPtr<CacheFile> mFile;
+  bool mLocked;
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -0,0 +1,675 @@
+/* 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 "CacheFileChunk.h"
+
+#include "CacheLog.h"
+#include "CacheFile.h"
+#include "nsThreadUtils.h"
+#include "nsAlgorithm.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+#define kMinBufSize        512
+
+class NotifyUpdateListenerEvent : public nsRunnable {
+public:
+  NotifyUpdateListenerEvent(CacheFileChunkListener *aCallback,
+                            CacheFileChunk *aChunk)
+    : mCallback(aCallback)
+    , mChunk(aChunk)
+  {
+    LOG(("NotifyUpdateListenerEvent::NotifyUpdateListenerEvent() [this=%p]",
+         this));
+    MOZ_COUNT_CTOR(NotifyUpdateListenerEvent);
+  }
+
+  ~NotifyUpdateListenerEvent()
+  {
+    LOG(("NotifyUpdateListenerEvent::~NotifyUpdateListenerEvent() [this=%p]",
+         this));
+    MOZ_COUNT_DTOR(NotifyUpdateListenerEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    LOG(("NotifyUpdateListenerEvent::Run() [this=%p]", this));
+
+    mCallback->OnChunkUpdated(mChunk);
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<CacheFileChunkListener> mCallback;
+  nsRefPtr<CacheFileChunk>         mChunk;
+};
+
+
+class ValidityPair {
+public:
+  ValidityPair(uint32_t aOffset, uint32_t aLen)
+    : mOffset(aOffset), mLen(aLen)
+  {}
+
+  ValidityPair& operator=(const ValidityPair& aOther) {
+    mOffset = aOther.mOffset;
+    mLen = aOther.mLen;
+    return *this;
+  }
+
+  bool Overlaps(const ValidityPair& aOther) const {
+    if ((mOffset <= aOther.mOffset && mOffset + mLen >= aOther.mOffset) ||
+        (aOther.mOffset <= mOffset && aOther.mOffset + mLen >= mOffset))
+      return true;
+
+    return false;
+  }
+
+  bool LessThan(const ValidityPair& aOther) const {
+    if (mOffset < aOther.mOffset)
+      return true;
+
+    if (mOffset == aOther.mOffset && mLen < aOther.mLen)
+      return true;
+
+    return false;
+  }
+
+  void Merge(const ValidityPair& aOther) {
+    MOZ_ASSERT(Overlaps(aOther));
+
+    uint32_t offset = std::min(mOffset, aOther.mOffset);
+    uint32_t end = std::max(mOffset + mLen, aOther.mOffset + aOther.mLen);
+
+    mOffset = offset;
+    mLen = end - offset;
+  }
+
+  uint32_t Offset() { return mOffset; }
+  uint32_t Len()    { return mLen; }
+
+private:
+  uint32_t mOffset;
+  uint32_t mLen;
+};
+
+
+NS_IMPL_ADDREF(CacheFileChunk)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFileChunk::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFileChunk");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    delete (this);
+    return 0;
+  }
+
+  if (!mRemovingChunk && count == 1) {
+    mFile->RemoveChunk(this);
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileChunk)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFileChunk::CacheFileChunk(CacheFile *aFile, uint32_t aIndex)
+  : mIndex(aIndex)
+  , mState(INITIAL)
+  , mIsDirty(false)
+  , mRemovingChunk(false)
+  , mDataSize(0)
+  , mBuf(nullptr)
+  , mBufSize(0)
+  , mRWBuf(nullptr)
+  , mRWBufSize(0)
+  , mReadHash(0)
+  , mFile(aFile)
+{
+  LOG(("CacheFileChunk::CacheFileChunk() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileChunk);
+}
+
+CacheFileChunk::~CacheFileChunk()
+{
+  LOG(("CacheFileChunk::~CacheFileChunk() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileChunk);
+
+  if (mBuf) {
+    free(mBuf);
+    mBuf = nullptr;
+    mBufSize = 0;
+  }
+
+  if (mRWBuf) {
+    free(mRWBuf);
+    mRWBuf = nullptr;
+    mRWBufSize = 0;
+  }
+
+  DoMemoryReport(MemorySize());
+}
+
+void
+CacheFileChunk::InitNew(CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::InitNew() [this=%p, listener=%p]", this, aCallback));
+
+  MOZ_ASSERT(mState == INITIAL);
+  MOZ_ASSERT(!mBuf);
+  MOZ_ASSERT(!mRWBuf);
+
+  mBuf = static_cast<char *>(moz_xmalloc(kMinBufSize));
+  mBufSize = kMinBufSize;
+  mDataSize = 0;
+  mState = READY;
+  mIsDirty = true;
+
+  DoMemoryReport(MemorySize());
+}
+
+nsresult
+CacheFileChunk::Read(CacheFileHandle *aHandle, uint32_t aLen,
+                     CacheHashUtils::Hash16_t aHash,
+                     CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::Read() [this=%p, handle=%p, len=%d, listener=%p]",
+       this, aHandle, aLen, aCallback));
+
+  MOZ_ASSERT(mState == INITIAL);
+  MOZ_ASSERT(!mBuf);
+  MOZ_ASSERT(!mRWBuf);
+  MOZ_ASSERT(aLen);
+
+  nsresult rv;
+
+  mRWBuf = static_cast<char *>(moz_xmalloc(aLen));
+  mRWBufSize = aLen;
+
+  DoMemoryReport(MemorySize());
+
+  rv = CacheFileIOManager::Read(aHandle, mIndex * kChunkSize, mRWBuf, aLen,
+                                this);
+  if (NS_FAILED(rv)) {
+    mState = READING;   // TODO: properly handle error states
+//    mState = ERROR;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mState = READING;
+  mListener = aCallback;
+  mDataSize = aLen;
+  mReadHash = aHash;
+  return NS_OK;
+}
+
+nsresult
+CacheFileChunk::Write(CacheFileHandle *aHandle,
+                      CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::Write() [this=%p, handle=%p, listener=%p]",
+       this, aHandle, aCallback));
+
+  MOZ_ASSERT(mState == READY);
+  MOZ_ASSERT(!mRWBuf);
+  MOZ_ASSERT(mBuf);
+  MOZ_ASSERT(mDataSize); // Don't write chunk when it is empty
+
+  nsresult rv;
+
+  mRWBuf = mBuf;
+  mRWBufSize = mBufSize;
+  mBuf = nullptr;
+  mBufSize = 0;
+
+  rv = CacheFileIOManager::Write(aHandle, mIndex * kChunkSize, mRWBuf,
+                                 mDataSize, false, this);
+  if (NS_FAILED(rv)) {
+    mState = WRITING;   // TODO: properly handle error states
+//    mState = ERROR;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mState = WRITING;
+  mListener = aCallback;
+  mIsDirty = false;
+  return NS_OK;
+}
+
+void
+CacheFileChunk::WaitForUpdate(CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::WaitForUpdate() [this=%p, listener=%p]",
+       this, aCallback));
+
+  MOZ_ASSERT(mFile->mOutput);
+  MOZ_ASSERT(IsReady());
+
+#ifdef DEBUG
+  for (uint32_t i = 0 ; i < mUpdateListeners.Length() ; i++) {
+    MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback);
+  }
+#endif
+
+  ChunkListenerItem *item = new ChunkListenerItem();
+  item->mTarget = NS_GetCurrentThread();
+  item->mCallback = aCallback;
+
+  mUpdateListeners.AppendElement(item);
+}
+
+nsresult
+CacheFileChunk::CancelWait(CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::CancelWait() [this=%p, listener=%p]", this, aCallback));
+
+  MOZ_ASSERT(IsReady());
+
+  uint32_t i;
+  for (i = 0 ; i < mUpdateListeners.Length() ; i++) {
+    ChunkListenerItem *item = mUpdateListeners[i];
+
+    if (item->mCallback == aCallback) {
+      mUpdateListeners.RemoveElementAt(i);
+      delete item;
+      break;
+    }
+  }
+
+#ifdef DEBUG
+  for ( ; i < mUpdateListeners.Length() ; i++) {
+    MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback);
+  }
+#endif
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileChunk::NotifyUpdateListeners()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::NotifyUpdateListeners() [this=%p]", this));
+
+  MOZ_ASSERT(IsReady());
+
+  nsresult rv, rv2;
+
+  rv = NS_OK;
+  for (uint32_t i = 0 ; i < mUpdateListeners.Length() ; i++) {
+    ChunkListenerItem *item = mUpdateListeners[i];
+
+    LOG(("CacheFileChunk::NotifyUpdateListeners() - Notifying listener %p "
+         "[this=%p]", item->mCallback.get(), this));
+
+    nsRefPtr<NotifyUpdateListenerEvent> ev;
+    ev = new NotifyUpdateListenerEvent(item->mCallback, this);
+    rv2 = item->mTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+    if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
+      rv = rv2;
+    delete item;
+  }
+
+  mUpdateListeners.Clear();
+
+  return rv;
+}
+
+uint32_t
+CacheFileChunk::Index()
+{
+  return mIndex;
+}
+
+CacheHashUtils::Hash16_t
+CacheFileChunk::Hash()
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(mBuf);
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(IsReady());
+
+  return CacheHashUtils::Hash16(BufForReading(), mDataSize);
+}
+
+uint32_t
+CacheFileChunk::DataSize()
+{
+  mFile->AssertOwnsLock();
+  return mDataSize;
+}
+
+void
+CacheFileChunk::UpdateDataSize(uint32_t aOffset, uint32_t aLen, bool aEOF)
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(!aEOF, "Implement me! What to do with opened streams?");
+  MOZ_ASSERT(aOffset <= mDataSize);
+
+  LOG(("CacheFileChunk::UpdateDataSize() [this=%p, offset=%d, len=%d, EOF=%d]",
+       this, aOffset, aLen, aEOF));
+
+  mIsDirty = true;
+
+  int64_t fileSize = kChunkSize * mIndex + aOffset + aLen;
+  bool notify = false;
+
+  if (fileSize > mFile->mDataSize)
+    mFile->mDataSize = fileSize;
+
+  if (aOffset + aLen > mDataSize) {
+    mDataSize = aOffset + aLen;
+    notify = true;
+  }
+
+  if (mState == READY || mState == WRITING) {
+    MOZ_ASSERT(mValidityMap.Length() == 0);
+
+    if (notify)
+      NotifyUpdateListeners();
+
+    return;
+  }
+
+  // We're still waiting for data from the disk. This chunk cannot be used by
+  // input stream, so there must be no update listener. We also need to keep
+  // track of where the data is written so that we can correctly merge the new
+  // data with the old one.
+
+  MOZ_ASSERT(mUpdateListeners.Length() == 0);
+  MOZ_ASSERT(mState == READING);
+
+  ValidityPair pair(aOffset, aLen);
+
+  if (mValidityMap.Length() == 0) {
+    mValidityMap.AppendElement(pair);
+    return;
+  }
+
+
+  // Find out where to place this pair into the map, it can overlap with
+  // one preceding pair and all subsequent pairs.
+  uint32_t pos = 0;
+  for (pos = mValidityMap.Length() ; pos > 0 ; pos--) {
+    if (mValidityMap[pos-1].LessThan(pair)) {
+      if (mValidityMap[pos-1].Overlaps(pair)) {
+        // Merge with the preceding pair
+        mValidityMap[pos-1].Merge(pair);
+        pos--; // Point to the updated pair
+      }
+      else {
+        if (pos == mValidityMap.Length())
+          mValidityMap.AppendElement(pair);
+        else
+          mValidityMap.InsertElementAt(pos, pair);
+      }
+
+      break;
+    }
+  }
+
+  if (!pos)
+    mValidityMap.InsertElementAt(0, pair);
+
+  // Now pos points to merged or inserted pair, check whether it overlaps with
+  // subsequent pairs.
+  while (pos + 1 < mValidityMap.Length()) {
+    if (mValidityMap[pos].Overlaps(mValidityMap[pos + 1])) {
+      mValidityMap[pos].Merge(mValidityMap[pos + 1]);
+      mValidityMap.RemoveElementAt(pos + 1);
+    }
+    else {
+      break;
+    }
+  }
+}
+
+nsresult
+CacheFileChunk::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileChunk::OnFileOpened should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileChunk::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                              nsresult aResult)
+{
+  LOG(("CacheFileChunk::OnDataWritten() [this=%p, handle=%p, result=0x%08x]",
+       this, aHandle, aResult));
+
+  nsCOMPtr<CacheFileChunkListener> listener;
+
+  {
+    CacheFileAutoLock lock(mFile);
+
+    MOZ_ASSERT(mState == WRITING);
+    MOZ_ASSERT(mListener);
+
+#if 0
+    // TODO: properly handle error states
+    if (NS_FAILED(aResult)) {
+      mState = ERROR;
+    }
+    else {
+#endif
+      mState = READY;
+      if (!mBuf) {
+        mBuf = mRWBuf;
+        mBufSize = mRWBufSize;
+      }
+      else {
+        free(mRWBuf);
+      }
+
+      mRWBuf = nullptr;
+      mRWBufSize = 0;
+
+      DoMemoryReport(MemorySize());
+#if 0
+    }
+#endif
+
+    mListener.swap(listener);
+  }
+
+  listener->OnChunkWritten(aResult, this);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileChunk::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
+                           nsresult aResult)
+{
+  LOG(("CacheFileChunk::OnDataRead() [this=%p, handle=%p, result=0x%08x]",
+       this, aHandle, aResult));
+
+  nsCOMPtr<CacheFileChunkListener> listener;
+
+  {
+    CacheFileAutoLock lock(mFile);
+
+    MOZ_ASSERT(mState == READING);
+    MOZ_ASSERT(mListener);
+
+    if (NS_SUCCEEDED(aResult)) {
+      CacheHashUtils::Hash16_t hash = CacheHashUtils::Hash16(mRWBuf,
+                                                             mRWBufSize);
+      if (hash != mReadHash) {
+        LOG(("CacheFileChunk::OnDataRead() - Hash mismatch! Hash of the data is"
+             " %hx, hash in metadata is %hx. [this=%p, idx=%d]",
+             hash, mReadHash, this, mIndex));
+        aResult = NS_ERROR_FILE_CORRUPTED;
+      }
+      else {
+        if (!mBuf) {
+          // Just swap the buffers if we don't have mBuf yet
+          MOZ_ASSERT(mDataSize == mRWBufSize);
+          mBuf = mRWBuf;
+          mBufSize = mRWBufSize;
+          mRWBuf = nullptr;
+          mRWBufSize = 0;
+        } else {
+          // Merge data with write buffer
+          if (mRWBufSize < mBufSize) {
+            mRWBuf = static_cast<char *>(moz_xrealloc(mRWBuf, mBufSize));
+            mRWBufSize = mBufSize;
+          }
+
+          for (uint32_t i = 0 ; i < mValidityMap.Length() ; i++) {
+            memcpy(mRWBuf + mValidityMap[i].Offset(),
+                   mBuf + mValidityMap[i].Offset(), mValidityMap[i].Len());
+          }
+
+          free(mBuf);
+          mBuf = mRWBuf;
+          mBufSize = mRWBufSize;
+          mRWBuf = nullptr;
+          mRWBufSize = 0;
+
+          DoMemoryReport(MemorySize());
+        }
+      }
+    }
+
+    if (NS_FAILED(aResult)) {
+#if 0
+      // TODO: properly handle error states
+      mState = ERROR;
+#endif
+      mState = READY;
+      mDataSize = 0;
+    }
+    else {
+      mState = READY;
+    }
+
+    mListener.swap(listener);
+  }
+
+  listener->OnChunkRead(aResult, this);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileChunk::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileChunk::OnFileDoomed should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileChunk::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileChunk::OnEOFSet should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+bool
+CacheFileChunk::IsReady()
+{
+  mFile->AssertOwnsLock();
+
+  return (mState == READY || mState == WRITING);
+}
+
+bool
+CacheFileChunk::IsDirty()
+{
+  mFile->AssertOwnsLock();
+
+  return mIsDirty;
+}
+
+char *
+CacheFileChunk::BufForWriting()
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(mBuf); // Writer should always first call EnsureBufSize()
+
+  MOZ_ASSERT((mState == READY && !mRWBuf) ||
+             (mState == WRITING && mRWBuf) ||
+             (mState == READING && mRWBuf));
+
+  return mBuf;
+}
+
+const char *
+CacheFileChunk::BufForReading()
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT((mState == READY && mBuf && !mRWBuf) ||
+             (mState == WRITING && mRWBuf));
+
+  return mBuf ? mBuf : mRWBuf;
+}
+
+void
+CacheFileChunk::EnsureBufSize(uint32_t aBufSize)
+{
+  mFile->AssertOwnsLock();
+
+  if (mBufSize >= aBufSize)
+    return;
+
+  bool copy = false;
+  if (!mBuf && mState == WRITING) {
+    // We need to duplicate the data that is being written on the background
+    // thread, so make sure that all the data fits into the new buffer.
+    copy = true;
+
+    if (mRWBufSize > aBufSize)
+      aBufSize = mRWBufSize;
+  }
+
+  // find smallest power of 2 greater than or equal to aBufSize
+  aBufSize--;
+  aBufSize |= aBufSize >> 1;
+  aBufSize |= aBufSize >> 2;
+  aBufSize |= aBufSize >> 4;
+  aBufSize |= aBufSize >> 8;
+  aBufSize |= aBufSize >> 16;
+  aBufSize++;
+
+  const uint32_t minBufSize = kMinBufSize;
+  const uint32_t maxBufSize = kChunkSize;
+  aBufSize = clamped(aBufSize, minBufSize, maxBufSize);
+
+  mBuf = static_cast<char *>(moz_xrealloc(mBuf, aBufSize));
+  mBufSize = aBufSize;
+
+  if (copy)
+    memcpy(mBuf, mRWBuf, mRWBufSize);
+
+  DoMemoryReport(MemorySize());
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.h
@@ -0,0 +1,143 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileChunk__h__
+#define CacheFileChunk__h__
+
+#include "CacheFileIOManager.h"
+#include "CacheStorageService.h"
+#include "CacheHashUtils.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+#define kChunkSize        16384
+#define kEmptyChunkHash   0xA8CA
+
+class CacheFileChunk;
+class CacheFile;
+class ValidityPair;
+
+
+#define CACHEFILECHUNKLISTENER_IID \
+{ /* baf16149-2ab5-499c-a9c2-5904eb95c288 */       \
+  0xbaf16149,                                      \
+  0x2ab5,                                          \
+  0x499c,                                          \
+  {0xa9, 0xc2, 0x59, 0x04, 0xeb, 0x95, 0xc2, 0x88} \
+}
+
+class CacheFileChunkListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILECHUNKLISTENER_IID)
+
+  NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) = 0;
+  NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) = 0;
+  NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                              CacheFileChunk *aChunk) = 0;
+  NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileChunkListener,
+                              CACHEFILECHUNKLISTENER_IID)
+
+
+class ChunkListenerItem {
+public:
+  ChunkListenerItem()  { MOZ_COUNT_CTOR(ChunkListenerItem); }
+  ~ChunkListenerItem() { MOZ_COUNT_DTOR(ChunkListenerItem); }
+
+  nsCOMPtr<nsIEventTarget>         mTarget;
+  nsCOMPtr<CacheFileChunkListener> mCallback;
+};
+
+class ChunkListeners {
+public:
+  ChunkListeners()  { MOZ_COUNT_CTOR(ChunkListeners); }
+  ~ChunkListeners() { MOZ_COUNT_DTOR(ChunkListeners); }
+
+  nsTArray<ChunkListenerItem *> mItems;
+};
+
+class CacheFileChunk : public CacheFileIOListener
+                     , public CacheMemoryConsumer
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheFileChunk(CacheFile *aFile, uint32_t aIndex);
+
+  void     InitNew(CacheFileChunkListener *aCallback);
+  nsresult Read(CacheFileHandle *aHandle, uint32_t aLen,
+                CacheHashUtils::Hash16_t aHash,
+                CacheFileChunkListener *aCallback);
+  nsresult Write(CacheFileHandle *aHandle, CacheFileChunkListener *aCallback);
+  void     WaitForUpdate(CacheFileChunkListener *aCallback);
+  nsresult CancelWait(CacheFileChunkListener *aCallback);
+  nsresult NotifyUpdateListeners();
+
+  uint32_t                 Index();
+  CacheHashUtils::Hash16_t Hash();
+  uint32_t                 DataSize();
+  void                     UpdateDataSize(uint32_t aOffset, uint32_t aLen,
+                                          bool aEOF);
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult);
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult);
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult);
+
+  bool   IsReady();
+  bool   IsDirty();
+
+  char *       BufForWriting();
+  const char * BufForReading();
+  void         EnsureBufSize(uint32_t aBufSize);
+  uint32_t     MemorySize() { return mRWBufSize + mBufSize; }
+
+private:
+  friend class CacheFileInputStream;
+  friend class CacheFileOutputStream;
+  friend class CacheFile;
+
+  virtual ~CacheFileChunk();
+
+  enum EState {
+    INITIAL = 0,
+    READING = 1,
+    WRITING = 2,
+    READY   = 3,
+    ERROR   = 4
+  };
+
+  uint32_t mIndex;
+  EState   mState;
+  bool     mIsDirty;
+  bool     mRemovingChunk;
+  uint32_t mDataSize;
+
+  char    *mBuf;
+  uint32_t mBufSize;
+
+  char                    *mRWBuf;
+  uint32_t                 mRWBufSize;
+  CacheHashUtils::Hash16_t mReadHash;
+
+  nsRefPtr<CacheFile>              mFile; // is null if chunk is cached to
+                                          // prevent reference cycles
+  nsCOMPtr<CacheFileChunkListener> mListener;
+  nsTArray<ChunkListenerItem *>    mUpdateListeners;
+  nsTArray<ValidityPair>           mValidityMap;
+};
+
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -0,0 +1,1780 @@
+/* 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 "CacheFileIOManager.h"
+
+#include "CacheLog.h"
+#include "../cache/nsCacheUtils.h"
+#include "CacheHashUtils.h"
+#include "CacheStorageService.h"
+#include "nsThreadUtils.h"
+#include "nsIFile.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "private/pprio.h"
+#include "mozilla/VisualEventTracer.h"
+
+// include files for ftruncate (or equivalent)
+#if defined(XP_UNIX)
+#include <unistd.h>
+#elif defined(XP_WIN)
+#include <windows.h>
+#undef CreateFile
+#undef CREATE_NEW
+#elif defined(XP_OS2)
+#define INCL_DOSERRORS
+#include <os2.h>
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+
+namespace mozilla {
+namespace net {
+
+#define kOpenHandlesLimit   64
+
+
+NS_IMPL_ADDREF(CacheFileHandle)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFileHandle::Release()
+{
+  LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get()));
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFileHandle");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    delete (this);
+    return 0;
+  }
+
+  if (!mRemovingHandle && count == 1 && !mClosed) {
+    CacheFileIOManager::gInstance->CloseHandle(this);
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash,
+                                 bool aPriority)
+  : mHash(aHash)
+  , mIsDoomed(false)
+  , mRemovingHandle(false)
+  , mPriority(aPriority)
+  , mClosed(false)
+  , mInvalid(false)
+  , mFileExists(false)
+  , mFileSize(-1)
+  , mFD(nullptr)
+{
+  LOG(("CacheFileHandle::CacheFileHandle() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileHandle);
+  PR_INIT_CLIST(this);
+}
+
+CacheFileHandle::~CacheFileHandle()
+{
+  LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileHandle);
+}
+
+
+/******************************************************************************
+ *  CacheFileHandles
+ *****************************************************************************/
+
+class CacheFileHandlesEntry : public PLDHashEntryHdr
+{
+public:
+  PRCList      *mHandles;
+  SHA1Sum::Hash mHash;
+};
+
+PLDHashTableOps CacheFileHandles::mOps =
+{
+  PL_DHashAllocTable,
+  PL_DHashFreeTable,
+  HashKey,
+  MatchEntry,
+  MoveEntry,
+  ClearEntry,
+  PL_DHashFinalizeStub
+};
+
+CacheFileHandles::CacheFileHandles()
+  : mInitialized(false)
+{
+  LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileHandles);
+}
+
+CacheFileHandles::~CacheFileHandles()
+{
+  LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileHandles);
+
+  if (mInitialized)
+    Shutdown();
+}
+
+nsresult
+CacheFileHandles::Init()
+{
+  LOG(("CacheFileHandles::Init() %p", this));
+
+  MOZ_ASSERT(!mInitialized);
+  mInitialized = PL_DHashTableInit(&mTable, &mOps, nullptr,
+                                   sizeof(CacheFileHandlesEntry), 512);
+
+  return mInitialized ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+void
+CacheFileHandles::Shutdown()
+{
+  LOG(("CacheFileHandles::Shutdown() %p", this));
+
+  if (mInitialized) {
+    PL_DHashTableFinish(&mTable);
+    mInitialized = false;
+  }
+}
+
+PLDHashNumber
+CacheFileHandles::HashKey(PLDHashTable *table, const void *key)
+{
+  const SHA1Sum::Hash *hash = static_cast<const SHA1Sum::Hash *>(key);
+  return static_cast<PLDHashNumber>(((*hash)[0] << 24) | ((*hash)[1] << 16) |
+                                    ((*hash)[2] << 8) | (*hash)[3]);
+}
+
+bool
+CacheFileHandles::MatchEntry(PLDHashTable *table,
+                             const PLDHashEntryHdr *header,
+                             const void *key)
+{
+  const CacheFileHandlesEntry *entry;
+
+  entry = static_cast<const CacheFileHandlesEntry *>(header);
+
+  return (memcmp(&entry->mHash, key, sizeof(SHA1Sum::Hash)) == 0);
+}
+
+void
+CacheFileHandles::MoveEntry(PLDHashTable *table,
+                            const PLDHashEntryHdr *from,
+                            PLDHashEntryHdr *to)
+{
+  const CacheFileHandlesEntry *src;
+  CacheFileHandlesEntry *dst;
+
+  src = static_cast<const CacheFileHandlesEntry *>(from);
+  dst = static_cast<CacheFileHandlesEntry *>(to);
+
+  dst->mHandles = src->mHandles;
+  memcpy(&dst->mHash, &src->mHash, sizeof(SHA1Sum::Hash));
+
+  LOG(("CacheFileHandles::MoveEntry() hash=%08x%08x%08x%08x%08x "
+       "moving from %p to %p", LOGSHA1(src->mHash), from, to));
+
+  // update pointer to mHash in all handles
+  CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(dst->mHandles);
+  while (handle != dst->mHandles) {
+    handle->mHash = &dst->mHash;
+    handle = (CacheFileHandle *)PR_NEXT_LINK(handle);
+  }
+}
+
+void
+CacheFileHandles::ClearEntry(PLDHashTable *table,
+                             PLDHashEntryHdr *header)
+{
+  CacheFileHandlesEntry *entry = static_cast<CacheFileHandlesEntry *>(header);
+  delete entry->mHandles;
+  entry->mHandles = nullptr;
+}
+
+nsresult
+CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash,
+                            CacheFileHandle **_retval)
+{
+  MOZ_ASSERT(mInitialized);
+
+  // find hash entry for key
+  CacheFileHandlesEntry *entry;
+  entry = static_cast<CacheFileHandlesEntry *>(
+    PL_DHashTableOperate(&mTable,
+                         (void *)aHash,
+                         PL_DHASH_LOOKUP));
+  if (PL_DHASH_ENTRY_IS_FREE(entry)) {
+    LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+         "no handle found", LOGSHA1(aHash)));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Check if the entry is doomed
+  CacheFileHandle *handle = static_cast<CacheFileHandle *>(
+                              PR_LIST_HEAD(entry->mHandles));
+  if (handle->IsDoomed()) {
+    LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+         "found doomed handle %p, entry %p", LOGSHA1(aHash), handle, entry));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+       "found handle %p, entry %p", LOGSHA1(aHash), handle, entry));
+  NS_ADDREF(*_retval = handle);
+  return NS_OK;
+}
+
+
+nsresult
+CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
+                            bool aPriority,
+                            CacheFileHandle **_retval)
+{
+  MOZ_ASSERT(mInitialized);
+
+  // find hash entry for key
+  CacheFileHandlesEntry *entry;
+  entry = static_cast<CacheFileHandlesEntry *>(
+    PL_DHashTableOperate(&mTable,
+                         (void *)aHash,
+                         PL_DHASH_ADD));
+  if (!entry) return NS_ERROR_OUT_OF_MEMORY;
+
+  if (!entry->mHandles) {
+    // new entry
+    entry->mHandles = new PRCList;
+    memcpy(&entry->mHash, aHash, sizeof(SHA1Sum::Hash));
+    PR_INIT_CLIST(entry->mHandles);
+
+    LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
+         "created new entry %p, list %p", LOGSHA1(aHash), entry,
+         entry->mHandles));
+  }
+#ifdef DEBUG
+  else {
+    MOZ_ASSERT(!PR_CLIST_IS_EMPTY(entry->mHandles));
+    CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles);
+    MOZ_ASSERT(handle->IsDoomed());
+  }
+#endif
+
+  nsRefPtr<CacheFileHandle> handle = new CacheFileHandle(&entry->mHash, aPriority);
+  PR_APPEND_LINK(handle, entry->mHandles);
+
+  LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
+       "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry));
+
+  NS_ADDREF(*_retval = handle);
+  handle.forget();
+  return NS_OK;
+}
+
+void
+CacheFileHandles::RemoveHandle(CacheFileHandle *aHandle)
+{
+  MOZ_ASSERT(mInitialized);
+
+  void *key = (void *)aHandle->Hash();
+
+  CacheFileHandlesEntry *entry;
+  entry = static_cast<CacheFileHandlesEntry *>(
+    PL_DHashTableOperate(&mTable, key, PL_DHASH_LOOKUP));
+
+  MOZ_ASSERT(PL_DHASH_ENTRY_IS_BUSY(entry));
+
+#ifdef DEBUG
+  {
+    CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles);
+    bool handleFound = false;
+    while (handle != entry->mHandles) {
+      if (handle == aHandle) {
+        handleFound = true;
+        break;
+      }
+      handle = (CacheFileHandle *)PR_NEXT_LINK(handle);
+    }
+    MOZ_ASSERT(handleFound);
+  }
+#endif
+
+  LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+       "removing handle %p", LOGSHA1(&entry->mHash), aHandle));
+
+  PR_REMOVE_AND_INIT_LINK(aHandle);
+  NS_RELEASE(aHandle);
+
+  if (PR_CLIST_IS_EMPTY(entry->mHandles)) {
+    LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+         "list %p is empty, removing entry %p", LOGSHA1(&entry->mHash),
+         entry->mHandles, entry));
+    PL_DHashTableOperate(&mTable, key, PL_DHASH_REMOVE);
+  }
+}
+
+PLDHashOperator
+GetHandles(PLDHashTable    *table,
+           PLDHashEntryHdr *hdr,
+           uint32_t         number,
+           void *           arg)
+{
+  const CacheFileHandlesEntry *entry;
+  nsTArray<nsRefPtr<CacheFileHandle> > *handles;
+
+  entry = static_cast<const CacheFileHandlesEntry *>(hdr);
+  handles = reinterpret_cast<nsTArray<nsRefPtr<CacheFileHandle> > *>(arg);
+
+  CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles);
+  while (handle != entry->mHandles) {
+    handles->AppendElement(handle);
+    handle = (CacheFileHandle *)PR_NEXT_LINK(handle);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+void
+CacheFileHandles::GetAllHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval)
+{
+  MOZ_ASSERT(mInitialized);
+
+  PL_DHashTableEnumerate(&mTable, GetHandles, _retval);
+}
+
+uint32_t
+CacheFileHandles::HandleCount()
+{
+  MOZ_ASSERT(mInitialized);
+
+  return mTable.entryCount;
+}
+
+// Events
+
+class ShutdownEvent : public nsRunnable {
+public:
+  ShutdownEvent(mozilla::Mutex *aLock, mozilla::CondVar *aCondVar)
+    : mLock(aLock)
+    , mCondVar(aCondVar)
+  {
+    MOZ_COUNT_CTOR(ShutdownEvent);
+  }
+
+  ~ShutdownEvent()
+  {
+    MOZ_COUNT_DTOR(ShutdownEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    MutexAutoLock lock(*mLock);
+
+    CacheFileIOManager::gInstance->ShutdownInternal();
+
+    mCondVar->Notify();
+    return NS_OK;
+  }
+
+protected:
+  mozilla::Mutex   *mLock;
+  mozilla::CondVar *mCondVar;
+};
+
+class OpenFileEvent : public nsRunnable {
+public:
+  OpenFileEvent(const nsACString &aKey,
+                uint32_t aFlags,
+                CacheFileIOListener *aCallback)
+    : mFlags(aFlags)
+    , mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+    , mKey(aKey)
+  {
+    MOZ_COUNT_CTOR(OpenFileEvent);
+
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+    mIOMan = CacheFileIOManager::gInstance;
+    MOZ_ASSERT(mTarget);
+
+    MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aKey.BeginReading());
+    MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::open-background");
+  }
+
+  ~OpenFileEvent()
+  {
+    MOZ_COUNT_DTOR(OpenFileEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      mRV = NS_OK;
+
+      if (mFlags & CacheFileIOManager::NOHASH) {
+        nsACString::const_char_iterator begin, end;
+        begin = mKey.BeginReading();
+        end = mKey.EndReading();
+        uint32_t i = 0;
+        while (begin != end && i < (SHA1Sum::HashSize << 1)) {
+          if (!(i & 1))
+            mHash[i >> 1] = 0;
+          uint8_t shift = (i & 1) ? 0 : 4;
+          if (*begin >= '0' && *begin <= '9')
+            mHash[i >> 1] |= (*begin - '0') << shift;
+          else if (*begin >= 'A' && *begin <= 'F')
+            mHash[i >> 1] |= (*begin - 'A' + 10) << shift;
+          else
+            break;
+
+          ++i;
+          ++begin;
+        }
+
+        if (i != (SHA1Sum::HashSize << 1) || begin != end)
+          mRV = NS_ERROR_INVALID_ARG;
+      }
+      else {
+        SHA1Sum sum;
+        sum.update(mKey.BeginReading(), mKey.Length());
+        sum.finish(mHash);
+      }
+
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::open-background");
+      if (NS_SUCCEEDED(mRV)) {
+        if (!mIOMan)
+          mRV = NS_ERROR_NOT_INITIALIZED;
+        else {
+          mRV = mIOMan->OpenFileInternal(&mHash, mFlags, getter_AddRefs(mHandle));
+          mIOMan = nullptr;
+          if (mHandle) {
+            MOZ_EVENT_TRACER_NAME_OBJECT(mHandle.get(), mKey.get());
+            mHandle->Key() = mKey;
+          }
+        }
+      }
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::open-background");
+
+      MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::open-result");
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::open-result");
+      mCallback->OnFileOpened(mHandle, mRV);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::open-result");
+    }
+    return NS_OK;
+  }
+
+protected:
+  SHA1Sum::Hash                 mHash;
+  uint32_t                      mFlags;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsRefPtr<CacheFileIOManager>  mIOMan;
+  nsRefPtr<CacheFileHandle>     mHandle;
+  nsresult                      mRV;
+  nsCString                     mKey;
+};
+
+class CloseHandleEvent : public nsRunnable {
+public:
+  CloseHandleEvent(CacheFileHandle *aHandle)
+    : mHandle(aHandle)
+  {
+    MOZ_COUNT_CTOR(CloseHandleEvent);
+  }
+
+  ~CloseHandleEvent()
+  {
+    MOZ_COUNT_DTOR(CloseHandleEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (!mHandle->IsClosed())
+      CacheFileIOManager::gInstance->CloseHandleInternal(mHandle);
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle> mHandle;
+};
+
+class ReadEvent : public nsRunnable {
+public:
+  ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf,
+            int32_t aCount, CacheFileIOListener *aCallback)
+    : mHandle(aHandle)
+    , mOffset(aOffset)
+    , mBuf(aBuf)
+    , mCount(aCount)
+    , mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(ReadEvent);
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+
+    MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
+    MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::read-background");
+  }
+
+  ~ReadEvent()
+  {
+    MOZ_COUNT_DTOR(ReadEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::read-background");
+      if (mHandle->IsClosed())
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else
+        mRV = CacheFileIOManager::gInstance->ReadInternal(
+          mHandle, mOffset, mBuf, mCount);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::read-background");
+
+      MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::read-result");
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::read-result");
+      if (mCallback)
+        mCallback->OnDataRead(mHandle, mBuf, mRV);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::read-result");
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle>     mHandle;
+  int64_t                       mOffset;
+  char                         *mBuf;
+  int32_t                       mCount;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsresult                      mRV;
+};
+
+class WriteEvent : public nsRunnable {
+public:
+  WriteEvent(CacheFileHandle *aHandle, int64_t aOffset, const char *aBuf,
+             int32_t aCount, bool aValidate, CacheFileIOListener *aCallback)
+    : mHandle(aHandle)
+    , mOffset(aOffset)
+    , mBuf(aBuf)
+    , mCount(aCount)
+    , mValidate(aValidate)
+    , mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(WriteEvent);
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+
+    MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
+    MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::write-background");
+  }
+
+  ~WriteEvent()
+  {
+    MOZ_COUNT_DTOR(WriteEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::write-background");
+      if (mHandle->IsClosed())
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else
+        mRV = CacheFileIOManager::gInstance->WriteInternal(
+          mHandle, mOffset, mBuf, mCount, mValidate);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::write-background");
+
+      MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::write-result");
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::write-result");
+      if (mCallback)
+        mCallback->OnDataWritten(mHandle, mBuf, mRV);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::write-result");
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle>     mHandle;
+  int64_t                       mOffset;
+  const char                   *mBuf;
+  int32_t                       mCount;
+  bool                          mValidate;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsresult                      mRV;
+};
+
+class DoomFileEvent : public nsRunnable {
+public:
+  DoomFileEvent(CacheFileHandle *aHandle,
+                CacheFileIOListener *aCallback)
+    : mCallback(aCallback)
+    , mHandle(aHandle)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(DoomFileEvent);
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+
+    MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
+    MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
+  }
+
+  ~DoomFileEvent()
+  {
+    MOZ_COUNT_DTOR(DoomFileEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
+      if (mHandle->IsClosed())
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else
+        mRV = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
+
+      MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
+      if (mCallback)
+        mCallback->OnFileDoomed(mHandle, mRV);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsRefPtr<CacheFileHandle>     mHandle;
+  nsresult                      mRV;
+};
+
+class DoomFileByKeyEvent : public nsRunnable {
+public:
+  DoomFileByKeyEvent(const nsACString &aKey,
+                     CacheFileIOListener *aCallback)
+    : mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(DoomFileByKeyEvent);
+
+    SHA1Sum sum;
+    sum.update(aKey.BeginReading(), aKey.Length());
+    sum.finish(mHash);
+
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+    mIOMan = CacheFileIOManager::gInstance;
+    MOZ_ASSERT(mTarget);
+  }
+
+  ~DoomFileByKeyEvent()
+  {
+    MOZ_COUNT_DTOR(DoomFileByKeyEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      if (!mIOMan)
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else {
+        mRV = mIOMan->DoomFileByKeyInternal(&mHash);
+        mIOMan = nullptr;
+      }
+
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      if (mCallback)
+        mCallback->OnFileDoomed(nullptr, mRV);
+    }
+    return NS_OK;
+  }
+
+protected:
+  SHA1Sum::Hash                 mHash;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsRefPtr<CacheFileIOManager>  mIOMan;
+  nsresult                      mRV;
+};
+
+class ReleaseNSPRHandleEvent : public nsRunnable {
+public:
+  ReleaseNSPRHandleEvent(CacheFileHandle *aHandle)
+    : mHandle(aHandle)
+  {
+    MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent);
+  }
+
+  ~ReleaseNSPRHandleEvent()
+  {
+    MOZ_COUNT_DTOR(ReleaseNSPRHandleEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mHandle->mFD && !mHandle->IsClosed())
+      CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle);
+
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle>     mHandle;
+};
+
+class TruncateSeekSetEOFEvent : public nsRunnable {
+public:
+  TruncateSeekSetEOFEvent(CacheFileHandle *aHandle, int64_t aTruncatePos,
+                          int64_t aEOFPos, CacheFileIOListener *aCallback)
+    : mHandle(aHandle)
+    , mTruncatePos(aTruncatePos)
+    , mEOFPos(aEOFPos)
+    , mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(TruncateSeekSetEOFEvent);
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+  }
+
+  ~TruncateSeekSetEOFEvent()
+  {
+    MOZ_COUNT_DTOR(TruncateSeekSetEOFEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      if (mHandle->IsClosed())
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else
+        mRV = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
+          mHandle, mTruncatePos, mEOFPos);
+
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      if (mCallback)
+        mCallback->OnEOFSet(mHandle, mRV);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle>     mHandle;
+  int64_t                       mTruncatePos;
+  int64_t                       mEOFPos;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsresult                      mRV;
+};
+
+
+CacheFileIOManager * CacheFileIOManager::gInstance = nullptr;
+
+NS_IMPL_ISUPPORTS0(CacheFileIOManager)
+
+CacheFileIOManager::CacheFileIOManager()
+  : mShuttingDown(false)
+  , mTreeCreated(false)
+{
+  LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileIOManager);
+  MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
+}
+
+CacheFileIOManager::~CacheFileIOManager()
+{
+  LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileIOManager);
+}
+
+nsresult
+CacheFileIOManager::Init()
+{
+  LOG(("CacheFileIOManager::Init()"));
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gInstance)
+    return NS_ERROR_ALREADY_INITIALIZED;
+
+  nsRefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();
+
+  nsresult rv = ioMan->InitInternal();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  ioMan.swap(gInstance);
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::InitInternal()
+{
+  nsresult rv;
+
+  mIOThread = new CacheIOThread();
+
+  rv = mIOThread->Init();
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mHandles.Init();
+  if (NS_FAILED(rv)) {
+    mIOThread->Shutdown();
+
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::Shutdown()
+{
+  LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance));
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gInstance)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  {
+    mozilla::Mutex lock("CacheFileIOManager::Shutdown() lock");
+    mozilla::CondVar condVar(lock, "CacheFileIOManager::Shutdown() condVar");
+
+    MutexAutoLock autoLock(lock);
+    nsRefPtr<ShutdownEvent> ev = new ShutdownEvent(&lock, &condVar);
+    DebugOnly<nsresult> rv;
+    rv = gInstance->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    condVar.Wait();
+  }
+
+  MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
+  MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);
+
+  nsRefPtr<CacheFileIOManager> ioMan;
+  ioMan.swap(gInstance);
+
+  if (ioMan->mIOThread)
+    ioMan->mIOThread->Shutdown();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::ShutdownInternal()
+{
+  mShuttingDown = true;
+
+  // close all handles and delete all associated files
+  nsTArray<nsRefPtr<CacheFileHandle> > handles;
+  mHandles.GetAllHandles(&handles);
+
+  for (uint32_t i=0 ; i<handles.Length() ; i++) {
+    CacheFileHandle *h = handles[i];
+    h->mRemovingHandle = true;
+    h->mClosed = true;
+
+    // Close file handle
+    if (h->mFD) {
+      ReleaseNSPRHandleInternal(h);
+    }
+
+    // Remove file if entry is doomed or invalid
+    if (h->mFileExists && (h->mIsDoomed || h->mInvalid)) {
+      h->mFile->Remove(false);
+    }
+
+    // Remove the handle from hashtable
+    mHandles.RemoveHandle(h);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::OnProfile()
+{
+  LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance));
+
+  MOZ_ASSERT(gInstance);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIFile> directory;
+
+#if defined(MOZ_WIDGET_ANDROID)
+  char* cachePath = getenv("CACHE_DIRECTORY");
+  if (cachePath && *cachePath) {
+    rv = NS_NewNativeLocalFile(nsDependentCString(cachePath),
+                               true, getter_AddRefs(directory));
+  }
+#endif
+
+  if (!directory) {
+    rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+                                getter_AddRefs(directory));
+  }
+
+  if (!directory) {
+    rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+                                getter_AddRefs(directory));
+  }
+
+  if (directory) {
+    rv = directory->Clone(getter_AddRefs(gInstance->mCacheDirectory));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = gInstance->mCacheDirectory->Append(NS_LITERAL_STRING("cache2"));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+// static
+already_AddRefed<nsIEventTarget>
+CacheFileIOManager::IOTarget()
+{
+  nsCOMPtr<nsIEventTarget> target;
+  if (gInstance && gInstance->mIOThread)
+    target = gInstance->mIOThread->Target();
+
+  return target.forget();
+}
+
+already_AddRefed<CacheIOThread>
+CacheFileIOManager::IOThread()
+{
+  nsRefPtr<CacheIOThread> thread;
+  if (gInstance)
+    thread = gInstance->mIOThread;
+
+  return thread.forget();
+}
+
+nsresult
+CacheFileIOManager::OpenFile(const nsACString &aKey,
+                             uint32_t aFlags,
+                             CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
+       PromiseFlatCString(aKey).get(), aFlags, aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (!ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  bool priority = aFlags & CacheFileIOManager::PRIORITY;
+  nsRefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, priority
+    ? CacheIOThread::OPEN_PRIORITY
+    : CacheIOThread::OPEN);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
+                                     uint32_t aFlags,
+                                     CacheFileHandle **_retval)
+{
+  nsresult rv;
+
+  if (mShuttingDown)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  if (!mTreeCreated) {
+    rv = CreateCacheTree();
+    if (NS_FAILED(rv)) return rv;
+  }
+
+  nsCOMPtr<nsIFile> file;
+  rv = GetFile(aHash, getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<CacheFileHandle> handle;
+  mHandles.GetHandle(aHash, getter_AddRefs(handle));
+
+  if (aFlags == CREATE_NEW) {
+    if (handle) {
+      rv = DoomFileInternal(handle);
+      NS_ENSURE_SUCCESS(rv, rv);
+      handle = nullptr;
+    }
+
+    rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    rv = file->Exists(&exists);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (exists) {
+      rv = file->Remove(false);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Cannot remove old entry from the disk");
+        // TODO log
+      }
+    }
+
+    handle->mFile.swap(file);
+    handle->mFileSize = 0;
+  }
+
+  if (handle) {
+    handle.swap(*_retval);
+    return NS_OK;
+  }
+
+  bool exists;
+  rv = file->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!exists && aFlags == OPEN)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (exists) {
+    rv = file->GetFileSize(&handle->mFileSize);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    handle->mFileExists = true;
+  }
+  else {
+    handle->mFileSize = 0;
+  }
+
+  handle->mFile.swap(file);
+  handle.swap(*_retval);
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CloseHandle(CacheFileHandle *aHandle)
+{
+  LOG(("CacheFileIOManager::CloseHandle() [handle=%p]", aHandle));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<CloseHandleEvent> ev = new CloseHandleEvent(aHandle);
+  rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CloseHandleInternal(CacheFileHandle *aHandle)
+{
+  // This method should be called only from CloseHandleEvent. If this handle is
+  // still unused then mRefCnt should be 2 (reference in hashtable and in
+  // CacheHandleEvent)
+
+  if (aHandle->mRefCnt != 2) {
+    // someone got this handle between calls to CloseHandle() and
+    // CloseHandleInternal()
+    return NS_OK;
+  }
+
+  // We're going to remove this handle, don't call CloseHandle() again
+  aHandle->mRemovingHandle = true;
+
+  // Close file handle
+  if (aHandle->mFD) {
+    ReleaseNSPRHandleInternal(aHandle);
+  }
+
+  // If the entry was doomed delete the file
+  if (aHandle->IsDoomed()) {
+    aHandle->mFile->Remove(false);
+  }
+
+  // Remove the handle from hashtable
+  mHandles.RemoveHandle(aHandle);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::Read(CacheFileHandle *aHandle, int64_t aOffset,
+                         char *aBuf, int32_t aCount,
+                         CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::Read() [handle=%p, offset=%lld, count=%d, "
+       "listener=%p]", aHandle, aOffset, aCount, aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<ReadEvent> ev = new ReadEvent(aHandle, aOffset, aBuf, aCount,
+                                         aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
+    ? CacheIOThread::READ_PRIORITY
+    : CacheIOThread::READ);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
+                                 char *aBuf, int32_t aCount)
+{
+  nsresult rv;
+
+  if (!aHandle->mFileExists) {
+    NS_WARNING("Trying to read from non-existent file");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!aHandle->mFD) {
+    rv = OpenNSPRHandle(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    NSPRHandleUsed(aHandle);
+  }
+
+  // Check again, OpenNSPRHandle could figure out the file was gone.
+  if (!aHandle->mFileExists) {
+    NS_WARNING("Trying to read from non-existent file");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
+  if (offset == -1)
+    return NS_ERROR_FAILURE;
+
+  int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount);
+  if (bytesRead != aCount)
+    return NS_ERROR_FAILURE;
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::Write(CacheFileHandle *aHandle, int64_t aOffset,
+                          const char *aBuf, int32_t aCount, bool aValidate,
+                          CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::Write() [handle=%p, offset=%lld, count=%d, "
+       "validate=%d, listener=%p]", aHandle, aOffset, aCount, aValidate,
+       aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
+                                           aValidate, aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
+                                  const char *aBuf, int32_t aCount,
+                                  bool aValidate)
+{
+  nsresult rv;
+
+  if (!aHandle->mFileExists) {
+    rv = CreateFile(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!aHandle->mFD) {
+    rv = OpenNSPRHandle(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    NSPRHandleUsed(aHandle);
+  }
+
+  // Check again, OpenNSPRHandle could figure out the file was gone.
+  if (!aHandle->mFileExists) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Write invalidates the entry by default
+  aHandle->mInvalid = true;
+
+  int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
+  if (offset == -1)
+    return NS_ERROR_FAILURE;
+
+  int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount);
+
+  if (bytesWritten != -1 && aHandle->mFileSize < aOffset+bytesWritten)
+      aHandle->mFileSize = aOffset+bytesWritten;
+
+  if (bytesWritten != aCount)
+    return NS_ERROR_FAILURE;
+
+  // Write was successful and this write validates the entry (i.e. metadata)
+  if (aValidate)
+    aHandle->mInvalid = false;
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::DoomFile(CacheFileHandle *aHandle,
+                             CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]",
+       aHandle, aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
+    ? CacheIOThread::DOOM_PRIORITY
+    : CacheIOThread::DOOM);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle)
+{
+  nsresult rv;
+
+  if (aHandle->IsDoomed())
+    return NS_OK;
+
+  if (aHandle->mFileExists) {
+    // we need to move the current file to the doomed directory
+    if (aHandle->mFD)
+      ReleaseNSPRHandleInternal(aHandle);
+
+    // find unused filename
+    nsCOMPtr<nsIFile> file;
+    rv = GetDoomedFile(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIFile> parentDir;
+    rv = file->GetParent(getter_AddRefs(parentDir));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsAutoCString leafName;
+    rv = file->GetNativeLeafName(leafName);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = aHandle->mFile->MoveToNative(parentDir, leafName);
+    if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) {
+      LOG(("  file already removed under our hands"));
+      aHandle->mFileExists = false;
+      rv = NS_OK;
+    }
+    else {
+      NS_ENSURE_SUCCESS(rv, rv);
+      aHandle->mFile.swap(file);
+    }
+  }
+
+  aHandle->mIsDoomed = true;
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::DoomFileByKey(const nsACString &aKey,
+                                  CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
+       PromiseFlatCString(aKey).get(), aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (!ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash)
+{
+  nsresult rv;
+
+  if (mShuttingDown)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  if (!mCacheDirectory)
+    return NS_ERROR_FILE_INVALID_PATH;
+
+  // Find active handle
+  nsRefPtr<CacheFileHandle> handle;
+  mHandles.GetHandle(aHash, getter_AddRefs(handle));
+
+  if (handle) {
+    if (handle->IsDoomed())
+      return NS_OK;
+
+    return DoomFileInternal(handle);
+  }
+
+  // There is no handle for this file, delete the file if exists
+  nsCOMPtr<nsIFile> file;
+  rv = GetFile(aHash, getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool exists;
+  rv = file->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!exists)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  rv = file->Remove(false);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Cannot remove old entry from the disk");
+    // TODO log
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle *aHandle)
+{
+  LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle);
+  rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::ReleaseNSPRHandleInternal(CacheFileHandle *aHandle)
+{
+  MOZ_ASSERT(aHandle->mFD);
+
+  DebugOnly<bool> found;
+  found = mHandlesByLastUsed.RemoveElement(aHandle);
+  MOZ_ASSERT(found);
+
+  PR_Close(aHandle->mFD);
+  aHandle->mFD = nullptr;
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::TruncateSeekSetEOF(CacheFileHandle *aHandle,
+                                       int64_t aTruncatePos, int64_t aEOFPos,
+                                       CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%lld, "
+       "EOFPos=%lld, listener=%p]", aHandle, aTruncatePos, aEOFPos, aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<TruncateSeekSetEOFEvent> ev = new TruncateSeekSetEOFEvent(
+                                           aHandle, aTruncatePos, aEOFPos,
+                                           aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::EnumerateEntryFiles(EEnumerateMode aMode,
+                                        CacheEntriesEnumerator** aEnumerator)
+{
+  LOG(("CacheFileIOManager::EnumerateEntryFiles(%d)", aMode));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (!ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  if (!ioMan->mCacheDirectory)
+    return NS_ERROR_FILE_NOT_FOUND;
+
+  nsCOMPtr<nsIFile> file;
+  rv = ioMan->mCacheDirectory->Clone(getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  switch (aMode) {
+  case ENTRIES:
+    rv = file->AppendNative(NS_LITERAL_CSTRING("entries"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    break;
+
+  case DOOMED:
+    rv = file->AppendNative(NS_LITERAL_CSTRING("doomed"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    break;
+
+  default:
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsAutoPtr<CacheEntriesEnumerator> enumerator(
+    new CacheEntriesEnumerator(file));
+
+  rv = enumerator->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aEnumerator = enumerator.forget();
+  return NS_OK;
+}
+
+static nsresult
+TruncFile(PRFileDesc *aFD, uint32_t aEOF)
+{
+#if defined(XP_UNIX)
+  if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
+    NS_ERROR("ftruncate failed");
+    return NS_ERROR_FAILURE;
+  }
+#elif defined(XP_WIN)
+  int32_t cnt = PR_Seek(aFD, aEOF, PR_SEEK_SET);
+  if (cnt == -1)
+    return NS_ERROR_FAILURE;
+  if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(aFD))) {
+    NS_ERROR("SetEndOfFile failed");
+    return NS_ERROR_FAILURE;
+  }
+#elif defined(XP_OS2)
+  if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(aFD), aEOF) != NO_ERROR) {
+    NS_ERROR("DosSetFileSize failed");
+    return NS_ERROR_FAILURE;
+  }
+#else
+  MOZ_ASSERT(false, "Not implemented!");
+#endif
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
+                                               int64_t aTruncatePos,
+                                               int64_t aEOFPos)
+{
+  nsresult rv;
+
+  if (!aHandle->mFileExists) {
+    rv = CreateFile(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!aHandle->mFD) {
+    rv = OpenNSPRHandle(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    NSPRHandleUsed(aHandle);
+  }
+
+  // Check again, OpenNSPRHandle could figure out the file was gone.
+  if (!aHandle->mFileExists) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // This operation always invalidates the entry
+  aHandle->mInvalid = true;
+
+  rv = TruncFile(aHandle->mFD, static_cast<uint32_t>(aTruncatePos));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = TruncFile(aHandle->mFD, static_cast<uint32_t>(aEOFPos));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CreateFile(CacheFileHandle *aHandle)
+{
+  MOZ_ASSERT(!aHandle->mFD);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIFile> file;
+  if (aHandle->IsDoomed()) {
+    rv = GetDoomedFile(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    rv = GetFile(aHandle->Hash(), getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    if (NS_SUCCEEDED(file->Exists(&exists)) && exists) {
+      NS_WARNING("Found a file that should not exist!");
+    }
+  }
+
+  rv = OpenNSPRHandle(aHandle, true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aHandle->mFileSize = 0;
+  return NS_OK;
+}
+
+void
+CacheFileIOManager::GetHashStr(const SHA1Sum::Hash *aHash, nsACString &_retval)
+{
+  _retval.Assign("");
+  const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+                           '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+  for (uint32_t i=0 ; i<sizeof(SHA1Sum::Hash) ; i++) {
+    _retval.Append(hexChars[(*aHash)[i] >> 4]);
+    _retval.Append(hexChars[(*aHash)[i] & 0xF]);
+  }
+}
+
+nsresult
+CacheFileIOManager::GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval)
+{
+  nsresult rv;
+  nsCOMPtr<nsIFile> file;
+  rv = mCacheDirectory->Clone(getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = file->AppendNative(NS_LITERAL_CSTRING("entries"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString leafName;
+  GetHashStr(aHash, leafName);
+
+  rv = file->AppendNative(leafName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  file.swap(*_retval);
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::GetDoomedFile(nsIFile **_retval)
+{
+  nsresult rv;
+  nsCOMPtr<nsIFile> file;
+  rv = mCacheDirectory->Clone(getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = file->AppendNative(NS_LITERAL_CSTRING("doomed"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = file->AppendNative(NS_LITERAL_CSTRING("dummyleaf"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  srand(static_cast<unsigned>(PR_Now()));
+  nsAutoCString leafName;
+  uint32_t iter=0;
+  while (true) {
+    iter++;
+    leafName.AppendInt(rand());
+    rv = file->SetNativeLeafName(leafName);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    if (NS_SUCCEEDED(file->Exists(&exists)) && !exists)
+      break;
+
+    leafName.Truncate();
+  }
+
+//  Telemetry::Accumulate(Telemetry::DISK_CACHE_GETDOOMEDFILE_ITERATIONS, iter);
+
+  file.swap(*_retval);
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CheckAndCreateDir(nsIFile *aFile, const char *aDir)
+{
+  nsresult rv;
+  bool exists;
+
+  nsCOMPtr<nsIFile> file;
+  if (!aDir) {
+    file = aFile;
+  } else {
+    nsAutoCString dir(aDir);
+    rv = aFile->Clone(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = file->AppendNative(dir);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = file->Exists(&exists);
+  if (NS_SUCCEEDED(rv) && !exists)
+    rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Cannot create directory");
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CreateCacheTree()
+{
+  MOZ_ASSERT(!mTreeCreated);
+
+  if (!mCacheDirectory)
+    return NS_ERROR_FILE_INVALID_PATH;
+
+  nsresult rv;
+
+  // ensure parent directory exists
+  nsCOMPtr<nsIFile> parentDir;
+  rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = CheckAndCreateDir(parentDir, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // ensure cache directory exists
+  rv = CheckAndCreateDir(mCacheDirectory, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // ensure entries directory exists
+  rv = CheckAndCreateDir(mCacheDirectory, "entries");
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // ensure doomed directory exists
+  rv = CheckAndCreateDir(mCacheDirectory, "doomed");
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mTreeCreated = true;
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate)
+{
+  MOZ_ASSERT(!aHandle->mFD);
+  MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
+  MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
+
+  nsresult rv;
+
+  if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
+    // close handle that hasn't been used for the longest time
+    rv = ReleaseNSPRHandleInternal(mHandlesByLastUsed[0]);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (aCreate) {
+    rv = aHandle->mFile->OpenNSPRFileDesc(
+           PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    aHandle->mFileExists = true;
+  }
+  else {
+    rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD);
+    if (NS_ERROR_FILE_NOT_FOUND == rv) {
+      LOG(("  file doesn't exists"));
+      aHandle->mFileExists = false;
+      aHandle->mIsDoomed = true;
+      return NS_OK;
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mHandlesByLastUsed.AppendElement(aHandle);
+  return NS_OK;
+}
+
+void
+CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle)
+{
+  MOZ_ASSERT(aHandle->mFD);
+
+  DebugOnly<bool> found;
+  found = mHandlesByLastUsed.RemoveElement(aHandle);
+  MOZ_ASSERT(found);
+
+  mHandlesByLastUsed.AppendElement(aHandle);
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileIOManager__h__
+#define CacheFileIOManager__h__
+
+#include "CacheIOThread.h"
+#include "CacheEntriesEnumerator.h"
+#include "nsIEventTarget.h"
+#include "nsCOMPtr.h"
+#include "mozilla/SHA1.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "pldhash.h"
+#include "prclist.h"
+#include "prio.h"
+
+class nsIFile;
+
+namespace mozilla {
+namespace net {
+
+class CacheFileHandle : public nsISupports
+                      , public PRCList
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority);
+  bool IsDoomed() { return mIsDoomed; }
+  const SHA1Sum::Hash *Hash() { return mHash; }
+  int64_t FileSize() { return mFileSize; }
+  bool IsPriority() { return mPriority; }
+  bool FileExists() { return mFileExists; }
+  bool IsClosed() { return mClosed; }
+  nsCString & Key() { return mKey; }
+
+private:
+  friend class CacheFileIOManager;
+  friend class CacheFileHandles;
+  friend class ReleaseNSPRHandleEvent;
+
+  virtual ~CacheFileHandle();
+
+  const SHA1Sum::Hash *mHash;
+  bool                 mIsDoomed;
+  bool                 mRemovingHandle;
+  bool                 mPriority;
+  bool                 mClosed;
+  bool                 mInvalid;
+  bool                 mFileExists; // This means that the file should exists,
+                                    // but it can be still deleted by OS/user
+                                    // and then a subsequent OpenNSPRFileDesc()
+                                    // will fail.
+  nsCOMPtr<nsIFile>    mFile;
+  int64_t              mFileSize;
+  PRFileDesc          *mFD;  // if null then the file doesn't exists on the disk
+  nsCString            mKey;
+};
+
+class CacheFileHandles {
+public:
+  CacheFileHandles();
+  ~CacheFileHandles();
+
+  nsresult Init();
+  void     Shutdown();
+
+  nsresult GetHandle(const SHA1Sum::Hash *aHash, CacheFileHandle **_retval);
+  nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval);
+  void     RemoveHandle(CacheFileHandle *aHandlle);
+  void     GetAllHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval);
+  uint32_t HandleCount();
+
+private:
+  static PLDHashNumber HashKey(PLDHashTable *table, const void *key);
+  static bool          MatchEntry(PLDHashTable *table,
+                                  const PLDHashEntryHdr *entry,
+                                  const void *key);
+  static void          MoveEntry(PLDHashTable *table,
+                                 const PLDHashEntryHdr *from,
+                                 PLDHashEntryHdr *to);
+  static void          ClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry);
+
+  static PLDHashTableOps mOps;
+  PLDHashTable           mTable;
+  bool                   mInitialized;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class OpenFileEvent;
+class CloseFileEvent;
+class ReadEvent;
+class WriteEvent;
+
+#define CACHEFILEIOLISTENER_IID \
+{ /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */       \
+  0xdcaf2ddc,                                      \
+  0x17cf,                                          \
+  0x4242,                                          \
+  {0xbc, 0xa1, 0x8c, 0x86, 0x93, 0x63, 0x75, 0xa5} \
+}
+
+class CacheFileIOListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEIOLISTENER_IID)
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) = 0;
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult) = 0;
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf,
+                        nsresult aResult) = 0;
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) = 0;
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileIOListener, CACHEFILEIOLISTENER_IID)
+
+
+class CacheFileIOManager : public nsISupports
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  enum {
+    OPEN       = 0U,
+    CREATE     = 1U,
+    CREATE_NEW = 2U,
+    PRIORITY   = 4U,
+    NOHASH     = 8U
+  };
+
+  CacheFileIOManager();
+
+  static nsresult Init();
+  static nsresult Shutdown();
+  static nsresult OnProfile();
+  static already_AddRefed<nsIEventTarget> IOTarget();
+  static already_AddRefed<CacheIOThread> IOThread();
+
+  static nsresult OpenFile(const nsACString &aKey,
+                           uint32_t aFlags,
+                           CacheFileIOListener *aCallback);
+  static nsresult Read(CacheFileHandle *aHandle, int64_t aOffset,
+                       char *aBuf, int32_t aCount,
+                       CacheFileIOListener *aCallback);
+  static nsresult Write(CacheFileHandle *aHandle, int64_t aOffset,
+                        const char *aBuf, int32_t aCount, bool aValidate,
+                        CacheFileIOListener *aCallback);
+  static nsresult DoomFile(CacheFileHandle *aHandle,
+                           CacheFileIOListener *aCallback);
+  static nsresult DoomFileByKey(const nsACString &aKey,
+                                CacheFileIOListener *aCallback);
+  static nsresult ReleaseNSPRHandle(CacheFileHandle *aHandle);
+  static nsresult TruncateSeekSetEOF(CacheFileHandle *aHandle,
+                                     int64_t aTruncatePos, int64_t aEOFPos,
+                                     CacheFileIOListener *aCallback);
+
+  enum EEnumerateMode {
+    ENTRIES,
+    DOOMED
+  };
+
+  static nsresult EnumerateEntryFiles(EEnumerateMode aMode,
+                                      CacheEntriesEnumerator** aEnumerator);
+
+private:
+  friend class CacheFileHandle;
+  friend class CacheFileChunk;
+  friend class CacheFile;
+  friend class ShutdownEvent;
+  friend class OpenFileEvent;
+  friend class CloseHandleEvent;
+  friend class ReadEvent;
+  friend class WriteEvent;
+  friend class DoomFileEvent;
+  friend class DoomFileByKeyEvent;
+  friend class ReleaseNSPRHandleEvent;
+  friend class TruncateSeekSetEOFEvent;
+
+  virtual ~CacheFileIOManager();
+
+  static nsresult CloseHandle(CacheFileHandle *aHandle);
+
+  nsresult InitInternal();
+  nsresult ShutdownInternal();
+
+  nsresult OpenFileInternal(const SHA1Sum::Hash *aHash,
+                            uint32_t aFlags,
+                            CacheFileHandle **_retval);
+  nsresult CloseHandleInternal(CacheFileHandle *aHandle);
+  nsresult ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
+                        char *aBuf, int32_t aCount);
+  nsresult WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
+                         const char *aBuf, int32_t aCount, bool aValidate);
+  nsresult DoomFileInternal(CacheFileHandle *aHandle);
+  nsresult DoomFileByKeyInternal(const SHA1Sum::Hash *aHash);
+  nsresult ReleaseNSPRHandleInternal(CacheFileHandle *aHandle);
+  nsresult TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
+                                      int64_t aTruncatePos, int64_t aEOFPos);
+
+  nsresult CreateFile(CacheFileHandle *aHandle);
+  static void GetHashStr(const SHA1Sum::Hash *aHash, nsACString &_retval);
+  nsresult GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval);
+  nsresult GetDoomedFile(nsIFile **_retval);
+  nsresult CheckAndCreateDir(nsIFile *aFile, const char *aDir);
+  nsresult CreateCacheTree();
+  nsresult OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate = false);
+  void     NSPRHandleUsed(CacheFileHandle *aHandle);
+
+
+  static CacheFileIOManager  *gInstance;
+  bool                        mShuttingDown;
+  nsRefPtr<CacheIOThread>     mIOThread;
+  nsCOMPtr<nsIFile>           mCacheDirectory;
+  bool                        mTreeCreated;
+  CacheFileHandles            mHandles;
+  nsTArray<CacheFileHandle *> mHandlesByLastUsed;
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.cpp
@@ -0,0 +1,634 @@
+/* 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 "CacheFileInputStream.h"
+
+#include "CacheLog.h"
+#include "CacheFile.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(CacheFileInputStream)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFileInputStream::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFileInputStream");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    delete (this);
+    return 0;
+  }
+
+  if (count == 1) {
+    mFile->RemoveInput(this);
+  }
+
+  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(mozilla::net::CacheFileChunkListener)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFileInputStream::CacheFileInputStream(CacheFile *aFile)
+  : mFile(aFile)
+  , mPos(0)
+  , mClosed(false)
+  , mStatus(NS_OK)
+  , mWaitingForUpdate(false)
+  , mListeningForChunk(-1)
+  , mInReadSegments(false)
+  , mCallbackFlags(0)
+{
+  LOG(("CacheFileInputStream::CacheFileInputStream() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileInputStream);
+}
+
+CacheFileInputStream::~CacheFileInputStream()
+{
+  LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileInputStream);
+}
+
+// nsIInputStream
+NS_IMETHODIMP
+CacheFileInputStream::Close()
+{
+  LOG(("CacheFileInputStream::Close() [this=%p]", this));
+  return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::Available(uint64_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::Available() - Stream is closed. [this=%p, "
+         "status=0x%08x]", this, mStatus));
+    return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
+  }
+
+  EnsureCorrectChunk(false);
+  *_retval = 0;
+
+  if (mChunk) {
+    int64_t canRead;
+    const char *buf;
+    CanRead(&canRead, &buf);
+
+    if (canRead > 0)
+      *_retval = canRead;
+    else if (canRead == 0 && !mFile->mOutput)
+      return NS_BASE_STREAM_CLOSED;
+  }
+
+  LOG(("CacheFileInputStream::Available() [this=%p, retval=%lld]",
+       this, *_retval));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::Read(char *aBuf, uint32_t aCount, uint32_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::Read() [this=%p, count=%d]", this, aCount));
+
+  nsresult rv;
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::Read() - Stream is closed. [this=%p, "
+         "status=0x%08x]", this, mStatus));
+
+    if NS_FAILED(mStatus)
+      return mStatus;
+
+    *_retval = 0;
+    return NS_OK;
+  }
+
+  EnsureCorrectChunk(false);
+  if (!mChunk) {
+    if (mListeningForChunk == -1) {
+      LOG(("  no chunk, returning 0 read and NS_OK"));
+      *_retval = 0;
+      return NS_OK;
+    }
+    else {
+      LOG(("  waiting for chuck, returning WOULD_BLOCK"));
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+  }
+
+  int64_t canRead;
+  const char *buf;
+  CanRead(&canRead, &buf);
+
+  if (canRead < 0) {
+    // file was truncated ???
+    MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
+    *_retval = 0;
+    rv = NS_OK;
+  }
+  else if (canRead > 0) {
+    *_retval = std::min(static_cast<uint32_t>(canRead), aCount);
+    memcpy(aBuf, buf, *_retval);
+    mPos += *_retval;
+
+    EnsureCorrectChunk(!(canRead < aCount && mPos % kChunkSize == 0));
+
+    rv = NS_OK;
+  }
+  else {
+    if (mFile->mOutput)
+      rv = NS_BASE_STREAM_WOULD_BLOCK;
+    else {
+      *_retval = 0;
+      rv = NS_OK;
+    }
+  }
+
+  LOG(("CacheFileInputStream::Read() [this=%p, rv=0x%08x, retval=%d",
+       this, rv, *_retval));
+
+  return rv;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
+                                   uint32_t aCount, uint32_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::ReadSegments() [this=%p, count=%d]",
+       this, aCount));
+
+  nsresult rv;
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::ReadSegments() - Stream is closed. [this=%p, "
+         "status=0x%08x]", this, mStatus));
+
+    if NS_FAILED(mStatus)
+      return mStatus;
+
+    *_retval = 0;
+    return NS_OK;
+  }
+
+  EnsureCorrectChunk(false);
+  if (!mChunk) {
+    if (mListeningForChunk == -1) {
+      *_retval = 0;
+      return NS_OK;
+    }
+    else {
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+  }
+
+  int64_t canRead;
+  const char *buf;
+  CanRead(&canRead, &buf);
+
+  if (canRead < 0) {
+    // file was truncated ???
+    MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
+    *_retval = 0;
+    rv = NS_OK;
+  }
+  else if (canRead > 0) {
+    uint32_t toRead = std::min(static_cast<uint32_t>(canRead), aCount);
+
+    // We need to release the lock to avoid lock re-entering
+#ifdef DEBUG
+    int64_t oldPos = mPos;
+#endif
+    mInReadSegments = true;
+    lock.Unlock();
+    rv = aWriter(this, aClosure, buf, 0, toRead, _retval);
+    lock.Lock();
+    mInReadSegments = false;
+#ifdef DEBUG
+    MOZ_ASSERT(oldPos == mPos);
+#endif
+
+    if (NS_SUCCEEDED(rv)) {
+      MOZ_ASSERT(*_retval <= toRead,
+                 "writer should not write more than we asked it to write");
+      mPos += *_retval;
+    }
+
+    EnsureCorrectChunk(!(canRead < aCount && mPos % kChunkSize == 0));
+
+    rv = NS_OK;
+  }
+  else {
+    if (mFile->mOutput)
+      rv = NS_BASE_STREAM_WOULD_BLOCK;
+    else {
+      *_retval = 0;
+      rv = NS_OK;
+    }
+  }
+
+  LOG(("CacheFileInputStream::ReadSegments() [this=%p, rv=0x%08x, retval=%d",
+       this, rv, *_retval));
+
+  return rv;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::IsNonBlocking(bool *_retval)
+{
+  *_retval = true;
+  return NS_OK;
+}
+
+// nsIAsyncInputStream
+NS_IMETHODIMP
+CacheFileInputStream::CloseWithStatus(nsresult aStatus)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::CloseWithStatus() [this=%p, aStatus=0x%08x]",
+       this, aStatus));
+
+  if (mClosed) {
+    MOZ_ASSERT(!mCallback);
+    return NS_OK;
+  }
+
+  mClosed = true;
+  mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
+
+  if (mChunk)
+    ReleaseChunk();
+
+  // TODO propagate error from input stream to other streams ???
+
+  MaybeNotifyListener();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::AsyncWait(nsIInputStreamCallback *aCallback,
+                                uint32_t aFlags,
+                                uint32_t aRequestedCount,
+                                nsIEventTarget *aEventTarget)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
+       "requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags,
+       aRequestedCount, aEventTarget));
+
+  mCallback = aCallback;
+  mCallbackFlags = aFlags;
+
+  if (!mCallback) {
+    if (mWaitingForUpdate) {
+      mChunk->CancelWait(this);
+      mWaitingForUpdate = false;
+    }
+    return NS_OK;
+  }
+
+  if (mClosed) {
+    NotifyListener();
+    return NS_OK;
+  }
+
+  EnsureCorrectChunk(false);
+
+  MaybeNotifyListener();
+
+  return NS_OK;
+}
+
+// nsISeekableStream
+NS_IMETHODIMP
+CacheFileInputStream::Seek(int32_t whence, int64_t offset)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::Seek() [this=%p, whence=%d, offset=%lld]",
+       this, whence, offset));
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::Seek() - Stream is closed. [this=%p]", this));
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  int64_t newPos = offset;
+  switch (whence) {
+    case NS_SEEK_SET:
+      break;
+    case NS_SEEK_CUR:
+      newPos += mPos;
+      break;
+    case NS_SEEK_END:
+      newPos += mFile->mDataSize;
+      break;
+    default:
+      NS_ERROR("invalid whence");
+      return NS_ERROR_INVALID_ARG;
+  }
+  mPos = newPos;
+  EnsureCorrectChunk(true);
+
+  LOG(("CacheFileInputStream::Seek() [this=%p, pos=%lld]", this, mPos));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::Tell(int64_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::Tell() - Stream is closed. [this=%p]", this));
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  *_retval = mPos;
+
+  LOG(("CacheFileInputStream::Tell() [this=%p, retval=%lld]", 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;
+}
+
+nsresult
+CacheFileInputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
+{
+  MOZ_CRASH("CacheFileInputStream::OnChunkWritten should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileInputStream::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                                       CacheFileChunk *aChunk)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::OnChunkAvailable() [this=%p, result=0x%08x, "
+       "idx=%d, chunk=%p]", this, aResult, aChunkIdx, aChunk));
+
+  MOZ_ASSERT(mListeningForChunk != -1);
+
+  if (mListeningForChunk != static_cast<int64_t>(aChunkIdx)) {
+    // This is not a chunk that we're waiting for
+    LOG(("CacheFileInputStream::OnChunkAvailable() - Notification is for a "
+         "different chunk. [this=%p, listeningForChunk=%lld]",
+         this, mListeningForChunk));
+
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(!mChunk);
+  MOZ_ASSERT(!mWaitingForUpdate);
+  mListeningForChunk = -1;
+
+  if (mClosed) {
+    MOZ_ASSERT(!mCallback);
+
+    LOG(("CacheFileInputStream::OnChunkAvailable() - Stream is closed, "
+         "ignoring notification. [this=%p]", this));
+
+    return NS_OK;
+  }
+
+  mChunk = aChunk;
+  MaybeNotifyListener();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileInputStream::OnChunkUpdated(CacheFileChunk *aChunk)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::OnChunkUpdated() [this=%p, idx=%d]",
+       this, aChunk->Index()));
+
+  if (!mWaitingForUpdate) {
+    LOG(("CacheFileInputStream::OnChunkUpdated() - Ignoring notification since "
+         "mWaitingforUpdate == false. [this=%p]", this));
+
+    return NS_OK;
+  }
+  else {
+    mWaitingForUpdate = false;
+  }
+
+  MOZ_ASSERT(mChunk == aChunk);
+
+  MaybeNotifyListener();
+
+  return NS_OK;
+}
+
+void
+CacheFileInputStream::ReleaseChunk()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileInputStream::ReleaseChunk() [this=%p, idx=%d]",
+       this, mChunk->Index()));
+
+  if (mWaitingForUpdate) {
+    LOG(("CacheFileInputStream::ReleaseChunk() - Canceling waiting for update. "
+         "[this=%p]", this));
+
+    mChunk->CancelWait(this);
+    mWaitingForUpdate = false;
+  }
+
+  mFile->ReleaseOutsideLock(mChunk.forget().get());
+}
+
+void
+CacheFileInputStream::EnsureCorrectChunk(bool aReleaseOnly)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileInputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
+       this, aReleaseOnly));
+
+  nsresult rv;
+
+  uint32_t chunkIdx = mPos / kChunkSize;
+
+  if (mChunk) {
+    if (mChunk->Index() == chunkIdx) {
+      // we have a correct chunk
+      LOG(("CacheFileInputStream::EnsureCorrectChunk() - Have correct chunk "
+           "[this=%p, idx=%d]", this, chunkIdx));
+
+      return;
+    }
+    else {
+      ReleaseChunk();
+    }
+  }
+
+  MOZ_ASSERT(!mWaitingForUpdate);
+
+  if (aReleaseOnly)
+    return;
+
+  if (mListeningForChunk == static_cast<int64_t>(chunkIdx)) {
+    // We're already waiting for this chunk
+    LOG(("CacheFileInputStream::EnsureCorrectChunk() - Already listening for "
+         "chunk %lld [this=%p]", mListeningForChunk, this));
+
+    return;
+  }
+
+  rv = mFile->GetChunkLocked(chunkIdx, false, this, getter_AddRefs(mChunk));
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileInputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
+         "[this=%p, idx=%d, rv=0x%08x]", this, chunkIdx, rv));
+
+  }
+  else if (!mChunk) {
+    mListeningForChunk = static_cast<int64_t>(chunkIdx);
+  }
+
+  MaybeNotifyListener();
+}
+
+void
+CacheFileInputStream::CanRead(int64_t *aCanRead, const char **aBuf)
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(mChunk);
+  MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+  uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
+  *aCanRead = mChunk->DataSize() - chunkOffset;
+  *aBuf = mChunk->BufForReading() + chunkOffset;
+
+  LOG(("CacheFileInputStream::CanRead() [this=%p, canRead=%lld]",
+       this, *aCanRead));
+}
+
+void
+CacheFileInputStream::NotifyListener()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileInputStream::NotifyListener() [this=%p]", this));
+
+  MOZ_ASSERT(mCallback);
+
+  if (!mCallbackTarget)
+    mCallbackTarget = NS_GetCurrentThread();
+
+  nsCOMPtr<nsIInputStreamCallback> asyncCallback =
+    NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
+
+  mCallback = nullptr;
+  mCallbackTarget = nullptr;
+
+  asyncCallback->OnInputStreamReady(this);
+}
+
+void
+CacheFileInputStream::MaybeNotifyListener()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileInputStream::MaybeNotifyListener() [this=%p, mCallback=%p, "
+       "mClosed=%d, mStatus=0x%08x, mChunk=%p, mListeningForChunk=%lld, "
+       "mWaitingForUpdate=%d]", this, mCallback.get(), mClosed, mStatus,
+       mChunk.get(), mListeningForChunk, mWaitingForUpdate));
+
+  if (!mCallback)
+    return;
+
+  if (mClosed) {
+    NotifyListener();
+    return;
+  }
+
+  if (!mChunk) {
+    if (mListeningForChunk == -1) {
+      // EOF, should we notify even if mCallbackFlags == WAIT_CLOSURE_ONLY ??
+      NotifyListener();
+    }
+    return;
+  }
+
+  MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+  if (mWaitingForUpdate)
+    return;
+
+  int64_t canRead;
+  const char *buf;
+  CanRead(&canRead, &buf);
+
+  if (canRead > 0) {
+    if (!(mCallbackFlags & WAIT_CLOSURE_ONLY))
+      NotifyListener();
+  }
+  else if (canRead == 0) {
+    if (!mFile->mOutput) {
+      // EOF
+      NotifyListener();
+    }
+    else {
+      mChunk->WaitForUpdate(this);
+      mWaitingForUpdate = true;
+    }
+  }
+  else {
+    // Output have set EOF before mPos?
+    MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
+    NotifyListener();
+  }
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileInputStream__h__
+#define CacheFileInputStream__h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "CacheFileChunk.h"
+
+
+namespace mozilla {
+namespace net {
+
+class CacheFile;
+
+class CacheFileInputStream : public nsIAsyncInputStream
+                           , public nsISeekableStream
+                           , public CacheFileChunkListener
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIINPUTSTREAM
+  NS_DECL_NSIASYNCINPUTSTREAM
+  NS_DECL_NSISEEKABLESTREAM
+
+public:
+  CacheFileInputStream(CacheFile *aFile);
+
+  NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                              CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk);
+
+private:
+  virtual ~CacheFileInputStream();
+
+  void ReleaseChunk();
+  void EnsureCorrectChunk(bool aReleaseOnly);
+  void CanRead(int64_t *aCanRead, const char **aBuf);
+  void NotifyListener();
+  void MaybeNotifyListener();
+
+  nsRefPtr<CacheFile>      mFile;
+  nsRefPtr<CacheFileChunk> mChunk;
+  int64_t                  mPos;
+  bool                     mClosed;
+  nsresult                 mStatus;
+  bool                     mWaitingForUpdate;
+  int64_t                  mListeningForChunk;
+  bool                     mInReadSegments;
+
+  nsCOMPtr<nsIInputStreamCallback> mCallback;
+  uint32_t                         mCallbackFlags;
+  nsCOMPtr<nsIEventTarget>         mCallbackTarget;
+};
+
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -0,0 +1,682 @@
+/* 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 "CacheFileMetadata.h"
+
+#include "CacheLog.h"
+#include "CacheFileIOManager.h"
+#include "CacheHashUtils.h"
+#include "CacheFileChunk.h"
+#include "../cache/nsCacheUtils.h"
+#include "mozilla/Telemetry.h"
+#include "prnetdb.h"
+
+
+namespace mozilla {
+namespace net {
+
+#define kMinMetadataRead 1024  // TODO find optimal value from telemetry
+#define kAlignSize       4096
+
+#define NO_EXPIRATION_TIME 0xFFFFFFFF
+
+NS_IMPL_ISUPPORTS1(CacheFileMetadata, CacheFileIOListener)
+
+CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString &aKey, bool aKeyIsHash)
+  : mHandle(aHandle)
+  , mKeyIsHash(aKeyIsHash)
+  , mHashArray(nullptr)
+  , mHashArraySize(0)
+  , mHashCount(0)
+  , mOffset(-1)
+  , mBuf(nullptr)
+  , mBufSize(0)
+  , mWriteBuf(nullptr)
+  , mElementsSize(0)
+  , mIsDirty(false)
+{
+  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
+       this, aHandle, PromiseFlatCString(aKey).get()));
+
+  MOZ_COUNT_CTOR(CacheFileMetadata);
+  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
+  mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME;
+  mKey = aKey;
+}
+
+CacheFileMetadata::~CacheFileMetadata()
+{
+  LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this));
+
+  MOZ_COUNT_DTOR(CacheFileMetadata);
+  MOZ_ASSERT(!mListener);
+
+  if (mHashArray) {
+    free(mHashArray);
+    mHashArray = nullptr;
+    mHashArraySize = 0;
+  }
+
+  if (mBuf) {
+    free(mBuf);
+    mBuf = nullptr;
+    mBufSize = 0;
+  }
+
+  DoMemoryReport(MemoryUsage());
+}
+
+CacheFileMetadata::CacheFileMetadata(const nsACString &aKey)
+  : mHandle(nullptr)
+  , mKeyIsHash(false)
+  , mHashArray(nullptr)
+  , mHashArraySize(0)
+  , mHashCount(0)
+  , mOffset(0)
+  , mBuf(nullptr)
+  , mBufSize(0)
+  , mWriteBuf(nullptr)
+  , mElementsSize(0)
+  , mIsDirty(true)
+{
+  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]",
+       this, PromiseFlatCString(aKey).get()));
+
+  MOZ_COUNT_CTOR(CacheFileMetadata);
+  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
+  mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME;
+  mMetaHdr.mFetchCount++;
+  mKey = aKey;
+  mMetaHdr.mKeySize = mKey.Length();
+}
+
+void
+CacheFileMetadata::SetHandle(CacheFileHandle *aHandle)
+{
+  LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle));
+
+  MOZ_ASSERT(!mHandle);
+
+  mHandle = aHandle;
+}
+
+nsresult
+CacheFileMetadata::ReadMetadata(CacheFileMetadataListener *aListener)
+{
+  LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this, aListener));
+
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(!mHashArray);
+  MOZ_ASSERT(!mBuf);
+  MOZ_ASSERT(!mWriteBuf);
+
+  nsresult rv;
+
+  int64_t size = mHandle->FileSize();
+  MOZ_ASSERT(size != -1);
+
+  if (size == 0) {
+    // this is a new entry
+    LOG(("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty "
+         "metadata. [this=%p]", this));
+
+    InitEmptyMetadata();
+    aListener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2*sizeof(uint32_t))) {
+    // there must be at least checksum, header and offset
+    LOG(("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
+         "empty metadata. [this=%p, filesize=%lld]", this, size));
+
+    InitEmptyMetadata();
+    aListener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  // round offset to 4k blocks
+  int64_t offset = (size / kAlignSize) * kAlignSize;
+
+  if (size - offset < kMinMetadataRead && offset > kAlignSize)
+    offset -= kAlignSize;
+
+  mBufSize = size - offset;
+  mBuf = static_cast<char *>(moz_xmalloc(mBufSize));
+
+  DoMemoryReport(MemoryUsage());
+
+  LOG(("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
+       "offset=%lld, filesize=%lld [this=%p]", offset, size, this));
+
+  mListener = aListener;
+  rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed"
+         " synchronously, creating empty metadata. [this=%p, rv=0x%08x]",
+         this, rv));
+
+    mListener = nullptr;
+    InitEmptyMetadata();
+    aListener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::WriteMetadata(uint32_t aOffset,
+                                 CacheFileMetadataListener *aListener)
+{
+  LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]",
+       this, aOffset, aListener));
+
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(!mWriteBuf);
+
+  nsresult rv;
+
+  mIsDirty = false;
+
+  mWriteBuf = static_cast<char *>(moz_xmalloc(sizeof(uint32_t) +
+                mHashCount * sizeof(CacheHashUtils::Hash16_t) +
+                sizeof(CacheFileMetadataHeader) + mKey.Length() + 1 +
+                mElementsSize + sizeof(uint32_t)));
+
+  char *p = mWriteBuf + sizeof(uint32_t);
+  memcpy(p, mHashArray, mHashCount * sizeof(CacheHashUtils::Hash16_t));
+  p += mHashCount * sizeof(CacheHashUtils::Hash16_t);
+  memcpy(p, &mMetaHdr, sizeof(CacheFileMetadataHeader));
+  p += sizeof(CacheFileMetadataHeader);
+  memcpy(p, mKey.get(), mKey.Length());
+  p += mKey.Length();
+  *p = 0;
+  p++;
+  memcpy(p, mBuf, mElementsSize);
+  p += mElementsSize;
+
+  CacheHashUtils::Hash32_t hash;
+  hash = CacheHashUtils::Hash(mWriteBuf + sizeof(uint32_t),
+                              p - mWriteBuf - sizeof(uint32_t));
+  *reinterpret_cast<uint32_t *>(mWriteBuf) = PR_htonl(hash);
+
+  *reinterpret_cast<uint32_t *>(p) = PR_htonl(aOffset);
+  p += sizeof(uint32_t);
+
+  mListener = aListener;
+  rv = CacheFileIOManager::Write(mHandle, aOffset, mWriteBuf, p - mWriteBuf,
+                                 true, this);
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() "
+         "failed synchronously. [this=%p, rv=0x%08x]", this, rv));
+
+    mListener = nullptr;
+    free(mWriteBuf);
+    mWriteBuf = nullptr;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  DoMemoryReport(MemoryUsage());
+
+  return NS_OK;
+}
+
+const char *
+CacheFileMetadata::GetElement(const char *aKey)
+{
+  const char *data = mBuf;
+  const char *limit = mBuf + mElementsSize;
+
+  while (data < limit) {
+    // Point to the value part
+    const char *value = data + strlen(data) + 1;
+    MOZ_ASSERT(value < limit, "Metadata elements corrupted");
+    if (strcmp(data, aKey) == 0) {
+      LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
+           this, aKey));
+      return value;
+    }
+
+    // Skip value part
+    data = value + strlen(value) + 1;
+  }
+  MOZ_ASSERT(data == limit, "Metadata elements corrupted");
+  LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]",
+       this, aKey));
+  return nullptr;
+}
+
+nsresult
+CacheFileMetadata::SetElement(const char *aKey, const char *aValue)
+{
+  LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]",
+       this, aKey, aValue));
+
+  MarkDirty();
+
+  const uint32_t keySize = strlen(aKey) + 1;
+  char *pos = const_cast<char *>(GetElement(aKey));
+
+  if (!aValue) {
+    // No value means remove the key/value pair completely, if existing
+    if (pos) {
+      uint32_t oldValueSize = strlen(pos) + 1;
+      uint32_t offset = pos - mBuf;
+      uint32_t remainder = mElementsSize - (offset + oldValueSize);
+
+      memmove(pos - keySize, pos + oldValueSize, remainder);
+      mElementsSize -= keySize + oldValueSize;
+    }
+    return NS_OK;
+  }
+
+  const uint32_t valueSize = strlen(aValue) + 1;
+  uint32_t newSize = mElementsSize + valueSize;
+  if (pos) {
+    const uint32_t oldValueSize = strlen(pos) + 1;
+    const uint32_t offset = pos - mBuf;
+    const uint32_t remainder = mElementsSize - (offset + oldValueSize);
+
+    // Update the value in place
+    newSize -= oldValueSize;
+    EnsureBuffer(newSize);
+
+    // Move the remainder to the right place
+    pos = mBuf + offset;
+    memmove(pos + valueSize, pos + oldValueSize, remainder);
+  } else {
+    // allocate new meta data element
+    newSize += keySize;
+    EnsureBuffer(newSize);
+
+    // Add after last element
+    pos = mBuf + mElementsSize;
+    memcpy(pos, aKey, keySize);
+    pos += keySize;
+  }
+
+  // Update value
+  memcpy(pos, aValue, valueSize);
+  mElementsSize = newSize;
+
+  return NS_OK;
+}
+
+CacheHashUtils::Hash16_t
+CacheFileMetadata::GetHash(uint32_t aIndex)
+{
+  MOZ_ASSERT(aIndex < mHashCount);
+  return PR_ntohs(mHashArray[aIndex]);
+}
+
+nsresult
+CacheFileMetadata::SetHash(uint32_t aIndex, CacheHashUtils::Hash16_t aHash)
+{
+  LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]",
+       this, aIndex, aHash));
+
+  MarkDirty();
+
+  MOZ_ASSERT(aIndex <= mHashCount);
+
+  if (aIndex > mHashCount) {
+    return NS_ERROR_INVALID_ARG;
+  } else if (aIndex == mHashCount) {
+    if ((aIndex + 1) * sizeof(CacheHashUtils::Hash16_t) > mHashArraySize) {
+      // reallocate hash array buffer
+      if (mHashArraySize == 0)
+        mHashArraySize = 32 * sizeof(CacheHashUtils::Hash16_t);
+      else
+        mHashArraySize *= 2;
+      mHashArray = static_cast<CacheHashUtils::Hash16_t *>(
+                     moz_xrealloc(mHashArray, mHashArraySize));
+    }
+
+    mHashCount++;
+  }
+
+  mHashArray[aIndex] = PR_htons(aHash);
+
+  DoMemoryReport(MemoryUsage());
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime)
+{
+  LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
+       this, aExpirationTime));
+
+  MarkDirty();
+  mMetaHdr.mExpirationTime = aExpirationTime;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetExpirationTime(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mExpirationTime;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::SetLastModified(uint32_t aLastModified)
+{
+  LOG(("CacheFileMetadata::SetLastModified() [this=%p, lastModified=%d]",
+       this, aLastModified));
+
+  MarkDirty();
+  mMetaHdr.mLastModified = aLastModified;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetLastModified(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mLastModified;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetLastFetched(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mLastFetched;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetFetchCount(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mFetchCount;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileMetadata::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                                 nsresult aResult)
+{
+  LOG(("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, result=0x%08x]",
+       this, aHandle, aResult));
+
+  MOZ_ASSERT(mListener);
+  MOZ_ASSERT(mWriteBuf);
+
+  free(mWriteBuf);
+  mWriteBuf = nullptr;
+
+  nsCOMPtr<CacheFileMetadataListener> listener;
+
+  mListener.swap(listener);
+  listener->OnMetadataWritten(aResult);
+
+  DoMemoryReport(MemoryUsage());
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
+                              nsresult aResult)
+{
+  LOG(("CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08x]",
+       this, aHandle, aResult));
+
+  MOZ_ASSERT(mListener);
+
+  nsresult rv;
+  nsCOMPtr<CacheFileMetadataListener> listener;
+
+  if (NS_FAILED(aResult)) {
+    LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed, "
+         "creating empty metadata. [this=%p, rv=0x%08x]", this, aResult));
+
+    InitEmptyMetadata();
+    mListener.swap(listener);
+    listener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  // check whether we have read all necessary data
+  uint32_t realOffset = PR_ntohl(*(reinterpret_cast<uint32_t *>(
+                                 mBuf + mBufSize - sizeof(uint32_t))));
+
+  int64_t size = mHandle->FileSize();
+  MOZ_ASSERT(size != -1);
+
+  if (realOffset >= size) {
+    LOG(("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating empty "
+         "metadata. [this=%p, realOffset=%d, size=%lld]", this, realOffset,
+         size));
+
+    InitEmptyMetadata();
+    mListener.swap(listener);
+    listener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  uint32_t usedOffset = size - mBufSize;
+
+  if (realOffset < usedOffset) {
+    uint32_t missing = usedOffset - realOffset;
+    // we need to read more data
+    mBuf = static_cast<char *>(moz_xrealloc(mBuf, mBufSize + missing));
+    memmove(mBuf + missing, mBuf, mBufSize);
+    mBufSize += missing;
+
+    DoMemoryReport(MemoryUsage());
+
+    LOG(("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
+         "have full metadata. [this=%p]", missing, this));
+
+    rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed"
+           " synchronously, creating empty metadata. [this=%p, rv=0x%08x]",
+           this, rv));
+
+      InitEmptyMetadata();
+      mListener.swap(listener);
+      listener->OnMetadataRead(rv);
+      return NS_OK;
+    }
+
+    return NS_OK;
+  }
+
+  // We have all data according to offset information at the end of the entry.
+  // Try to parse it.
+  rv = ParseMetadata(realOffset, realOffset - usedOffset);
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
+         "empty metadata. [this=%p]", this));
+    InitEmptyMetadata();
+  }
+
+  mListener.swap(listener);
+  listener->OnMetadataRead(NS_OK);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileMetadata::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+void
+CacheFileMetadata::InitEmptyMetadata()
+{
+  if (mBuf) {
+    free(mBuf);
+    mBuf = nullptr;
+    mBufSize = 0;
+  }
+  mOffset = 0;
+  mMetaHdr.mFetchCount = 1;
+  mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME;
+  mMetaHdr.mKeySize = mKey.Length();
+
+  DoMemoryReport(MemoryUsage());
+}
+
+nsresult
+CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset)
+{
+  LOG(("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
+       "bufOffset=%d]", this, aMetaOffset, aBufOffset));
+
+  nsresult rv;
+
+  uint32_t metaposOffset = mBufSize - sizeof(uint32_t);
+  uint32_t hashesOffset = aBufOffset + sizeof(uint32_t);
+  uint32_t hashCount = aMetaOffset / kChunkSize;
+  if (aMetaOffset % kChunkSize)
+    hashCount++;
+  uint32_t hashesLen = hashCount * sizeof(CacheHashUtils::Hash16_t);
+  uint32_t hdrOffset = hashesOffset + hashesLen;
+  uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader);
+
+  LOG(("CacheFileMetadata::ParseMetadata() [this=%p]\n  metaposOffset=%d\n  "
+       "hashesOffset=%d\n  hashCount=%d\n  hashesLen=%d\n  hdfOffset=%d\n  "
+       "keyOffset=%d\n", this, metaposOffset, hashesOffset, hashCount,
+       hashesLen,hdrOffset, keyOffset));
+
+  if (keyOffset > metaposOffset) {
+    LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
+         this));
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  uint32_t elementsOffset = reinterpret_cast<CacheFileMetadataHeader *>(
+                              mBuf + hdrOffset)->mKeySize + keyOffset + 1;
+
+  if (elementsOffset > metaposOffset) {
+    LOG(("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
+         "[this=%p]", elementsOffset, this));
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  // check that key ends with \0
+  if (mBuf[elementsOffset - 1] != 0) {
+    LOG(("CacheFileMetadata::ParseMetadata() - Elements not null terminated. "
+         "[this=%p]", this));
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  if (!mKeyIsHash) {
+    uint32_t keySize = reinterpret_cast<CacheFileMetadataHeader *>(
+                         mBuf + hdrOffset)->mKeySize;
+
+    if (keySize != mKey.Length()) {
+      LOG(("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s [this=%p]",
+           nsCString(mBuf + keyOffset, keySize).get(), this));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+
+    if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) {
+      LOG(("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s [this=%p]",
+           nsCString(mBuf + keyOffset, keySize).get(), this));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+  }
+
+  // check metadata hash (data from hashesOffset to metaposOffset)
+  CacheHashUtils::Hash32_t hash;
+  hash = CacheHashUtils::Hash(mBuf + hashesOffset,
+                              metaposOffset - hashesOffset);
+
+  if (hash != PR_ntohl(*(reinterpret_cast<uint32_t *>(mBuf + aBufOffset)))) {
+    LOG(("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of "
+         "the metadata is %x, hash in file is %x [this=%p]", hash,
+         PR_ntohl(*(reinterpret_cast<uint32_t *>(mBuf + aBufOffset))), this));
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  // check elements
+  rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
+  if (NS_FAILED(rv))
+    return rv;
+
+  mHashArraySize = hashesLen;
+  mHashCount = hashCount;
+  if (mHashArraySize) {
+    mHashArray = static_cast<CacheHashUtils::Hash16_t *>(
+                   moz_xmalloc(mHashArraySize));
+    memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
+  }
+
+  memcpy(&mMetaHdr, mBuf + hdrOffset, sizeof(CacheFileMetadataHeader));
+  mMetaHdr.mFetchCount++;
+  MarkDirty();
+
+  mElementsSize = metaposOffset - elementsOffset;
+  memmove(mBuf, mBuf + elementsOffset, mElementsSize);
+  mOffset = aMetaOffset;
+
+  // TODO: shrink memory if buffer is too big
+
+  DoMemoryReport(MemoryUsage());
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::CheckElements(const char *aBuf, uint32_t aSize)
+{
+  if (aSize) {
+    // Check if the metadata ends with a zero byte.
+    if (aBuf[aSize - 1] != 0) {
+      NS_ERROR("Metadata elements are not null terminated");
+      LOG(("CacheFileMetadata::CheckElements() - Elements are not null "
+           "terminated. [this=%p]", this));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+    // Check that there are an even number of zero bytes
+    // to match the pattern { key \0 value \0 }
+    bool odd = false;
+    for (uint32_t i = 0; i < aSize; i++) {
+      if (aBuf[i] == 0)
+        odd = !odd;
+    }
+    if (odd) {
+      NS_ERROR("Metadata elements are malformed");
+      LOG(("CacheFileMetadata::CheckElements() - Elements are malformed. "
+           "[this=%p]", this));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+  }
+  return NS_OK;
+}
+
+void
+CacheFileMetadata::EnsureBuffer(uint32_t aSize)
+{
+  if (mBufSize < aSize) {
+    mBufSize = aSize;
+    mBuf = static_cast<char *>(moz_xrealloc(mBuf, mBufSize));
+  }
+
+  DoMemoryReport(MemoryUsage());
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileMetadata__h__
+#define CacheFileMetadata__h__
+
+#include "CacheFileIOManager.h"
+#include "CacheStorageService.h"
+#include "CacheHashUtils.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+typedef struct {
+  uint32_t        mFetchCount;
+  uint32_t        mLastFetched;
+  uint32_t        mLastModified;
+  uint32_t        mExpirationTime;
+  uint32_t        mKeySize;
+} CacheFileMetadataHeader;
+
+#define CACHEFILEMETADATALISTENER_IID \
+{ /* a9e36125-3f01-4020-9540-9dafa8d31ba7 */       \
+  0xa9e36125,                                      \
+  0x3f01,                                          \
+  0x4020,                                          \
+  {0x95, 0x40, 0x9d, 0xaf, 0xa8, 0xd3, 0x1b, 0xa7} \
+}
+
+class CacheFileMetadataListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEMETADATALISTENER_IID)
+
+  NS_IMETHOD OnMetadataRead(nsresult aResult) = 0;
+  NS_IMETHOD OnMetadataWritten(nsresult aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileMetadataListener,
+                              CACHEFILEMETADATALISTENER_IID)
+
+
+class CacheFileMetadata : public CacheFileIOListener
+                        , public CacheMemoryConsumer
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheFileMetadata(CacheFileHandle *aHandle,
+                    const nsACString &aKey,
+                    bool aKeyIsHash);
+  CacheFileMetadata(const nsACString &aKey);
+
+  void SetHandle(CacheFileHandle *aHandle);
+
+  nsresult ReadMetadata(CacheFileMetadataListener *aListener);
+  nsresult WriteMetadata(uint32_t aOffset,
+                         CacheFileMetadataListener *aListener);
+
+  const char * GetElement(const char *aKey);
+  nsresult     SetElement(const char *aKey, const char *aValue);
+
+  CacheHashUtils::Hash16_t GetHash(uint32_t aIndex);
+  nsresult                 SetHash(uint32_t aIndex,
+                                   CacheHashUtils::Hash16_t aHash);
+
+  nsresult SetExpirationTime(uint32_t aExpirationTime);
+  nsresult GetExpirationTime(uint32_t *_retval);
+  nsresult SetLastModified(uint32_t aLastModified);
+  nsresult GetLastModified(uint32_t *_retval);
+  nsresult GetLastFetched(uint32_t *_retval);
+  nsresult GetFetchCount(uint32_t *_retval);
+
+  int64_t  Offset() { return mOffset; }
+  uint32_t ElementsSize() { return mElementsSize; }
+  void     MarkDirty() { mIsDirty = true; }
+  bool     IsDirty() { return mIsDirty; }
+  uint32_t MemoryUsage() { return mHashArraySize + mBufSize; }
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult);
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult);
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult);
+
+private:
+  virtual ~CacheFileMetadata();
+
+  void     InitEmptyMetadata();
+  nsresult ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset);
+  nsresult CheckElements(const char *aBuf, uint32_t aSize);
+  void     EnsureBuffer(uint32_t aSize);
+
+  nsRefPtr<CacheFileHandle>           mHandle;
+  nsCString                           mKey;
+  bool                                mKeyIsHash;
+  CacheHashUtils::Hash16_t           *mHashArray;
+  uint32_t                            mHashArraySize;
+  uint32_t                            mHashCount;
+  int64_t                             mOffset;
+  char                               *mBuf; // used for parsing, then points
+                                            // to elements
+  uint32_t                            mBufSize;
+  char                               *mWriteBuf;
+  CacheFileMetadataHeader             mMetaHdr;
+  uint32_t                            mElementsSize;
+  bool                                mIsDirty;
+  nsCOMPtr<CacheFileMetadataListener> mListener;
+};
+
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -0,0 +1,382 @@
+/* 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 "CacheFileOutputStream.h"
+
+#include "CacheLog.h"
+#include "CacheFile.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(CacheFileOutputStream)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFileOutputStream::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFileOutputStream");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    {
+      CacheFileAutoLock lock(mFile);
+      mFile->RemoveOutput(this);
+    }
+    delete (this);
+    return 0;
+  }
+
+  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(mozilla::net::CacheFileChunkListener)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile)
+  : mFile(aFile)
+  , mPos(0)
+  , mClosed(false)
+  , mStatus(NS_OK)
+  , mCallbackFlags(0)
+{
+  LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileOutputStream);
+}
+
+CacheFileOutputStream::~CacheFileOutputStream()
+{
+  LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileOutputStream);
+}
+
+// nsIOutputStream
+NS_IMETHODIMP
+CacheFileOutputStream::Close()
+{
+  LOG(("CacheFileOutputStream::Close() [this=%p]", this));
+  return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::Flush()
+{
+  // TODO do we need to implement flush ???
+  LOG(("CacheFileOutputStream::Flush() [this=%p]", this));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::Write(const char * aBuf, uint32_t aCount,
+                             uint32_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+
+  LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount));
+
+  if (mClosed) {
+    LOG(("CacheFileOutputStream::Write() - Stream is closed. [this=%p, "
+         "status=0x%08x]", this, mStatus));
+
+    return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
+  }
+
+  *_retval = aCount;
+
+  while (aCount) {
+    EnsureCorrectChunk(false);
+
+    FillHole();
+
+    uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
+    uint32_t canWrite = kChunkSize - chunkOffset;
+    uint32_t thisWrite = std::min(static_cast<uint32_t>(canWrite), aCount);
+    mChunk->EnsureBufSize(chunkOffset + thisWrite);
+    memcpy(mChunk->BufForWriting() + chunkOffset, aBuf, thisWrite);
+
+    mPos += thisWrite;
+    aBuf += thisWrite;
+    aCount -= thisWrite;
+
+    mChunk->UpdateDataSize(chunkOffset, thisWrite, false);
+  }
+
+  EnsureCorrectChunk(true);
+
+  LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]",
+       *_retval, this));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount,
+                                 uint32_t *_retval)
+{
+  LOG(("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p"
+       ", count=%d]", this, aFromStream, aCount));
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void *aClosure,
+                                     uint32_t aCount, uint32_t *_retval)
+{
+  LOG(("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, "
+       "count=%d]", this, aCount));
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::IsNonBlocking(bool *_retval)
+{
+  *_retval = false;
+  return NS_OK;
+}
+
+// nsIAsyncOutputStream
+NS_IMETHODIMP
+CacheFileOutputStream::CloseWithStatus(nsresult aStatus)
+{
+  CacheFileAutoLock lock(mFile);
+
+  LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08x]",
+       this, aStatus));
+
+  if (mClosed) {
+    MOZ_ASSERT(!mCallback);
+    return NS_OK;
+  }
+
+  mClosed = true;
+  mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
+
+  if (mChunk)
+    ReleaseChunk();
+
+  if (mCallback)
+    NotifyListener();
+
+  mFile->RemoveOutput(this);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback,
+                                 uint32_t aFlags,
+                                 uint32_t aRequestedCount,
+                                 nsIEventTarget *aEventTarget)
+{
+  CacheFileAutoLock lock(mFile);
+
+  LOG(("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
+       "requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags,
+       aRequestedCount, aEventTarget));
+
+  mCallback = aCallback;
+  mCallbackFlags = aFlags;
+
+  if (!mCallback)
+    return NS_OK;
+
+  // The stream is blocking so it is writable at any time
+  if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY))
+    NotifyListener();
+
+  return NS_OK;
+}
+
+// nsISeekableStream
+NS_IMETHODIMP
+CacheFileOutputStream::Seek(int32_t whence, int64_t offset)
+{
+  CacheFileAutoLock lock(mFile);
+
+  LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%lld]",
+       this, whence, offset));
+
+  if (mClosed) {
+    LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this));
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  int64_t newPos = offset;
+  switch (whence) {
+    case NS_SEEK_SET:
+      break;
+    case NS_SEEK_CUR:
+      newPos += mPos;
+      break;
+    case NS_SEEK_END:
+      newPos += mFile->mDataSize;
+      break;
+    default:
+      NS_ERROR("invalid whence");
+      return NS_ERROR_INVALID_ARG;
+  }
+  mPos = newPos;
+  EnsureCorrectChunk(true);
+
+  LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%lld]", this, mPos));
+  return NS_OK;
+}
+
+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;
+  }
+
+  *_retval = mPos;
+
+  LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%lld]", 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;
+}
+
+nsresult
+CacheFileOutputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
+{
+  MOZ_CRASH(
+    "CacheFileOutputStream::OnChunkWritten should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileOutputStream::OnChunkAvailable(nsresult aResult,
+                                        uint32_t aChunkIdx,
+                                        CacheFileChunk *aChunk)
+{
+  MOZ_CRASH(
+    "CacheFileOutputStream::OnChunkAvailable should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileOutputStream::OnChunkUpdated(CacheFileChunk *aChunk)
+{
+  MOZ_CRASH(
+    "CacheFileOutputStream::OnChunkUpdated should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+void
+CacheFileOutputStream::ReleaseChunk()
+{
+  LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]",
+       this, mChunk->Index()));
+
+  mFile->ReleaseOutsideLock(mChunk.forget().get());
+}
+
+void
+CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
+       this, aReleaseOnly));
+
+  uint32_t chunkIdx = mPos / kChunkSize;
+
+  if (mChunk) {
+    if (mChunk->Index() == chunkIdx) {
+      // we have a correct chunk
+      LOG(("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk "
+           "[this=%p, idx=%d]", this, chunkIdx));
+
+      return;
+    }
+    else {
+      ReleaseChunk();
+    }
+  }
+
+  if (aReleaseOnly)
+    return;
+
+  DebugOnly<nsresult> rv;
+  rv = mFile->GetChunkLocked(chunkIdx, true, nullptr, getter_AddRefs(mChunk));
+  MOZ_ASSERT(NS_SUCCEEDED(rv),
+             "CacheFile::GetChunkLocked() should always succeed for writer");
+}
+
+void
+CacheFileOutputStream::FillHole()
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(mChunk);
+  MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+  uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize;
+  if (mChunk->DataSize() >= pos)
+    return;
+
+  LOG(("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range "
+       "%d-%d [this=%p]", mChunk->Index(), mChunk->DataSize(), pos - 1, this));
+
+  mChunk->EnsureBufSize(pos);
+  memset(mChunk->BufForWriting() + mChunk->DataSize(), 0,
+         pos - mChunk->DataSize());
+
+  mChunk->UpdateDataSize(mChunk->DataSize(), pos - mChunk->DataSize(), false);
+}
+
+void
+CacheFileOutputStream::NotifyListener()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this));
+
+  MOZ_ASSERT(mCallback);
+
+  if (!mCallbackTarget)
+    mCallbackTarget = NS_GetCurrentThread();
+
+  nsCOMPtr<nsIOutputStreamCallback> asyncCallback =
+    NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget);
+
+  mCallback = nullptr;
+  mCallbackTarget = nullptr;
+
+  asyncCallback->OnOutputStreamReady(this);
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.h
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileOutputStream__h__
+#define CacheFileOutputStream__h__
+
+#include "nsIAsyncOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "CacheFileChunk.h"
+
+
+namespace mozilla {
+namespace net {
+
+class CacheFile;
+
+class CacheFileOutputStream : public nsIAsyncOutputStream
+                            , public nsISeekableStream
+                            , public CacheFileChunkListener
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOUTPUTSTREAM
+  NS_DECL_NSIASYNCOUTPUTSTREAM
+  NS_DECL_NSISEEKABLESTREAM
+
+public:
+  CacheFileOutputStream(CacheFile *aFile);
+
+  NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                              CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk);
+
+private:
+  virtual ~CacheFileOutputStream();
+
+  void ReleaseChunk();
+  void EnsureCorrectChunk(bool aReleaseOnly);
+  void FillHole();
+  void NotifyListener();
+
+  nsRefPtr<CacheFile>      mFile;
+  nsRefPtr<CacheFileChunk> mChunk;
+  int64_t                  mPos;
+  bool                     mClosed;
+  nsresult                 mStatus;
+
+  nsCOMPtr<nsIOutputStreamCallback> mCallback;
+  uint32_t                          mCallbackFlags;
+  nsCOMPtr<nsIEventTarget>          mCallbackTarget;
+};
+
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.cpp
@@ -0,0 +1,87 @@
+/* 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 "CacheHashUtils.h"
+
+#include "plstr.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ *  CacheHashUtils::Hash(const char * key, uint32_t initval)
+ *
+ *  See http://burtleburtle.net/bob/hash/evahash.html for more information
+ *  about this hash function.
+ *
+ *  This algorithm is used to check the data integrity.
+ */
+
+static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
+{
+  a -= b; a -= c; a ^= (c>>13);
+  b -= c; b -= a; b ^= (a<<8);
+  c -= a; c -= b; c ^= (b>>13);
+  a -= b; a -= c; a ^= (c>>12);
+  b -= c; b -= a; b ^= (a<<16);
+  c -= a; c -= b; c ^= (b>>5);
+  a -= b; a -= c; a ^= (c>>3);
+  b -= c; b -= a; b ^= (a<<10);
+  c -= a; c -= b; c ^= (b>>15);
+}
+
+CacheHashUtils::Hash32_t
+CacheHashUtils::Hash(const char *aData, uint32_t aSize, uint32_t aInitval)
+{
+  const uint8_t *k = reinterpret_cast<const uint8_t*>(aData);
+  uint32_t a, b, c, len/*, length*/;
+
+//  length = PL_strlen(key);
+  /* Set up the internal state */
+  len = aSize;
+  a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
+  c = aInitval;        /* variable initialization of internal state */
+
+  /*---------------------------------------- handle most of the key */
+  while (len >= 12)
+  {
+    a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24);
+    b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24);
+    c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24);
+    hashmix(a, b, c);
+    k += 12; len -= 12;
+  }
+
+  /*------------------------------------- handle the last 11 bytes */
+  c += aSize;
+  switch(len) {              /* all the case statements fall through */
+    case 11: c += (uint32_t(k[10])<<24);
+    case 10: c += (uint32_t(k[9])<<16);
+    case 9 : c += (uint32_t(k[8])<<8);
+    /* the low-order byte of c is reserved for the length */
+    case 8 : b += (uint32_t(k[7])<<24);
+    case 7 : b += (uint32_t(k[6])<<16);
+    case 6 : b += (uint32_t(k[5])<<8);
+    case 5 : b += k[4];
+    case 4 : a += (uint32_t(k[3])<<24);
+    case 3 : a += (uint32_t(k[2])<<16);
+    case 2 : a += (uint32_t(k[1])<<8);
+    case 1 : a += k[0];
+    /* case 0: nothing left to add */
+  }
+  hashmix(a, b, c);
+
+  return c;
+}
+
+CacheHashUtils::Hash16_t
+CacheHashUtils::Hash16(const char *aData, uint32_t aSize, uint32_t aInitval)
+{
+  Hash32_t hash = Hash(aData, aSize, aInitval);
+  return (hash & 0xFFFF);
+}
+
+} // net
+} // mozilla
+
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheHashUtils__h__
+#define CacheHashUtils__h__
+
+#include "mozilla/Types.h"
+#include "prnetdb.h"
+#include "nsPrintfCString.h"
+
+#define LOGSHA1(x) \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[0]), \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[1]), \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[2]), \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[3]), \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[4])
+
+#define SHA1STRING(x) \
+    (nsPrintfCString("%08x%08x%08x%08x%08x", LOGSHA1(x)).get())
+
+namespace mozilla {
+namespace net {
+
+class CacheHashUtils
+{
+public:
+  typedef uint16_t Hash16_t;
+  typedef uint32_t Hash32_t;
+
+  static Hash32_t Hash(const char* aData, uint32_t aSize, uint32_t aInitval=0);
+  static Hash16_t Hash16(const char* aData, uint32_t aSize,
+                         uint32_t aInitval=0);
+};
+
+
+} // net
+} // mozilla
+
+#endif