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 162892 0c91d9aa9476a9c2c6eb01e6142267d7987977e2
parent 162891 7609a7ace997dac3c643e61200a21a34d76a3663
child 162893 4d66cd80b6be17120ae9aabc93dbcb6ed9c161db
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs913807
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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'
+