Bug 913807 - HTTP cache v2: API+top service+integration+tests, off by default, r=michal+ehsan+mark.finkle+fabrice+mhammond+gavin
authorHonza Bambas <honzab.moz@firemni.cz>
Fri, 20 Sep 2013 11:11:25 +0200
changeset 156754 0c91d9aa9476a9c2c6eb01e6142267d7987977e2
parent 156753 7609a7ace997dac3c643e61200a21a34d76a3663
child 156755 4d66cd80b6be17120ae9aabc93dbcb6ed9c161db
push idunknown
push userunknown
push dateunknown
reviewersmichal
bugs913807
milestone27.0a1
Bug 913807 - HTTP cache v2: API+top service+integration+tests, off by default, r=michal+ehsan+mark.finkle+fabrice+mhammond+gavin
b2g/app/b2g.js
b2g/installer/package-manifest.in
browser/base/content/pageinfo/pageInfo.js
browser/base/content/sanitize.js
browser/base/content/test/general/browser_bookmark_titles.js
browser/base/content/test/general/browser_save_private_link_perwindowpb.js
browser/base/content/test/social/browser_social_errorPage.js
browser/components/places/tests/unit/head_bookmarks.js
browser/components/places/tests/unit/test_clearHistory_shutdown.js
browser/components/preferences/advanced.js
browser/components/preferences/in-content/advanced.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
browser/devtools/styleeditor/test/head.js
browser/installer/package-manifest.in
browser/metro/base/content/sanitize.js
browser/modules/offlineAppCache.jsm
content/base/test/test_bug482935.html
dom/src/offline/nsDOMOfflineResourceList.cpp
dom/tests/mochitest/ajax/offline/foreign2.html
dom/tests/mochitest/ajax/offline/offlineTests.js
dom/tests/mochitest/ajax/offline/test_foreign.html
dom/tests/mochitest/ajax/offline/test_offlineMode.html
image/src/imgRequest.cpp
image/test/unit/test_private_channel.js
layout/build/nsLayoutStatics.cpp
mobile/android/app/mobile.js
mobile/android/installer/package-manifest.in
mobile/android/modules/Sanitizer.jsm
modules/libpref/src/init/all.js
netwerk/base/public/moz.build
netwerk/base/public/nsIApplicationCacheService.idl
netwerk/base/public/nsILoadContextInfo.idl
netwerk/base/src/LoadContextInfo.cpp
netwerk/base/src/LoadContextInfo.h
netwerk/base/src/moz.build
netwerk/build/Makefile.in
netwerk/build/nsNetModule.cpp
netwerk/cache/nsApplicationCacheService.cpp
netwerk/cache/nsCacheUtils.cpp
netwerk/cache/nsDiskCacheDeviceSQL.cpp
netwerk/cache/nsDiskCacheDeviceSQL.h
netwerk/cache2/AppCacheStorage.cpp
netwerk/cache2/AppCacheStorage.h
netwerk/cache2/CacheEntriesEnumerator.cpp
netwerk/cache2/CacheEntriesEnumerator.h
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheEntry.h
netwerk/cache2/CacheIOThread.cpp
netwerk/cache2/CacheIOThread.h
netwerk/cache2/CacheLog.cpp
netwerk/cache2/CacheLog.h
netwerk/cache2/CacheObserver.cpp
netwerk/cache2/CacheObserver.h
netwerk/cache2/CacheStorage.cpp
netwerk/cache2/CacheStorage.h
netwerk/cache2/CacheStorageService.cpp
netwerk/cache2/CacheStorageService.h
netwerk/cache2/Makefile.in
netwerk/cache2/OldWrappers.cpp
netwerk/cache2/OldWrappers.h
netwerk/cache2/moz.build
netwerk/cache2/nsICacheEntry.idl
netwerk/cache2/nsICacheEntryDoomCallback.idl
netwerk/cache2/nsICacheEntryOpenCallback.idl
netwerk/cache2/nsICacheStorage.idl
netwerk/cache2/nsICacheStorageService.idl
netwerk/cache2/nsICacheStorageVisitor.idl
netwerk/moz.build
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/HttpChannelParentListener.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/test/unit/head_cache.js
netwerk/test/unit/head_cache2.js
netwerk/test/unit/socks_client_subprocess.js
netwerk/test/unit/test_304_responses.js
netwerk/test/unit/test_307_redirect.js
netwerk/test/unit/test_MIME_params.js
netwerk/test/unit/test_NetUtil.js
netwerk/test/unit/test_URIs.js
netwerk/test/unit/test_XHR_redirects.js
netwerk/test/unit/test_about_networking.js
netwerk/test/unit/test_addr_in_use_error.js
netwerk/test/unit/test_assoc.js
netwerk/test/unit/test_auth_jar.js
netwerk/test/unit/test_auth_proxy.js
netwerk/test/unit/test_authentication.js
netwerk/test/unit/test_backgroundfilesaver.js
netwerk/test/unit/test_bug203271.js
netwerk/test/unit/test_bug248970_cache.js
netwerk/test/unit/test_bug248970_cookie.js
netwerk/test/unit/test_bug261425.js
netwerk/test/unit/test_bug263127.js
netwerk/test/unit/test_bug321706.js
netwerk/test/unit/test_bug331825.js
netwerk/test/unit/test_bug337744.js
netwerk/test/unit/test_bug365133.js
netwerk/test/unit/test_bug368702.js
netwerk/test/unit/test_bug369787.js
netwerk/test/unit/test_bug371473.js
netwerk/test/unit/test_bug376844.js
netwerk/test/unit/test_bug376865.js
netwerk/test/unit/test_bug380994.js
netwerk/test/unit/test_bug388281.js
netwerk/test/unit/test_bug396389.js
netwerk/test/unit/test_bug411952.js
netwerk/test/unit/test_bug412945.js
netwerk/test/unit/test_bug414122.js
netwerk/test/unit/test_bug429347.js
netwerk/test/unit/test_bug455311.js
netwerk/test/unit/test_bug468426.js
netwerk/test/unit/test_bug468594.js
netwerk/test/unit/test_bug470716.js
netwerk/test/unit/test_bug479413.js
netwerk/test/unit/test_bug479485.js
netwerk/test/unit/test_bug482601.js
netwerk/test/unit/test_bug484684.js
netwerk/test/unit/test_bug490095.js
netwerk/test/unit/test_bug504014.js
netwerk/test/unit/test_bug510359.js
netwerk/test/unit/test_bug515583.js
netwerk/test/unit/test_bug528292.js
netwerk/test/unit/test_bug536324_64bit_content_length.js
netwerk/test/unit/test_bug540566.js
netwerk/test/unit/test_bug543805.js
netwerk/test/unit/test_bug553970.js
netwerk/test/unit/test_bug561042.js
netwerk/test/unit/test_bug561276.js
netwerk/test/unit/test_bug580508.js
netwerk/test/unit/test_bug586908.js
netwerk/test/unit/test_bug596443.js
netwerk/test/unit/test_bug618835.js
netwerk/test/unit/test_bug633743.js
netwerk/test/unit/test_bug650995.js
netwerk/test/unit/test_bug651100.js
netwerk/test/unit/test_bug654926.js
netwerk/test/unit/test_bug654926_doom_and_read.js
netwerk/test/unit/test_bug654926_test_seek.js
netwerk/test/unit/test_bug659569.js
netwerk/test/unit/test_bug660066.js
netwerk/test/unit/test_bug667818.js
netwerk/test/unit/test_bug667907.js
netwerk/test/unit/test_bug669001.js
netwerk/test/unit/test_bug712914_secinfo_validation.js
netwerk/test/unit/test_bug767025.js
netwerk/test/unit/test_bug770243.js
netwerk/test/unit/test_bug812167.js
netwerk/test/unit/test_bug826063.js
netwerk/test/unit/test_bug856978.js
netwerk/test/unit/test_bug894586.js
netwerk/test/unit/test_cache2-01-basic.js
netwerk/test/unit/test_cache2-01b-basic-datasize.js
netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js
netwerk/test/unit/test_cache2-01d-basic-not-wanted.js
netwerk/test/unit/test_cache2-02-open-non-existing.js
netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js
netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js
netwerk/test/unit/test_cache2-05-visit.js
netwerk/test/unit/test_cache2-06-pb-mode.js
netwerk/test/unit/test_cache2-07-visit-memory.js
netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js
netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js
netwerk/test/unit/test_cache2-10-evict-direct.js
netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js
netwerk/test/unit/test_cache2-11-evict-memory.js
netwerk/test/unit/test_cache2-12-evict-disk.js
netwerk/test/unit/test_cache2-13-evict-non-existing.js
netwerk/test/unit/test_cache2-14-concurent-readers.js
netwerk/test/unit/test_cache2-15-conditional-304.js
netwerk/test/unit/test_cache2-16-conditional-200.js
netwerk/test/unit/test_cache2-17-evict-all.js
netwerk/test/unit/test_cache2-18-not-valid.js
netwerk/test/unit/test_cache2-19-range-206.js
netwerk/test/unit/test_cache2-20-range-200.js
netwerk/test/unit/test_cache2-21-anon-storage.js
netwerk/test/unit/test_cache2-22-anon-visit.js
netwerk/test/unit/test_cacheForOfflineUse_no-store.js
netwerk/test/unit/test_cache_jar.js
netwerk/test/unit/test_cacheflags.js
netwerk/test/unit/test_channel_close.js
netwerk/test/unit/test_compressappend.js
netwerk/test/unit/test_content_encoding_gzip.js
netwerk/test/unit/test_content_sniffer.js
netwerk/test/unit/test_cookie_header.js
netwerk/test/unit/test_cookiejars.js
netwerk/test/unit/test_data_protocol.js
netwerk/test/unit/test_dns_localredirect.js
netwerk/test/unit/test_dns_service.js
netwerk/test/unit/test_doomentry.js
netwerk/test/unit/test_duplicate_headers.js
netwerk/test/unit/test_event_sink.js
netwerk/test/unit/test_fallback_no-cache-entry_canceled.js
netwerk/test/unit/test_fallback_no-cache-entry_passing.js
netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js
netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js
netwerk/test/unit/test_fallback_request-error_canceled.js
netwerk/test/unit/test_fallback_request-error_passing.js
netwerk/test/unit/test_fallback_response-error_canceled.js
netwerk/test/unit/test_fallback_response-error_passing.js
netwerk/test/unit/test_file_partial_inputstream.js
netwerk/test/unit/test_file_protocol.js
netwerk/test/unit/test_force_sniffing.js
netwerk/test/unit/test_freshconnection.js
netwerk/test/unit/test_gre_resources.js
netwerk/test/unit/test_gzipped_206.js
netwerk/test/unit/test_head.js
netwerk/test/unit/test_header_Accept-Language.js
netwerk/test/unit/test_headers.js
netwerk/test/unit/test_httpauth.js
netwerk/test/unit/test_httpcancel.js
netwerk/test/unit/test_httpsuspend.js
netwerk/test/unit/test_idn_urls.js
netwerk/test/unit/test_invalidport.js
netwerk/test/unit/test_mismatch_last-modified.js
netwerk/test/unit/test_multipart_byteranges.js
netwerk/test/unit/test_multipart_streamconv.js
netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js
netwerk/test/unit/test_nestedabout_serialize.js
netwerk/test/unit/test_net_addr.js
netwerk/test/unit/test_nojsredir.js
netwerk/test/unit/test_offlinecache_custom-directory.js
netwerk/test/unit/test_pinned_app_cache.js
netwerk/test/unit/test_plaintext_sniff.js
netwerk/test/unit/test_post.js
netwerk/test/unit/test_private_cookie_changed.js
netwerk/test/unit/test_private_necko_channel.js
netwerk/test/unit/test_progress.js
netwerk/test/unit/test_proxy-failover_canceled.js
netwerk/test/unit/test_proxy-failover_passing.js
netwerk/test/unit/test_proxy-replace_canceled.js
netwerk/test/unit/test_proxy-replace_passing.js
netwerk/test/unit/test_psl.js
netwerk/test/unit/test_range_requests.js
netwerk/test/unit/test_readline.js
netwerk/test/unit/test_redirect-caching_canceled.js
netwerk/test/unit/test_redirect-caching_failure.js
netwerk/test/unit/test_redirect-caching_passing.js
netwerk/test/unit/test_redirect_baduri.js
netwerk/test/unit/test_redirect_canceled.js
netwerk/test/unit/test_redirect_failure.js
netwerk/test/unit/test_redirect_from_script.js
netwerk/test/unit/test_redirect_loop.js
netwerk/test/unit/test_redirect_passing.js
netwerk/test/unit/test_reentrancy.js
netwerk/test/unit/test_reopen.js
netwerk/test/unit/test_resumable_channel.js
netwerk/test/unit/test_resumable_truncate.js
netwerk/test/unit/test_safeoutputstream.js
netwerk/test/unit/test_simple.js
netwerk/test/unit/test_socks.js
netwerk/test/unit/test_speculative_connect.js
netwerk/test/unit/test_streamcopier.js
netwerk/test/unit/test_traceable_channel.js
netwerk/test/unit/test_unix_domain.js
netwerk/test/unit/test_xmlhttprequest.js
netwerk/test/unit/xpcshell.ini
netwerk/test/unit_ipc/head_cc.js
netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js
netwerk/test/unit_ipc/test_cookie_header_wrap.js
netwerk/test/unit_ipc/test_cookiejars_wrap.js
netwerk/test/unit_ipc/xpcshell.ini
toolkit/components/places/AsyncFaviconHelpers.cpp
toolkit/components/places/nsLivemarkService.js
toolkit/components/places/tests/browser/browser_bug680727.js
toolkit/forgetaboutsite/ForgetAboutSite.jsm
toolkit/modules/LoadContextInfo.jsm
toolkit/modules/Services.jsm
toolkit/modules/moz.build
toolkit/modules/tests/xpcshell/test_Services.js
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsPrefetchService.cpp
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -20,16 +20,18 @@ pref("browser.cache.disk.capacity", 5500
 pref("browser.cache.disk.parent_directory", "/cache");
 #endif
 pref("browser.cache.disk.smart_size.enabled", false);
 pref("browser.cache.disk.smart_size.first_run", false);
 
 pref("browser.cache.memory.enable", true);
 pref("browser.cache.memory.capacity", 1024); // kilobytes
 
+pref("browser.cache.memory_limit", 2048); // 2 MB
+
 /* image cache prefs */
 pref("image.cache.size", 1048576); // bytes
 pref("image.high_quality_downscaling.enabled", false);
 pref("canvas.image.cache.limit", 10485760); // 10 MB
 
 /* offline cache prefs */
 pref("browser.offline-apps.notify", false);
 pref("browser.cache.offline.enable", true);
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -249,16 +249,17 @@
 @BINPATH@/components/layout_xul.xpt
 @BINPATH@/components/locale.xpt
 @BINPATH@/components/lwbrk.xpt
 @BINPATH@/components/migration.xpt
 @BINPATH@/components/mimetype.xpt
 @BINPATH@/components/mozfind.xpt
 @BINPATH@/components/necko_about.xpt
 @BINPATH@/components/necko_cache.xpt
+@BINPATH@/components/necko_cache2.xpt
 @BINPATH@/components/necko_cookie.xpt
 @BINPATH@/components/necko_dns.xpt
 @BINPATH@/components/necko_file.xpt
 @BINPATH@/components/necko_ftp.xpt
 @BINPATH@/components/necko_http.xpt
 @BINPATH@/components/necko_res.xpt
 @BINPATH@/components/necko_socket.xpt
 @BINPATH@/components/necko_strconv.xpt
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -1,12 +1,16 @@
 /* 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/. */
 
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
 //******** define a js object to implement nsITreeView
 function pageInfoTreeView(treeid, copycol)
 {
   // copycol is the index number for the column that we want to add to
   // the copy-n-paste buffer when the user hits accel-c
   this.treeid = treeid;
   this.copycol = copycol;
   this.rows = 0;
@@ -211,23 +215,25 @@ var gStrings = { };
 var gBundle;
 
 const PERMISSION_CONTRACTID     = "@mozilla.org/permissionmanager;1";
 const PREFERENCES_CONTRACTID    = "@mozilla.org/preferences-service;1";
 const ATOM_CONTRACTID           = "@mozilla.org/atom-service;1";
 
 // a number of services I'll need later
 // the cache services
-const nsICacheService = Components.interfaces.nsICacheService;
-const ACCESS_READ     = Components.interfaces.nsICache.ACCESS_READ;
-const cacheService = Components.classes["@mozilla.org/network/cache-service;1"].getService(nsICacheService);
-var httpCacheSession = cacheService.createSession("HTTP", 0, true);
-httpCacheSession.doomEntriesIfExpired = false;
-var ftpCacheSession = cacheService.createSession("FTP", 0, true);
-ftpCacheSession.doomEntriesIfExpired = false;
+const nsICacheStorageService = Components.interfaces.nsICacheStorageService;
+const nsICacheStorage = Components.interfaces.nsICacheStorage;
+const cacheService = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"].getService(nsICacheStorageService);
+
+var loadContextInfo = LoadContextInfo.fromLoadContext(
+  window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+        .getInterface(Components.interfaces.nsIWebNavigation)
+        .QueryInterface(Components.interfaces.nsILoadContext), false);
+var diskStorage = cacheService.diskCacheStorage(loadContextInfo, false);
 
 const nsICookiePermission  = Components.interfaces.nsICookiePermission;
 const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
 
 const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
 const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
 
 // clipboard helper
@@ -458,29 +464,26 @@ function toggleGroupbox(id)
       elt.flexWhenOpened = elt.flex;
       elt.flex = 0;
     }
   }
 }
 
 function openCacheEntry(key, cb)
 {
-  var tries = 0;
   var checkCacheListener = {
-    onCacheEntryAvailable: function(entry, access, status) {
-      if (entry || tries == 1) {
-        cb(entry);
-      }
-      else {
-        tries++;
-        ftpCacheSession.asyncOpenCacheEntry(key, ACCESS_READ, this, true);
-      }
-    }
+    onCacheEntryCheck: function(entry, appCache) {
+      return nsICacheEntryOpenCallback.ENTRY_VALID;
+    },
+    onCacheEntryAvailable: function(entry, isNew, appCache, status) {
+      cb(entry);
+    },
+    get mainThreadOnly() { return true; }
   };
-  httpCacheSession.asyncOpenCacheEntry(key, ACCESS_READ, checkCacheListener, true);
+  diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener);
 }
 
 function makeGeneralTab()
 {
   var title = (gDocument.title) ? gBundle.getFormattedString("pageTitle", [gDocument.title]) : gBundle.getString("noPageTitle");
   document.getElementById("titletext").value = title;
 
   var url = gDocument.location.toString();
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -108,22 +108,22 @@ Sanitizer.prototype = {
   // pref to determine a range
   ignoreTimespan : true,
   range : null,
 
   items: {
     cache: {
       clear: function ()
       {
-        var cacheService = Cc["@mozilla.org/network/cache-service;1"].
-                          getService(Ci.nsICacheService);
+        var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+                    getService(Ci.nsICacheStorageService);
         try {
           // Cache doesn't consult timespan, nor does it have the
           // facility for timespan-based eviction.  Wipe it.
-          cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE);
+          cache.clear();
         } catch(er) {}
 
         var imageCache = Cc["@mozilla.org/image/tools;1"].
                          getService(Ci.imgITools).getImgCacheForDocument(null);
         try {
           imageCache.clearCache(false); // true=chrome, false=content
         } catch(er) {}
       },
--- a/browser/base/content/test/general/browser_bookmark_titles.js
+++ b/browser/base/content/test/general/browser_bookmark_titles.js
@@ -54,17 +54,17 @@ function generatorTest() {
     let proxy = Services.prefs.getIntPref('network.proxy.type');
     Services.prefs.setIntPref('network.proxy.type', 0);
     registerCleanupFunction(function () {
         BrowserOffline.toggleOfflineStatus();
         Services.prefs.setIntPref('network.proxy.type', proxy);
     });
 
     // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
-    Services.cache.evictEntries(Services.cache.STORE_ANYWHERE);
+    Services.cache2.clear();
 
     let [uri, title] = tests[0];
     content.location = uri;
     yield undefined;
     // The offline mode test is only good if the page failed to load.
     is(content.document.documentURI.substring(0, 14), 'about:neterror',
         "Offline mode successfully simulated network outage.");
     checkBookmark(uri, title);
--- a/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
+++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
@@ -1,39 +1,48 @@
 /* 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/. */
+
+let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
+
 function test() {
   // initialization
   waitForExplicitFinish();
   let windowsToClose = [];
   let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.html";
   let fileName;
   let MockFilePicker = SpecialPowers.MockFilePicker;
-  let cache = Cc["@mozilla.org/network/cache-service;1"]
-              .getService(Ci.nsICacheService);
+  let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+              .getService(Ci.nsICacheStorageService);
 
-  function checkDiskCacheFor(filename) {
-    let visitor = {
-      visitDevice: function(deviceID, deviceInfo) {
-        if (deviceID == "disk")
-          info(deviceID + " device contains " + deviceInfo.entryCount + " entries");
-        return deviceID == "disk";
+  function checkDiskCacheFor(filename, goon) {
+    Visitor.prototype = {
+      onCacheStorageInfo: function(num, consumption)
+      {
+        info("disk storage contains " + num + " entries");
       },
-
-      visitEntry: function(deviceID, entryInfo) {
-        info(entryInfo.key);
-        is(entryInfo.key.contains(filename), false, "web content present in disk cache");
+      onCacheEntryInfo: function(entry)
+      {
+        info(entry.key);
+        is(entry.key.contains(filename), false, "web content present in disk cache");
+      },
+      onCacheEntryVisitCompleted: function()
+      {
+        goon();
       }
     };
-    cache.visitEntries(visitor);
+    function Visitor() {}
+
+    var storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+    storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */);
   }
 
   function contextMenuOpened(aWindow, event) {
-    cache.evictEntries(Ci.nsICache.STORE_ANYWHERE);
+    cache.clear();
 
     event.currentTarget.removeEventListener("popupshown", contextMenuOpened);
 
     // Create the folder the image will be saved into.
     var destDir = createTemporarySaveDirectory();
     var destFile = destDir.clone();
 
     MockFilePicker.displayDirectory = destDir;
@@ -60,18 +69,17 @@ function test() {
     event.target.hidePopup();
   }
 
   function onTransferComplete(downloadSuccess) {
     ok(downloadSuccess, "Image file should have been downloaded successfully");
 
     // Give the request a chance to finish and create a cache entry
     executeSoon(function() {
-      checkDiskCacheFor(fileName);
-      finish();
+      checkDiskCacheFor(fileName, finish);
     });
   }
 
   function createTemporarySaveDirectory() {
     var saveDir = Cc["@mozilla.org/file/directory_service;1"]
                     .getService(Ci.nsIProperties)
                     .get("TmpD", Ci.nsIFile);
     saveDir.append("testsavedir");
--- a/browser/base/content/test/social/browser_social_errorPage.js
+++ b/browser/base/content/test/social/browser_social_errorPage.js
@@ -15,17 +15,17 @@ let origProxyType = Services.prefs.getIn
 
 function goOffline() {
   // Simulate a network outage with offline mode. (Localhost is still
   // accessible in offline mode, so disable the test proxy as well.)
   if (!Services.io.offline)
     BrowserOffline.toggleOfflineStatus();
   Services.prefs.setIntPref('network.proxy.type', 0);
   // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
-  Services.cache.evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE);
+  Services.cache2.clear();
 }
 
 function goOnline(callback) {
   Services.prefs.setIntPref('network.proxy.type', origProxyType);
   if (Services.io.offline)
     BrowserOffline.toggleOfflineStatus();
   if (callback)
     callback();
--- a/browser/components/places/tests/unit/head_bookmarks.js
+++ b/browser/components/places/tests/unit/head_bookmarks.js
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
 
 // Import common head.
 let (commonFile = do_get_file("../../../../../toolkit/components/places/tests/head_common.js", false)) {
   let uri = Services.io.newFileURI(commonFile);
   Services.scriptloader.loadSubScript(uri.spec, this);
 }
 
 // Put any other stuff relative to this test folder below.
--- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -129,23 +129,25 @@ function run_test_continue()
 }
 
 function getDistinctNotifications() {
   let ar = EXPECTED_NOTIFICATIONS.concat(UNEXPECTED_NOTIFICATIONS);
   return [ar[i] for (i in ar) if (ar.slice(0, i).indexOf(ar[i]) == -1)];
 }
 
 function storeCache(aURL, aContent) {
-  let cache = Cc["@mozilla.org/network/cache-service;1"].
-              getService(Ci.nsICacheService);
-  let session = cache.createSession("FTP", Ci.nsICache.STORE_ANYWHERE,
-                                    Ci.nsICache.STREAM_BASED);
+  let cache = Services.cache2;
+  let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
 
   var storeCacheListener = {
-    onCacheEntryAvailable: function (entry, access, status) {
+    onCacheEntryCheck: function (entry, appcache) {
+      return nsICacheEntryOpenCallback.ENTRY_VALID;
+    },
+
+    onCacheEntryAvailable: function (entry, isnew, appcache, status) {
       do_check_eq(status, Cr.NS_OK);
 
       entry.setMetaDataElement("servertype", "0");
       var os = entry.openOutputStream(0);
 
       var written = os.write(aContent, aContent.length);
       if (written != aContent.length) {
         do_throw("os.write has not written all data!\n" +
@@ -153,31 +155,29 @@ function storeCache(aURL, aContent) {
                  "  Actual: " + aContent.length + "\n");
       }
       os.close();
       entry.close();
       do_execute_soon(run_test_continue);
     }
   };
 
-  session.asyncOpenCacheEntry(aURL,
-                              Ci.nsICache.ACCESS_READ_WRITE,
-                              storeCacheListener);
+  storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "",
+                       Ci.nsICacheStorage.OPEN_NORMALLY,
+                       storeCacheListener);
 }
 
 
 function checkCache(aURL) {
-  let cache = Cc["@mozilla.org/network/cache-service;1"].
-              getService(Ci.nsICacheService);
-  let session = cache.createSession("FTP", Ci.nsICache.STORE_ANYWHERE,
-                                    Ci.nsICache.STREAM_BASED);
+  let cache = Services.cache2;
+  let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
 
   var checkCacheListener = {
-    onCacheEntryAvailable: function (entry, access, status) {
+    onCacheEntryAvailable: function (entry, isnew, appcache, status) {
       do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
       do_test_finished();
     }
   };
 
-  session.asyncOpenCacheEntry(aURL,
-                              Ci.nsICache.ACCESS_READ,
-                              checkCacheListener);
+  storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "",
+                       Ci.nsICacheStorage.OPEN_READONLY,
+                       checkCacheListener);
 }
--- a/browser/components/preferences/advanced.js
+++ b/browser/components/preferences/advanced.js
@@ -2,16 +2,17 @@
 # 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/.
 
 // Load DownloadUtils module for convertByteUnits
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 Components.utils.import("resource://gre/modules/ctypes.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
 
 var gAdvancedPane = {
   _inited: false,
 
   /**
    * Brings the appropriate tab to the front and initializes various bits of UI.
    */
   init: function ()
@@ -72,18 +73,18 @@ var gAdvancedPane = {
 #ifdef MOZ_CRASHREPORTER
     this.initSubmitCrashes();
 #endif
     this.initTelemetry();
 #ifdef MOZ_SERVICES_HEALTHREPORT
     this.initSubmitHealthReport();
 #endif
 
-    this.updateActualCacheSize("disk");
-    this.updateActualCacheSize("offline");
+    this.updateActualCacheSize();
+    this.updateActualAppCacheSize();
 
     // Notify observers that the UI is now ready
     Services.obs.notifyObservers(window, "advanced-pane-loaded", null);
   },
 
   /**
    * Stores the identity of the current tab in preferences so that the selected
    * tab can be persisted between openings of the preferences window.
@@ -291,32 +292,74 @@ var gAdvancedPane = {
    * Displays a dialog in which proxy settings may be changed.
    */
   showConnections: function ()
   {
     document.documentElement.openSubDialog("chrome://browser/content/preferences/connection.xul",
                                            "", null);
   },
 
-  // Retrieves the amount of space currently used by disk or offline cache
-  updateActualCacheSize: function (device)
+  // Retrieves the amount of space currently used by disk cache
+  updateActualCacheSize: function ()
+  {
+    var sum = 0;
+    function updateUI(consumption) {
+      var actualSizeLabel = document.getElementById("actualDiskCacheSize");
+      var sizeStrings = DownloadUtils.convertByteUnits(consumption);
+      var prefStrBundle = document.getElementById("bundlePreferences");
+      var sizeStr = prefStrBundle.getFormattedString("actualDiskCacheSize", sizeStrings);
+      actualSizeLabel.value = sizeStr;
+    }
+
+    Visitor.prototype = {
+      expected: 0,
+      sum: 0,
+      QueryInterface: function listener_qi(iid) {
+        if (iid.equals(Ci.nsISupports) ||
+            iid.equals(Ci.nsICacheStorageVisitor)) {
+          return this;
+        }
+        throw Components.results.NS_ERROR_NO_INTERFACE;
+      },
+      onCacheStorageInfo: function(num, consumption)
+      {
+        this.sum += consumption;
+        if (!--this.expected)
+          updateUI(this.sum);
+      }
+    };
+    function Visitor(callbacksExpected) {
+      this.expected = callbacksExpected;
+    }
+
+    var cacheService =
+      Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+                .getService(Components.interfaces.nsICacheStorageService);
+    // non-anonymous
+    var storage1 = cacheService.diskCacheStorage(LoadContextInfo.default, false);
+    // anonymous
+    var storage2 = cacheService.diskCacheStorage(LoadContextInfo.anonymous, false);
+
+    // expect 2 callbacks
+    var visitor = new Visitor(2);
+    storage1.asyncVisitStorage(visitor, false /* Do not walk entries */);
+    storage2.asyncVisitStorage(visitor, false /* Do not walk entries */);
+  },
+
+  // Retrieves the amount of space currently used by offline cache
+  updateActualAppCacheSize: function ()
   {
     var visitor = {
       visitDevice: function (deviceID, deviceInfo)
       {
-        if (deviceID == device) {
-          var actualSizeLabel = document.getElementById(device == "disk" ?
-                                                        "actualDiskCacheSize" :
-                                                        "actualAppCacheSize");
+        if (deviceID == "offline") {
+          var actualSizeLabel = document.getElementById("actualAppCacheSize");
           var sizeStrings = DownloadUtils.convertByteUnits(deviceInfo.totalSize);
           var prefStrBundle = document.getElementById("bundlePreferences");
-          var sizeStr = prefStrBundle.getFormattedString(device == "disk" ?
-                                                         "actualDiskCacheSize" :
-                                                         "actualAppCacheSize",
-                                                         sizeStrings);
+          var sizeStr = prefStrBundle.getFormattedString("actualAppCacheSize", sizeStrings);
           actualSizeLabel.value = sizeStr;
         }
         // Do not enumerate entries
         return false;
       },
 
       visitEntry: function (deviceID, entryInfo)
       {
@@ -367,33 +410,33 @@ var gAdvancedPane = {
     return isNaN(intValue) ? 0 : intValue * 1024;
   },
 
   /**
    * Clears the cache.
    */
   clearCache: function ()
   {
-    var cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
-                                 .getService(Components.interfaces.nsICacheService);
+    var cache = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+                                 .getService(Components.interfaces.nsICacheStorageService);
     try {
-      cacheService.evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE);
+      cache.clear();
     } catch(ex) {}
-    this.updateActualCacheSize("disk");
+    this.updateActualCacheSize();
   },
 
   /**
    * Clears the application cache.
    */
   clearOfflineAppCache: function ()
   {
     Components.utils.import("resource:///modules/offlineAppCache.jsm");
     OfflineAppCacheHelper.clear();
 
-    this.updateActualCacheSize("offline");
+    this.updateActualAppCacheSize();
     this.updateOfflineApps();
   },
 
   readOfflineNotify: function()
   {
     var pref = document.getElementById("browser.offline-apps.notify");
     var button = document.getElementById("offlineNotifyExceptions");
     button.disabled = !pref.value;
@@ -528,17 +571,17 @@ var gAdvancedPane = {
                        .getService(Components.interfaces.nsIPermissionManager);
     pm.remove(host, "offline-app",
               Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
     pm.remove(host, "offline-app",
               Components.interfaces.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
 
     list.removeChild(item);
     gAdvancedPane.offlineAppSelected();
-    this.updateActualCacheSize("offline");
+    this.updateActualAppCacheSize();
   },
 
   // UPDATE TAB
 
   /*
    * Preferences:
    *
    * app.update.enabled
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -1,14 +1,15 @@
 /* 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/. */
 
 // Load DownloadUtils module for convertByteUnits
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
 
 var gAdvancedPane = {
   _inited: false,
 
   /**
    * Brings the appropriate tab to the front and initializes various bits of UI.
    */
   init: function ()
@@ -62,18 +63,18 @@ var gAdvancedPane = {
     this.updateOfflineApps();
 #ifdef MOZ_CRASHREPORTER
     this.initSubmitCrashes();
 #endif
     this.initTelemetry();
 #ifdef MOZ_SERVICES_HEALTHREPORT
     this.initSubmitHealthReport();
 #endif
-    this.updateActualCacheSize("disk");
-    this.updateActualCacheSize("offline");
+    this.updateActualCacheSize();
+    this.updateActualAppCacheSize();
   },
 
   /**
    * Stores the identity of the current tab in preferences so that the selected
    * tab can be persisted between openings of the preferences window.
    */
   tabSelectionChanged: function ()
   {
@@ -273,32 +274,77 @@ var gAdvancedPane = {
   showConnections: function ()
   {
     openDialog("chrome://browser/content/preferences/connection.xul",
                "mozilla:connectionmanager",
                "model=yes",
                null);
   },
 
-  // Retrieves the amount of space currently used by disk or offline cache
-  updateActualCacheSize: function (device)
+  // Retrieves the amount of space currently used by disk cache
+  updateActualCacheSize: function ()
+  {
+    var sum = 0;
+    function updateUI(consumption) {
+      var actualSizeLabel = document.getElementById("actualDiskCacheSize");
+      var sizeStrings = DownloadUtils.convertByteUnits(consumption);
+      var prefStrBundle = document.getElementById("bundlePreferences");
+      var sizeStr = prefStrBundle.getFormattedString("actualDiskCacheSize", sizeStrings);
+      actualSizeLabel.value = sizeStr;
+    }
+
+    Visitor.prototype = {
+      expected: 0,
+      sum: 0,
+      QueryInterface: function listener_qi(iid) {
+        if (iid.equals(Ci.nsISupports) ||
+            iid.equals(Ci.nsICacheStorageVisitor)) {
+          return this;
+        }
+        throw Components.results.NS_ERROR_NO_INTERFACE;
+      },
+      onCacheStorageInfo: function(num, consumption)
+      {
+        this.sum += consumption;
+        if (!--this.expected)
+          updateUI(this.sum);
+      },
+      onCacheEntryInfo: function(entry)
+      {
+      }
+    };
+    function Visitor(callbacksExpected) {
+      this.expected = callbacksExpected;
+    }
+
+    var cacheService =
+      Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+                .getService(Components.interfaces.nsICacheStorageService);
+    // non-anonymous
+    var storage1 = cacheService.diskCacheStorage(LoadContextInfo.default, false);
+    // anonymous
+    var storage2 = cacheService.diskCacheStorage(LoadContextInfo.anonymous, false);
+
+    // expect 2 callbacks
+    var visitor = new Visitor(2);
+    storage1.asyncVisitStorage(visitor, false /* Do not walk entries */);
+    storage2.asyncVisitStorage(visitor, false /* Do not walk entries */);
+  },
+
+  // Retrieves the amount of space currently used by offline cache
+  updateActualAppCacheSize: function ()
   {
     var visitor = {
       visitDevice: function (deviceID, deviceInfo)
       {
-        if (deviceID == device) {
-          var actualSizeLabel = document.getElementById(device == "disk" ?
-                                                        "actualDiskCacheSize" :
-                                                        "actualAppCacheSize");
+        if (deviceID == "offline") {
+          var actualSizeLabel = document.getElementById("actualAppCacheSize");
           var sizeStrings = DownloadUtils.convertByteUnits(deviceInfo.totalSize);
           var prefStrBundle = document.getElementById("bundlePreferences");
-          var sizeStr = prefStrBundle.getFormattedString(device == "disk" ?
-                                                         "actualDiskCacheSize" :
-                                                         "actualAppCacheSize",
-                                                         sizeStrings);
+          var sizeStr = prefStrBundle.getFormattedString("actualAppCacheSize", sizeStrings);
           actualSizeLabel.value = sizeStr;
         }
         // Do not enumerate entries
         return false;
       },
 
       visitEntry: function (deviceID, entryInfo)
       {
@@ -354,28 +400,28 @@ var gAdvancedPane = {
    */
   clearCache: function ()
   {
     var cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
                                  .getService(Components.interfaces.nsICacheService);
     try {
       cacheService.evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE);
     } catch(ex) {}
-    this.updateActualCacheSize("disk");
+    this.updateActualCacheSize();
   },
 
   /**
    * Clears the application cache.
    */
   clearOfflineAppCache: function ()
   {
     Components.utils.import("resource:///modules/offlineAppCache.jsm");
     OfflineAppCacheHelper.clear();
 
-    this.updateActualCacheSize("offline");
+    this.updateActualAppCacheSize();
     this.updateOfflineApps();
   },
 
   readOfflineNotify: function()
   {
     var pref = document.getElementById("browser.offline-apps.notify");
     var button = document.getElementById("offlineNotifyExceptions");
     button.disabled = !pref.value;
@@ -511,17 +557,17 @@ var gAdvancedPane = {
                        .getService(Components.interfaces.nsIPermissionManager);
     pm.remove(host, "offline-app",
               Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
     pm.remove(host, "offline-app",
               Components.interfaces.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
 
     list.removeChild(item);
     gAdvancedPane.offlineAppSelected();
-    this.updateActualCacheSize("offline");
+    this.updateActualAppCacheSize();
   },
 
   // UPDATE TAB
 
   /*
    * Preferences:
    *
    * app.update.enabled
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
@@ -1,34 +1,37 @@
 /* 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/. */
 
 // Check about:cache after private browsing
 // This test covers MozTrap test 6047
 // bug 880621
 
+let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
+
 let tmp = {};
 
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://browser/content/sanitize.js", tmp);
 
 let Sanitizer = tmp.Sanitizer;
 
 function test() {
 
   waitForExplicitFinish();
 
   sanitizeCache();
 
-  let nrEntriesR1 = get_device_entry_count("disk");
-  is (nrEntriesR1, 0, "Disk cache reports 0KB and has no entries");
+  let nrEntriesR1 = getStorageEntryCount("regular", function(nrEntriesR1) {
+    is(nrEntriesR1, 0, "Disk cache reports 0KB and has no entries");
 
-  get_cache_for_private_window();
+    get_cache_for_private_window();
+  });
 }
 
 function cleanup() {
   let prefs = Services.prefs.getBranch("privacy.cpd.");
 
   prefs.clearUserPref("history");
   prefs.clearUserPref("downloads");
   prefs.clearUserPref("cache");
@@ -56,39 +59,52 @@ function sanitizeCache() {
   prefs.setBoolPref("passwords", false);
   prefs.setBoolPref("sessions", false);
   prefs.setBoolPref("siteSettings", false);
 
   s.sanitize();
 }
 
 function get_cache_service() {
-  return Components.classes["@mozilla.org/network/cache-service;1"]
-                   .getService(Components.interfaces.nsICacheService);
+  return Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+                   .getService(Components.interfaces.nsICacheStorageService);
 }
 
-function get_device_entry_count(device) {
+function getStorageEntryCount(device, goon) {
   var cs = get_cache_service();
-  var entry_count = -1;
+
+  var storage;
+  switch (device) {
+  case "private":
+    storage = cs.diskCacheStorage(LoadContextInfo.private, false);
+    break;
+  case "regular":
+    storage = cs.diskCacheStorage(LoadContextInfo.default, false);
+    break;
+  default:
+    throw "Unknown device " + device + " at getStorageEntryCount";
+  }
 
   var visitor = {
-    visitDevice: function (deviceID, deviceInfo) {
-      if (device == deviceID) {
-        entry_count = deviceInfo.entryCount;
-      }
-      return false;
+    entryCount: 0,
+    onCacheStorageInfo: function (aEntryCount, aConsumption) {
     },
-    visitEntry: function (deviceID, entryInfo) {
-      do_throw("nsICacheVisitor.visitEntry should not be called " +
-               "when checking the availability of devices");
+    onCacheEntryInfo: function(entry)
+    {
+      info(device + ":" + entry.key + "\n");
+      if (entry.key.match(/^http:\/\/example.org\//))
+        ++this.entryCount;
+    },
+    onCacheEntryVisitCompleted: function()
+    {
+      goon(this.entryCount);
     }
   };
 
-  cs.visitEntries(visitor);
-  return entry_count;
+  storage.asyncVisitStorage(visitor, true);
 }
 
 function get_cache_for_private_window () {
   let win = OpenBrowserWindow({private: true});
   win.addEventListener("load", function () {
     win.removeEventListener("load", arguments.callee, false);
 
     executeSoon(function() {
@@ -99,23 +115,25 @@ function get_cache_for_private_window ()
       win.gBrowser.selectedTab = tab;
       let newTabBrowser = win.gBrowser.getBrowserForTab(tab);
 
       newTabBrowser.addEventListener("load", function eventHandler() {
         newTabBrowser.removeEventListener("load", eventHandler, true);
 
         executeSoon(function() {
 
-          let nrEntriesP = get_device_entry_count("memory");
-          is (nrEntriesP, 1, "Memory cache reports some entries from example.org domain");
+          getStorageEntryCount("private", function(nrEntriesP) {
+            ok(nrEntriesP >= 1, "Memory cache reports some entries from example.org domain");
+
+            getStorageEntryCount("regular", function(nrEntriesR2) {
+              is(nrEntriesR2, 0, "Disk cache reports 0KB and has no entries");
 
-          let nrEntriesR2 = get_device_entry_count("disk");
-          is (nrEntriesR2, 0, "Disk cache reports 0KB and has no entries");
+              cleanup();
 
-          cleanup();
-
-          win.close();
-          finish();
+              win.close();
+              finish();
+            });
+          });
         });
       }, true);
     });
   }, false);
 }
--- a/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
@@ -8,26 +8,26 @@
 let gUI;
 
 function test() {
   waitForExplicitFinish();
   let windowsToClose = [];
   let testURI = 'http://' + TEST_HOST + '/browser/browser/devtools/styleeditor/test/test_private.html';
 
   function checkCache() {
-    checkDiskCacheFor(TEST_HOST);
-
-    gUI = null;
-    finish();
+    checkDiskCacheFor(TEST_HOST, function() {
+      gUI = null;
+      finish();
+    });
   }
 
   function doTest(aWindow) {
     aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
       aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
-      cache.evictEntries(Ci.nsICache.STORE_ANYWHERE);
+      cache.clear();
       openStyleEditorInWindow(aWindow, function(panel) {
         gUI = panel.UI;
         gUI.on("editor-added", onEditorAdded);
       });
     }, true);
 
     aWindow.gBrowser.selectedBrowser.loadURI(testURI);
   }
--- a/browser/devtools/styleeditor/test/head.js
+++ b/browser/devtools/styleeditor/test/head.js
@@ -4,22 +4,24 @@
 const TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/styleeditor/test/";
 const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleeditor/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleeditor/test/";
 const TEST_HOST = 'mochi.test:8888';
 
 let tempScope = {};
 Cu.import("resource://gre/modules/devtools/Loader.jsm", tempScope);
 let TargetFactory = tempScope.devtools.TargetFactory;
-Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
+Cu.import("resource://gre/modules/LoadContextInfo.jsm", tempScope);
+let LoadContextInfo = tempScope.LoadContextInfo;
+Cu.import("resource://gre/modules/devtools/Console.jsm", tempScope);
 let console = tempScope.console;
 
 let gPanelWindow;
-let cache = Cc["@mozilla.org/network/cache-service;1"]
-              .getService(Ci.nsICacheService);
+let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+              .getService(Ci.nsICacheStorageService);
 
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
 
 function cleanup()
 {
@@ -79,30 +81,35 @@ function addTabAndLaunchStyleEditorChrom
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     launchStyleEditorChrome(aCallback, aSheet, aLine, aCol);
   }, true);
 }
 */
 
-function checkDiskCacheFor(host)
+function checkDiskCacheFor(host, done)
 {
   let foundPrivateData = false;
 
-  let visitor = {
-    visitDevice: function(deviceID, deviceInfo) {
-      if (deviceID == "disk")
-        info("disk device contains " + deviceInfo.entryCount + " entries");
-      return deviceID == "disk";
+  Visitor.prototype = {
+    onCacheStorageInfo: function(num, consumption)
+    {
+      info("disk storage contains " + num + " entries");
     },
-
-    visitEntry: function(deviceID, entryInfo) {
-      info(entryInfo.key);
-      foundPrivateData |= entryInfo.key.contains(host);
+    onCacheEntryInfo: function(entry)
+    {
+      info(entry.key);
+      foundPrivateData |= entry.key.contains(host);
+    },
+    onCacheEntryVisitCompleted: function()
+    {
       is(foundPrivateData, false, "web content present in disk cache");
+      done();
     }
   };
-  cache.visitEntries(visitor);
-  is(foundPrivateData, false, "private data present in disk cache");
+  function Visitor() {}
+
+  var storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+  storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */);
 }
 
 registerCleanupFunction(cleanup);
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -258,16 +258,17 @@
 @BINPATH@/components/layout_xul.xpt
 @BINPATH@/components/locale.xpt
 @BINPATH@/components/lwbrk.xpt
 @BINPATH@/browser/components/migration.xpt
 @BINPATH@/components/mimetype.xpt
 @BINPATH@/components/mozfind.xpt
 @BINPATH@/components/necko_about.xpt
 @BINPATH@/components/necko_cache.xpt
+@BINPATH@/components/necko_cache2.xpt
 @BINPATH@/components/necko_cookie.xpt
 @BINPATH@/components/necko_dns.xpt
 @BINPATH@/components/necko_file.xpt
 @BINPATH@/components/necko_ftp.xpt
 @BINPATH@/components/necko_http.xpt
 @BINPATH@/components/necko_res.xpt
 @BINPATH@/components/necko_socket.xpt
 @BINPATH@/components/necko_strconv.xpt
--- a/browser/metro/base/content/sanitize.js
+++ b/browser/metro/base/content/sanitize.js
@@ -1,13 +1,15 @@
 // -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+XPCOMUtils.defineLazyModuleGetter(this, "LoadContextInfo",
+                                  "resource://gre/modules/LoadContextInfo.jsm");
 function Sanitizer() {}
 
 Sanitizer.prototype = {
   // warning to the caller: this one may raise an exception (e.g. bug #265028)
   clearItem: function (aItemName)
   {
     if (this.items[aItemName].canClear)
       this.items[aItemName].clear();
@@ -80,19 +82,19 @@ Sanitizer.prototype = {
       {
         return (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED);
       }
     },
 
     cache: {
       clear: function ()
       {
-        var cacheService = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
+        var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
         try {
-          cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE);
+          cache.clear();
         } catch(er) {}
 
         let imageCache = Cc["@mozilla.org/image/cache;1"].getService(Ci.imgICache);
         try {
           imageCache.clearCache(false); // true=chrome, false=content
         } catch(er) {}
       },
 
@@ -138,19 +140,20 @@ Sanitizer.prototype = {
       {
         return true;
       }
     },
 
     offlineApps: {
       clear: function ()
       {
-        var cacheService = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
+        var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+        var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
         try {
-          cacheService.evictEntries(Ci.nsICache.STORE_OFFLINE);
+          appCacheStorage.asyncEvictStorage(null);
         } catch(er) {}
       },
 
       get canClear()
       {
           return true;
       }
     },
--- a/browser/modules/offlineAppCache.jsm
+++ b/browser/modules/offlineAppCache.jsm
@@ -1,18 +1,20 @@
 /* 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/. */
  
 this.EXPORTED_SYMBOLS = ["OfflineAppCacheHelper"];
 
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 this.OfflineAppCacheHelper = {
   clear: function() {
-    var cacheService = Cc["@mozilla.org/network/cache-service;1"].
-                       getService(Ci.nsICacheService);
+    var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+    var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
     try {
-      cacheService.evictEntries(Ci.nsICache.STORE_OFFLINE);
+      appCacheStorage.asyncEvictStorage(null);
     } catch(er) {}
   }
 };
--- a/content/base/test/test_bug482935.html
+++ b/content/base/test/test_bug482935.html
@@ -6,19 +6,19 @@
   <link rel="stylesheet" type="text/css" href="	/tests/SimpleTest/test.css" />
 </head>
 <body onload="onWindowLoad()">
 <script class="testbody" type="text/javascript">
 
 var url = "bug482935.sjs";
 
 function clearCache() {
-    SpecialPowers.Cc["@mozilla.org/network/cache-service;1"].
-               getService(SpecialPowers.Ci.nsICacheService).
-               evictEntries(SpecialPowers.Ci.nsICache.STORE_ANYWHERE);
+    SpecialPowers.Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+               getService(SpecialPowers.Ci.nsICacheStorageService).
+               clear();
 }
 
 // Tests that the response is cached if the request is cancelled
 // after it has reached state 4
 function testCancelInPhase4() {
 
   clearCache();
 
--- a/dom/src/offline/nsDOMOfflineResourceList.cpp
+++ b/dom/src/offline/nsDOMOfflineResourceList.cpp
@@ -798,19 +798,26 @@ nsDOMOfflineResourceList::CacheKeys()
 
   if (mCachedKeys)
     return NS_OK;
 
   nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(GetOwner());
   nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
   nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
 
+  uint32_t appId = 0;
+  bool inBrowser = false;
+  if (loadContext) {
+    loadContext->GetAppId(&appId);
+    loadContext->GetIsInBrowserElement(&inBrowser);
+  }
+
   nsAutoCString groupID;
-  mApplicationCacheService->BuildGroupID(
-      mManifestURI, loadContext, groupID);
+  mApplicationCacheService->BuildGroupIDForApp(
+      mManifestURI, appId, inBrowser, groupID);
 
   nsCOMPtr<nsIApplicationCache> appCache;
   mApplicationCacheService->GetActiveCache(groupID,
                                            getter_AddRefs(appCache));
 
   if (!appCache) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
--- a/dom/tests/mochitest/ajax/offline/foreign2.html
+++ b/dom/tests/mochitest/ajax/offline/foreign2.html
@@ -9,17 +9,17 @@
 <script class="testbody" type="text/javascript">
 
 function manifestUpdated()
 {
   var appCacheService = SpecialPowers.Cc["@mozilla.org/network/application-cache-service;1"]
     .getService(SpecialPowers.Ci.nsIApplicationCacheService);
 
   var foreign2cache = appCacheService.chooseApplicationCache(
-    "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.html", OfflineTest.loadContext());
+    "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.html", OfflineTest.loadContextInfo());
 
   window.opener.OfflineTest.ok(foreign2cache, "Foreign 2 cache present, chosen for foreign2.html");
   window.opener.OfflineTest.is(foreign2cache.manifestURI.asciiSpec, "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.cacheManifest")
 
   var foreign1cache = OfflineTest.getActiveCache(
     "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign1.cacheManifest");
   window.opener.OfflineTest.ok(foreign1cache, "Foreign 1 cache loaded");
   foreign1cache.discard();
@@ -36,17 +36,17 @@ function onLoaded()
     "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign1.cacheManifest");
   window.opener.OfflineTest.ok(foreign1cache, "Foreign 1 cache loaded");
 
   var foreign2cache = window.opener.OfflineTest.getActiveCache(
     "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.cacheManifest");
   window.opener.OfflineTest.ok(!foreign2cache, "Foreign 2 cache not present");
 
   foreign1cache = appCacheService.chooseApplicationCache(
-    "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.html", window.opener.OfflineTest.loadContext());
+    "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.html", window.opener.OfflineTest.loadContextInfo());
   window.opener.OfflineTest.ok(!foreign1cache, "foreign2.html not chosen from foreign1 cache");
 
   try
   {
     window.opener.OfflineTest.ok(applicationCache.status == SpecialPowers.Ci.nsIDOMOfflineResourceList.UNCACHED,
         "there is no associated application cache");
   }
   catch (ex)
--- a/dom/tests/mochitest/ajax/offline/offlineTests.js
+++ b/dom/tests/mochitest/ajax/offline/offlineTests.js
@@ -1,11 +1,13 @@
 // Utility functions for offline tests.
 var Cc = SpecialPowers.Cc;
 var Ci = SpecialPowers.Ci;
+var Cu = SpecialPowers.Cu;
+var LoadContextInfo = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {}).LoadContextInfo;
 
 const kNetBase = 2152398848; // 0x804B0000
 var NS_ERROR_CACHE_KEY_NOT_FOUND = kNetBase + 61;
 var NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION = kNetBase + 64;
 
 // Reading the contents of multiple cache entries asynchronously
 function OfflineCacheContents(urls) {
   this.urls = urls;
@@ -330,23 +332,28 @@ manifestURL: function(overload)
 loadContext: function()
 {
   return SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
                                    .getInterface(SpecialPowers.Ci.nsIWebNavigation)
                                    .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
                                    .getInterface(SpecialPowers.Ci.nsILoadContext);
 },
 
+loadContextInfo: function()
+{
+  return LoadContextInfo.fromLoadContext(this.loadContext(), false);
+},
+
 getActiveCache: function(overload)
 {
   // Note that this is the current active cache in the cache stack, not the
   // one associated with this window.
   var serv = Cc["@mozilla.org/network/application-cache-service;1"]
              .getService(Ci.nsIApplicationCacheService);
-  var groupID = serv.buildGroupID(this.manifestURL(overload), this.loadContext());
+  var groupID = serv.buildGroupID(this.manifestURL(overload), this.loadContextInfo());
   return serv.getActiveCache(groupID);
 },
 
 getActiveSession: function()
 {
   var cache = this.getActiveCache();
   if (!cache) {
     return null;
--- a/dom/tests/mochitest/ajax/offline/test_foreign.html
+++ b/dom/tests/mochitest/ajax/offline/test_foreign.html
@@ -22,17 +22,17 @@
 var win;
 
 function manifestUpdated()
 {
   var appCacheService = SpecialPowers.Cc["@mozilla.org/network/application-cache-service;1"]
     .getService(SpecialPowers.Ci.nsIApplicationCacheService);
 
   foreign1cache = appCacheService.chooseApplicationCache(
-    "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.html", OfflineTest.loadContext());
+    "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.html", OfflineTest.loadContextInfo());
 
   OfflineTest.ok(foreign1cache, "foreign2.html chosen from foreign1 cache");
   OfflineTest.is(foreign1cache.manifestURI.asciiSpec, "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign1.cacheManifest")
 
   win = window.open("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/foreign2.html");
 }
 
 function onDone() // called by the open window after stuff is finished
--- a/dom/tests/mochitest/ajax/offline/test_offlineMode.html
+++ b/dom/tests/mochitest/ajax/offline/test_offlineMode.html
@@ -18,16 +18,23 @@
 
 var gImplicitWindow = null;
 var gCompleteTimeout = null;
 var gGotExplicitVersion = 0;
 var gGotImplicitVersion = 0;
 var gGotDynamicVersion = 0;
 var gGotOnError = false;
 
+function createURI(urispec)
+{
+  var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
+                         .getService(Components.interfaces.nsIIOService);
+  return ioServ.newURI(urispec, null, null);
+}
+
 // test
 
 function manifestUpdated()
 {
   applicationCache.mozAdd("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/notonwhitelist.html");
   OfflineTest.waitForAdd("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/notonwhitelist.html", dynamicAdded);
 }
 
@@ -107,22 +114,22 @@ function goOffline()
 {
   var listener = {
     onCacheEntryDoomed: function (status) {
       OfflineTest.priv(goOfflineContinue)();
     }
   };
 
   // Delete HTTP cache to ensure we are going from offline cache
-  var sessionServ = Cc["@mozilla.org/network/cache-service;1"]
-      .getService(Ci.nsICacheService);
-  cacheSession = sessionServ.createSession("HTTP", Ci.nsICache.STORE_ANYWHERE, Ci.nsICache.STREAM_BASED);
-  cacheSession.doomEntry("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/notonwhitelist.html", null);
-  cacheSession.doomEntry("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/updatingIframe.sjs", null);
-  cacheSession.doomEntry("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/updatingImplicit.html", listener);
+  var cache = Cc["@mozilla.org/network/cache-storage-service;1"]
+      .getService(Ci.nsICacheStorageService);
+  var storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+  storage.asyncDoomURI(createURI("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/notonwhitelist.html"), "", null);
+  storage.asyncDoomURI(createURI("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/updatingIframe.sjs"), "", null);
+  storage.asyncDoomURI(createURI("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/updatingImplicit.html"), "", listener);
 }
 
 function goOfflineContinue()
 {
   var ioserv = Cc["@mozilla.org/network/io-service;1"]
       .getService(Ci.nsIIOService);
 
   ioserv.offline = true;
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -21,17 +21,17 @@
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheChannel.h"
 #include "nsMimeTypes.h"
 
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIScriptSecurityManager.h"
 
-#include "nsICacheVisitor.h"
+#include "nsICacheEntry.h"
 
 #include "plstr.h" // PL_strcasestr(...)
 #include "nsNetUtil.h"
 #include "nsIProtocolHandler.h"
 #include "imgIRequest.h"
 
 using namespace mozilla;
 using namespace mozilla::image;
@@ -334,17 +334,17 @@ void imgRequest::SetCacheValidation(imgC
 {
   /* get the expires info */
   if (aCacheEntry) {
     nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aRequest));
     if (cacheChannel) {
       nsCOMPtr<nsISupports> cacheToken;
       cacheChannel->GetCacheToken(getter_AddRefs(cacheToken));
       if (cacheToken) {
-        nsCOMPtr<nsICacheEntryInfo> entryDesc(do_QueryInterface(cacheToken));
+        nsCOMPtr<nsICacheEntry> entryDesc(do_QueryInterface(cacheToken));
         if (entryDesc) {
           uint32_t expiration;
           /* get the expiration time from the caching channel's token */
           entryDesc->GetExpirationTime(&expiration);
 
           // Expiration time defaults to 0. We set the expiration time on our
           // entry if it hasn't been set yet.
           if (aCacheEntry->GetExpiryTime() == 0)
--- a/image/test/unit/test_private_channel.js
+++ b/image/test/unit/test_private_channel.js
@@ -93,18 +93,19 @@ function run_loadImage_tests() {
             server.stop(do_test_finished);
           });
         });
       });
     });
   }
 
   Services.obs.addObserver(observer, "cacheservice:empty-cache", false);
-  let cs = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
-  cs.evictEntries(Ci.nsICache.STORE_ANYWHERE);
+  let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+             .getService(Ci.nsICacheStorageService);
+  cs.clear();
 }
 
 function cleanup()
 {
   for (var i = 0; i < requests.length; ++i) {
     requests[i].cancelAndForgetObserver(0);
   }
 }
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -54,16 +54,17 @@
 #include "nsListControlFrame.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "SVGElementFactory.h"
 #include "nsSVGUtils.h"
 #include "nsMathMLAtoms.h"
 #include "nsMathMLOperators.h"
 #include "Navigator.h"
 #include "DOMStorageObserver.h"
+#include "CacheObserver.h"
 #include "DisplayItemClip.h"
 
 #include "AudioChannelService.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #include "nsXULContentUtils.h"
 #include "nsXULPrototypeCache.h"
@@ -118,16 +119,17 @@ using namespace mozilla::system;
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "nsIMEStateManager.h"
 #include "nsDocument.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 
 extern void NS_ShutdownEventTargetChainRecycler();
 
 using namespace mozilla;
+using namespace mozilla::net;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 using namespace mozilla::dom::time;
 
 nsrefcnt nsLayoutStatics::sLayoutStaticRefcnt = 0;
 
 nsresult
 nsLayoutStatics::Initialize()
@@ -273,16 +275,18 @@ nsLayoutStatics::Initialize()
   nsPermissionManager::AppClearDataObserverInit();
   nsCookieService::AppClearDataObserverInit();
   nsApplicationCacheService::AppClearDataObserverInit();
 
   InitializeDateCacheCleaner();
 
   HTMLVideoElement::Init();
 
+  CacheObserver::Init();
+
   return NS_OK;
 }
 
 void
 nsLayoutStatics::Shutdown()
 {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
@@ -394,9 +398,11 @@ nsLayoutStatics::Shutdown()
 
   ContentParent::ShutDown();
 
   nsRefreshDriver::Shutdown();
 
   DisplayItemClip::Shutdown();
 
   nsDocument::XPCOMShutdown();
+
+  CacheObserver::Shutdown();
 }
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -59,16 +59,18 @@ pref("browser.cache.disk.smart_size.firs
 #ifdef MOZ_PKG_SPECIAL
 // low memory devices
 pref("browser.cache.memory.enable", false);
 #else
 pref("browser.cache.memory.enable", true);
 #endif
 pref("browser.cache.memory.capacity", 1024); // kilobytes
 
+pref("browser.cache.memory_limit", 5120); // 5 MB
+
 /* image cache prefs */
 pref("image.cache.size", 1048576); // bytes
 pref("image.high_quality_downscaling.enabled", false);
 
 /* offline cache prefs */
 pref("browser.offline-apps.notify", true);
 pref("browser.cache.offline.enable", true);
 pref("browser.cache.offline.capacity", 5120); // kilobytes
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -198,16 +198,17 @@
 @BINPATH@/components/layout_xul.xpt
 @BINPATH@/components/locale.xpt
 @BINPATH@/components/lwbrk.xpt
 @BINPATH@/components/migration.xpt
 @BINPATH@/components/mimetype.xpt
 @BINPATH@/components/mozfind.xpt
 @BINPATH@/components/necko_about.xpt
 @BINPATH@/components/necko_cache.xpt
+@BINPATH@/components/necko_cache2.xpt
 @BINPATH@/components/necko_cookie.xpt
 @BINPATH@/components/necko_dns.xpt
 @BINPATH@/components/necko_file.xpt
 @BINPATH@/components/necko_ftp.xpt
 @BINPATH@/components/necko_http.xpt
 @BINPATH@/components/necko_res.xpt
 @BINPATH@/components/necko_socket.xpt
 @BINPATH@/components/necko_strconv.xpt
--- a/mobile/android/modules/Sanitizer.jsm
+++ b/mobile/android/modules/Sanitizer.jsm
@@ -63,19 +63,19 @@ Sanitizer.prototype = {
       item.clear();
     }
   },
 
   items: {
     cache: {
       clear: function ()
       {
-        var cacheService = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
+        var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
         try {
-          cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE);
+          cache.clear();
         } catch(er) {}
 
         let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                                                          .getImgCacheForDocument(null);
         try {
           imageCache.clearCache(false); // true=chrome, false=content
         } catch(er) {}
       },
@@ -121,19 +121,20 @@ Sanitizer.prototype = {
       {
         return true;
       }
     },
 
     offlineApps: {
       clear: function ()
       {
-        var cacheService = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
+        var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+        var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
         try {
-          cacheService.evictEntries(Ci.nsICache.STORE_OFFLINE);
+          appCacheStorage.asyncEvictStorage(null);
         } catch(er) {}
       },
 
       get canClear()
       {
           return true;
       }
     },
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -33,16 +33,23 @@ pref("general.useragent.enable_overrides
 
 pref("general.config.obscure_value", 13); // for MCD .cfg files
 
 pref("general.warnOnAboutConfig", true);
 
 // maximum number of dated backups to keep at any time
 pref("browser.bookmarks.max_backups",       5);
 
+// Preference for switching the cache backend, can be changed freely at runtime
+// 0 - use the old (Darin's) cache [DEFAULT]
+// 1 - use the new cache back-end (cache v2)
+// 2 - do a random choise for A/B testing (browser chooses old or new back-end at startup
+//     and keeps it per session)
+pref("browser.cache.use_new_backend",       0);
+
 pref("browser.cache.disk.enable",           true);
 // Is this the first-time smartsizing has been introduced?
 pref("browser.cache.disk.smart_size.first_run", true);
 // Does the user want smart-sizing?
 pref("browser.cache.disk.smart_size.enabled", true);
 // Which max value should we use for smart-sizing?
 pref("browser.cache.disk.smart_size.use_old_max", true);
 // Size (in KB) explicitly set by the user. Used when smart_size.enabled == false
@@ -55,16 +62,19 @@ pref("browser.cache.memory.enable",     
 //pref("browser.cache.memory.capacity",     -1);
 // Max-size (in KB) for entries in memory cache. Set to -1 for no limit.  
 // (Note: entries bigger than than 90% of the mem-cache are never cached)
 pref("browser.cache.memory.max_entry_size",  5120);
 pref("browser.cache.disk_cache_ssl",        true);
 // 0 = once-per-session, 1 = each-time, 2 = never, 3 = when-appropriate/automatically
 pref("browser.cache.check_doc_frequency",   3);
 
+// Limit for how much memory the cache can consume, for any data, in Kb
+pref("browser.cache.memory_limit", 51200); // 50 MB
+
 pref("browser.cache.offline.enable",           true);
 // enable offline apps by default, disable prompt
 pref("offline-apps.allow_by_default",          true);
 
 // offline cache capacity in kilobytes
 pref("browser.cache.offline.capacity",         512000);
 
 // the user should be warned if offline app disk usage exceeds this amount
@@ -4118,17 +4128,17 @@ pref("layers.frame-counter", false);
 pref("layers.max-active", -1);
 
 // Whether to use the deprecated texture architecture rather than the new one.
 #ifdef XP_MACOSX
 pref("layers.offmainthreadcomposition.enabled", true);
 pref("layers.use-deprecated-textures", false);
 #else
 #ifdef MOZ_WIDGET_GONK
-pref("layers.use-deprecated-textures", false);
+pref("layers.use-deprecated-textures", true);
 #else
 pref("layers.offmainthreadcomposition.enabled", false);
 pref("layers.use-deprecated-textures", true);
 #endif
 #endif
 
 // same effect as layers.offmainthreadcomposition.enabled, but specifically for
 // use with tests.
--- a/netwerk/base/public/moz.build
+++ b/netwerk/base/public/moz.build
@@ -41,16 +41,17 @@ XPIDL_SOURCES += [
     'nsIExternalProtocolHandler.idl',
     'nsIFileStreams.idl',
     'nsIFileURL.idl',
     'nsIIOService.idl',
     'nsIIOService2.idl',
     'nsIIncrementalDownload.idl',
     'nsIInputStreamChannel.idl',
     'nsIInputStreamPump.idl',
+    'nsILoadContextInfo.idl',
     'nsILoadGroup.idl',
     'nsILoadGroupChild.idl',
     'nsIMIMEInputStream.idl',
     'nsIMultiPartChannel.idl',
     'nsINSSErrorsService.idl',
     'nsINestedURI.idl',
     'nsINetAddr.idl',
     'nsINetUtil.idl',
--- a/netwerk/base/public/nsIApplicationCacheService.idl
+++ b/netwerk/base/public/nsIApplicationCacheService.idl
@@ -4,31 +4,31 @@
  * 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 "nsISupports.idl"
 
 interface nsIApplicationCache;
 interface nsIFile;
 interface nsIURI;
-interface nsILoadContext;
+interface nsILoadContextInfo;
 
 /**
  * The application cache service manages the set of application cache
  * groups.
  */
-[scriptable, uuid(9b5b2cde-d5dd-48d3-87f8-8e8b776952a8)]
+[scriptable, uuid(03b41c3d-0816-4134-8b2e-4f5afbdb1f06)]
 interface nsIApplicationCacheService : nsISupports
 {
     /**
      * Create group string identifying cache group according the manifest
      * URL and the given load context.
      */
     ACString buildGroupID(in nsIURI aManifestURL,
-                          in nsILoadContext aLoadContext);
+                          in nsILoadContextInfo aLoadContextInfo);
 
     /**
      * Same as buildGroupID method, just doesn't require load context.
      */
     ACString buildGroupIDForApp(in nsIURI aManifestURL,
                                 in unsigned long aAppID,
                                 in boolean aInBrowser);
 
@@ -82,17 +82,17 @@ interface nsIApplicationCacheService : n
      *    deleted (this is used for application uninstallation).
      */
     void discardByAppId(in int32_t appID, in boolean discardOnlyBrowserEntries);
 
     /**
      * Try to find the best application cache to serve a resource.
      */
     nsIApplicationCache chooseApplicationCache(in ACString key,
-                                               [optional] in nsILoadContext loadContext);
+                                               [optional] in nsILoadContextInfo aLoadContextInfo);
 
     /**
      * Flags the key as being opportunistically cached.
      *
      * This method should also propagate the entry to other
      * application caches with the same opportunistic namespace, but
      * this is not currently implemented.
      *
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsILoadContextInfo.idl
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Helper interface to carry informatin about the load context
+ * encapsulating an AppID, IsInBrowser and IsPrivite properties.
+ * It shall be used where nsILoadContext cannot be used or is not
+ * available.
+ */
+[scriptable, uuid(1ea9cbdb-9df4-46a0-8c45-f4091aad9459)]
+interface nsILoadContextInfo : nsISupports
+{
+  /**
+   * Whether the context is in a Private Browsing mode
+   */
+  readonly attribute boolean isPrivate;
+
+  /**
+   * Whether the context belongs under an App
+   */
+  const unsigned long NO_APP_ID = 0;
+  const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX
+  readonly attribute uint32_t appId;
+
+  /**
+   * Whether the context is in a browser tag
+   */
+  readonly attribute boolean isInBrowserElement;
+
+  /**
+   * Whether the load is initiated as anonymous
+   */
+  readonly attribute boolean isAnonymous;
+
+%{C++
+  /**
+   * De-XPCOMed getters
+   */
+  bool IsPrivate()
+  {
+    bool pb;
+    GetIsPrivate(&pb);
+    return pb;
+  }
+
+  uint32_t AppId()
+  {
+    uint32_t appId;
+    GetAppId(&appId);
+    return appId;
+  }
+
+  bool IsInBrowserElement()
+  {
+    bool ib;
+    GetIsInBrowserElement(&ib);
+    return ib;
+  }
+
+  bool IsAnonymous()
+  {
+    bool anon;
+    GetIsAnonymous(&anon);
+    return anon;
+  }
+%}
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/LoadContextInfo.cpp
@@ -0,0 +1,115 @@
+/* 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 "LoadContextInfo.h"
+
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS1(LoadContextInfo, nsILoadContextInfo)
+
+LoadContextInfo::LoadContextInfo(bool aIsPrivate, uint32_t aAppId, bool aIsInBrowser, bool aIsAnonymous)
+  : mAppId(aAppId)
+  , mIsPrivate(aIsPrivate)
+  , mIsInBrowser(aIsInBrowser)
+  , mIsAnonymous(aIsAnonymous)
+{
+}
+
+LoadContextInfo::~LoadContextInfo()
+{
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool *aIsPrivate)
+{
+  *aIsPrivate = mIsPrivate;
+  return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetAppId(uint32_t *aAppId)
+{
+  *aAppId = mAppId;
+  return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsInBrowserElement(bool *aIsInBrowser)
+{
+  *aIsInBrowser = mIsInBrowser;
+  return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool *aIsAnonymous)
+{
+  *aIsAnonymous = mIsAnonymous;
+  return NS_OK;
+}
+
+LoadContextInfo *
+GetLoadContextInfo(nsIChannel * aChannel)
+{
+  bool pb = NS_UsePrivateBrowsing(aChannel);
+  uint32_t appId;
+  bool ib;
+  if (!NS_GetAppInfo(aChannel, &appId, &ib)) {
+    appId = nsILoadContextInfo::NO_APP_ID;
+    ib = false;
+  }
+
+  bool anon = false;
+  nsLoadFlags loadFlags;
+  nsresult rv = aChannel->GetLoadFlags(&loadFlags);
+  if (NS_SUCCEEDED(rv))
+    anon = !!(loadFlags & nsIChannel::LOAD_ANONYMOUS);
+
+  return new LoadContextInfo(pb, appId, ib, anon);
+}
+
+LoadContextInfo *
+GetLoadContextInfo(nsILoadContext * aLoadContext, bool aIsAnonymous)
+{
+  if (!aLoadContext)
+    return new LoadContextInfo(false, nsILoadContextInfo::NO_APP_ID, false, aIsAnonymous); // nullptr?
+
+  bool pb = aLoadContext->UsePrivateBrowsing();
+
+  bool ib;
+  nsresult rv = aLoadContext->GetIsInBrowserElement(&ib);
+  if (NS_FAILED(rv))
+    ib = false; // todo NS_WARNING...
+
+  uint32_t appId;
+  rv = aLoadContext->GetAppId(&appId);
+  if (NS_FAILED(rv))
+    appId = nsILoadContextInfo::NO_APP_ID;
+
+  return new LoadContextInfo(pb, appId, ib, aIsAnonymous);
+}
+
+LoadContextInfo *
+GetLoadContextInfo(nsILoadContextInfo* aInfo)
+{
+  return new LoadContextInfo(aInfo->IsPrivate(),
+                             aInfo->AppId(),
+                             aInfo->IsInBrowserElement(),
+                             aInfo->IsAnonymous());
+}
+
+LoadContextInfo *
+GetLoadContextInfo(bool const aIsPrivate,
+                   uint32_t const aAppId,
+                   bool const aIsInBrowserElement,
+                   bool const aIsAnonymous)
+{
+  return new LoadContextInfo(aIsPrivate,
+                             aAppId,
+                             aIsInBrowserElement,
+                             aIsAnonymous);
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/LoadContextInfo.h
@@ -0,0 +1,53 @@
+/* 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 nsLoadContextInfo_h__
+#define nsLoadContextInfo_h__
+
+#include "nsILoadContextInfo.h"
+
+class nsIChannel;
+class nsILoadContext;
+
+namespace mozilla {
+namespace net {
+
+class LoadContextInfo : public nsILoadContextInfo
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSILOADCONTEXTINFO
+
+  LoadContextInfo(bool aIsPrivate, uint32_t aAppId, bool aIsInBrowser, bool aIsAnonymous);
+
+private:
+  virtual ~LoadContextInfo();
+
+protected:
+  uint32_t mAppId;
+  bool mIsPrivate : 1;
+  bool mIsInBrowser : 1;
+  bool mIsAnonymous : 1;
+};
+
+LoadContextInfo *
+GetLoadContextInfo(nsIChannel * aChannel);
+
+LoadContextInfo *
+GetLoadContextInfo(nsILoadContext * aLoadContext,
+                   bool aAnonymous);
+
+LoadContextInfo *
+GetLoadContextInfo(nsILoadContextInfo* aInfo);
+
+LoadContextInfo *
+GetLoadContextInfo(bool const aIsPrivate = false,
+                   uint32_t const aAppId = nsILoadContextInfo::NO_APP_ID,
+                   bool const aIsInBrowserElement = false,
+                   bool const aIsAnonymous = false);
+
+} // net
+} // mozilla
+
+#endif
--- a/netwerk/base/src/moz.build
+++ b/netwerk/base/src/moz.build
@@ -18,16 +18,17 @@ EXPORTS.mozilla.net += [
     'DashboardTypes.h',
 ]
 
 CPP_SOURCES += [
     'ArrayBufferInputStream.cpp',
     'BackgroundFileSaver.cpp',
     'Dashboard.cpp',
     'EventTokenBucket.cpp',
+    'LoadContextInfo.cpp',
     'NetworkActivityMonitor.cpp',
     'ProxyAutoConfig.cpp',
     'RedirectChannelRegistrar.cpp',
     'Tickler.cpp',
     'nsAsyncRedirectVerifyHelper.cpp',
     'nsAsyncStreamCopier.cpp',
     'nsAuthInformationHolder.cpp',
     'nsBase64Encoder.cpp',
--- a/netwerk/build/Makefile.in
+++ b/netwerk/build/Makefile.in
@@ -7,16 +7,17 @@ EXPORT_LIBRARY = 1
 SHARED_LIBRARY_LIBS = \
   ../base/src/$(LIB_PREFIX)neckobase_s.$(LIB_SUFFIX) \
   ../dns/$(LIB_PREFIX)neckodns_s.$(LIB_SUFFIX) \
   ../socket/$(LIB_PREFIX)neckosocket_s.$(LIB_SUFFIX) \
   ../streamconv/src/$(LIB_PREFIX)nkconv_s.$(LIB_SUFFIX) \
   ../streamconv/converters/$(LIB_PREFIX)nkcnvts_s.$(LIB_SUFFIX) \
   ../mime/$(LIB_PREFIX)nkmime_s.$(LIB_SUFFIX) \
   ../cache/$(LIB_PREFIX)nkcache_s.$(LIB_SUFFIX) \
+  ../cache2/$(LIB_PREFIX)nkcache2_s.$(LIB_SUFFIX) \
   ../protocol/about/$(LIB_PREFIX)nkabout_s.$(LIB_SUFFIX) \
   $(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \
     ../protocol/$(d)/$(LIB_PREFIX)nk$(d)_s.$(LIB_SUFFIX)) \
   ../ipc/$(LIB_PREFIX)neckoipc_s.$(LIB_SUFFIX) \
   $(NULL)
 
 ifdef MOZ_SRTP
 SHARED_LIBRARY_LIBS += \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -118,16 +118,20 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsEf
 
 #include "nsSerializationHelper.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSerializationHelper)
 
 #include "RedirectChannelRegistrar.h"
 typedef mozilla::net::RedirectChannelRegistrar RedirectChannelRegistrar;
 NS_GENERIC_FACTORY_CONSTRUCTOR(RedirectChannelRegistrar)
 
+#include "CacheStorageService.h"
+typedef mozilla::net::CacheStorageService CacheStorageService;
+NS_GENERIC_FACTORY_CONSTRUCTOR(CacheStorageService)
+
 ///////////////////////////////////////////////////////////////////////////////
 
 extern nsresult
 net_NewIncrementalDownload(nsISupports *, const nsIID &, void **);
 
 #define NS_INCREMENTALDOWNLOAD_CID \
 { /* a62af1ba-79b3-4896-8aaf-b148bfce4280 */         \
     0xa62af1ba,                                      \
@@ -802,16 +806,17 @@ NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERV
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #elif defined(MOZ_ENABLE_QTNETWORK)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #elif defined(MOZ_WIDGET_ANDROID)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID);
 NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID);
+NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID);
 
 static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
     { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor },
     { &kNS_STREAMTRANSPORTSERVICE_CID, false, nullptr, nsStreamTransportServiceConstructor },
     { &kNS_SOCKETTRANSPORTSERVICE_CID, false, nullptr, nsSocketTransportServiceConstructor },
     { &kNS_SERVERSOCKET_CID, false, nullptr, nsServerSocketConstructor },
     { &kNS_UDPSERVERSOCKET_CID, false, nullptr, nsUDPServerSocketConstructor },
     { &kNS_SOCKETPROVIDERSERVICE_CID, false, nullptr, nsSocketProviderService::Create },
@@ -941,16 +946,17 @@ static const mozilla::Module::CIDEntry k
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNetworkLinkServiceConstructor },
 #elif defined(MOZ_ENABLE_QTNETWORK)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsQtNetworkLinkServiceConstructor },
 #elif defined(MOZ_WIDGET_ANDROID)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsAndroidNetworkLinkServiceConstructor },
 #endif
     { &kNS_SERIALIZATION_HELPER_CID, false, nullptr, nsSerializationHelperConstructor },
     { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor },
+    { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor },
     { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
     { NS_IOSERVICE_CONTRACTID, &kNS_IOSERVICE_CID },
     { NS_NETUTIL_CONTRACTID, &kNS_IOSERVICE_CID },
     { NS_STREAMTRANSPORTSERVICE_CONTRACTID, &kNS_STREAMTRANSPORTSERVICE_CID },
     { NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &kNS_SOCKETTRANSPORTSERVICE_CID },
@@ -1083,16 +1089,17 @@ static const mozilla::Module::ContractID
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #elif defined(MOZ_ENABLE_QTNETWORK)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #elif defined(MOZ_WIDGET_ANDROID)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #endif
     { NS_SERIALIZATION_HELPER_CONTRACTID, &kNS_SERIALIZATION_HELPER_CID },
     { NS_REDIRECTCHANNELREGISTRAR_CONTRACTID, &kNS_REDIRECTCHANNELREGISTRAR_CID },
+    { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID },
     { nullptr }
 };
 
 static const mozilla::Module kNeckoModule = {
     mozilla::Module::kVersion,
     kNeckoCIDs,
     kNeckoContracts,
     kNeckoCategories,
--- a/netwerk/cache/nsApplicationCacheService.cpp
+++ b/netwerk/cache/nsApplicationCacheService.cpp
@@ -4,16 +4,17 @@
 
 #include "nsDiskCache.h"
 #include "nsDiskCacheDeviceSQL.h"
 #include "nsCacheService.h"
 #include "nsApplicationCacheService.h"
 #include "nsCRT.h"
 #include "nsNetUtil.h"
 #include "nsIObserverService.h"
+#include "nsILoadContextInfo.h"
 
 using namespace mozilla;
 
 static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID);
 
 //-----------------------------------------------------------------------------
 // nsApplicationCacheService
 //-----------------------------------------------------------------------------
@@ -23,30 +24,27 @@ NS_IMPL_ISUPPORTS1(nsApplicationCacheSer
 nsApplicationCacheService::nsApplicationCacheService()
 {
     nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID);
     mCacheService = nsCacheService::GlobalInstance();
 }
 
 NS_IMETHODIMP
 nsApplicationCacheService::BuildGroupID(nsIURI *aManifestURL,
-                                        nsILoadContext *aLoadContext,
+                                        nsILoadContextInfo *aLoadContextInfo,
                                         nsACString &_result)
 {
     nsresult rv;
 
     uint32_t appId = NECKO_NO_APP_ID;
     bool isInBrowserElement = false;
 
-    if (aLoadContext) {
-        rv = aLoadContext->GetAppId(&appId);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = aLoadContext->GetIsInBrowserElement(&isInBrowserElement);
-        NS_ENSURE_SUCCESS(rv, rv);
+    if (aLoadContextInfo) {
+        appId = aLoadContextInfo->AppId();
+        isInBrowserElement = aLoadContextInfo->IsInBrowserElement();
     }
 
     rv = nsOfflineCacheDevice::BuildApplicationCacheGroupID(
         aManifestURL, appId, isInBrowserElement, _result);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
@@ -129,27 +127,27 @@ nsApplicationCacheService::DeactivateGro
     nsRefPtr<nsOfflineCacheDevice> device;
     nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
     NS_ENSURE_SUCCESS(rv, rv);
     return device->DeactivateGroup(group);
 }
 
 NS_IMETHODIMP
 nsApplicationCacheService::ChooseApplicationCache(const nsACString &key,
-                                                  nsILoadContext *aLoadContext,
+                                                  nsILoadContextInfo *aLoadContextInfo,
                                                   nsIApplicationCache **out)
 {
     if (!mCacheService)
         return NS_ERROR_UNEXPECTED;
 
     nsRefPtr<nsOfflineCacheDevice> device;
     nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    return device->ChooseApplicationCache(key, aLoadContext, out);
+    return device->ChooseApplicationCache(key, aLoadContextInfo, out);
 }
 
 NS_IMETHODIMP
 nsApplicationCacheService::CacheOpportunistically(nsIApplicationCache* cache,
                                                   const nsACString &key)
 {
     if (!mCacheService)
         return NS_ERROR_UNEXPECTED;
--- a/netwerk/cache/nsCacheUtils.cpp
+++ b/netwerk/cache/nsCacheUtils.cpp
@@ -58,17 +58,17 @@ nsShutdownThread::BlockingShutdown(nsITh
   rv = NS_NewNamedThread("thread shutdown", getter_AddRefs(workerThread));
   if (NS_FAILED(rv)) {
     NS_WARNING("Can't create nsShutDownThread worker thread!");
     return rv;
   }
 
   {
     MutexAutoLock lock(st->mLock);
-    rv = aThread->Dispatch(st, NS_DISPATCH_NORMAL);
+    rv = workerThread->Dispatch(st, NS_DISPATCH_NORMAL);
     if (NS_FAILED(rv)) {
       NS_WARNING(
         "Dispatching event in nsShutdownThread::BlockingShutdown failed!");
     } else {
       st->mCondVar.Wait();
     }
   }
 
--- a/netwerk/cache/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -20,16 +20,17 @@
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "nsCRT.h"
 #include "nsArrayUtils.h"
 #include "nsIArray.h"
 #include "nsIVariant.h"
+#include "nsILoadContextInfo.h"
 #include "nsThreadUtils.h"
 #include "nsISerializable.h"
 #include "nsSerializationHelper.h"
 
 #include "mozIStorageService.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageFunction.h"
 #include "mozStorageHelper.h"
@@ -2418,56 +2419,58 @@ nsOfflineCacheDevice::DiscardByAppId(int
 {
   nsresult rv;
 
   nsAutoCString jaridsuffix;
   jaridsuffix.Append('%');
   rv = AppendJARIdentifier(jaridsuffix, appID, browserEntriesOnly);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  AutoResetStatement statement(mStatement_EnumerateApps);
-  rv = statement->BindUTF8StringByIndex(0, jaridsuffix);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  bool hasRows;
-  rv = statement->ExecuteStep(&hasRows);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  while (hasRows) {
-    nsAutoCString group;
-    rv = statement->GetUTF8String(0, group);
+  {
+    AutoResetStatement statement(mStatement_EnumerateApps);
+    rv = statement->BindUTF8StringByIndex(0, jaridsuffix);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCString clientID;
-    rv = statement->GetUTF8String(1, clientID);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIRunnable> ev =
-      new nsOfflineCacheDiscardCache(this, group, clientID);
-
-    rv = nsCacheService::DispatchToCacheIOThread(ev);
-    NS_ENSURE_SUCCESS(rv, rv);
-
+    bool hasRows;
     rv = statement->ExecuteStep(&hasRows);
     NS_ENSURE_SUCCESS(rv, rv);
+
+    while (hasRows) {
+      nsAutoCString group;
+      rv = statement->GetUTF8String(0, group);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCString clientID;
+      rv = statement->GetUTF8String(1, clientID);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<nsIRunnable> ev =
+        new nsOfflineCacheDiscardCache(this, group, clientID);
+
+      rv = nsCacheService::DispatchToCacheIOThread(ev);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = statement->ExecuteStep(&hasRows);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
   }
 
   if (!browserEntriesOnly) {
     // If deleting app, delete any 'inBrowserElement' entries too
     rv = DiscardByAppId(appID, true);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 bool
 nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI,
                                   const nsACString &clientID,
-                                  nsILoadContext *loadContext)
+                                  nsILoadContextInfo *loadContextInfo)
 {
   {
     MutexAutoLock lock(mLock);
     if (!mActiveCaches.Contains(clientID))
       return false;
   }
 
   nsAutoCString groupID;
@@ -2488,22 +2491,19 @@ nsOfflineCacheDevice::CanUseCache(nsIURI
   if (!NS_SecurityCompareURIs(keyURI, groupURI,
                               GetStrictFileOriginPolicy()))
     return false;
 
   // Get extended origin attributes
   uint32_t appId = NECKO_NO_APP_ID;
   bool isInBrowserElement = false;
 
-  if (loadContext) {
-      rv = loadContext->GetAppId(&appId);
-      NS_ENSURE_SUCCESS(rv, false);
-
-      rv = loadContext->GetIsInBrowserElement(&isInBrowserElement);
-      NS_ENSURE_SUCCESS(rv, false);
+  if (loadContextInfo) {
+      appId = loadContextInfo->AppId();
+      isInBrowserElement = loadContextInfo->IsInBrowserElement();
   }
 
   // Check the groupID we found is equal to groupID based
   // on the load context demanding load from app cache.
   // This is check of extended origin.
   nsAutoCString demandedGroupID;
   rv = BuildApplicationCacheGroupID(groupURI, appId, isInBrowserElement,
                                     demandedGroupID);
@@ -2513,17 +2513,17 @@ nsOfflineCacheDevice::CanUseCache(nsIURI
     return false;
 
   return true;
 }
 
 
 nsresult
 nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
-                                             nsILoadContext *loadContext,
+                                             nsILoadContextInfo *loadContextInfo,
                                              nsIApplicationCache **out)
 {
   *out = nullptr;
 
   nsCOMPtr<nsIURI> keyURI;
   nsresult rv = NS_NewURI(getter_AddRefs(keyURI), key);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2541,17 +2541,17 @@ nsOfflineCacheDevice::ChooseApplicationC
     rv = statement->GetInt32(1, &itemType);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
       nsAutoCString clientID;
       rv = statement->GetUTF8String(0, clientID);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (CanUseCache(keyURI, clientID, loadContext)) {
+      if (CanUseCache(keyURI, clientID, loadContextInfo)) {
         return GetApplicationCache(clientID, out);
       }
     }
 
     rv = statement->ExecuteStep(&hasRows);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
@@ -2573,17 +2573,17 @@ nsOfflineCacheDevice::ChooseApplicationC
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Don't associate with a cache based solely on a whitelist entry
     if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
       nsAutoCString clientID;
       rv = nsstatement->GetUTF8String(0, clientID);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (CanUseCache(keyURI, clientID, loadContext)) {
+      if (CanUseCache(keyURI, clientID, loadContextInfo)) {
         return GetApplicationCache(clientID, out);
       }
     }
 
     rv = nsstatement->ExecuteStep(&hasRows);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
--- a/netwerk/cache/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -152,17 +152,17 @@ public:
                                                        nsIApplicationCache **out);
 
   nsresult                GetActiveCache(const nsACString &group,
                                          nsIApplicationCache **out);
 
   nsresult                DeactivateGroup(const nsACString &group);
 
   nsresult                ChooseApplicationCache(const nsACString &key,
-                                                 nsILoadContext *loadContext,
+                                                 nsILoadContextInfo *loadContext,
                                                  nsIApplicationCache **out);
 
   nsresult                CacheOpportunistically(nsIApplicationCache* cache,
                                                  const nsACString &key);
 
   nsresult                DiscardByAppId(int32_t appID, bool isInBrowser);
 
   nsresult                GetGroups(uint32_t *count,char ***keys);
@@ -203,17 +203,17 @@ private:
   nsresult InitActiveCaches();
   nsresult UpdateEntry(nsCacheEntry *entry);
   nsresult UpdateEntrySize(nsCacheEntry *entry, uint32_t newSize);
   nsresult DeleteEntry(nsCacheEntry *entry, bool deleteData);
   nsresult DeleteData(nsCacheEntry *entry);
   nsresult EnableEvictionObserver();
   nsresult DisableEvictionObserver();
 
-  bool CanUseCache(nsIURI *keyURI, const nsACString &clientID, nsILoadContext *loadContext);
+  bool CanUseCache(nsIURI *keyURI, const nsACString &clientID, nsILoadContextInfo *loadContext);
 
   nsresult MarkEntry(const nsCString &clientID,
                      const nsACString &key,
                      uint32_t typeBits);
   nsresult UnmarkEntry(const nsCString &clientID,
                        const nsACString &key,
                        uint32_t typeBits);
 
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/AppCacheStorage.cpp
@@ -0,0 +1,156 @@
+/* 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 "AppCacheStorage.h"
+#include "CacheStorageService.h"
+#include "CacheLog.h"
+
+#include "OldWrappers.h"
+
+#include "nsICacheEntryDoomCallback.h"
+
+#include "nsICacheService.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED0(AppCacheStorage, CacheStorage)
+
+AppCacheStorage::AppCacheStorage(nsILoadContextInfo* aInfo,
+                                 nsIApplicationCache* aAppCache)
+: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */)
+, mAppCache(aAppCache)
+{
+  MOZ_COUNT_CTOR(AppCacheStorage);
+}
+
+AppCacheStorage::~AppCacheStorage()
+{
+  ProxyReleaseMainThread(mAppCache);
+  MOZ_COUNT_DTOR(AppCacheStorage);
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncOpenURI(nsIURI *aURI,
+                                            const nsACString & aIdExtension,
+                                            uint32_t aFlags,
+                                            nsICacheEntryOpenCallback *aCallback)
+{
+  if (!CacheStorageService::Self())
+    return NS_ERROR_NOT_INITIALIZED;
+
+  NS_ENSURE_ARG(aURI);
+  NS_ENSURE_ARG(aCallback);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIApplicationCache> appCache = mAppCache;
+
+  if (!appCache) {
+    rv = ChooseApplicationCache(aURI, getter_AddRefs(appCache));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!appCache) {
+    LOG(("AppCacheStorage::AsyncOpenURI entry not found in any appcache, giving up"));
+    aCallback->OnCacheEntryAvailable(nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIURI> noRefURI;
+  rv = aURI->CloneIgnoringRef(getter_AddRefs(noRefURI));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString cacheKey;
+  rv = noRefURI->GetAsciiSpec(cacheKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<_OldCacheLoad> appCacheLoad =
+    new _OldCacheLoad(cacheKey, aCallback, appCache,
+                      LoadInfo(), WriteToDisk(), aFlags);
+  rv = appCacheLoad->Start();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncDoomURI(nsIURI *aURI, const nsACString & aIdExtension,
+                                            nsICacheEntryDoomCallback* aCallback)
+{
+  if (!CacheStorageService::Self())
+    return NS_ERROR_NOT_INITIALIZED;
+
+  if (!mAppCache) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // TODO - remove entry from app cache
+  // I think no one is using this...
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncEvictStorage(nsICacheEntryDoomCallback* aCallback)
+{
+  if (!CacheStorageService::Self())
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsresult rv;
+
+  nsCOMPtr<nsIApplicationCacheService> appCacheService =
+    do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!mAppCache) {
+    if (LoadInfo()->AppId() == nsILoadContextInfo::NO_APP_ID &&
+        !LoadInfo()->IsInBrowserElement()) {
+
+      // Clear everything.
+      nsCOMPtr<nsICacheService> serv =
+          do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = serv->EvictEntries(nsICache::STORE_OFFLINE);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    else {
+      // Clear app or inbrowser staff.
+      rv = appCacheService->DiscardByAppId(LoadInfo()->AppId(),
+                                           LoadInfo()->IsInBrowserElement());
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+  else {
+    // Discard the group
+    nsAutoCString groupID;
+    rv = mAppCache->GetGroupID(groupID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = appCacheService->DeactivateGroup(groupID);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (aCallback)
+    aCallback->OnCacheEntryDoomed(NS_OK);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
+                                                 bool aVisitEntries)
+{
+  if (!CacheStorageService::Self())
+    return NS_ERROR_NOT_INITIALIZED;
+
+  LOG(("AppCacheStorage::AsyncVisitStorage [this=%p, cb=%p]", this, aVisitor));
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/AppCacheStorage.h
@@ -0,0 +1,37 @@
+/* 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 AppCacheStorage__h__
+#define AppCacheStorage__h__
+
+#include "CacheStorage.h"
+
+#include "nsCOMPtr.h"
+#include "nsILoadContextInfo.h"
+#include "nsIApplicationCache.h"
+
+class nsIApplicationCache;
+
+namespace mozilla {
+namespace net {
+
+class AppCacheStorage : public CacheStorage
+{
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSICACHESTORAGE
+
+public:
+  AppCacheStorage(nsILoadContextInfo* aInfo,
+                  nsIApplicationCache* aAppCache);
+
+private:
+  virtual ~AppCacheStorage();
+
+  nsCOMPtr<nsIApplicationCache> mAppCache;
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheEntriesEnumerator.cpp
@@ -0,0 +1,186 @@
+/* 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 "CacheEntriesEnumerator.h"
+
+#include "CacheFileIOManager.h"
+#include "CacheFile.h"
+
+#include "nsIDirectoryEnumerator.h"
+#include "nsIFile.h"
+#include "nsIThread.h"
+#include "nsISimpleEnumerator.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+CacheEntriesEnumerator::CacheEntriesEnumerator(nsIFile* aEntriesDirectory)
+: mEntriesDirectory(aEntriesDirectory)
+{
+  MOZ_COUNT_CTOR(CacheEntriesEnumerator);
+}
+
+CacheEntriesEnumerator::~CacheEntriesEnumerator()
+{
+  MOZ_COUNT_DTOR(CacheEntriesEnumerator);
+
+  if (mEnumerator) {
+    mEnumerator->Close();
+    ProxyReleaseMainThread(mEnumerator);
+  }
+}
+
+nsresult CacheEntriesEnumerator::Init()
+{
+  nsresult rv;
+
+  nsCOMPtr<nsISimpleEnumerator> e;
+  rv = mEntriesDirectory->GetDirectoryEntries(getter_AddRefs(e));
+
+  if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) {
+    return NS_OK;
+  }
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mEnumerator = do_QueryInterface(e, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+bool CacheEntriesEnumerator::HasMore()
+{
+#ifdef DEBUG
+  if (!mThreadCheck)
+    mThreadCheck = NS_GetCurrentThread();
+  else
+    MOZ_ASSERT(mThreadCheck == NS_GetCurrentThread());
+#endif
+
+  if (!mEnumerator) {
+    return false;
+  }
+
+  if (mCurrentFile)
+    return true;
+
+  nsresult rv;
+
+  rv = mEnumerator->GetNextFile(getter_AddRefs(mCurrentFile));
+
+  if (NS_FAILED(rv)) {
+    mEnumerator->Close();
+    mEnumerator = nullptr;
+    return false;
+  }
+
+  return !!mCurrentFile;
+}
+
+namespace { // anon
+
+class FileConsumer : public CacheFileListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  nsresult Init(nsCSubstring const & aKey,
+                CacheEntriesEnumeratorCallback* aCallback);
+
+  virtual ~FileConsumer() {}
+
+private:
+  NS_IMETHOD OnFileReady(nsresult aResult, bool aIsNew);
+  NS_IMETHOD OnFileDoomed(nsresult aResult) { return NS_OK; }
+
+  nsRefPtr<CacheFile> mFile;
+  nsRefPtr<CacheEntriesEnumeratorCallback> mCallback;
+};
+
+nsresult FileConsumer::Init(const nsCSubstring &aKey,
+                            CacheEntriesEnumeratorCallback *aCallback)
+{
+  mCallback = aCallback;
+
+  mFile = new CacheFile();
+  nsresult rv = mFile->Init(aKey, false, false, false, true, this);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP FileConsumer::OnFileReady(nsresult aResult, bool aIsNew)
+{
+  //MOZ_ASSERT(!aIsNew);
+
+  if (NS_FAILED(aResult)) {
+    mCallback->OnFile(nullptr);
+  }
+  else {
+    mCallback->OnFile(mFile);
+  }
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(FileConsumer, CacheFileListener);
+
+} // anon
+
+nsresult CacheEntriesEnumerator::GetNextCacheFile(CacheEntriesEnumeratorCallback* aCallback)
+{
+#ifdef DEBUG
+  MOZ_ASSERT(mThreadCheck == NS_GetCurrentThread());
+#endif
+
+  nsresult rv;
+
+  NS_ENSURE_TRUE(mCurrentFile, NS_ERROR_UNEXPECTED);
+
+  nsAutoCString key;
+  rv = mCurrentFile->GetNativeLeafName(key);
+
+  mCurrentFile = nullptr;
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<FileConsumer> consumer = new FileConsumer();
+  rv = consumer->Init(key, aCallback);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult CacheEntriesEnumerator::GetNextFile(nsIFile** aFile)
+{
+#ifdef DEBUG
+  MOZ_ASSERT(mThreadCheck == NS_GetCurrentThread());
+#endif
+
+  NS_ENSURE_TRUE(mCurrentFile, NS_ERROR_UNEXPECTED);
+
+  mCurrentFile.forget(aFile);
+  return NS_OK;
+}
+
+nsresult CacheEntriesEnumerator::GetCacheFileFromFile(nsIFile* aFile,
+                                                      CacheEntriesEnumeratorCallback* aCallback)
+{
+  nsresult rv;
+
+  nsAutoCString key;
+  rv = aFile->GetNativeLeafName(key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<FileConsumer> consumer = new FileConsumer();
+  rv = consumer->Init(key, aCallback);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheEntriesEnumerator.h
@@ -0,0 +1,55 @@
+/* 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 CacheEntriesEnumerator__h__
+#define CacheEntriesEnumerator__h__
+
+#include "nsCOMPtr.h"
+
+class nsIFile;
+class nsIDirectoryEnumerator;
+class nsIThread;
+
+namespace mozilla {
+namespace net {
+
+class CacheFileIOManager;
+class CacheFileListener;
+class CacheFile;
+
+class CacheEntriesEnumeratorCallback : public nsISupports
+{
+public:
+  virtual void OnFile(CacheFile* aFile) = 0;
+};
+
+class CacheEntriesEnumerator
+{
+public:
+  ~CacheEntriesEnumerator();
+
+  bool HasMore();
+  nsresult GetNextCacheFile(CacheEntriesEnumeratorCallback* aCallback);
+  nsresult GetNextFile(nsIFile** aFile);
+  nsresult GetCacheFileFromFile(nsIFile* aFile, CacheEntriesEnumeratorCallback* aCallback);
+
+protected:
+  friend class CacheFileIOManager;
+  CacheEntriesEnumerator(nsIFile* aEntriesDirectory);
+  nsresult Init();
+
+private:
+  nsCOMPtr<nsIDirectoryEnumerator> mEnumerator;
+  nsCOMPtr<nsIFile> mEntriesDirectory;
+  nsCOMPtr<nsIFile> mCurrentFile;
+
+#ifdef DEBUG
+  nsCOMPtr<nsIThread> mThreadCheck;
+#endif
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -0,0 +1,1333 @@
+/* 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 "CacheEntry.h"
+#include "CacheStorageService.h"
+#include "CacheLog.h"
+
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIURI.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheStorage.h"
+#include "nsISerializable.h"
+#include "nsIStreamTransportService.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsProxyRelease.h"
+#include "nsSerializationHelper.h"
+#include "nsThreadUtils.h"
+#include <math.h>
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+static uint32_t const ENTRY_WANTED =
+  nsICacheEntryOpenCallback::ENTRY_WANTED;
+static uint32_t const ENTRY_NEEDS_REVALIDATION =
+  nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
+static uint32_t const ENTRY_NOT_WANTED =
+  nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
+
+NS_IMPL_ISUPPORTS1(CacheEntry::Handle, nsICacheEntry)
+
+// CacheEntry::Handle
+
+CacheEntry::Handle::Handle(CacheEntry* aEntry)
+: mEntry(aEntry)
+{
+  MOZ_COUNT_CTOR(CacheEntry::Handle);
+
+  LOG(("New CacheEntry::Handle %p for entry %p", this, aEntry));
+}
+
+CacheEntry::Handle::~Handle()
+{
+  mEntry->OnWriterClosed(this);
+
+  MOZ_COUNT_DTOR(CacheEntry::Handle);
+}
+
+// CacheEntry
+
+NS_IMPL_ISUPPORTS3(CacheEntry,
+                   nsICacheEntry,
+                   nsIRunnable,
+                   CacheFileListener)
+
+CacheEntry::CacheEntry(const nsACString& aStorageID,
+                       nsIURI* aURI,
+                       const nsACString& aEnhanceID,
+                       bool aUseDisk)
+: mFrecency(0)
+, mSortingExpirationTime(uint32_t(-1))
+, mLock("CacheEntry")
+, mFileStatus(NS_ERROR_NOT_INITIALIZED)
+, mURI(aURI)
+, mEnhanceID(aEnhanceID)
+, mStorageID(aStorageID)
+, mUseDisk(aUseDisk)
+, mIsDoomed(false)
+, mSecurityInfoLoaded(false)
+, mPreventCallbacks(false)
+, mIsRegistered(false)
+, mIsRegistrationAllowed(true)
+, mHasMainThreadOnlyCallback(false)
+, mHasData(false)
+, mState(NOTLOADED)
+, mWriter(nullptr)
+, mPredictedDataSize(0)
+, mDataSize(0)
+{
+  MOZ_COUNT_CTOR(CacheEntry);
+
+  mService = CacheStorageService::Self();
+
+  CacheStorageService::Self()->RecordMemoryOnlyEntry(
+    this, !aUseDisk, true /* overwrite */);
+}
+
+CacheEntry::~CacheEntry()
+{
+  ProxyReleaseMainThread(mURI);
+
+  LOG(("CacheEntry::~CacheEntry [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheEntry);
+}
+
+#ifdef MOZ_LOGGING
+
+char const * CacheEntry::StateString(uint32_t aState)
+{
+  switch (aState) {
+  case NOTLOADED:     return "NOTLOADED";
+  case LOADING:       return "LOADING";
+  case EMPTY:         return "EMPTY";
+  case WRITING:       return "WRITING";
+  case READY:         return "READY";
+  case REVALIDATING:  return "REVALIDATING";
+  }
+
+  return "?";
+}
+
+#endif
+
+nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult)
+{
+  return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
+}
+
+nsresult CacheEntry::HashingKey(nsACString &aResult)
+{
+  return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult);
+}
+
+// static
+nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
+                                nsCSubstring const& aEnhanceID,
+                                nsIURI* aURI,
+                                nsACString &aResult)
+{
+  /**
+   * This key is used to salt hash that is a base for disk file name.
+   * Changing it will cause we will not be able to find files on disk.
+   */
+
+  if (aStorageID.Length()) {
+    aResult.Append(aStorageID);
+    aResult.Append(':');
+  }
+
+  if (aEnhanceID.Length()) {
+    aResult.Append(aEnhanceID);
+    aResult.Append(':');
+  }
+
+  nsAutoCString spec;
+  nsresult rv = aURI->GetAsciiSpec(spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aResult.Append(spec);
+
+  return NS_OK;
+}
+
+void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags)
+{
+  LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
+    this, StateString(mState), aFlags, aCallback));
+
+  bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
+  bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
+  bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
+
+  bool mainThreadOnly;
+  if (aCallback && NS_FAILED(aCallback->GetMainThreadOnly(&mainThreadOnly)))
+    mainThreadOnly = true; // rather play safe...
+
+  MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
+  MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  if (Load(truncate, priority) ||
+      PendingCallbacks() ||
+      !InvokeCallback(aCallback, readonly)) {
+    // Load in progress or callback bypassed...
+    if (mainThreadOnly) {
+      LOG(("  callback is main-thread only"));
+      mHasMainThreadOnlyCallback = true;
+    }
+
+    RememberCallback(aCallback, readonly);
+  }
+}
+
+bool CacheEntry::Load(bool aTruncate, bool aPriority)
+{
+  LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
+
+  mLock.AssertCurrentThreadOwns();
+
+  if (mState > LOADING) {
+    LOG(("  already loaded"));
+    return false;
+  }
+
+  if (mState == LOADING) {
+    LOG(("  already loading"));
+    return true;
+  }
+
+  MOZ_ASSERT(!mFile);
+
+  bool directLoad = aTruncate || !mUseDisk;
+  if (directLoad) {
+    // Just fake the load has already been done as "new".
+    mState = EMPTY;
+    mFileStatus = NS_OK;
+  }
+  else {
+    mState = LOADING;
+  }
+
+  mFile = new CacheFile();
+
+  BackgroundOp(Ops::REGISTER);
+
+  mozilla::MutexAutoUnlock unlock(mLock);
+
+  nsresult rv;
+
+  nsAutoCString fileKey;
+  rv = HashingKeyWithStorage(fileKey);
+
+  LOG(("  performing load, file=%p", mFile.get()));
+  if (NS_SUCCEEDED(rv)) {
+    rv = mFile->Init(fileKey,
+                     aTruncate,
+                     !mUseDisk,
+                     aPriority,
+                     false /* key is not a hash */,
+                     directLoad ? nullptr : this);
+  }
+
+  if (NS_FAILED(rv)) {
+    mFileStatus = rv;
+    AsyncDoom(nullptr);
+    return false;
+  }
+
+  return mState == LOADING;
+}
+
+NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
+{
+  LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08x, new=%d]",
+      this, aResult, aIsNew));
+
+  // OnFileReady, that is the only code that can transit from LOADING
+  // to any follow-on state, can only be invoked ones on an entry,
+  // thus no need to lock.  Until this moment there is no consumer that
+  // could manipulate the entry state.
+  mozilla::MutexAutoLock lock(mLock);
+
+  MOZ_ASSERT(mState == LOADING);
+
+  mState = (aIsNew || NS_FAILED(aResult))
+    ? EMPTY
+    : READY;
+
+  mFileStatus = aResult;
+
+  if (mState == READY)
+    mHasData = true;
+
+  InvokeCallbacks();
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
+{
+  if (mDoomCallback) {
+    nsRefPtr<DoomCallbackRunnable> event =
+      new DoomCallbackRunnable(this, aResult);
+    NS_DispatchToMainThread(event);
+  }
+
+  return NS_OK;
+}
+
+already_AddRefed<CacheEntry> CacheEntry::ReopenTruncated(nsICacheEntryOpenCallback* aCallback)
+{
+  LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
+
+  mLock.AssertCurrentThreadOwns();
+
+  // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
+  mPreventCallbacks = true;
+
+  nsRefPtr<CacheEntry> newEntry;
+  {
+    mozilla::MutexAutoUnlock unlock(mLock);
+
+    // The following call dooms this entry (calls DoomAlreadyRemoved on us)
+    nsresult rv = CacheStorageService::Self()->AddStorageEntry(
+      GetStorageID(), GetURI(), GetEnhanceID(),
+      mUseDisk,
+      true, // always create
+      true, // truncate existing (this one)
+      getter_AddRefs(newEntry));
+
+    LOG(("  exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
+
+    if (NS_SUCCEEDED(rv)) {
+      newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
+    }
+    else {
+      AsyncDoom(nullptr);
+    }
+  }
+
+  mPreventCallbacks = false;
+
+  if (!newEntry)
+    return nullptr;
+
+  newEntry->TransferCallbacks(*this);
+  mCallbacks.Clear();
+  mReadOnlyCallbacks.Clear();
+  mHasMainThreadOnlyCallback = false;
+
+  return newEntry.forget();
+}
+
+void CacheEntry::TransferCallbacks(CacheEntry const& aFromEntry)
+{
+  mozilla::MutexAutoLock lock(mLock);
+
+  LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
+    this, &aFromEntry));
+
+  mCallbacks.AppendObjects(aFromEntry.mCallbacks);
+  mReadOnlyCallbacks.AppendObjects(aFromEntry.mReadOnlyCallbacks);
+  if (aFromEntry.mHasMainThreadOnlyCallback)
+    mHasMainThreadOnlyCallback = true;
+
+  if (mCallbacks.Length() || mReadOnlyCallbacks.Length())
+    BackgroundOp(Ops::CALLBACKS, true);
+}
+
+void CacheEntry::RememberCallback(nsICacheEntryOpenCallback* aCallback,
+                                  bool aReadOnly)
+{
+  // AsyncOpen can be called w/o a callback reference (when this is a new/truncated entry)
+  if (!aCallback)
+    return;
+
+  LOG(("CacheEntry::RememberCallback [this=%p, cb=%p]", this, aCallback));
+
+  mLock.AssertCurrentThreadOwns();
+
+  if (!aReadOnly)
+    mCallbacks.AppendObject(aCallback);
+  else
+    mReadOnlyCallbacks.AppendObject(aCallback);
+}
+
+bool CacheEntry::PendingCallbacks()
+{
+  mLock.AssertCurrentThreadOwns();
+  return mCallbacks.Length() || mReadOnlyCallbacks.Length();
+}
+
+void CacheEntry::InvokeCallbacksMainThread()
+{
+  mozilla::MutexAutoLock lock(mLock);
+  InvokeCallbacks();
+}
+
+void CacheEntry::InvokeCallbacks()
+{
+  LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
+
+  mLock.AssertCurrentThreadOwns();
+
+  do {
+    if (mPreventCallbacks) {
+      LOG(("CacheEntry::InvokeCallbacks END [this=%p] callbacks prevented!", this));
+      return;
+    }
+
+    if (!mCallbacks.Count()) {
+      LOG(("  no r/w callbacks"));
+      break;
+    }
+
+    if (mHasMainThreadOnlyCallback && !NS_IsMainThread()) {
+      nsRefPtr<nsRunnableMethod<CacheEntry> > event =
+        NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksMainThread);
+      NS_DispatchToMainThread(event);
+      LOG(("CacheEntry::InvokeCallbacks END [this=%p] dispatching to maintread", this));
+      return;
+    }
+
+    nsCOMPtr<nsICacheEntryOpenCallback> callback = mCallbacks[0];
+    mCallbacks.RemoveElementAt(0);
+
+    if (!InvokeCallback(callback, false)) {
+      mCallbacks.InsertElementAt(0, callback);
+      LOG(("CacheEntry::InvokeCallbacks END [this=%p] callback bypassed", this));
+      return;
+    }
+  } while (true);
+
+  while (mReadOnlyCallbacks.Count()) {
+    if (mHasMainThreadOnlyCallback && !NS_IsMainThread()) {
+      nsRefPtr<nsRunnableMethod<CacheEntry> > event =
+        NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksMainThread);
+      NS_DispatchToMainThread(event);
+      LOG(("CacheEntry::InvokeCallbacks END [this=%p] dispatching to maintread", this));
+      return;
+    }
+
+    nsCOMPtr<nsICacheEntryOpenCallback> callback = mReadOnlyCallbacks[0];
+    mReadOnlyCallbacks.RemoveElementAt(0);
+
+    if (!InvokeCallback(callback, true)) {
+      // Didn't trigger, so we must stop
+      mReadOnlyCallbacks.InsertElementAt(0, callback);
+      break;
+    }
+  }
+
+  if (!mCallbacks.Count() && !mReadOnlyCallbacks.Count())
+    mHasMainThreadOnlyCallback = false;
+
+  LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
+}
+
+bool CacheEntry::InvokeCallback(nsICacheEntryOpenCallback* aCallback,
+                                bool aReadOnly)
+{
+  LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
+    this, StateString(mState), aCallback));
+
+  mLock.AssertCurrentThreadOwns();
+
+  bool notWanted = false;
+
+  if (!mIsDoomed) {
+    // When we are here, the entry must be loaded from disk
+    MOZ_ASSERT(mState > LOADING);
+
+    if (mState == WRITING ||
+        mState == REVALIDATING) {
+      // Prevent invoking other callbacks since one of them is now writing
+      // or revalidating this entry.  No consumers should get this entry
+      // until metadata are filled with values downloaded from the server
+      // or the entry revalidated and output stream has been opened.
+      LOG(("  entry is being written/revalidated, callback bypassed"));
+      return false;
+    }
+
+    if (!aReadOnly) {
+      if (mState == EMPTY) {
+        // Advance to writing state, we expect to invoke the callback and let
+        // it fill content of this entry.  Must set and check the state here
+        // to prevent more then one
+        mState = WRITING;
+        LOG(("  advancing to WRITING state"));
+      }
+
+      if (!aCallback) {
+        // We can be given no callback only in case of recreate, it is ok
+        // to advance to WRITING state since the caller of recreate is expected
+        // to write this entry now.
+        return true;
+      }
+
+      if (mState == READY) {
+        // Metadata present, validate the entry
+        uint32_t checkResult;
+        {
+          // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
+          mozilla::MutexAutoUnlock unlock(mLock);
+
+          nsresult rv = aCallback->OnCacheEntryCheck(this, nullptr, &checkResult);
+          LOG(("  OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult));
+
+          if (NS_FAILED(rv))
+            checkResult = ENTRY_WANTED;
+        }
+
+        switch (checkResult) {
+        case ENTRY_WANTED:
+          // Nothing more to do here, the consumer is responsible to handle
+          // the result of OnCacheEntryCheck it self.
+          // Proceed to callback...
+          break;
+
+        case ENTRY_NEEDS_REVALIDATION:
+          LOG(("  will be holding callbacks until entry is revalidated"));
+          // State is READY now and from that state entry cannot transit to any other
+          // state then REVALIDATING for which cocurrency is not an issue.  Potentially
+          // no need to lock here.
+          mState = REVALIDATING;
+          break;
+
+        case ENTRY_NOT_WANTED:
+          LOG(("  consumer not interested in the entry"));
+          // Do not give this entry to the consumer, it is not interested in us.
+          notWanted = true;
+          break;
+        }
+      }
+    }
+  }
+
+  if (aCallback) {
+    mozilla::MutexAutoUnlock unlock(mLock);
+    InvokeAvailableCallback(aCallback, aReadOnly, notWanted);
+  }
+
+  return true;
+}
+
+void CacheEntry::InvokeAvailableCallback(nsICacheEntryOpenCallback* aCallback,
+                                         bool aReadOnly,
+                                         bool aNotWanted)
+{
+  LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
+    this, StateString(mState), aCallback, aReadOnly, aNotWanted));
+
+  uint32_t const state = mState;
+
+  // When we are here, the entry must be loaded from disk
+  MOZ_ASSERT(state > LOADING || mIsDoomed);
+
+  if (!NS_IsMainThread()) {
+    // Must happen on the main thread :(
+    nsRefPtr<AvailableCallbackRunnable> event =
+      new AvailableCallbackRunnable(this, aCallback, aReadOnly, aNotWanted);
+    NS_DispatchToMainThread(event);
+    return;
+  }
+
+  // This happens only on the main thread / :( /
+
+  if (mIsDoomed || aNotWanted) {
+    LOG(("  doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
+    aCallback->OnCacheEntryAvailable(nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
+    return;
+  }
+
+  if (state == READY) {
+    LOG(("  ready/has-meta, notifying OCEA with entry and NS_OK"));
+    {
+      mozilla::MutexAutoLock lock(mLock);
+      BackgroundOp(Ops::FRECENCYUPDATE);
+    }
+
+    aCallback->OnCacheEntryAvailable(this, false, nullptr, NS_OK);
+    return;
+  }
+
+  if (aReadOnly) {
+    LOG(("  r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
+    aCallback->OnCacheEntryAvailable(nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
+    return;
+  }
+
+  // This is a new or potentially non-valid entry and needs to be fetched first.
+  // The Handle blocks other consumers until the channel
+  // either releases the entry or marks metadata as filled or whole entry valid,
+  // i.e. until MetaDataReady() or SetValid() on the entry is called respectively.
+
+  // Consumer will be responsible to fill or validate the entry metadata and data.
+
+  nsRefPtr<Handle> handle = NewWriteHandle();
+  nsresult rv = aCallback->OnCacheEntryAvailable(handle, state == WRITING, nullptr, NS_OK);
+
+  if (NS_FAILED(rv)) {
+    LOG(("  writing/revalidating failed (0x%08x)", rv));
+
+    // Consumer given a new entry failed to take care of the entry.
+    OnWriterClosed(handle);
+    return;
+  }
+
+  LOG(("  writing/revalidating"));
+}
+
+CacheEntry::Handle* CacheEntry::NewWriteHandle()
+{
+  mozilla::MutexAutoLock lock(mLock);
+
+  BackgroundOp(Ops::FRECENCYUPDATE);
+  return (mWriter = new Handle(this));
+}
+
+void CacheEntry::OnWriterClosed(Handle const* aHandle)
+{
+  LOG(("CacheEntry::OnWriterClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
+
+  nsCOMPtr<nsIOutputStream> outputStream;
+
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    if (mWriter != aHandle) {
+      LOG(("  not the current writer"));
+      return;
+    }
+
+    if (mOutputStream) {
+      // No one took our internal output stream, so there are no data
+      // and output stream has to be open symultaneously with input stream
+      // on this entry again.
+      mHasData = false;
+    }
+
+    outputStream.swap(mOutputStream);
+    mWriter = nullptr;
+
+    if (mState == WRITING) {
+      LOG(("  reverting to state EMPTY - write failed"));
+      mState = EMPTY;
+    }
+    else if (mState == REVALIDATING) {
+      LOG(("  reverting to state READY - reval failed"));
+      mState = READY;
+    }
+
+    InvokeCallbacks();
+  }
+
+  if (outputStream) {
+    LOG(("  abandoning phantom output stream"));
+    outputStream->Close();
+  }
+}
+
+bool CacheEntry::UsingDisk() const
+{
+  CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
+
+  return mUseDisk;
+}
+
+bool CacheEntry::SetUsingDisk(bool aUsingDisk)
+{
+  // Called by the service when this entry is reopen to reflect
+  // demanded storage target.
+
+  if (mState >= READY) {
+    // Don't modify after this entry has been filled.
+    return false;
+  }
+
+  CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
+
+  bool changed = mUseDisk != aUsingDisk;
+  mUseDisk = aUsingDisk;
+  return changed;
+}
+
+uint32_t CacheEntry::GetMetadataMemoryConsumption()
+{
+  NS_ENSURE_SUCCESS(mFileStatus, 0);
+
+  uint32_t size;
+  if (NS_FAILED(mFile->ElementsSize(&size)))
+    return 0;
+
+  return size;
+}
+
+// nsICacheEntry
+
+NS_IMETHODIMP CacheEntry::GetPersistToDisk(bool *aPersistToDisk)
+{
+  // No need to sync when only reading.
+  // When consumer needs to be consistent with state of the memory storage entries
+  // table, then let it use GetUseDisk getter that must be called under the service lock.
+  *aPersistToDisk = mUseDisk;
+  return NS_OK;
+}
+NS_IMETHODIMP CacheEntry::SetPersistToDisk(bool aPersistToDisk)
+{
+  LOG(("CacheEntry::SetPersistToDisk [this=%p, persist=%d]", this, aPersistToDisk));
+
+  if (mState >= READY) {
+    LOG(("  failed, called after filling the entry"));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (mUseDisk == aPersistToDisk)
+    return NS_OK;
+
+  mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
+
+  mUseDisk = aPersistToDisk;
+  CacheStorageService::Self()->RecordMemoryOnlyEntry(
+    this, !aPersistToDisk, false /* don't overwrite */);
+
+  // File persistence is setup just before we open output stream on it.
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey)
+{
+  return mURI->GetAsciiSpec(aKey);
+}
+
+NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount)
+{
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
+}
+
+NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched)
+{
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  return mFile->GetLastFetched(aLastFetched);
+}
+
+NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified)
+{
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  return mFile->GetLastModified(aLastModified);
+}
+
+NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
+{
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  return mFile->GetExpirationTime(aExpirationTime);
+}
+
+NS_IMETHODIMP CacheEntry::SetExpirationTime(uint32_t aExpirationTime)
+{
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  nsresult rv = mFile->SetExpirationTime(aExpirationTime);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Aligned assignment, thus atomic.
+  mSortingExpirationTime = aExpirationTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
+{
+  LOG(("CacheEntry::OpenInputStream [this=%p]", this));
+
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIInputStream> stream;
+  rv = mFile->OpenInputStream(getter_AddRefs(stream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsISeekableStream> seekable =
+    do_QueryInterface(stream, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  if (!mHasData) {
+    // So far output stream on this new entry not opened, do it now.
+    LOG(("  creating phantom output stream"));
+    rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  stream.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval)
+{
+  LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
+
+  nsresult rv;
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  MOZ_ASSERT(mState > EMPTY);
+
+  if (mOutputStream) {
+    LOG(("  giving phantom output stream"));
+    mOutputStream.forget(_retval);
+  }
+  else {
+    rv = OpenOutputStreamInternal(offset, _retval);
+    if (NS_FAILED(rv)) return rv;
+  }
+
+  // Entry considered ready when writer opens output stream.
+  if (mState < READY)
+    mState = READY;
+
+  // Invoke any pending readers now.
+  InvokeCallbacks();
+
+  return NS_OK;
+}
+
+nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
+{
+  LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
+
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  mLock.AssertCurrentThreadOwns();
+
+  if (mIsDoomed) {
+    LOG(("  doomed..."));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  MOZ_ASSERT(mState > LOADING);
+
+  if (!mFile)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  nsresult rv;
+
+  // No need to sync on mUseDisk here, we don't need to be consistent
+  // with content of the memory storage entries hash table.
+  if (!mUseDisk) {
+    rv = mFile->SetMemoryOnly();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsCOMPtr<nsIOutputStream> stream;
+  rv = mFile->OpenOutputStream(getter_AddRefs(stream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsISeekableStream> seekable =
+    do_QueryInterface(stream, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Prevent opening output stream again.
+  mHasData = true;
+
+  stream.swap(*_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
+{
+  *aPredictedDataSize = mPredictedDataSize;
+  return NS_OK;
+}
+NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
+{
+  mPredictedDataSize = aPredictedDataSize;
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+  {
+    mozilla::MutexAutoLock lock(mLock);
+    if (mSecurityInfoLoaded) {
+      NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+      return NS_OK;
+    }
+  }
+
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  char const* info;
+  nsCOMPtr<nsISupports> secInfo;
+  nsresult rv;
+
+  rv = mFile->GetElement("security-info", &info);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (info) {
+    rv = NS_DeserializeObject(nsDependentCString(info),
+                              getter_AddRefs(secInfo));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    mSecurityInfo.swap(secInfo);
+    mSecurityInfoLoaded = true;
+
+    NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+  }
+
+  return NS_OK;
+}
+NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
+{
+  nsresult rv;
+
+  NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
+
+  nsRefPtr<CacheFile> file;
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    mSecurityInfo = aSecurityInfo;
+    mSecurityInfoLoaded = true;
+
+    if (!mFile)
+      return NS_ERROR_NOT_AVAILABLE;
+
+    file = mFile;
+  }
+
+  nsCOMPtr<nsISerializable> serializable =
+    do_QueryInterface(aSecurityInfo);
+  if (aSecurityInfo && !serializable)
+    return NS_ERROR_UNEXPECTED;
+
+  nsCString info;
+  if (serializable) {
+    rv = NS_SerializeToString(serializable, info);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize)
+{
+  NS_ENSURE_ARG(aStorageDataSize);
+
+  int64_t dataSize;
+  nsresult rv = GetDataSize(&dataSize);
+  if (NS_FAILED(rv))
+    return rv;
+
+  *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback)
+{
+  LOG(("CacheEntry::AsyncDoom [this=%p]", this));
+
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    if (mIsDoomed || mDoomCallback)
+      return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
+
+    mIsDoomed = true;
+    mDoomCallback = aCallback;
+    BackgroundOp(Ops::DOOM);
+  }
+
+  // Immediately remove the entry from the storage hash table
+  CacheStorageService::Self()->RemoveEntry(this);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
+{
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  const char *value;
+  nsresult rv = mFile->GetElement(aKey, &value);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!value)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  *aRetval = NS_strdup(value);
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
+{
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  return mFile->SetElement(aKey, aValue);
+}
+
+NS_IMETHODIMP CacheEntry::MetaDataReady()
+{
+  mozilla::MutexAutoLock lock(mLock);
+
+  LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
+
+  MOZ_ASSERT(mState > EMPTY);
+
+  if (mState == WRITING)
+    mState = READY;
+
+  InvokeCallbacks();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::SetValid()
+{
+  LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
+
+  nsCOMPtr<nsIOutputStream> outputStream;
+
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    MOZ_ASSERT(mState > EMPTY);
+
+    mState = READY;
+    mHasData = true;
+
+    InvokeCallbacks();
+
+    outputStream.swap(mOutputStream);
+  }
+
+  if (outputStream) {
+    LOG(("  abandoning phantom output stream"));
+    outputStream->Close();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::Recreate(nsICacheEntry **_retval)
+{
+  LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  nsRefPtr<CacheEntry> newEntry = ReopenTruncated(nullptr);
+  if (newEntry) {
+    nsRefPtr<Handle> handle = newEntry->NewWriteHandle();
+    handle.forget(_retval);
+    return NS_OK;
+  }
+
+  BackgroundOp(Ops::CALLBACKS, true);
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::SetDataSize(uint32_t size)
+{
+  // ?
+  mDataSize = size;
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetDataSize(int64_t *aDataSize)
+{
+  LOG(("CacheEntry::GetDataSize [this=%p]", this));
+  *aDataSize = 0;
+
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    if (!mHasData) {
+      LOG(("  write in progress (no data)"));
+      return NS_ERROR_IN_PROGRESS;
+    }
+  }
+
+  NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
+
+  // mayhemer: TODO Problem with compression?
+  if (!mFile->DataSize(aDataSize)) {
+    LOG(("  write in progress (stream active)"));
+    return NS_ERROR_IN_PROGRESS;
+  }
+
+  LOG(("  size=%lld", *aDataSize));
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::MarkValid()
+{
+  // NOT IMPLEMENTED ACTUALLY
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::MaybeMarkValid()
+{
+  // NOT IMPLEMENTED ACTUALLY
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
+{
+  *aWriteAccess = aWriteAllowed;
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::Close()
+{
+  // NOT IMPLEMENTED ACTUALLY
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetStoragePolicy(nsCacheStoragePolicy *aStoragePolicy)
+{
+  // NOT IMPLEMENTED ACTUALLY
+  return NS_OK;
+}
+NS_IMETHODIMP CacheEntry::SetStoragePolicy(nsCacheStoragePolicy aStoragePolicy)
+{
+  // NOT IMPLEMENTED ACTUALLY
+  return NS_OK;
+}
+
+// nsIRunnable
+
+NS_IMETHODIMP CacheEntry::Run()
+{
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  BackgroundOp(mBackgroundOperations.Grab());
+  return NS_OK;
+}
+
+// Management methods
+
+double CacheEntry::GetFrecency() const
+{
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+  return mFrecency;
+}
+
+uint32_t CacheEntry::GetExpirationTime() const
+{
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+  return mSortingExpirationTime;
+}
+
+bool CacheEntry::IsRegistered() const
+{
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+  return mIsRegistered;
+}
+
+bool CacheEntry::CanRegister() const
+{
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+  return !mIsRegistered && mIsRegistrationAllowed;
+}
+
+void CacheEntry::SetRegistered(bool aRegistered)
+{
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+  MOZ_ASSERT(mIsRegistrationAllowed);
+
+  mIsRegistered = aRegistered;
+
+  if (!aRegistered) // Never allow registration again
+    mIsRegistrationAllowed = false;
+}
+
+bool CacheEntry::Purge(uint32_t aWhat)
+{
+  LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
+
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+  switch (aWhat) {
+  case PURGE_DATA_ONLY_DISK_BACKED:
+  case PURGE_WHOLE_ONLY_DISK_BACKED:
+    // This is an in-memory only entry, don't purge it
+    if (!mUseDisk) {
+      LOG(("  not using disk"));
+      return false;
+    }
+  }
+
+  if (mState == WRITING || mState == LOADING || mFrecency == 0) {
+    // In-progress (write or load) entries should (at least for consistency and from
+    // the logical point of view) stay in memory.
+    // Zero-frecency entries are those which have never been given to any consumer, those
+    // are actually very fresh and should not go just because frecency had not been set
+    // so far.
+    LOG(("  state=%s, frecency=%1.10f", StateString(mState), mFrecency));
+    return false;
+  }
+
+  switch (aWhat) {
+  case PURGE_WHOLE_ONLY_DISK_BACKED:
+  case PURGE_WHOLE:
+    {
+      CacheStorageService::Self()->UnregisterEntry(this);
+      CacheStorageService::Self()->RemoveEntry(this);
+
+      // Entry removed it self from control arrays, return true
+      return true;
+    }
+
+  case PURGE_DATA_ONLY_DISK_BACKED:
+    {
+      NS_ENSURE_SUCCESS(mFileStatus, false);
+
+      mFile->ThrowMemoryCachedData();
+
+      // Entry has been left in control arrays, return false (not purged)
+      return false;
+    }
+  }
+
+  LOG(("  ?"));
+  return false;
+}
+
+void CacheEntry::PurgeAndDoom()
+{
+  LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
+
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+  CacheStorageService::Self()->RemoveEntry(this);
+  DoomAlreadyRemoved();
+}
+
+void CacheEntry::DoomAlreadyRemoved()
+{
+  LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
+
+  mIsDoomed = true;
+
+  if (!CacheStorageService::IsOnManagementThread()) {
+    mozilla::MutexAutoLock lock(mLock);
+
+    BackgroundOp(Ops::DOOM);
+    return;
+  }
+
+  CacheStorageService::Self()->UnregisterEntry(this);
+
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    if (mCallbacks.Length() || mReadOnlyCallbacks.Length()) {
+      // Must force post here since may be indirectly called from
+      // InvokeCallbacks of this entry and we don't want reentrancy here.
+      BackgroundOp(Ops::CALLBACKS, true);
+    }
+  }
+
+  if (NS_SUCCEEDED(mFileStatus)) {
+    nsresult rv = mFile->Doom(mDoomCallback ? this : nullptr);
+    if (NS_SUCCEEDED(rv)) {
+      LOG(("  file doomed"));
+      return;
+    }
+  }
+
+  OnFileDoomed(NS_OK);
+}
+
+void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
+{
+  mLock.AssertCurrentThreadOwns();
+
+  if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
+    if (mBackgroundOperations.Set(aOperations))
+      CacheStorageService::Self()->Dispatch(this);
+
+    LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
+    return;
+  }
+
+  mozilla::MutexAutoUnlock unlock(mLock);
+
+  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
+
+  if (aOperations & Ops::FRECENCYUPDATE) {
+    #ifndef M_LN2
+    #define M_LN2 0.69314718055994530942
+    #endif
+
+    // Half-life is 90 days.
+    static double const half_life = 90.0 * (24 * 60 * 60);
+    // Must convert from seconds to milliseconds since PR_Now() gives usecs.
+    static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
+
+    double now_decay = static_cast<double>(PR_Now()) * decay;
+
+    if (mFrecency == 0) {
+      mFrecency = now_decay;
+    }
+    else {
+      // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
+      // more precise.
+      mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
+    }
+    LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency));
+  }
+
+  if (aOperations & Ops::REGISTER) {
+    LOG(("CacheEntry REGISTER [this=%p]", this));
+
+    CacheStorageService::Self()->RegisterEntry(this);
+  }
+
+  if (aOperations & Ops::DOOM) {
+    LOG(("CacheEntry DOOM [this=%p]", this));
+
+    DoomAlreadyRemoved();
+  }
+
+  if (aOperations & Ops::CALLBACKS) {
+    LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
+
+    mozilla::MutexAutoLock lock(mLock);
+    InvokeCallbacks();
+  }
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheEntry.h
@@ -0,0 +1,301 @@
+/* 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 CacheEntry__h__
+#define CacheEntry__h__
+
+#include "nsICacheEntry.h"
+#include "CacheFile.h"
+
+#include "nsIRunnable.h"
+#include "nsIOutputStream.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntryDoomCallback.h"
+
+#include "nsCOMPtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "nsCOMArray.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Mutex.h"
+
+static inline uint32_t
+PRTimeToSeconds(PRTime t_usec)
+{
+  PRTime usec_per_sec = PR_USEC_PER_SEC;
+  return uint32_t(t_usec /= usec_per_sec);
+}
+
+#define NowInSeconds() PRTimeToSeconds(PR_Now())
+
+class nsIStorageStream;
+class nsIOutputStream;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class CacheStorageService;
+class CacheStorage;
+
+namespace {
+class FrecencyComparator;
+class ExpirationComparator;
+class EvictionRunnable;
+class WalkRunnable;
+}
+
+class CacheEntry : public nsICacheEntry
+                 , public nsIRunnable
+                 , public CacheFileListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICACHEENTRY
+  NS_DECL_NSIRUNNABLE
+
+  CacheEntry(const nsACString& aStorageID, nsIURI* aURI, const nsACString& aEnhanceID,
+             bool aUseDisk);
+
+  void AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags);
+
+public:
+  uint32_t GetMetadataMemoryConsumption();
+  nsCString const &GetStorageID() const { return mStorageID; }
+  nsCString const &GetEnhanceID() const { return mEnhanceID; }
+  nsIURI* GetURI() const { return mURI; }
+  bool UsingDisk() const;
+  bool SetUsingDisk(bool aUsingDisk);
+
+  // Methods for entry management (eviction from memory),
+  // called only on the management thread.
+
+  // TODO make these inline
+  double GetFrecency() const;
+  uint32_t GetExpirationTime() const;
+
+  bool IsRegistered() const;
+  bool CanRegister() const;
+  void SetRegistered(bool aRegistered);
+
+  enum EPurge {
+    PURGE_DATA_ONLY_DISK_BACKED,
+    PURGE_WHOLE_ONLY_DISK_BACKED,
+    PURGE_WHOLE,
+  };
+
+  bool Purge(uint32_t aWhat);
+  void PurgeAndDoom();
+  void DoomAlreadyRemoved();
+
+  nsresult HashingKeyWithStorage(nsACString &aResult);
+  nsresult HashingKey(nsACString &aResult);
+
+  static nsresult HashingKey(nsCSubstring const& aStorageID,
+                             nsCSubstring const& aEnhanceID,
+                             nsIURI* aURI,
+                             nsACString &aResult);
+
+  // Accessed only on the service management thread
+  double mFrecency;
+  uint32_t mSortingExpirationTime;
+
+private:
+  virtual ~CacheEntry();
+
+  // CacheFileListener
+  NS_IMETHOD OnFileReady(nsresult aResult, bool aIsNew);
+  NS_IMETHOD OnFileDoomed(nsresult aResult);
+
+  // Keep the service alive during life-time of an entry
+  nsRefPtr<CacheStorageService> mService;
+
+  // We must monitor when a cache entry whose consumer is responsible
+  // for writing it the first time gets released.  We must then invoke
+  // waiting callbacks to not break the chain.
+  class Handle : public nsICacheEntry
+  {
+  public:
+    Handle(CacheEntry* aEntry);
+    virtual ~Handle();
+
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_FORWARD_NSICACHEENTRY(mEntry->)
+  private:
+    nsRefPtr<CacheEntry> mEntry;
+  };
+
+  // Since OnCacheEntryAvailable must be invoked on the main thread
+  // we need a runnable for it...
+  class AvailableCallbackRunnable : public nsRunnable
+  {
+  public:
+    AvailableCallbackRunnable(CacheEntry* aEntry,
+                              nsICacheEntryOpenCallback* aCallback,
+                              bool aReadOnly,
+                              bool aNotWanted)
+      : mEntry(aEntry), mCallback(aCallback)
+      , mReadOnly(aReadOnly), mNotWanted(aNotWanted) {}
+
+  private:
+    NS_IMETHOD Run()
+    {
+      mEntry->InvokeAvailableCallback(mCallback, mReadOnly, mNotWanted);
+      return NS_OK;
+    }
+
+    nsRefPtr<CacheEntry> mEntry;
+    nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
+    bool mReadOnly : 1;
+    bool mNotWanted : 1;
+  };
+
+  // Since OnCacheEntryDoomed must be invoked on the main thread
+  // we need a runnable for it...
+  class DoomCallbackRunnable : public nsRunnable
+  {
+  public:
+    DoomCallbackRunnable(CacheEntry* aEntry, nsresult aRv)
+      : mEntry(aEntry), mRv(aRv) {}
+
+  private:
+    NS_IMETHOD Run()
+    {
+      nsCOMPtr<nsICacheEntryDoomCallback> callback;
+      {
+        mozilla::MutexAutoLock lock(mEntry->mLock);
+        mEntry->mDoomCallback.swap(callback);
+      }
+
+      if (callback)
+        callback->OnCacheEntryDoomed(mRv);
+      return NS_OK;
+    }
+
+    nsRefPtr<CacheEntry> mEntry;
+    nsresult mRv;
+  };
+
+  // Loads from disk asynchronously
+  bool Load(bool aTruncate, bool aPriority);
+  void OnLoaded();
+
+  void RememberCallback(nsICacheEntryOpenCallback* aCallback, bool aReadOnly);
+  bool PendingCallbacks();
+  void InvokeCallbacks();
+  bool InvokeCallback(nsICacheEntryOpenCallback* aCallback, bool aReadOnly);
+  void InvokeAvailableCallback(nsICacheEntryOpenCallback* aCallback, bool aReadOnly, bool aNotWanted);
+  void InvokeCallbacksMainThread();
+
+  nsresult OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval);
+
+  // When this entry is new and recreated w/o a callback, we need to wrap it
+  // with a handle to detect writing consumer is gone.
+  Handle* NewWriteHandle();
+  void OnWriterClosed(Handle const* aHandle);
+
+  // Schedules a background operation on the management thread.
+  // When executed on the management thread directly, the operation(s)
+  // is (are) executed immediately.
+  void BackgroundOp(uint32_t aOperation, bool aForceAsync = false);
+
+  already_AddRefed<CacheEntry> ReopenTruncated(nsICacheEntryOpenCallback* aCallback);
+  void TransferCallbacks(CacheEntry const& aFromEntry);
+
+  mozilla::Mutex mLock;
+
+  nsCOMArray<nsICacheEntryOpenCallback> mCallbacks, mReadOnlyCallbacks;
+  nsCOMPtr<nsICacheEntryDoomCallback> mDoomCallback;
+
+  nsRefPtr<CacheFile> mFile;
+  nsresult mFileStatus;
+  nsCOMPtr<nsIURI> mURI;
+  nsCString mEnhanceID;
+  nsCString mStorageID;
+
+  // Whether it's allowed to persist the data to disk
+  // Synchronized by the service management lock.
+  // Hence, leave it as a standalone boolean.
+  bool mUseDisk;
+
+  // Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
+  // Left as a standalone flag to not bother with locking (there is no need).
+  bool mIsDoomed;
+
+  // Following flags are all synchronized with the cache entry lock.
+
+  // Whether security info has already been looked up in metadata.
+  bool mSecurityInfoLoaded : 1;
+  // Prevents any callback invocation
+  bool mPreventCallbacks : 1;
+  // Accessed only on the management thread.
+  // Whether this entry is registered in the storage service helper arrays
+  bool mIsRegistered : 1;
+  // After deregistration entry is no allowed to register again
+  bool mIsRegistrationAllowed : 1;
+  // Way around when having a callback that cannot be invoked on non-main thread
+  bool mHasMainThreadOnlyCallback : 1;
+  // true: after load and an existing file, or after output stream has been opened.
+  //       note - when opening an input stream, and this flag is false, output stream
+  //       is open along ; this makes input streams on new entries behave correctly
+  //       when EOF is reached (WOULD_BLOCK is returned).
+  // false: after load and a new file, or dropped to back to false when a writer
+  //        fails to open an output stream.
+  bool mHasData : 1;
+
+#ifdef MOZ_LOGGING
+  static char const * StateString(uint32_t aState);
+#endif
+
+  enum EState {      // transiting to:
+    NOTLOADED = 0,   // -> LOADING | EMPTY
+    LOADING = 1,     // -> EMPTY | READY
+    EMPTY = 2,       // -> WRITING
+    WRITING = 3,     // -> EMPTY | READY
+    READY = 4,       // -> REVALIDATING
+    REVALIDATING = 5 // -> READY
+  };
+
+  // State of this entry.
+  EState mState;
+
+  // If a new (empty) entry is requested to open an input stream before
+  // output stream has been opened, we must open output stream internally
+  // on CacheFile and hold until writer releases the entry or opens the output
+  // stream for read (then we trade him mOutputStream).
+  nsCOMPtr<nsIOutputStream> mOutputStream;
+
+  // Weak reference to the current writter.  There can be more then one
+  // writer at a time and OnWriterClosed() must be processed only for the
+  // current one.
+  Handle* mWriter;
+
+  // Background thread scheduled operation.  Set (under the lock) one
+  // of this flags to tell the background thread what to do.
+  class Ops {
+  public:
+    static uint32_t const REGISTER =          1 << 0;
+    static uint32_t const FRECENCYUPDATE =    1 << 1;
+    static uint32_t const DOOM =              1 << 2;
+    static uint32_t const CALLBACKS =         1 << 3;
+
+    Ops() : mFlags(0) { }
+    uint32_t Grab() { uint32_t flags = mFlags; mFlags = 0; return flags; }
+    bool Set(uint32_t aFlags) { if (mFlags & aFlags) return false; mFlags |= aFlags; return true; }
+  private:
+    uint32_t mFlags;
+  } mBackgroundOperations;
+
+  nsCOMPtr<nsISupports> mSecurityInfo;
+
+  int64_t mPredictedDataSize;
+  uint32_t mDataSize; // ???
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheIOThread.cpp
@@ -0,0 +1,250 @@
+/* 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 "CacheIOThread.h"
+#include "CacheFileIOManager.h"
+
+#include "nsIRunnable.h"
+#include "nsISupportsImpl.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+#include "mozilla/VisualEventTracer.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS1(CacheIOThread, nsIThreadObserver)
+
+CacheIOThread::CacheIOThread()
+: mMonitor("CacheIOThread")
+, mThread(nullptr)
+, mLowestLevelWaiting(LAST_LEVEL)
+, mHasXPCOMEvents(false)
+, mShutdown(false)
+{
+}
+
+CacheIOThread::~CacheIOThread()
+{
+#ifdef DEBUG
+  for (uint32_t level = 0; level < LAST_LEVEL; ++level) {
+    MOZ_ASSERT(!mEventQueue[level].Length());
+  }
+#endif
+}
+
+nsresult CacheIOThread::Init()
+{
+  mThread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this,
+                            PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+                            PR_JOINABLE_THREAD, 128 * 1024);
+  if (!mThread)
+    return NS_ERROR_FAILURE;
+
+  return NS_OK;
+}
+
+nsresult CacheIOThread::Dispatch(nsIRunnable* aRunnable, uint32_t aLevel)
+{
+  NS_ENSURE_ARG(aLevel < LAST_LEVEL);
+
+  MonitorAutoLock lock(mMonitor);
+
+  if (mShutdown && (PR_GetCurrentThread() != mThread))
+    return NS_ERROR_UNEXPECTED;
+
+  mEventQueue[aLevel].AppendElement(aRunnable);
+  if (mLowestLevelWaiting > aLevel)
+    mLowestLevelWaiting = aLevel;
+
+  mMonitor.NotifyAll();
+
+  return NS_OK;
+}
+
+bool CacheIOThread::IsCurrentThread()
+{
+  return mThread == PR_GetCurrentThread();
+}
+
+nsresult CacheIOThread::Shutdown()
+{
+  {
+    MonitorAutoLock lock(mMonitor);
+    mShutdown = true;
+    mMonitor.NotifyAll();
+  }
+
+  PR_JoinThread(mThread);
+  mThread = nullptr;
+
+  return NS_OK;
+}
+
+already_AddRefed<nsIEventTarget> CacheIOThread::Target()
+{
+  nsCOMPtr<nsIEventTarget> target;
+
+  if (mThread)
+  {
+    MonitorAutoLock lock(mMonitor);
+    if (!mXPCOMThread)
+      lock.Wait();
+
+    target = mXPCOMThread;
+  }
+
+  return target.forget();
+}
+
+// static
+void CacheIOThread::ThreadFunc(void* aClosure)
+{
+  PR_SetCurrentThreadName("Cache2 I/O");
+  CacheIOThread* thread = static_cast<CacheIOThread*>(aClosure);
+  thread->ThreadFunc();
+}
+
+void CacheIOThread::ThreadFunc()
+{
+  nsCOMPtr<nsIThreadInternal> threadInternal;
+
+  {
+    MonitorAutoLock lock(mMonitor);
+
+    // This creates nsThread for this PRThread
+    mXPCOMThread = NS_GetCurrentThread();
+
+    threadInternal = do_QueryInterface(mXPCOMThread);
+    if (threadInternal)
+      threadInternal->SetObserver(this);
+
+    lock.NotifyAll();
+
+    static PRIntervalTime const waitTime = PR_MillisecondsToInterval(5000);
+
+    do {
+loopStart:
+      // Reset the lowest level now, so that we can detect a new event on
+      // a lower level (i.e. higher priority) has been scheduled while
+      // executing any previously scheduled event.
+      mLowestLevelWaiting = LAST_LEVEL;
+
+      // Process xpcom events first
+      while (mHasXPCOMEvents) {
+        eventtracer::AutoEventTracer tracer(this, eventtracer::eExec, eventtracer::eDone,
+          "net::cache::io::level(xpcom)");
+
+        mHasXPCOMEvents = false;
+        MonitorAutoUnlock unlock(mMonitor);
+
+        bool processedEvent;
+        nsresult rv;
+        do {
+          rv = mXPCOMThread->ProcessNextEvent(false, &processedEvent);
+        } while (NS_SUCCEEDED(rv) && processedEvent);
+      }
+
+      uint32_t level;
+      for (level = 0; level < LAST_LEVEL; ++level) {
+        if (!mEventQueue[level].Length()) {
+          // no events on this level, go to the next level
+          continue;
+        }
+
+        LoopOneLevel(level);
+
+        // Go to the first (lowest) level again
+        goto loopStart;
+      }
+
+      if (EventsPending())
+        continue;
+
+      lock.Wait(waitTime);
+
+      if (EventsPending())
+        continue;
+
+    } while (!mShutdown);
+
+    MOZ_ASSERT(!EventsPending());
+  } // lock
+
+  if (threadInternal)
+    threadInternal->SetObserver(nullptr);
+}
+
+static const char* const sLevelTraceName[] = {
+  "net::cache::io::level(0)",
+  "net::cache::io::level(1)",
+  "net::cache::io::level(2)",
+  "net::cache::io::level(3)",
+  "net::cache::io::level(4)",
+  "net::cache::io::level(5)",
+  "net::cache::io::level(6)",
+  "net::cache::io::level(7)",
+  "net::cache::io::level(8)",
+  "net::cache::io::level(9)",
+  "net::cache::io::level(10)",
+  "net::cache::io::level(11)",
+  "net::cache::io::level(12)"
+};
+
+void CacheIOThread::LoopOneLevel(uint32_t aLevel)
+{
+  eventtracer::AutoEventTracer tracer(this, eventtracer::eExec, eventtracer::eDone,
+    sLevelTraceName[aLevel]);
+
+  nsTArray<nsRefPtr<nsIRunnable> > events;
+  events.SwapElements(mEventQueue[aLevel]);
+  uint32_t length = events.Length();
+
+  bool returnEvents = false;
+  uint32_t index;
+  {
+    MonitorAutoUnlock unlock(mMonitor);
+
+    for (index = 0; index < length; ++index) {
+      if (EventsPending(aLevel)) {
+        // Somebody scheduled a new event on a lower level, break and harry
+        // to execute it!  Don't forget to return what we haven't exec.
+        returnEvents = true;
+        break;
+      }
+
+      events[index]->Run();
+      events[index] = nullptr;
+    }
+  }
+
+  if (returnEvents)
+    mEventQueue[aLevel].InsertElementsAt(0, events.Elements() + index, length - index);
+}
+
+bool CacheIOThread::EventsPending(uint32_t aLastLevel)
+{
+  return mLowestLevelWaiting < aLastLevel || mHasXPCOMEvents;
+}
+
+NS_IMETHODIMP CacheIOThread::OnDispatchedEvent(nsIThreadInternal *thread)
+{
+  MonitorAutoLock lock(mMonitor);
+  mHasXPCOMEvents = true;
+  lock.Notify();
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheIOThread::OnProcessNextEvent(nsIThreadInternal *thread, bool mayWait, uint32_t recursionDepth)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheIOThread::AfterProcessNextEvent(nsIThreadInternal *thread, uint32_t recursionDepth)
+{
+  return NS_OK;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheIOThread.h
@@ -0,0 +1,69 @@
+/* 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 CacheIOThread__h__
+#define CacheIOThread__h__
+
+#include "nsIThreadInternal.h"
+#include "nsISupportsImpl.h"
+#include "prthread.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Monitor.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace net {
+
+class CacheIOThread : public nsIThreadObserver
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITHREADOBSERVER
+
+  CacheIOThread();
+  virtual ~CacheIOThread();
+
+  enum ELevel {
+    IMMEDIATE,
+    DOOM_PRIORITY,
+    OPEN_PRIORITY,
+    READ_PRIORITY,
+    DOOM,
+    OPEN,
+    READ,
+    OPEN_TRUNCATE,
+    WRITE,
+    CLOSE,
+    EVICT,
+    LAST_LEVEL
+  };
+
+  nsresult Init();
+  nsresult Dispatch(nsIRunnable* aRunnable, uint32_t aLevel);
+  bool IsCurrentThread();
+  nsresult Shutdown();
+  already_AddRefed<nsIEventTarget> Target();
+
+private:
+  static void ThreadFunc(void* aClosure);
+  void ThreadFunc();
+  void LoopOneLevel(uint32_t aLevel);
+  bool EventsPending(uint32_t aLastLevel = LAST_LEVEL);
+
+  mozilla::Monitor mMonitor;
+  PRThread* mThread;
+  nsCOMPtr<nsIThread> mXPCOMThread;
+  uint32_t mLowestLevelWaiting;
+  nsTArray<nsRefPtr<nsIRunnable> > mEventQueue[LAST_LEVEL];
+
+  bool mHasXPCOMEvents;
+  bool mShutdown;
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheLog.cpp
@@ -0,0 +1,30 @@
+/* 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"
+
+namespace mozilla {
+namespace net {
+
+#if defined(PR_LOGGING)
+// Log module for cache2 (2013) cache implementation logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+//    set NSPR_LOG_MODULES=cache2:5
+//    set NSPR_LOG_FILE=nspr.log
+//
+// this enables PR_LOG_DEBUG level information and places all output in
+// the file nspr.log
+PRLogModuleInfo* GetCache2Log()
+{
+  static PRLogModuleInfo *sLog;
+  if (!sLog)
+    sLog = PR_NewLogModule("cache2");
+  return sLog;
+}
+#endif
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheLog.h
@@ -0,0 +1,25 @@
+/* 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 CacheLog__h__
+#define CacheLog__h__
+
+#define FORCE_PR_LOG
+
+#include "prlog.h"
+
+namespace mozilla {
+namespace net {
+
+#if defined(PR_LOGGING)
+extern PRLogModuleInfo* GetCache2Log();
+#define LOG(x)  PR_LOG(GetCache2Log(), PR_LOG_DEBUG, x)
+#else
+#define LOG(x)
+#endif /* PR_LOGGING */
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheObserver.cpp
@@ -0,0 +1,148 @@
+/* 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 "CacheObserver.h"
+
+#include "CacheStorageService.h"
+#include "CacheFileIOManager.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace net {
+
+CacheObserver* CacheObserver::sSelf = nullptr;
+
+static uint32_t const kDefaultMemoryLimit = 50 * 1024; // 50 MB
+uint32_t CacheObserver::sMemoryLimit = kDefaultMemoryLimit;
+
+static uint32_t const kDefaultUseNewCache = 0; // Don't use the new cache by default
+uint32_t CacheObserver::sUseNewCache = kDefaultUseNewCache;
+
+NS_IMPL_ISUPPORTS2(CacheObserver,
+                   nsIObserver,
+                   nsISupportsWeakReference)
+
+nsresult
+CacheObserver::Init()
+{
+  if (sSelf) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (!obs) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  sSelf = new CacheObserver();
+  NS_ADDREF(sSelf);
+
+  obs->AddObserver(sSelf, "prefservice:after-app-defaults", true);
+  obs->AddObserver(sSelf, "profile-do-change", true);
+  obs->AddObserver(sSelf, "profile-before-change", true);
+  obs->AddObserver(sSelf, "xpcom-shutdown", true);
+  obs->AddObserver(sSelf, "last-pb-context-exited", true);
+  obs->AddObserver(sSelf, "memory-pressure", true);
+
+  return NS_OK;
+}
+
+nsresult
+CacheObserver::Shutdown()
+{
+  if (!sSelf) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  NS_RELEASE(sSelf);
+  return NS_OK;
+}
+
+void
+CacheObserver::AttachToPreferences()
+{
+  mozilla::Preferences::AddUintVarCache(
+    &sMemoryLimit, "browser.cache.memory_limit", kDefaultMemoryLimit);
+  mozilla::Preferences::AddUintVarCache(
+    &sUseNewCache, "browser.cache.use_new_backend", kDefaultUseNewCache);
+}
+
+// static
+bool const CacheObserver::UseNewCache()
+{
+  switch (sUseNewCache) {
+    case 0: // use the old cache backend
+      return false;
+
+    case 1: // use the new cache backend
+      return true;
+
+    case 2: // use A/B testing
+    {
+      static bool const sABTest = rand() & 1;
+      return sABTest;
+    }
+  }
+
+  return true;
+}
+
+NS_IMETHODIMP
+CacheObserver::Observe(nsISupports* aSubject,
+                       const char* aTopic,
+                       const PRUnichar* aData)
+{
+  if (!strcmp(aTopic, "prefservice:after-app-defaults")) {
+    CacheFileIOManager::Init();
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "profile-do-change")) {
+    CacheFileIOManager::Init();
+    CacheFileIOManager::OnProfile();
+    AttachToPreferences();
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "profile-before-change")) {
+    nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
+    if (service)
+      service->Shutdown();
+
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "xpcom-shutdown")) {
+    nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
+    if (service)
+      service->Shutdown();
+
+    CacheFileIOManager::Shutdown();
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "last-pb-context-exited")) {
+    nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
+    if (service)
+      service->DropPrivateBrowsingEntries();
+
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "memory-pressure")) {
+    nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
+    if (service)
+      service->PurgeFromMemory(nsICacheStorageService::PURGE_EVERYTHING);
+
+    return NS_OK;
+  }
+
+  return NS_OK;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheObserver.h
@@ -0,0 +1,44 @@
+/* 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 CacheObserver__h__
+#define CacheObserver__h__
+
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+class CacheObserver : public nsIObserver
+                    , public nsSupportsWeakReference
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  virtual ~CacheObserver() {}
+
+  static nsresult Init();
+  static nsresult Shutdown();
+  static CacheObserver* Self() { return sSelf; }
+
+  // Access to preferences
+  static uint32_t const MemoryLimit() // <0.5MB,1024MB>, result in bytes.
+    { return std::max(512U, std::min(1048576U, sMemoryLimit)) << 10; }
+  static bool const UseNewCache();
+
+private:
+  static CacheObserver* sSelf;
+
+  void AttachToPreferences();
+
+  static uint32_t sMemoryLimit;
+  static uint32_t sUseNewCache;
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheStorage.cpp
@@ -0,0 +1,155 @@
+/* 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 "CacheStorage.h"
+#include "CacheStorageService.h"
+#include "CacheEntry.h"
+#include "CacheLog.h"
+
+#include "OldWrappers.h"
+
+#include "nsICacheEntryDoomCallback.h"
+
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS1(CacheStorage, nsICacheStorage)
+
+CacheStorage::CacheStorage(nsILoadContextInfo* aInfo,
+                           bool aAllowDisk,
+                           bool aLookupAppCache)
+: mLoadContextInfo(GetLoadContextInfo(aInfo))
+, mWriteToDisk(aAllowDisk)
+, mLookupAppCache(aLookupAppCache)
+{
+}
+
+CacheStorage::~CacheStorage()
+{
+}
+
+NS_IMETHODIMP CacheStorage::AsyncOpenURI(nsIURI *aURI,
+                                         const nsACString & aIdExtension,
+                                         uint32_t aFlags,
+                                         nsICacheEntryOpenCallback *aCallback)
+{
+  if (!CacheStorageService::Self())
+    return NS_ERROR_NOT_INITIALIZED;
+
+  NS_ENSURE_ARG(aURI);
+  NS_ENSURE_ARG(aCallback);
+
+  nsresult rv;
+
+  bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
+
+  nsCOMPtr<nsIURI> noRefURI;
+  rv = aURI->CloneIgnoringRef(getter_AddRefs(noRefURI));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIApplicationCache> appCache;
+  if (LookupAppCache()) {
+    MOZ_ASSERT(!truncate);
+
+    rv = ChooseApplicationCache(noRefURI, getter_AddRefs(appCache));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (appCache) {
+    nsAutoCString cacheKey;
+    rv = noRefURI->GetAsciiSpec(cacheKey);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsRefPtr<_OldCacheLoad> appCacheLoad =
+      new _OldCacheLoad(cacheKey, aCallback, appCache,
+                        LoadInfo(), WriteToDisk(), aFlags);
+    rv = appCacheLoad->Start();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    LOG(("CacheStorage::AsyncOpenURI loading from appcache"));
+    return NS_OK;
+  }
+
+  nsRefPtr<CacheEntry> entry;
+  rv = CacheStorageService::Self()->AddStorageEntry(
+    this, noRefURI, aIdExtension,
+    true, // create always
+    truncate, // replace any existing one?
+    getter_AddRefs(entry));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // May invoke the callback synchronously
+  entry->AsyncOpen(aCallback, aFlags);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorage::AsyncDoomURI(nsIURI *aURI, const nsACString & aIdExtension,
+                                         nsICacheEntryDoomCallback* aCallback)
+{
+  if (!CacheStorageService::Self())
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsresult rv = CacheStorageService::Self()->DoomStorageEntry(
+    this, aURI, aIdExtension, aCallback);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorage::AsyncEvictStorage(nsICacheEntryDoomCallback* aCallback)
+{
+  if (!CacheStorageService::Self())
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsresult rv = CacheStorageService::Self()->DoomStorageEntries(
+    this, aCallback);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
+                                              bool aVisitEntries)
+{
+  LOG(("CacheStorage::AsyncVisitStorage [this=%p, cb=%p, disk=%d]", this, aVisitor, (bool)mWriteToDisk));
+  if (!CacheStorageService::Self())
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsresult rv = CacheStorageService::Self()->WalkStorageEntries(
+    this, aVisitEntries, aVisitor);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+// Internal
+
+nsresult CacheStorage::ChooseApplicationCache(nsIURI* aURI,
+                                              nsIApplicationCache** aCache)
+{
+  nsresult rv;
+
+  nsCOMPtr<nsIApplicationCacheService> appCacheService =
+    do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString cacheKey;
+  rv = aURI->GetAsciiSpec(cacheKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = appCacheService->ChooseApplicationCache(cacheKey, LoadInfo(), aCache);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheStorage.h
@@ -0,0 +1,59 @@
+/* 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 CacheStorage__h__
+#define CacheStorage__h__
+
+#include "nsICacheStorage.h"
+#include "CacheEntry.h"
+#include "LoadContextInfo.h"
+
+#include "nsRefPtrHashtable.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsILoadContextInfo.h"
+#include "nsIApplicationCache.h"
+#include "nsICacheEntryDoomCallback.h"
+
+class nsIURI;
+class nsIApplicationCache;
+
+namespace mozilla {
+namespace net {
+
+// This dance is needed to make CacheEntryTable declarable-only in headers
+// w/o exporting CacheEntry.h file to make nsNetModule.cpp compilable.
+typedef nsRefPtrHashtable<nsCStringHashKey, CacheEntry> TCacheEntryTable;
+class CacheEntryTable : public TCacheEntryTable { };
+
+class CacheStorage : public nsICacheStorage
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICACHESTORAGE
+
+public:
+  CacheStorage(nsILoadContextInfo* aInfo,
+               bool aAllowDisk,
+               bool aLookupAppCache);
+
+protected:
+  virtual ~CacheStorage();
+
+  nsresult ChooseApplicationCache(nsIURI* aURI, nsIApplicationCache** aCache);
+
+  nsRefPtr<LoadContextInfo> mLoadContextInfo;
+  bool mWriteToDisk : 1;
+  bool mLookupAppCache : 1;
+
+public:
+  nsIApplicationCache* AppCache() const { return nullptr; }
+  nsILoadContextInfo* LoadInfo() const { return mLoadContextInfo; }
+  bool WriteToDisk() const { return mWriteToDisk && !mLoadContextInfo->IsPrivate(); }
+  bool LookupAppCache() const { return mLookupAppCache; }
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -0,0 +1,1440 @@
+/* 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 "CacheStorageService.h"
+#include "CacheFileIOManager.h"
+#include "CacheObserver.h"
+
+#include "nsICacheStorageVisitor.h"
+#include "nsIObserverService.h"
+#include "nsICacheService.h" // for old cache preference
+#include "CacheStorage.h"
+#include "AppCacheStorage.h"
+#include "CacheEntry.h"
+
+#include "OldWrappers.h"
+
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/VisualEventTracer.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+void LoadContextInfoMappingKey(nsAutoCString &key, nsILoadContextInfo* aInfo)
+{
+  /**
+   * This key is used to salt file hashes.  When form of the key is changed
+   * cache entries will fail to find on disk.
+   */
+  key.Append(aInfo->IsPrivate() ? 'P' : '-');
+  key.Append(aInfo->IsAnonymous() ? 'A' : '-');
+  key.Append(':');
+  if (aInfo->AppId() != nsILoadContextInfo::NO_APP_ID) {
+    key.AppendInt(aInfo->AppId());
+  }
+  if (aInfo->IsInBrowserElement()) {
+    key.Append('B');
+  }
+}
+
+void AppendMemoryStorageID(nsAutoCString &key)
+{
+  key.Append('M');
+}
+
+}
+
+// Not defining as static or class member of CacheStorageService since
+// it would otherwise need to include CacheEntry.h and that then would
+// need to be exported to make nsNetModule.cpp compilable.
+typedef nsClassHashtable<nsCStringHashKey, CacheEntryTable>
+        GlobalEntryTables;
+
+/**
+ * Keeps tables of entries.  There is one entries table for each distinct load
+ * context type.  The distinction is based on following load context info states:
+ * <isPrivate|isAnon|appId|inBrowser> which builds a mapping key.
+ *
+ * Thread-safe to access, protected by the service mutex.
+ */
+static GlobalEntryTables* sGlobalEntryTables;
+
+CacheMemoryConsumer::CacheMemoryConsumer()
+: mReportedMemoryConsumption(0)
+{
+}
+
+void
+CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize)
+{
+  if (CacheStorageService::Self())
+    CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
+}
+
+NS_IMPL_ISUPPORTS1(CacheStorageService, nsICacheStorageService)
+
+CacheStorageService* CacheStorageService::sSelf = nullptr;
+
+CacheStorageService::CacheStorageService()
+: mLock("CacheStorageService")
+, mShutdown(false)
+, mMemorySize(0)
+, mPurging(false)
+{
+  CacheFileIOManager::Init();
+
+  MOZ_ASSERT(!sSelf);
+
+  sSelf = this;
+  sGlobalEntryTables = new GlobalEntryTables();
+
+  NS_NewNamedThread("Cache Mngmnt", getter_AddRefs(mThread));
+}
+
+CacheStorageService::~CacheStorageService()
+{
+  LOG(("CacheStorageService::~CacheStorageService"));
+  sSelf = nullptr;
+
+  if (mMemorySize != 0)
+    NS_ERROR("Network cache reported memory consumption is not at 0, probably leaking?");
+}
+
+void CacheStorageService::Shutdown()
+{
+  if (mShutdown)
+    return;
+
+  LOG(("CacheStorageService::Shutdown - start"));
+
+  mShutdown = true;
+
+  nsCOMPtr<nsIRunnable> event =
+    NS_NewRunnableMethod(this, &CacheStorageService::ShutdownBackground);
+
+  if (mThread)
+    mThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+
+  mozilla::MutexAutoLock lock(mLock);
+  sGlobalEntryTables->Clear();
+  delete sGlobalEntryTables;
+  sGlobalEntryTables = nullptr;
+
+  LOG(("CacheStorageService::Shutdown - done"));
+}
+
+void CacheStorageService::ShutdownBackground()
+{
+  MOZ_ASSERT(IsOnManagementThread());
+
+  mFrecencyArray.Clear();
+  mExpirationArray.Clear();
+}
+
+// Internal management methods
+
+namespace { // anon
+
+// EvictionRunnable
+// Responsible for purgin and unregistring entries (loaded) in memory
+
+class EvictionRunnable : public nsRunnable
+{
+public:
+  EvictionRunnable(nsCSubstring const & aContextKey, TCacheEntryTable* aEntries,
+                   bool aUsingDisk,
+                   nsICacheEntryDoomCallback* aCallback)
+    : mContextKey(aContextKey)
+    , mEntries(aEntries)
+    , mCallback(aCallback)
+    , mUsingDisk(aUsingDisk) { }
+
+  NS_IMETHOD Run()
+  {
+    LOG(("EvictionRunnable::Run [this=%p, disk=%d]", this, mUsingDisk));
+    if (CacheStorageService::IsOnManagementThread()) {
+      if (mUsingDisk) {
+        // TODO for non private entries:
+        // - rename/move all files to TRASH, block shutdown
+        // - start the TRASH removal process
+        // - may also be invoked from the main thread...
+      }
+
+      if (mEntries) {
+        // Process only a limited number of entries during a single loop to
+        // prevent block of the management thread.
+        mBatch = 50;
+        mEntries->Enumerate(&EvictionRunnable::EvictEntry, this);
+      }
+
+      // Anything left?  Process in a separate invokation.
+      if (mEntries && mEntries->Count())
+        NS_DispatchToCurrentThread(this);
+      else if (mCallback)
+        NS_DispatchToMainThread(this); // TODO - we may want caller thread
+    }
+    else if (NS_IsMainThread()) {
+      mCallback->OnCacheEntryDoomed(NS_OK);
+    }
+    else {
+      MOZ_ASSERT(false, "Not main or cache management thread");
+    }
+
+    return NS_OK;
+  }
+
+private:
+  virtual ~EvictionRunnable()
+  {
+    if (mCallback)
+      ProxyReleaseMainThread(mCallback);
+  }
+
+  static PLDHashOperator EvictEntry(const nsACString& aKey,
+                                    nsRefPtr<CacheEntry>& aEntry,
+                                    void* aClosure)
+  {
+    EvictionRunnable* evictor = static_cast<EvictionRunnable*>(aClosure);
+
+    LOG(("  evicting entry=%p", aEntry.get()));
+
+    // HACK ...
+    // in-mem-only should only be Purge(WHOLE)'ed
+    // on-disk may use the same technique I think, disk eviction runs independently
+    if (!evictor->mUsingDisk) {
+      // When evicting memory-only entries we have to remove them from
+      // the master table as well.  PurgeAndDoom() enters the service
+      // management lock.
+      aEntry->PurgeAndDoom();
+    }
+    else {
+      // Disk (+memory-only) entries are already removed from the master
+      // hash table, save locking here!
+      aEntry->DoomAlreadyRemoved();
+    }
+
+    if (!--evictor->mBatch)
+      return PLDHashOperator(PL_DHASH_REMOVE | PL_DHASH_STOP);
+
+    return PL_DHASH_REMOVE;
+  }
+
+  nsCString mContextKey;
+  nsAutoPtr<TCacheEntryTable> mEntries;
+  nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
+  uint32_t mBatch;
+  bool mUsingDisk;
+};
+
+// WalkRunnable
+// Responsible to visit the storage and walk all entries on it asynchronously
+
+class WalkRunnable : public nsRunnable
+{
+public:
+  WalkRunnable(nsCSubstring const & aContextKey, bool aVisitEntries,
+               bool aUsingDisk,
+               nsICacheStorageVisitor* aVisitor)
+    : mContextKey(aContextKey)
+    , mCallback(aVisitor)
+    , mSize(0)
+    , mNotifyStorage(true)
+    , mVisitEntries(aVisitEntries)
+    , mUsingDisk(aUsingDisk)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+private:
+  NS_IMETHODIMP Run()
+  {
+    if (CacheStorageService::IsOnManagementThread()) {
+      LOG(("WalkRunnable::Run - collecting [this=%p, disk=%d]", this, (bool)mUsingDisk));
+      // First, walk, count and grab all entries from the storage
+      // TODO
+      // - walk files on disk, when the storage is not private
+      //    - should create representative entries only for the time
+      //      of need
+
+      mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
+
+      if (!CacheStorageService::IsRunning())
+        return NS_ERROR_NOT_INITIALIZED;
+
+      CacheEntryTable* entries;
+      if (sGlobalEntryTables->Get(mContextKey, &entries))
+        entries->EnumerateRead(&WalkRunnable::WalkStorage, this);
+
+      // Next, we dispatch to the main thread
+    }
+    else if (NS_IsMainThread()) {
+      LOG(("WalkRunnable::Run - notifying [this=%p, disk=%d]", this, (bool)mUsingDisk));
+      if (mNotifyStorage) {
+        LOG(("  storage"));
+        // Second, notify overall storage info
+        mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize);
+        if (!mVisitEntries)
+          return NS_OK; // done
+
+        mNotifyStorage = false;
+      }
+      else {
+        LOG(("  entry [left=%d]", mEntryArray.Length()));
+        // Third, notify each entry until depleted.
+        if (!mEntryArray.Length()) {
+          mCallback->OnCacheEntryVisitCompleted();
+          return NS_OK; // done
+        }
+
+        mCallback->OnCacheEntryInfo(mEntryArray[0]);
+        mEntryArray.RemoveElementAt(0);
+
+        // Dispatch to the main thread again
+      }
+    }
+    else {
+      MOZ_ASSERT(false);
+      return NS_ERROR_FAILURE;
+    }
+
+    NS_DispatchToMainThread(this);
+    return NS_OK;
+  }
+
+  virtual ~WalkRunnable()
+  {
+    if (mCallback)
+      ProxyReleaseMainThread(mCallback);
+  }
+
+  static PLDHashOperator
+  WalkStorage(const nsACString& aKey,
+              CacheEntry* aEntry,
+              void* aClosure)
+  {
+    WalkRunnable* walker = static_cast<WalkRunnable*>(aClosure);
+
+    if (!walker->mUsingDisk && aEntry->UsingDisk())
+      return PL_DHASH_NEXT;
+
+    walker->mSize += aEntry->GetMetadataMemoryConsumption();
+
+    int64_t size;
+    if (NS_SUCCEEDED(aEntry->GetDataSize(&size)))
+      walker->mSize += size;
+
+    walker->mEntryArray.AppendElement(aEntry);
+    return PL_DHASH_NEXT;
+  }
+
+  nsCString mContextKey;
+  nsCOMPtr<nsICacheStorageVisitor> mCallback;
+  nsTArray<nsRefPtr<CacheEntry> > mEntryArray;
+
+  uint64_t mSize;
+
+  bool mNotifyStorage : 1;
+  bool mVisitEntries : 1;
+  bool mUsingDisk : 1;
+};
+
+PLDHashOperator CollectPrivateContexts(const nsACString& aKey,
+                                       CacheEntryTable* aTable,
+                                       void* aClosure)
+{
+  if (aKey[0] == 'P') {
+    nsTArray<nsCString>* keys = static_cast<nsTArray<nsCString>*>(aClosure);
+    keys->AppendElement(aKey);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+PLDHashOperator CollectContexts(const nsACString& aKey,
+                                       CacheEntryTable* aTable,
+                                       void* aClosure)
+{
+  nsTArray<nsCString>* keys = static_cast<nsTArray<nsCString>*>(aClosure);
+  keys->AppendElement(aKey);
+
+  return PL_DHASH_NEXT;
+}
+
+} // anon
+
+void CacheStorageService::DropPrivateBrowsingEntries()
+{
+  mozilla::MutexAutoLock lock(mLock);
+
+  if (mShutdown)
+    return;
+
+  nsTArray<nsCString> keys;
+  sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys);
+
+  for (uint32_t i = 0; i < keys.Length(); ++i)
+    DoomStorageEntries(keys[i], true, nullptr);
+}
+
+// Helper methods
+
+nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent)
+{
+  if (!mThread)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  return mThread->Dispatch(aEvent, nsIThread::DISPATCH_NORMAL);
+}
+
+// nsICacheStorageService
+
+NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+                                                      nsICacheStorage * *_retval)
+{
+  NS_ENSURE_ARG(aLoadContextInfo);
+  NS_ENSURE_ARG(_retval);
+
+  nsCOMPtr<nsICacheStorage> storage;
+  if (CacheObserver::UseNewCache()) {
+    storage = new CacheStorage(aLoadContextInfo, false, false);
+  }
+  else {
+    storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
+  }
+
+  storage.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::DiskCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+                                                    bool aLookupAppCache,
+                                                    nsICacheStorage * *_retval)
+{
+  NS_ENSURE_ARG(aLoadContextInfo);
+  NS_ENSURE_ARG(_retval);
+
+  // TODO save some heap granularity - cache commonly used storages.
+
+  nsCOMPtr<nsICacheStorage> storage;
+  if (CacheObserver::UseNewCache()) {
+    storage = new CacheStorage(aLoadContextInfo, true, aLookupAppCache);
+  }
+  else {
+    storage = new _OldStorage(aLoadContextInfo, true, aLookupAppCache, false, nullptr);
+  }
+
+  storage.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::AppCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+                                                   nsIApplicationCache *aApplicationCache,
+                                                   nsICacheStorage * *_retval)
+{
+  NS_ENSURE_ARG(aLoadContextInfo);
+  NS_ENSURE_ARG(_retval);
+
+  nsCOMPtr<nsICacheStorage> storage;
+  if (CacheObserver::UseNewCache()) {
+    // Using classification since cl believes we want to instantiate this method
+    // having the same name as the desired class...
+    storage = new mozilla::net::AppCacheStorage(aLoadContextInfo, aApplicationCache);
+  }
+  else {
+    storage = new _OldStorage(aLoadContextInfo, true, false, true, aApplicationCache);
+  }
+
+  storage.forget(_retval);
+  return NS_OK;
+}
+
+namespace { // anon
+
+class CacheFilesDeletor : public nsRunnable
+                        , public CacheEntriesEnumeratorCallback
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  CacheFilesDeletor(nsICacheEntryDoomCallback* aCallback);
+  ~CacheFilesDeletor();
+
+  nsresult DeleteAll();
+  nsresult DeleteOverLimit();
+  nsresult DeleteDoomed();
+
+private:
+  nsresult Init(CacheFileIOManager::EEnumerateMode aMode);
+  NS_IMETHOD Run();
+  NS_IMETHOD Execute();
+  void Callback();
+  virtual void OnFile(CacheFile* aFile);
+
+  nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
+  nsAutoPtr<CacheEntriesEnumerator> mEnumerator;
+  nsRefPtr<CacheIOThread> mIOThread;
+
+  uint32_t mRunning;
+  enum {
+    ALL,
+    OVERLIMIT,
+    DOOMED
+  } mMode;
+  nsresult mRv;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(CacheFilesDeletor, nsRunnable);
+
+CacheFilesDeletor::CacheFilesDeletor(nsICacheEntryDoomCallback* aCallback)
+: mCallback(aCallback)
+, mRunning(0)
+, mRv(NS_OK)
+{
+  MOZ_COUNT_CTOR(CacheFilesDeletor);
+  MOZ_EVENT_TRACER_WAIT(static_cast<nsRunnable*>(this), "net::cache::deletor");
+}
+
+CacheFilesDeletor::~CacheFilesDeletor()
+{
+  MOZ_COUNT_DTOR(CacheFilesDeletor);
+  MOZ_EVENT_TRACER_DONE(static_cast<nsRunnable*>(this), "net::cache::deletor");
+
+  if (mMode == ALL) {
+    // Now delete the doomed entries if some left.
+    nsRefPtr<CacheFilesDeletor> deletor = new CacheFilesDeletor(mCallback);
+
+    nsRefPtr<nsRunnableMethod<CacheFilesDeletor, nsresult> > event =
+      NS_NewRunnableMethod(deletor.get(), &CacheFilesDeletor::DeleteDoomed);
+    NS_DispatchToMainThread(event);
+  }
+}
+
+nsresult CacheFilesDeletor::DeleteAll()
+{
+  mMode = ALL;
+  return Init(CacheFileIOManager::ENTRIES);
+}
+
+nsresult CacheFilesDeletor::DeleteOverLimit()
+{
+  mMode = OVERLIMIT;
+  return Init(CacheFileIOManager::ENTRIES);
+}
+
+nsresult CacheFilesDeletor::DeleteDoomed()
+{
+  mMode = DOOMED;
+  return Init(CacheFileIOManager::DOOMED);
+}
+
+nsresult CacheFilesDeletor::Init(CacheFileIOManager::EEnumerateMode aMode)
+{
+  nsresult rv;
+
+  rv = CacheFileIOManager::EnumerateEntryFiles(
+    aMode, getter_Transfers(mEnumerator));
+
+  if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) {
+    rv = NS_OK;
+  }
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mIOThread = CacheFileIOManager::IOThread();
+  NS_ENSURE_TRUE(mIOThread, NS_ERROR_NOT_INITIALIZED);
+
+  rv = mIOThread->Dispatch(this, CacheIOThread::EVICT);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+void CacheFilesDeletor::Callback()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+  if (obsSvc) {
+    obsSvc->NotifyObservers(CacheStorageService::Self(),
+                            "cacheservice:empty-cache",
+                            nullptr);
+  }
+
+  if (!mCallback)
+    return;
+
+  nsCOMPtr<nsICacheEntryDoomCallback> callback;
+  callback.swap(mCallback);
+  callback->OnCacheEntryDoomed(mRv);
+}
+
+NS_IMETHODIMP CacheFilesDeletor::Run()
+{
+  if (!mRunning) {
+    MOZ_EVENT_TRACER_EXEC(static_cast<nsRunnable*>(this), "net::cache::deletor");
+  }
+
+  MOZ_EVENT_TRACER_EXEC(static_cast<nsRunnable*>(this), "net::cache::deletor::exec");
+
+  nsresult rv = Execute();
+  if (NS_SUCCEEDED(mRv))
+    mRv = rv;
+
+  if (!mEnumerator || !mEnumerator->HasMore()) {
+    // No enumerator or no more elements means the job is done.
+    mEnumerator = nullptr;
+
+    if (mMode != ALL) {
+      nsRefPtr<nsRunnableMethod<CacheFilesDeletor> > event =
+        NS_NewRunnableMethod(this, &CacheFilesDeletor::Callback);
+      NS_DispatchToMainThread(event);
+    }
+  }
+
+  MOZ_EVENT_TRACER_DONE(static_cast<nsRunnable*>(this), "net::cache::deletor::exec");
+
+  return NS_OK;
+}
+
+nsresult CacheFilesDeletor::Execute()
+{
+  LOG(("CacheFilesDeletor::Execute [this=%p]", this));
+
+  if (!mEnumerator) {
+    // No enumerator means the job is done.
+    return NS_OK;
+  }
+
+  nsresult rv;
+  TimeStamp start;
+
+  switch (mMode) {
+  case OVERLIMIT:
+    // Examine file by file and delete what is considered expired/unused.
+    while (mEnumerator->HasMore()) {
+      rv = mEnumerator->GetNextCacheFile(this);
+      if (NS_FAILED(rv))
+        return rv;
+
+      // Limit up to 5 concurrent file opens
+      if (mRunning >= 5)
+        break;
+
+      ++mRunning;
+    }
+    break;
+
+  case ALL:
+  case DOOMED:
+    // Simply delete all files, don't doom then though the backend
+    start = TimeStamp::NowLoRes();
+
+    while (mEnumerator->HasMore()) {
+      nsCOMPtr<nsIFile> file;
+      rv = mEnumerator->GetNextFile(getter_AddRefs(file));
+      if (NS_FAILED(rv))
+        return rv;
+
+#ifdef PR_LOG
+      nsAutoCString key;
+      file->GetNativeLeafName(key);
+      LOG(("  deleting file with key=%s", key.get()));
+#endif
+
+      rv = file->Remove(false);
+      if (NS_FAILED(rv)) {
+        LOG(("  could not remove the file, probably doomed, rv=0x%08x", rv));
+#if 0
+        // No need to open and doom the file manually since we doom all entries
+        // we currently have loaded in memory.
+        rv = mEnumerator->GetCacheFileFromFile(file, this);
+        if (NS_FAILED(rv))
+          return rv;
+#endif
+      }
+
+      ++mRunning;
+
+      if (!(mRunning % (1 << 5)) && mEnumerator->HasMore()) {
+        TimeStamp now(TimeStamp::NowLoRes());
+#define DELETOR_LOOP_LIMIT_MS 200
+        static TimeDuration const kLimitms = TimeDuration::FromMilliseconds(DELETOR_LOOP_LIMIT_MS);
+        if ((now - start) > kLimitms) {
+          LOG(("  deleted %u files, breaking %dms loop", mRunning, DELETOR_LOOP_LIMIT_MS));
+          rv = mIOThread->Dispatch(this, CacheIOThread::EVICT);
+          return rv;
+        }
+      }
+    }
+
+    break;
+
+  default:
+    MOZ_ASSERT(false);
+  }
+
+  return NS_OK;
+}
+
+void CacheFilesDeletor::OnFile(CacheFile* aFile)
+{
+  LOG(("CacheFilesDeletor::OnFile [this=%p, file=%p]", this, aFile));
+
+  if (!aFile)
+    return;
+
+  MOZ_EVENT_TRACER_EXEC(static_cast<nsRunnable*>(this), "net::cache::deletor::file");
+
+#ifdef PR_LOG
+  nsAutoCString key;
+  aFile->Key(key);
+#endif
+
+  switch (mMode) {
+  case OVERLIMIT:
+    if (mEnumerator->HasMore())
+      mEnumerator->GetNextCacheFile(this);
+
+    // NO BREAK ..so far..
+    // mayhemer TODO - here we should decide based on frecency and exp time
+    // whether to delete the file or not.  Then we have to check the consumption
+    // as well.
+
+  case ALL:
+  case DOOMED:
+    LOG(("  dooming file with key=%s", key.get()));
+    // Uncompromisely delete the file!
+    aFile->Doom(nullptr);
+    break;
+  }
+
+  MOZ_EVENT_TRACER_DONE(static_cast<nsRunnable*>(this), "net::cache::deletor::file");
+}
+
+} // anon
+
+NS_IMETHODIMP CacheStorageService::Clear()
+{
+  nsresult rv;
+
+  if (CacheObserver::UseNewCache()) {
+    {
+      mozilla::MutexAutoLock lock(mLock);
+
+      NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+      nsTArray<nsCString> keys;
+      sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys);
+
+      for (uint32_t i = 0; i < keys.Length(); ++i)
+        DoomStorageEntries(keys[i], true, nullptr);
+    }
+
+    // TODO - Callback can be provided!
+    nsRefPtr<CacheFilesDeletor> deletor = new CacheFilesDeletor(nullptr);
+    rv = deletor->DeleteAll();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    nsCOMPtr<nsICacheService> serv =
+        do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = serv->EvictEntries(nsICache::STORE_ANYWHERE);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat)
+{
+  uint32_t what;
+
+  switch (aWhat) {
+  case PURGE_DISK_DATA_ONLY:
+    what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED;
+    break;
+
+  case PURGE_DISK_ALL:
+    what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED;
+    break;
+
+  case PURGE_EVERYTHING:
+    what = CacheEntry::PURGE_WHOLE;
+    break;
+
+  default:
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIRunnable> event =
+    new PurgeFromMemoryRunnable(this, what);
+
+  return Dispatch(event);
+}
+
+NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget)
+{
+  NS_ENSURE_ARG(aEventTarget);
+
+  if (CacheObserver::UseNewCache()) {
+    nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+    ioTarget.forget(aEventTarget);
+  }
+  else {
+    nsresult rv;
+
+    nsCOMPtr<nsICacheService> serv =
+        do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = serv->GetCacheIOTarget(aEventTarget);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+// Methods used by CacheEntry for management of in-memory structures.
+
+namespace { // anon
+
+class FrecencyComparator
+{
+public:
+  bool Equals(CacheEntry* a, CacheEntry* b) const {
+    return a->GetFrecency() == b->GetFrecency();
+  }
+  bool LessThan(CacheEntry* a, CacheEntry* b) const {
+    return a->GetFrecency() < b->GetFrecency();
+  }
+};
+
+class ExpirationComparator
+{
+public:
+  bool Equals(CacheEntry* a, CacheEntry* b) const {
+    return a->GetExpirationTime() == b->GetExpirationTime();
+  }
+  bool LessThan(CacheEntry* a, CacheEntry* b) const {
+    return a->GetExpirationTime() < b->GetExpirationTime();
+  }
+};
+
+} // anon
+
+void
+CacheStorageService::RegisterEntry(CacheEntry* aEntry)
+{
+  MOZ_ASSERT(IsOnManagementThread());
+
+  if (mShutdown || !aEntry->CanRegister())
+    return;
+
+  LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry));
+
+  mFrecencyArray.InsertElementSorted(aEntry, FrecencyComparator());
+  mExpirationArray.InsertElementSorted(aEntry, ExpirationComparator());
+
+  aEntry->SetRegistered(true);
+}
+
+void
+CacheStorageService::UnregisterEntry(CacheEntry* aEntry)
+{
+  MOZ_ASSERT(IsOnManagementThread());
+
+  if (!aEntry->IsRegistered())
+    return;
+
+  LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry));
+
+  mozilla::DebugOnly<bool> removedFrecency = mFrecencyArray.RemoveElement(aEntry);
+  mozilla::DebugOnly<bool> removedExpiration = mExpirationArray.RemoveElement(aEntry);
+
+  MOZ_ASSERT(mShutdown || (removedFrecency && removedExpiration));
+
+  // Note: aEntry->CanRegister() since now returns false
+  aEntry->SetRegistered(false);
+}
+
+static bool
+AddExactEntry(CacheEntryTable* aEntries,
+              nsCString const& aKey,
+              CacheEntry* aEntry,
+              bool aOverwrite)
+{
+  nsRefPtr<CacheEntry> existingEntry;
+  if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
+    bool equals = existingEntry == aEntry;
+    LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
+    return equals; // Already there...
+  }
+
+  LOG(("AddExactEntry [entry=%p put]", aEntry));
+  aEntries->Put(aKey, aEntry);
+  return true;
+}
+
+static bool
+RemoveExactEntry(CacheEntryTable* aEntries,
+                 nsCString const& aKey,
+                 CacheEntry* aEntry,
+                 bool aOverwrite)
+{
+  nsRefPtr<CacheEntry> existingEntry;
+  if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
+    LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
+    return false; // Already removed...
+  }
+
+  if (!aOverwrite && existingEntry != aEntry) {
+    LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
+    return false; // Already replaced...
+  }
+
+  LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
+  aEntries->Remove(aKey);
+  return true;
+}
+
+void
+CacheStorageService::RemoveEntry(CacheEntry* aEntry)
+{
+  LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
+
+  nsAutoCString entryKey;
+  nsresult rv = aEntry->HashingKey(entryKey);
+  if (NS_FAILED(rv)) {
+    NS_ERROR("aEntry->HashingKey() failed?");
+    return;
+  }
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  if (mShutdown) {
+    LOG(("  after shutdown"));
+    return;
+  }
+
+  CacheEntryTable* entries;
+  if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries))
+    RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
+
+  nsAutoCString memoryStorageID(aEntry->GetStorageID());
+  AppendMemoryStorageID(memoryStorageID);
+
+  if (sGlobalEntryTables->Get(memoryStorageID, &entries))
+    RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
+}
+
+void
+CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry,
+                                           bool aOnlyInMemory,
+                                           bool aOverwrite)
+{
+  LOG(("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, overwrite=%d]",
+    aEntry, aOnlyInMemory, aOverwrite));
+  // This method is responsible to put this entry to a special record hashtable
+  // that contains only entries that are stored in memory.
+  // Keep in mind that every entry, regardless of whether is in-memory-only or not
+  // is always recorded in the storage master hash table, the one identified by
+  // CacheEntry.StorageID().
+
+  mLock.AssertCurrentThreadOwns();
+
+  if (mShutdown) {
+    LOG(("  after shutdown"));
+    return;
+  }
+
+  nsresult rv;
+
+  nsAutoCString entryKey;
+  rv = aEntry->HashingKey(entryKey);
+  if (NS_FAILED(rv)) {
+    NS_ERROR("aEntry->HashingKey() failed?");
+    return;
+  }
+
+  CacheEntryTable* entries = nullptr;
+  nsAutoCString memoryStorageID(aEntry->GetStorageID());
+  AppendMemoryStorageID(memoryStorageID);
+
+  if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) {
+    if (!aOnlyInMemory) {
+      LOG(("  not recorded as memory only"));
+      return;
+    }
+
+    entries = new CacheEntryTable();
+    sGlobalEntryTables->Put(memoryStorageID, entries);
+    LOG(("  new memory-only storage table for %s", memoryStorageID.get()));
+  }
+
+  if (aOnlyInMemory) {
+    AddExactEntry(entries, entryKey, aEntry, aOverwrite);
+  }
+  else {
+    RemoveExactEntry(entries, entryKey, aEntry, aOverwrite);
+  }
+}
+
+void
+CacheStorageService::OnMemoryConsumptionChange(CacheMemoryConsumer* aConsumer,
+                                               uint32_t aCurrentMemoryConsumption)
+{
+  LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]",
+    aConsumer, aCurrentMemoryConsumption));
+
+  uint32_t savedMemorySize = aConsumer->mReportedMemoryConsumption;
+  if (savedMemorySize == aCurrentMemoryConsumption)
+    return;
+
+  // Exchange saved size with current one.
+  aConsumer->mReportedMemoryConsumption = aCurrentMemoryConsumption;
+
+  mMemorySize -= savedMemorySize;
+  mMemorySize += aCurrentMemoryConsumption;
+
+  LOG(("  mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize), aCurrentMemoryConsumption, savedMemorySize));
+
+  // Bypass purging when memory has not grew up significantly
+  if (aCurrentMemoryConsumption <= savedMemorySize)
+    return;
+
+  if (mPurging) {
+    LOG(("  already purging"));
+    return;
+  }
+
+  if (mMemorySize <= CacheObserver::MemoryLimit())
+    return;
+
+  // Throw the oldest data or whole entries away when over certain limits
+  mPurging = true;
+
+  // Must always dipatch, since this can be called under e.g. a CacheFile's lock.
+  nsCOMPtr<nsIRunnable> event =
+    NS_NewRunnableMethod(this, &CacheStorageService::PurgeOverMemoryLimit);
+
+  Dispatch(event);
+}
+
+void
+CacheStorageService::PurgeOverMemoryLimit()
+{
+  MOZ_ASSERT(IsOnManagementThread());
+
+  LOG(("CacheStorageService::PurgeOverMemoryLimit"));
+
+#ifdef MOZ_LOGGING
+  TimeStamp start(TimeStamp::Now());
+#endif
+
+  uint32_t const memoryLimit = CacheObserver::MemoryLimit();
+
+  if (mMemorySize > memoryLimit) {
+    LOG(("  memory data consumption over the limit, abandon expired entries"));
+    PurgeExpired();
+  }
+
+  bool frecencyNeedsSort = true;
+  if (mMemorySize > memoryLimit) {
+    LOG(("  memory data consumption over the limit, abandon disk backed data"));
+    PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_DATA_ONLY_DISK_BACKED);
+  }
+
+  if (mMemorySize > memoryLimit) {
+    LOG(("  metadata consumtion over the limit, abandon disk backed entries"));
+    PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED);
+  }
+
+  if (mMemorySize > memoryLimit) {
+    LOG(("  memory data consumption over the limit, abandon any entry"));
+    PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE);
+  }
+
+  LOG(("  purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
+
+  mPurging = false;
+}
+
+void
+CacheStorageService::PurgeExpired()
+{
+  MOZ_ASSERT(IsOnManagementThread());
+
+  mExpirationArray.Sort(ExpirationComparator());
+  uint32_t now = NowInSeconds();
+
+  uint32_t const memoryLimit = CacheObserver::MemoryLimit();
+
+  for (uint32_t i = 0; mMemorySize > memoryLimit && i < mExpirationArray.Length();) {
+    nsRefPtr<CacheEntry> entry = mExpirationArray[i];
+
+    uint32_t expirationTime = entry->GetExpirationTime();
+    if (expirationTime > 0 && expirationTime <= now) {
+      LOG(("  dooming expired entry=%p, exptime=%u (now=%u)",
+        entry.get(), entry->GetExpirationTime(), now));
+
+      entry->PurgeAndDoom();
+      continue;
+    }
+
+    // not purged, move to the next one
+    ++i;
+  }
+}
+
+void
+CacheStorageService::PurgeByFrecency(bool &aFrecencyNeedsSort, uint32_t aWhat)
+{
+  MOZ_ASSERT(IsOnManagementThread());
+
+  if (aFrecencyNeedsSort) {
+    mFrecencyArray.Sort(FrecencyComparator());
+    aFrecencyNeedsSort = false;
+  }
+
+  uint32_t const memoryLimit = CacheObserver::MemoryLimit();
+
+  for (uint32_t i = 0; mMemorySize > memoryLimit && i < mFrecencyArray.Length();) {
+    nsRefPtr<CacheEntry> entry = mFrecencyArray[i];
+
+    if (entry->Purge(aWhat)) {
+      LOG(("  abandoned (%d), entry=%p, frecency=%1.10f",
+        aWhat, entry.get(), entry->GetFrecency()));
+      continue;
+    }
+
+    // not purged, move to the next one
+    ++i;
+  }
+}
+
+void
+CacheStorageService::PurgeAll(uint32_t aWhat)
+{
+  LOG(("CacheStorageService::PurgeAll aWhat=%d", aWhat));
+  MOZ_ASSERT(IsOnManagementThread());
+
+  for (uint32_t i = 0; i < mFrecencyArray.Length();) {
+    nsRefPtr<CacheEntry> entry = mFrecencyArray[i];
+
+    if (entry->Purge(aWhat)) {
+      LOG(("  abandoned entry=%p", entry.get()));
+      continue;
+    }
+
+    // not purged, move to the next one
+    ++i;
+  }
+}
+
+// Methods exposed to and used by CacheStorage.
+
+nsresult
+CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
+                                     nsIURI* aURI,
+                                     const nsACString & aIdExtension,
+                                     bool aCreateIfNotExist,
+                                     bool aReplace,
+                                     CacheEntry** aResult)
+{
+  NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+  NS_ENSURE_ARG(aStorage);
+
+  nsAutoCString contextKey;
+  LoadContextInfoMappingKey(contextKey, aStorage->LoadInfo());
+
+  return AddStorageEntry(contextKey, aURI, aIdExtension,
+                         aStorage->WriteToDisk(), aCreateIfNotExist, aReplace,
+                         aResult);
+}
+
+nsresult
+CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
+                                     nsIURI* aURI,
+                                     const nsACString & aIdExtension,
+                                     bool aWriteToDisk,
+                                     bool aCreateIfNotExist,
+                                     bool aReplace,
+                                     CacheEntry** aResult)
+{
+  NS_ENSURE_ARG(aURI);
+
+  nsresult rv;
+
+  nsAutoCString entryKey;
+  rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]",
+    entryKey.get(), aContextKey.BeginReading()));
+
+  nsRefPtr<CacheEntry> entry;
+
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+    // Ensure storage table
+    CacheEntryTable* entries;
+    if (!sGlobalEntryTables->Get(aContextKey, &entries)) {
+      entries = new CacheEntryTable();
+      sGlobalEntryTables->Put(aContextKey, entries);
+      LOG(("  new storage entries table for context %s", aContextKey.BeginReading()));
+    }
+
+    bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
+
+    // Check entry that is memory-only is also in related memory-only hashtable.
+    // If not, it has been evicted and we will truncate it ; doom is pending for it,
+    // this consumer just made it sooner then the entry has actually been removed
+    // from the master hash table.
+    // (This can be bypassed when entry is about to be replaced anyway.)
+    if (entryExists && !entry->UsingDisk() && !aReplace) {
+      nsAutoCString memoryStorageID(aContextKey);
+      AppendMemoryStorageID(memoryStorageID);
+      CacheEntryTable* memoryEntries;
+      aReplace = sGlobalEntryTables->Get(memoryStorageID, &memoryEntries) &&
+                 memoryEntries->GetWeak(entryKey) != entry;
+
+#ifdef MOZ_LOGGING
+      if (aReplace) {
+        LOG(("  memory-only entry %p for %s already doomed, replacing", entry.get(), entryKey.get()));
+      }
+#endif
+    }
+
+    // If truncate is demanded, delete and doom the current entry
+    if (entryExists && aReplace) {
+      entries->Remove(entryKey);
+
+      LOG(("  dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(), entryKey.get()));
+      // On purpose called under the lock to prevent races of doom and open on I/O thread
+      entry->DoomAlreadyRemoved();
+
+      entry = nullptr;
+      entryExists = false;
+    }
+
+    if (entryExists && entry->SetUsingDisk(aWriteToDisk)) {
+      RecordMemoryOnlyEntry(entry, !aWriteToDisk, true /* overwrite */);
+    }
+
+    // Ensure entry for the particular URL, if not read/only
+    if (!entryExists && (aCreateIfNotExist || aReplace)) {
+      // Entry is not in the hashtable or has just been truncated...
+      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk);
+      entries->Put(entryKey, entry);
+      LOG(("  new entry %p for %s", entry.get(), entryKey.get()));
+    }
+  }
+
+  entry.forget(aResult);
+  return NS_OK;
+}
+
+namespace { // anon
+
+class CacheEntryDoomByKeyCallback : public CacheFileIOListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
+    : mCallback(aCallback) { }
+  virtual ~CacheEntryDoomByKeyCallback();
+
+private:
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; }
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, nsresult aResult) { return NS_OK; }
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) { return NS_OK; }
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; }
+
+  nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
+};
+
+CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback()
+{
+  if (mCallback)
+    ProxyReleaseMainThread(mCallback);
+}
+
+NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(CacheFileHandle *aHandle,
+                                                        nsresult aResult)
+{
+  if (!mCallback)
+    return NS_OK;
+
+  mCallback->OnCacheEntryDoomed(aResult);
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(CacheEntryDoomByKeyCallback, CacheFileIOListener);
+
+} // anon
+
+nsresult
+CacheStorageService::DoomStorageEntry(CacheStorage const* aStorage,
+                                      nsIURI *aURI,
+                                      const nsACString & aIdExtension,
+                                      nsICacheEntryDoomCallback* aCallback)
+{
+  LOG(("CacheStorageService::DoomStorageEntry"));
+
+  NS_ENSURE_ARG(aStorage);
+  NS_ENSURE_ARG(aURI);
+
+  nsAutoCString contextKey;
+  LoadContextInfoMappingKey(contextKey, aStorage->LoadInfo());
+
+  nsAutoCString entryKey;
+  nsresult rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<CacheEntry> entry;
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+    CacheEntryTable* entries;
+    if (sGlobalEntryTables->Get(contextKey, &entries)) {
+      if (entries->Get(entryKey, getter_AddRefs(entry))) {
+        if (aStorage->WriteToDisk() || !entry->UsingDisk()) {
+          // When evicting from disk storage, purge
+          // When evicting from memory storage and the entry is memory-only, purge
+          LOG(("  purging entry %p for %s [storage use disk=%d, entry use disk=%d]",
+            entry.get(), entryKey.get(), aStorage->WriteToDisk(), entry->UsingDisk()));
+          entries->Remove(entryKey);
+        }
+        else {
+          // Otherwise, leave it
+          LOG(("  leaving entry %p for %s [storage use disk=%d, entry use disk=%d]",
+            entry.get(), entryKey.get(), aStorage->WriteToDisk(), entry->UsingDisk()));
+          entry = nullptr;
+        }
+      }
+    }
+  }
+
+  if (entry) {
+    LOG(("  dooming entry %p for %s", entry.get(), entryKey.get()));
+    return entry->AsyncDoom(aCallback);
+  }
+
+  LOG(("  no entry loaded for %s", entryKey.get()));
+
+  if (aStorage->WriteToDisk()) {
+    nsAutoCString contextKey;
+    LoadContextInfoMappingKey(contextKey, aStorage->LoadInfo());
+
+    rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    LOG(("  dooming file only for %s", entryKey.get()));
+
+    nsRefPtr<CacheEntryDoomByKeyCallback> callback(
+      new CacheEntryDoomByKeyCallback(aCallback));
+    rv = CacheFileIOManager::DoomFileByKey(entryKey, callback);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  if (aCallback)
+    aCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE);
+
+  return NS_OK;
+}
+
+nsresult
+CacheStorageService::DoomStorageEntries(CacheStorage const* aStorage,
+                                        nsICacheEntryDoomCallback* aCallback)
+{
+  LOG(("CacheStorageService::DoomStorageEntries"));
+
+  NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+  NS_ENSURE_ARG(aStorage);
+
+  nsAutoCString contextKey;
+  LoadContextInfoMappingKey(contextKey, aStorage->LoadInfo());
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  return DoomStorageEntries(contextKey, aStorage->WriteToDisk(), aCallback);
+}
+
+nsresult
+CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
+                                        bool aDiskStorage,
+                                        nsICacheEntryDoomCallback* aCallback)
+{
+  mLock.AssertCurrentThreadOwns();
+
+  NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+  nsAutoCString memoryStorageID(aContextKey);
+  AppendMemoryStorageID(memoryStorageID);
+
+  nsAutoPtr<CacheEntryTable> entries;
+  if (aDiskStorage) {
+    LOG(("  dooming disk+memory storage of %s", aContextKey.BeginReading()));
+    // Grab all entries in this storage
+    sGlobalEntryTables->RemoveAndForget(aContextKey, entries);
+    // Just remove the memory-only records table
+    sGlobalEntryTables->Remove(memoryStorageID);
+  }
+  else {
+    LOG(("  dooming memory-only storage of %s", aContextKey.BeginReading()));
+    // Grab the memory-only records table, EvictionRunnable will safely remove
+    // entries one by one from the master hashtable on the background management
+    // thread.  Code at AddStorageEntry ensures a new entry will always replace
+    // memory only entries that EvictionRunnable yet didn't manage to remove.
+    sGlobalEntryTables->RemoveAndForget(memoryStorageID, entries);
+  }
+
+  nsRefPtr<EvictionRunnable> evict = new EvictionRunnable(
+    aContextKey, entries.forget(), aDiskStorage, aCallback);
+
+  return Dispatch(evict);
+}
+
+nsresult
+CacheStorageService::WalkStorageEntries(CacheStorage const* aStorage,
+                                        bool aVisitEntries,
+                                        nsICacheStorageVisitor* aVisitor)
+{
+  LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]", aVisitor, aVisitEntries));
+  NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
+
+  NS_ENSURE_ARG(aStorage);
+
+  nsAutoCString contextKey;
+  LoadContextInfoMappingKey(contextKey, aStorage->LoadInfo());
+
+  nsRefPtr<WalkRunnable> event = new WalkRunnable(
+    contextKey, aVisitEntries, aStorage->WriteToDisk(), aVisitor);
+  return Dispatch(event);
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheStorageService.h
@@ -0,0 +1,222 @@
+/* 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 CacheStorageService__h__
+#define CacheStorageService__h__
+
+#include "nsICacheStorageService.h"
+
+#include "nsClassHashtable.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Atomics.h"
+#include "nsTArray.h"
+
+class nsIURI;
+class nsICacheEntryOpenCallback;
+class nsICacheEntryDoomCallback;
+class nsICacheStorageVisitor;
+class nsIRunnable;
+class nsIThread;
+class nsIEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class CacheStorageService;
+class CacheStorage;
+class CacheEntry;
+class CacheEntryTable;
+
+class CacheMemoryConsumer
+{
+private:
+  friend class CacheStorageService;
+  uint32_t mReportedMemoryConsumption;
+protected:
+  CacheMemoryConsumer();
+  void DoMemoryReport(uint32_t aCurrentSize);
+};
+
+class CacheStorageService : public nsICacheStorageService
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICACHESTORAGESERVICE
+
+  CacheStorageService();
+
+  void Shutdown();
+  void DropPrivateBrowsingEntries();
+
+  static CacheStorageService* Self() { return sSelf; }
+  nsresult Dispatch(nsIRunnable* aEvent);
+  static bool IsOnManagementThread() { return sSelf && NS_GetCurrentThread() == sSelf->mThread; }
+  static bool IsRunning() { return sSelf && !sSelf->mShutdown; }
+  nsIEventTarget* Thread() const { return mThread; }
+  mozilla::Mutex& Lock() { return mLock; }
+
+private:
+  virtual ~CacheStorageService();
+  void ShutdownBackground();
+
+private:
+  // The following methods may only be called on the management
+  // thread.
+  friend class CacheEntry;
+
+  /**
+   * Registers the entry in management ordered arrays, a mechanism
+   * helping with weighted purge of entries.
+   * Management arrays keep hard reference to the entry.  Entry is
+   * responsible to remove it self or the service is responsible to
+   * remove the entry when it's no longer needed.
+   */
+  void RegisterEntry(CacheEntry* aEntry);
+
+  /**
+   * Deregisters the entry from management arrays.  References are
+   * then released.
+   */
+  void UnregisterEntry(CacheEntry* aEntry);
+
+  /**
+   * Removes the entry from the related entry hash table, if still present.
+   */
+  void RemoveEntry(CacheEntry* aEntry);
+
+  /**
+   * Tells the storage service whether this entry is only to be stored in
+   * memory.
+   */
+  void RecordMemoryOnlyEntry(CacheEntry* aEntry,
+                             bool aOnlyInMemory,
+                             bool aOverwrite);
+
+private:
+  // Following methods are thread safe to call.
+  friend class CacheStorage;
+
+  /**
+   * Get, or create when not existing and demanded, an entry for the storage
+   * and uri+id extension.
+   */
+  nsresult AddStorageEntry(CacheStorage const* aStorage,
+                           nsIURI* aURI,
+                           const nsACString & aIdExtension,
+                           bool aCreateIfNotExist,
+                           bool aReplace,
+                           CacheEntry** aResult);
+
+  /**
+   * Removes the entry from the related entry hash table, if still present
+   * and returns it.
+   */
+  nsresult DoomStorageEntry(CacheStorage const* aStorage,
+                            nsIURI* aURI,
+                            const nsACString & aIdExtension,
+                            nsICacheEntryDoomCallback* aCallback);
+
+  /**
+   * Removes and returns entry table for the storage.
+   */
+  nsresult DoomStorageEntries(CacheStorage const* aStorage,
+                              nsICacheEntryDoomCallback* aCallback);
+
+  /**
+   * Walk all entiries beloging to the storage.
+   */
+  nsresult WalkStorageEntries(CacheStorage const* aStorage,
+                              bool aVisitEntries,
+                              nsICacheStorageVisitor* aVisitor);
+
+private:
+  friend class CacheMemoryConsumer;
+
+  /**
+   * When memory consumption of this entry radically changes, this method
+   * is called to reflect the size of allocated memory.  This call may purge
+   * unspecified number of entries from memory (but not from disk).
+   */
+  void OnMemoryConsumptionChange(CacheMemoryConsumer* aConsumer,
+                                 uint32_t aCurrentMemoryConsumption);
+  void PurgeOverMemoryLimit();
+
+private:
+  class PurgeFromMemoryRunnable : public nsRunnable
+  {
+  public:
+    PurgeFromMemoryRunnable(CacheStorageService* aService, uint32_t aWhat)
+      : mService(aService), mWhat(aWhat) { }
+
+  private:
+    virtual ~PurgeFromMemoryRunnable() { }
+
+    NS_IMETHOD Run() {
+      mService->PurgeAll(mWhat);
+      return NS_OK;
+    }
+
+    nsRefPtr<CacheStorageService> mService;
+    uint32_t mWhat;
+  };
+
+  /**
+   * Purges entries from memory based on the frecency ordered array.
+   */
+  void PurgeByFrecency(bool &aFrecencyNeedsSort, uint32_t aWhat);
+  void PurgeExpired();
+  void PurgeAll(uint32_t aWhat);
+
+  nsresult DoomStorageEntries(nsCSubstring const& aContextKey,
+                              bool aDiskStorage,
+                              nsICacheEntryDoomCallback* aCallback);
+  nsresult AddStorageEntry(nsCSubstring const& aContextKey,
+                           nsIURI* aURI,
+                           const nsACString & aIdExtension,
+                           bool aWriteToDisk,
+                           bool aCreateIfNotExist,
+                           bool aReplace,
+                           CacheEntry** aResult);
+
+  static CacheStorageService* sSelf;
+
+  mozilla::Mutex mLock;
+
+  bool mShutdown;
+
+  // The service thread
+  nsCOMPtr<nsIThread> mThread;
+
+  // Accessible only on the service thread
+  nsTArray<nsRefPtr<CacheEntry> > mFrecencyArray;
+  nsTArray<nsRefPtr<CacheEntry> > mExpirationArray;
+  mozilla::Atomic<uint32_t> mMemorySize;
+  bool mPurging;
+};
+
+template<class T>
+void ProxyReleaseMainThread(nsCOMPtr<T> &object)
+{
+  T* release;
+  object.forget(&release);
+
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  NS_ProxyRelease(mainThread, release);
+}
+
+} // net
+} // mozilla
+
+#define NS_CACHE_STORAGE_SERVICE_CID \
+  { 0xea70b098, 0x5014, 0x4e21, \
+  { 0xae, 0xe1, 0x75, 0xe6, 0xb2, 0xc4, 0xb8, 0xe0 } } \
+
+#define NS_CACHE_STORAGE_SERVICE_CONTRACTID \
+  "@mozilla.org/netwerk/cache-storage-service;1"
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/Makefile.in
@@ -0,0 +1,17 @@
+#
+# 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/.
+
+DEPTH     = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+LOCAL_INCLUDES = \
+  -I$(srcdir)/../base/src \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/OldWrappers.cpp
@@ -0,0 +1,866 @@
+// Stuff to link the old imp to the new api - will go away!
+
+#include "OldWrappers.h"
+#include "CacheStorage.h"
+#include "CacheStorageService.h"
+#include "CacheLog.h"
+#include "LoadContextInfo.h"
+
+#include "nsIURI.h"
+#include "nsICacheService.h"
+#include "nsICacheSession.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIStreamTransportService.h"
+#include "nsIFile.h"
+#include "nsICacheEntryDoomCallback.h"
+#include "nsICacheListener.h"
+#include "nsICacheStorageVisitor.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+
+static NS_DEFINE_CID(kStreamTransportServiceCID,
+                     NS_STREAMTRANSPORTSERVICE_CID);
+
+namespace mozilla {
+namespace net {
+
+namespace { // anon
+
+// Fires the doom callback back on the main thread
+// after the cache I/O thread is looped.
+
+class DoomCallbackSynchronizer : public nsRunnable
+{
+public:
+  DoomCallbackSynchronizer(nsICacheEntryDoomCallback* cb) : mCB(cb)
+  {
+    MOZ_COUNT_CTOR(DoomCallbackSynchronizer);
+  }
+  nsresult Dispatch();
+
+private:
+  virtual ~DoomCallbackSynchronizer()
+  {
+    MOZ_COUNT_DTOR(DoomCallbackSynchronizer);
+  }
+
+  NS_DECL_NSIRUNNABLE
+  nsCOMPtr<nsICacheEntryDoomCallback> mCB;
+};
+
+nsresult DoomCallbackSynchronizer::Dispatch()
+{
+  nsresult rv;
+
+  nsCOMPtr<nsICacheService> serv =
+      do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIEventTarget> eventTarget;
+  rv = serv->GetCacheIOTarget(getter_AddRefs(eventTarget));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = eventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP DoomCallbackSynchronizer::Run()
+{
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(this);
+  }
+  else {
+    if (mCB)
+      mCB->OnCacheEntryDoomed(NS_OK);
+  }
+  return NS_OK;
+}
+
+// Receives doom callback from the old API and forwards to the new API
+
+class DoomCallbackWrapper : public nsICacheListener
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICACHELISTENER
+
+  DoomCallbackWrapper(nsICacheEntryDoomCallback* cb) : mCB(cb)
+  {
+    MOZ_COUNT_CTOR(DoomCallbackWrapper);
+  }
+
+private:
+  virtual ~DoomCallbackWrapper()
+  {
+    MOZ_COUNT_DTOR(DoomCallbackWrapper);
+  }
+
+  nsCOMPtr<nsICacheEntryDoomCallback> mCB;
+};
+
+NS_IMPL_ISUPPORTS1(DoomCallbackWrapper, nsICacheListener);
+
+NS_IMETHODIMP DoomCallbackWrapper::OnCacheEntryAvailable(nsICacheEntryDescriptor *descriptor,
+                                                         nsCacheAccessMode accessGranted,
+                                                         nsresult status)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP DoomCallbackWrapper::OnCacheEntryDoomed(nsresult status)
+{
+  if (!mCB)
+    return NS_ERROR_NULL_POINTER;
+
+  mCB->OnCacheEntryDoomed(status);
+  mCB = nullptr;
+  return NS_OK;
+}
+
+// Receives visit callbacks from the old API and forwards it to the new API
+
+class VisitCallbackWrapper : public nsICacheVisitor
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICACHEVISITOR
+
+  VisitCallbackWrapper(char* const deviceID,
+                       nsICacheStorageVisitor* cb,
+                       bool visitEntries)
+  : mCB(cb)
+  , mVisitEntries(visitEntries)
+  , mDeviceID(deviceID)
+  {
+    MOZ_COUNT_CTOR(VisitCallbackWrapper);
+  }
+
+private:
+  virtual ~VisitCallbackWrapper();
+  nsCOMPtr<nsICacheStorageVisitor> mCB;
+  bool mVisitEntries;
+  char* const mDeviceID;
+};
+
+NS_IMPL_ISUPPORTS1(VisitCallbackWrapper, nsICacheVisitor)
+
+VisitCallbackWrapper::~VisitCallbackWrapper()
+{
+  if (mVisitEntries)
+    mCB->OnCacheEntryVisitCompleted();
+
+  MOZ_COUNT_DTOR(VisitCallbackWrapper);
+}
+
+NS_IMETHODIMP VisitCallbackWrapper::VisitDevice(const char * deviceID,
+                                                nsICacheDeviceInfo *deviceInfo,
+                                                bool *_retval)
+{
+  if (!mCB)
+    return NS_ERROR_NULL_POINTER;
+
+  *_retval = false;
+  if (strcmp(deviceID, mDeviceID)) {
+    // Not the device we want to visit
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  uint32_t entryCount;
+  rv = deviceInfo->GetEntryCount(&entryCount);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  uint32_t totalSize;
+  rv = deviceInfo->GetTotalSize(&totalSize);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mCB->OnCacheStorageInfo(entryCount, totalSize);
+  *_retval = mVisitEntries;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP VisitCallbackWrapper::VisitEntry(const char * deviceID,
+                                               nsICacheEntryInfo *entryInfo,
+                                               bool *_retval)
+{
+  MOZ_ASSERT(!strcmp(deviceID, mDeviceID));
+
+  nsRefPtr<_OldCacheEntryWrapper> wrapper = new _OldCacheEntryWrapper(entryInfo);
+  nsresult rv = mCB->OnCacheEntryInfo(wrapper);
+  *_retval = NS_SUCCEEDED(rv);
+
+  return NS_OK;
+}
+
+} // anon
+
+
+// _OldCacheEntryWrapper
+
+_OldCacheEntryWrapper::_OldCacheEntryWrapper(nsICacheEntryDescriptor* desc)
+: mOldDesc(desc), mOldInfo(desc)
+{
+  MOZ_COUNT_CTOR(_OldCacheEntryWrapper);
+  LOG(("Creating _OldCacheEntryWrapper %p for descriptor %p", this, desc));
+}
+
+_OldCacheEntryWrapper::_OldCacheEntryWrapper(nsICacheEntryInfo* info)
+: mOldInfo(info)
+{
+  MOZ_COUNT_CTOR(_OldCacheEntryWrapper);
+  LOG(("Creating _OldCacheEntryWrapper %p for info %p", this, info));
+}
+
+_OldCacheEntryWrapper::~_OldCacheEntryWrapper()
+{
+  MOZ_COUNT_DTOR(_OldCacheEntryWrapper);
+  LOG(("Destroying _OldCacheEntryWrapper %p for descriptor %p", this, mOldInfo.get()));
+}
+
+NS_IMPL_ISUPPORTS1(_OldCacheEntryWrapper, nsICacheEntry)
+
+NS_IMETHODIMP _OldCacheEntryWrapper::AsyncDoom(nsICacheEntryDoomCallback* listener)
+{
+  nsRefPtr<DoomCallbackWrapper> cb = listener
+    ? new DoomCallbackWrapper(listener)
+    : nullptr;
+  return AsyncDoom(cb);
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::GetDataSize(int64_t *aSize)
+{
+  uint32_t size;
+  nsresult rv = GetDataSize(&size);
+  if (NS_FAILED(rv))
+    return rv;
+
+  *aSize = size;
+  return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::GetPersistToDisk(bool *aPersistToDisk)
+{
+  if (!mOldDesc) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
+  nsresult rv;
+
+  nsCacheStoragePolicy policy;
+  rv = mOldDesc->GetStoragePolicy(&policy);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aPersistToDisk = policy != nsICache::STORE_IN_MEMORY;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::SetPersistToDisk(bool aPersistToDisk)
+{
+  if (!mOldDesc) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
+  nsresult rv;
+
+  nsCacheStoragePolicy policy;
+  rv = mOldDesc->GetStoragePolicy(&policy);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (policy == nsICache::STORE_OFFLINE) {
+    return aPersistToDisk
+      ? NS_OK
+      : NS_ERROR_NOT_AVAILABLE;
+  }
+
+  policy = aPersistToDisk
+    ? nsICache::STORE_ON_DISK
+    : nsICache::STORE_IN_MEMORY;
+  rv = mOldDesc->SetStoragePolicy(policy);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::Recreate(nsICacheEntry** aResult)
+{
+  NS_ENSURE_TRUE(mOldDesc, NS_ERROR_NOT_AVAILABLE);
+
+  nsCacheAccessMode mode;
+  nsresult rv = mOldDesc->GetAccessGranted(&mode);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!(mode & nsICache::ACCESS_WRITE))
+    return NS_ERROR_NOT_AVAILABLE;
+
+  LOG(("_OldCacheEntryWrapper::Recreate [this=%p]", this));
+
+  nsCOMPtr<nsICacheEntry> self(this);
+  self.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::OpenInputStream(int64_t offset,
+                                                     nsIInputStream * *_retval)
+{
+  if (offset > PR_UINT32_MAX)
+    return NS_ERROR_INVALID_ARG;
+
+  return OpenInputStream(uint32_t(offset), _retval);
+}
+NS_IMETHODIMP _OldCacheEntryWrapper::OpenOutputStream(int64_t offset,
+                                                      nsIOutputStream * *_retval)
+{
+  if (offset > PR_UINT32_MAX)
+    return NS_ERROR_INVALID_ARG;
+
+  return OpenOutputStream(uint32_t(offset), _retval);
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::MaybeMarkValid()
+{
+  LOG(("_OldCacheEntryWrapper::MaybeMarkValid [this=%p]", this));
+
+  NS_ENSURE_TRUE(mOldDesc, NS_ERROR_NULL_POINTER);
+
+  nsCacheAccessMode mode;
+  nsresult rv = mOldDesc->GetAccessGranted(&mode);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mode & nsICache::ACCESS_WRITE) {
+    LOG(("Marking cache entry valid [entry=%p, descr=%p]", this, mOldDesc));
+    return mOldDesc->MarkValid();
+  }
+
+  LOG(("Not marking read-only cache entry valid [entry=%p, descr=%p]", this, mOldDesc));
+  return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::HasWriteAccess(bool aWriteAllowed_unused, bool *aWriteAccess)
+{
+  NS_ENSURE_TRUE(mOldDesc, NS_ERROR_NULL_POINTER);
+  NS_ENSURE_ARG(aWriteAccess);
+
+  nsCacheAccessMode mode;
+  nsresult rv = mOldDesc->GetAccessGranted(&mode);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aWriteAccess = !!(mode & nsICache::ACCESS_WRITE);
+
+  LOG(("_OldCacheEntryWrapper::HasWriteAccess [this=%p, write-access=%d]", this, *aWriteAccess));
+
+  return NS_OK;
+}
+
+
+namespace { // anon
+
+void
+GetCacheSessionNameForStoragePolicy(
+        nsCacheStoragePolicy storagePolicy,
+        bool isPrivate,
+        uint32_t appId,
+        bool inBrowser,
+        nsACString& sessionName)
+{
+  MOZ_ASSERT(!isPrivate || storagePolicy == nsICache::STORE_IN_MEMORY);
+
+  switch (storagePolicy) {
+    case nsICache::STORE_IN_MEMORY:
+      sessionName.AssignASCII(isPrivate ? "HTTP-memory-only-PB" : "HTTP-memory-only");
+      break;
+    case nsICache::STORE_OFFLINE:
+      sessionName.AssignLiteral("HTTP-offline");
+      break;
+    default:
+      sessionName.AssignLiteral("HTTP");
+      break;
+  }
+  if (appId != nsILoadContextInfo::NO_APP_ID || inBrowser) {
+    sessionName.Append('~');
+    sessionName.AppendInt(appId);
+    sessionName.Append('~');
+    sessionName.AppendInt(inBrowser);
+  }
+}
+
+nsresult
+GetCacheSession(bool aWriteToDisk,
+                nsILoadContextInfo* aLoadInfo,
+                nsIApplicationCache* aAppCache,
+                nsICacheSession** _result)
+{
+  nsresult rv;
+
+  nsCacheStoragePolicy storagePolicy;
+  if (aAppCache)
+    storagePolicy = nsICache::STORE_OFFLINE;
+  else if (!aWriteToDisk || aLoadInfo->IsPrivate())
+    storagePolicy = nsICache::STORE_IN_MEMORY;
+  else
+    storagePolicy = nsICache::STORE_ANYWHERE;
+
+  nsAutoCString clientId;
+  if (aAppCache) {
+    aAppCache->GetClientID(clientId);
+  }
+  else {
+    GetCacheSessionNameForStoragePolicy(
+      storagePolicy,
+      aLoadInfo->IsPrivate(),
+      aLoadInfo->AppId(),
+      aLoadInfo->IsInBrowserElement(),
+      clientId);
+  }
+
+  LOG(("  GetCacheSession for client=%s, policy=%d", clientId.get(), storagePolicy));
+
+  nsCOMPtr<nsICacheService> serv =
+      do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsICacheSession> session;
+  rv = serv->CreateSession(clientId.get(),
+                           storagePolicy,
+                           nsICache::STREAM_BASED,
+                           getter_AddRefs(session));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = session->SetIsPrivate(aLoadInfo->IsPrivate());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = session->SetDoomEntriesIfExpired(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aAppCache) {
+    nsCOMPtr<nsIFile> profileDirectory;
+    aAppCache->GetProfileDirectory(getter_AddRefs(profileDirectory));
+    if (profileDirectory)
+      rv = session->SetProfileDirectory(profileDirectory);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  session.forget(_result);
+  return NS_OK;
+}
+
+} // anon
+
+
+NS_IMPL_ISUPPORTS_INHERITED1(_OldCacheLoad, nsRunnable, nsICacheListener)
+
+_OldCacheLoad::_OldCacheLoad(nsCSubstring const& aCacheKey,
+                             nsICacheEntryOpenCallback* aCallback,
+                             nsIApplicationCache* aAppCache,
+                             nsILoadContextInfo* aLoadInfo,
+                             bool aWriteToDisk,
+                             uint32_t aFlags)
+  : mCacheKey(aCacheKey)
+  , mCallback(aCallback)
+  , mLoadInfo(GetLoadContextInfo(aLoadInfo))
+  , mFlags(aFlags)
+  , mWriteToDisk(aWriteToDisk)
+  , mMainThreadOnly(true)
+  , mNew(true)
+  , mStatus(NS_ERROR_UNEXPECTED)
+  , mRunCount(0)
+  , mAppCache(aAppCache)
+{
+  MOZ_COUNT_CTOR(_OldCacheLoad);
+}
+
+_OldCacheLoad::~_OldCacheLoad()
+{
+  ProxyReleaseMainThread(mAppCache);
+  MOZ_COUNT_DTOR(_OldCacheLoad);
+}
+
+nsresult _OldCacheLoad::Start()
+{
+  LOG(("_OldCacheLoad::Start [this=%p, key=%s]", this, mCacheKey.get()));
+  MOZ_ASSERT(NS_IsMainThread());
+
+  bool mainThreadOnly;
+  if (mCallback && (
+      NS_SUCCEEDED(mCallback->GetMainThreadOnly(&mainThreadOnly)) &&
+      !mainThreadOnly)) {
+    mMainThreadOnly = false;
+  }
+
+  nsresult rv;
+
+  // XXX: Start the cache service; otherwise DispatchToCacheIOThread will
+  // fail.
+  nsCOMPtr<nsICacheService> service =
+    do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+
+  // Ensure the stream transport service gets initialized on the main thread
+  if (NS_SUCCEEDED(rv)) {
+    nsCOMPtr<nsIStreamTransportService> sts =
+      do_GetService(kStreamTransportServiceCID, &rv);
+  }
+
+  if (NS_SUCCEEDED(rv)) {
+    rv = service->GetCacheIOTarget(getter_AddRefs(mCacheThread));
+  }
+
+  if (NS_SUCCEEDED(rv)) {
+    rv = mCacheThread->Dispatch(this, NS_DISPATCH_NORMAL);
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+_OldCacheLoad::Run()
+{
+  LOG(("_OldCacheLoad::Run [this=%p, key=%s, cb=%p]", this, mCacheKey.get(), mCallback.get()));
+
+  nsresult rv;
+
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsICacheSession> session;
+    rv = GetCacheSession(mWriteToDisk, mLoadInfo, mAppCache, getter_AddRefs(session));
+    if (NS_SUCCEEDED(rv)) {
+      // AsyncOpenCacheEntry isn't really async when its called on the
+      // cache service thread.
+
+      nsCacheAccessMode cacheAccess;
+      if (mFlags & nsICacheStorage::OPEN_TRUNCATE)
+        cacheAccess = nsICache::ACCESS_WRITE;
+      else if ((mFlags & nsICacheStorage::OPEN_READONLY) || mAppCache)
+        cacheAccess = nsICache::ACCESS_READ;
+      else
+        cacheAccess = nsICache::ACCESS_READ_WRITE;
+
+      LOG(("  session->AsyncOpenCacheEntry with access=%d", cacheAccess));
+
+      bool bypassBusy = mFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
+      rv = session->AsyncOpenCacheEntry(mCacheKey, cacheAccess, this, bypassBusy);
+
+      if (NS_SUCCEEDED(rv))
+        return NS_OK;
+    }
+
+    // Opening failed, propagate the error to the consumer
+    LOG(("  Opening cache entry failed with rv=0x%08x", rv));
+    mStatus = rv;
+    mNew = false;
+    NS_DispatchToMainThread(this);
+  } else {
+    if (!mCallback) {
+      LOG(("  duplicate call, bypassed"));
+      return NS_OK;
+    }
+
+    if (mMainThreadOnly)
+      Check();
+
+    // break cycles
+    nsCOMPtr<nsICacheEntryOpenCallback> cb = mCallback.forget();
+    mCacheThread = nullptr;
+    nsCOMPtr<nsICacheEntry> entry = mCacheEntry.forget();
+
+    rv = cb->OnCacheEntryAvailable(entry, mNew, mAppCache, mStatus);
+
+    if (NS_FAILED(rv) && entry) {
+      LOG(("  cb->OnCacheEntryAvailable failed with rv=0x%08x", rv));
+      if (mNew)
+        entry->AsyncDoom(nullptr);
+      else
+        entry->Close();
+    }
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+_OldCacheLoad::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
+                                     nsCacheAccessMode access,
+                                     nsresult status)
+{
+  LOG(("_OldCacheLoad::OnCacheEntryAvailable [this=%p, ent=%p, cb=%p, appcache=%p, access=%x]",
+    this, entry, mCallback.get(), mAppCache.get(), access));
+
+  // XXX Bug 759805: Sometimes we will call this method directly from
+  // HttpCacheQuery::Run when AsyncOpenCacheEntry fails, but
+  // AsyncOpenCacheEntry will also call this method. As a workaround, we just
+  // ensure we only execute this code once.
+  NS_ENSURE_TRUE(mRunCount == 0, NS_ERROR_UNEXPECTED);
+  ++mRunCount;
+
+  mCacheEntry = entry ? new _OldCacheEntryWrapper(entry) : nullptr;
+  mStatus = status;
+  mNew = access == nsICache::ACCESS_WRITE;
+
+  if (!mMainThreadOnly)
+    Check();
+
+  return NS_DispatchToMainThread(this);
+}
+
+void
+_OldCacheLoad::Check()
+{
+  if (!mCacheEntry)
+    return;
+
+  if (mNew)
+    return;
+
+  uint32_t result;
+  nsresult rv = mCallback->OnCacheEntryCheck(mCacheEntry, mAppCache, &result);
+  LOG(("  OnCacheEntryCheck result ent=%p, cb=%p, appcache=%p, rv=0x%08x, result=%d",
+    mCacheEntry.get(), mCallback.get(), mAppCache.get(), rv, result));
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("cache check failed");
+  }
+
+  if (result == nsICacheEntryOpenCallback::ENTRY_NOT_WANTED) {
+    mCacheEntry->Close();
+    mCacheEntry = nullptr;
+    mStatus = NS_ERROR_CACHE_KEY_NOT_FOUND;
+  }
+}
+
+NS_IMETHODIMP
+_OldCacheLoad::OnCacheEntryDoomed(nsresult)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsICacheStorage old cache wrapper
+
+NS_IMPL_ISUPPORTS1(_OldStorage, nsICacheStorage)
+
+_OldStorage::_OldStorage(nsILoadContextInfo* aInfo,
+                         bool aAllowDisk,
+                         bool aLookupAppCache,
+                         bool aOfflineStorage,
+                         nsIApplicationCache* aAppCache)
+: mLoadInfo(GetLoadContextInfo(aInfo))
+, mAppCache(aAppCache)
+, mWriteToDisk(aAllowDisk)
+, mLookupAppCache(aLookupAppCache)
+, mOfflineStorage(aOfflineStorage)
+{
+  MOZ_COUNT_CTOR(_OldStorage);
+}
+
+_OldStorage::~_OldStorage()
+{
+  MOZ_COUNT_DTOR(_OldStorage);
+}
+
+NS_IMETHODIMP _OldStorage::AsyncOpenURI(nsIURI *aURI,
+                                        const nsACString & aIdExtension,
+                                        uint32_t aFlags,
+                                        nsICacheEntryOpenCallback *aCallback)
+{
+  NS_ENSURE_ARG(aURI);
+  NS_ENSURE_ARG(aCallback);
+
+#ifdef MOZ_LOGGING
+  nsAutoCString uriSpec;
+  aURI->GetAsciiSpec(uriSpec);
+  LOG(("_OldStorage::AsyncOpenURI [this=%p, uri=%s, ide=%s, flags=%x]",
+    this, uriSpec.get(), aIdExtension.BeginReading(), aFlags));
+#endif
+
+  nsresult rv;
+
+  nsAutoCString cacheKey;
+  rv = AssembleCacheKey(aURI, aIdExtension, cacheKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!mAppCache && (mLookupAppCache || mOfflineStorage)) {
+    rv = ChooseApplicationCache(cacheKey, getter_AddRefs(mAppCache));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsRefPtr<_OldCacheLoad> cacheLoad =
+    new _OldCacheLoad(cacheKey, aCallback, mAppCache,
+                      mLoadInfo, mWriteToDisk, aFlags);
+
+  rv = cacheLoad->Start();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP _OldStorage::AsyncDoomURI(nsIURI *aURI, const nsACString & aIdExtension,
+                                        nsICacheEntryDoomCallback* aCallback)
+{
+  LOG(("_OldStorage::AsyncDoomURI"));
+
+  nsresult rv;
+
+  nsAutoCString cacheKey;
+  rv = AssembleCacheKey(aURI, aIdExtension, cacheKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsICacheSession> session;
+  rv = GetCacheSession(mWriteToDisk, mLoadInfo, mAppCache, getter_AddRefs(session));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<DoomCallbackWrapper> cb = aCallback
+    ? new DoomCallbackWrapper(aCallback)
+    : nullptr;
+  rv = session->DoomEntry(cacheKey, cb);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP _OldStorage::AsyncEvictStorage(nsICacheEntryDoomCallback* aCallback)
+{
+  LOG(("_OldStorage::AsyncEvictStorage"));
+
+  nsresult rv;
+
+  if (!mAppCache && mOfflineStorage) {
+    // Special casing for pure offline storage
+    if (mLoadInfo->AppId() == nsILoadContextInfo::NO_APP_ID &&
+        !mLoadInfo->IsInBrowserElement()) {
+
+      // Clear everything.
+      nsCOMPtr<nsICacheService> serv =
+          do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = serv->EvictEntries(nsICache::STORE_OFFLINE);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    else {
+      // Clear app or inbrowser staff.
+      nsCOMPtr<nsIApplicationCacheService> appCacheService =
+        do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = appCacheService->DiscardByAppId(mLoadInfo->AppId(),
+                                           mLoadInfo->IsInBrowserElement());
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+  else {
+    nsCOMPtr<nsICacheSession> session;
+    rv = GetCacheSession(mWriteToDisk, mLoadInfo, mAppCache, getter_AddRefs(session));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = session->EvictEntries();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (aCallback) {
+    nsRefPtr<DoomCallbackSynchronizer> sync =
+      new DoomCallbackSynchronizer(aCallback);
+    rv = sync->Dispatch();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP _OldStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
+                                             bool aVisitEntries)
+{
+  LOG(("_OldStorage::AsyncVisitStorage"));
+
+  NS_ENSURE_ARG(aVisitor);
+
+  if (mLoadInfo->IsAnonymous()) {
+    // There is no concept of 'anonymous' storage in the old cache
+    // since anon cache entries are stored in 'non-anon' storage
+    // with a special prefix.
+    // Just fake we have 0 items with 0 consumption.  This at least
+    // prevents displaying double size in the advanced section of
+    // the Options dialog.
+    aVisitor->OnCacheStorageInfo(0, 0);
+    if (aVisitEntries)
+      aVisitor->OnCacheEntryVisitCompleted();
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  nsCOMPtr<nsICacheService> serv =
+      do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  char* deviceID;
+  if (mAppCache || mOfflineStorage) {
+    deviceID = const_cast<char*>("offline");
+  } else if (!mWriteToDisk || mLoadInfo->IsPrivate()) {
+    deviceID = const_cast<char*>("memory");
+  } else {
+    deviceID = const_cast<char*>("disk");
+  }
+
+  nsRefPtr<VisitCallbackWrapper> cb = new VisitCallbackWrapper(
+    deviceID, aVisitor, aVisitEntries);
+  rv = serv->VisitEntries(cb);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+// Internal
+
+nsresult _OldStorage::AssembleCacheKey(nsIURI *aURI,
+                                       nsACString const & aIdExtension,
+                                       nsACString & aCacheKey)
+{
+  // Copied from nsHttpChannel::AssembleCacheKey
+
+  aCacheKey.Truncate();
+
+  if (mLoadInfo->IsAnonymous()) {
+    aCacheKey.AssignLiteral("anon&");
+  }
+
+  if (!aIdExtension.IsEmpty()) {
+    aCacheKey.AppendPrintf("id=%s&", aIdExtension.BeginReading());
+  }
+
+  nsresult rv;
+
+  nsCOMPtr<nsIURI> noRefURI;
+  rv = aURI->CloneIgnoringRef(getter_AddRefs(noRefURI));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString uriSpec;
+  rv = noRefURI->GetAsciiSpec(uriSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!aCacheKey.IsEmpty()) {
+    aCacheKey.AppendLiteral("uri=");
+  }
+  aCacheKey.Append(uriSpec);
+
+  return NS_OK;
+}
+
+nsresult _OldStorage::ChooseApplicationCache(nsCSubstring const &cacheKey,
+                                             nsIApplicationCache** aCache)
+{
+  nsresult rv;
+
+  nsCOMPtr<nsIApplicationCacheService> appCacheService =
+    do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = appCacheService->ChooseApplicationCache(cacheKey, mLoadInfo, aCache);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/OldWrappers.h
@@ -0,0 +1,120 @@
+// Stuff to link the old imp to the new api - will go away!
+
+#ifndef OLDWRAPPERS__H__
+#define OLDWRAPPERS__H__
+
+#include "nsICacheEntry.h"
+#include "nsICacheListener.h"
+#include "nsICacheStorage.h"
+
+#include "nsCOMPtr.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntryDescriptor.h"
+#include "nsThreadUtils.h"
+
+class nsIURI;
+class nsICacheEntryOpenCallback;
+class nsIApplicationCache;
+class nsILoadContextInfo;
+
+namespace mozilla { namespace net {
+
+class CacheStorage;
+
+class _OldCacheEntryWrapper : public nsICacheEntry
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_FORWARD_SAFE_NSICACHEENTRYDESCRIPTOR(mOldDesc)
+  NS_FORWARD_NSICACHEENTRYINFO(mOldInfo->)
+
+  NS_IMETHOD AsyncDoom(nsICacheEntryDoomCallback* listener);
+  NS_IMETHOD GetPersistToDisk(bool *aPersistToDisk);
+  NS_IMETHOD SetPersistToDisk(bool aPersistToDisk);
+  NS_IMETHOD SetValid() { return NS_OK; }
+  NS_IMETHOD MetaDataReady() { return NS_OK; }
+  NS_IMETHOD Recreate(nsICacheEntry**);
+  NS_IMETHOD GetDataSize(int64_t *size);
+  NS_IMETHOD OpenInputStream(int64_t offset, nsIInputStream * *_retval);
+  NS_IMETHOD OpenOutputStream(int64_t offset, nsIOutputStream * *_retval);
+  NS_IMETHOD MaybeMarkValid();
+  NS_IMETHOD HasWriteAccess(bool aWriteOnly, bool *aWriteAccess);
+
+  _OldCacheEntryWrapper(nsICacheEntryDescriptor* desc);
+  _OldCacheEntryWrapper(nsICacheEntryInfo* info);
+
+  virtual ~_OldCacheEntryWrapper();
+
+private:
+  _OldCacheEntryWrapper() MOZ_DELETE;
+  nsICacheEntryDescriptor* mOldDesc; // ref holded in mOldInfo
+  nsCOMPtr<nsICacheEntryInfo> mOldInfo;
+};
+
+
+class _OldCacheLoad : public nsRunnable
+                    , public nsICacheListener
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_NSICACHELISTENER
+
+  _OldCacheLoad(nsCSubstring const& aCacheKey,
+                nsICacheEntryOpenCallback* aCallback,
+                nsIApplicationCache* aAppCache,
+                nsILoadContextInfo* aLoadInfo,
+                bool aWriteToDisk,
+                uint32_t aFlags);
+  virtual ~_OldCacheLoad();
+
+  nsresult Start();
+
+private:
+  void Check();
+
+  nsCOMPtr<nsIEventTarget> mCacheThread;
+
+  nsCString mCacheKey;
+  nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
+  nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+  uint32_t mFlags;
+
+  bool const mWriteToDisk : 1;
+  bool mMainThreadOnly : 1;
+  bool mNew : 1;
+
+  nsCOMPtr<nsICacheEntry> mCacheEntry;
+  nsresult mStatus;
+  uint32_t mRunCount;
+  nsCOMPtr<nsIApplicationCache> mAppCache;
+};
+
+
+class _OldStorage : public nsICacheStorage
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICACHESTORAGE
+
+public:
+  _OldStorage(nsILoadContextInfo* aInfo,
+              bool aAllowDisk,
+              bool aLookupAppCache,
+              bool aOfflineStorage,
+              nsIApplicationCache* aAppCache);
+
+private:
+  virtual ~_OldStorage();
+  nsresult AssembleCacheKey(nsIURI *aURI, nsACString const & aIdExtension, nsACString & _result);
+  nsresult ChooseApplicationCache(nsCSubstring const &cacheKey, nsIApplicationCache** aCache);
+
+  nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+  nsCOMPtr<nsIApplicationCache> mAppCache;
+  bool const mWriteToDisk : 1;
+  bool const mLookupAppCache : 1;
+  bool const mOfflineStorage : 1;
+};
+
+}}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/moz.build
@@ -0,0 +1,51 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+    'nsICacheEntry.idl',
+    'nsICacheEntryDoomCallback.idl',
+    'nsICacheEntryOpenCallback.idl',
+    'nsICacheStorage.idl',
+    'nsICacheStorageService.idl',
+    'nsICacheStorageVisitor.idl',
+]
+
+XPIDL_MODULE = 'necko_cache2'
+
+MODULE = 'nkcache2'
+
+EXPORTS += [
+    'CacheObserver.h',
+    'CacheStorageService.h',
+]
+
+CPP_SOURCES += [
+    'AppCacheStorage.cpp',
+    'CacheEntriesEnumerator.cpp',
+    'CacheEntry.cpp',
+    'CacheFile.cpp',
+    'CacheFileChunk.cpp',
+    'CacheFileMetadata.cpp',
+    'CacheFileInputStream.cpp',
+    'CacheFileIOManager.cpp',
+    'CacheFileOutputStream.cpp',
+    'CacheHashUtils.cpp',
+    'CacheIOThread.cpp',
+    'CacheLog.cpp',
+    'CacheObserver.cpp',
+    'CacheStorage.cpp',
+    'CacheStorageService.cpp',
+    'OldWrappers.cpp',
+]
+
+LIBRARY_NAME = 'nkcache2_s'
+
+FAIL_ON_WARNINGS = True
+
+LIBXUL_LIBRARY = True
+
+MSVC_ENABLE_PGO = True
+
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -0,0 +1,203 @@
+/* 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/. */
<