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 idunknown
push userunknown
push dateunknown
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