Bug 442806: Use seperate, versioned caches for offline apps. r+sr=bz
☠☠ backed out by e376bc87ac4a ☠ ☠
authorDave Camp <dcamp@mozilla.com>
Sun, 24 Aug 2008 21:51:19 -0700
changeset 18361 ebafb9df6ce0aaf17bb68508ceded54ce15114c1
parent 18360 90bc3d600db13fa8809c7d3091dfef95ef816e59
child 18362 e376bc87ac4ac55653d08eae143ceeff73f0c2bf
push idunknown
push userunknown
push dateunknown
bugs442806
milestone1.9.1a2pre
Bug 442806: Use seperate, versioned caches for offline apps. r+sr=bz
browser/base/content/browser.js
browser/components/preferences/advanced.js
content/base/src/nsContentSink.cpp
content/base/src/nsContentUtils.cpp
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
docshell/base/nsDocShell.cpp
dom/src/offline/Makefile.in
dom/src/offline/nsDOMOfflineResourceList.cpp
dom/src/offline/nsDOMOfflineResourceList.h
dom/tests/mochitest/ajax/offline/offlineTests.js
dom/tests/mochitest/ajax/offline/test_changingManifest.html
dom/tests/mochitest/ajax/offline/test_simpleManifest.html
netwerk/base/public/Makefile.in
netwerk/base/public/nsIApplicationCache.idl
netwerk/base/public/nsIApplicationCacheContainer.idl
netwerk/base/public/nsIApplicationCacheService.idl
netwerk/base/public/nsINetUtil.idl
netwerk/base/public/nsNetUtil.h
netwerk/base/src/nsIOService.cpp
netwerk/base/src/nsIOService.h
netwerk/build/Makefile.in
netwerk/build/nsNetCID.h
netwerk/build/nsNetModule.cpp
netwerk/cache/public/Makefile.in
netwerk/cache/public/nsICacheService.idl
netwerk/cache/public/nsIOfflineCacheSession.idl
netwerk/cache/src/nsCacheService.cpp
netwerk/cache/src/nsCacheService.h
netwerk/cache/src/nsCacheSession.cpp
netwerk/cache/src/nsCacheSession.h
netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
netwerk/cache/src/nsDiskCacheDeviceSQL.h
netwerk/protocol/http/src/nsHttpChannel.cpp
netwerk/protocol/http/src/nsHttpChannel.h
uriloader/prefetch/nsIOfflineCacheUpdate.idl
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5405,24 +5405,18 @@ var OfflineApps = {
              getService(Ci.nsIPermissionManager);
     pm.add(aURI, "offline-app",
            Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
   },
 
   // XXX: duplicated in preferences/advanced.js
   _getOfflineAppUsage: function (host)
   {
-    var cacheService = Components.classes["@mozilla.org/network/cache-service;1"].
-                       getService(Components.interfaces.nsICacheService);
-    var cacheSession = cacheService.createSession("HTTP-offline",
-                                                  Components.interfaces.nsICache.STORE_OFFLINE,
-                                                  true).
-                       QueryInterface(Components.interfaces.nsIOfflineCacheSession);
-    var usage = cacheSession.getDomainUsage(host);
-
+    // XXX Bug 442810: include offline cache usage.
+    var usage = 0;
     var storageManager = Components.classes["@mozilla.org/dom/storagemanager;1"].
                          getService(Components.interfaces.nsIDOMStorageManager);
     usage += storageManager.getUsage(host);
 
     return usage;
   },
 
   _checkUsage: function(aURI) {
--- a/browser/components/preferences/advanced.js
+++ b/browser/components/preferences/advanced.js
@@ -211,23 +211,18 @@ var gAdvancedPane = {
     document.documentElement.openWindow("Browser:Permissions",
                                         "chrome://browser/content/preferences/permissions.xul",
                                         "", params);
   },
 
   // XXX: duplicated in browser.js
   _getOfflineAppUsage: function (host)
   {
-    var cacheService = Components.classes["@mozilla.org/network/cache-service;1"].
-                       getService(Components.interfaces.nsICacheService);
-    var cacheSession = cacheService.createSession("HTTP-offline",
-                                                  Components.interfaces.nsICache.STORE_OFFLINE,
-                                                  true).
-                       QueryInterface(Components.interfaces.nsIOfflineCacheSession);
-    var usage = cacheSession.getDomainUsage(host);
+    // XXX Bug 442710: include offline cache usage.
+    var usage = 0;
 
     var storageManager = Components.classes["@mozilla.org/dom/storagemanager;1"].
                          getService(Components.interfaces.nsIDOMStorageManager);
     usage += storageManager.getUsage(host);
 
     return usage;
   },
 
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -66,16 +66,18 @@
 #include "nsIContentViewer.h"
 #include "nsIAtom.h"
 #include "nsGkAtoms.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsNetCID.h"
 #include "nsIOfflineCacheUpdate.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheContainer.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIDOMLoadStatus.h"
 #include "nsICookieService.h"
 #include "nsIPrompt.h"
 #include "nsServiceManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsParserUtils.h"
 #include "nsCRT.h"
@@ -876,16 +878,34 @@ nsContentSink::ProcessOfflineManifest(ns
     return;
   }
 
   // Only update if the document has permission to use offline APIs.
   if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
     return;
   }
 
+  // XXX: at this point in the spec there is an algorithm for
+  // confirming whether the cache that was selected at load time was
+  // the proper application cache for this document.  This will
+  // be implemented in a separate patch;  For now just assume that we
+  // chose an acceptable application cache.
+
+  nsCOMPtr<nsIApplicationCacheContainer> channelContainer =
+    do_QueryInterface(mDocument->GetChannel());
+
+  nsCOMPtr<nsIApplicationCacheContainer> docContainer =
+    do_QueryInterface(mDocument);
+
+  if (channelContainer && docContainer) {
+    nsCOMPtr<nsIApplicationCache> appCache;
+    channelContainer->GetApplicationCache(getter_AddRefs(appCache));
+    docContainer->SetApplicationCache(appCache);
+  }
+
   nsCOMPtr<nsIURI> manifestURI;
   nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI),
                                             manifestSpec, mDocument,
                                             mDocumentURI);
   if (!manifestURI) {
     return;
   }
 
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -799,51 +799,17 @@ nsContentUtils::GetOfflineAppManifest(ns
   nsContentUtils::NewURIWithDocumentCharset(aURI, manifestSpec,
                                             topDoc, topDoc->GetBaseURI());
 }
 
 /* static */
 PRBool
 nsContentUtils::OfflineAppAllowed(nsIURI *aURI)
 {
-  nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
-  if (!innerURI)
-    return PR_FALSE;
-
-  // only http and https applications can use offline APIs.
-  PRBool match;
-  nsresult rv = innerURI->SchemeIs("http", &match);
-  NS_ENSURE_SUCCESS(rv, PR_FALSE);
-
-  if (!match) {
-    rv = innerURI->SchemeIs("https", &match);
-    NS_ENSURE_SUCCESS(rv, PR_FALSE);
-    if (!match) {
-      return PR_FALSE;
-    }
-  }
-
-  nsCOMPtr<nsIPermissionManager> permissionManager =
-    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
-  if (!permissionManager) {
-    return PR_FALSE;
-  }
-
-  PRUint32 perm;
-  permissionManager->TestExactPermission(innerURI, "offline-app", &perm);
-
-  if (perm == nsIPermissionManager::UNKNOWN_ACTION) {
-    return GetBoolPref("offline-apps.allow_by_default");
-  }
-
-  if (perm == nsIPermissionManager::DENY_ACTION) {
-    return PR_FALSE;
-  }
-
-  return PR_TRUE;
+  return NS_OfflineAppAllowed(aURI, sPrefBranch);
 }
 
 // static
 void
 nsContentUtils::Shutdown()
 {
   sInitialized = PR_FALSE;
 
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1138,16 +1138,17 @@ NS_INTERFACE_TABLE_HEAD(nsDocument)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMNode)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsPIDOMEventTarget)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOM3Node)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOM3Document)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsISupportsWeakReference)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIRadioGroupContainer)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIMutationObserver)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMNodeSelector)
+    NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIApplicationCacheContainer)
     // nsNodeSH::PreCreate() depends on the identity pointer being the
     // same as nsINode (which nsIDocument inherits), so if you change
     // the below line, make sure nsNodeSH::PreCreate() still does the
     // right thing!
     NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsDocument, nsISupports, nsIDocument)
   NS_INTERFACE_TABLE_END
   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsDocument)
   if (aIID.Equals(NS_GET_IID(nsIDOMXPathEvaluator)) ||
@@ -1949,16 +1950,32 @@ nsDocument::GetPrincipal()
 
 void
 nsDocument::SetPrincipal(nsIPrincipal *aNewPrincipal)
 {
   mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
 }
 
 NS_IMETHODIMP
+nsDocument::GetApplicationCache(nsIApplicationCache **aApplicationCache)
+{
+  NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::SetApplicationCache(nsIApplicationCache *aApplicationCache)
+{
+  mApplicationCache = aApplicationCache;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocument::GetContentType(nsAString& aContentType)
 {
   CopyUTF8toUTF16(mContentType, aContentType);
 
   return NS_OK;
 }
 
 void
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -88,16 +88,18 @@
 #include "nsIRequest.h"
 #include "nsILoadGroup.h"
 #include "nsTObserverArray.h"
 #include "nsStubMutationObserver.h"
 #include "nsIChannel.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsContentList.h"
 #include "nsGkAtoms.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheContainer.h"
 
 // Put these here so all document impls get them automatically
 #include "nsHTMLStyleSheet.h"
 #include "nsIHTMLCSSStyleSheet.h"
 
 #include "nsStyleSet.h"
 #include "nsXMLEventsManager.h"
 #include "pldhash.h"
@@ -403,16 +405,17 @@ class nsDocument : public nsIDocument,
                    public nsIDOM3Document,
                    public nsSupportsWeakReference,
                    public nsIDOMEventTarget,
                    public nsIDOM3EventTarget,
                    public nsIDOMNSEventTarget,
                    public nsIScriptObjectPrincipal,
                    public nsIRadioGroupContainer,
                    public nsIDOMNodeSelector,
+                   public nsIApplicationCacheContainer,
                    public nsStubMutationObserver
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   virtual void Reset(nsIChannel *aChannel, nsILoadGroup *aLoadGroup);
   virtual void ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
                           nsIPrincipal* aPrincipal);
@@ -753,16 +756,19 @@ public:
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
 
   // nsIScriptObjectPrincipal
   virtual nsIPrincipal* GetPrincipal();
 
+  // nsIApplicationCacheContainer
+  NS_DECL_NSIAPPLICATIONCACHECONTAINER
+
   virtual nsresult Init();
   
   virtual nsresult AddXMLEventsContent(nsIContent * aXMLEventsElement);
 
   virtual nsresult CreateElem(nsIAtom *aName, nsIAtom *aPrefix,
                               PRInt32 aNamespaceID,
                               PRBool aDocumentDefaultType,
                               nsIContent **aResult);
@@ -1022,16 +1028,20 @@ protected:
 
   nsCOMPtr<nsIScriptEventManager> mScriptEventManager;
 
   nsString mBaseTarget;
 
   // Our update nesting level
   PRUint32 mUpdateNestLevel;
 
+  // The application cache that this document is associated with, if
+  // any.  This can change during the lifetime of the document.
+  nsCOMPtr<nsIApplicationCache> mApplicationCache;
+
 private:
   friend class nsUnblockOnloadEvent;
 
   void PostUnblockOnloadEvent();
   void DoUnblockOnload();
 
   /**
    * See if aDocument is a child of this.  If so, return the frame element in
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -138,16 +138,19 @@
 #include "nsIHistoryEntry.h"
 #include "nsISHistoryListener.h"
 #include "nsIWindowWatcher.h"
 #include "nsIPromptFactory.h"
 #include "nsIObserver.h"
 #include "nsINestedURI.h"
 #include "nsITransportSecurityInfo.h"
 #include "nsINSSErrorsService.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIPermissionManager.h"
 
 // Editor-related
 #include "nsIEditingSession.h"
 
 #include "nsPIDOMWindow.h"
 #include "nsIDOMDocument.h"
 #include "nsICachingChannel.h"
 #include "nsICacheVisitor.h"
@@ -453,16 +456,32 @@ NS_IMETHODIMP nsDocShell::GetInterface(c
              NS_SUCCEEDED(EnsureScriptEnvironment())) {
         return mScriptGlobal->QueryInterface(aIID, aSink);
     }
     else if (aIID.Equals(NS_GET_IID(nsIDOMDocument)) &&
              NS_SUCCEEDED(EnsureContentViewer())) {
         mContentViewer->GetDOMDocument((nsIDOMDocument **) aSink);
         return *aSink ? NS_OK : NS_NOINTERFACE;
     }
+    else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer)) &&
+             NS_SUCCEEDED(EnsureContentViewer())) {
+        *aSink = nsnull;
+
+        // Return the toplevel document as an
+        // nsIApplicationCacheContainer.
+
+        nsCOMPtr<nsIDocShellTreeItem> rootItem;
+        GetSameTypeRootTreeItem(getter_AddRefs(rootItem));
+        nsCOMPtr<nsIDOMDocument> domDoc = do_GetInterface(rootItem);
+        NS_ASSERTION(domDoc, "Should have a document.");
+        if (!domDoc)
+            return NS_ERROR_NO_INTERFACE;
+
+        return domDoc->QueryInterface(aIID, aSink);
+    }
     else if (aIID.Equals(NS_GET_IID(nsIPrompt)) &&
              NS_SUCCEEDED(EnsureScriptEnvironment())) {
         nsresult rv;
         nsCOMPtr<nsIWindowWatcher> wwatch =
             do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
         NS_ENSURE_SUCCESS(rv, rv);
 
         nsCOMPtr<nsIDOMWindow> window(do_QueryInterface(mScriptGlobal));
@@ -7270,16 +7289,25 @@ nsDocShell::DoURILoad(nsIURI * aURI,
 
     uriLoader = do_GetService(NS_URI_LOADER_CONTRACTID, &rv);
     if (NS_FAILED(rv)) return rv;
 
     nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
     if (aFirstParty) {
         // tag first party URL loads
         loadFlags |= nsIChannel::LOAD_INITIAL_DOCUMENT_URI;
+
+        // Toplevel document loads in domains with the offline-app
+        // permission should check for an associated application
+        // cache.
+        nsCOMPtr<nsIDocShellTreeItem> root;
+        GetSameTypeRootTreeItem(getter_AddRefs(root));
+        if (root == this && NS_OfflineAppAllowed(aURI)) {
+            loadFlags |= nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;
+        }
     }
 
     if (mLoadType == LOAD_ERROR_PAGE) {
         // Error pages are LOAD_BACKGROUND
         loadFlags |= nsIChannel::LOAD_BACKGROUND;
     }
 
     // open a channel for the url
--- a/dom/src/offline/Makefile.in
+++ b/dom/src/offline/Makefile.in
@@ -54,16 +54,17 @@ REQUIRES       = xpcom         \
 		 thebes        \
 		 js            \
 		 layout        \
 		 locale        \
 		 necko         \
 		 nkcache       \
 		 pref          \
 		 prefetch      \
+		 docshell      \
 		 widget        \
 		 xpconnect     \
 		 $(NULL)
 
 CPPSRCS =                              \
 	nsDOMOfflineResourceList.cpp   \
 	$(NULL)
 
--- a/dom/src/offline/nsDOMOfflineResourceList.cpp
+++ b/dom/src/offline/nsDOMOfflineResourceList.cpp
@@ -40,26 +40,26 @@
 #include "nsDOMClassInfo.h"
 #include "nsDOMError.h"
 #include "nsIPrefetchService.h"
 #include "nsCPrefetchService.h"
 #include "nsNetUtil.h"
 #include "nsNetCID.h"
 #include "nsICacheSession.h"
 #include "nsICacheService.h"
-#include "nsIOfflineCacheSession.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsIDOMLoadStatus.h"
 #include "nsAutoPtr.h"
 #include "nsContentUtils.h"
 #include "nsIJSContextStack.h"
 #include "nsEventDispatcher.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIObserverService.h"
 #include "nsIScriptGlobalObject.h"
+#include "nsIWebNavigation.h"
 
 // Event names
 
 #define CHECKING_STR    "checking"
 #define ERROR_STR       "error"
 #define NOUPDATE_STR    "noupdate"
 #define DOWNLOADING_STR "downloading"
 #define PROGRESS_STR    "progress"
@@ -68,29 +68,28 @@
 
 // To prevent abuse of the resource list for data storage, the number
 // of offline urls and their length are limited.
 
 static const char kMaxEntriesPref[] =  "offline.max_site_resources";
 #define DEFAULT_MAX_ENTRIES 100
 #define MAX_URI_LENGTH 2048
 
-static nsCAutoString gCachedAsciiHost;
+static nsCAutoString gCachedManifestSpec;
 static char **gCachedKeys = nsnull;
 static PRUint32 gCachedKeysCount = 0;
 
 //
 // nsDOMOfflineResourceList
 //
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMOfflineResourceList)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMOfflineResourceList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mWindow)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCacheSession)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCacheUpdate)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCheckingListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mErrorListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mNoUpdateListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mDownloadingListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mProgressListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCachedListeners)
@@ -109,17 +108,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPendingEvents[i].listener);
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mPendingEvents[i].listeners);
   }
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMOfflineResourceList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWindow)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCacheSession)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCacheUpdate)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mCheckingListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mErrorListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mNoUpdateListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mDownloadingListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mProgressListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mCachedListeners)
@@ -176,46 +174,30 @@ nsDOMOfflineResourceList::Init()
   if (mInitialized) {
     return NS_OK;
   }
 
   if (!mManifestURI) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
+  mManifestURI->GetAsciiSpec(mManifestSpec);
+
   nsresult rv = nsContentUtils::GetSecurityManager()->
                    CheckSameOriginURI(mManifestURI, mDocumentURI, PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Dynamically-managed resources are stored as a separate ownership list
   // from the manifest.
-  rv = mManifestURI->GetAsciiSpec(mDynamicOwnerSpec);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mDynamicOwnerSpec.Append("#dynamic");
-
   nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(mDocumentURI);
   if (!innerURI)
     return NS_ERROR_FAILURE;
 
-  rv = innerURI->GetAsciiHost(mAsciiHost);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsICacheService> serv = do_GetService(NS_CACHESERVICE_CONTRACTID,
-                                                 &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsICacheSession> session;
-  rv = serv->CreateSession("HTTP-offline",
-                           nsICache::STORE_OFFLINE,
-                           nsICache::STREAM_BASED,
-                           getter_AddRefs(session));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mCacheSession = do_QueryInterface(session, &rv);
+  mApplicationCacheService =
+    do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Check for in-progress cache updates
   nsCOMPtr<nsIOfflineCacheUpdateService> cacheUpdateService =
     do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRUint32 numUpdates;
@@ -314,16 +296,21 @@ nsDOMOfflineResourceList::Add(const nsAS
 {
   nsresult rv = Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
+  nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache();
+  if (!appCache) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
   if (aURI.Length() > MAX_URI_LENGTH) return NS_ERROR_DOM_BAD_URI;
 
   // this will fail if the URI is not absolute
   nsCOMPtr<nsIURI> requestedURI;
   rv = NS_NewURI(getter_AddRefs(requestedURI), aURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCAutoString scheme;
@@ -369,59 +356,79 @@ nsDOMOfflineResourceList::Remove(const n
 {
   nsresult rv = Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
+  nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache();
+  if (!appCache) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
   nsCAutoString key;
   rv = GetCacheKey(aURI, key);
   NS_ENSURE_SUCCESS(rv, rv);
 
   ClearCachedKeys();
 
-  rv = mCacheSession->RemoveOwnedKey(mAsciiHost, mDynamicOwnerSpec, key);
-  NS_ENSURE_SUCCESS(rv, rv);
+  // XXX: This is a race condition.  remove() is specced to remove
+  // from the currently associated application cache, but if this
+  // happens during an update (or after an update, if we haven't
+  // swapped yet), that remove() will be lost when the next update is
+  // finished.  Need to bring this issue up.
 
-  rv = mCacheSession->EvictUnownedEntries();
+  rv = appCache->UnmarkEntry(key, nsIApplicationCache::ITEM_DYNAMIC);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMOfflineResourceList::GetStatus(PRUint16 *aStatus)
 {
   nsresult rv = Init();
 
-  // It is OK to check the status without a manifest attribute (you'll
-  // just get "uncached").
-  if (rv == NS_ERROR_DOM_INVALID_STATE_ERR && !mManifestURI) {
+  // Init may fail with INVALID_STATE_ERR if there is no manifest URI.
+  // The status attribute should not throw that exception, convert it
+  // to an UNCACHED.
+  if (rv == NS_ERROR_DOM_INVALID_STATE_ERR ||
+      !nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
     *aStatus = nsIDOMOfflineResourceList::UNCACHED;
     return NS_OK;
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // If there is an update in process, use its status.
   if (mCacheUpdate) {
-    return mCacheUpdate->GetStatus(aStatus);
+    rv = mCacheUpdate->GetStatus(aStatus);
+    if (NS_SUCCEEDED(rv) && *aStatus != nsIDOMOfflineResourceList::IDLE) {
+      return NS_OK;
+    }
   }
 
-  // XXX: the spec allows either UNCACHED or IDLE, depending on whether
-  // the application is associated with a specific offline cache.  Until
-  // we have versioned application caches, the best approximation is
-  // probably IDLE if the offline-app permission is set, and UNCACHED
-  // otherwise.
+  // If this object is not associated with a cache, return UNCACHED
+  nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache();
+  if (!appCache) {
+    *aStatus = nsIDOMOfflineResourceList::UNCACHED;
+    return NS_OK;
+  }
 
-  if (nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
+  nsCOMPtr<nsIApplicationCache> activeCache;
+  rv = mApplicationCacheService->GetActiveCache(mManifestSpec,
+                                                getter_AddRefs(activeCache));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (appCache == activeCache) {
     *aStatus = nsIDOMOfflineResourceList::IDLE;
   } else {
-    *aStatus = nsIDOMOfflineResourceList::UNCACHED;
+    *aStatus = nsIDOMOfflineResourceList::UPDATEREADY;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMOfflineResourceList::Update()
 {
@@ -449,17 +456,45 @@ nsDOMOfflineResourceList::SwapCache()
 {
   nsresult rv = Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
-  return NS_ERROR_NOT_IMPLEMENTED;
+  if (!mToplevel) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  nsCOMPtr<nsIApplicationCacheService> serv =
+    do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIApplicationCache> currentAppCache = GetDocumentAppCache();
+
+  nsCOMPtr<nsIApplicationCache> newAppCache;
+  rv = serv->GetActiveCache(mManifestSpec, getter_AddRefs(newAppCache));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!newAppCache || newAppCache == currentAppCache) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  ClearCachedKeys();
+
+  nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer =
+    GetDocumentAppCacheContainer();
+
+  if (appCacheContainer) {
+    rv = appCacheContainer->SetApplicationCache(newAppCache);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
 }
 
 //
 // nsDOMOfflineResourceList::nsIDOMEventTarget
 //
 
 NS_IMETHODIMP
 nsDOMOfflineResourceList::GetOnchecking(nsIDOMEventListener **aOnchecking)
@@ -939,30 +974,35 @@ nsDOMOfflineResourceList::UpdateAdded(ns
 nsresult
 nsDOMOfflineResourceList::UpdateCompleted(nsIOfflineCacheUpdate *aUpdate)
 {
   if (aUpdate != mCacheUpdate) {
     // This isn't the update we're watching.
     return NS_OK;
   }
 
+  PRBool partial;
+  mCacheUpdate->GetPartial(&partial);
+  PRBool isUpgrade;
+  mCacheUpdate->GetIsUpgrade(&isUpgrade);
+
   PRBool succeeded;
   nsresult rv = mCacheUpdate->GetSucceeded(&succeeded);
 
   mCacheUpdate->RemoveObserver(this);
   mCacheUpdate = nsnull;
 
-  if (NS_SUCCEEDED(rv) && succeeded) {
-    // XXX: the spec requires a "cached" event to be sent if this is a
-    // first-time cache attempt, and "updateready" if this page was loaded
-    // from an existing application cache.  Since we don't have versioned
-    // application caches yet, basically each update acts like a first-time
-    // update, so we'll always fire "cached" for now.
-    SendEvent(NS_LITERAL_STRING(CACHED_STR),
-              mOnCachedListener, mCachedListeners);
+  if (NS_SUCCEEDED(rv) && succeeded && !partial) {
+    if (isUpgrade) {
+      SendEvent(NS_LITERAL_STRING(UPDATEREADY_STR),
+                mOnUpdateReadyListener, mUpdateReadyListeners);
+    } else {
+      SendEvent(NS_LITERAL_STRING(CACHED_STR),
+                mOnCachedListener, mCachedListeners);
+    }
   }
 
   return NS_OK;
 }
 
 nsresult
 nsDOMOfflineResourceList::GetCacheKey(nsIURI *aURI, nsCString &aKey)
 {
@@ -976,39 +1016,79 @@ nsDOMOfflineResourceList::GetCacheKey(ns
   if (FindCharInReadable('#', specStart, specEnd)) {
     aKey.BeginReading(specEnd);
     aKey = Substring(specEnd, specStart);
   }
 
   return NS_OK;
 }
 
+already_AddRefed<nsIApplicationCacheContainer>
+nsDOMOfflineResourceList::GetDocumentAppCacheContainer()
+{
+  nsCOMPtr<nsIDOMWindow> window = do_QueryReferent(mWindow);
+  if (!window) {
+    return nsnull;
+  }
+
+  nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(window);
+  if (!webnav) {
+    return nsnull;
+  }
+
+  nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer =
+    do_GetInterface(webnav);
+  return appCacheContainer.forget();
+}
+
+already_AddRefed<nsIApplicationCache>
+nsDOMOfflineResourceList::GetDocumentAppCache()
+{
+  nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer =
+    GetDocumentAppCacheContainer();
+
+  if (appCacheContainer) {
+    nsCOMPtr<nsIApplicationCache> applicationCache;
+    appCacheContainer->GetApplicationCache(
+      getter_AddRefs(applicationCache));
+    return applicationCache.forget();
+  }
+
+  return nsnull;
+}
+
 nsresult
 nsDOMOfflineResourceList::CacheKeys()
 {
-  if (gCachedKeys && mAsciiHost == gCachedAsciiHost)
+  if (gCachedKeys && mManifestSpec == gCachedManifestSpec)
     return NS_OK;
 
   ClearCachedKeys();
 
-  nsresult rv = mCacheSession->GetOwnedKeys(mAsciiHost, mDynamicOwnerSpec,
-                                            &gCachedKeysCount, &gCachedKeys);
+  nsCOMPtr<nsIApplicationCache> appCache;
+  mApplicationCacheService->GetActiveCache(mManifestSpec,
+                                           getter_AddRefs(appCache));
+
+  if (!appCache) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  nsresult rv = appCache->GatherEntries(nsIApplicationCache::ITEM_DYNAMIC,
+                                        &gCachedKeysCount, &gCachedKeys);
 
   if (NS_SUCCEEDED(rv))
-    gCachedAsciiHost = mAsciiHost;
+    gCachedManifestSpec = mManifestSpec;
 
   return rv;
 }
 
 void
 nsDOMOfflineResourceList::ClearCachedKeys()
 {
   if (gCachedKeys) {
     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(gCachedKeysCount, gCachedKeys);
     gCachedKeys = nsnull;
     gCachedKeysCount = 0;
   }
 
-  gCachedAsciiHost = "";
+  gCachedManifestSpec = "";
 }
 
-
-
--- a/dom/src/offline/nsDOMOfflineResourceList.h
+++ b/dom/src/offline/nsDOMOfflineResourceList.h
@@ -36,17 +36,19 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsDOMOfflineResourceList_h___
 #define nsDOMOfflineResourceList_h___
 
 #include "nscore.h"
 #include "nsIDOMOfflineResourceList.h"
-#include "nsIOfflineCacheSession.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheService.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsWeakReference.h"
 #include "nsCOMArray.h"
 #include "nsIDOMEventListener.h"
@@ -92,31 +94,36 @@ private:
 
   nsresult SendEvent(const nsAString &aEventName,
                      nsIDOMEventListener *aListener,
                      const nsCOMArray<nsIDOMEventListener> &aListeners);
 
   nsresult UpdateAdded(nsIOfflineCacheUpdate *aUpdate);
   nsresult UpdateCompleted(nsIOfflineCacheUpdate *aUpdate);
 
+  already_AddRefed<nsIApplicationCacheContainer> GetDocumentAppCacheContainer();
+  already_AddRefed<nsIApplicationCache> GetDocumentAppCache();
+
   nsresult GetCacheKey(const nsAString &aURI, nsCString &aKey);
   nsresult GetCacheKey(nsIURI *aURI, nsCString &aKey);
 
   nsresult CacheKeys();
   void ClearCachedKeys();
 
   PRBool mInitialized;
   PRBool mToplevel;
+
   nsCOMPtr<nsIURI> mManifestURI;
+  // AsciiSpec of mManifestURI
+  nsCString mManifestSpec;
+
   nsCOMPtr<nsIURI> mDocumentURI;
   nsCOMPtr<nsIWeakReference> mWindow;
-  nsCOMPtr<nsIOfflineCacheSession> mCacheSession;
+  nsCOMPtr<nsIApplicationCacheService> mApplicationCacheService;
   nsCOMPtr<nsIOfflineCacheUpdate> mCacheUpdate;
-  nsCAutoString mAsciiHost;
-  nsCAutoString mDynamicOwnerSpec;
 
   nsCOMArray<nsIDOMEventListener> mCheckingListeners;
   nsCOMArray<nsIDOMEventListener> mErrorListeners;
   nsCOMArray<nsIDOMEventListener> mNoUpdateListeners;
   nsCOMArray<nsIDOMEventListener> mDownloadingListeners;
   nsCOMArray<nsIDOMEventListener> mProgressListeners;
   nsCOMArray<nsIDOMEventListener> mCachedListeners;
   nsCOMArray<nsIDOMEventListener> mUpdateReadyListeners;
--- a/dom/tests/mochitest/ajax/offline/offlineTests.js
+++ b/dom/tests/mochitest/ajax/offline/offlineTests.js
@@ -46,24 +46,19 @@ fetch: function(callback)
   if (this.urls.length == 0) {
     callback(this.contents);
     return;
   }
 
   var url = this.urls.shift();
   var self = this;
 
-  var cacheService = Cc["@mozilla.org/network/cache-service;1"]
-                     .getService(Ci.nsICacheService);
-  var cacheSession = cacheService.createSession("HTTP-offline",
-                                                Ci.nsICache.STORE_OFFLINE,
-                                                true);
+  var cacheSession = OfflineTest.getActiveSession();
   cacheSession.asyncOpenCacheEntry(url, Ci.nsICache.ACCESS_READ, this);
 }
-
 };
 
 var OfflineTest = {
 
 _slaveWindow: null,
 
 // The window where test results should be sent.
 _masterWindow: null,
@@ -162,57 +157,37 @@ is: function(a, b, name)
 
 isnot: function(a, b, name)
 {
   return this._masterWindow.SimpleTest.isnot(a, b, name);
 },
 
 clear: function()
 {
-  // Clear the ownership list
-  var cacheService = Cc["@mozilla.org/network/cache-service;1"]
-                     .getService(Ci.nsICacheService);
-  var cacheSession = cacheService.createSession("HTTP-offline",
-                                                Ci.nsICache.STORE_OFFLINE,
-                                                true)
-                     .QueryInterface(Ci.nsIOfflineCacheSession);
-
-  // Get the asciiHost from the page URL
-  var locationURI = Cc["@mozilla.org/network/standard-url;1"]
-                     .createInstance(Ci.nsIURI);
-  locationURI.spec = window.location.href;
-  var asciiHost = locationURI.asciiHost;
-
-  // Clear manifest-owned urls
-  cacheSession.setOwnedKeys(asciiHost,
-                            this.getManifestUrl() + "#manifest", 0, []);
-
-  // Clear dynamically-owned urls
-  cacheSession.setOwnedKeys(asciiHost,
-                            this.getManifestUrl() + "#dynamic", 0, []);
-
-  cacheSession.evictUnownedEntries();
+  // XXX: maybe we should just wipe out the entire disk cache.
+  var appCacheService = Cc["@mozilla.org/network/application-cache-service;1"]
+                        .getService(Ci.nsIApplicationCacheService);
+  var applicationCache = this.getActiveCache();
+  if (applicationCache) {
+    applicationCache.discard();
+  }
 },
 
 failEvent: function(e)
 {
   OfflineTest.ok(false, "Unexpected event: " + e.type);
 },
 
 // The offline API as specified has no way to watch the load of a resource
 // added with applicationCache.add().
 waitForAdd: function(url, onFinished) {
   // Check every half second for ten seconds.
   var numChecks = 20;
   var waitFunc = function() {
-    var cacheService = Cc["@mozilla.org/network/cache-service;1"]
-    .getService(Ci.nsICacheService);
-    var cacheSession = cacheService.createSession("HTTP-offline",
-                                                  Ci.nsICache.STORE_OFFLINE,
-                                                  true);
+    var cacheSession = OfflineTest.getActiveSession();
     var entry;
     try {
       var entry = cacheSession.openCacheEntry(url, Ci.nsICache.ACCESS_READ, true);
     } catch (e) {
     }
 
     if (entry) {
       entry.close();
@@ -231,48 +206,74 @@ waitForAdd: function(url, onFinished) {
   setTimeout(this.priv(waitFunc), 500);
 },
 
 getManifestUrl: function()
 {
   return window.top.document.documentElement.getAttribute("manifest");
 },
 
+getActiveCache: function()
+{
+  // 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);
+  return serv.getActiveCache(this.getManifestUrl());
+},
+
+getActiveSession: function()
+{
+  var cache = this.getActiveCache();
+  if (!cache) return null;
+
+  var cacheService = Cc["@mozilla.org/network/cache-service;1"]
+                     .getService(Ci.nsICacheService);
+  return cacheService.createSession(cache.clientID,
+                                    Ci.nsICache.STORE_OFFLINE,
+                                    true);
+},
+
 priv: function(func)
 {
   var self = this;
   return function() {
     netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
     func(arguments);
   }
 },
 
 checkCache: function(url, expectEntry)
 {
-  var cacheService = Cc["@mozilla.org/network/cache-service;1"]
-  .getService(Ci.nsICacheService);
-  var cacheSession = cacheService.createSession("HTTP-offline",
-                                                Ci.nsICache.STORE_OFFLINE,
-                                                true);
+  var cacheSession = this.getActiveSession();
+  if (!cacheSession) {
+    if (expectEntry) {
+      this.ok(false, url + " should exist in the offline cache");
+    } else {
+      this.ok(true, url + " should not exist in the offline cache");
+    }
+    return;
+  }
+
   try {
     var entry = cacheSession.openCacheEntry(url, Ci.nsICache.ACCESS_READ, false);
     if (expectEntry) {
       this.ok(true, url + " should exist in the offline cache");
     } else {
       this.ok(false, url + " should not exist in the offline cache");
     }
     entry.close();
   } catch (e) {
     if (e.result == NS_ERROR_CACHE_KEY_NOT_FOUND) {
       if (expectEntry) {
         this.ok(false, url + " should exist in the offline cache");
       } else {
         this.ok(true, url + " should not exist in the offline cache");
       }
-    } else if (e.result == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
+    } else if (e.result == NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION) {
       // There was a cache key that we couldn't access yet, that's good enough.
       if (expectEntry) {
         this.ok(true, url + " should exist in the offline cache");
       } else {
         this.ok(false, url + " should not exist in the offline cache");
       }
     } else {
       throw e;
--- a/dom/tests/mochitest/ajax/offline/test_changingManifest.html
+++ b/dom/tests/mochitest/ajax/offline/test_changingManifest.html
@@ -46,18 +46,19 @@ function manifestUpdated()
   OfflineTest.ok(gGotDownloading, "Should get a downloading event");
 
   // Get the initial contents of the first two files.
   fetcher = new OfflineCacheContents([g1SecUrl, g1HourUrl]);
   fetcher.fetch(function(contents) {
     gCacheContents = contents;
 
      // Now make sure applicationCache.update() does what we expect.
-    applicationCache.oncached = OfflineTest.priv(manifestUpdatedAgain);
+    applicationCache.onupdateready = OfflineTest.priv(manifestUpdatedAgain);
     applicationCache.onnoupdate = failAndFinish;
+    applicationCache.oncached = failAndFinish;
 
     gGotChecking = false;
     gGotDownloading = false;
 
     // The changing versions give out a new version each second,
     // make sure it has time to grab a new version, and for the
     // 1-second cache timeout to pass.
     window.setTimeout("applicationCache.update()", 5000);
--- a/dom/tests/mochitest/ajax/offline/test_simpleManifest.html
+++ b/dom/tests/mochitest/ajax/offline/test_simpleManifest.html
@@ -31,36 +31,16 @@ function addFinished()
 
   // We're done
 
   OfflineTest.teardown();
 
   OfflineTest.finish();
 }
 
-function secondUpdate()
-{
-  OfflineTest.ok(gGotChecking, "Should get a checking event");
-  OfflineTest.ok(gGotDownloading, "Should get a downloading event");
-
-  // The document that requested the manifest should be in the cache
-  OfflineTest.checkCache(window.location.href, true);
-
-  OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", true);
-  OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", true);
-  OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", true);
-
-  // Now add a file using the applicationCache API
-  applicationCache.add("http://localhost:8888/tests/SimpleTest/EventUtils.js");
-
-  // Wait for the add() to be downloaded
-  OfflineTest.waitForAdd("http://localhost:8888/tests/SimpleTest/EventUtils.js",
-                         OfflineTest.priv(addFinished));
-}
-
 function manifestUpdated()
 {
   OfflineTest.ok(gGotChecking, "Should get a checking event");
   OfflineTest.ok(gGotDownloading, "Should get a downloading event");
 
   // The manifest itself should be in the cache
   OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/simpleManifest.cacheManifest", true);
 
@@ -71,36 +51,28 @@ function manifestUpdated()
   OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", true);
   OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", true);
   OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", true);
 
   // The bad entries from the manifest should not be in the cache
   OfflineTest.checkCache("https://localhost:8888/MochiKit/packed.js", false);
   OfflineTest.checkCache("bad:/uri/invalid", false);
 
-  // Remove items from the cache.
-  OfflineTest.clear();
+  applicationCache.swapCache();
 
-  // Make sure OfflineTest.clear() properly removed the items
+  // XXX: make sure that the previous version went away after the swapCache().
 
-  OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", false);
-  OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", false);
-  OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", false);
-  OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/EventUtils.js",
-                         false);
+  // Now add a file using the applicationCache API
+  applicationCache.add("http://localhost:8888/tests/SimpleTest/EventUtils.js");
 
-  // Now make sure applicationCache.update() does what we expect.
-  applicationCache.oncached = OfflineTest.priv(secondUpdate);
-
-  gGotChecking = false;
-  gGotDownloading = false;
-  applicationCache.update();
+  // Wait for the add() to be downloaded
+  OfflineTest.waitForAdd("http://localhost:8888/tests/SimpleTest/EventUtils.js",
+                         OfflineTest.priv(addFinished));
 }
 
-
 if (OfflineTest.setup()) {
   OfflineTest.ok(applicationCache instanceof EventTarget,
                  "applicationCache should be an event target");
 
   applicationCache.onerror = OfflineTest.failEvent;
 
   applicationCache.addEventListener("checking", function() {
     OfflineTest.is(applicationCache.status, 2, "CHECKING state while checking");
--- a/netwerk/base/public/Makefile.in
+++ b/netwerk/base/public/Makefile.in
@@ -56,16 +56,19 @@ SDK_XPIDLSRCS   = \
 		nsIURI.idl \
 		nsIURL.idl \
 		nsIFileURL.idl \
 		nsIUploadChannel.idl \
 		nsIUnicharStreamListener.idl \
 		$(NULL)
 
 XPIDLSRCS	= \
+		nsIApplicationCache.idl \
+		nsIApplicationCacheContainer.idl \
+		nsIApplicationCacheService.idl \
 		nsIAuthInformation.idl \
 		nsIAuthPrompt.idl \
 		nsIAuthPrompt2.idl \
 		nsIAuthPromptAdapterFactory.idl \
 		nsIAuthPromptCallback.idl \
 		nsIAsyncStreamCopier.idl \
 		nsISafeOutputStream.idl \
 		nsIBufferedStreams.idl \
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsIApplicationCache.idl
@@ -0,0 +1,143 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsIApplicationCache.idl.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dave Camp <dcamp@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+/**
+ * Application caches store resources for offline use.  Each
+ * application cache has a unique client ID for use with
+ * nsICacheService::openSession() to access the cache's entries.
+ *
+ * Each entry in the application cache can be marked with a set of
+ * types, as discussed in the WHAT-WG offline applications
+ * specification.
+ *
+ * All application caches with the same group ID belong to a cache
+ * group.  Each group has one "active" cache that will service future
+ * loads.  Inactive caches will be removed from the cache when they are
+ * no longer referenced.
+ */
+[scriptable, uuid(a9cfbeef-8f8d-49c3-b899-303390383ae9)]
+interface nsIApplicationCache : nsISupports
+{
+    /**
+     * Entries in an application cache can be marked as one or more of
+     * the following types.
+     */
+
+    /* This item is the application manifest. */
+    const unsigned long ITEM_MANIFEST =      1 << 0;
+
+    /* This item was explicitly listed in the application manifest. */
+    const unsigned long ITEM_EXPLICIT =      1 << 1;
+
+    /* This item was navigated in a toplevel browsing context, and
+     * named this cache's group as its manifest. */
+    const unsigned long ITEM_IMPLICIT =      1 << 2;
+
+    /* This item was added by the dynamic scripting API */
+    const unsigned long ITEM_DYNAMIC =       1 << 3;
+
+    /* This item was listed in the application manifest, but named a
+     * different cache group as its manifest. */
+    const unsigned long ITEM_FOREIGN = 1 << 4;
+
+    /* This item was listed as a fallback entry. */
+    const unsigned long ITEM_FALLBACK = 1 << 5;
+
+    /* This item matched an opportunistic cache namespace and was
+     * cached accordingly. */
+    const unsigned long ITEM_OPPORTUNISTIC = 1 << 6;
+
+    /**
+     * The group ID for this cache group.  This is the URI of the
+     * manifest file.
+     **/
+    readonly attribute ACString groupID;
+
+    /**
+     * The client ID for this application cache.  Clients can open a
+     * session with nsICacheService::createSession() using this client
+     * ID and a storage policy of STORE_OFFLINE to access this cache.
+     */
+    readonly attribute ACString clientID;
+
+    /**
+     * TRUE if the cache is the active cache for this group.
+     */
+    readonly attribute boolean active;
+
+    /**
+     * Makes this cache the active application cache for this group.
+     * Future loads associated with this group will come from this
+     * cache.  Other caches from this cache group will be deactivated.
+     */
+    void activate();
+
+    /**
+     * Discard this application cache.  Removes all cached resources
+     * for this cache.  If this is the active application cache for the
+     * group, the group will be removed.
+     */
+    void discard();
+
+    /**
+     * Adds item types to a given entry.
+     */
+    void markEntry(in ACString key, in unsigned long typeBits);
+
+    /**
+     * Removes types from a given entry.  If the resulting entry has
+     * no types left, the entry is removed.
+     */
+    void unmarkEntry(in ACString key, in unsigned long typeBits);
+
+    /**
+     * Gets the types for a given entry.
+     */
+    unsigned long getTypes(in ACString key);
+
+    /**
+     * Returns any entries in the application cache whose type matches
+     * one or more of the bits in typeBits.
+     */
+    void gatherEntries(in PRUint32 typeBits,
+                       out unsigned long count,
+                       [array, size_is(count)] out string keys);
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsIApplicationCacheContainer.idl
@@ -0,0 +1,52 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsIApplicationCache.idl.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dave Camp <dcamp@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIApplicationCache;
+
+/**
+ * Interface used by objects that can be associated with an
+ * application cache.
+ */
+[scriptable, uuid(bbb80700-1f7f-4258-aff4-1743cc5a7d23)]
+interface nsIApplicationCacheContainer : nsISupports
+{
+    attribute nsIApplicationCache applicationCache;
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsIApplicationCacheService.idl
@@ -0,0 +1,71 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsIApplicationCache.idl.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dave Camp <dcamp@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIApplicationCache;
+
+/**
+ * The application cache service manages the set of application cache
+ * groups.
+ */
+[scriptable, uuid(3f411c68-0d17-420d-839a-6355eea877b9)]
+interface nsIApplicationCacheService : nsISupports
+{
+    /**
+     * Create a new, empty application cache for the given cache
+     * group.
+     */
+    nsIApplicationCache createApplicationCache(in ACString group);
+
+    /**
+     * Get an application cache object for the given client ID.
+     */
+    nsIApplicationCache getApplicationCache(in ACString clientID);
+
+    /**
+     * Get the currently active cache object for a cache group.
+     */
+    nsIApplicationCache getActiveCache(in ACString group);
+
+    /**
+     * Try to find the best application cache to serve a resource.
+     */
+    nsIApplicationCache chooseApplicationCache(in ACString key);
+};
--- a/netwerk/base/public/nsINetUtil.idl
+++ b/netwerk/base/public/nsINetUtil.idl
@@ -36,16 +36,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
+interface nsIPrefBranch;
 
 /**
  * nsINetUtil provides various network-related utility methods.
  */
 [scriptable, uuid(57322c6f-f4ec-4e46-8253-b74be220de16)]
 interface nsINetUtil : nsISupports
 {
   /**
@@ -215,8 +216,30 @@ interface nsINetUtil : nsISupports
    * cases when parseContentType would claim to have a charset, if the type
    * that won out does not have a charset parameter specified.
    */
   boolean extractCharsetFromContentType(in AUTF8String aTypeHeader,
                                         out AUTF8String aCharset,
                                         out long aCharsetStart,
                                         out long aCharsetEnd);
 };
+
+/**
+ * nsINetUtil methods added in mozilla 1.9.1.
+ *
+ * XXX bug 451255: Merge this up in to nsINetUtil as soon as possible.
+ */
+[scriptable, uuid(da76ab60-2ec5-4649-84c4-4954a08f6d37)]
+interface nsINetUtil_MOZILLA_1_9_1 : nsISupports
+{
+    /**
+     * Checks whether a document at the given URI should have access
+     * to the offline cache.
+     * @param aURI
+     *        The URI to check
+     * @param aPrefBranch
+     *        The pref branch to use to check the
+     *        offline-apps.allow_by_default pref.  If not specified,
+     *        the pref service will be used.
+     */
+    boolean OfflineAppAllowed(in nsIURI aURI,
+                              in nsIPrefBranch aPrefBranch);
+};
--- a/netwerk/base/public/nsNetUtil.h
+++ b/netwerk/base/public/nsNetUtil.h
@@ -1436,9 +1436,33 @@ NS_GetFinalChannelURI(nsIChannel* channe
     
     if (loadFlags & nsIChannel::LOAD_REPLACE) {
         return channel->GetURI(uri);
     }
     
     return channel->GetOriginalURI(uri);
 }
 
+/**
+ * Checks whether a document at the given URI should have access
+ * to the offline cache.
+ * @param uri
+ *        The URI to check
+ * @param prefBranch
+ *        The pref branch to use to check the
+ *        offline-apps.allow_by_default pref.  If not specified,
+ *        the pref service will be used.
+ */
+inline PRBool
+NS_OfflineAppAllowed(nsIURI *aURI, nsIPrefBranch *aPrefBranch = nsnull)
+{
+    nsresult rv;
+    nsCOMPtr<nsINetUtil_MOZILLA_1_9_1> util = do_GetIOService(&rv);
+    NS_ENSURE_SUCCESS(rv, PR_FALSE);
+
+    PRBool allowed;
+    rv = util->OfflineAppAllowed(aURI, aPrefBranch, &allowed);
+    NS_ENSURE_SUCCESS(rv, PR_FALSE);
+
+    return allowed;
+}
+
 #endif // !nsNetUtil_h__
--- a/netwerk/base/src/nsIOService.cpp
+++ b/netwerk/base/src/nsIOService.cpp
@@ -65,16 +65,17 @@
 #include "nsEscape.h"
 #include "nsNetCID.h"
 #include "nsIRecyclingAllocator.h"
 #include "nsISocketTransport.h"
 #include "nsCRT.h"
 #include "nsINestedURI.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
+#include "nsIPermissionManager.h"
 
 #if defined(XP_WIN)
 #include "nsNativeConnectionHelper.h"
 #endif
 
 #define PORT_PREF_PREFIX     "network.security.ports."
 #define PORT_PREF(x)         PORT_PREF_PREFIX x
 #define AUTODIAL_PREF        "network.autodial-helper.enabled"
@@ -273,22 +274,23 @@ nsIOService::GetInstance() {
             return nsnull;
         }
         return gIOService;
     }
     NS_ADDREF(gIOService);
     return gIOService;
 }
 
-NS_IMPL_THREADSAFE_ISUPPORTS5(nsIOService,
+NS_IMPL_THREADSAFE_ISUPPORTS6(nsIOService,
                               nsIIOService,
                               nsIIOService2,
                               nsINetUtil,
                               nsIObserver,
-                              nsISupportsWeakReference)
+                              nsISupportsWeakReference,
+                              nsINetUtil_MOZILLA_1_9_1)
 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsresult
 nsIOService::OnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
                                PRUint32 flags)
 {
     nsCOMPtr<nsIChannelEventSink> sink =
@@ -978,8 +980,63 @@ nsIOService::ExtractCharsetFromContentTy
     net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset,
                          aCharsetStart, aCharsetEnd);
     if (*aHadCharset && *aCharsetStart == *aCharsetEnd) {
         *aHadCharset = PR_FALSE;
     }
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsIOService::OfflineAppAllowed(nsIURI *aURI,
+                               nsIPrefBranch *aPrefBranch,
+                               PRBool *aAllowed)
+{
+    *aAllowed = PR_FALSE;
+
+    nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
+    if (!innerURI)
+        return NS_OK;
+
+    // only http and https applications can use offline APIs.
+    PRBool match;
+    nsresult rv = innerURI->SchemeIs("http", &match);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!match) {
+        rv = innerURI->SchemeIs("https", &match);
+        NS_ENSURE_SUCCESS(rv, rv);
+        if (!match) {
+            return NS_OK;
+        }
+    }
+
+    nsCOMPtr<nsIPermissionManager> permissionManager =
+        do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+    if (!permissionManager) {
+        return NS_OK;
+    }
+
+    PRUint32 perm;
+    permissionManager->TestExactPermission(innerURI, "offline-app", &perm);
+
+    if (perm == nsIPermissionManager::UNKNOWN_ACTION) {
+        nsCOMPtr<nsIPrefBranch> branch = aPrefBranch;
+        if (!branch) {
+            branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+        }
+        if (branch) {
+            rv = branch->GetBoolPref("offline-apps.allow_by_default", aAllowed);
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
+
+        return NS_OK;
+    }
+
+    if (perm == nsIPermissionManager::DENY_ACTION) {
+        return NS_OK;
+    }
+
+    *aAllowed = PR_TRUE;
+
+    return NS_OK;
+}
+
--- a/netwerk/base/src/nsIOService.h
+++ b/netwerk/base/src/nsIOService.h
@@ -72,24 +72,26 @@ static const char gScheme[][sizeof("reso
     {"chrome", "file", "http", "jar", "resource"};
 
 class nsIPrefBranch;
 class nsIPrefBranch2;
 
 class nsIOService : public nsIIOService2
                   , public nsIObserver
                   , public nsINetUtil
+                  , public nsINetUtil_MOZILLA_1_9_1
                   , public nsSupportsWeakReference
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIIOSERVICE
     NS_DECL_NSIIOSERVICE2
     NS_DECL_NSIOBSERVER
     NS_DECL_NSINETUTIL
+    NS_DECL_NSINETUTIL_MOZILLA_1_9_1
 
     // Gets the singleton instance of the IO Service, creating it as needed
     // Returns nsnull on out of memory or failure to initialize.
     // Returns an addrefed pointer.
     static nsIOService* GetInstance();
 
     NS_HIDDEN_(nsresult) Init();
     NS_HIDDEN_(nsresult) NewURI(const char* aSpec, nsIURI* aBaseURI,
--- a/netwerk/build/Makefile.in
+++ b/netwerk/build/Makefile.in
@@ -108,16 +108,21 @@ endif
 
 ifdef NECKO_COOKIES
 SHARED_LIBRARY_LIBS += \
 		../cookie/src/$(LIB_PREFIX)neckocookie_s.$(LIB_SUFFIX) \
                 $(NULL)
 LOCAL_INCLUDES	+= -I$(srcdir)/../cookie/src
 endif
 
+ifdef MOZ_STORAGE
+REQUIRES += storage
+DEFINES += -DNECKO_OFFLINE_CACHE
+endif
+
 EXTRA_DSO_LDOPTS = \
 		$(LIBS_DIR) \
 		$(EXTRA_DSO_LIBS) \
 		$(MOZ_UNICHARUTIL_LIBS) \
 		$(MOZ_COMPONENT_LIBS) \
 		$(ZLIB_LIBS) \
 		$(NULL)
 
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -460,16 +460,28 @@
 #define NS_CACHESERVICE_CID                          \
 { /* 6c84aec9-29a5-4264-8fbc-bee8f922ea67 */         \
     0x6c84aec9,                                      \
     0x29a5,                                          \
     0x4264,                                          \
     {0x8f, 0xbc, 0xbe, 0xe8, 0xf9, 0x22, 0xea, 0x67} \
 }
 
+// service implementing nsIApplicationCacheService.
+#define NS_APPLICATIONCACHESERVICE_CLASSNAME \
+    "nsApplicationCacheService"
+#define NS_APPLICATIONCACHESERVICE_CONTRACTID \
+    "@mozilla.org/network/application-cache-service;1"
+#define NS_APPLICATIONCACHESERVICE_CID               \
+{ /* 02bf7a2a-39d8-4a23-a50c-2cbb085ab7a5 */         \
+    0x02bf7a2a,                                      \
+    0x39d8,                                          \
+    0x4a23,                                          \
+    {0xa5, 0x0c, 0x2c, 0xbb, 0x08, 0x5a, 0xb7, 0xa5} \
+}
 
 /******************************************************************************
  * netwerk/protocol/http/ classes
  */
 
 #define NS_HTTPPROTOCOLHANDLER_CID \
 { /* 4f47e42e-4d23-4dd3-bfda-eb29255e9ea3 */         \
     0x4f47e42e,                                      \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -51,16 +51,17 @@
 #include "nsLoadGroup.h"
 #include "nsStreamLoader.h"
 #include "nsUnicharStreamLoader.h"
 #include "nsFileStreams.h"
 #include "nsBufferedStreams.h"
 #include "nsMIMEInputStream.h"
 #include "nsSOCKSSocketProvider.h"
 #include "nsCacheService.h"
+#include "nsDiskCacheDeviceSQL.h"
 #include "nsMimeTypes.h"
 #include "nsNetStrings.h"
 
 #include "nsNetCID.h"
 
 #if defined(XP_MACOSX)
 #define BUILD_APPLEFILE_DECODER 1
 #else
@@ -182,17 +183,21 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeAbo
 // about
 #ifdef NS_BUILD_REFCNT_LOGGING
 #include "nsAboutBloat.h"
 #endif
 #include "nsAboutCache.h"
 #include "nsAboutCacheEntry.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAboutCacheEntry)
 #endif
-  
+
+#ifdef NECKO_OFFLINE_CACHE
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsOfflineCacheDevice, nsOfflineCacheDevice::GetInstance)
+#endif
+
 #ifdef NECKO_PROTOCOL_file
 // file
 #include "nsFileProtocolHandler.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsFileProtocolHandler, Init)
 #endif
 
 #ifdef NECKO_PROTOCOL_ftp
 // ftp
@@ -1040,16 +1045,24 @@ static const nsModuleComponentInfo gNetM
     },
 
     {  NS_CACHESERVICE_CLASSNAME,
        NS_CACHESERVICE_CID,
        NS_CACHESERVICE_CONTRACTID,
        nsCacheService::Create
     },
 
+#ifdef NECKO_OFFLINE_CACHE
+    {  NS_APPLICATIONCACHESERVICE_CLASSNAME,
+       NS_APPLICATIONCACHESERVICE_CID,
+       NS_APPLICATIONCACHESERVICE_CONTRACTID,
+       nsOfflineCacheDeviceConstructor
+    },
+#endif
+
 #ifdef NECKO_COOKIES
     { NS_COOKIEMANAGER_CLASSNAME,
       NS_COOKIEMANAGER_CID,
       NS_COOKIEMANAGER_CONTRACTID,
       nsCookieServiceConstructor
     },
 
     { NS_COOKIESERVICE_CLASSNAME,
--- a/netwerk/cache/public/Makefile.in
+++ b/netwerk/cache/public/Makefile.in
@@ -48,14 +48,13 @@ include $(DEPTH)/config/autoconf.mk
 
 XPIDLSRCS	= \
 		nsICache.idl		      \
 		nsICacheEntryDescriptor.idl   \
 		nsICacheListener.idl	      \
 		nsICacheService.idl           \
 		nsICacheSession.idl           \
 		nsICacheVisitor.idl           \
-		nsIOfflineCacheSession.idl    \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 DEFINES += -DIMPL_NS_NET
--- a/netwerk/cache/public/nsICacheService.idl
+++ b/netwerk/cache/public/nsICacheService.idl
@@ -81,21 +81,17 @@ interface nsICacheService : nsISupports
     void visitEntries(in nsICacheVisitor visitor);
 
     /**
      * Evicts all entries in all devices implied by the storage policy.
      */
     void evictEntries(in nsCacheStoragePolicy  storagePolicy);
 
     /**
-     * Return a unique, temporary cache client ID.
-     *
-     * This is used by the offline cache.  The offline cache lets clients
-     * accumulate entries in a temporary client and merge them in as a group
-     * using nsIOfflineCacheSession.mergeTemporaryClient().
+     * This method is deprecated and will throw NS_ERROR_NOT_IMPLEMENTED.
      */
     ACString createTemporaryClientID(in nsCacheStoragePolicy storagePolicy);
 };
 
 %{C++
 /**
  * Observer service notification that is sent when
  * nsICacheService::evictEntries() or nsICacheSession::evictEntries()
deleted file mode 100644
--- a/netwerk/cache/public/nsIOfflineCacheSession.idl
+++ /dev/null
@@ -1,233 +0,0 @@
-/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- *
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is nsIOfflineCacheSession.idl.
- *
- * The Initial Developer of the Original Code is
- * Mozilla Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Dave Camp <dcamp@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "nsISupports.idl"
-#include "nsICache.idl"
-
-/**
- * The offline cache is meant to reliably store resources for
- * offline use.  The expected semantics are:
- *
- * a) Once populated, the cache will not evict an application resource
- *    unless explicitly asked.
- *
- * b) Resources no longer in use by the application should be evicted.
- *
- * c) If the cache fills up, new entries should be rejected rather
- *    than throwing out old ones.
- *
- * The offline cache uses domains to concretely represent an
- * application.  It maintains a list of resources to be pinned for
- * each domain.  This list is separate from actual cache
- * population - the caller is still responsible for placing items
- * in the cache, and ownership can be declared without a
- * corresponding entry.
- *
- * A key can optionally be associated with a specific URI within
- * the domain.
- */
-
-[scriptable, uuid(3a33e268-4175-4440-a933-89d461c86c5f)]
-interface nsIOfflineCacheSession : nsISupports
-{
-    /**
-     * Gets the list of owner domains in the cache.
-     *
-     * @param count
-     *        The number of domains returned
-     * @param uris
-     *        The domains that own resources in the cache
-     */
-    void getOwnerDomains(out unsigned long count,
-                         [array, size_is(count)]out string domains);
-
-    /**
-     * Gets the list of owner URIs associated with a domain.
-     *
-     * @param ownerAsciiDomain
-     *        The domain to query
-     *        !! IMPORTANT !! : This must be ascii encoded host - nsIURI.asciiHost
-     * @param count
-     *        The number of uris returned
-     * @param uris
-     *        The uris in this domain that own resources
-     */
-    void getOwnerURIs(in ACString ownerAsciiDomain,
-                      out unsigned long count,
-                      [array, size_is(count)]out string uris);
-
-    /**
-     * Sets the resources owned by a given domain/URI pair.
-     *
-     * Setting a list will remove any resources previously owned by this
-     * domain/URI pair.
-     *
-     * A key can be added while there is no associated entry.  When
-     * an entry is created with this key, it will be owned by the
-     * domain/URI pair.
-     *
-     * @param ownerAsciiDomain
-     *        The domain that owns the resources
-     *        !! IMPORTANT !! : This must be ascii encoded host - nsIURI.asciiHost
-     * @param ownerAsciiKey
-     *        The specific key that owns the resources.  You may use 
-     *        ascii encoded URI spec of the owner - nsIURI.asciiSpec.
-     *        This can be empty if none specifically owns the resources.
-     * @param count
-     *        The number of keys in keys.
-     * @param keys
-     *        The keys that the domain/URI pair own.  This can be empty to
-     *        clear ownership for the domain/URI pair.
-     */
-    void setOwnedKeys(in ACString ownerAsciiDomain,
-                      in ACString ownerAsciiKey,
-                      in unsigned long count,
-                      [array, size_is(count)]in string keys);
-
-    /**
-     * Gets the list of resources owned by a given domain/URI pair.
-     *
-     * @param ownerAsciiDomain
-     *        The domain that owns the resources
-     *        !! IMPORTANT !! : This must be ascii encoded host - nsIURI.asciiHost
-     * @param ownerAsciiKey
-     *        The specific key that owns the resources.  You may use 
-     *        ascii encoded URI spec of the owner - nsIURI.asciiSpec.
-     *        This can be empty if none specifically owns the resources.
-     * @param count
-     *        The number of keys in keys.
-     * @param keys
-     *        The keys that the domain/URI pair own.
-     */
-    void getOwnedKeys(in ACString ownerAsciiDomain,
-                      in ACString ownerAsciiKey,
-                      out unsigned long count,
-                      [array, size_is(count)]out string keys);
-
-    /**
-     * Adds an owned key to a domain/URI pair.
-     *
-     * A key can be added while there is no associated entry.  When
-     * an entry is created with this key, it will be owned by the
-     * domain/URI pair.
-     *
-     * @param ownerAsciiDomain
-     *        The domain that owns the resources
-     *        !! IMPORTANT !! : This must be ascii encoded host - nsIURI.asciiHost
-     * @param ownerAsciiKey
-     *        The specific key that owns the resources.  You may use 
-     *        ascii encoded URI spec of the owner - nsIURI.asciiSpec.
-     *        This can be empty if none specifically owns the resources.
-     * @param key
-     *        The key to add.
-     */
-    void addOwnedKey(in ACString ownerAsciiDomain,
-                     in ACString ownerAsciiKey,
-                     in ACString key);
-
-    /**
-     * Removes an owned key from a domain/URI pair.
-     *
-     * If the key does not exist, an NS_ERROR_NOT_AVAILABLE exception
-     * will be thrown.
-     *
-     * @param ownerAsciiDomain
-     *        The domain that owns the resources
-     *        !! IMPORTANT !! : This must be ascii encoded host - nsIURI.asciiHost
-     * @param ownerAsciiKey
-     *        The specific key that owns the resources.  You may use 
-     *        ascii encoded URI spec of the owner - nsIURI.asciiSpec.
-     *        This can be empty if none specifically owns the resources.
-     * @param key The key to remove.
-     */
-    void removeOwnedKey(in ACString ownerAsciiDomain,
-                        in ACString ownerAsciiKey,
-                        in ACString key);
-
-    /**
-     * Checks whether a key is owned by a given domain/URI pair.
-     *
-     * @param ownerAsciiDomain
-     *        The domain that owns the resources
-     *        !! IMPORTANT !! : This must be ascii encoded host - nsIURI.asciiHost
-     * @param ownerAsciiKey
-     *        The specific key that owns the resources.  You may use 
-     *        ascii encoded URI spec of the owner - nsIURI.asciiSpec.
-     *        This can be empty if none specifically owns the resources.
-     * @param key The key to check
-     */
-    boolean keyIsOwned(in ACString ownerAsciiDomain,
-                       in ACString ownerAsciiKey,
-                       in ACString key);
-
-    /**
-     * Remove all keys owned by a domain, including keys owned by
-     * a specific URI.
-     *
-     * @param ownerAsciiDomain
-     *        The domain for which keys should be removed
-     *        !! IMPORTANT !! : This must be ascii encoded host - nsIURI.asciiHost
-     */
-    void clearKeysOwnedByDomain(in ACString ownerAsciiDomain);
-
-    /**
-     * Get the number of bytes used in the cache by a domain.
-     *
-     * @param domain The domain to check.
-     */
-    unsigned long getDomainUsage(in ACString ownerDomain);
-
-    /**
-     * Evict all entries that are not owned by a domain.
-     */
-    void evictUnownedEntries();
-
-    /**
-     * Merge the items from a temporary clientID in to this client.  This lets
-     * offline cache updates accumulate in a temporary client and be moved
-     * in all at once.
-     *
-     * Entries in the temporary client will replace any entries in this client
-     * with the same cache key.
-     *
-     * Ownership lists for a given domain/URI pair from the temporary client
-     * will replace ownership lists for the same domain/URI pair.
-     */
-    void mergeTemporaryClientID(in ACString temporaryClientID);
-};
--- a/netwerk/cache/src/nsCacheService.cpp
+++ b/netwerk/cache/src/nsCacheService.cpp
@@ -658,18 +658,17 @@ nsCacheService::Shutdown()
         mMemoryDevice = nsnull;
 
 #ifdef NECKO_DISK_CACHE
         delete mDiskDevice;
         mDiskDevice = nsnull;
 #endif // !NECKO_DISK_CACHE
 
 #ifdef NECKO_OFFLINE_CACHE
-        delete mOfflineDevice;
-        mOfflineDevice = nsnull;
+        NS_IF_RELEASE(mOfflineDevice);
 #endif // !NECKO_OFFLINE_CACHE
 
 #if defined(NECKO_DISK_CACHE) && defined(PR_LOGGING)
         LogCacheStatistics();
 #endif
     }
 }
 
@@ -824,255 +823,16 @@ nsCacheService::IsStorageEnabledForPolic
     if (gService->mEnableOfflineDevice &&
         storagePolicy == nsICache::STORE_OFFLINE) {
         return PR_TRUE;
     }
     
     return PR_FALSE;
 }
 
-
-nsresult nsCacheService::GetOfflineOwnerDomains(nsCacheSession * session,
-                                                PRUint32 * count,
-                                                char *** domains)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->GetOwnerDomains(session->ClientID()->get(),
-                                                     count, domains);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-
-nsresult nsCacheService::GetOfflineOwnerURIs(nsCacheSession * session,
-                                             const nsACString & ownerDomain,
-                                             PRUint32 * count,
-                                             char *** uris)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->GetOwnerURIs(session->ClientID()->get(),
-                                                  ownerDomain, count, uris);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult
-nsCacheService::SetOfflineOwnedKeys(nsCacheSession * session,
-                                    const nsACString & ownerDomain,
-                                    const nsACString & ownerURI,
-                                    PRUint32 count,
-                                    const char ** keys)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->SetOwnedKeys(session->ClientID()->get(),
-                                                  ownerDomain,
-                                                  ownerURI,
-                                                  count,
-                                                  keys);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult nsCacheService::GetOfflineOwnedKeys(nsCacheSession * session,
-                                             const nsACString & ownerDomain,
-                                             const nsACString & ownerURI,
-                                             PRUint32 * count,
-                                             char *** keys)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->GetOwnedKeys(session->ClientID()->get(),
-                                                  ownerDomain,
-                                                  ownerURI,
-                                                  count,
-                                                  keys);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult nsCacheService::AddOfflineOwnedKey(nsCacheSession * session,
-                                            const nsACString & ownerDomain,
-                                            const nsACString & ownerURI,
-                                            const nsACString & key)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->AddOwnedKey(session->ClientID()->get(),
-                                                 ownerDomain,
-                                                 ownerURI,
-                                                 key);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult nsCacheService::RemoveOfflineOwnedKey(nsCacheSession * session,
-                                               const nsACString & ownerDomain,
-                                               const nsACString & ownerURI,
-                                               const nsACString & key)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->RemoveOwnedKey(session->ClientID()->get(),
-                                                    ownerDomain,
-                                                    ownerURI,
-                                                    key);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult nsCacheService::OfflineKeyIsOwned(nsCacheSession * session,
-                                           const nsACString &  ownerDomain,
-                                           const nsACString & ownerURI,
-                                           const nsACString & key,
-                                           PRBool *isOwned)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->KeyIsOwned(session->ClientID()->get(),
-                                                ownerDomain,
-                                                ownerURI,
-                                                key,
-                                                isOwned);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult nsCacheService::ClearOfflineKeysOwnedByDomain(nsCacheSession * session,
-                                                       const nsACString & domain)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->ClearKeysOwnedByDomain(session->ClientID()->get(),
-                                                            domain);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult nsCacheService::GetOfflineDomainUsage(nsCacheSession * session,
-                                               const nsACString & domain,
-                                               PRUint32 * usage)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->GetDomainUsage(session->ClientID()->get(),
-                                                    domain,
-                                                    usage);
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult nsCacheService::EvictUnownedOfflineEntries(nsCacheSession * session)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->EvictUnownedEntries(session->ClientID()->get());
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
-nsresult nsCacheService::MergeTemporaryClientID(nsCacheSession * session,
-                                                const nsACString & clientID)
-{
-#ifdef NECKO_OFFLINE_CACHE
-    if (session->StoragePolicy() != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->MergeTemporaryClientID
-        (session->ClientID()->get(), PromiseFlatCString(clientID).get());
-#else // !NECKO_OFFLINE_CACHE
-    return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
 NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
 {
     NS_ENSURE_ARG_POINTER(visitor);
 
     nsCacheServiceAutoLock lock;
 
     if (!(mEnableDiskDevice || mEnableMemoryDevice))
         return NS_ERROR_NOT_AVAILABLE;
@@ -1119,30 +879,17 @@ NS_IMETHODIMP nsCacheService::VisitEntri
 NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
 {
     return  EvictEntriesForClient(nsnull, storagePolicy);
 }
 
 NS_IMETHODIMP nsCacheService::CreateTemporaryClientID(nsCacheStoragePolicy storagePolicy,
                                                       nsACString &clientID)
 {
-#ifdef NECKO_OFFLINE_CACHE
-    // Only the offline cache device supports temporary clients
-    if (storagePolicy != nsICache::STORE_OFFLINE)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    if (!gService->mOfflineDevice) {
-        nsresult rv = gService->CreateOfflineDevice();
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    return gService->mOfflineDevice->CreateTemporaryClientID(clientID);
-#else // !NECKO_OFFLINE_CACHE
     return NS_ERROR_NOT_IMPLEMENTED;
-#endif
 }
 
 /**
  * Internal Methods
  */
 nsresult
 nsCacheService::CreateDiskDevice()
 {
@@ -1183,29 +930,30 @@ nsCacheService::CreateOfflineDevice()
     CACHE_LOG_ALWAYS(("Creating offline device"));
 
     if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
     if (mOfflineDevice)        return NS_OK;
 
     mOfflineDevice = new nsOfflineCacheDevice;
     if (!mOfflineDevice)       return NS_ERROR_OUT_OF_MEMORY;
 
+    NS_ADDREF(mOfflineDevice);
+
     // set the preferences
     mOfflineDevice->SetCacheParentDirectory(
         mObserver->OfflineCacheParentDirectory());
     mOfflineDevice->SetCapacity(mObserver->OfflineCacheCapacity());
 
     nsresult rv = mOfflineDevice->Init();
     if (NS_FAILED(rv)) {
         CACHE_LOG_DEBUG(("mOfflineDevice->Init() failed (0x%.8x)\n", rv));
         CACHE_LOG_DEBUG(("    - disabling offline cache for this session.\n"));
 
         mEnableOfflineDevice = PR_FALSE;
-        delete mOfflineDevice;
-        mOfflineDevice = nsnull;
+        NS_RELEASE(mOfflineDevice);
     }
     return rv;
 #else // !NECKO_DISK_CACHE
     NS_NOTREACHED("nsCacheService::CreateOfflineDevice");
     return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 }
 
--- a/netwerk/cache/src/nsCacheService.h
+++ b/netwerk/cache/src/nsCacheService.h
@@ -92,64 +92,16 @@ public:
                                     nsICacheListener *         listener,
                                     nsICacheEntryDescriptor ** result);
 
     static nsresult  EvictEntriesForSession(nsCacheSession *   session);
 
     static nsresult  IsStorageEnabledForPolicy(nsCacheStoragePolicy  storagePolicy,
                                                PRBool *              result);
 
-
-    static nsresult  GetOfflineOwnerDomains(nsCacheSession *          session,
-                                            PRUint32 *                count,
-                                            char ***                  domains);
-    static nsresult  GetOfflineOwnerURIs(nsCacheSession *             session,
-                                         const nsACString &           ownerDomain,
-                                         PRUint32 *                   count,
-                                         char ***                     uris);
-
-    static nsresult  SetOfflineOwnedKeys(nsCacheSession *             session,
-                                         const nsACString &           ownerDomain,
-                                         const nsACString &           ownerUri,
-                                         PRUint32                     count,
-                                         const char **                keys);
-
-    static nsresult  GetOfflineOwnedKeys(nsCacheSession *             session,
-                                         const nsACString &           ownerDomain,
-                                         const nsACString &           ownerURI,
-                                         PRUint32 *                   count,
-                                         char ***                     keys);
-
-    static nsresult  AddOfflineOwnedKey(nsCacheSession *              session,
-                                        const nsACString &            ownerDomain,
-                                        const nsACString &            ownerURI,
-                                        const nsACString &            key);
-
-    static nsresult  RemoveOfflineOwnedKey(nsCacheSession *           session,
-                                           const nsACString &         ownerDomain,
-                                           const nsACString &         ownerURI,
-                                           const nsACString &         key);
-
-    static nsresult  OfflineKeyIsOwned(nsCacheSession *               session,
-                                       const nsACString &             ownerDomain,
-                                       const nsACString &             ownerURI,
-                                       const nsACString &             key,
-                                       PRBool *                       isOwned);
-
-    static nsresult  ClearOfflineKeysOwnedByDomain(nsCacheSession   * session,
-                                                   const nsACString & domain);
-    static nsresult  GetOfflineDomainUsage(nsCacheSession           * session,
-                                           const nsACString         & domain,
-                                           PRUint32                 * usage);
-
-    static nsresult  EvictUnownedOfflineEntries(nsCacheSession *      session);
-
-    static nsresult  MergeTemporaryClientID(nsCacheSession *            session,
-                                            const nsACString &          fromClientID);
-
     /**
      * Methods called by nsCacheEntryDescriptor
      */
 
     static void      CloseDescriptor(nsCacheEntryDescriptor * descriptor);
 
     static nsresult  GetFileForEntry(nsCacheEntry *         entry,
                                      nsIFile **             result);
@@ -203,16 +155,17 @@ public:
     static void      SetOfflineCacheCapacity(PRInt32  capacity);
 
     static void      SetMemoryCache();
 
     nsresult         Init();
     void             Shutdown();
 private:
     friend class nsCacheServiceAutoLock;
+    friend class nsOfflineCacheDevice;
 
     /**
      * Internal Methods
      */
 
     static void      Lock();
     static void      Unlock();
 
--- a/netwerk/cache/src/nsCacheSession.cpp
+++ b/netwerk/cache/src/nsCacheSession.cpp
@@ -39,25 +39,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsCacheSession.h"
 #include "nsCacheService.h"
 #include "nsCRT.h"
 
-NS_IMPL_ADDREF(nsCacheSession)
-NS_IMPL_RELEASE(nsCacheSession)
-
-NS_INTERFACE_MAP_BEGIN(nsCacheSession)
-    NS_INTERFACE_MAP_ENTRY(nsICacheSession)
-    NS_INTERFACE_MAP_ENTRY_CONDITIONAL(
-        nsIOfflineCacheSession, (StoragePolicy() == nsICache::STORE_OFFLINE))
-    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICacheSession)
-NS_INTERFACE_MAP_END
+NS_IMPL_ISUPPORTS1(nsCacheSession, nsICacheSession)
 
 nsCacheSession::nsCacheSession(const char *         clientID,
                                nsCacheStoragePolicy storagePolicy,
                                PRBool               streamBased)
     : mClientID(clientID),
       mInfo(0)
 {
   SetStoragePolicy(storagePolicy);
@@ -130,80 +122,8 @@ NS_IMETHODIMP nsCacheSession::EvictEntri
 }
 
 
 NS_IMETHODIMP nsCacheSession::IsStorageEnabled(PRBool *result)
 {
 
     return nsCacheService::IsStorageEnabledForPolicy(StoragePolicy(), result);
 }
-
-NS_IMETHODIMP nsCacheSession::GetOwnerDomains(PRUint32 * count,
-                                              char *** domains)
-{
-    return nsCacheService::GetOfflineOwnerDomains(this, count, domains);
-}
-
-NS_IMETHODIMP nsCacheSession::GetOwnerURIs(const nsACString & domain,
-                                           PRUint32 * count,
-                                           char *** uris)
-{
-    return nsCacheService::GetOfflineOwnerURIs(this, domain, count, uris);
-}
-
-NS_IMETHODIMP nsCacheSession::SetOwnedKeys(const nsACString & domain,
-                                           const nsACString & uri,
-                                           PRUint32 count,
-                                           const char ** keys)
-{
-    return nsCacheService::SetOfflineOwnedKeys(this, domain, uri, count, keys);
-}
-
-NS_IMETHODIMP nsCacheSession::GetOwnedKeys(const nsACString & domain,
-                                           const nsACString & uri,
-                                           PRUint32 * count,
-                                           char *** keys)
-{
-    return nsCacheService::GetOfflineOwnedKeys(this, domain, uri, count, keys);
-}
-
-NS_IMETHODIMP nsCacheSession::AddOwnedKey(const nsACString & domain,
-                                          const nsACString & uri,
-                                          const nsACString & key)
-{
-    return nsCacheService::AddOfflineOwnedKey(this, domain, uri, key);
-}
-
-NS_IMETHODIMP nsCacheSession::RemoveOwnedKey(const nsACString & domain,
-                                             const nsACString & uri,
-                                             const nsACString & key)
-{
-    return nsCacheService::RemoveOfflineOwnedKey(this, domain, uri, key);
-}
-
-NS_IMETHODIMP nsCacheSession::KeyIsOwned(const nsACString & domain,
-                                         const nsACString & uri,
-                                         const nsACString & key,
-                                         PRBool * isOwned)
-{
-    return nsCacheService::OfflineKeyIsOwned(this, domain, uri, key, isOwned);
-}
-
-NS_IMETHODIMP nsCacheSession::ClearKeysOwnedByDomain(const nsACString & domain)
-{
-    return nsCacheService::ClearOfflineKeysOwnedByDomain(this, domain);
-}
-
-NS_IMETHODIMP nsCacheSession::GetDomainUsage(const nsACString & domain,
-                                             PRUint32 *usage)
-{
-    return nsCacheService::GetOfflineDomainUsage(this, domain, usage);
-}
-
-NS_IMETHODIMP nsCacheSession::EvictUnownedEntries()
-{
-    return nsCacheService::EvictUnownedOfflineEntries(this);
-}
-
-NS_IMETHODIMP nsCacheSession::MergeTemporaryClientID(const nsACString& fromClientID)
-{
-    return nsCacheService::MergeTemporaryClientID(this, fromClientID);
-}
--- a/netwerk/cache/src/nsCacheSession.h
+++ b/netwerk/cache/src/nsCacheSession.h
@@ -41,26 +41,23 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _nsCacheSession_h_
 #define _nsCacheSession_h_
 
 #include "nspr.h"
 #include "nsError.h"
 #include "nsICacheSession.h"
-#include "nsIOfflineCacheSession.h"
 #include "nsString.h"
 
 class nsCacheSession : public nsICacheSession
-                     , public nsIOfflineCacheSession
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSICACHESESSION
-    NS_DECL_NSIOFFLINECACHESESSION
     
     nsCacheSession(const char * clientID, nsCacheStoragePolicy storagePolicy, PRBool streamBased);
     virtual ~nsCacheSession();
     
     nsCString *           ClientID()      { return &mClientID; }
 
     enum SessionInfo {
         eStoragePolicyMask        = 0x000000FF,
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
@@ -38,30 +38,32 @@
 
 #include "nsCache.h"
 #include "nsDiskCache.h"
 #include "nsDiskCacheDeviceSQL.h"
 #include "nsCacheService.h"
 
 #include "nsNetUtil.h"
 #include "nsAutoPtr.h"
+#include "nsEscape.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "nsCRT.h"
 #include "nsIVariant.h"
 
 #include "mozIStorageService.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageFunction.h"
 #include "mozStorageHelper.h"
 
 #include "nsICacheVisitor.h"
 #include "nsISeekableStream.h"
 
 static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
+static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID);
 
 #define LOG(args) CACHE_LOG_DEBUG(args)
 
 static PRUint32 gNextTemporaryClientID = 0;
 
 /*****************************************************************************
  * helpers
  */
@@ -536,19 +538,153 @@ NS_IMETHODIMP
 nsOfflineCacheEntryInfo::GetDataSize(PRUint32 *aDataSize)
 {
   *aDataSize = mRec->dataSize;
   return NS_OK;
 }
 
 
 /******************************************************************************
+ * nsApplicationCache
+ */
+
+class nsApplicationCache : public nsIApplicationCache
+                         , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIAPPLICATIONCACHE
+
+  nsApplicationCache(nsOfflineCacheDevice *device,
+                     const nsACString &group,
+                     const nsACString &clientID);
+
+  virtual ~nsApplicationCache();
+
+  void MarkInvalid() { mValid = PR_FALSE; }
+
+private:
+  nsRefPtr<nsOfflineCacheDevice> mDevice;
+  nsCString mGroup;
+  nsCString mClientID;
+  PRBool mValid;
+};
+
+NS_IMPL_ISUPPORTS2(nsApplicationCache,
+                   nsIApplicationCache,
+                   nsISupportsWeakReference)
+
+nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device,
+                                       const nsACString &group,
+                                       const nsACString &clientID)
+  : mDevice(device)
+  , mGroup(group)
+  , mClientID(clientID)
+  , mValid(PR_TRUE)
+{
+}
+
+nsApplicationCache::~nsApplicationCache()
+{
+  mDevice->mCaches.Remove(mClientID);
+
+  // If this isn't an active cache anymore, it can be destroyed.
+  if (mValid && !mDevice->IsActiveCache(mGroup, mClientID))
+    Discard();
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetGroupID(nsACString &out)
+{
+  out = mGroup;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetClientID(nsACString &out)
+{
+  out = mClientID;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetActive(PRBool *out)
+{
+  *out = mDevice->IsActiveCache(mGroup, mClientID);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::Activate()
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+
+  mDevice->ActivateCache(mGroup, mClientID);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::Discard()
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+
+  mValid = PR_FALSE;
+
+  if (mDevice->IsActiveCache(mGroup, mClientID))
+  {
+    mDevice->DeactivateGroup(mGroup);
+  }
+
+  return mDevice->EvictEntries(mClientID.get());
+}
+
+NS_IMETHODIMP
+nsApplicationCache::MarkEntry(const nsACString &key,
+                              PRUint32 typeBits)
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+
+  return mDevice->MarkEntry(mClientID, key, typeBits);
+}
+
+
+NS_IMETHODIMP
+nsApplicationCache::UnmarkEntry(const nsACString &key,
+                                PRUint32 typeBits)
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+
+  return mDevice->UnmarkEntry(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetTypes(const nsACString &key,
+                             PRUint32 *typeBits)
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+
+  return mDevice->GetTypes(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GatherEntries(PRUint32 typeBits,
+                                  PRUint32 * count,
+                                  char *** keys)
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+
+  return mDevice->GatherEntries(mClientID, typeBits, count, keys);
+}
+
+/******************************************************************************
  * nsOfflineCacheDevice
  */
 
+NS_IMPL_ISUPPORTS1(nsOfflineCacheDevice, nsIApplicationCacheService)
+
 nsOfflineCacheDevice::nsOfflineCacheDevice()
   : mDB(nsnull)
   , mCacheCapacity(0)
   , mDeltaCounter(0)
 {
 }
 
 nsOfflineCacheDevice::~nsOfflineCacheDevice()
@@ -692,16 +828,33 @@ nsOfflineCacheDevice::DeleteData(nsCache
 
   return binding->mDataFile->Remove(PR_FALSE);
 }
 
 /**
  * nsCacheDevice implementation
  */
 
+/* static */
+nsOfflineCacheDevice *
+nsOfflineCacheDevice::GetInstance()
+{
+  nsresult rv;
+  nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID, &rv);
+  NS_ENSURE_SUCCESS(rv, nsnull);
+
+  nsICacheService *iservice = static_cast<nsICacheService*>(serv.get());
+  nsCacheService *cacheService = static_cast<nsCacheService*>(iservice);
+  rv = cacheService->CreateOfflineDevice();
+  NS_ENSURE_SUCCESS(rv, nsnull);
+
+  NS_IF_ADDREF(cacheService->mOfflineDevice);
+  return cacheService->mOfflineDevice;
+}
+
 nsresult
 nsOfflineCacheDevice::Init()
 {
   NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
 
   // SetCacheParentDirectory must have been called
   NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
 
@@ -730,113 +883,187 @@ nsOfflineCacheDevice::Init()
   // XXX in the future we may wish to verify the schema for moz_cache
   //     perhaps using "PRAGMA table_info" ?
 
   // build the table
   //
   //  "Generation" is the data file generation number.
   //  "Flags" is a bit-field indicating the state of the entry.
   //
-  mDB->ExecuteSimpleSQL(
-      NS_LITERAL_CSTRING("CREATE TABLE moz_cache (\n"
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
                          "  ClientID        TEXT,\n"
                          "  Key             TEXT,\n"
                          "  MetaData        BLOB,\n"
                          "  Generation      INTEGER,\n"
                          "  Flags           INTEGER,\n"
                          "  DataSize        INTEGER,\n"
                          "  FetchCount      INTEGER,\n"
                          "  LastFetched     INTEGER,\n"
                          "  LastModified    INTEGER,\n"
-                         "  ExpirationTime  INTEGER\n"
+                         "  ExpirationTime  INTEGER,\n"
+                         "  ItemType        INTEGER DEFAULT 0\n"
                          ");\n"));
-  // maybe the table already exists, so don't bother checking for errors.
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  // build the ownership table
+  // Databases from 1.9.0 don't have the ItemType column.  Add the column
+  // here, but don't worry about failures (the column probably already exists)
   mDB->ExecuteSimpleSQL(
-      NS_LITERAL_CSTRING("CREATE TABLE moz_cache_owners (\n"
-                         " ClientID TEXT,\n"
-                         " Domain TEXT,\n"
-                         " URI TEXT,\n"
-                         " Key TEXT\n"
+    NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
+
+  // Create the table for storing cache groups.  All actions on
+  // moz_cache_groups use the GroupID, so use it as the primary key.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
+                         " GroupID TEXT PRIMARY KEY,\n"
+                         " ActiveClientID TEXT\n"
                          ");\n"));
-  // maybe the table already exists, so don't bother checking for errors.
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Databases from 1.9.0 have a moz_cache_index that should be dropped
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  mDB->ExecuteSimpleSQL(
-      NS_LITERAL_CSTRING("CREATE UNIQUE INDEX moz_cache_index"
-                         " ON moz_cache (ClientID, Key);"));
-  // maybe the index already exists, so don't bother checking for errors.
+  // Key/ClientID pairs should be unique in the database.  All queries
+  // against moz_cache use the Key (which is also the most unique), so
+  // use it as the primary key for this index.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
+                         " moz_cache_key_clientid_index"
+                         " ON moz_cache (Key, ClientID);"));
+  NS_ENSURE_SUCCESS(rv, rv);
 
   mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
   if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
 
   rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 2, mEvictionFunction);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // create all (most) of our statements up front
   struct StatementSql {
     nsCOMPtr<mozIStorageStatement> &statement;
     const char *sql;
     StatementSql (nsCOMPtr<mozIStorageStatement> &aStatement, const char *aSql):
       statement (aStatement), sql (aSql) {}
   } prepared[] = {
     StatementSql ( mStatement_CacheSize,         "SELECT Sum(DataSize) from moz_cache;" ),
-    StatementSql ( mStatement_DomainSize,        "SELECT Sum(moz_cache.DataSize) FROM moz_cache INNER JOIN moz_cache_owners ON moz_cache.ClientID=moz_cache_owners.ClientID AND moz_cache.Key = moz_cache_owners.Key WHERE moz_cache.ClientID=? AND moz_cache_owners.Domain=?;" ),
+    // XXX bug 442810: Restore the ability to monitor individual cache usage
+    StatementSql ( mStatement_DomainSize,        "SELECT 0;"),
     StatementSql ( mStatement_EntryCount,        "SELECT count(*) from moz_cache;" ),
     StatementSql ( mStatement_UpdateEntry,       "UPDATE moz_cache SET MetaData = ?, Flags = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_UpdateEntrySize,   "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_UpdateEntryFlags,  "UPDATE moz_cache SET Flags = ? WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_DeleteEntry,       "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_FindEntry,         "SELECT MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_BindEntry,         "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?,?);" ),
-    StatementSql ( mStatement_ClearOwnership,    "DELETE FROM moz_cache_owners WHERE ClientId = ? AND Domain = ? AND URI = ?;" ),
-    StatementSql ( mStatement_RemoveOwnership,   "DELETE FROM moz_cache_owners WHERE ClientID = ? AND Domain = ? AND URI = ? AND Key = ?;" ),
-    StatementSql ( mStatement_ClearDomain,       "DELETE FROM moz_cache_owners WHERE ClientID = ? AND Domain = ?;" ),
-    StatementSql ( mStatement_AddOwnership,      "INSERT INTO moz_cache_owners (ClientID, Domain, URI, Key) VALUES (?, ?, ?, ?);" ),
-    StatementSql ( mStatement_CheckOwnership,    "SELECT Key From moz_cache_owners WHERE ClientID = ? AND Domain = ? AND URI = ? AND Key = ?;" ),
-    StatementSql ( mStatement_ListOwned,         "SELECT Key FROM moz_cache_owners WHERE ClientID = ? AND Domain = ? AND URI = ?;" ),
-    StatementSql ( mStatement_ListOwners,        "SELECT DISTINCT Domain, URI FROM moz_cache_owners WHERE ClientID = ?;"),
-    StatementSql ( mStatement_ListOwnerDomains,  "SELECT DISTINCT Domain FROM moz_cache_owners WHERE ClientID = ?;"),
-    StatementSql ( mStatement_ListOwnerURIs,     "SELECT DISTINCT URI FROM moz_cache_owners WHERE ClientID = ? AND Domain = ?;"),
-    StatementSql ( mStatement_DeleteConflicts,   "DELETE FROM moz_cache WHERE rowid IN (SELECT old.rowid FROM moz_cache AS old, moz_cache AS new WHERE old.Key = new.Key AND old.ClientID = ? AND new.ClientID = ?)"),
-    StatementSql ( mStatement_DeleteUnowned,     "DELETE FROM moz_cache WHERE rowid IN (SELECT moz_cache.rowid FROM moz_cache LEFT OUTER JOIN moz_cache_owners ON (moz_cache.ClientID = moz_cache_owners.ClientID AND moz_cache.Key = moz_cache_owners.Key) WHERE moz_cache.ClientID = ? AND moz_cache_owners.Domain ISNULL);" ),
-    StatementSql ( mStatement_SwapClientID,       "UPDATE OR REPLACE moz_cache SET ClientID = ? WHERE ClientID = ?;")
+
+    StatementSql ( mStatement_MarkEntry,         "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
+    StatementSql ( mStatement_UnmarkEntry,       "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
+    StatementSql ( mStatement_GetTypes,          "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
+    StatementSql ( mStatement_CleanupUnmarked,   "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
+    StatementSql ( mStatement_GatherEntries,     "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
+
+    StatementSql ( mStatement_ActivateClient,    "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID) VALUES (?, ?);" ),
+    StatementSql ( mStatement_DeactivateGroup,   "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
+    StatementSql ( mStatement_FindClient,        "SELECT ClientID FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;")
   };
-  for (PRUint32 i=0; i<NS_ARRAY_LENGTH(prepared); ++i)
+  for (PRUint32 i = 0; NS_SUCCEEDED(rv) && i < NS_ARRAY_LENGTH(prepared); ++i)
   {
-    rv |= mDB->CreateStatement(nsDependentCString(prepared[i].sql),
-                               getter_AddRefs(prepared[i].statement));
+    LOG(("Creating statement: %s\n", prepared[i].sql));
+
+    rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
+                              getter_AddRefs(prepared[i].statement));
+    NS_ENSURE_SUCCESS(rv, rv);
   }
-  NS_ENSURE_SUCCESS(rv, rv);
 
   // Clear up any dangling active flags
   rv = mDB->ExecuteSimpleSQL(
          NS_LITERAL_CSTRING("UPDATE moz_cache"
                             " SET Flags=(Flags & ~1)"
                             " WHERE (Flags & 1);"));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Clear up dangling temporary sessions
-  EvictionObserver evictionObserver(mDB, mEvictionFunction);
+  rv = InitActiveCaches();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::InitActiveCaches()
+{
+  NS_ENSURE_TRUE(mCaches.Init(), NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(mActiveCachesByGroup.Init(), NS_ERROR_OUT_OF_MEMORY);
 
-  rv = mDB->ExecuteSimpleSQL(
-    NS_LITERAL_CSTRING("DELETE FROM moz_cache"
-                       " WHERE (ClientID GLOB \"TempClient*\")"));
+  nsresult rv = mActiveCaches.Init(5);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageStatement> statement;
+  rv =
+    mDB->CreateStatement(NS_LITERAL_CSTRING("SELECT GroupID, ActiveClientID"
+                                            " FROM moz_cache_groups"),
+                         getter_AddRefs(statement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool hasRows;
+  rv = statement->ExecuteStep(&hasRows);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  evictionObserver.Apply();
+  while (hasRows)
+  {
+    nsCAutoString group;
+    statement->GetUTF8String(0, group);
+    nsCString clientID;
+    statement->GetUTF8String(1, clientID);
+
+    mActiveCaches.Put(clientID);
+    mActiveCachesByGroup.Put(group, new nsCString(clientID));
+
+    rv = statement->ExecuteStep(&hasRows);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   return NS_OK;
 }
 
+/* static */
+PLDHashOperator
+nsOfflineCacheDevice::ShutdownApplicationCache(const nsACString &key,
+                                               nsIWeakReference *weakRef,
+                                               void *ctx)
+{
+  nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(weakRef);
+  if (obj)
+  {
+    nsApplicationCache *appCache = static_cast<nsApplicationCache*>(obj.get());
+    appCache->MarkInvalid();
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 nsresult
 nsOfflineCacheDevice::Shutdown()
 {
   NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
 
+  if (mCaches.IsInitialized())
+    mCaches.EnumerateRead(ShutdownApplicationCache, this);
+
+  EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+  // Delete all rows whose clientID is not an active clientID.
+  nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE rowid IN (SELECT moz_cache.rowid FROM moz_cache LEFT OUTER JOIN moz_cache_groups ON (moz_cache.ClientID = moz_cache_groups.ActiveClientID) WHERE moz_cache_groups.GroupID ISNULL)"));
+
+  if (NS_FAILED(rv))
+    NS_WARNING("Failed to clean up unused application caches.");
+  else
+    evictionObserver.Apply();
+
   mDB = 0;
   mEvictionFunction = 0;
 
   return NS_OK;
 }
 
 const char *
 nsOfflineCacheDevice::GetDeviceID()
@@ -1021,17 +1248,16 @@ nsOfflineCacheDevice::BindEntry(nsCacheE
   rv |= statement->BindInt64Parameter(7, rec.lastFetched);
   rv |= statement->BindInt64Parameter(8, rec.lastModified);
   rv |= statement->BindInt64Parameter(9, rec.expirationTime);
   NS_ENSURE_SUCCESS(rv, rv);
   
   PRBool hasRows;
   rv = statement->ExecuteStep(&hasRows);
   NS_ENSURE_SUCCESS(rv, rv);
-
   NS_ASSERTION(!hasRows, "INSERT should not result in output");
 
   entry->SetData(binding);
   return NS_OK;
 }
 
 void
 nsOfflineCacheDevice::DoomEntry(nsCacheEntry *entry)
@@ -1245,41 +1471,147 @@ nsOfflineCacheDevice::EvictEntries(const
        clientID ? clientID : ""));
 
   // called to evict all entries matching the given clientID.
 
   // need trigger to fire user defined function after a row is deleted
   // so we can delete the corresponding data file.
   EvictionObserver evictionObserver(mDB, mEvictionFunction);
 
-  const char *deleteCmd;
+  nsCOMPtr<mozIStorageStatement> statement;
+  nsresult rv;
   if (clientID)
   {
-    deleteCmd =
-      PR_smprintf("DELETE FROM moz_cache WHERE ClientID=%q AND Flags=0;",
-                  clientID);
-    if (!deleteCmd)
-      return NS_ERROR_OUT_OF_MEMORY;
+    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=? AND Flags = 0;"),
+                              getter_AddRefs(statement));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = statement->BindUTF8StringParameter(0, nsDependentCString(clientID));
+    NS_ENSURE_SUCCESS(rv, rv);
   }
   else
   {
-    deleteCmd = "DELETE FROM moz_cache WHERE Flags = 0;";
+    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE Flags = 0;"),
+                              getter_AddRefs(statement));
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  nsresult rv = mDB->ExecuteSimpleSQL(nsDependentCString(deleteCmd));
-  if (clientID)
-    PR_smprintf_free((char *) deleteCmd);
+  rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   evictionObserver.Apply();
 
   return NS_OK;
 }
 
 nsresult
+nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
+                                const nsACString &key,
+                                PRUint32 typeBits)
+{
+  LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
+       clientID.get(), PromiseFlatCString(key).get(), typeBits));
+
+  AutoResetStatement statement(mStatement_MarkEntry);
+  nsresult rv = statement->BindInt32Parameter(0, typeBits);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindUTF8StringParameter(1, clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindUTF8StringParameter(2, key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID,
+                                  const nsACString &key,
+                                  PRUint32 typeBits)
+{
+  LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
+       clientID.get(), PromiseFlatCString(key).get(), typeBits));
+
+  AutoResetStatement statement(mStatement_UnmarkEntry);
+  nsresult rv = statement->BindInt32Parameter(0, typeBits);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindUTF8StringParameter(1, clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindUTF8StringParameter(2, key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Remove the entry if it is now empty.
+
+  EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+  AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
+  rv = cleanupStatement->BindUTF8StringParameter(0, clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = cleanupStatement->BindUTF8StringParameter(1, key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = cleanupStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  evictionObserver.Apply();
+
+  return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
+                               const nsACString &key,
+                               PRUint32 *typeBits)
+{
+  LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
+       clientID.get(), PromiseFlatCString(key).get()));
+
+  AutoResetStatement statement(mStatement_GetTypes);
+  nsresult rv = statement->BindUTF8StringParameter(0, clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindUTF8StringParameter(1, key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool hasRows;
+  rv = statement->ExecuteStep(&hasRows);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!hasRows)
+    return NS_ERROR_CACHE_KEY_NOT_FOUND;
+
+  *typeBits = statement->AsInt32(0);
+
+  return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GatherEntries(const nsCString &clientID,
+                                    PRUint32 typeBits,
+                                    PRUint32 *count,
+                                    char ***keys)
+{
+  LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
+       clientID.get(), typeBits));
+
+  AutoResetStatement statement(mStatement_GatherEntries);
+  nsresult rv = statement->BindUTF8StringParameter(0, clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->BindInt32Parameter(1, typeBits);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys);
+}
+
+nsresult
 nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
                                      PRUint32 resultIndex,
                                      PRUint32 * count,
                                      char *** values)
 {
   PRBool hasRows;
   nsresult rv = statement->ExecuteStep(&hasRows);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -1307,322 +1639,200 @@ nsOfflineCacheDevice::RunSimpleQuery(moz
     }
   }
 
   *values = ret;
 
   return NS_OK;
 }
 
-nsresult
-nsOfflineCacheDevice::GetOwnerDomains(const char * clientID,
-                                      PRUint32 * count,
-                                      char *** domains)
+NS_IMETHODIMP
+nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group,
+                                             nsIApplicationCache **out)
 {
-  LOG(("nsOfflineCacheDevice::GetOwnerDomains [cid=%s]\n", clientID));
-
-  AutoResetStatement statement(mStatement_ListOwnerDomains);
-  nsresult rv = statement->BindUTF8StringParameter(
-                                          0, nsDependentCString(clientID));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return RunSimpleQuery(mStatement_ListOwnerDomains, 0, count, domains);
-}
-
-nsresult
-nsOfflineCacheDevice::GetOwnerURIs(const char * clientID,
-                                   const nsACString & ownerDomain,
-                                   PRUint32 * count,
-                                   char *** domains)
-{
-  LOG(("nsOfflineCacheDevice::GetOwnerURIs [cid=%s]\n", clientID));
-
-  AutoResetStatement statement(mStatement_ListOwnerURIs);
-  nsresult rv = statement->BindUTF8StringParameter(
-                                          0, nsDependentCString(clientID));
-  rv = statement->BindUTF8StringParameter(
-                                          1, ownerDomain);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return RunSimpleQuery(mStatement_ListOwnerURIs, 0, count, domains);
-}
+  *out = nsnull;
 
-nsresult
-nsOfflineCacheDevice::SetOwnedKeys(const char * clientID,
-                                   const nsACString & ownerDomain,
-                                   const nsACString & ownerURI,
-                                   PRUint32 count,
-                                   const char ** keys)
-{
-  LOG(("nsOfflineCacheDevice::SetOwnedKeys [cid=%s]\n", clientID));
-  mozStorageTransaction transaction(mDB, PR_FALSE);
-
-  nsDependentCString clientIDStr(clientID);
-
-  AutoResetStatement clearStatement(mStatement_ClearOwnership);
-  nsresult rv = clearStatement->BindUTF8StringParameter(
-                                               0, clientIDStr);
-  rv |= clearStatement->BindUTF8StringParameter(1, ownerDomain);
-  rv |= clearStatement->BindUTF8StringParameter(2, ownerURI);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = clearStatement->Execute();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  for (PRUint32 i = 0; i < count; i++)
-  {
-    AutoResetStatement insertStatement(mStatement_AddOwnership);
-    rv = insertStatement->BindUTF8StringParameter(0, clientIDStr);
-    rv |= insertStatement->BindUTF8StringParameter(1, ownerDomain);
-    rv |= insertStatement->BindUTF8StringParameter(2, ownerURI);
-    rv |= insertStatement->BindUTF8StringParameter(3, nsDependentCString(keys[i]));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = insertStatement->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
+  nsCString clientID;
+  // Some characters are special in the clientID.  Escape the groupID
+  // before putting it in to the client key.
+  if (!NS_Escape(nsCString(group), clientID, url_Path)) {
+    return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  return transaction.Commit();
-}
+  PRTime now = PR_Now();
+
+  // Include the timestamp to guarantee uniqueness across runs, and
+  // the gNextTemporaryClientID for uniqueness within a second.
+  clientID.Append(nsPrintfCString(64, "|%016lld|%d",
+                                  now / PR_USEC_PER_SEC,
+                                  gNextTemporaryClientID++));
 
-nsresult
-nsOfflineCacheDevice::GetOwnedKeys(const char * clientID,
-                                   const nsACString & ownerDomain,
-                                   const nsACString & ownerURI,
-                                   PRUint32 * count,
-                                   char *** keys)
-{
-  LOG(("nsOfflineCacheDevice::GetOwnedKeys [cid=%s]\n", clientID));
+  nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this,
+                                                               group,
+                                                               clientID);
+  if (!cache)
+    return NS_ERROR_OUT_OF_MEMORY;
 
-  AutoResetStatement statement(mStatement_ListOwned);
-  nsresult rv = statement->BindUTF8StringParameter(
-                                           0, nsDependentCString(clientID));
-  rv |= statement->BindUTF8StringParameter(1, ownerDomain);
-  rv |= statement->BindUTF8StringParameter(2, ownerURI);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(cache);
+  if (!weak)
+    return NS_ERROR_OUT_OF_MEMORY;
 
-  return RunSimpleQuery(mStatement_ListOwned, 0, count, keys);
+  mCaches.Put(clientID, weak);
+
+  cache.swap(*out);
+
+  return NS_OK;
 }
 
-nsresult
-nsOfflineCacheDevice::AddOwnedKey(const char * clientID,
-                                  const nsACString & ownerDomain,
-                                  const nsACString & ownerURI,
-                                  const nsACString & key)
+NS_IMETHODIMP
+nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
+                                          nsIApplicationCache **out)
 {
-  LOG(("nsOfflineCacheDevice::AddOwnedKey [cid=%s]\n", clientID));
+  *out = nsnull;
 
-  PRBool isOwned;
-  nsresult rv = KeyIsOwned(clientID, ownerDomain, ownerURI, key, &isOwned);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (isOwned) return NS_OK;
+  nsCOMPtr<nsIApplicationCache> cache;
 
-  AutoResetStatement statement(mStatement_AddOwnership);
-  rv = statement->BindUTF8StringParameter(0, nsDependentCString(clientID));
-  rv |= statement->BindUTF8StringParameter(1, ownerDomain);
-  rv |= statement->BindUTF8StringParameter(2, ownerURI);
-  rv |= statement->BindUTF8StringParameter(3, key);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsWeakPtr weak;
+  if (mCaches.Get(clientID, getter_AddRefs(weak)))
+    cache = do_QueryReferent(weak);
 
-  return statement->Execute();
-}
+  if (!cache)
+  {
+    nsCString group;
+    nsresult rv = GetGroupForCache(clientID, group);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-nsresult
-nsOfflineCacheDevice::RemoveOwnedKey(const char * clientID,
-                                     const nsACString & ownerDomain,
-                                     const nsACString & ownerURI,
-                                     const nsACString & key)
-{
-  LOG(("nsOfflineCacheDevice::RemoveOwnedKey [cid=%s]\n", clientID));
+    if (group.IsEmpty()) {
+      return NS_OK;
+    }
 
-  PRBool isOwned;
-  nsresult rv = KeyIsOwned(clientID, ownerDomain, ownerURI, key, &isOwned);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (!isOwned) return NS_ERROR_NOT_AVAILABLE;
+    cache = new nsApplicationCache(this, group, clientID);
+    weak = do_GetWeakReference(cache);
+    if (!weak)
+      return NS_ERROR_OUT_OF_MEMORY;
 
-  AutoResetStatement statement(mStatement_RemoveOwnership);
-  rv = statement->BindUTF8StringParameter(0, nsDependentCString(clientID));
-  rv |= statement->BindUTF8StringParameter(1, ownerDomain);
-  rv |= statement->BindUTF8StringParameter(2, ownerURI);
-  rv |= statement->BindUTF8StringParameter(3, key);
-  NS_ENSURE_SUCCESS(rv, rv);
+    mCaches.Put(clientID, weak);
+  }
 
-  return statement->Execute();
+  cache.swap(*out);
+
+  return NS_OK;
 }
 
-nsresult
-nsOfflineCacheDevice::KeyIsOwned(const char * clientID,
-                                 const nsACString & ownerDomain,
-                                 const nsACString & ownerURI,
-                                 const nsACString & key,
-                                 PRBool * isOwned)
+NS_IMETHODIMP
+nsOfflineCacheDevice::GetActiveCache(const nsACString &group,
+                                     nsIApplicationCache **out)
 {
-  AutoResetStatement statement(mStatement_CheckOwnership);
-  nsresult rv = statement->BindUTF8StringParameter(
-                                           0, nsDependentCString(clientID));
-  rv |= statement->BindUTF8StringParameter(1, ownerDomain);
-  rv |= statement->BindUTF8StringParameter(2, ownerURI);
-  rv |= statement->BindUTF8StringParameter(3, key);
-  NS_ENSURE_SUCCESS(rv, rv);
+  *out = nsnull;
 
-  return statement->ExecuteStep(isOwned);
+  nsCString *clientID;
+  if (mActiveCachesByGroup.Get(group, &clientID))
+    return GetApplicationCache(*clientID, out);
+
+  return NS_OK;
 }
 
-nsresult
-nsOfflineCacheDevice::ClearKeysOwnedByDomain(const char *clientID,
-                                             const nsACString &domain)
+NS_IMETHODIMP
+nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
+                                             nsIApplicationCache **out)
 {
-  LOG(("nsOfflineCacheDevice::ClearKeysOwnedByDomain [cid=%s]\n", clientID));
-
-  AutoResetStatement statement(mStatement_ClearDomain);
-  nsresult rv = statement->BindUTF8StringParameter(
-                                           0, nsDependentCString(clientID));
-  rv |= statement->BindUTF8StringParameter(1, domain);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return statement->Execute();
-}
+  *out = nsnull;
 
-nsresult
-nsOfflineCacheDevice::GetDomainUsage(const char *clientID,
-                                     const nsACString &domain,
-                                     PRUint32 *usage)
-{
-  LOG(("nsOfflineCacheDevice::GetDomainUsage [cid=%s]\n", clientID));
-
-  *usage = 0;
-
-  AutoResetStatement statement(mStatement_DomainSize);
-  nsresult rv;
-      
-  rv = statement->BindUTF8StringParameter(0, nsDependentCString(clientID));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = statement->BindUTF8StringParameter(1, domain);
+  AutoResetStatement statement(mStatement_FindClient);
+  nsresult rv = statement->BindUTF8StringParameter(
+                                           0, key);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool hasRows;
   rv = statement->ExecuteStep(&hasRows);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!hasRows)
-      return NS_OK;
-
-  *usage = statement->AsInt32(0);
-
-  return NS_OK;
-}
-
-nsresult
-nsOfflineCacheDevice::EvictUnownedEntries(const char *clientID)
-{
-  LOG(("nsOfflineCacheDevice::EvictUnownedEntries [cid=%s]\n", clientID));
-  EvictionObserver evictionObserver(mDB, mEvictionFunction);
+  while (hasRows) {
+    nsCString clientID;
+    rv = statement->GetUTF8String(0, clientID);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-  AutoResetStatement statement(mStatement_DeleteUnowned);
-  nsresult rv = statement->BindUTF8StringParameter(
-                                              0, nsDependentCString(clientID));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = statement->Execute();
-  NS_ENSURE_SUCCESS(rv, rv);
+    if (mActiveCaches.Contains(clientID))
+      return GetApplicationCache(clientID, out);
 
-  evictionObserver.Apply();
-  return NS_OK;
-}
-
-nsresult
-nsOfflineCacheDevice::CreateTemporaryClientID(nsACString &clientID)
-{
-  nsCAutoString str;
-  str.AssignLiteral("TempClient");
-  str.AppendInt(gNextTemporaryClientID++);
-
-  clientID.Assign(str);
+    rv = statement->ExecuteStep(&hasRows);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   return NS_OK;
 }
 
 nsresult
-nsOfflineCacheDevice::MergeTemporaryClientID(const char *clientID,
-                                             const char *fromClientID)
+nsOfflineCacheDevice::ActivateCache(const nsCSubstring &group,
+                                    const nsCSubstring &clientID)
 {
-  LOG(("nsOfflineCacheDevice::MergeTemporaryClientID [cid=%s, from=%s]\n",
-       clientID, fromClientID));
-  mozStorageTransaction transaction(mDB, PR_FALSE);
+  AutoResetStatement statement(mStatement_ActivateClient);
+  nsresult rv = statement->BindUTF8StringParameter(0, group);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindUTF8StringParameter(1, clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  // Move over ownerships
-  AutoResetStatement listOwnersStatement(mStatement_ListOwners);
-  nsresult rv = listOwnersStatement->BindUTF8StringParameter(
-                                          0, nsDependentCString(fromClientID));
+  rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // List all the owners in the new session
-  nsTArray<nsCString> domainArray;
-  nsTArray<nsCString> uriArray;
-
-  PRBool hasRows;
-  rv = listOwnersStatement->ExecuteStep(&hasRows);
-  NS_ENSURE_SUCCESS(rv, rv);
-  while (hasRows)
+  nsCString *active;
+  if (mActiveCachesByGroup.Get(group, &active))
   {
-    PRUint32 length;
-    domainArray.AppendElement(
-      nsDependentCString(listOwnersStatement->AsSharedUTF8String(0, &length)));
-    uriArray.AppendElement(
-      nsDependentCString(listOwnersStatement->AsSharedUTF8String(1, &length)));
+    mActiveCaches.Remove(*active);
+    mActiveCachesByGroup.Remove(group);
+    active = nsnull;
+  }
 
-    rv = listOwnersStatement->ExecuteStep(&hasRows);
-    NS_ENSURE_SUCCESS(rv, rv);
+  if (!clientID.IsEmpty())
+  {
+    mActiveCaches.Put(clientID);
+    mActiveCachesByGroup.Put(group, new nsCString(clientID));
   }
 
-  // Now move over each ownership set
-  for (PRUint32 i = 0; i < domainArray.Length(); i++) {
-    PRUint32 count;
-    char **keys;
-    rv = GetOwnedKeys(fromClientID, domainArray[i], uriArray[i],
-                      &count, &keys);
-    NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+PRBool
+nsOfflineCacheDevice::IsActiveCache(const nsCSubstring &group,
+                                    const nsCSubstring &clientID)
+{
+  nsCString *active = nsnull;
+  return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
+}
 
-    rv = SetOwnedKeys(clientID, domainArray[i], uriArray[i],
-                      count, const_cast<const char **>(keys));
-    NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, keys);
-    NS_ENSURE_SUCCESS(rv, rv);
+nsresult
+nsOfflineCacheDevice::DeactivateGroup(const nsCSubstring &group)
+{
+  nsCString *active = nsnull;
 
-    // Now clear out the temporary session's copy
-    rv = SetOwnedKeys(fromClientID, domainArray[i], uriArray[i], 0, 0);
-    NS_ENSURE_SUCCESS(rv, rv);
+  AutoResetStatement statement(mStatement_DeactivateGroup);
+  nsresult rv = statement->BindUTF8StringParameter(
+                                           0, group);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mActiveCachesByGroup.Get(group, &active))
+  {
+    mActiveCaches.Remove(*active);
+    mActiveCachesByGroup.Remove(group);
+    active = nsnull;
   }
 
-  EvictionObserver evictionObserver(mDB, mEvictionFunction);
-
-  AutoResetStatement deleteStatement(mStatement_DeleteConflicts);
-  rv = deleteStatement->BindUTF8StringParameter(
-                                          0, nsDependentCString(clientID));
-  rv |= deleteStatement->BindUTF8StringParameter(
-                                          1, nsDependentCString(fromClientID));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = deleteStatement->Execute();
-  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
 
-  AutoResetStatement swapStatement(mStatement_SwapClientID);
-  rv = swapStatement->BindUTF8StringParameter(
-                                          0, nsDependentCString(clientID));
-  rv |= swapStatement->BindUTF8StringParameter(
-                                          1, nsDependentCString(fromClientID));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = swapStatement->Execute();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = transaction.Commit();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  evictionObserver.Apply();
+nsresult
+nsOfflineCacheDevice::GetGroupForCache(const nsACString &clientID,
+                                       nsCString &out)
+{
+  out.Assign(clientID);
+  out.Truncate(out.FindChar('|'));
+  NS_UnescapeURL(out);
 
   return NS_OK;
 }
 
 /**
  * Preference accessors
  */
 
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.h
@@ -34,25 +34,31 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsOfflineCacheDevice_h__
 #define nsOfflineCacheDevice_h__
 
 #include "nsCacheDevice.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheService.h"
 #include "nsILocalFile.h"
 #include "nsIObserver.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageFunction.h"
 #include "nsIFile.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsVoidArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsHashSets.h"
+#include "nsWeakReference.h"
 
 class nsOfflineCacheDevice;
 
 class nsOfflineCacheEvictionFunction : public mozIStorageFunction {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGEFUNCTION
 
@@ -65,26 +71,32 @@ public:
 
 private:
   nsOfflineCacheDevice *mDevice;
   nsCOMArray<nsIFile> mItems;
 
 };
 
 class nsOfflineCacheDevice : public nsCacheDevice
+                           , public nsIApplicationCacheService
 {
 public:
   nsOfflineCacheDevice();
 
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIAPPLICATIONCACHESERVICE
+
   /**
    * nsCacheDevice methods
    */
 
   virtual ~nsOfflineCacheDevice();
 
+  static nsOfflineCacheDevice *GetInstance();
+
   virtual nsresult        Init();
   virtual nsresult        Shutdown();
 
   virtual const char *    GetDeviceID(void);
   virtual nsCacheEntry *  FindEntry(nsCString * key, PRBool *collision);
   virtual nsresult        DeactivateEntry(nsCacheEntry * entry);
   virtual nsresult        BindEntry(nsCacheEntry * entry);
   virtual void            DoomEntry( nsCacheEntry * entry );
@@ -138,46 +150,69 @@ public:
   nsresult                KeyIsOwned(const char *             clientID,
                                      const nsACString &       ownerDomain,
                                      const nsACString &       ownerURI,
                                      const nsACString &       key,
                                      PRBool *                 isOwned);
 
   nsresult                ClearKeysOwnedByDomain(const char *clientID,
                                                  const nsACString &ownerDomain);
-  nsresult                GetDomainUsage(const char *clientID,
-                                         const nsACString &ownerDomain,
-                                         PRUint32 *usage);
   nsresult                EvictUnownedEntries(const char *clientID);
 
-  nsresult                CreateTemporaryClientID(nsACString &clientID);
-  nsresult                MergeTemporaryClientID(const char *clientID,
-                                                 const char *fromClientID);
-
+  nsresult                ActivateCache(const nsCSubstring &group,
+                                        const nsCSubstring &clientID);
+  PRBool                  IsActiveCache(const nsCSubstring &group,
+                                        const nsCSubstring &clientID);
+  nsresult                DeactivateGroup(const nsCSubstring &group);
+  nsresult                GetGroupForCache(const nsCSubstring &clientID,
+                                           nsCString &out);
 
   /**
    * Preference accessors
    */
 
   void                    SetCacheParentDirectory(nsILocalFile * parentDir);
   void                    SetCapacity(PRUint32  capacity);
 
   nsILocalFile *          CacheDirectory() { return mCacheDirectory; }
   PRUint32                CacheCapacity() { return mCacheCapacity; }
   PRUint32                CacheSize();
   PRUint32                EntryCount();
   
 private:
+  friend class nsApplicationCache;
+
+  static PLDHashOperator ShutdownApplicationCache(const nsACString &key,
+                                                  nsIWeakReference *weakRef,
+                                                  void *ctx);
+
   PRBool   Initialized() { return mDB != nsnull; }
+
+  nsresult InitActiveCaches();
   nsresult UpdateEntry(nsCacheEntry *entry);
   nsresult UpdateEntrySize(nsCacheEntry *entry, PRUint32 newSize);
   nsresult DeleteEntry(nsCacheEntry *entry, PRBool deleteData);
   nsresult DeleteData(nsCacheEntry *entry);
   nsresult EnableEvictionObserver();
   nsresult DisableEvictionObserver();
+
+  nsresult MarkEntry(const nsCString &clientID,
+                     const nsACString &key,
+                     PRUint32 typeBits);
+  nsresult UnmarkEntry(const nsCString &clientID,
+                       const nsACString &key,
+                       PRUint32 typeBits);
+  nsresult GetTypes(const nsCString &clientID,
+                    const nsACString &key,
+                    PRUint32 *typeBits);
+  nsresult GatherEntries(const nsCString &clientID,
+                         PRUint32 typeBits,
+                         PRUint32 *count,
+                         char *** values);
+
   nsresult RunSimpleQuery(mozIStorageStatement *statment,
                           PRUint32 resultIndex,
                           PRUint32 * count,
                           char *** values);
 
   nsCOMPtr<mozIStorageConnection>          mDB;
   nsRefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
 
@@ -185,27 +220,28 @@ private:
   nsCOMPtr<mozIStorageStatement>  mStatement_DomainSize;
   nsCOMPtr<mozIStorageStatement>  mStatement_EntryCount;
   nsCOMPtr<mozIStorageStatement>  mStatement_UpdateEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_UpdateEntrySize;
   nsCOMPtr<mozIStorageStatement>  mStatement_UpdateEntryFlags;
   nsCOMPtr<mozIStorageStatement>  mStatement_DeleteEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_FindEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_BindEntry;
-  nsCOMPtr<mozIStorageStatement>  mStatement_ClearOwnership;
-  nsCOMPtr<mozIStorageStatement>  mStatement_RemoveOwnership;
   nsCOMPtr<mozIStorageStatement>  mStatement_ClearDomain;
-  nsCOMPtr<mozIStorageStatement>  mStatement_AddOwnership;
-  nsCOMPtr<mozIStorageStatement>  mStatement_CheckOwnership;
-  nsCOMPtr<mozIStorageStatement>  mStatement_DeleteConflicts;
-  nsCOMPtr<mozIStorageStatement>  mStatement_DeleteUnowned;
-  nsCOMPtr<mozIStorageStatement>  mStatement_ListOwned;
-  nsCOMPtr<mozIStorageStatement>  mStatement_ListOwners;
-  nsCOMPtr<mozIStorageStatement>  mStatement_ListOwnerDomains;
-  nsCOMPtr<mozIStorageStatement>  mStatement_ListOwnerURIs;
-  nsCOMPtr<mozIStorageStatement>  mStatement_SwapClientID;
+  nsCOMPtr<mozIStorageStatement>  mStatement_MarkEntry;
+  nsCOMPtr<mozIStorageStatement>  mStatement_UnmarkEntry;
+  nsCOMPtr<mozIStorageStatement>  mStatement_GetTypes;
+  nsCOMPtr<mozIStorageStatement>  mStatement_CleanupUnmarked;
+  nsCOMPtr<mozIStorageStatement>  mStatement_GatherEntries;
+  nsCOMPtr<mozIStorageStatement>  mStatement_ActivateClient;
+  nsCOMPtr<mozIStorageStatement>  mStatement_DeactivateGroup;
+  nsCOMPtr<mozIStorageStatement>  mStatement_FindClient;
 
   nsCOMPtr<nsILocalFile>          mCacheDirectory;
   PRUint32                        mCacheCapacity;
   PRInt32                         mDeltaCounter;
+
+  nsInterfaceHashtable<nsCStringHashKey, nsIWeakReference> mCaches;
+  nsClassHashtable<nsCStringHashKey, nsCString> mActiveCachesByGroup;
+  nsCStringHashSet mActiveCaches;
 };
 
 #endif // nsOfflineCacheDevice_h__
--- a/netwerk/protocol/http/src/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/src/nsHttpChannel.cpp
@@ -42,16 +42,17 @@
 #include "nsHttpChannel.h"
 #include "nsHttpTransaction.h"
 #include "nsHttpConnection.h"
 #include "nsHttpHandler.h"
 #include "nsHttpAuthCache.h"
 #include "nsHttpResponseHead.h"
 #include "nsHttp.h"
 #include "nsIHttpAuthenticator.h"
+#include "nsIApplicationCacheService.h"
 #include "nsIAuthInformation.h"
 #include "nsIAuthPrompt2.h"
 #include "nsIAuthPromptProvider.h"
 #include "nsIStringBundle.h"
 #include "nsXPCOM.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIURL.h"
 #include "nsIIDNService.h"
@@ -1366,36 +1367,71 @@ nsHttpChannel::OpenCacheEntry(PRBool off
             return NS_ERROR_NOT_AVAILABLE;
         accessRequested = nsICache::ACCESS_READ;
     }
     else if (BYPASS_LOCAL_CACHE(mLoadFlags))
         accessRequested = nsICache::ACCESS_WRITE; // replace cache entry
     else
         accessRequested = nsICache::ACCESS_READ_WRITE; // normal browsing
 
+    if (!mApplicationCache) {
+        // Pick up an application cache from the load group if available
+        nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
+        GetCallback(appCacheContainer);
+
+        if (appCacheContainer) {
+            appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
+        }
+
+        if ((mLoadFlags & LOAD_CHECK_OFFLINE_CACHE) && !mApplicationCache) {
+            // We're supposed to load from an application cache, but
+            // one was not supplied by the load group.  Ask the
+            // application cache service to choose one for us.
+            nsCOMPtr<nsIApplicationCacheService> appCacheService =
+                do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
+            if (appCacheService) {
+                nsresult rv = appCacheService->ChooseApplicationCache
+                    (cacheKey, getter_AddRefs(mApplicationCache));
+                NS_ENSURE_SUCCESS(rv, rv);
+            }
+        }
+    }
+
     nsCOMPtr<nsICacheSession> session;
-    if (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE) {
-        // when LOAD_CHECK_OFFLINE_CACHE set prefer the offline cache
-        rv = gHttpHandler->GetCacheSession(nsICache::STORE_OFFLINE,
-                                           getter_AddRefs(session));
-        if (NS_FAILED(rv)) return rv;
-
-        // we'll try to synchronously open the cache entry... however, it may be
-        // in use and not yet validated, in which case we'll try asynchronously
-        // opening the cache entry.
+
+    // If we have an application cache, we check it first.
+    if (mApplicationCache) {
+        nsCAutoString appCacheClientID;
+        mApplicationCache->GetClientID(appCacheClientID);
+
+        nsCOMPtr<nsICacheService> serv =
+            do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = serv->CreateSession(appCacheClientID.get(),
+                                 nsICache::STORE_OFFLINE,
+                                 nsICache::STREAM_BASED,
+                                 getter_AddRefs(session));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // we'll try to synchronously open the cache entry... however,
+        // it may be in use and not yet validated, in which case we'll
+        // try asynchronously opening the cache entry.
         //
-        // we need open in ACCESS_READ only because we don't want to overwrite
-        // the offline cache entry non-atomically so ACCESS_READ
-        // will prevent us from writing to the HTTP-offline cache as a
-        // normal cache entry.
-        rv = session->OpenCacheEntry(cacheKey, nsICache::ACCESS_READ, PR_FALSE,
+        // we need open in ACCESS_READ only because we don't want to
+        // overwrite the offline cache entry non-atomically so
+        // ACCESS_READ will prevent us from writing to the
+        // HTTP-offline cache as a normal cache entry.  XXX: this
+        // needs to be checked.
+        rv = session->OpenCacheEntry(cacheKey,
+                                     nsICache::ACCESS_READ, PR_FALSE,
                                      getter_AddRefs(mCacheEntry));
     }
 
-    if (!(mLoadFlags & LOAD_CHECK_OFFLINE_CACHE) ||
+    if (!mApplicationCache ||
         (NS_FAILED(rv) && rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION)) 
     {
         rv = gHttpHandler->GetCacheSession(storagePolicy,
                                            getter_AddRefs(session));
         if (NS_FAILED(rv)) return rv;
 
         rv = session->OpenCacheEntry(cacheKey, accessRequested, PR_FALSE,
                                      getter_AddRefs(mCacheEntry));
@@ -1452,30 +1488,28 @@ nsHttpChannel::OpenOfflineCacheEntryForW
         // don't use the cache if our consumer is making a conditional request
         // (see bug 331825).
         return NS_OK;
     }
 
     nsCAutoString cacheKey;
     GenerateCacheKey(cacheKey);
 
+    NS_ENSURE_TRUE(!mOfflineCacheClientID.IsEmpty(),
+                   NS_ERROR_NOT_AVAILABLE);
+
     nsCOMPtr<nsICacheSession> session;
-    if (!mOfflineCacheClientID.IsEmpty()) {
-        nsCOMPtr<nsICacheService> serv =
-            do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
-        if (NS_FAILED(rv)) return rv;
-
-        rv = serv->CreateSession(mOfflineCacheClientID.get(),
-                                 nsICache::STORE_OFFLINE,
-                                 nsICache::STREAM_BASED,
-                                 getter_AddRefs(session));
-    } else {
-        rv = gHttpHandler->GetCacheSession(nsICache::STORE_OFFLINE,
-                                           getter_AddRefs(session));
-    }
+    nsCOMPtr<nsICacheService> serv =
+        do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+    if (NS_FAILED(rv)) return rv;
+
+    rv = serv->CreateSession(mOfflineCacheClientID.get(),
+                             nsICache::STORE_OFFLINE,
+                             nsICache::STREAM_BASED,
+                             getter_AddRefs(session));
     if (NS_FAILED(rv)) return rv;
 
     rv = session->OpenCacheEntry(cacheKey, nsICache::ACCESS_READ_WRITE,
                                  PR_FALSE, getter_AddRefs(mOfflineCacheEntry));
 
     if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
         // access to the cache entry has been denied (because the cache entry
         // is probably in use by another channel).  Either the cache is being
@@ -3382,16 +3416,17 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
     NS_INTERFACE_MAP_ENTRY(nsICacheListener)
     NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
     NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
     NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
     NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
     NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
     NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
     NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+    NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
 NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIRequest
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::GetName(nsACString &aName)
@@ -4872,16 +4907,34 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnec
 
     rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
     if (NS_FAILED(rv)) return rv;
 
     return mTransactionPump->AsyncRead(this, nsnull);
 }
 
 //-----------------------------------------------------------------------------
+// nsHttpChannel::nsIApplicationCacheContainer
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsHttpChannel::GetApplicationCache(nsIApplicationCache **out)
+{
+    NS_IF_ADDREF(*out = mApplicationCache);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache)
+{
+    mApplicationCache = appCache;
+    return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
 // nsHttpChannel::nsContentEncodings <public>
 //-----------------------------------------------------------------------------
 
 nsHttpChannel::nsContentEncodings::nsContentEncodings(nsIHttpChannel* aChannel,
                                                           const char* aEncodingHeader) :
     mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(PR_FALSE)
 {
     mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
--- a/netwerk/protocol/http/src/nsHttpChannel.h
+++ b/netwerk/protocol/http/src/nsHttpChannel.h
@@ -61,18 +61,21 @@
 #include "nsIIOService.h"
 #include "nsIURI.h"
 #include "nsILoadGroup.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIInputStream.h"
 #include "nsIProgressEventSink.h"
 #include "nsICachingChannel.h"
+#include "nsICacheSession.h"
 #include "nsICacheEntryDescriptor.h"
 #include "nsICacheListener.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheContainer.h"
 #include "nsIEncodedChannel.h"
 #include "nsITransport.h"
 #include "nsIUploadChannel.h"
 #include "nsIStringEnumerator.h"
 #include "nsIOutputStream.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIPrompt.h"
 #include "nsIResumableChannel.h"
@@ -98,16 +101,17 @@ class nsHttpChannel : public nsHashPrope
                     , public nsIUploadChannel
                     , public nsICacheListener
                     , public nsIEncodedChannel
                     , public nsITransportEventSink
                     , public nsIResumableChannel
                     , public nsISupportsPriority
                     , public nsIProtocolProxyCallback
                     , public nsIProxiedChannel
+                    , public nsIApplicationCacheContainer
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIREQUEST
     NS_DECL_NSICHANNEL
     NS_DECL_NSIHTTPCHANNEL
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
@@ -116,16 +120,17 @@ public:
     NS_DECL_NSICACHELISTENER
     NS_DECL_NSIENCODEDCHANNEL
     NS_DECL_NSIHTTPCHANNELINTERNAL
     NS_DECL_NSITRANSPORTEVENTSINK
     NS_DECL_NSIRESUMABLECHANNEL
     NS_DECL_NSISUPPORTSPRIORITY
     NS_DECL_NSIPROTOCOLPROXYCALLBACK
     NS_DECL_NSIPROXIEDCHANNEL
+    NS_DECL_NSIAPPLICATIONCACHECONTAINER
 
     nsHttpChannel();
     virtual ~nsHttpChannel();
 
     nsresult Init(nsIURI *uri,
                   PRUint8 capabilities,
                   nsProxyInfo* proxyInfo);
 
@@ -258,16 +263,18 @@ private:
     nsCacheAccessMode                 mCacheAccess;
     PRUint32                          mPostID;
     PRUint32                          mRequestTime;
 
     nsCOMPtr<nsICacheEntryDescriptor> mOfflineCacheEntry;
     nsCacheAccessMode                 mOfflineCacheAccess;
     nsCString                         mOfflineCacheClientID;
 
+    nsCOMPtr<nsIApplicationCache>     mApplicationCache;
+
     // auth specific data
     nsISupports                      *mProxyAuthContinuationState;
     nsCString                         mProxyAuthType;
     nsISupports                      *mAuthContinuationState;
     nsCString                         mAuthType;
     nsHttpAuthIdentity                mIdent;
     nsHttpAuthIdentity                mProxyIdent;
 
--- a/uriloader/prefetch/nsIOfflineCacheUpdate.idl
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -110,31 +110,37 @@ interface nsIOfflineCacheUpdateObserver 
  * Each update object maintains a list of nsIDOMLoadStatus items for the
  * resources it is updating.  The list of these items will be available
  * after the object is scheduled.
  *
  * One update object will be updating at a time.  The active object will
  * load its items one by one, sending itemCompleted() to any registered
  * observers.
  */
-[scriptable, uuid(7dc06ede-1098-4384-b95e-65525ab254c9)]
+[scriptable, uuid(4b206247-82ee-46cf-a8b7-f7284e753bc2)]
 interface nsIOfflineCacheUpdate : nsISupports {
   /**
    * Fetch the status of the running update.  This will return a value
    * defined in nsIDOMOfflineResourceList.
    */
   readonly attribute unsigned short status;
 
   /**
    * TRUE if the update is being used to add specific resources.
    * FALSE if the complete cache update process is happening.
    */
   readonly attribute boolean partial;
 
   /**
+   * TRUE if this is an upgrade attempt, FALSE if it is a new cache
+   * attempt.
+   */
+  readonly attribute boolean isUpgrade;
+
+  /**
    * The domain being updated, and the domain that will own any URIs added
    * with this update.
    */
   readonly attribute ACString updateDomain;
 
   /**
    * The manifest for the offline application being updated.
    */
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -35,24 +35,26 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsOfflineCacheUpdate.h"
 
 #include "nsCPrefetchService.h"
 #include "nsCURILoader.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheService.h"
 #include "nsICache.h"
 #include "nsICacheService.h"
 #include "nsICacheSession.h"
 #include "nsICachingChannel.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsIObserverService.h"
-#include "nsIOfflineCacheSession.h"
+#include "nsIURL.h"
 #include "nsIWebProgress.h"
 #include "nsICryptoHash.h"
 #include "nsICacheEntryDescriptor.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStreamUtils.h"
 #include "nsThreadUtils.h"
@@ -99,40 +101,65 @@ NS_IMPL_ISUPPORTS6(nsOfflineCacheUpdateI
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdateItem <public>
 //-----------------------------------------------------------------------------
 
 nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate,
                                                    nsIURI *aURI,
                                                    nsIURI *aReferrerURI,
-                                                   const nsACString &aClientID)
+                                                   nsIApplicationCache *aPreviousApplicationCache,
+                                                   const nsACString &aClientID,
+                                                   PRUint32 type)
     : mURI(aURI)
     , mReferrerURI(aReferrerURI)
+    , mPreviousApplicationCache(aPreviousApplicationCache)
     , mClientID(aClientID)
+    , mItemType(type)
     , mUpdate(aUpdate)
     , mChannel(nsnull)
     , mState(nsIDOMLoadStatus::UNINITIALIZED)
     , mBytesRead(0)
 {
 }
 
 nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem()
 {
 }
 
 nsresult
 nsOfflineCacheUpdateItem::OpenChannel()
 {
-    nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
-                                mURI,
-                                nsnull, nsnull, this,
-                                nsIRequest::LOAD_BACKGROUND |
-                                nsICachingChannel::LOAD_ONLY_IF_MODIFIED |
-                                nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE);
+#if defined(PR_LOGGING)
+    if (LOG_ENABLED()) {
+        nsCAutoString spec;
+        mURI->GetSpec(spec);
+        LOG(("%p: Opening channel for %s", this, spec.get()));
+    }
+#endif
+
+    nsresult rv = nsOfflineCacheUpdate::GetCacheKey(mURI, mCacheKey);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = NS_NewChannel(getter_AddRefs(mChannel),
+                       mURI,
+                       nsnull, nsnull, this,
+                       nsIRequest::LOAD_BACKGROUND |
+                       nsICachingChannel::LOAD_ONLY_IF_MODIFIED |
+                       nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer =
+        do_QueryInterface(mChannel, &rv);
+
+    // Support for nsIApplicationCacheContainer is required.
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Use the existing application cache as the cache to check.
+    rv = appCacheContainer->SetApplicationCache(mPreviousApplicationCache);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // configure HTTP specific stuff
     nsCOMPtr<nsIHttpChannel> httpChannel =
         do_QueryInterface(mChannel);
     if (httpChannel) {
         httpChannel->SetReferrer(mReferrerURI);
         httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
@@ -387,18 +414,21 @@ nsOfflineCacheUpdateItem::GetStatus(PRUi
 
 //-----------------------------------------------------------------------------
 // nsOfflineManifestItem <public>
 //-----------------------------------------------------------------------------
 
 nsOfflineManifestItem::nsOfflineManifestItem(nsOfflineCacheUpdate *aUpdate,
                                              nsIURI *aURI,
                                              nsIURI *aReferrerURI,
+                                             nsIApplicationCache *aPreviousApplicationCache,
                                              const nsACString &aClientID)
-    : nsOfflineCacheUpdateItem(aUpdate, aURI, aReferrerURI, aClientID)
+    : nsOfflineCacheUpdateItem(aUpdate, aURI, aReferrerURI,
+                               aPreviousApplicationCache, aClientID,
+                               nsIApplicationCache::ITEM_MANIFEST)
     , mParserState(PARSE_INIT)
     , mNeedsUpdate(PR_TRUE)
     , mManifestHashInitialized(PR_FALSE)
 {
 }
 
 nsOfflineManifestItem::~nsOfflineManifestItem()
 {
@@ -752,30 +782,52 @@ nsOfflineCacheUpdate::nsOfflineCacheUpda
 {
 }
 
 nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
 {
     LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
 }
 
+/* static */
+nsresult
+nsOfflineCacheUpdate::GetCacheKey(nsIURI *aURI, nsACString &aKey)
+{
+    aKey.Truncate();
+
+    nsCOMPtr<nsIURI> newURI;
+    nsresult rv = aURI->Clone(getter_AddRefs(newURI));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIURL> newURL;
+    newURL = do_QueryInterface(newURI);
+    if (newURL) {
+        newURL->SetRef(EmptyCString());
+    }
+
+    rv = newURI->GetAsciiSpec(aKey);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
+
 nsresult
 nsOfflineCacheUpdate::Init(PRBool aPartialUpdate,
                            nsIURI *aManifestURI,
                            nsIURI *aDocumentURI)
 {
     nsresult rv;
 
     // Make sure the service has been initialized
     nsOfflineCacheUpdateService* service =
         nsOfflineCacheUpdateService::EnsureService();
     if (!service)
         return NS_ERROR_FAILURE;
 
-    LOG(("nsOfflineCacheUpdate::Init [%p]", this));
+    LOG(("nsOfflineCacheUpdate::Init [%p, %d]", this, aPartialUpdate));
 
     mPartialUpdate = aPartialUpdate;
 
     // Only http and https applications are supported.
     PRBool match;
     rv = aManifestURI->SchemeIs("http", &match);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -788,68 +840,41 @@ nsOfflineCacheUpdate::Init(PRBool aParti
 
     mManifestURI = aManifestURI;
 
     rv = mManifestURI->GetAsciiHost(mUpdateDomain);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCAutoString manifestSpec;
 
-    rv = mManifestURI->GetAsciiSpec(manifestSpec);
+    rv = GetCacheKey(mManifestURI, manifestSpec);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    PRInt32 ref = manifestSpec.FindChar('#');
-    if (ref != kNotFound)
-        manifestSpec.Truncate(ref);
-
-    mManifestOwnerSpec = manifestSpec;
-    mManifestOwnerSpec.AppendLiteral("#manifest");
-
-    mDynamicOwnerSpec = manifestSpec;
-    mDynamicOwnerSpec.AppendLiteral("#dynamic");
-
     mDocumentURI = aDocumentURI;
 
-    nsCOMPtr<nsICacheService> cacheService =
-        do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+    nsCOMPtr<nsIApplicationCacheService> cacheService =
+        do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsICacheSession> session;
-    rv = cacheService->CreateSession("HTTP-offline",
-                                     nsICache::STORE_OFFLINE,
-                                     nsICache::STREAM_BASED,
-                                     getter_AddRefs(session));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    mMainCacheSession = do_QueryInterface(session, &rv);
+    rv = cacheService->GetActiveCache(manifestSpec,
+                                      getter_AddRefs(mPreviousApplicationCache));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Partial updates don't use temporary cache sessions
-    if (aPartialUpdate) {
-        mCacheSession = mMainCacheSession;
+    // Partial updates to existing application caches don't need a new cache.
+    if (aPartialUpdate && mPreviousApplicationCache) {
+        mApplicationCache = mPreviousApplicationCache;
     } else {
-        rv = cacheService->CreateTemporaryClientID(nsICache::STORE_OFFLINE,
-                                                   mClientID);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = cacheService->CreateSession(mClientID.get(),
-                                         nsICache::STORE_OFFLINE,
-                                         nsICache::STREAM_BASED,
-                                         getter_AddRefs(session));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        mCacheSession = do_QueryInterface(session, &rv);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        // The manifest implicitly owns itself.
-        rv = mCacheSession->AddOwnedKey(mUpdateDomain, mManifestOwnerSpec,
-                                        manifestSpec);
+        rv = cacheService->CreateApplicationCache(manifestSpec,
+                                                  getter_AddRefs(mApplicationCache));
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
+    rv = mApplicationCache->GetClientID(mClientID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     mState = STATE_INITIALIZED;
 
     return NS_OK;
 }
 
 nsresult
 nsOfflineCacheUpdate::HandleManifest(PRBool *aDoUpdate)
 {
@@ -866,27 +891,31 @@ nsOfflineCacheUpdate::HandleManifest(PRB
 
     if (!mManifestItem->NeedsUpdate()) {
         return NS_OK;
     }
 
     // Add items requested by the manifest.
     const nsCOMArray<nsIURI> &manifestURIs = mManifestItem->GetExplicitURIs();
     for (PRInt32 i = 0; i < manifestURIs.Count(); i++) {
-        rv = AddURI(manifestURIs[i], mManifestOwnerSpec);
+        rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // The document that requested the manifest is implicitly included
     // as part of that manifest update.
-    rv = AddURI(mDocumentURI, mManifestOwnerSpec);
+    rv = AddURI(mDocumentURI, nsIApplicationCache::ITEM_IMPLICIT);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Add items previously cached implicitly
+    rv = AddExistingItems(nsIApplicationCache::ITEM_IMPLICIT);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Add items requested by the script API
-    rv = AddOwnedItems(mDynamicOwnerSpec);
+    rv = AddExistingItems(nsIApplicationCache::ITEM_DYNAMIC);
     NS_ENSURE_SUCCESS(rv, rv);
 
     *aDoUpdate = PR_TRUE;
 
     return NS_OK;
 }
 
 void
@@ -917,16 +946,25 @@ nsOfflineCacheUpdate::LoadCompleted()
 
         if (!doUpdate) {
             mSucceeded = PR_FALSE;
             NotifyNoUpdate();
             Finish();
             return;
         }
 
+        rv = mApplicationCache->MarkEntry(mManifestItem->mCacheKey,
+                                          mManifestItem->mItemType);
+        if (NS_FAILED(rv)) {
+            mSucceeded = PR_FALSE;
+            NotifyError();
+            Finish();
+            return;
+        }
+
         mState = STATE_DOWNLOADING;
         NotifyDownloading();
 
         // Start fetching resources.
         ProcessNextURI();
 
         return;
     }
@@ -942,16 +980,24 @@ nsOfflineCacheUpdate::LoadCompleted()
     // Check for failures.  4XX and 5XX errors will cause the update to fail.
     if (NS_FAILED(rv) || status == 0 || status >= 400) {
         mSucceeded = PR_FALSE;
         NotifyError();
         Finish();
         return;
     }
 
+    rv = mApplicationCache->MarkEntry(item->mCacheKey, item->mItemType);
+    if (NS_FAILED(rv)) {
+        mSucceeded = PR_FALSE;
+        NotifyError();
+        Finish();
+        return;
+    }
+
     rv = NotifyCompleted(item);
     if (NS_FAILED(rv)) return;
 
     ProcessNextURI();
 }
 
 nsresult
 nsOfflineCacheUpdate::Begin()
@@ -966,17 +1012,19 @@ nsOfflineCacheUpdate::Begin()
         ProcessNextURI();
         return NS_OK;
     }
 
     // Start checking the manifest.
     nsCOMPtr<nsIURI> uri;
 
     mManifestItem = new nsOfflineManifestItem(this, mManifestURI,
-                                              mDocumentURI, mClientID);
+                                              mDocumentURI,
+                                              mPreviousApplicationCache,
+                                              mClientID);
     if (!mManifestItem) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
     mState = STATE_CHECKING;
     NotifyChecking();
 
     nsresult rv = mManifestItem->OpenChannel();
@@ -1004,35 +1052,35 @@ nsOfflineCacheUpdate::Cancel()
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdate <private>
 //-----------------------------------------------------------------------------
 
 nsresult
-nsOfflineCacheUpdate::AddOwnedItems(const nsACString &aOwnerURI)
+nsOfflineCacheUpdate::AddExistingItems(PRUint32 aType)
 {
-    PRUint32 count;
-    char **keys;
-    nsresult rv = mMainCacheSession->GetOwnedKeys(mUpdateDomain, aOwnerURI,
-                                                  &count, &keys);
+    if (!mPreviousApplicationCache) {
+        return NS_OK;
+    }
+
+    PRUint32 count = 0;
+    char **keys = nsnull;
+    nsresult rv = mPreviousApplicationCache->GatherEntries(aType,
+                                                           &count, &keys);
     NS_ENSURE_SUCCESS(rv, rv);
 
     AutoFreeArray autoFree(count, keys);
 
     for (PRUint32 i = 0; i < count; i++) {
         nsCOMPtr<nsIURI> uri;
         if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) {
-            nsRefPtr<nsOfflineCacheUpdateItem> item =
-                new nsOfflineCacheUpdateItem(this, uri, mDocumentURI,
-                                             mClientID);
-            if (!item) return NS_ERROR_OUT_OF_MEMORY;
-
-            mItems.AppendElement(item);
+            rv = AddURI(uri, aType);
+            NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     return NS_OK;
 }
 
 nsresult
 nsOfflineCacheUpdate::ProcessNextURI()
@@ -1085,16 +1133,18 @@ nsOfflineCacheUpdate::GatherObservers(ns
     return NS_OK;
 }
 
 nsresult
 nsOfflineCacheUpdate::NotifyError()
 {
     LOG(("nsOfflineCacheUpdate::NotifyError [%p]", this));
 
+    mState = STATE_FINISHED;
+
     nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
     nsresult rv = GatherObservers(observers);
     NS_ENSURE_SUCCESS(rv, rv);
 
     for (PRInt32 i = 0; i < observers.Count(); i++) {
         observers[i]->Error(this);
     }
 
@@ -1117,16 +1167,18 @@ nsOfflineCacheUpdate::NotifyChecking()
     return NS_OK;
 }
 
 nsresult
 nsOfflineCacheUpdate::NotifyNoUpdate()
 {
     LOG(("nsOfflineCacheUpdate::NotifyNoUpdate [%p]", this));
 
+    mState = STATE_FINISHED;
+
     nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
     nsresult rv = GatherObservers(observers);
     NS_ENSURE_SUCCESS(rv, rv);
 
     for (PRInt32 i = 0; i < observers.Count(); i++) {
         observers[i]->NoUpdate(this);
     }
 
@@ -1191,28 +1243,30 @@ nsOfflineCacheUpdate::Finish()
     nsOfflineCacheUpdateService* service =
         nsOfflineCacheUpdateService::EnsureService();
 
     if (!service)
         return NS_ERROR_FAILURE;
 
     if (!mPartialUpdate) {
         if (mSucceeded) {
-            nsresult rv = mMainCacheSession->MergeTemporaryClientID(mClientID);
+            nsresult rv = mApplicationCache->Activate();
             if (NS_FAILED(rv)) {
                 NotifyError();
                 mSucceeded = PR_FALSE;
             }
         }
 
         if (!mSucceeded) {
             // Update was not merged, mark all the loads as failures
             for (PRUint32 i = 0; i < mItems.Length(); i++) {
                 mItems[i]->Cancel();
             }
+
+            mApplicationCache->Discard();
         }
     }
 
     return service->UpdateFinished(this);
 }
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdate::nsIOfflineCacheUpdate
@@ -1266,73 +1320,85 @@ nsOfflineCacheUpdate::GetSucceeded(PRBoo
 {
     NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
 
     *aSucceeded = mSucceeded;
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetIsUpgrade(PRBool *aIsUpgrade)
+{
+    NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+    *aIsUpgrade = (mPreviousApplicationCache != nsnull);
+
+    return NS_OK;
+}
+
 nsresult
-nsOfflineCacheUpdate::AddURI(nsIURI *aURI, const nsACString &aOwnerSpec)
+nsOfflineCacheUpdate::AddURI(nsIURI *aURI, PRUint32 aType)
 {
     NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
 
     if (mState >= STATE_DOWNLOADING)
         return NS_ERROR_NOT_AVAILABLE;
 
-    // Manifest URIs must have the same scheme as the manifest.
+    // Resource URIs must have the same scheme as the manifest.
     nsCAutoString scheme;
     aURI->GetScheme(scheme);
 
     PRBool match;
     if (NS_FAILED(mManifestURI->SchemeIs(scheme.get(), &match)) || !match)
         return NS_ERROR_FAILURE;
 
-    // Save the cache key as an owned URI
-    nsCAutoString spec;
-    nsresult rv = aURI->GetSpec(spec);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // url fragments aren't used in cache keys
-    nsCAutoString::const_iterator specStart, specEnd;
-    spec.BeginReading(specStart);
-    spec.EndReading(specEnd);
-    if (FindCharInReadable('#', specStart, specEnd)) {
-        spec.BeginReading(specEnd);
-        rv = mCacheSession->AddOwnedKey(mUpdateDomain, aOwnerSpec,
-                                        Substring(specEnd, specStart));
-        NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-        rv = mCacheSession->AddOwnedKey(mUpdateDomain, aOwnerSpec, spec);
-        NS_ENSURE_SUCCESS(rv, rv);
-    }
-
     // Don't fetch the same URI twice.
     for (PRUint32 i = 0; i < mItems.Length(); i++) {
         PRBool equals;
         if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals) {
+            // retain both types.
+            mItems[i]->mItemType |= aType;
             return NS_OK;
         }
     }
 
     nsRefPtr<nsOfflineCacheUpdateItem> item =
-        new nsOfflineCacheUpdateItem(this, aURI, mDocumentURI, mClientID);
+        new nsOfflineCacheUpdateItem(this, aURI, mDocumentURI,
+                                     mPreviousApplicationCache, mClientID,
+                                     aType);
     if (!item) return NS_ERROR_OUT_OF_MEMORY;
 
     mItems.AppendElement(item);
     mAddedItems = PR_TRUE;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdate::AddDynamicURI(nsIURI *aURI)
 {
-    return AddURI(aURI, mDynamicOwnerSpec);
+    // If this is a partial update and the resource is already in the
+    // cache, we should only mark the entry, not fetch it again.
+    if (mPartialUpdate) {
+        nsCAutoString key;
+        GetCacheKey(aURI, key);
+
+        PRUint32 types;
+        nsresult rv = mApplicationCache->GetTypes(key, &types);
+        if (NS_SUCCEEDED(rv)) {
+            if (!(types & nsIApplicationCache::ITEM_DYNAMIC)) {
+                mApplicationCache->MarkEntry
+                    (key, nsIApplicationCache::ITEM_DYNAMIC);
+            }
+            return NS_OK;
+        }
+    }
+
+    return AddURI(aURI, nsIApplicationCache::ITEM_DYNAMIC);
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdate::GetCount(PRUint32 *aNumItems)
 {
     LOG(("nsOfflineCacheUpdate::GetNumItems [%p, num=%d]",
          this, mItems.Length()));
 
--- a/uriloader/prefetch/nsOfflineCacheUpdate.h
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -47,18 +47,17 @@
 #include "nsICacheService.h"
 #include "nsIChannelEventSink.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMLoadStatus.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
-#include "nsIOfflineCacheSession.h"
-#include "nsIPrefetchService.h"
+#include "nsIApplicationCache.h"
 #include "nsIRequestObserver.h"
 #include "nsIRunnable.h"
 #include "nsIStreamListener.h"
 #include "nsIURI.h"
 #include "nsIWebProgressListener.h"
 #include "nsClassHashtable.h"
 #include "nsString.h"
 #include "nsTArray.h"
@@ -82,22 +81,27 @@ public:
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIRUNNABLE
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSICHANNELEVENTSINK
 
     nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate,
                              nsIURI *aURI,
                              nsIURI *aReferrerURI,
-                             const nsACString &aClientID);
+                             nsIApplicationCache *aPreviousApplicationCache,
+                             const nsACString &aClientID,
+                             PRUint32 aType);
     virtual ~nsOfflineCacheUpdateItem();
 
     nsCOMPtr<nsIURI>           mURI;
     nsCOMPtr<nsIURI>           mReferrerURI;
+    nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
     nsCString                  mClientID;
+    nsCString                  mCacheKey;
+    PRUint32                   mItemType;
 
     nsresult OpenChannel();
     nsresult Cancel();
 
 private:
     nsOfflineCacheUpdate*          mUpdate;
     nsCOMPtr<nsIChannel>           mChannel;
     PRUint16                       mState;
@@ -111,16 +115,17 @@ class nsOfflineManifestItem : public nsO
 {
 public:
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIREQUESTOBSERVER
 
     nsOfflineManifestItem(nsOfflineCacheUpdate *aUpdate,
                           nsIURI *aURI,
                           nsIURI *aReferrerURI,
+                          nsIApplicationCache *aPreviousApplicationCache,
                           const nsACString &aClientID);
     virtual ~nsOfflineManifestItem();
 
     nsCOMArray<nsIURI> &GetExplicitURIs() { return mExplicitURIs; }
 
     PRBool ParseSucceeded()
         { return (mParserState != PARSE_INIT && mParserState != PARSE_ERROR); }
     PRBool NeedsUpdate() { return mParserState != PARSE_INIT && mNeedsUpdate; }
@@ -172,29 +177,32 @@ class nsOfflineCacheUpdate : public nsIO
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOFFLINECACHEUPDATE
 
     nsOfflineCacheUpdate();
     ~nsOfflineCacheUpdate();
 
+    static nsresult GetCacheKey(nsIURI *aURI, nsACString &aKey);
+
     nsresult Init();
 
     nsresult Begin();
     nsresult Cancel();
 
     void LoadCompleted();
+
 private:
     nsresult HandleManifest(PRBool *aDoUpdate);
-    nsresult AddURI(nsIURI *aURI, const nsACString &aOwnerSpec);
+    nsresult AddURI(nsIURI *aURI, PRUint32 aItemType);
 
     nsresult ProcessNextURI();
 
-    nsresult AddOwnedItems(const nsACString &aOwnerURI);
+    nsresult AddExistingItems(PRUint32 aType);
 
     nsresult GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers);
     nsresult NotifyError();
     nsresult NotifyChecking();
     nsresult NotifyNoUpdate();
     nsresult NotifyDownloading();
     nsresult NotifyStarted(nsOfflineCacheUpdateItem *aItem);
     nsresult NotifyCompleted(nsOfflineCacheUpdateItem *aItem);
@@ -209,24 +217,22 @@ private:
         STATE_FINISHED
     } mState;
 
     PRBool mAddedItems;
     PRBool mPartialUpdate;
     PRBool mSucceeded;
     nsCString mUpdateDomain;
     nsCOMPtr<nsIURI> mManifestURI;
-    nsCString mManifestOwnerSpec;
-    nsCString mDynamicOwnerSpec;
 
     nsCOMPtr<nsIURI> mDocumentURI;
 
     nsCString mClientID;
-    nsCOMPtr<nsIOfflineCacheSession> mCacheSession;
-    nsCOMPtr<nsIOfflineCacheSession> mMainCacheSession;
+    nsCOMPtr<nsIApplicationCache> mApplicationCache;
+    nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
 
     nsCOMPtr<nsIObserverService> mObserverService;
 
     nsRefPtr<nsOfflineManifestItem> mManifestItem;
 
     /* Items being updated */
     PRInt32 mCurrentItem;
     nsTArray<nsRefPtr<nsOfflineCacheUpdateItem> > mItems;