move the offline cache update logic from the content sink and prefetch service into an nsOfflineCacheUpdate object. bug=388839, r=biesi, sr=jst
authordcamp@mozilla.com
Tue, 24 Jul 2007 22:35:39 -0700
changeset 3942 ab8225779bcd18807ee32b048da70a513cd6da1c
parent 3941 59040f379535e476fe85a6eeaf60d1cc2f404a5d
child 3943 a3344b3670e1b994e9e5957cd87ccf2519d9c2c8
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbiesi, jst
bugs388839
milestone1.9a7pre
move the offline cache update logic from the content sink and prefetch service into an nsOfflineCacheUpdate object. bug=388839, r=biesi, sr=jst
content/base/src/nsContentSink.cpp
content/base/src/nsContentSink.h
content/html/document/src/nsHTMLContentSink.cpp
docshell/build/Makefile.in
docshell/build/nsDocShellModule.cpp
dom/src/base/Makefile.in
dom/src/offline/nsDOMOfflineLoadStatusList.cpp
dom/src/offline/nsDOMOfflineLoadStatusList.h
dom/src/offline/nsDOMOfflineResourceList.cpp
dom/tests/mochitest/ajax/offline/test_pendingOfflineLoads.html
netwerk/cache/public/nsIOfflineCacheSession.idl
netwerk/cache/src/nsCacheService.cpp
netwerk/cache/src/nsCacheService.h
netwerk/cache/src/nsCacheSession.cpp
netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
netwerk/cache/src/nsDiskCacheDeviceSQL.h
uriloader/prefetch/Makefile.in
uriloader/prefetch/nsCPrefetchService.h
uriloader/prefetch/nsIOfflineCacheUpdate.idl
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -40,16 +40,17 @@
 /*
  * Base class for the XML and HTML content sinks, which construct a
  * DOM based on information from the parser.
  */
 
 #include "nsContentSink.h"
 #include "nsScriptLoader.h"
 #include "nsIDocument.h"
+#include "nsIDOMDocument.h"
 #include "nsICSSLoader.h"
 #include "nsStyleConsts.h"
 #include "nsStyleLinkElement.h"
 #include "nsINodeInfo.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsCPrefetchService.h"
 #include "nsIURI.h"
@@ -67,17 +68,18 @@
 #include "nsGkAtoms.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsNetCID.h"
 #include "nsICache.h"
 #include "nsICacheService.h"
 #include "nsICacheSession.h"
-#include "nsIOfflineCacheSession.h"
+#include "nsIOfflineCacheUpdate.h"
+#include "nsIDOMLoadStatus.h"
 #include "nsICookieService.h"
 #include "nsIPrompt.h"
 #include "nsServiceManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsParserUtils.h"
 #include "nsCRT.h"
 #include "nsEscape.h"
 #include "nsWeakReference.h"
@@ -674,24 +676,22 @@ nsContentSink::ProcessLink(nsIContent* a
 {
   // XXX seems overkill to generate this string array
   nsStringArray linkTypes;
   nsStyleLinkElement::ParseLinkTypes(aRel, linkTypes);
 
   PRBool hasPrefetch = (linkTypes.IndexOf(NS_LITERAL_STRING("prefetch")) != -1);
   // prefetch href if relation is "next" or "prefetch"
   if (hasPrefetch || linkTypes.IndexOf(NS_LITERAL_STRING("next")) != -1) {
-    PrefetchHref(aHref, aElement, hasPrefetch, PR_FALSE);
+    PrefetchHref(aHref, aElement, hasPrefetch);
   }
 
   // fetch href into the offline cache if relation is "offline-resource"
   if (linkTypes.IndexOf(NS_LITERAL_STRING("offline-resource")) != -1) {
-    AddOfflineResource(aHref);
-    if (mSaveOfflineResources)
-      PrefetchHref(aHref, aElement, PR_TRUE, PR_TRUE);
+    AddOfflineResource(aHref, aElement);
   }
 
   // is it a stylesheet link?
   if (linkTypes.IndexOf(NS_LITERAL_STRING("stylesheet")) == -1) {
     return NS_OK;
   }
 
   PRBool isAlternate = linkTypes.IndexOf(NS_LITERAL_STRING("alternate")) != -1;
@@ -766,18 +766,17 @@ nsContentSink::ProcessMETATag(nsIContent
 
   return rv;
 }
 
 
 void
 nsContentSink::PrefetchHref(const nsAString &aHref,
                             nsIContent *aSource,
-                            PRBool aExplicit,
-                            PRBool aOffline)
+                            PRBool aExplicit)
 {
   //
   // SECURITY CHECK: disable prefetching from mailnews!
   //
   // walk up the docshell tree to see if any containing
   // docshell are of type MAIL.
   //
   if (!mDocShell)
@@ -811,138 +810,79 @@ nsContentSink::PrefetchHref(const nsAStr
     // construct URI using document charset
     const nsACString &charset = mDocument->GetDocumentCharacterSet();
     nsCOMPtr<nsIURI> uri;
     NS_NewURI(getter_AddRefs(uri), aHref,
               charset.IsEmpty() ? nsnull : PromiseFlatCString(charset).get(),
               mDocumentBaseURI);
     if (uri) {
       nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aSource);
-      if (aOffline)
-        prefetchService->PrefetchURIForOfflineUse(uri,
-                                                  mDocumentURI,
-                                                  domNode,
-                                                  aExplicit);
-      else
-        prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit);
+      prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit);
     }
   }
 }
 
 nsresult
-nsContentSink::GetOfflineCacheSession(nsIOfflineCacheSession **aSession)
-{
-  if (!mOfflineCacheSession) {
-    nsresult 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);
-
-    mOfflineCacheSession =
-      do_QueryInterface(session, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  NS_ADDREF(*aSession = mOfflineCacheSession);
-
-  return NS_OK;
-}
-
-nsresult
-nsContentSink::AddOfflineResource(const nsAString &aHref)
+nsContentSink::AddOfflineResource(const nsAString &aHref, nsIContent *aSource)
 {
   PRBool match;
   nsresult rv;
 
   nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(mDocumentURI);
   if (!innerURI)
     return NS_ERROR_FAILURE;
 
-  nsCAutoString ownerHost;
-  rv = innerURI->GetHostPort(ownerHost);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCAutoString ownerSpec;
-  rv = mDocumentURI->GetSpec(ownerSpec);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   if (!mHaveOfflineResources) {
     mHaveOfflineResources = PR_TRUE;
-    mSaveOfflineResources = PR_FALSE;
 
     // only let http and https urls add offline resources
     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<nsIOfflineCacheSession> session;
-    rv = GetOfflineCacheSession(getter_AddRefs(session));
+    // create updater
+    mOfflineCacheUpdate =
+      do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCAutoString ownerDomain;
+    rv = innerURI->GetHostPort(ownerDomain);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // we're going to replace the list, clear it out
-    rv = session->SetOwnedKeys(ownerHost, ownerSpec, 0, nsnull);
+    nsCAutoString ownerSpec;
+    rv = mDocumentURI->GetSpec(ownerSpec);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mSaveOfflineResources = PR_TRUE;
+    rv = mOfflineCacheUpdate->Init(PR_FALSE, ownerDomain,
+                                   ownerSpec, mDocumentURI);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Kick off this update when the document is done loading
+    nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(mDocument);
+    mOfflineCacheUpdate->ScheduleOnDocumentStop(doc);
   }
 
-  if (!mSaveOfflineResources) return NS_OK;
+  if (!mOfflineCacheUpdate) return NS_OK;
 
   const nsACString &charset = mDocument->GetDocumentCharacterSet();
   nsCOMPtr<nsIURI> uri;
   rv = NS_NewURI(getter_AddRefs(uri), aHref,
                  charset.IsEmpty() ? nsnull : PromiseFlatCString(charset).get(),
                  mDocumentBaseURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // only http and https urls can be marked as offline resources
-  rv = uri->SchemeIs("http", &match);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!match) {
-    rv = uri->SchemeIs("https", &match);
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (!match)
-      return NS_OK;
-  }
-
-  nsCAutoString spec;
-  rv = uri->GetSpec(spec);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aSource);
 
-  nsCOMPtr<nsIOfflineCacheSession> offlineCacheSession;
-  rv = GetOfflineCacheSession(getter_AddRefs(offlineCacheSession));
-  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);
-    offlineCacheSession->AddOwnedKey(ownerHost, ownerSpec,
-                                     Substring(specEnd, specStart));
-  } else {
-    offlineCacheSession->AddOwnedKey(ownerHost, ownerSpec, spec);
-  }
-
-  return NS_OK;
+  return mOfflineCacheUpdate->AddURI(uri, domNode);
 }
 
 void
 nsContentSink::ScrollToRef()
 {
   if (mRef.IsEmpty()) {
     return;
   }
--- a/content/base/src/nsContentSink.h
+++ b/content/base/src/nsContentSink.h
@@ -70,17 +70,17 @@ class nsIDocShell;
 class nsICSSLoader;
 class nsIParser;
 class nsIAtom;
 class nsIChannel;
 class nsIContent;
 class nsIViewManager;
 class nsNodeInfoManager;
 class nsScriptLoader;
-class nsIOfflineCacheSession;
+class nsIOfflineCacheUpdate;
 
 #ifdef NS_DEBUG
 
 extern PRLogModuleInfo* gContentSinkLogModuleInfo;
 
 #define SINK_TRACE_CALLS              0x1
 #define SINK_TRACE_REFLOW             0x2
 #define SINK_ALWAYS_REFLOW            0x4
@@ -163,19 +163,18 @@ protected:
   virtual nsresult ProcessStyleLink(nsIContent* aElement,
                                     const nsSubstring& aHref,
                                     PRBool aAlternate,
                                     const nsSubstring& aTitle,
                                     const nsSubstring& aType,
                                     const nsSubstring& aMedia);
 
   void PrefetchHref(const nsAString &aHref, nsIContent *aSource,
-                    PRBool aExplicit, PRBool aOffline);
-  nsresult GetOfflineCacheSession(nsIOfflineCacheSession **aSession);
-  nsresult AddOfflineResource(const nsAString &aHref);
+                    PRBool aExplicit);
+  nsresult AddOfflineResource(const nsAString &aHref, nsIContent *aSource);
 
   void ScrollToRef();
   nsresult RefreshIfEnabled(nsIViewManager* vm);
 
   // Start layout.  If aIgnorePendingSheets is true, this will happen even if
   // we still have stylesheet loads pending.  Otherwise, we'll wait until the
   // stylesheets are all done loading.
   void StartLayout(PRBool aIgnorePendingSheets);
@@ -257,34 +256,32 @@ protected:
   // logic which decides whether to switch to the high frequency
   // parser interrupt mode.
   PRUint8 mDeflectedCount;
 
   // Do we notify based on time?
   PRPackedBool mNotifyOnTimer;
 
   // For saving <link rel="offline-resource"> links
-  nsCOMPtr<nsIOfflineCacheSession> mOfflineCacheSession;
+  nsCOMPtr<nsIOfflineCacheUpdate> mOfflineCacheUpdate;
 
   // Have we already called BeginUpdate for this set of content changes?
   PRUint8 mBeganUpdate : 1;
   PRUint8 mLayoutStarted : 1;
   PRUint8 mScrolledToRefAlready : 1;
   PRUint8 mCanInterruptParser : 1;
   PRUint8 mDynamicLowerValue : 1;
   PRUint8 mParsing : 1;
   PRUint8 mDroppedTimer : 1;
   PRUint8 mInTitle : 1;
   PRUint8 mChangeScrollPosWhenScrollingToRef : 1;
   // If true, we deferred starting layout until sheets load
   PRUint8 mDeferredLayoutStart : 1;
   // true if an <link rel="offline-resource"> nodes have been encountered.
   PRUint8 mHaveOfflineResources : 1;
-  // true if offline-resource links should be saved to the offline cache
-  PRUint8 mSaveOfflineResources : 1;
   // If true, we deferred notifications until sheets load
   PRUint8 mDeferredFlushTags : 1;
   
   // -- Can interrupt parsing members --
   PRUint32 mDelayTimerStart;
 
   // Interrupt parsing during token procesing after # of microseconds
   PRInt32 mMaxTokenProcessingTime;
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -3000,26 +3000,24 @@ HTMLContentSink::ProcessLINKTag(const ns
         // XXX seems overkill to generate this string array
         nsStringArray linkTypes;
         nsStyleLinkElement::ParseLinkTypes(relVal, linkTypes);
         PRBool hasPrefetch = (linkTypes.IndexOf(NS_LITERAL_STRING("prefetch")) != -1);
         if (hasPrefetch || linkTypes.IndexOf(NS_LITERAL_STRING("next")) != -1) {
           nsAutoString hrefVal;
           element->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
           if (!hrefVal.IsEmpty()) {
-            PrefetchHref(hrefVal, element, hasPrefetch, PR_FALSE);
+            PrefetchHref(hrefVal, element, hasPrefetch);
           }
         }
         if (linkTypes.IndexOf(NS_LITERAL_STRING("offline-resource")) != -1) {
           nsAutoString hrefVal;
           element->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
           if (!hrefVal.IsEmpty()) {
-            AddOfflineResource(hrefVal);
-            if (mSaveOfflineResources)
-              PrefetchHref(hrefVal, element, PR_TRUE, PR_TRUE);
+            AddOfflineResource(hrefVal, element);
           }
         }
       }
     }
   }
 
   return result;
 }
--- a/docshell/build/Makefile.in
+++ b/docshell/build/Makefile.in
@@ -55,16 +55,17 @@ LIBXUL_LIBRARY	= 1
 PACKAGE_FILE = docshell.pkg
 
 REQUIRES	= xpcom \
 		  string \
 		  dom \
 		  js \
 		  shistory \
 		  necko \
+		  nkcache \
 		  gfx \
 		  content \
 		  layout \
 		  webshell \
 		  widget \
 		  pref \
 		  view \
 		  intl \
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -49,16 +49,17 @@
 #include "nsAboutRedirector.h"
 
 // uriloader
 #include "nsURILoader.h"
 #include "nsDocLoader.h"
 #include "nsOSHelperAppService.h"
 #include "nsExternalProtocolHandler.h"
 #include "nsPrefetchService.h"
+#include "nsOfflineCacheUpdate.h"
 #include "nsHandlerAppImpl.h"
 
 // session history
 #include "nsSHEntry.h"
 #include "nsSHistory.h"
 #include "nsSHTransaction.h"
 
 // global history
@@ -95,16 +96,19 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWe
 
 // uriloader
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsURILoader)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDocLoader, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsOSHelperAppService, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalProtocolHandler)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBlockedExternalProtocolHandler)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefetchService, Init)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsOfflineCacheUpdateService,
+                                         nsOfflineCacheUpdateService::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsOfflineCacheUpdate)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsLocalHandlerApp)
 
 #if defined(XP_MAC) || defined(XP_MACOSX)
 #include "nsInternetConfigService.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsInternetConfigService)
 #endif
 
 // session history
@@ -200,16 +204,20 @@ static const nsModuleComponentInfo gDocS
   { "Netscape Mime Mapping Service", NS_EXTERNALHELPERAPPSERVICE_CID, NS_MIMESERVICE_CONTRACTID, 
      nsOSHelperAppServiceConstructor, },
   { "Netscape Default Protocol Handler", NS_EXTERNALPROTOCOLHANDLER_CID, NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default", 
      nsExternalProtocolHandlerConstructor, },
   { "Netscape Default Blocked Protocol Handler", NS_BLOCKEDEXTERNALPROTOCOLHANDLER_CID, NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default-blocked", 
      nsBlockedExternalProtocolHandlerConstructor, },
   {  NS_PREFETCHSERVICE_CLASSNAME, NS_PREFETCHSERVICE_CID, NS_PREFETCHSERVICE_CONTRACTID,
      nsPrefetchServiceConstructor, },
+  { NS_OFFLINECACHEUPDATESERVICE_CLASSNAME, NS_OFFLINECACHEUPDATESERVICE_CID, NS_OFFLINECACHEUPDATESERVICE_CONTRACTID,
+    nsOfflineCacheUpdateServiceConstructor, },
+  { NS_OFFLINECACHEUPDATE_CLASSNAME, NS_OFFLINECACHEUPDATE_CID, NS_OFFLINECACHEUPDATE_CONTRACTID,
+    nsOfflineCacheUpdateConstructor, },
   { "Local Application Handler App", NS_LOCALHANDLERAPP_CID, 
     NS_LOCALHANDLERAPP_CONTRACTID, nsLocalHandlerAppConstructor, },
 #if defined(XP_MAC) || defined(XP_MACOSX)
   { "Internet Config Service", NS_INTERNETCONFIGSERVICE_CID, NS_INTERNETCONFIGSERVICE_CONTRACTID,
     nsInternetConfigServiceConstructor, },
 #endif
         
     // session history
--- a/dom/src/base/Makefile.in
+++ b/dom/src/base/Makefile.in
@@ -61,16 +61,17 @@ REQUIRES	= xpcom \
 		  pref \
 		  oji \
 		  necko \
 		  nkcache \
 		  mimetype \
 		  java \
 		  locale \
 		  uriloader \
+		  prefetch \
 		  xuldoc \
 		  webshell \
 		  view \
 		  uconv \
 		  shistory \
 		  plugin \
 		  windowwatcher \
 		  chardet \
--- a/dom/src/offline/nsDOMOfflineLoadStatusList.cpp
+++ b/dom/src/offline/nsDOMOfflineLoadStatusList.cpp
@@ -149,16 +149,17 @@ nsDOMOfflineLoadStatus::GetStatus(PRUint
 // nsDOMOfflineLoadStatusList
 //
 
 NS_INTERFACE_MAP_BEGIN(nsDOMOfflineLoadStatusList)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMLoadStatusList)
   NS_INTERFACE_MAP_ENTRY(nsIDOMLoadStatusList)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdateObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(LoadStatusList)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(nsDOMOfflineLoadStatusList)
 NS_IMPL_RELEASE(nsDOMOfflineLoadStatusList)
 
 nsDOMOfflineLoadStatusList::nsDOMOfflineLoadStatusList(nsIURI *aURI)
@@ -180,124 +181,41 @@ nsDOMOfflineLoadStatusList::Init()
     return NS_OK;
   }
 
   mInitialized = PR_TRUE;
 
   nsresult rv = mURI->GetHostPort(mHostPort);
   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));
+  nsCOMPtr<nsIOfflineCacheUpdateService> cacheUpdateService =
+    do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mCacheSession = do_QueryInterface(session, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // get the current list of loads from the prefetch queue
-  nsCOMPtr<nsIPrefetchService> prefetchService =
-    do_GetService(NS_PREFETCHSERVICE_CONTRACTID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsISimpleEnumerator> e;
-  rv = prefetchService->EnumerateQueue(PR_FALSE, PR_TRUE, getter_AddRefs(e));
+  PRUint32 numUpdates;
+  rv = cacheUpdateService->GetNumUpdates(&numUpdates);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  PRBool more;
-  while (NS_SUCCEEDED(rv = e->HasMoreElements(&more)) && more) {
-    nsCOMPtr<nsIDOMLoadStatus> status;
-    rv = e->GetNext(getter_AddRefs(status));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    PRBool shouldInclude;
-    rv = ShouldInclude(status, &shouldInclude);
+  for (PRUint32 i = 0; i < numUpdates; i++) {
+    nsCOMPtr<nsIOfflineCacheUpdate> cacheUpdate;
+    rv = cacheUpdateService->GetUpdate(i, getter_AddRefs(cacheUpdate));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (!shouldInclude) {
-      continue;
-    }
+    rv = WatchUpdate(cacheUpdate);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
-    nsDOMOfflineLoadStatus *wrapper = new nsDOMOfflineLoadStatus(status);
-    if (!wrapper) return NS_ERROR_OUT_OF_MEMORY;
-
-    mItems.AppendObject(wrapper);
-  }
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // watch for changes in the prefetch queue
+  // watch for new offline cache updates
   nsCOMPtr<nsIObserverService> observerServ =
     do_GetService("@mozilla.org/observer-service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = observerServ->AddObserver(this, "offline-load-requested", PR_TRUE);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = observerServ->AddObserver(this, "offline-load-completed", PR_TRUE);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
-nsDOMOfflineLoadStatusList::ShouldInclude(nsIDOMLoadStatus *aStatus,
-                                          PRBool *aShouldInclude)
-{
-  *aShouldInclude = PR_FALSE;
-
-  nsAutoString uriStr;
-  nsresult rv = aStatus->GetUri(uriStr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIURI> uri;
-  rv = NS_NewURI(getter_AddRefs(uri), uriStr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCAutoString hostport;
-  rv = uri->GetHostPort(hostport);
+  rv = observerServ->AddObserver(this, "offline-cache-update-added", PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (hostport != mHostPort)
-    return NS_OK;
-
-  // Check that the URL is owned by this domain
-  nsCAutoString spec;
-  rv = uri->GetSpec(spec);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIDOMNode> source;
-  rv = aStatus->GetSource(getter_AddRefs(source));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCAutoString ownerURI;
-  if (source) {
-    // Came from a <link> element, check that it's owned by this URI
-    rv = mURI->GetSpec(ownerURI);
-    NS_ENSURE_SUCCESS(rv, rv);
-  } else {
-    // Didn't come from a <link> element, check that it's owned by
-    // resource list (no owner URI)
-    ownerURI.Truncate();
-  }
-
-  PRBool owned;
-  rv = mCacheSession->KeyIsOwned(mHostPort, ownerURI, spec, &owned);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (!owned) {
-    return NS_OK;
-  }
-
-  *aShouldInclude = PR_TRUE;
-
   return NS_OK;
 }
 
 nsIDOMLoadStatus *
 nsDOMOfflineLoadStatusList::FindWrapper(nsIDOMLoadStatus *aStatus,
                                         PRUint32 *index)
 {
   for (int i = 0; i < mItems.Count(); i++) {
@@ -307,16 +225,55 @@ nsDOMOfflineLoadStatusList::FindWrapper(
       *index = i;
       return mItems[i];
     }
   }
 
   return nsnull;
 }
 
+nsresult
+nsDOMOfflineLoadStatusList::WatchUpdate(nsIOfflineCacheUpdate *aUpdate)
+{
+  nsCAutoString owner;
+  nsresult rv = aUpdate->GetUpdateDomain(owner);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (owner != mHostPort) {
+    // This update doesn't belong to us
+    return NS_OK;
+  }
+
+  PRUint32 numItems;
+  rv = aUpdate->GetCount(&numItems);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  for (PRUint32 i = 0; i < numItems; i++) {
+    nsCOMPtr<nsIDOMLoadStatus> status;
+    rv = aUpdate->Item(i, getter_AddRefs(status));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsDOMOfflineLoadStatus *wrapper = new nsDOMOfflineLoadStatus(status);
+    if (!wrapper) return NS_ERROR_OUT_OF_MEMORY;
+
+    mItems.AppendObject(wrapper);
+
+    rv = SendLoadEvent(NS_LITERAL_STRING(LOADREQUESTED_STR),
+                       mLoadRequestedEventListeners,
+                       wrapper);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aUpdate->AddObserver(this, PR_TRUE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 //
 // nsDOMOfflineLoadStatusList::nsIDOMLoadStatusList
 //
 
 NS_IMETHODIMP
 nsDOMOfflineLoadStatusList::GetLength(PRUint32 *aLength)
 {
   nsresult rv = Init();
@@ -503,49 +460,42 @@ nsDOMOfflineLoadStatusList::SendLoadEven
 // nsDOMLoadStatusList::nsIObserver
 //
 NS_IMETHODIMP
 nsDOMOfflineLoadStatusList::Observe(nsISupports *aSubject,
                                     const char *aTopic,
                                     const PRUnichar *aData)
 {
   nsresult rv;
-  if (!strcmp(aTopic, "offline-load-requested")) {
-    nsCOMPtr<nsIDOMLoadStatus> status = do_QueryInterface(aSubject);
-    if (status) {
-      PRBool shouldInclude;
-      rv = ShouldInclude(status, &shouldInclude);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (!shouldInclude) return NS_OK;
-
-      nsDOMOfflineLoadStatus *wrapper = new nsDOMOfflineLoadStatus(status);
-      if (!wrapper) return NS_ERROR_OUT_OF_MEMORY;
-
-      mItems.AppendObject(wrapper);
-
-      rv = SendLoadEvent(NS_LITERAL_STRING(LOADREQUESTED_STR),
-                         mLoadRequestedEventListeners,
-                         wrapper);
+  if (!strcmp(aTopic, "offline-cache-update-added")) {
+    nsCOMPtr<nsIOfflineCacheUpdate> update = do_QueryInterface(aSubject);
+    if (update) {
+      rv = WatchUpdate(update);
       NS_ENSURE_SUCCESS(rv, rv);
     }
-  } else if (!strcmp(aTopic, "offline-load-completed")) {
-    nsCOMPtr<nsIDOMLoadStatus> status = do_QueryInterface(aSubject);
-    if (status) {
-      PRUint32 index;
-      nsCOMPtr<nsIDOMLoadStatus> wrapper = FindWrapper(status, &index);
-      if (wrapper) {
-        mItems.RemoveObjectAt(index);
+  }
+
+  return NS_OK;
+}
+
+//
+// nsDOMLoadStatusList::nsIOfflineCacheUpdateObserver
+//
 
-        rv = SendLoadEvent(NS_LITERAL_STRING(LOADCOMPLETED_STR),
-                           mLoadCompletedEventListeners,
-                           wrapper);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-    }
+NS_IMETHODIMP
+nsDOMOfflineLoadStatusList::ItemCompleted(nsIDOMLoadStatus *aItem)
+{
+  PRUint32 index;
+  nsCOMPtr<nsIDOMLoadStatus> wrapper = FindWrapper(aItem, &index);
+  if (wrapper) {
+    mItems.RemoveObjectAt(index);
+    nsresult rv = SendLoadEvent(NS_LITERAL_STRING(LOADCOMPLETED_STR),
+                                mLoadCompletedEventListeners,
+                                wrapper);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 //
 // nsDOMLoadStatusEvent
 //
--- a/dom/src/offline/nsDOMOfflineLoadStatusList.h
+++ b/dom/src/offline/nsDOMOfflineLoadStatusList.h
@@ -38,63 +38,63 @@
 
 #ifndef nsDOMOfflineLoadStatusList_h___
 #define nsDOMOfflineLoadStatusList_h___
 
 #include "nscore.h"
 #include "nsIDOMLoadStatus.h"
 #include "nsIDOMLoadStatusEvent.h"
 #include "nsIDOMLoadStatusList.h"
-#include "nsIOfflineCacheSession.h"
+#include "nsIOfflineCacheUpdate.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsIURI.h"
 #include "nsDOMEvent.h"
 #include "nsIDOMEventTarget.h"
 #include "nsIDOMEventListener.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsIScriptContext.h"
 
 class nsDOMOfflineLoadStatus;
 
 class nsDOMOfflineLoadStatusList : public nsIDOMLoadStatusList,
                                    public nsIDOMEventTarget,
                                    public nsIObserver,
+                                   public nsIOfflineCacheUpdateObserver,
                                    public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMLOADSTATUSLIST
   NS_DECL_NSIDOMEVENTTARGET
   NS_DECL_NSIOBSERVER
+  NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
 
   nsDOMOfflineLoadStatusList(nsIURI *aURI);
   virtual ~nsDOMOfflineLoadStatusList();
 
   nsresult Init();
 
 private :
-  nsresult          ShouldInclude       (nsIDOMLoadStatus *aStatus,
-                                         PRBool *aInclude);
+  nsresult          WatchUpdate         (nsIOfflineCacheUpdate *aUpdate);
   nsIDOMLoadStatus *FindWrapper         (nsIDOMLoadStatus *aStatus,
                                          PRUint32 *aIndex);
   void              NotifyEventListeners(const nsCOMArray<nsIDOMEventListener>& aListeners,
                                          nsIDOMEvent* aEvent);
 
   nsresult          SendLoadEvent       (const nsAString& aEventName,
                                          const nsCOMArray<nsIDOMEventListener>& aListeners,
                                          nsIDOMLoadStatus *aStatus);
 
   PRBool mInitialized;
 
   nsCOMPtr<nsIURI> mURI;
   nsCOMArray<nsIDOMLoadStatus> mItems;
   nsCString mHostPort;
-  nsCOMPtr<nsIOfflineCacheSession> mCacheSession;
 
   nsCOMPtr<nsIScriptContext> mScriptContext;
 
   nsCOMArray<nsIDOMEventListener> mLoadRequestedEventListeners;
   nsCOMArray<nsIDOMEventListener> mLoadCompletedEventListeners;
 };
 
 class nsDOMLoadStatusEvent : public nsDOMEvent,
--- a/dom/src/offline/nsDOMOfflineResourceList.cpp
+++ b/dom/src/offline/nsDOMOfflineResourceList.cpp
@@ -41,16 +41,18 @@
 #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"
 
 // 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
@@ -160,53 +162,40 @@ nsDOMOfflineResourceList::Add(const nsAS
 
   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);
 
-  // only http/https urls will work offline
-  PRBool match;
-  rv = requestedURI->SchemeIs("http", &match);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (!match) {
-    rv = requestedURI->SchemeIs("https", &match);
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (!match) return NS_ERROR_DOM_BAD_URI;
-  }
-
   PRUint32 length;
   rv = GetLength(&length);
   NS_ENSURE_SUCCESS(rv, rv);
   PRUint32 maxEntries = nsContentUtils::GetIntPref(kMaxEntriesPref,
                                                    DEFAULT_MAX_ENTRIES);
 
   if (length > maxEntries) return NS_ERROR_NOT_AVAILABLE;
 
   ClearCachedKeys();
 
-  nsCAutoString key;
-  rv = GetCacheKey(requestedURI, key);
+  nsCOMPtr<nsIOfflineCacheUpdate> update =
+    do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mCacheSession->AddOwnedKey(mHostPort,
-                                  NS_LITERAL_CSTRING(""),
-                                  key);
+  rv = update->Init(PR_TRUE, mHostPort, NS_LITERAL_CSTRING(""), mURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIPrefetchService> prefetchService =
-    do_GetService(NS_PREFETCHSERVICE_CONTRACTID, &rv);
+  rv = update->AddURI(requestedURI, nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return prefetchService->PrefetchURIForOfflineUse(requestedURI,
-                                                   mURI,
-                                                   nsnull,
-                                                   PR_TRUE);
+  rv = update->Schedule();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMOfflineResourceList::Remove(const nsAString& aURI)
 {
   nsresult rv = Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -249,38 +238,27 @@ nsDOMOfflineResourceList::Clear()
 }
 
 NS_IMETHODIMP
 nsDOMOfflineResourceList::Refresh()
 {
   nsresult rv = Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = CacheKeys();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // try to start fetching it now, but it's not fatal if it fails
-  nsCOMPtr<nsIPrefetchService> prefetchService =
-    do_GetService(NS_PREFETCHSERVICE_CONTRACTID, &rv);
+  nsCOMPtr<nsIOfflineCacheUpdate> update =
+    do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  for (PRUint32 i = 0; i < gCachedKeysCount; i++) {
-    // this will fail if the URI is not absolute
-    nsCOMPtr<nsIURI> requestedURI;
-    nsresult rv = NS_NewURI(getter_AddRefs(requestedURI), gCachedKeys[i]);
-    NS_ENSURE_SUCCESS(rv, rv);
+  rv = update->Init(PR_FALSE, mHostPort, NS_LITERAL_CSTRING(""), mURI);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = prefetchService->PrefetchURIForOfflineUse(requestedURI,
-                                                   mURI,
-                                                   nsnull,
-                                                   PR_TRUE);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  rv = update->Schedule();
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  return NS_OK;
+  return rv;
 }
 
 nsresult
 nsDOMOfflineResourceList::GetCacheKey(const nsAString &aURI, nsCString &aKey)
 {
   nsCOMPtr<nsIURI> requestedURI;
   nsresult rv = NS_NewURI(getter_AddRefs(requestedURI), aURI);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/tests/mochitest/ajax/offline/test_pendingOfflineLoads.html
+++ b/dom/tests/mochitest/ajax/offline/test_pendingOfflineLoads.html
@@ -99,20 +99,16 @@ function run_test()
 {
   navigator.pendingOfflineLoads.addEventListener("loadrequested",
                                                  load_requested,
                                                  false);
   navigator.pendingOfflineLoads.addEventListener("loadcompleted",
                                                  load_completed,
                                                  false);
 
-  // The <link rel="offline-resource"> should already be in the queue
-  ok(navigator.pendingOfflineLoads.length == 2,
-     "<link rel=\"offline-resource\"> loads should already be in the queue");
-
   for (var i = 0; i < navigator.pendingOfflineLoads.length; i++) {
     var load = navigator.pendingOfflineLoads[i];
     check_load_added(navigator.pendingOfflineLoads[i]);
   }
 
   // Add the correct resources
   navigator.offlineResources.add("http://localhost:8888/unknown2");
   navigator.offlineResources.add(thisURL);
--- a/netwerk/cache/public/nsIOfflineCacheSession.idl
+++ b/netwerk/cache/public/nsIOfflineCacheSession.idl
@@ -35,17 +35,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"
 #include "nsICache.idl"
 
-[scriptable, uuid(e9581d9f-3c0c-4722-8d2b-3d18f8d41299)]
+[scriptable, uuid(0058c32b-0d93-4cf8-a561-e6f749c8a7b1)]
 interface nsIOfflineCacheSession : nsISupports
 {
     /**
      * 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.
@@ -62,16 +62,36 @@ interface nsIOfflineCacheSession : nsISu
      * 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.
      */
 
     /**
+     * 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 ownerDomain The domain to query
+     * @param count The number of uris returned
+     * @param uris The uris in this domain that own resources
+     */
+    void getOwnerURIs(in ACString ownerDomain,
+                      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.
--- a/netwerk/cache/src/nsCacheService.cpp
+++ b/netwerk/cache/src/nsCacheService.cpp
@@ -825,16 +825,59 @@ 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
--- a/netwerk/cache/src/nsCacheService.h
+++ b/netwerk/cache/src/nsCacheService.h
@@ -93,16 +93,24 @@ public:
                                     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,
--- a/netwerk/cache/src/nsCacheSession.cpp
+++ b/netwerk/cache/src/nsCacheSession.cpp
@@ -131,16 +131,29 @@ 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);
 }
 
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
@@ -784,17 +784,19 @@ nsOfflineCacheDevice::Init()
     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_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_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_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);" )
   };
   for (PRUint32 i=0; i<NS_ARRAY_LENGTH(prepared); ++i)
   {
     rv |= mDB->CreateStatement(nsDependentCString(prepared[i].sql),
                                getter_AddRefs(prepared[i].statement));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1247,16 +1249,87 @@ nsOfflineCacheDevice::EvictEntries(const
   if (clientID)
     PR_smprintf_free((char *) deleteCmd);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
+nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
+                                     PRUint32 resultIndex,
+                                     PRUint32 * count,
+                                     char *** values)
+{
+  PRBool hasRows;
+  nsresult rv = statement->ExecuteStep(&hasRows);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsTArray<nsCString> valArray;
+  while (hasRows)
+  {
+    PRUint32 length;
+    valArray.AppendElement(
+      nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length)));
+
+    rv = statement->ExecuteStep(&hasRows);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  *count = valArray.Length();
+  char **ret = static_cast<char **>(NS_Alloc(*count * sizeof(char*)));
+  if (!ret) return NS_ERROR_OUT_OF_MEMORY;
+
+  for (PRUint32 i = 0; i <  *count; i++) {
+    ret[i] = NS_strdup(valArray[i].get());
+    if (!ret[i]) {
+      NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  *values = ret;
+
+  return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetOwnerDomains(const char * clientID,
+                                      PRUint32 * count,
+                                      char *** domains)
+{
+  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);
+}
+
+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);
@@ -1300,46 +1373,17 @@ nsOfflineCacheDevice::GetOwnedKeys(const
 
   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);
 
-  PRBool hasRows;
-  rv = statement->ExecuteStep(&hasRows);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsTArray<nsCString> keyArray;
-  while (hasRows)
-  {
-    PRUint32 length;
-    keyArray.AppendElement(
-      nsDependentCString(statement->AsSharedUTF8String(0, &length)));
-
-    rv = statement->ExecuteStep(&hasRows);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  *count = keyArray.Length();
-  char **ret = static_cast<char **>(NS_Alloc(*count * sizeof(char*)));
-  if (!ret) return NS_ERROR_OUT_OF_MEMORY;
-
-  for (PRUint32 i = 0; i <  *count; i++) {
-    ret[i] = NS_strdup(keyArray[i].get());
-    if (!ret[i]) {
-      NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  *keys = ret;
-
-  return NS_OK;
+  return RunSimpleQuery(mStatement_ListOwned, 0, count, keys);
 }
 
 nsresult
 nsOfflineCacheDevice::AddOwnedKey(const char * clientID,
                                   const nsACString & ownerDomain,
                                   const nsACString & ownerURI,
                                   const nsACString & key)
 {
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.h
@@ -81,16 +81,23 @@ public:
   virtual nsresult        OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize);
   
   virtual nsresult        Visit(nsICacheVisitor * visitor);
 
   virtual nsresult        EvictEntries(const char * clientID);
 
 
   /* Entry ownership */
+  nsresult                GetOwnerDomains(const char *        clientID,
+                                          PRUint32 *          count,
+                                          char ***            domains);
+  nsresult                GetOwnerURIs(const char *           clientID,
+                                       const nsACString &     ownerDomain,
+                                       PRUint32 *             count,
+                                       char ***               uris);
   nsresult                SetOwnedKeys(const char *           clientID,
                                        const nsACString &     ownerDomain,
                                        const nsACString &     ownerUrl,
                                        PRUint32               count,
                                        const char **          keys);
   nsresult                GetOwnedKeys(const char *           clientID,
                                        const nsACString &     ownerDomain,
                                        const nsACString &     ownerUrl,
@@ -130,16 +137,20 @@ public:
 private:
   PRBool   Initialized() { return mDB != nsnull; }
   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 RunSimpleQuery(mozIStorageStatement *statment,
+                          PRUint32 resultIndex,
+                          PRUint32 * count,
+                          char *** values);
 
   nsCOMPtr<mozIStorageConnection> mDB;
   nsCOMPtr<mozIStorageStatement>  mStatement_CacheSize;
   nsCOMPtr<mozIStorageStatement>  mStatement_EntryCount;
   nsCOMPtr<mozIStorageStatement>  mStatement_UpdateEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_UpdateEntrySize;
   nsCOMPtr<mozIStorageStatement>  mStatement_UpdateEntryFlags;
   nsCOMPtr<mozIStorageStatement>  mStatement_DeleteEntry;
@@ -147,15 +158,17 @@ private:
   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_DeleteUnowned;
   nsCOMPtr<mozIStorageStatement>  mStatement_ListOwned;
+  nsCOMPtr<mozIStorageStatement>  mStatement_ListOwnerDomains;
+  nsCOMPtr<mozIStorageStatement>  mStatement_ListOwnerURIs;
 
   nsCOMPtr<nsILocalFile>          mCacheDirectory;
   PRUint32                        mCacheCapacity;
   PRInt32                         mDeltaCounter;
 };
 
 #endif // nsOfflineCacheDevice_h__
--- a/uriloader/prefetch/Makefile.in
+++ b/uriloader/prefetch/Makefile.in
@@ -52,19 +52,22 @@ REQUIRES	= xpcom \
 		  uriloader \
 		  nkcache \
 		  chardet \
 		  pref \
 		  $(NULL)
 
 CPPSRCS = \
 		nsPrefetchService.cpp \
+		nsOfflineCacheUpdate.cpp \
 		$(NULL)
+
 XPIDLSRCS =	\
 		nsIPrefetchService.idl \
+		nsIOfflineCacheUpdate.idl \
 		$(NULL)
 EXPORTS = \
 		nsCPrefetchService.h \
 		$(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
--- a/uriloader/prefetch/nsCPrefetchService.h
+++ b/uriloader/prefetch/nsCPrefetchService.h
@@ -50,9 +50,42 @@
 #define NS_PREFETCHSERVICE_CID                       \
 { /* 6b8bdffc-3394-417d-be83-a81b7c0f63bf */         \
     0x6b8bdffc,                                      \
     0x3394,                                          \
     0x417d,                                          \
     {0xbe, 0x83, 0xa8, 0x1b, 0x7c, 0x0f, 0x63, 0xbf} \
 }
 
+/**
+ * nsOfflineCacheUpdateService : nsIOfflineCacheUpdateService
+ */
+
+#define NS_OFFLINECACHEUPDATESERVICE_CLASSNAME \
+  "nsOfflineCacheUpdateService"
+#define NS_OFFLINECACHEUPDATESERVICE_CONTRACTID \
+  "@mozilla.org/offlinecacheupdate-service;1"
+#define NS_OFFLINECACHEUPDATESERVICE_CID             \
+{ /* ec06f3fc-70db-4ecd-94e0-a6e91ca44d8a */         \
+    0xec06f3fc,                                      \
+    0x70db,                                          \
+    0x4ecd ,                                         \
+    {0x94, 0xe0, 0xa6, 0xe9, 0x1c, 0xa4, 0x4d, 0x8a} \
+}
+
+/**
+ * nsOfflineCacheUpdate : nsIOfflineCacheUpdate
+ */
+
+#define NS_OFFLINECACHEUPDATE_CLASSNAME \
+  "nsOfflineCacheUpdate"
+#define NS_OFFLINECACHEUPDATE_CONTRACTID \
+  "@mozilla.org/offlinecacheupdate;1"
+#define NS_OFFLINECACHEUPDATE_CID                    \
+{ /* e56f5e01-c7cc-4675-a9d7-b8f1e4127295 */         \
+    0xe56f5e01,                                      \
+    0xc7cc,                                          \
+    0x4675,                                          \
+    {0xa9, 0xd7, 0xb8, 0xf1, 0xe4, 0x12, 0x72, 0x95} \
+}
+
+
 #endif // !nsCPrefetchService_h__
new file mode 100644
--- /dev/null
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -0,0 +1,159 @@
+/* -*- mode: C++; 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 mozilla.org code.
+ *
+ * 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"
+
+interface nsIURI;
+interface nsIDOMNode;
+interface nsIDOMDocument;
+interface nsIDOMLoadStatus;
+
+[scriptable, uuid(e0785ebb-b3a1-426a-a70e-be2b923e973e)]
+interface nsIOfflineCacheUpdateObserver : nsISupports {
+  /**
+   * An item has finished loading.
+   *
+   * @param aItem load status for the item that completed.
+   */
+  void itemCompleted(in nsIDOMLoadStatus aItem);
+};
+
+/**
+ * An nsIOfflineCacheUpdate is used to update a domain's offline resources.
+ * It can be used to perform partial or complete updates.
+ *
+ * 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)]
+interface nsIOfflineCacheUpdate : nsISupports {
+  /**
+   * The domain being updated, and the domain that will own any URIs added
+   * with this update.
+   */
+  readonly attribute ACString updateDomain;
+
+  /**
+   * The URI that will own any URIs added by this update
+   */
+  readonly attribute ACString ownerURI;
+
+  /**
+   * Initialize the update.
+   *
+   * @param aPartialUpdate
+   *        TRUE if the update should just update the URIs given to it,
+   *        FALSE if all URLs for the owner domain should be added.
+   * @param aUpdateDomain
+   *        The domain which is being updated, and which will own any
+   *        URIs added.
+   * @param aOwnerURI
+   *        The owner URI for any URIs added.
+   * @param aReferrerURI
+   *        The page that is requesting the update.
+   */
+  void init(in boolean aPartialUpdate,
+            in ACString aUpdateDomain,
+            in ACString aOwnerURI,
+            in nsIURI aReferrerURI);
+
+  /**
+   * Add a URI to the offline cache as part of the update.
+   *
+   * @param aURI
+   *        The URI to add.
+   * @param aSource
+   *        The DOM node (<link> tag) associated with this node (for
+   *        implementing nsIDOMLoadStatus).
+   */
+  void addURI(in nsIURI aURI, in nsIDOMNode aSource);
+
+  /**
+   * Add the update to the offline update queue.  An offline-cache-update-added
+   * event will be sent to the observer service.
+   */
+  void schedule();
+
+  /**
+   * Request that the update be scheduled when a document finishes loading.
+   *
+   * @param aDocument
+   *        When this document finishes loading, the update will be scheduled.
+   */
+  void scheduleOnDocumentStop(in nsIDOMDocument aDocument);
+
+  /**
+   * Access to the list of items in the update.
+   */
+  readonly attribute unsigned long count;
+  nsIDOMLoadStatus item(in unsigned long index);
+
+  /**
+   * Observe loads that are added to the update.
+   *
+   * @param aObserver
+   *        object that notifications will be sent to.
+   * @param aHoldWeak
+   *        TRUE if you want the update to hold a weak reference to the
+   *        observer, FALSE for a strong reference.
+   */
+  void addObserver(in nsIOfflineCacheUpdateObserver aObserver,
+                   in boolean aHoldWeak);
+
+  /**
+   * Remove an observer from the update.
+   *
+   * @param aObserver
+   *        the observer to remove.
+   */
+  void removeObserver(in nsIOfflineCacheUpdateObserver aObserver);
+};
+
+[scriptable, uuid(f99ca10f-5cde-4966-b845-433f2921a201)]
+interface nsIOfflineCacheUpdateService : nsISupports {
+    /**
+     * Access to the list of cache updates that have been scheduled.
+     */
+    readonly attribute unsigned long numUpdates;
+    nsIOfflineCacheUpdate getUpdate(in unsigned long index);
+};
new file mode 100644
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -0,0 +1,1058 @@
+/* -*- mode: C++; 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 mozilla.org code.
+ *
+ * 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 "nsOfflineCacheUpdate.h"
+
+#include "nsCPrefetchService.h"
+#include "nsCURILoader.h"
+#include "nsICache.h"
+#include "nsICacheService.h"
+#include "nsICacheSession.h"
+#include "nsICachingChannel.h"
+#include "nsIDOMWindow.h"
+#include "nsIObserverService.h"
+#include "nsIOfflineCacheSession.h"
+#include "nsIWebProgress.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "prlog.h"
+
+static nsOfflineCacheUpdateService *gOfflineCacheUpdateService = nsnull;
+
+#if defined(PR_LOGGING)
+//
+// To enable logging (see prlog.h for full details):
+//
+//    set NSPR_LOG_MODULES=nsOfflineCacheUpdate:5
+//    set NSPR_LOG_FILE=offlineupdate.log
+//
+// this enables PR_LOG_ALWAYS level information and places all output in
+// the file offlineupdate.log
+//
+static PRLogModuleInfo *gOfflineCacheUpdateLog;
+#endif
+#define LOG(args) PR_LOG(gOfflineCacheUpdateLog, 4, args)
+#define LOG_ENABLED() PR_LOG_TEST(gOfflineCacheUpdateLog, 4)
+
+class AutoFreeArray {
+public:
+    AutoFreeArray(PRUint32 count, char **values)
+        : mCount(count), mValues(values) {};
+    ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); }
+private:
+    PRUint32 mCount;
+    char **mValues;
+};
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS5(nsOfflineCacheUpdateItem,
+                   nsIDOMLoadStatus,
+                   nsIRequestObserver,
+                   nsIStreamListener,
+                   nsIInterfaceRequestor,
+                   nsIChannelEventSink)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate,
+                                                   nsIURI *aURI,
+                                                   nsIURI *aReferrerURI,
+                                                   nsIDOMNode *aSource)
+    : mURI(aURI)
+    , mReferrerURI(aReferrerURI)
+    , mUpdate(aUpdate)
+    , mChannel(nsnull)
+    , mState(nsIDOMLoadStatus::UNINITIALIZED)
+    , mBytesRead(0)
+{
+    mSource = do_GetWeakReference(aSource);
+}
+
+nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem()
+{
+}
+
+nsresult
+nsOfflineCacheUpdateItem::OpenChannel()
+{
+    nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
+                                mURI,
+                                nsnull, nsnull, this,
+                                nsIRequest::LOAD_BACKGROUND |
+                                nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
+    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"),
+                                      NS_LITERAL_CSTRING("offline-resource"),
+                                      PR_FALSE);
+    }
+
+    nsCOMPtr<nsICachingChannel> cachingChannel =
+        do_QueryInterface(mChannel);
+    if (cachingChannel) {
+        rv = cachingChannel->SetCacheForOfflineUse(PR_TRUE);
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    rv = mChannel->AsyncOpen(this, nsnull);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mState = nsIDOMLoadStatus::REQUESTED;
+
+    return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdateItem::Cancel()
+{
+    if (mChannel) {
+        mChannel->Cancel(NS_ERROR_ABORT);
+        mChannel = nsnull;
+    }
+
+    mState = nsIDOMLoadStatus::UNINITIALIZED;
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnStartRequest(nsIRequest *aRequest,
+                                         nsISupports *aContext)
+{
+    mState = nsIDOMLoadStatus::RECEIVING;
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest *aRequest,
+                                          nsISupports *aContext,
+                                          nsIInputStream *aStream,
+                                          PRUint32 aOffset,
+                                          PRUint32 aCount)
+{
+    PRUint32 bytesRead = 0;
+    aStream->ReadSegments(NS_DiscardSegment, nsnull, aCount, &bytesRead);
+    mBytesRead += bytesRead;
+    LOG(("loaded %u bytes into offline cache [offset=%u]\n",
+         bytesRead, aOffset));
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest,
+                                        nsISupports *aContext,
+                                        nsresult aStatus)
+{
+    LOG(("done fetching offline item [status=%x]\n", aStatus));
+
+    mState = nsIDOMLoadStatus::LOADED;
+
+    if (mBytesRead == 0 && aStatus == NS_OK) {
+        // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
+        // specified), but the object should report loadedSize as if it
+        // did.
+        mChannel->GetContentLength(&mBytesRead);
+    }
+
+    mUpdate->LoadCompleted();
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetInterface(const nsIID &aIID, void **aResult)
+{
+    if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+        NS_ADDREF_THIS();
+        *aResult = static_cast<nsIChannelEventSink *>(this);
+        return NS_OK;
+    }
+
+    return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel,
+                                            nsIChannel *aNewChannel,
+                                            PRUint32 aFlags)
+{
+    nsCOMPtr<nsIURI> newURI;
+    nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+    if (NS_FAILED(rv))
+        return rv;
+
+    nsCOMPtr<nsICachingChannel> oldCachingChannel =
+        do_QueryInterface(aOldChannel);
+    nsCOMPtr<nsICachingChannel> newCachingChannel =
+      do_QueryInterface(aOldChannel);
+    if (newCachingChannel)
+      newCachingChannel->SetCacheForOfflineUse(PR_TRUE);
+
+    PRBool match;
+    rv = newURI->SchemeIs("http", &match);
+    if (NS_FAILED(rv) || !match) {
+        if (NS_FAILED(newURI->SchemeIs("https", &match)) ||
+            !match) {
+            LOG(("rejected: URL is not of type http\n"));
+            return NS_ERROR_ABORT;
+        }
+    }
+
+    // HTTP request headers are not automatically forwarded to the new channel.
+    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+    NS_ENSURE_STATE(httpChannel);
+
+    httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+                                  NS_LITERAL_CSTRING("offline-resource"),
+                                  PR_FALSE);
+
+    mChannel = aNewChannel;
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIDOMLoadStatus
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetSource(nsIDOMNode **aSource)
+{
+    if (mSource) {
+        return CallQueryReferent(mSource.get(), aSource);
+    } else {
+        *aSource = nsnull;
+        return NS_OK;
+    }
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetUri(nsAString &aURI)
+{
+    nsCAutoString spec;
+    nsresult rv = mURI->GetSpec(spec);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    CopyUTF8toUTF16(spec, aURI);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetTotalSize(PRInt32 *aTotalSize)
+{
+    if (mChannel) {
+        return mChannel->GetContentLength(aTotalSize);
+    }
+
+    *aTotalSize = -1;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetLoadedSize(PRInt32 *aLoadedSize)
+{
+    *aLoadedSize = mBytesRead;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetReadyState(PRUint16 *aReadyState)
+{
+    *aReadyState = mState;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus)
+{
+    if (!mChannel) {
+        *aStatus = 0;
+        return NS_OK;
+    }
+
+    nsresult rv;
+    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRUint32 httpStatus;
+    rv = httpChannel->GetResponseStatus(&httpStatus);
+    if (rv == NS_ERROR_NOT_AVAILABLE) {
+        // Someone's calling this before we got a response... Check our
+        // ReadyState.  If we're at RECEIVING or LOADED, then this means the
+        // connection errored before we got any data; return a somewhat
+        // sensible error code in that case.
+        if (mState >= nsIDOMLoadStatus::RECEIVING) {
+            *aStatus = NS_ERROR_NOT_AVAILABLE;
+            return NS_OK;
+        }
+
+        *aStatus = 0;
+        return NS_OK;
+    }
+
+    NS_ENSURE_SUCCESS(rv, rv);
+    *aStatus = PRUint16(httpStatus);
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS1(nsOfflineCacheUpdate,
+                   nsIOfflineCacheUpdate);
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdate::nsOfflineCacheUpdate()
+    : mState(STATE_UNINITIALIZED)
+    , mAddedItems(PR_FALSE)
+    , mPartialUpdate(PR_FALSE)
+{
+}
+
+nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
+{
+    LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
+}
+
+nsresult
+nsOfflineCacheUpdate::Init(PRBool aPartialUpdate,
+                           const nsACString &aUpdateDomain,
+                           const nsACString &aOwnerURI,
+                           nsIURI *aReferrerURI)
+{
+    nsresult rv;
+
+    // Make sure the service has been initialized
+    if (!nsOfflineCacheUpdateService::GetInstance()) {
+        return NS_ERROR_FAILURE;
+    }
+
+    LOG(("nsOfflineCacheUpdate::Init [%p]", this));
+
+    mPartialUpdate = aPartialUpdate;
+    mUpdateDomain = aUpdateDomain;
+    mOwnerURI = aOwnerURI;
+    mReferrerURI = aReferrerURI;
+
+    nsCOMPtr<nsICacheService> cacheService =
+        do_GetService(NS_CACHESERVICE_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);
+
+    mCacheSession = do_QueryInterface(session, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mState = STATE_INITIALIZED;
+
+    return NS_OK;
+}
+
+void
+nsOfflineCacheUpdate::LoadCompleted()
+{
+    nsresult rv;
+
+    LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this));
+
+    NS_ASSERTION(mItems.Length() >= 1, "Unknown load completed");
+
+    nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[0];
+    mItems.RemoveElementAt(0);
+
+    rv = NotifyCompleted(item);
+    if (NS_FAILED(rv)) return;
+
+    ProcessNextURI();
+}
+
+nsresult
+nsOfflineCacheUpdate::Begin()
+{
+    LOG(("nsOfflineCacheUpdate::Begin [%p]", this));
+
+    if (!mPartialUpdate) {
+        // All offline items for a domain should be updated as a group;  add
+        // the other offline items requested for this domain
+        nsresult rv = AddDomainItems();
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    mState = STATE_RUNNING;
+
+    ProcessNextURI();
+
+    return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::Cancel()
+{
+    LOG(("nsOfflineCacheUpdate::Cancel [%p]", this));
+
+    mState = STATE_CANCELLED;
+
+    if (mItems.Length() > 0) {
+        // First load might be running
+        mItems[0]->Cancel();
+    }
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsOfflineCacheUpdate::AddOwnedItems(const nsACString &aOwnerURI)
+{
+    PRUint32 count;
+    char **keys;
+    nsresult rv = mCacheSession->GetOwnedKeys(mUpdateDomain, aOwnerURI,
+                                              &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, mReferrerURI, nsnull);
+            if (!item) return NS_ERROR_OUT_OF_MEMORY;
+
+            mItems.AppendElement(item);
+        }
+    }
+
+    return NS_OK;
+}
+
+// Add all URIs needed by this domain to the update
+nsresult
+nsOfflineCacheUpdate::AddDomainItems()
+{
+    PRUint32 count;
+    char **uris;
+    nsresult rv = mCacheSession->GetOwnerURIs(mUpdateDomain, &count, &uris);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    AutoFreeArray autoFree(count, uris);
+
+    for (PRUint32 i = 0; i < count; i++) {
+        const char *ownerURI = uris[i];
+        // if this update includes changes to this owner URI, ignore the
+        // set in the database.
+        if (!mAddedItems || !mOwnerURI.Equals(ownerURI)) {
+            rv = AddOwnedItems(nsDependentCString(ownerURI));
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
+    }
+
+    return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::ProcessNextURI()
+{
+    LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, numItems=%d]",
+         this, mItems.Length()));
+
+    if (mState == STATE_CANCELLED || mItems.Length() == 0) {
+        return Finish();
+    }
+
+#if defined(PR_LOGGING)
+    if (LOG_ENABLED()) {
+        nsCAutoString spec;
+        mItems[0]->mURI->GetSpec(spec);
+        LOG(("%p: Opening channel for %s", this, spec.get()));
+    }
+#endif
+
+    nsresult rv = mItems[0]->OpenChannel();
+    if (NS_FAILED(rv)) {
+        LoadCompleted();
+        return rv;
+    }
+
+    return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::NotifyCompleted(nsOfflineCacheUpdateItem *aItem)
+{
+    nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+
+    for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) {
+        nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+            do_QueryReferent(mWeakObservers[i]);
+        if (observer)
+            observers.AppendObject(observer);
+        else
+            mWeakObservers.RemoveObjectAt(i--);
+    }
+
+    for (PRInt32 i = 0; i < mObservers.Count(); i++) {
+        observers.AppendObject(mObservers[i]);
+    }
+
+    for (PRInt32 i = 0; i < observers.Count(); i++) {
+        observers[i]->ItemCompleted(aItem);
+    }
+
+    return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::Finish()
+{
+    LOG(("nsOfflineCacheUpdate::Finish [%p]", this));
+
+    mState = STATE_FINISHED;
+
+    nsOfflineCacheUpdateService *service =
+        nsOfflineCacheUpdateService::GetInstance();
+
+    if (!service)
+        return NS_ERROR_FAILURE;
+
+    return service->UpdateFinished(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsIOfflineCacheUpdate
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain)
+{
+    NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+    aUpdateDomain = mUpdateDomain;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetOwnerURI(nsACString &aOwnerURI)
+{
+    NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+    aOwnerURI = mOwnerURI;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::AddURI(nsIURI *aURI, nsIDOMNode *aSource)
+{
+    NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+    if (mState >= STATE_RUNNING)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    // only http and https urls can be put in the offline cache
+    PRBool match;
+    nsresult rv = aURI->SchemeIs("http", &match);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!match) {
+        rv = aURI->SchemeIs("https", &match);
+        NS_ENSURE_SUCCESS(rv, rv);
+        if (!match)
+            return NS_ERROR_ABORT;
+    }
+
+    // Save the cache key as an owned URI
+    nsCAutoString spec;
+    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, mOwnerURI,
+                                        Substring(specEnd, specStart));
+        NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+        rv = mCacheSession->AddOwnedKey(mUpdateDomain, mOwnerURI, spec);
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+   nsRefPtr<nsOfflineCacheUpdateItem> item =
+        new nsOfflineCacheUpdateItem(this, aURI, mReferrerURI, aSource);
+    if (!item) return NS_ERROR_OUT_OF_MEMORY;
+
+    mItems.AppendElement(item);
+    mAddedItems = PR_TRUE;
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetCount(PRUint32 *aNumItems)
+{
+    LOG(("nsOfflineCacheUpdate::GetNumItems [%p, num=%d]",
+         this, mItems.Length()));
+
+    NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+    *aNumItems = mItems.Length();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::Item(PRUint32 aIndex, nsIDOMLoadStatus **aItem)
+{
+    LOG(("nsOfflineCacheUpdate::GetItems [%p, index=%d]", this, aIndex));
+
+    NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+    if (aIndex < mItems.Length())
+        NS_IF_ADDREF(*aItem = mItems.ElementAt(aIndex));
+    else
+        *aItem = nsnull;
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::AddObserver(nsIOfflineCacheUpdateObserver *aObserver,
+                                  PRBool aHoldWeak)
+{
+    LOG(("nsOfflineCacheUpdate::AddObserver [%p]", this));
+
+    NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+    if (aHoldWeak) {
+        nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(aObserver);
+        mWeakObservers.AppendObject(weakRef);
+    } else {
+        mObservers.AppendObject(aObserver);
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver)
+{
+    LOG(("nsOfflineCacheUpdate::RemoveObserver [%p]", this));
+
+    NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+    for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) {
+        nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+            do_QueryReferent(mWeakObservers[i]);
+        if (observer == aObserver) {
+            mWeakObservers.RemoveObjectAt(i);
+            return NS_OK;
+        }
+    }
+
+    for (PRInt32 i = 0; i < mObservers.Count(); i++) {
+        if (mObservers[i] == aObserver) {
+            mObservers.RemoveObjectAt(i);
+            return NS_OK;
+        }
+    }
+
+    return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::Schedule()
+{
+    LOG(("nsOfflineCacheUpdate::Schedule [%p]", this));
+
+    nsOfflineCacheUpdateService *service =
+        nsOfflineCacheUpdateService::GetInstance();
+
+    if (!service) {
+        return NS_ERROR_FAILURE;
+    }
+
+    return service->Schedule(this);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::ScheduleOnDocumentStop(nsIDOMDocument *aDocument)
+{
+    LOG(("nsOfflineCacheUpdate::ScheduleOnDocumentStop [%p]", this));
+
+    nsOfflineCacheUpdateService *service =
+        nsOfflineCacheUpdateService::GetInstance();
+
+    if (!service) {
+        return NS_ERROR_FAILURE;
+    }
+
+    return service->ScheduleOnDocumentStop(this, aDocument);
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS4(nsOfflineCacheUpdateService,
+                   nsIOfflineCacheUpdateService,
+                   nsIWebProgressListener,
+                   nsIObserver,
+                   nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdateService::nsOfflineCacheUpdateService()
+    : mDisabled(PR_FALSE)
+    , mUpdateRunning(PR_FALSE)
+{
+}
+
+nsOfflineCacheUpdateService::~nsOfflineCacheUpdateService()
+{
+    gOfflineCacheUpdateService = nsnull;
+}
+
+nsresult
+nsOfflineCacheUpdateService::Init()
+{
+    nsresult rv;
+
+#if defined(PR_LOGGING)
+    if (!gOfflineCacheUpdateLog)
+        gOfflineCacheUpdateLog = PR_NewLogModule("nsOfflineCacheUpdate");
+#endif
+
+    if (!mDocUpdates.Init())
+        return NS_ERROR_FAILURE;
+
+    // Observe xpcom-shutdown event
+    nsCOMPtr<nsIObserverService> observerService =
+        do_GetService("@mozilla.org/observer-service;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = observerService->AddObserver(this,
+                                      NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+                                      PR_TRUE);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Register as an observer for the document loader
+    nsCOMPtr<nsIWebProgress> progress =
+        do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+    if (progress) {
+        nsresult rv = progress->AddProgressListener
+                          (this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    gOfflineCacheUpdateService = this;
+
+    return NS_OK;
+}
+
+nsOfflineCacheUpdateService *
+nsOfflineCacheUpdateService::GetInstance()
+{
+    if (!gOfflineCacheUpdateService) {
+        gOfflineCacheUpdateService = new nsOfflineCacheUpdateService();
+        if (!gOfflineCacheUpdateService)
+            return nsnull;
+        NS_ADDREF(gOfflineCacheUpdateService);
+        nsresult rv = gOfflineCacheUpdateService->Init();
+        if (NS_FAILED(rv)) {
+            NS_RELEASE(gOfflineCacheUpdateService);
+            return nsnull;
+        }
+        return gOfflineCacheUpdateService;
+    }
+
+    NS_ADDREF(gOfflineCacheUpdateService);
+
+    return gOfflineCacheUpdateService;
+}
+
+nsresult
+nsOfflineCacheUpdateService::Schedule(nsOfflineCacheUpdate *aUpdate)
+{
+    LOG(("nsOfflineCacheUpdateService::Schedule [%p, update=%p]",
+         this, aUpdate));
+
+    nsresult rv;
+    nsCOMPtr<nsIObserverService> observerService =
+        do_GetService("@mozilla.org/observer-service;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(aUpdate),
+                                     "offline-cache-update-added",
+                                     nsnull);
+
+    mUpdates.AppendElement(aUpdate);
+
+    ProcessNextUpdate();
+
+    return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdateService::ScheduleOnDocumentStop(nsOfflineCacheUpdate *aUpdate,
+                                                    nsIDOMDocument *aDocument)
+{
+    LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, update=%p, doc=%p]",
+         this, aUpdate, aDocument));
+
+    if (!mDocUpdates.Put(aDocument, aUpdate))
+        return NS_ERROR_FAILURE;
+
+    return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdateService::UpdateFinished(nsOfflineCacheUpdate *aUpdate)
+{
+    LOG(("nsOfflineCacheUpdateService::UpdateFinished [%p, update=%p]",
+         this, aUpdate));
+
+    NS_ASSERTION(mUpdates.Length() > 0 &&
+                 mUpdates[0] == aUpdate, "Unknown update completed");
+
+    // keep this item alive until we're done notifying observers
+    nsRefPtr<nsOfflineCacheUpdate> update = mUpdates[0];
+    mUpdates.RemoveElementAt(0);
+    mUpdateRunning = PR_FALSE;
+
+    nsresult rv;
+    nsCOMPtr<nsIObserverService> observerService =
+        do_GetService("@mozilla.org/observer-service;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(aUpdate),
+                                     "offline-cache-update-completed",
+                                     nsnull);
+
+    ProcessNextUpdate();
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsOfflineCacheUpdateService::ProcessNextUpdate()
+{
+    LOG(("nsOfflineCacheUpdateService::ProcessNextUpdate [%p, num=%d]",
+         this, mUpdates.Length()));
+
+    if (mDisabled)
+        return NS_ERROR_ABORT;
+
+    if (mUpdateRunning)
+        return NS_OK;
+
+    if (mUpdates.Length() > 0) {
+        mUpdateRunning = PR_TRUE;
+        return mUpdates[0]->Begin();
+    }
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIOfflineCacheUpdateService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::GetNumUpdates(PRUint32 *aNumUpdates)
+{
+    LOG(("nsOfflineCacheUpdateService::GetNumUpdates [%p]", this));
+
+    *aNumUpdates = mUpdates.Length();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::GetUpdate(PRUint32 aIndex,
+                                       nsIOfflineCacheUpdate **aUpdate)
+{
+    LOG(("nsOfflineCacheUpdateService::GetUpdate [%p, %d]", this, aIndex));
+
+    if (aIndex < mUpdates.Length()) {
+        NS_ADDREF(*aUpdate = mUpdates[aIndex]);
+    } else {
+        *aUpdate = nsnull;
+    }
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::Observe(nsISupports     *aSubject,
+                                     const char      *aTopic,
+                                     const PRUnichar *aData)
+{
+    if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+        if (mUpdates.Length() > 0)
+            mUpdates[0]->Cancel();
+        mDisabled = PR_TRUE;
+    }
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIWebProgressListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OnProgressChange(nsIWebProgress *aProgress,
+                                              nsIRequest *aRequest,
+                                              PRInt32 curSelfProgress,
+                                              PRInt32 maxSelfProgress,
+                                              PRInt32 curTotalProgress,
+                                              PRInt32 maxTotalProgress)
+{
+    NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OnStateChange(nsIWebProgress* aWebProgress,
+                                           nsIRequest *aRequest,
+                                           PRUint32 progressStateFlags,
+                                           nsresult aStatus)
+{
+    if ((progressStateFlags & STATE_IS_DOCUMENT) &&
+        (progressStateFlags & STATE_STOP)) {
+        if (mDocUpdates.Count() == 0)
+            return NS_OK;
+
+        nsCOMPtr<nsIDOMWindow> window;
+        aWebProgress->GetDOMWindow(getter_AddRefs(window));
+        if (!window) return NS_OK;
+
+        nsCOMPtr<nsIDOMDocument> doc;
+        window->GetDocument(getter_AddRefs(doc));
+        if (!doc) return NS_OK;
+
+        LOG(("nsOfflineCacheUpdateService::OnStateChange [%p, doc=%p]",
+             this, doc.get()));
+
+        nsRefPtr<nsOfflineCacheUpdate> update;
+        if (mDocUpdates.Get(doc, getter_AddRefs(update))) {
+            Schedule(update);
+            mDocUpdates.Remove(doc);
+        }
+
+        return NS_OK;
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OnLocationChange(nsIWebProgress* aWebProgress,
+                                              nsIRequest* aRequest,
+                                              nsIURI *location)
+{
+    NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OnStatusChange(nsIWebProgress* aWebProgress,
+                                            nsIRequest* aRequest,
+                                            nsresult aStatus,
+                                            const PRUnichar* aMessage)
+{
+    NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OnSecurityChange(nsIWebProgress *aWebProgress,
+                                              nsIRequest *aRequest,
+                                              PRUint32 state)
+{
+    NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+    return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; 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 mozilla.org code.
+ *
+ * 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 ***** */
+
+#ifndef nsOfflineCacheUpdate_h__
+#define nsOfflineCacheUpdate_h__
+
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#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 "nsIRequestObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIWebProgressListener.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+class nsOfflineCacheUpdate;
+
+class nsOfflineCacheUpdateItem : public nsIDOMLoadStatus
+                               , public nsIStreamListener
+                               , public nsIInterfaceRequestor
+                               , public nsIChannelEventSink
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIDOMLOADSTATUS
+    NS_DECL_NSIREQUESTOBSERVER
+    NS_DECL_NSISTREAMLISTENER
+    NS_DECL_NSIINTERFACEREQUESTOR
+    NS_DECL_NSICHANNELEVENTSINK
+
+    nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate,
+                             nsIURI *aURI,
+                             nsIURI *aReferrerURI,
+                             nsIDOMNode *aSource);
+    ~nsOfflineCacheUpdateItem();
+
+    nsCOMPtr<nsIURI>           mURI;
+    nsCOMPtr<nsIURI>           mReferrerURI;
+    nsCOMPtr<nsIWeakReference> mSource;
+
+    nsresult OpenChannel();
+    nsresult Cancel();
+
+private:
+    nsOfflineCacheUpdate*          mUpdate;
+    nsCOMPtr<nsIChannel>           mChannel;
+    PRUint16                       mState;
+    PRInt32                        mBytesRead;
+};
+
+class nsOfflineCacheUpdate : public nsIOfflineCacheUpdate
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOFFLINECACHEUPDATE
+
+    nsOfflineCacheUpdate();
+    ~nsOfflineCacheUpdate();
+
+    nsresult Init();
+
+    nsresult Begin();
+    nsresult Cancel();
+
+    void LoadCompleted();
+private:
+    nsresult ProcessNextURI();
+    nsresult AddOwnedItems(const nsACString &aOwnerURI);
+    nsresult AddDomainItems();
+    nsresult NotifyAdded(nsOfflineCacheUpdateItem *aItem);
+    nsresult NotifyCompleted(nsOfflineCacheUpdateItem *aItem);
+    nsresult Finish();
+
+    enum {
+        STATE_UNINITIALIZED,
+        STATE_INITIALIZED,
+        STATE_RUNNING,
+        STATE_CANCELLED,
+        STATE_FINISHED
+    } mState;
+
+    PRBool mAddedItems;
+    PRBool mPartialUpdate;
+    nsCString mUpdateDomain;
+    nsCString mOwnerURI;
+    nsCOMPtr<nsIURI> mReferrerURI;
+
+    nsCOMPtr<nsIOfflineCacheSession> mCacheSession;
+    nsCOMPtr<nsIObserverService> mObserverService;
+
+    /* Items being updated */
+    nsTArray<nsRefPtr<nsOfflineCacheUpdateItem> > mItems;
+
+    /* Clients watching this update for changes */
+    nsCOMArray<nsIWeakReference> mWeakObservers;
+    nsCOMArray<nsIOfflineCacheUpdateObserver> mObservers;
+};
+
+class nsOfflineCacheUpdateService : public nsIOfflineCacheUpdateService
+                                  , public nsIWebProgressListener
+                                  , public nsIObserver
+                                  , public nsSupportsWeakReference
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOFFLINECACHEUPDATESERVICE
+    NS_DECL_NSIWEBPROGRESSLISTENER
+    NS_DECL_NSIOBSERVER
+
+    nsOfflineCacheUpdateService();
+    ~nsOfflineCacheUpdateService();
+
+    nsresult Init();
+
+    nsresult Schedule(nsOfflineCacheUpdate *aUpdate);
+    nsresult ScheduleOnDocumentStop(nsOfflineCacheUpdate *aUpdate,
+                                    nsIDOMDocument *aDocument);
+    nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate);
+
+    static nsOfflineCacheUpdateService *GetInstance();
+
+private:
+    nsresult ProcessNextUpdate();
+
+    nsTArray<nsRefPtr<nsOfflineCacheUpdate> > mUpdates;
+    nsRefPtrHashtable<nsVoidPtrHashKey, nsOfflineCacheUpdate> mDocUpdates;
+
+    PRBool mDisabled;
+    PRBool mUpdateRunning;
+};
+
+#endif