Bug 461071: Allow application caches to be obsoleted. r=honzab, r+sr=bz
authorDave Camp <dcamp@mozilla.com>
Tue, 04 Nov 2008 10:59:12 -0800
changeset 21313 785a4fc138f5e64fe41a065002825b4c06c2b012
parent 21312 b5fcc6701197028bd1cf2aa36fcade10b2786774
child 21314 838205b0621cfd1b91f7f74dd2e81e7622c5dd77
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab, r
bugs461071
milestone1.9.1b2pre
Bug 461071: Allow application caches to be obsoleted. r=honzab, r+sr=bz
dom/public/idl/offline/nsIDOMOfflineResourceList.idl
dom/src/offline/nsDOMOfflineResourceList.cpp
dom/src/offline/nsDOMOfflineResourceList.h
dom/tests/mochitest/ajax/offline/Makefile.in
dom/tests/mochitest/ajax/offline/obsolete.html
dom/tests/mochitest/ajax/offline/test_obsolete.html
netwerk/base/public/nsIApplicationCacheService.idl
netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
netwerk/cache/src/nsDiskCacheDeviceSQL.h
uriloader/prefetch/nsIOfflineCacheUpdate.idl
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
--- a/dom/public/idl/offline/nsIDOMOfflineResourceList.idl
+++ b/dom/public/idl/offline/nsIDOMOfflineResourceList.idl
@@ -33,17 +33,17 @@
  * 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 "domstubs.idl"
 
-[scriptable, uuid(8449bce2-0d8c-4c74-ab79-b41b8d81f1c4)]
+[scriptable, uuid(c9732cb7-e104-4cf1-9936-a8592fd46c5a)]
 interface nsIDOMOfflineResourceList : nsISupports
 {
   /**
    * Enumerate the list of dynamically-managed entries.
    */
   readonly attribute unsigned long length;
   DOMString item(in unsigned long index);
 
@@ -77,40 +77,37 @@ interface nsIDOMOfflineResourceList : ns
   const unsigned short IDLE = 1;
 
   /* The manifest is being fetched and checked for updates */
   const unsigned short CHECKING = 2;
 
   /* Resources are being downloaded to be added to the cache */
   const unsigned short DOWNLOADING = 3;
 
-  /**
-   * There is a new version of the application cache available
-   *
-   * Versioned application caches are not currently implemented, so this
-   * value will not yet be returned
-   */
+  /* There is a new version of the application cache available */
   const unsigned short UPDATEREADY = 4;
 
+  /* The application cache group is now obsolete. */
+  const unsigned short OBSOLETE = 5;
+
   readonly attribute unsigned short status;
 
   /**
    * Begin the application update process on the associated application cache.
    */
   void update();
 
   /**
-   * Swap in the newest version of the application cache.
-   *
-   * Versioned application caches are not currently implemented, so this
-   * method will throw an exception.
+   * Swap in the newest version of the application cache, or disassociate
+   * from the cache if the cache group is obsolete.
    */
   void swapCache();
 
   /* Events */
   attribute nsIDOMEventListener onchecking;
   attribute nsIDOMEventListener onerror;
   attribute nsIDOMEventListener onnoupdate;
   attribute nsIDOMEventListener ondownloading;
   attribute nsIDOMEventListener onprogress;
   attribute nsIDOMEventListener onupdateready;
   attribute nsIDOMEventListener oncached;
+  attribute nsIDOMEventListener onobsolete;
 };
--- a/dom/src/offline/nsDOMOfflineResourceList.cpp
+++ b/dom/src/offline/nsDOMOfflineResourceList.cpp
@@ -60,16 +60,17 @@
 
 #define CHECKING_STR    "checking"
 #define ERROR_STR       "error"
 #define NOUPDATE_STR    "noupdate"
 #define DOWNLOADING_STR "downloading"
 #define PROGRESS_STR    "progress"
 #define CACHED_STR      "cached"
 #define UPDATEREADY_STR "updateready"
+#define OBSOLETE_STR    "obsolete"
 
 // To prevent abuse of the resource list for data storage, the number
 // of offline urls and their length are limited.
 
 static const char kMaxEntriesPref[] =  "offline.max_site_resources";
 #define DEFAULT_MAX_ENTRIES 100
 #define MAX_URI_LENGTH 2048
 
@@ -85,24 +86,26 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCheckingListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mErrorListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mNoUpdateListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mDownloadingListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mProgressListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCachedListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mUpdateReadyListeners)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mObsoleteListeners)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnCheckingListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnNoUpdateListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnDownloadingListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnProgressListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnCachedListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnUpdateReadyListener)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnObsoleteListener)
 
   for (PRUint32 i = 0; i < tmp->mPendingEvents.Length(); i++) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPendingEvents[i].event);
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPendingEvents[i].listener);
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mPendingEvents[i].listeners);
   }
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -113,24 +116,26 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mCheckingListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mErrorListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mNoUpdateListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mDownloadingListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mProgressListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mCachedListeners)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mUpdateReadyListeners)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mObsoleteListeners)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnCheckingListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnErrorListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnNoUpdateListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnDownloadingListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnProgressListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnCachedListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnUpdateReadyListener)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnObsoleteListener)
 
   for (PRUint32 i = 0; i < tmp->mPendingEvents.Length(); i++) {
     NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPendingEvents[i].event);
     NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPendingEvents[i].listener);
     NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mPendingEvents[i].listeners);
   }
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@@ -232,24 +237,26 @@ nsDOMOfflineResourceList::Disconnect()
 {
   mCheckingListeners.Clear();
   mErrorListeners.Clear();
   mNoUpdateListeners.Clear();
   mDownloadingListeners.Clear();
   mProgressListeners.Clear();
   mCachedListeners.Clear();
   mUpdateReadyListeners.Clear();
+  mObsoleteListeners.Clear();
 
   mOnCheckingListener = nsnull;
   mOnErrorListener = nsnull;
   mOnNoUpdateListener = nsnull;
   mOnDownloadingListener = nsnull;
   mOnProgressListener = nsnull;
   mOnCachedListener = nsnull;
   mOnUpdateReadyListener = nsnull;
+  mOnObsoleteListener = nsnull;
 
   mPendingEvents.Clear();
 }
 
 //
 // nsDOMOfflineResourceList::nsIDOMOfflineResourceList
 //
 
@@ -418,17 +425,19 @@ nsDOMOfflineResourceList::GetStatus(PRUi
     return NS_OK;
   }
 
   nsCOMPtr<nsIApplicationCache> activeCache;
   rv = mApplicationCacheService->GetActiveCache(mManifestSpec,
                                                 getter_AddRefs(activeCache));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (appCache == activeCache) {
+  if (activeCache == nsnull) {
+    *aStatus = nsIDOMOfflineResourceList::OBSOLETE;
+  } else if (appCache == activeCache) {
     *aStatus = nsIDOMOfflineResourceList::IDLE;
   } else {
     *aStatus = nsIDOMOfflineResourceList::UPDATEREADY;
   }
 
   return NS_OK;
 }
 
@@ -473,17 +482,20 @@ nsDOMOfflineResourceList::SwapCache()
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIApplicationCache> currentAppCache = GetDocumentAppCache();
 
   nsCOMPtr<nsIApplicationCache> newAppCache;
   rv = serv->GetActiveCache(mManifestSpec, getter_AddRefs(newAppCache));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!newAppCache || newAppCache == currentAppCache) {
+  // In the case of an obsolete cache group, newAppCache might be null.
+  // We will disassociate from the cache in that case.
+
+  if (newAppCache == currentAppCache) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   ClearCachedKeys();
 
   nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer =
     GetDocumentAppCacheContainer();
 
@@ -643,16 +655,37 @@ nsDOMOfflineResourceList::SetOnupdaterea
   nsresult rv = Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   mOnUpdateReadyListener = aOnupdateready;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMOfflineResourceList::GetOnobsolete(nsIDOMEventListener **aOnobsolete)
+{
+  nsresult rv = Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  NS_ENSURE_ARG_POINTER(aOnobsolete);
+  NS_IF_ADDREF(*aOnobsolete = mOnObsoleteListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMOfflineResourceList::SetOnobsolete(nsIDOMEventListener *aOnobsolete)
+{
+  nsresult rv = Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mOnObsoleteListener = aOnobsolete;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMOfflineResourceList::AddEventListener(const nsAString& aType,
                                            nsIDOMEventListener *aListener,
                                            PRBool aUseCapture)
 {
   nsresult rv = Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ENSURE_ARG(aListener);
@@ -666,16 +699,17 @@ nsDOMOfflineResourceList::AddEventListen
 
   IMPL_ADD_LISTENER(CHECKING_STR, mCheckingListeners)
   IMPL_ADD_LISTENER(ERROR_STR, mErrorListeners)
   IMPL_ADD_LISTENER(NOUPDATE_STR, mNoUpdateListeners)
   IMPL_ADD_LISTENER(DOWNLOADING_STR, mDownloadingListeners)
   IMPL_ADD_LISTENER(PROGRESS_STR, mProgressListeners)
   IMPL_ADD_LISTENER(CACHED_STR, mCachedListeners)
   IMPL_ADD_LISTENER(UPDATEREADY_STR, mUpdateReadyListeners)
+  IMPL_ADD_LISTENER(OBSOLETE_STR, mObsoleteListeners)
   {
     return NS_ERROR_INVALID_ARG;
   }
 
   array->AppendObject(aListener);
 #undef IMPL_ADD_LISTENER
 
   return NS_OK;
@@ -700,16 +734,17 @@ nsDOMOfflineResourceList::RemoveEventLis
 
   IMPL_REMOVE_LISTENER(CHECKING_STR, mCheckingListeners)
   IMPL_REMOVE_LISTENER(ERROR_STR, mErrorListeners)
   IMPL_REMOVE_LISTENER(NOUPDATE_STR, mNoUpdateListeners)
   IMPL_REMOVE_LISTENER(DOWNLOADING_STR, mDownloadingListeners)
   IMPL_REMOVE_LISTENER(PROGRESS_STR, mProgressListeners)
   IMPL_REMOVE_LISTENER(CACHED_STR, mCachedListeners)
   IMPL_REMOVE_LISTENER(UPDATEREADY_STR, mUpdateReadyListeners)
+  IMPL_REMOVE_LISTENER(OBSOLETE_STR, mObsoleteListeners)
   {
     return NS_ERROR_INVALID_ARG;
   }
 
   // Allow a caller to remove O(N^2) behavior by removing end-to-start.
   for (PRUint32 i = array->Count() - 1; i != PRUint32(-1); --i) {
     if (array->ObjectAt(i) == aListener) {
       array->RemoveObjectAt(i);
@@ -921,16 +956,24 @@ nsDOMOfflineResourceList::ItemStarted(ns
 
 NS_IMETHODIMP
 nsDOMOfflineResourceList::ItemCompleted(nsIOfflineCacheUpdate *aUpdate,
                                         nsIDOMLoadStatus *aItem)
 {
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMOfflineResourceList::Obsolete(nsIOfflineCacheUpdate *aUpdate)
+{
+  SendEvent(NS_LITERAL_STRING(OBSOLETE_STR),
+            mOnObsoleteListener, mObsoleteListeners);
+  return NS_OK;
+}
+
 nsresult
 nsDOMOfflineResourceList::GetCacheKey(const nsAString &aURI, nsCString &aKey)
 {
   nsCOMPtr<nsIURI> requestedURI;
   nsresult rv = NS_NewURI(getter_AddRefs(requestedURI), aURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return GetCacheKey(requestedURI, aKey);
--- a/dom/src/offline/nsDOMOfflineResourceList.h
+++ b/dom/src/offline/nsDOMOfflineResourceList.h
@@ -126,24 +126,26 @@ private:
 
   nsCOMArray<nsIDOMEventListener> mCheckingListeners;
   nsCOMArray<nsIDOMEventListener> mErrorListeners;
   nsCOMArray<nsIDOMEventListener> mNoUpdateListeners;
   nsCOMArray<nsIDOMEventListener> mDownloadingListeners;
   nsCOMArray<nsIDOMEventListener> mProgressListeners;
   nsCOMArray<nsIDOMEventListener> mCachedListeners;
   nsCOMArray<nsIDOMEventListener> mUpdateReadyListeners;
+  nsCOMArray<nsIDOMEventListener> mObsoleteListeners;
 
   nsCOMPtr<nsIDOMEventListener> mOnCheckingListener;
   nsCOMPtr<nsIDOMEventListener> mOnErrorListener;
   nsCOMPtr<nsIDOMEventListener> mOnNoUpdateListener;
   nsCOMPtr<nsIDOMEventListener> mOnDownloadingListener;
   nsCOMPtr<nsIDOMEventListener> mOnProgressListener;
   nsCOMPtr<nsIDOMEventListener> mOnCachedListener;
   nsCOMPtr<nsIDOMEventListener> mOnUpdateReadyListener;
+  nsCOMPtr<nsIDOMEventListener> mOnObsoleteListener;
 
   struct PendingEvent {
     nsCOMPtr<nsIDOMEvent> event;
     nsCOMPtr<nsIDOMEventListener> listener;
     nsCOMArray<nsIDOMEventListener> listeners;
   };
 
   nsTArray<PendingEvent> mPendingEvents;
--- a/dom/tests/mochitest/ajax/offline/Makefile.in
+++ b/dom/tests/mochitest/ajax/offline/Makefile.in
@@ -55,16 +55,18 @@ include $(topsrcdir)/config/rules.mk
 	test_identicalManifest.html \
 	test_changingManifest.html \
 	test_offlineIFrame.html \
 	test_bug445544.html \
 	445544_part1.html \
 	445544_part2.html \
 	445544.cacheManifest \
 	445544.cacheManifest^headers^ \
+	test_obsolete.html \
+	obsolete.html \
 	badManifestMagic.cacheManifest \
 	badManifestMagic.cacheManifest^headers^ \
 	missingFile.cacheManifest \
 	missingFile.cacheManifest^headers^ \
 	simpleManifest.cacheManifest \
 	simpleManifest.cacheManifest^headers^ \
 	simpleManifest.notmanifest \
 	changing1Sec.sjs \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/obsolete.html
@@ -0,0 +1,62 @@
+<html manifest="obsolete.cacheManifest">
+<head>
+<title>obsolete test</title>
+<script type="text/javascript">
+
+function obsolete(evt)
+{
+  window.opener.ok(true, "Got an 'obsolete' event");
+
+  // The cache status is switched immediately AFTER sending the event,
+  // make sure that it isn't OBSOLETE yet...
+  window.opener.isnot(applicationCache.status, 5,
+                      "Status should not yet be 5 (obsolete)");
+
+  // But check that it is after the event is fired.
+  setTimeout(function(){
+    window.opener.is(applicationCache.status, 5,
+                     "Status should be 5 (obsolete)");
+
+    // Now swapCache(), and our new status should be UNCACHED.
+    applicationCache.swapCache();
+    window.opener.is(applicationCache.status, 0,
+                     "Status should be 0 (UNCACHED)");
+    window.opener.finish();
+  }, 0);
+}
+
+function fail(evt)
+{
+  window.opener.ok(false, "Got an unexpected event: " + evt.type)
+  window.opener.finish();
+}
+
+applicationCache.oncached = function() {
+  // ok, we've successfully loaded from the initial cache.
+  try {
+    applicationCache.swapCache();
+    window.opener.todo(false, "We shouldn't have to swapCache in the oncached handler (bug 443023)");
+  } catch(e) {
+  }
+
+  // Now delete the manifest and refresh, we should get an "obsolete" message.
+  applicationCache.oncached = fail;
+  applicationCache.onupdateready = fail;
+  applicationCache.onobsolete = obsolete;
+
+  var req = new XMLHttpRequest();
+  req.open("DELETE", "obsolete.cacheManifest");
+  req.send("");
+  req.onreadystatechange = function() {
+    if (req.readyState == 4) {
+      applicationCache.update();
+    }
+  }
+}
+
+</script>
+</head>
+
+<body>
+<h1></h1>
+</body> </html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/test_obsolete.html
@@ -0,0 +1,56 @@
+<html>
+<head>
+<title>Test obsolete application caches</title>
+
+<script type="text/javascript" src="/MochiKit/packed.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="text/javascript">
+
+var gTestWin;
+
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+// Enable the offline-app permission before loading the new window
+var pm = Cc["@mozilla.org/permissionmanager;1"]
+        .getService(Ci.nsIPermissionManager);
+var uri = Cc["@mozilla.org/network/io-service;1"]
+         .getService(Ci.nsIIOService)
+         .newURI(window.location.href, null, null);
+pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+// Now add the manifest that the test frame will use.
+var manifest =
+"CACHE MANIFEST\n" +
+"obsolete.html\n";
+
+var req = new XMLHttpRequest();
+req.open("PUT", "obsolete.cacheManifest");
+req.setRequestHeader("Content-Type", "text/cache-manifest");
+req.send(manifest);
+req.onreadystatechange = function() {
+  if (req.readyState == 4) {
+    // now this will properly load the manifest.
+    gTestWin = window.open("obsolete.html");
+  }
+}
+
+function finish()
+{
+  gTestWin.close();
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body>
+
+</body>
+</html>
--- a/netwerk/base/public/nsIApplicationCacheService.idl
+++ b/netwerk/base/public/nsIApplicationCacheService.idl
@@ -40,17 +40,17 @@
 #include "nsISupports.idl"
 
 interface nsIApplicationCache;
 
 /**
  * The application cache service manages the set of application cache
  * groups.
  */
-[scriptable, uuid(23b37fbd-5c55-43a5-b592-abf31c05e967)]
+[scriptable, uuid(36595ec8-e849-49f8-9eb4-e895a3cd39fe)]
 interface nsIApplicationCacheService : nsISupports
 {
     /**
      * Create a new, empty application cache for the given cache
      * group.
      */
     nsIApplicationCache createApplicationCache(in ACString group);
 
@@ -60,16 +60,21 @@ interface nsIApplicationCacheService : n
     nsIApplicationCache getApplicationCache(in ACString clientID);
 
     /**
      * Get the currently active cache object for a cache group.
      */
     nsIApplicationCache getActiveCache(in ACString group);
 
     /**
+     * Deactivate the currently-active cache object for a cache group.
+     */
+    void deactivateGroup(in ACString group);
+
+    /**
      * Try to find the best application cache to serve a resource.
      */
     nsIApplicationCache chooseApplicationCache(in ACString key);
 
     /**
      * Flags the key as being opportunistically cached.
      *
      * This method should also propagate the entry to other
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
@@ -2024,16 +2024,38 @@ nsOfflineCacheDevice::GetActiveCache(con
 
   nsCString *clientID;
   if (mActiveCachesByGroup.Get(group, &clientID))
     return GetApplicationCache(*clientID, out);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsOfflineCacheDevice::DeactivateGroup(const nsACString &group)
+{
+  nsCString *active = nsnull;
+
+  AutoResetStatement statement(mStatement_DeactivateGroup);
+  nsresult rv = statement->BindUTF8StringParameter(0, group);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mActiveCachesByGroup.Get(group, &active))
+  {
+    mActiveCaches.Remove(*active);
+    mActiveCachesByGroup.Remove(group);
+    active = nsnull;
+  }
+
+  return NS_OK;
+}
+
 PRBool
 nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI, const nsCString &clientID)
 {
   if (mActiveCaches.Contains(clientID)) {
     nsCAutoString groupID;
     nsresult rv = GetGroupForCache(clientID, groupID);
     NS_ENSURE_SUCCESS(rv, PR_FALSE);
 
@@ -2180,39 +2202,16 @@ PRBool
 nsOfflineCacheDevice::IsActiveCache(const nsCSubstring &group,
                                     const nsCSubstring &clientID)
 {
   nsCString *active = nsnull;
   return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
 }
 
 nsresult
-nsOfflineCacheDevice::DeactivateGroup(const nsCSubstring &group)
-{
-  nsCString *active = nsnull;
-
-  AutoResetStatement statement(mStatement_DeactivateGroup);
-  nsresult rv = statement->BindUTF8StringParameter(
-                                           0, group);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = statement->Execute();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (mActiveCachesByGroup.Get(group, &active))
-  {
-    mActiveCaches.Remove(*active);
-    mActiveCachesByGroup.Remove(group);
-    active = nsnull;
-  }
-
-  return NS_OK;
-}
-
-nsresult
 nsOfflineCacheDevice::GetGroupForCache(const nsACString &clientID,
                                        nsCString &out)
 {
   out.Assign(clientID);
   out.Truncate(out.FindChar('|'));
   NS_UnescapeURL(out);
 
   return NS_OK;
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.h
@@ -171,17 +171,16 @@ public:
   nsresult                ClearKeysOwnedByDomain(const char *clientID,
                                                  const nsACString &ownerDomain);
   nsresult                EvictUnownedEntries(const char *clientID);
 
   nsresult                ActivateCache(const nsCSubstring &group,
                                         const nsCSubstring &clientID);
   PRBool                  IsActiveCache(const nsCSubstring &group,
                                         const nsCSubstring &clientID);
-  nsresult                DeactivateGroup(const nsCSubstring &group);
   nsresult                GetGroupForCache(const nsCSubstring &clientID,
                                            nsCString &out);
 
   /**
    * Preference accessors
    */
 
   void                    SetCacheParentDirectory(nsILocalFile * parentDir);
--- a/uriloader/prefetch/nsIOfflineCacheUpdate.idl
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -41,17 +41,17 @@
 interface nsIURI;
 interface nsIDOMNode;
 interface nsIDOMDocument;
 interface nsIDOMLoadStatus;
 interface nsIOfflineCacheUpdate;
 interface nsIPrincipal;
 interface nsIPrefBranch;
 
-[scriptable, uuid(0aa38757-999c-44d6-bdb4-7dd32634fa83)]
+[scriptable, uuid(a28abeaf-a0b4-4440-b2fe-bc78249710ea)]
 interface nsIOfflineCacheUpdateObserver : nsISupports {
   /**
    * There was an error updating the cache.
    *
    * @param aUpdate
    *        The nsIOfflineCacheUpdate being processed.
    */
   void error(in nsIOfflineCacheUpdate aUpdate);
@@ -68,16 +68,24 @@ interface nsIOfflineCacheUpdateObserver 
    * No update was necessary.
    *
    * @param aUpdate
    *        The nsIOfflineCacheUpdate being processed.
    */
   void noUpdate(in nsIOfflineCacheUpdate aUpdate);
 
   /**
+   * The cache group is now obsolete.
+   *
+   * @param aUpdate
+   *        The nsIOfflineCacheUpdate being processed.
+   */
+  void obsolete(in nsIOfflineCacheUpdate aUpdate);
+
+  /**
    * Starting to download resources
    *
    * @param aUpdate
    *        The nsIOfflineCacheUpdate being processed.
    */
   void downloading(in nsIOfflineCacheUpdate aUpdate);
 
   /**
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -899,16 +899,17 @@ NS_IMPL_ISUPPORTS1(nsOfflineCacheUpdate,
 // nsOfflineCacheUpdate <public>
 //-----------------------------------------------------------------------------
 
 nsOfflineCacheUpdate::nsOfflineCacheUpdate()
     : mState(STATE_UNINITIALIZED)
     , mAddedItems(PR_FALSE)
     , mPartialUpdate(PR_FALSE)
     , mSucceeded(PR_TRUE)
+    , mObsolete(PR_FALSE)
     , mCurrentItem(-1)
 {
 }
 
 nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
 {
     LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
 }
@@ -1114,16 +1115,29 @@ nsOfflineCacheUpdate::LoadCompleted()
     }
 
     if (mState == STATE_CHECKING) {
         // Manifest load finished.
 
         NS_ASSERTION(mManifestItem,
                      "Must have a manifest item in STATE_CHECKING.");
 
+        // A 404 or 410 is interpreted as an intentional removal of
+        // the manifest file, rather than a transient server error.
+        // Obsolete this cache group if one of these is returned.
+        PRUint16 status;
+        rv = mManifestItem->GetStatus(&status);
+        if (status == 404 || status == 410) {
+            mSucceeded = PR_FALSE;
+            mObsolete = PR_TRUE;
+            NotifyObsolete();
+            Finish();
+            return;
+        }
+
         PRBool doUpdate;
         if (NS_FAILED(HandleManifest(&doUpdate))) {
             mSucceeded = PR_FALSE;
             NotifyError();
             Finish();
             return;
         }
 
@@ -1387,16 +1401,34 @@ nsOfflineCacheUpdate::NotifyNoUpdate()
     for (PRInt32 i = 0; i < observers.Count(); i++) {
         observers[i]->NoUpdate(this);
     }
 
     return NS_OK;
 }
 
 nsresult
+nsOfflineCacheUpdate::NotifyObsolete()
+{
+    LOG(("nsOfflineCacheUpdate::NotifyObsolete [%p]", this));
+
+    mState = STATE_FINISHED;
+
+    nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+    nsresult rv = GatherObservers(observers);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    for (PRInt32 i = 0; i < observers.Count(); i++) {
+        observers[i]->Obsolete(this);
+    }
+
+    return NS_OK;
+}
+
+nsresult
 nsOfflineCacheUpdate::NotifyDownloading()
 {
     LOG(("nsOfflineCacheUpdate::NotifyDownloading [%p]", this));
 
     nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
     nsresult rv = GatherObservers(observers);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1490,16 +1522,26 @@ nsOfflineCacheUpdate::Finish()
                 mSucceeded = PR_FALSE;
             }
 
             for (PRInt32 i = 0; i < mDocuments.Count(); i++) {
                 AssociateDocument(mDocuments[i]);
             }
         }
 
+        if (mObsolete) {
+            nsCOMPtr<nsIApplicationCacheService> appCacheService =
+                do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
+            if (appCacheService) {
+                nsCAutoString groupID;
+                mApplicationCache->GetGroupID(groupID);
+                appCacheService->DeactivateGroup(groupID);
+             }
+         }
+
         if (!mSucceeded) {
             // Update was not merged, mark all the loads as failures
             for (PRUint32 i = 0; i < mItems.Length(); i++) {
                 mItems[i]->Cancel();
             }
 
             mApplicationCache->Discard();
         }
--- a/uriloader/prefetch/nsOfflineCacheUpdate.h
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -232,34 +232,37 @@ private:
     // specified namespaces will be added.
     nsresult AddExistingItems(PRUint32 aType,
                               nsTArray<nsCString>* namespaceFilter = nsnull);
 
     nsresult GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers);
     nsresult NotifyError();
     nsresult NotifyChecking();
     nsresult NotifyNoUpdate();
+    nsresult NotifyObsolete();
     nsresult NotifyDownloading();
     nsresult NotifyStarted(nsOfflineCacheUpdateItem *aItem);
     nsresult NotifyCompleted(nsOfflineCacheUpdateItem *aItem);
     nsresult AssociateDocument(nsIDOMDocument *aDocument);
     nsresult Finish();
 
     enum {
         STATE_UNINITIALIZED,
         STATE_INITIALIZED,
         STATE_CHECKING,
         STATE_DOWNLOADING,
         STATE_CANCELLED,
         STATE_FINISHED
     } mState;
 
-    PRBool mAddedItems;
-    PRBool mPartialUpdate;
-    PRBool mSucceeded;
+    PRPackedBool mAddedItems;
+    PRPackedBool mPartialUpdate;
+    PRPackedBool mSucceeded;
+    PRPackedBool mObsolete;
+
     nsCString mUpdateDomain;
     nsCOMPtr<nsIURI> mManifestURI;
 
     nsCOMPtr<nsIURI> mDocumentURI;
 
     nsCString mClientID;
     nsCOMPtr<nsIApplicationCache> mApplicationCache;
     nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;