Bug 442813: Implement fallback/opportunistic caching for application caches. p=me, Honza Bambas <honzab@allpeers.com>, r+sr=biesi
authorDave Camp <dcamp@mozilla.com>
Tue, 30 Sep 2008 11:34:23 -0700
changeset 19925 13620c17990b2254207f8c5e2f2e433541e9559c
parent 19924 6bdc478490ff38f4ed5a9b709f18d028ac75eeea
child 19927 4e05b346168329004a998697c0043003ac5710b0
push id2550
push userdcamp@mozilla.com
push dateTue, 30 Sep 2008 18:43:21 +0000
treeherdermozilla-central@13620c17990b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs442813
milestone1.9.1b1pre
Bug 442813: Implement fallback/opportunistic caching for application caches. p=me, Honza Bambas <honzab@allpeers.com>, r+sr=biesi
netwerk/base/public/nsIApplicationCache.idl
netwerk/base/public/nsIApplicationCacheService.idl
netwerk/build/nsNetCID.h
netwerk/build/nsNetModule.cpp
netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
netwerk/cache/src/nsDiskCacheDeviceSQL.h
netwerk/protocol/http/public/nsIHttpChannelInternal.idl
netwerk/protocol/http/src/nsHttpChannel.cpp
netwerk/protocol/http/src/nsHttpChannel.h
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
--- a/netwerk/base/public/nsIApplicationCache.idl
+++ b/netwerk/base/public/nsIApplicationCache.idl
@@ -34,31 +34,89 @@
  * 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 nsIArray;
+
+/**
+ * Application caches can store a set of namespace entries that affect
+ * loads from the application cache.  If a load from the cache fails
+ * to match an exact cache entry, namespaces entries will be searched
+ * for a substring match, and should be applied appropriately.
+ */
+[scriptable, uuid(96e4c264-2065-4ce9-93bb-43734c62c4eb)]
+interface nsIApplicationCacheNamespace : nsISupports
+{
+    /**
+     * Items matching this namespace can be fetched from the network
+     * when loading from this cache.  The "data" attribute is unused.
+     */
+    const unsigned long NAMESPACE_BYPASS = 1 << 0;
+
+    /**
+     * Items matching this namespace can be fetched from the network
+     * when loading from this cache.  If the load fails, the cache entry
+     * specified by the "data" attribute should be loaded instead.
+     */
+    const unsigned long NAMESPACE_FALLBACK = 1 << 1;
+
+    /**
+     * Items matching this namespace should be cached
+     * opportunistically.  Successful toplevel loads of documents
+     * in this namespace should be placed in the application cache.
+     * Namespaces specifying NAMESPACE_OPPORTUNISTIC may also specify
+     * NAMESPACE_FALLBACK to supply a fallback entry.
+     */
+    const unsigned long NAMESPACE_OPPORTUNISTIC = 1 << 2;
+
+    /**
+     * Initialize the namespace.
+     */
+    void init(in unsigned long itemType,
+              in ACString namespaceSpec,
+              in ACString data);
+
+    /**
+     * The namespace type.
+     */
+    readonly attribute unsigned long itemType;
+
+    /**
+     * The prefix of this namespace.  This should be the asciiSpec of the
+     * URI prefix.
+     */
+    readonly attribute ACString namespaceSpec;
+
+    /**
+     * Data associated with this namespace, such as a fallback.  URI data should
+     * use the asciiSpec of the URI.
+     */
+    readonly attribute ACString data;
+};
+
 /**
  * Application caches store resources for offline use.  Each
  * application cache has a unique client ID for use with
  * nsICacheService::openSession() to access the cache's entries.
  *
  * Each entry in the application cache can be marked with a set of
  * types, as discussed in the WHAT-WG offline applications
  * specification.
  *
  * All application caches with the same group ID belong to a cache
  * group.  Each group has one "active" cache that will service future
  * loads.  Inactive caches will be removed from the cache when they are
  * no longer referenced.
  */
-[scriptable, uuid(a9cfbeef-8f8d-49c3-b899-303390383ae9)]
+[scriptable, uuid(1e1a2371-875b-4e57-98ff-a81af1750ca2)]
 interface nsIApplicationCache : nsISupports
 {
     /**
      * Entries in an application cache can be marked as one or more of
      * the following types.
      */
 
     /* This item is the application manifest. */
@@ -135,9 +193,21 @@ interface nsIApplicationCache : nsISuppo
 
     /**
      * Returns any entries in the application cache whose type matches
      * one or more of the bits in typeBits.
      */
     void gatherEntries(in PRUint32 typeBits,
                        out unsigned long count,
                        [array, size_is(count)] out string keys);
+
+    /**
+     * Add a set of namespace entries to the application cache.
+     * @param namespaces
+     *        An nsIArray of nsIApplicationCacheNamespace entries.
+     */
+    void addNamespaces(in nsIArray namespaces);
+
+    /**
+     * Get the most specific namespace matching a given key.
+     */
+    nsIApplicationCacheNamespace getMatchingNamespace(in ACString key);
 };
--- 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(3f411c68-0d17-420d-839a-6355eea877b9)]
+[scriptable, uuid(23b37fbd-5c55-43a5-b592-abf31c05e967)]
 interface nsIApplicationCacheService : nsISupports
 {
     /**
      * Create a new, empty application cache for the given cache
      * group.
      */
     nsIApplicationCache createApplicationCache(in ACString group);
 
@@ -63,9 +63,23 @@ interface nsIApplicationCacheService : n
      * Get the currently active cache object for a cache group.
      */
     nsIApplicationCache getActiveCache(in ACString group);
 
     /**
      * Try to find the best application cache to serve a resource.
      */
     nsIApplicationCache chooseApplicationCache(in ACString key);
+
+    /**
+     * Flags the key as being opportunistically cached.
+     *
+     * This method should also propagate the entry to other
+     * application caches with the same opportunistic namespace, but
+     * this is not currently implemented.
+     *
+     * @param cache
+     *        The cache in which the entry is cached now.
+     * @param key
+     *        The cache entry key.
+     */
+    void cacheOpportunistically(in nsIApplicationCache cache, in ACString key);
 };
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -473,16 +473,28 @@
 #define NS_APPLICATIONCACHESERVICE_CID               \
 { /* 02bf7a2a-39d8-4a23-a50c-2cbb085ab7a5 */         \
     0x02bf7a2a,                                      \
     0x39d8,                                          \
     0x4a23,                                          \
     {0xa5, 0x0c, 0x2c, 0xbb, 0x08, 0x5a, 0xb7, 0xa5} \
 }
 
+#define NS_APPLICATIONCACHENAMESPACE_CLASSNAME \
+    "nsApplicationCacheNamespace"
+#define NS_APPLICATIONCACHENAMESPACE_CONTRACTID \
+    "@mozilla.org/network/application-cache-namespace;1"
+#define NS_APPLICATIONCACHENAMESPACE_CID             \
+{ /* b00ed78a-04e2-4f74-8e1c-d1af79dfd12f */         \
+    0xb00ed78a,                                      \
+    0x04e2,                                          \
+    0x4f74,                                          \
+   {0x8e, 0x1c, 0xd1, 0xaf, 0x79, 0xdf, 0xd1, 0x2f}  \
+}
+
 /******************************************************************************
  * netwerk/protocol/http/ classes
  */
 
 #define NS_HTTPPROTOCOLHANDLER_CID \
 { /* 4f47e42e-4d23-4dd3-bfda-eb29255e9ea3 */         \
     0x4f47e42e,                                      \
     0x4d23,                                          \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -186,16 +186,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeAbo
 #endif
 #include "nsAboutCache.h"
 #include "nsAboutCacheEntry.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAboutCacheEntry)
 #endif
 
 #ifdef NECKO_OFFLINE_CACHE
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsOfflineCacheDevice, nsOfflineCacheDevice::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsApplicationCacheNamespace)
 #endif
 
 #ifdef NECKO_PROTOCOL_file
 // file
 #include "nsFileProtocolHandler.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsFileProtocolHandler, Init)
 #endif
 
@@ -1051,16 +1052,21 @@ static const nsModuleComponentInfo gNetM
     },
 
 #ifdef NECKO_OFFLINE_CACHE
     {  NS_APPLICATIONCACHESERVICE_CLASSNAME,
        NS_APPLICATIONCACHESERVICE_CID,
        NS_APPLICATIONCACHESERVICE_CONTRACTID,
        nsOfflineCacheDeviceConstructor
     },
+    {  NS_APPLICATIONCACHENAMESPACE_CLASSNAME,
+       NS_APPLICATIONCACHENAMESPACE_CID,
+       NS_APPLICATIONCACHENAMESPACE_CONTRACTID,
+       nsApplicationCacheNamespaceConstructor
+    },
 #endif
 
 #ifdef NECKO_COOKIES
     { NS_COOKIEMANAGER_CLASSNAME,
       NS_COOKIEMANAGER_CID,
       NS_COOKIEMANAGER_CONTRACTID,
       nsCookieServiceConstructor
     },
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is IBM Corporation.
  * Portions created by IBM Corporation are Copyright (C) 2004
  * IBM Corporation. All Rights Reserved.
  *
  * Contributor(s):
  *   Darin Fisher <darin@meer.net>
  *   Dave Camp <dcamp@mozilla.com>
+ *   Honza Bambas <honzab@firemni.cz>
  *
  * 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
@@ -36,24 +37,27 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsCache.h"
 #include "nsDiskCache.h"
 #include "nsDiskCacheDeviceSQL.h"
 #include "nsCacheService.h"
 
+#include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsAutoPtr.h"
 #include "nsEscape.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "nsCRT.h"
+#include "nsArrayUtils.h"
+#include "nsIArray.h"
 #include "nsIVariant.h"
 
 #include "mozIStorageService.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageFunction.h"
 #include "mozStorageHelper.h"
 
 #include "nsICacheVisitor.h"
@@ -540,16 +544,54 @@ NS_IMETHODIMP
 nsOfflineCacheEntryInfo::GetDataSize(PRUint32 *aDataSize)
 {
   *aDataSize = mRec->dataSize;
   return NS_OK;
 }
 
 
 /******************************************************************************
+ * nsApplicationCacheNamespace
+ */
+
+NS_IMPL_ISUPPORTS1(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::Init(PRUint32 itemType,
+                                  const nsACString &namespaceSpec,
+                                  const nsACString &data)
+{
+  mItemType = itemType;
+  mNamespaceSpec = namespaceSpec;
+  mData = data;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetItemType(PRUint32 *out)
+{
+  *out = mItemType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out)
+{
+  out = mNamespaceSpec;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetData(nsACString &out)
+{
+  out = mData;
+  return NS_OK;
+}
+
+/******************************************************************************
  * nsApplicationCache
  */
 
 class nsApplicationCache : public nsIApplicationCache
                          , public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS
@@ -671,16 +713,55 @@ nsApplicationCache::GatherEntries(PRUint
                                   PRUint32 * count,
                                   char *** keys)
 {
   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
 
   return mDevice->GatherEntries(mClientID, typeBits, count, keys);
 }
 
+NS_IMETHODIMP
+nsApplicationCache::AddNamespaces(nsIArray *namespaces)
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+
+  if (!namespaces)
+    return NS_OK;
+
+  mozStorageTransaction transaction(mDevice->mDB, PR_FALSE);
+
+  PRUint32 length;
+  nsresult rv = namespaces->GetLength(&length);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  for (PRUint32 i = 0; i < length; i++) {
+    nsCOMPtr<nsIApplicationCacheNamespace> ns =
+      do_QueryElementAt(namespaces, i);
+    if (ns) {
+      rv = mDevice->AddNamespace(mClientID, ns);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  rv = transaction.Commit();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetMatchingNamespace(const nsACString &key,
+                                         nsIApplicationCacheNamespace **out)
+
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+
+  return mDevice->GetMatchingNamespace(mClientID, key, out);
+}
+
 /******************************************************************************
  * nsOfflineCacheDevice
  */
 
 NS_IMPL_ISUPPORTS1(nsOfflineCacheDevice, nsIApplicationCacheService)
 
 nsOfflineCacheDevice::nsOfflineCacheDevice()
   : mDB(nsnull)
@@ -929,30 +1010,64 @@ nsOfflineCacheDevice::Init()
   // moz_cache_groups use the GroupID, so use it as the primary key.
   rv = mDB->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
                          " GroupID TEXT PRIMARY KEY,\n"
                          " ActiveClientID TEXT\n"
                          ");\n"));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mDB->ExecuteSimpleSQL(
+    NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
+                       "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
+
+  // ClientID: clientID joining moz_cache and moz_cache_namespaces
+  // tables.
+  // Data: Data associated with this namespace (e.g. a fallback URI
+  // for fallback entries).
+  // ItemType: the type of namespace.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
+                         " moz_cache_namespaces (\n"
+                         " ClientID TEXT,\n"
+                         " NameSpace TEXT,\n"
+                         " Data TEXT,\n"
+                         " ItemType INTEGER\n"
+                          ");\n"));
+   NS_ENSURE_SUCCESS(rv, rv);
+
   // Databases from 1.9.0 have a moz_cache_index that should be dropped
   rv = mDB->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Key/ClientID pairs should be unique in the database.  All queries
   // against moz_cache use the Key (which is also the most unique), so
   // use it as the primary key for this index.
   rv = mDB->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
                          " moz_cache_key_clientid_index"
                          " ON moz_cache (Key, ClientID);"));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
+                         " moz_cache_namespaces_clientid_index"
+                         " ON moz_cache_namespaces (ClientID, NameSpace);"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Used for namespace lookups.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
+                         " moz_cache_namespaces_namespace_index"
+                         " ON moz_cache_namespaces (NameSpace);"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+
   mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
   if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
 
   rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 2, mEvictionFunction);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // create all (most) of our statements up front
   struct StatementSql {
@@ -964,28 +1079,43 @@ nsOfflineCacheDevice::Init()
     StatementSql ( mStatement_CacheSize,         "SELECT Sum(DataSize) from moz_cache;" ),
     // XXX bug 442810: Restore the ability to monitor individual cache usage
     StatementSql ( mStatement_DomainSize,        "SELECT 0;"),
     StatementSql ( mStatement_EntryCount,        "SELECT count(*) from moz_cache;" ),
     StatementSql ( mStatement_UpdateEntry,       "UPDATE moz_cache SET MetaData = ?, Flags = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_UpdateEntrySize,   "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_UpdateEntryFlags,  "UPDATE moz_cache SET Flags = ? WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_DeleteEntry,       "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
-    StatementSql ( mStatement_FindEntry,         "SELECT MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
+    StatementSql ( mStatement_FindEntry,         "SELECT MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType 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_MarkEntry,         "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_UnmarkEntry,       "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
     StatementSql ( mStatement_GetTypes,          "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
     StatementSql ( mStatement_CleanupUnmarked,   "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
     StatementSql ( mStatement_GatherEntries,     "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
 
-    StatementSql ( mStatement_ActivateClient,    "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID) VALUES (?, ?);" ),
+    StatementSql ( mStatement_ActivateClient,    "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
     StatementSql ( mStatement_DeactivateGroup,   "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
-    StatementSql ( mStatement_FindClient,        "SELECT ClientID FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;")
+    StatementSql ( mStatement_FindClient,        "SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
+
+    // Search for namespaces that match the URI.  Use the <= operator
+    // to ensure that we use the index on moz_cache_namespaces.
+    StatementSql ( mStatement_FindClientByNamespace, "SELECT ns.ClientID, ns.ItemType FROM"
+                                                     "  moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
+                                                     "  ON ns.ClientID = groups.ActiveClientID"
+                                                     " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
+                                                     " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
+    StatementSql ( mStatement_FindNamespaceEntry,    "SELECT ns.ClientID, ns.Data, ns.ItemType FROM "
+                                                     "  moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
+                                                     "  ON ns.ClientID = groups.ActiveClientID"
+                                                     " WHERE ClientID = ?1"
+                                                     " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
+                                                     " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
+    StatementSql ( mStatement_InsertNamespaceEntry,  "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
   };
   for (PRUint32 i = 0; NS_SUCCEEDED(rv) && i < NS_ARRAY_LENGTH(prepared); ++i)
   {
     LOG(("Creating statement: %s\n", prepared[i].sql));
 
     rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
                               getter_AddRefs(prepared[i].statement));
     NS_ENSURE_SUCCESS(rv, rv);
@@ -1063,23 +1193,39 @@ nsOfflineCacheDevice::Shutdown()
   NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
 
   if (mCaches.IsInitialized())
     mCaches.EnumerateRead(ShutdownApplicationCache, this);
 
   EvictionObserver evictionObserver(mDB, mEvictionFunction);
 
   // Delete all rows whose clientID is not an active clientID.
-  nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE rowid IN (SELECT moz_cache.rowid FROM moz_cache LEFT OUTER JOIN moz_cache_groups ON (moz_cache.ClientID = moz_cache_groups.ActiveClientID) WHERE moz_cache_groups.GroupID ISNULL)"));
+  nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_cache WHERE rowid IN"
+    "  (SELECT moz_cache.rowid FROM"
+    "    moz_cache LEFT OUTER JOIN moz_cache_groups ON"
+    "      (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
+    "   WHERE moz_cache_groups.GroupID ISNULL)"));
 
   if (NS_FAILED(rv))
     NS_WARNING("Failed to clean up unused application caches.");
   else
     evictionObserver.Apply();
 
+  // Delete all namespaces whose clientID is not an active clientID.
+  rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_cache_namespaces WHERE rowid IN"
+    "  (SELECT moz_cache_namespaces.rowid FROM"
+    "    moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
+    "      (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)"
+    "   WHERE moz_cache_groups.GroupID ISNULL)"));
+
+  if (NS_FAILED(rv))
+    NS_WARNING("Failed to clean up namespaces.");
+
   mDB = 0;
   mEvictionFunction = 0;
 
   return NS_OK;
 }
 
 const char *
 nsOfflineCacheDevice::GetDeviceID()
@@ -1513,16 +1659,37 @@ nsOfflineCacheDevice::EvictEntries(const
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   evictionObserver.Apply();
 
+  statement = nsnull;
+  // Also evict any namespaces associated with this clientID.
+  if (clientID)
+  {
+    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
+                              getter_AddRefs(statement));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = statement->BindUTF8StringParameter(0, nsDependentCString(clientID));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else
+  {
+    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
+                              getter_AddRefs(statement));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = statement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult
 nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
                                 const nsACString &key,
                                 PRUint32 typeBits)
 {
@@ -1576,16 +1743,89 @@ nsOfflineCacheDevice::UnmarkEntry(const 
   NS_ENSURE_SUCCESS(rv, rv);
 
   evictionObserver.Apply();
 
   return NS_OK;
 }
 
 nsresult
+nsOfflineCacheDevice::GetMatchingNamespace(const nsCString &clientID,
+                                           const nsACString &key,
+                                           nsIApplicationCacheNamespace **out)
+{
+  LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
+       clientID.get(), PromiseFlatCString(key).get()));
+
+  nsresult rv;
+
+  AutoResetStatement statement(mStatement_FindNamespaceEntry);
+
+  rv = statement->BindUTF8StringParameter(0, clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindUTF8StringParameter(1, key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool hasRows;
+  rv = statement->ExecuteStep(&hasRows);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *out = nsnull;
+
+  while (hasRows)
+  {
+    nsCString namespaceSpec;
+    rv = statement->GetUTF8String(0, namespaceSpec);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCString data;
+    rv = statement->GetUTF8String(1, data);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRInt32 itemType;
+    rv = statement->GetInt32(2, &itemType);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // XXX: There is currently a proposal on the table to extend matching
+    // semantics for bypass entries to be a namespace (possibly with extra
+    // filter data).  When/if that happens, this logic will need to change.
+    if ((itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS) &&
+        (namespaceSpec != key)) {
+      rv = statement->ExecuteStep(&hasRows);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      continue;
+    }
+
+    nsCOMPtr<nsIApplicationCacheNamespace> ns =
+      new nsApplicationCacheNamespace();
+    if (!ns)
+      return NS_ERROR_OUT_OF_MEMORY;
+
+    rv = ns->Init(itemType, namespaceSpec, data);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    ns.swap(*out);
+    return NS_OK;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::CacheOpportunistically(const nsCString &clientID,
+                                             const nsACString &key)
+{
+  // XXX: We should also be propagating this cache entry to other matching
+  // caches.  See bug 444807.
+
+  return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
+}
+
+nsresult
 nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
                                const nsACString &key,
                                PRUint32 *typeBits)
 {
   LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
        clientID.get(), PromiseFlatCString(key).get()));
 
   AutoResetStatement statement(mStatement_GetTypes);
@@ -1621,16 +1861,56 @@ nsOfflineCacheDevice::GatherEntries(cons
 
   rv = statement->BindInt32Parameter(1, typeBits);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys);
 }
 
 nsresult
+nsOfflineCacheDevice::AddNamespace(const nsCString &clientID,
+                                   nsIApplicationCacheNamespace *ns)
+{
+  nsCString namespaceSpec;
+  nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString data;
+  rv = ns->GetData(data);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRUint32 itemType;
+  rv = ns->GetItemType(&itemType);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, type=%d]",
+       PromiseFlatCString(clientID).get(),
+       namespaceSpec.get(), data.get(), itemType));
+
+  AutoResetStatement statement(mStatement_InsertNamespaceEntry);
+
+  rv = statement->BindUTF8StringParameter(0, clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->BindUTF8StringParameter(1, namespaceSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->BindUTF8StringParameter(2, data);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->BindInt32Parameter(3, itemType);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->Execute();
+  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);
@@ -1744,82 +2024,142 @@ nsOfflineCacheDevice::GetActiveCache(con
 
   nsCString *clientID;
   if (mActiveCachesByGroup.Get(group, &clientID))
     return GetApplicationCache(*clientID, out);
 
   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);
+
+    nsCOMPtr<nsIURI> groupURI;
+    rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
+    if (NS_SUCCEEDED(rv)) {
+      // When we are choosing an initial cache to load the top
+      // level document from, the URL of that document must have
+      // the same origin as the manifest, according to the spec.
+      // The following check is here because explicit, fallback
+      // and dynamic entries might have origin different from the
+      // manifest origin.
+      if (NS_SecurityCompareURIs(keyURI, groupURI,
+                                 GetStrictFileOriginPolicy()))
+        return PR_TRUE;
+    }
+  }
+
+  return PR_FALSE;
+}
+
+
 NS_IMETHODIMP
 nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
                                              nsIApplicationCache **out)
 {
   *out = nsnull;
 
+  nsCOMPtr<nsIURI> keyURI;
+  nsresult rv = NS_NewURI(getter_AddRefs(keyURI), key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // First try to find a matching cache entry.
   AutoResetStatement statement(mStatement_FindClient);
-  nsresult rv = statement->BindUTF8StringParameter(
-                                           0, key);
+  rv = statement->BindUTF8StringParameter(0, key);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool hasRows;
   rv = statement->ExecuteStep(&hasRows);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIURI> keyURI;
-  rv = NS_NewURI(getter_AddRefs(keyURI), key);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   while (hasRows) {
     PRInt32 itemType;
     rv = statement->GetInt32(1, &itemType);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
       nsCAutoString clientID;
       rv = statement->GetUTF8String(0, clientID);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (mActiveCaches.Contains(clientID)) {
-        nsCAutoString groupID;
-        rv = GetGroupForCache(clientID, groupID);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        nsCOMPtr<nsIURI> groupURI;
-        rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
-        if (NS_SUCCEEDED(rv)) {
-          // When we are choosing an initial cache to load the top
-          // level document from, the URL of that document must have
-          // the same origin as the manifest, according to the spec.
-          // The following check is here because explicit, fallback
-          // and dynamic entries might have origin different from the
-          // manifest origin. XXX: dynamic shouldn't?
-          if (NS_SecurityCompareURIs(keyURI, groupURI,
-                                     GetStrictFileOriginPolicy()))
-            return GetApplicationCache(clientID, out);
-        }
+      if (CanUseCache(keyURI, clientID)) {
+        return GetApplicationCache(clientID, out);
       }
     }
 
     rv = statement->ExecuteStep(&hasRows);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  // OK, we didn't find an exact match.  Search for a client with a
+  // matching namespace.
+
+  AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
+
+  rv = nsstatement->BindUTF8StringParameter(0, key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = nsstatement->ExecuteStep(&hasRows);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  while (hasRows)
+  {
+    PRInt32 itemType;
+    rv = nsstatement->GetInt32(1, &itemType);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Don't associate with a cache based solely on a whitelist entry
+    if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
+      nsCAutoString clientID;
+      rv = nsstatement->GetUTF8String(0, clientID);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (CanUseCache(keyURI, clientID)) {
+        return GetApplicationCache(clientID, out);
+      }
+    }
+
+    rv = nsstatement->ExecuteStep(&hasRows);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache,
+                                             const nsACString &key)
+{
+  NS_ENSURE_ARG_POINTER(cache);
+
+  nsresult rv;
+
+  nsCAutoString clientID;
+  rv = cache->GetClientID(clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return CacheOpportunistically(clientID, key);
+}
+
 nsresult
 nsOfflineCacheDevice::ActivateCache(const nsCSubstring &group,
                                     const nsCSubstring &clientID)
 {
   AutoResetStatement statement(mStatement_ActivateClient);
   nsresult rv = statement->BindUTF8StringParameter(0, group);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = statement->BindUTF8StringParameter(1, clientID);
   NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindInt32Parameter(2, SecondsFromPRTime(PR_Now()));
+  NS_ENSURE_SUCCESS(rv, rv);
 
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCString *active;
   if (mActiveCachesByGroup.Get(group, &active))
   {
     mActiveCaches.Remove(*active);
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.h
@@ -50,18 +50,33 @@
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsVoidArray.h"
 #include "nsInterfaceHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsHashSets.h"
 #include "nsWeakReference.h"
 
+class nsIURI;
 class nsOfflineCacheDevice;
 
+class nsApplicationCacheNamespace : public nsIApplicationCacheNamespace
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIAPPLICATIONCACHENAMESPACE
+
+  nsApplicationCacheNamespace() : mItemType(0) {}
+
+private:
+  PRUint32 mItemType;
+  nsCString mNamespaceSpec;
+  nsCString mData;
+};
+
 class nsOfflineCacheEvictionFunction : public mozIStorageFunction {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGEFUNCTION
 
   nsOfflineCacheEvictionFunction(nsOfflineCacheDevice *device)
     : mDevice(device)
   {}
@@ -191,29 +206,40 @@ private:
   nsresult InitActiveCaches();
   nsresult UpdateEntry(nsCacheEntry *entry);
   nsresult UpdateEntrySize(nsCacheEntry *entry, PRUint32 newSize);
   nsresult DeleteEntry(nsCacheEntry *entry, PRBool deleteData);
   nsresult DeleteData(nsCacheEntry *entry);
   nsresult EnableEvictionObserver();
   nsresult DisableEvictionObserver();
 
+  PRBool CanUseCache(nsIURI *keyURI, const nsCString &clientID);
+
   nsresult MarkEntry(const nsCString &clientID,
                      const nsACString &key,
                      PRUint32 typeBits);
   nsresult UnmarkEntry(const nsCString &clientID,
                        const nsACString &key,
                        PRUint32 typeBits);
+
+  nsresult CacheOpportunistically(const nsCString &clientID,
+                                  const nsACString &key);
   nsresult GetTypes(const nsCString &clientID,
                     const nsACString &key,
                     PRUint32 *typeBits);
+
+  nsresult GetMatchingNamespace(const nsCString &clientID,
+                                const nsACString &key,
+                                nsIApplicationCacheNamespace **out);
   nsresult GatherEntries(const nsCString &clientID,
                          PRUint32 typeBits,
                          PRUint32 *count,
                          char *** values);
+  nsresult AddNamespace(const nsCString &clientID,
+                        nsIApplicationCacheNamespace *ns);
 
   nsresult RunSimpleQuery(mozIStorageStatement *statment,
                           PRUint32 resultIndex,
                           PRUint32 * count,
                           char *** values);
 
   nsCOMPtr<mozIStorageConnection>          mDB;
   nsRefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
@@ -226,21 +252,24 @@ private:
   nsCOMPtr<mozIStorageStatement>  mStatement_UpdateEntryFlags;
   nsCOMPtr<mozIStorageStatement>  mStatement_DeleteEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_FindEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_BindEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_ClearDomain;
   nsCOMPtr<mozIStorageStatement>  mStatement_MarkEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_UnmarkEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_GetTypes;
+  nsCOMPtr<mozIStorageStatement>  mStatement_FindNamespaceEntry;
+  nsCOMPtr<mozIStorageStatement>  mStatement_InsertNamespaceEntry;
   nsCOMPtr<mozIStorageStatement>  mStatement_CleanupUnmarked;
   nsCOMPtr<mozIStorageStatement>  mStatement_GatherEntries;
   nsCOMPtr<mozIStorageStatement>  mStatement_ActivateClient;
   nsCOMPtr<mozIStorageStatement>  mStatement_DeactivateGroup;
   nsCOMPtr<mozIStorageStatement>  mStatement_FindClient;
+  nsCOMPtr<mozIStorageStatement>  mStatement_FindClientByNamespace;
 
   nsCOMPtr<nsILocalFile>          mCacheDirectory;
   PRUint32                        mCacheCapacity;
   PRInt32                         mDeltaCounter;
 
   nsInterfaceHashtable<nsCStringHashKey, nsIWeakReference> mCaches;
   nsClassHashtable<nsCStringHashKey, nsCString> mActiveCachesByGroup;
   nsCStringHashSet mActiveCaches;
--- a/netwerk/protocol/http/public/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/public/nsIHttpChannelInternal.idl
@@ -40,17 +40,17 @@
 interface nsIURI;
 interface nsIProxyInfo;
 
 /** 
  * Dumping ground for http.  This interface will never be frozen.  If you are 
  * using any feature exposed by this interface, be aware that this interface 
  * will change and you will be broken.  You have been warned.
  */
-[scriptable, uuid(602e8cf0-c387-4598-9e6c-aa4b1551ed1c)]
+[scriptable, uuid(3ce040fb-3933-462a-8d62-80b78fbd0809)]
 interface nsIHttpChannelInternal : nsISupports
 {
     /**
      * An http channel can own a reference to the document URI
      */
     attribute nsIURI documentURI;
 
     /**
@@ -67,9 +67,15 @@ interface nsIHttpChannelInternal : nsISu
      * Helper method to set a cookie with a consumer-provided
      * cookie header, _but_ using the channel's other information
      * (URI's, prompters, date headers etc).
      *
      * @param aCookieHeader
      *        The cookie header to be parsed.
      */
     void setCookie(in string aCookieHeader);
+
+    /**
+     * Setup this channel as an application cache fallback channel.
+     */
+    void setupFallbackChannel(in string aFallbackKey);
+
 };
--- a/netwerk/protocol/http/src/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/src/nsHttpChannel.cpp
@@ -21,16 +21,18 @@
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Darin Fisher <darin@meer.net> (original author)
  *   Christian Biesinger <cbiesinger@web.de>
  *   Google Inc.
  *   Jan Wrobel <wrobel@blues.ath.cx>
  *   Jan Odvarko <odvarko@gmail.com>
+ *   Dave Camp <dcamp@mozilla.com>
+ *   Honza Bambas <honzab@firemni.cz>
  *
  * 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
@@ -120,16 +122,18 @@ nsHttpChannel::nsHttpChannel()
     , mCanceled(PR_FALSE)
     , mTransactionReplaced(PR_FALSE)
     , mUploadStreamHasHeaders(PR_FALSE)
     , mAuthRetryPending(PR_FALSE)
     , mSuppressDefensiveAuth(PR_FALSE)
     , mResuming(PR_FALSE)
     , mInitedCacheEntry(PR_FALSE)
     , mCacheForOfflineUse(PR_FALSE)
+    , mCachingOpportunistically(PR_FALSE)
+    , mFallbackChannel(PR_FALSE)
     , mTracingEnabled(PR_TRUE)
 {
     LOG(("Creating nsHttpChannel @%x\n", this));
 
     // grab a reference to the handler to ensure that it doesn't go away.
     nsHttpHandler *handler = gHttpHandler;
     NS_ADDREF(handler);
 }
@@ -287,18 +291,23 @@ nsHttpChannel::Connect(PRBool firstTime)
 
         // open a cache entry for this channel...
         rv = OpenCacheEntry(offline, &delayed);
 
         if (NS_FAILED(rv)) {
             LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
             // if this channel is only allowed to pull from the cache, then
             // we must fail if we were unable to open a cache entry.
-            if (mLoadFlags & LOAD_ONLY_FROM_CACHE)
-                return NS_ERROR_DOCUMENT_NOT_CACHED;
+            if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+                // If we have a fallback URI (and we're not already
+                // falling back), process the fallback asynchronously.
+                if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
+                    return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+                }
+            }
             // otherwise, let's just proceed without using the cache.
         }
 
         // if cacheForOfflineUse has been set, open up an offline cache
         // entry to update
         if (mCacheForOfflineUse) {
             rv = OpenOfflineCacheEntryForWriting();
             if (NS_FAILED(rv)) return rv;
@@ -457,16 +466,52 @@ nsHttpChannel::HandleAsyncNotModified()
     CloseCacheEntry(PR_TRUE);
 
     mIsPending = PR_FALSE;
 
     if (mLoadGroup)
         mLoadGroup->RemoveRequest(this, nsnull, mStatus);
 }
 
+void
+nsHttpChannel::HandleAsyncFallback()
+{
+    NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?");
+
+    if (mSuspendCount) {
+        LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
+        mPendingAsyncCallOnResume = &nsHttpChannel::HandleAsyncFallback;
+        return;
+    }
+
+    nsresult rv = NS_OK;
+
+    LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
+
+    // since this event is handled asynchronously, it is possible that this
+    // channel could have been canceled, in which case there would be no point
+    // in processing the fallback.
+    if (!mCanceled) {
+        PRBool fallingBack;
+        rv = ProcessFallback(&fallingBack);
+        if (NS_FAILED(rv) || !fallingBack) {
+            // If ProcessFallback fails, then we have to send out the
+            // OnStart/OnStop notifications.
+            LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, fallingBack));
+            mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
+            DoNotifyListener();
+        }
+    }
+
+    mIsPending = PR_FALSE;
+
+    if (mLoadGroup)
+        mLoadGroup->RemoveRequest(this, nsnull, mStatus);
+}
+
 nsresult
 nsHttpChannel::SetupTransaction()
 {
     LOG(("nsHttpChannel::SetupTransaction [this=%x]\n", this));
 
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
 
     nsresult rv;
@@ -875,16 +920,33 @@ nsHttpChannel::ProcessResponse()
 
 nsresult
 nsHttpChannel::ProcessNormal()
 {
     nsresult rv;
 
     LOG(("nsHttpChannel::ProcessNormal [this=%x]\n", this));
 
+    PRBool succeeded;
+    rv = GetRequestSucceeded(&succeeded);
+    if (NS_SUCCEEDED(rv) && !succeeded) {
+        PRBool fallingBack;
+        rv = ProcessFallback(&fallingBack);
+        if (NS_FAILED(rv)) {
+            DoNotifyListener();
+            return rv;
+        }
+
+        if (fallingBack) {
+            // Do not continue with normal processing, fallback is in
+            // progress now.
+            return NS_OK;
+        }
+    }
+
     // if we're here, then any byte-range requests failed to result in a partial
     // response.  we must clear this flag to prevent BufferPartialContent from
     // being called inside our OnDataAvailable (see bug 136678).
     mCachedContentIsPartial = PR_FALSE;
 
     ClearBogusContentEncodingIfNeeded();
 
     // this must be called before firing OnStartRequest, since http clients,
@@ -1308,16 +1370,110 @@ nsHttpChannel::ProcessNotModified()
     rv = ReadFromCache();
     if (NS_FAILED(rv)) return rv;
 
     mTransactionReplaced = PR_TRUE;
     return NS_OK;
 }
 
 nsresult
+nsHttpChannel::ProcessFallback(PRBool *fallingBack)
+{
+    LOG(("nsHttpChannel::ProcessFallback [this=%x]\n", this));
+    nsresult rv;
+
+    *fallingBack = PR_FALSE;
+
+    // At this point a load has failed (either due to network problems
+    // or an error returned on the server).  Perform an application
+    // cache fallback if we have a URI to fall back to.
+    if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
+        LOG(("  choosing not to fallback [%p,%s,%d]",
+             mApplicationCache.get(), mFallbackKey.get(), mFallbackChannel));
+        return NS_OK;
+    }
+
+    // Make sure the fallback entry hasn't been marked as a foreign
+    // entry.
+    PRUint32 fallbackEntryType;
+    rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
+        // This cache points to a fallback that refers to a different
+        // manifest.  Refuse to fall back.
+        return NS_OK;
+    }
+
+    NS_ASSERTION(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
+                 "Fallback entry not marked correctly!");
+
+    // Kill any opportunistic cache entry, and disable opportunistic
+    // caching for the fallback.
+    if (mOfflineCacheEntry) {
+        mOfflineCacheEntry->Doom();
+        mOfflineCacheEntry = 0;
+        mOfflineCacheAccess = 0;
+    }
+
+    mCacheForOfflineUse = PR_FALSE;
+    mCachingOpportunistically = PR_FALSE;
+    mOfflineCacheClientID.Truncate();
+    mOfflineCacheEntry = 0;
+    mOfflineCacheAccess = 0;
+
+    // Close the current cache entry.
+    if (mCacheEntry)
+        CloseCacheEntry(PR_TRUE);
+
+    // Create a new channel to load the fallback entry.
+    nsRefPtr<nsIChannel> newChannel;
+    rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Make sure the new channel loads from the fallback key.
+    nsCOMPtr<nsIHttpChannelInternal> httpInternal =
+        do_QueryInterface(newChannel, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // ... and fallbacks should only load from the cache.
+    PRUint32 newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
+    rv = newChannel->SetLoadFlags(newLoadFlags);
+
+    // Inform consumers about this fake redirect
+    PRUint32 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
+    rv = gHttpHandler->OnChannelRedirect(this, newChannel, redirectFlags);
+    if (NS_FAILED(rv))
+        return rv;
+
+    rv = newChannel->AsyncOpen(mListener, mListenerContext);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // close down this channel
+    Cancel(NS_BINDING_REDIRECTED);
+
+    // disconnect from our listener
+    mListener = 0;
+    mListenerContext = 0;
+    // and from our callbacks
+    mCallbacks = nsnull;
+    mProgressSink = nsnull;
+
+    *fallingBack = PR_TRUE;
+
+    return NS_OK;
+}
+
+nsresult
 nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed)
 {
     nsresult rv;
 
     *delayed = PR_FALSE;
 
     LOG(("nsHttpChannel::OpenCacheEntry [this=%x]", this));
 
@@ -1397,16 +1553,20 @@ nsHttpChannel::OpenCacheEntry(PRBool off
                     (cacheKey, getter_AddRefs(mApplicationCache));
                 NS_ENSURE_SUCCESS(rv, rv);
             }
         }
     }
 
     nsCOMPtr<nsICacheSession> session;
 
+    // Will be set to true if we've found the right session, but need
+    // to open the cache entry asynchronously.
+    PRBool waitingForValidation = PR_FALSE;
+
     // If we have an application cache, we check it first.
     if (mApplicationCache) {
         nsCAutoString appCacheClientID;
         mApplicationCache->GetClientID(appCacheClientID);
 
         nsCOMPtr<nsICacheService> serv =
             do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
         NS_ENSURE_SUCCESS(rv, rv);
@@ -1416,40 +1576,92 @@ nsHttpChannel::OpenCacheEntry(PRBool off
                                  nsICache::STREAM_BASED,
                                  getter_AddRefs(session));
         NS_ENSURE_SUCCESS(rv, rv);
 
         // we'll try to synchronously open the cache entry... however,
         // it may be in use and not yet validated, in which case we'll
         // try asynchronously opening the cache entry.
         //
-        // we need open in ACCESS_READ only because we don't want to
-        // overwrite the offline cache entry non-atomically so
-        // ACCESS_READ will prevent us from writing to the
-        // HTTP-offline cache as a normal cache entry.  XXX: this
-        // needs to be checked.
+        // We open with ACCESS_READ only, because we don't want to
+        // overwrite the offline cache entry non-atomically.
+        // ACCESS_READ will prevent us from writing to the offline
+        // cache as a normal cache entry.
         rv = session->OpenCacheEntry(cacheKey,
                                      nsICache::ACCESS_READ, PR_FALSE,
                                      getter_AddRefs(mCacheEntry));
+        if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
+            accessRequested = nsICache::ACCESS_READ;
+            waitingForValidation = PR_TRUE;
+            rv = NS_OK;
+        }
+
+        if (NS_FAILED(rv) && !mCacheForOfflineUse && !mFallbackChannel) {
+            // Check for namespace match.
+            nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
+            rv = mApplicationCache->GetMatchingNamespace
+                (cacheKey, getter_AddRefs(namespaceEntry));
+            NS_ENSURE_SUCCESS(rv, rv);
+
+            PRUint32 namespaceType = 0;
+            if (!namespaceEntry ||
+                NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
+                (namespaceType &
+                 (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
+                  nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC |
+                  nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) {
+                // When loading from an application cache, only items
+                // on the whitelist or matching a
+                // fallback/opportunistic namespace should hit the
+                // network...
+                mLoadFlags |= LOAD_ONLY_FROM_CACHE;
+
+                // ... and if there were an application cache entry,
+                // we would have found it earlier.
+                return NS_ERROR_CACHE_KEY_NOT_FOUND;
+            }
+
+            if (namespaceType &
+                nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
+                rv = namespaceEntry->GetData(mFallbackKey);
+                NS_ENSURE_SUCCESS(rv, rv);
+            }
+
+            if ((namespaceType &
+                 nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC) &&
+                mLoadFlags & LOAD_DOCUMENT_URI) {
+                // Document loads for items in an opportunistic namespace
+                // should be placed in the offline cache.
+                nsCString clientID;
+                mApplicationCache->GetClientID(clientID);
+
+                mCacheForOfflineUse = !clientID.IsEmpty();
+                SetOfflineCacheClientID(clientID);
+                mCachingOpportunistically = PR_TRUE;
+            }
+        }
     }
 
-    if (!mApplicationCache ||
-        (NS_FAILED(rv) && rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION)) 
-    {
+    if (!mCacheEntry && !waitingForValidation) {
         rv = gHttpHandler->GetCacheSession(storagePolicy,
                                            getter_AddRefs(session));
         if (NS_FAILED(rv)) return rv;
 
         rv = session->OpenCacheEntry(cacheKey, accessRequested, PR_FALSE,
                                      getter_AddRefs(mCacheEntry));
+        if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
+            waitingForValidation = PR_TRUE;
+            rv = NS_OK;
+        }
+        if (NS_FAILED(rv)) return rv;
     }
 
-    if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
-        // access to the cache entry has been denied (because the cache entry
-        // is probably in use by another channel).
+    if (waitingForValidation) {
+        // access to the cache entry has been denied (because the
+        // cache entry is probably in use by another channel).
         if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
             LOG(("bypassing local cache since it is busy\n"));
             return NS_ERROR_NOT_AVAILABLE;
         }
         rv = session->AsyncOpenCacheEntry(cacheKey, accessRequested, this);
         if (NS_FAILED(rv)) return rv;
         // we'll have to wait for the cache entry
         *delayed = PR_TRUE;
@@ -1536,17 +1748,17 @@ nsHttpChannel::GenerateCacheKey(nsACStri
     if (mPostID) {
         char buf[32];
         PR_snprintf(buf, sizeof(buf), "id=%x&uri=", mPostID);
         cacheKey.Assign(buf);
     } else
         cacheKey.Truncate();
     
     // Strip any trailing #ref from the URL before using it as the key
-    const char *spec = mSpec.get();
+    const char *spec = mFallbackChannel ? mFallbackKey.get() : mSpec.get();
     const char *p = strchr(spec, '#');
     if (p)
         cacheKey.Append(spec, p - spec);
     else
         cacheKey.Append(spec);
     return NS_OK;
 }
 
@@ -1662,19 +1874,21 @@ nsHttpChannel::CheckCache()
     rv = mCachedResponseHead->Parse((char *) buf.get());
     NS_ENSURE_SUCCESS(rv, rv);
     buf.Adopt(0);
 
     // Don't bother to validate LOAD_ONLY_FROM_CACHE items.
     // Don't bother to validate items that are read-only,
     // unless they are read-only because of INHIBIT_CACHING or because
     // we're updating the offline cache.
+    // Don't bother to validate if this is a fallback entry.
     if (mLoadFlags & LOAD_ONLY_FROM_CACHE ||
         (mCacheAccess == nsICache::ACCESS_READ &&
-         !((mLoadFlags & INHIBIT_CACHING) || mCacheForOfflineUse))) {
+         !((mLoadFlags & INHIBIT_CACHING) || mCacheForOfflineUse)) ||
+        mFallbackChannel) {
         mCachedContentIsValid = PR_TRUE;
         return NS_OK;
     }
 
     PRUint16 isCachedRedirect = mCachedResponseHead->Status()/100 == 3;
 
     if (method != nsHttp::Head && !isCachedRedirect) {
         // If the cached content-length is set and it does not match the data
@@ -2016,19 +2230,35 @@ nsHttpChannel::CloseOfflineCacheEntry()
     if (!mOfflineCacheEntry)
         return;
 
     LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%x]", this));
 
     if (NS_FAILED(mStatus)) {
         mOfflineCacheEntry->Doom();
     }
+    else {
+        PRBool succeeded;
+        if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
+            mOfflineCacheEntry->Doom();
+    }
 
     mOfflineCacheEntry = 0;
     mOfflineCacheAccess = 0;
+
+    if (mCachingOpportunistically) {
+        nsCOMPtr<nsIApplicationCacheService> appCacheService =
+            do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
+        if (appCacheService) {
+            nsCAutoString cacheKey;
+            GenerateCacheKey(cacheKey);
+            appCacheService->CacheOpportunistically(mApplicationCache,
+                                                    cacheKey);
+        }
+    }
 }
 
 
 // Initialize the cache entry for writing.
 //  - finalize storage policy
 //  - store security info
 //  - update expiration time
 //  - store headers and other meta data
@@ -2373,16 +2603,25 @@ nsHttpChannel::SetupReplacementChannel(n
         nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
         if (!resumableChannel) {
             NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
             return NS_ERROR_NOT_RESUMABLE;
         }
         resumableChannel->ResumeAt(mStartPos, mEntityID);
     }
 
+    // transfer application cache information
+    if (mApplicationCache) {
+        nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer =
+            do_QueryInterface(newChannel);
+        if (appCacheContainer) {
+            appCacheContainer->SetApplicationCache(mApplicationCache);
+        }
+    }
+
     // transfer any properties
     nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
     if (bag)
         mPropertyHash.EnumerateRead(CopyProperties, bag.get());
 
     return NS_OK;
 }
 
@@ -4273,16 +4512,27 @@ nsHttpChannel::SetCookie(const char *aCo
     return cs->SetCookieStringFromHttp(mURI,
                                        mDocumentURI ? mDocumentURI : mOriginalURI,
                                        prompt,
                                        aCookieHeader,
                                        mResponseHead->PeekHeader(nsHttp::Date),
                                        this);
 }
 
+NS_IMETHODIMP
+nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey)
+{
+    LOG(("nsHttpChannel::SetupFallbackChannel [this=%x, key=%s]",
+         this, aFallbackKey));
+    mFallbackChannel = PR_TRUE;
+    mFallbackKey = aFallbackKey;
+
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsISupportsPriority
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::GetPriority(PRInt32 *value)
 {
     *value = mPriority;
@@ -4399,16 +4649,25 @@ nsHttpChannel::OnStartRequest(nsIRequest
     if (mConnectionInfo->ProxyInfo() &&
            (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
             mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
             mStatus == NS_ERROR_NET_TIMEOUT)) {
         if (NS_SUCCEEDED(ProxyFailover()))
             return NS_OK;
     }
 
+    // on other request errors, try to fall back
+    PRBool fallingBack;
+    if (NS_FAILED(mStatus) &&
+        NS_SUCCEEDED(ProcessFallback(&fallingBack)) &&
+        fallingBack) {
+
+        return NS_OK;
+    }
+
     return CallOnStartRequest();
 }
 
 NS_IMETHODIMP
 nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
 {
     LOG(("nsHttpChannel::OnStopRequest [this=%x request=%x status=%x]\n",
         this, request, status));
--- a/netwerk/protocol/http/src/nsHttpChannel.h
+++ b/netwerk/protocol/http/src/nsHttpChannel.h
@@ -164,21 +164,23 @@ private:
     void     AddCookiesToRequest();
     nsresult ApplyContentConversions();
     nsresult CallOnStartRequest();
     nsresult ProcessResponse();
     nsresult ProcessNormal();
     nsresult ProcessNotModified();
     nsresult ProcessRedirection(PRUint32 httpStatus);
     nsresult ProcessAuthentication(PRUint32 httpStatus);
+    nsresult ProcessFallback(PRBool *fallingBack);
     PRBool   ResponseWouldVary();
 
     // redirection specific methods
     void     HandleAsyncRedirect();
     void     HandleAsyncNotModified();
+    void     HandleAsyncFallback();
     nsresult PromptTempRedirect();
     nsresult SetupReplacementChannel(nsIURI *, nsIChannel *, PRBool preserveMethod);
 
     // proxy specific methods
     nsresult ProxyFailover();
     nsresult DoReplaceWithProxy(nsIProxyInfo *);
     void HandleAsyncReplaceWithProxy();
     nsresult ResolveProxy();
@@ -295,32 +297,43 @@ private:
 
     // Suspend counter.  This is used if someone tries to suspend/resume us
     // before we have either a cache pump or a transaction pump.
     PRUint32                          mSuspendCount;
 
     // redirection specific data.
     PRUint8                           mRedirectionLimit;
 
+    // If the channel is associated with a cache, and the URI matched
+    // a fallback namespace, this will hold the key for the fallback
+    // cache entry.
+    nsCString                         mFallbackKey;
+
     // state flags
     PRUint32                          mIsPending                : 1;
     PRUint32                          mWasOpened                : 1;
     PRUint32                          mApplyConversion          : 1;
     PRUint32                          mAllowPipelining          : 1;
     PRUint32                          mCachedContentIsValid     : 1;
     PRUint32                          mCachedContentIsPartial   : 1;
     PRUint32                          mResponseHeadersModified  : 1;
     PRUint32                          mCanceled                 : 1;
     PRUint32                          mTransactionReplaced      : 1;
     PRUint32                          mUploadStreamHasHeaders   : 1;
     PRUint32                          mAuthRetryPending         : 1;
     PRUint32                          mSuppressDefensiveAuth    : 1;
     PRUint32                          mResuming                 : 1;
     PRUint32                          mInitedCacheEntry         : 1;
     PRUint32                          mCacheForOfflineUse       : 1;
+    // True if mCacheForOfflineUse was set because we were caching
+    // opportunistically.
+    PRUint32                          mCachingOpportunistically : 1;
+    // True if we are loading a fallback cache entry from the
+    // application cache.
+    PRUint32                          mFallbackChannel          : 1;
     PRUint32                          mTracingEnabled           : 1;
 
     class nsContentEncodings : public nsIUTF8StringEnumerator
     {
     public:
         NS_DECL_ISUPPORTS
         NS_DECL_NSIUTF8STRINGENUMERATOR
 
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -49,21 +49,22 @@
 #include "nsIDocumentLoader.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsIObserverService.h"
 #include "nsIURL.h"
 #include "nsIWebProgress.h"
 #include "nsICryptoHash.h"
 #include "nsICacheEntryDescriptor.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStreamUtils.h"
-#include "nsStringEnumerator.h"
 #include "nsThreadUtils.h"
 #include "prlog.h"
 
 static nsOfflineCacheUpdateService *gOfflineCacheUpdateService = nsnull;
 
 #if defined(PR_LOGGING)
 //
 // To enable logging (see prlog.h for full details):
@@ -84,16 +85,28 @@ public:
     AutoFreeArray(PRUint32 count, char **values)
         : mCount(count), mValues(values) {};
     ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); }
 private:
     PRUint32 mCount;
     char **mValues;
 };
 
+static nsresult
+DropReferenceFromURL(nsIURI * aURI)
+{
+    nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+    if (url) {
+        nsresult rv = url->SetRef(EmptyCString());
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdateItem::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_ISUPPORTS6(nsOfflineCacheUpdateItem,
                    nsIDOMLoadStatus,
                    nsIRequestObserver,
                    nsIStreamListener,
@@ -425,16 +438,17 @@ nsOfflineManifestItem::nsOfflineManifest
                                              const nsACString &aClientID)
     : nsOfflineCacheUpdateItem(aUpdate, aURI, aReferrerURI,
                                aPreviousApplicationCache, aClientID,
                                nsIApplicationCache::ITEM_MANIFEST)
     , mParserState(PARSE_INIT)
     , mNeedsUpdate(PR_TRUE)
     , mManifestHashInitialized(PR_FALSE)
 {
+    ReadStrictFileOriginPolicyPref();
 }
 
 nsOfflineManifestItem::~nsOfflineManifestItem()
 {
 }
 
 //-----------------------------------------------------------------------------
 // nsOfflineManifestItem <private>
@@ -505,16 +519,43 @@ nsOfflineManifestItem::ReadManifest(nsII
 
     // any leftovers are saved for next time
     manifest->mReadBuf = Substring(begin, end);
 
     return NS_OK;
 }
 
 nsresult
+nsOfflineManifestItem::AddNamespace(PRUint32 namespaceType,
+                                    const nsCString &namespaceSpec,
+                                    const nsCString &data)
+
+{
+    nsresult rv;
+    if (!mNamespaces) {
+        mNamespaces = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    nsCOMPtr<nsIApplicationCacheNamespace> ns =
+        do_CreateInstance(NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = ns->Init(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
+                  nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC,
+                  namespaceSpec, data);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mNamespaces->AppendElement(ns, PR_FALSE);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
+
+nsresult
 nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin,
                                           const nsCString::const_iterator &aEnd)
 {
     nsCString::const_iterator begin = aBegin;
     nsCString::const_iterator end = aEnd;
 
     // all lines ignore trailing spaces and tabs
     nsCString::const_iterator last = end;
@@ -562,17 +603,17 @@ nsOfflineManifestItem::HandleManifestLin
     }
 
     if (line.EqualsLiteral("FALLBACK:")) {
         mParserState = PARSE_FALLBACK_ENTRIES;
         return NS_OK;
     }
 
     if (line.EqualsLiteral("NETWORK:")) {
-        mParserState = PARSE_NETWORK_ENTRIES;
+        mParserState = PARSE_BYPASS_ENTRIES;
         return NS_OK;
     }
 
     nsresult rv;
 
     switch(mParserState) {
     case PARSE_INIT:
     case PARSE_ERROR: {
@@ -580,32 +621,103 @@ nsOfflineManifestItem::HandleManifestLin
         return NS_ERROR_FAILURE;
     }
 
     case PARSE_CACHE_ENTRIES: {
         nsCOMPtr<nsIURI> uri;
         rv = NS_NewURI(getter_AddRefs(uri), line, nsnull, mURI);
         if (NS_FAILED(rv))
             break;
+        if (NS_FAILED(DropReferenceFromURL(uri)))
+            break;
 
         nsCAutoString scheme;
         uri->GetScheme(scheme);
 
         // Manifest URIs must have the same scheme as the manifest.
         PRBool match;
         if (NS_FAILED(mURI->SchemeIs(scheme.get(), &match)) || !match)
             break;
 
         mExplicitURIs.AppendObject(uri);
         break;
     }
-    case PARSE_FALLBACK_ENTRIES:
-    case PARSE_NETWORK_ENTRIES: {
-        // we don't currently implement fallbacks or whitelists,
-        // ignore these for now.
+
+    case PARSE_FALLBACK_ENTRIES: {
+        PRInt32 separator = line.FindChar(' ');
+        if (separator == kNotFound) {
+            separator = line.FindChar('\t');
+            if (separator == kNotFound)
+                break;
+        }
+
+        nsCString namespaceSpec(Substring(line, 0, separator));
+        nsCString fallbackSpec(Substring(line, separator + 1));
+        namespaceSpec.CompressWhitespace();
+        fallbackSpec.CompressWhitespace();
+
+        nsCOMPtr<nsIURI> namespaceURI;
+        rv = NS_NewURI(getter_AddRefs(namespaceURI), namespaceSpec, nsnull, mURI);
+        if (NS_FAILED(rv))
+            break;
+        if (NS_FAILED(DropReferenceFromURL(namespaceURI)))
+            break;
+        rv = namespaceURI->GetAsciiSpec(namespaceSpec);
+        if (NS_FAILED(rv))
+            break;
+
+
+        nsCOMPtr<nsIURI> fallbackURI;
+        rv = NS_NewURI(getter_AddRefs(fallbackURI), fallbackSpec, nsnull, mURI);
+        if (NS_FAILED(rv))
+            break;
+        if (NS_FAILED(DropReferenceFromURL(fallbackURI)))
+            break;
+        rv = fallbackURI->GetAsciiSpec(fallbackSpec);
+        if (NS_FAILED(rv))
+            break;
+
+        // Manifest and namespace must be same origin
+        if (!NS_SecurityCompareURIs(mURI, namespaceURI,
+                                    mStrictFileOriginPolicy))
+            break;
+
+        // Fallback and namespace must be same origin
+        if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI,
+                                    mStrictFileOriginPolicy))
+            break;
+
+        mFallbackURIs.AppendObject(fallbackURI);
+        mOpportunisticNamespaces.AppendElement(namespaceSpec);
+
+        AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
+                     nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC,
+                     namespaceSpec, fallbackSpec);
+        break;
+    }
+
+    case PARSE_BYPASS_ENTRIES: {
+        nsCOMPtr<nsIURI> bypassURI;
+        rv = NS_NewURI(getter_AddRefs(bypassURI), line, nsnull, mURI);
+        if (NS_FAILED(rv))
+            break;
+
+        nsCAutoString scheme;
+        bypassURI->GetScheme(scheme);
+        PRBool equals;
+        if (NS_FAILED(mURI->SchemeIs(scheme.get(), &equals)) || !equals)
+            break;
+        if (NS_FAILED(DropReferenceFromURL(bypassURI)))
+            break;
+        nsCString spec;
+        if (NS_FAILED(bypassURI->GetAsciiSpec(spec)))
+            break;
+
+        AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
+                     spec, EmptyCString());
         break;
     }
     }
 
     return NS_OK;
 }
 
 nsresult 
@@ -675,16 +787,26 @@ nsOfflineManifestItem::CheckNewManifestC
     
         rv = cacheDescriptor->SetMetaDataElement("offline-manifest-hash", PromiseFlatCString(newManifestHashValue).get());
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return NS_OK;
 }
 
+void
+nsOfflineManifestItem::ReadStrictFileOriginPolicyPref()
+{
+    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+    mStrictFileOriginPolicy =
+        (!prefs ||
+         NS_FAILED(prefs->GetBoolPref("security.fileuri.strict_origin_policy",
+                                      &mStrictFileOriginPolicy)));
+}
+
 NS_IMETHODIMP
 nsOfflineManifestItem::OnStartRequest(nsIRequest *aRequest,
                                       nsISupports *aContext)
 {
     nsresult rv;
 
     nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -943,29 +1065,41 @@ nsOfflineCacheUpdate::HandleManifest(PRB
 
     // Add items requested by the manifest.
     const nsCOMArray<nsIURI> &manifestURIs = mManifestItem->GetExplicitURIs();
     for (PRInt32 i = 0; i < manifestURIs.Count(); i++) {
         rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
+    const nsCOMArray<nsIURI> &fallbackURIs = mManifestItem->GetFallbackURIs();
+    for (PRInt32 i = 0; i < fallbackURIs.Count(); i++) {
+        rv = AddURI(fallbackURIs[i], nsIApplicationCache::ITEM_FALLBACK);
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
     // The document that requested the manifest is implicitly included
     // as part of that manifest update.
     rv = AddURI(mDocumentURI, nsIApplicationCache::ITEM_IMPLICIT);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Add items previously cached implicitly
     rv = AddExistingItems(nsIApplicationCache::ITEM_IMPLICIT);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Add items requested by the script API
     rv = AddExistingItems(nsIApplicationCache::ITEM_DYNAMIC);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    // Add opportunistically cached items conforming current opportunistic
+    // namespace list
+    rv = AddExistingItems(nsIApplicationCache::ITEM_OPPORTUNISTIC,
+                          &mManifestItem->GetOpportunisticNamespaces());
+    NS_ENSURE_SUCCESS(rv, rv);
+
     *aDoUpdate = PR_TRUE;
 
     return NS_OK;
 }
 
 void
 nsOfflineCacheUpdate::LoadCompleted()
 {
@@ -1100,31 +1234,49 @@ nsOfflineCacheUpdate::Cancel()
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdate <private>
 //-----------------------------------------------------------------------------
 
 nsresult
-nsOfflineCacheUpdate::AddExistingItems(PRUint32 aType)
+nsOfflineCacheUpdate::AddExistingItems(PRUint32 aType,
+                                       nsTArray<nsCString>* namespaceFilter)
 {
     if (!mPreviousApplicationCache) {
         return NS_OK;
     }
 
+    if (namespaceFilter && namespaceFilter->Length() == 0) {
+        // Don't bother to walk entries when there are no namespaces
+        // defined.
+        return NS_OK;
+    }
+
     PRUint32 count = 0;
     char **keys = nsnull;
     nsresult rv = mPreviousApplicationCache->GatherEntries(aType,
                                                            &count, &keys);
     NS_ENSURE_SUCCESS(rv, rv);
 
     AutoFreeArray autoFree(count, keys);
 
     for (PRUint32 i = 0; i < count; i++) {
+        if (namespaceFilter) {
+            PRBool found = PR_FALSE;
+            for (PRUint32 j = 0; j < namespaceFilter->Length() && !found; j++) {
+                found = StringBeginsWith(nsDependentCString(keys[i]),
+                                         namespaceFilter->ElementAt(j));
+            }
+
+            if (!found)
+                continue;
+        }
+
         nsCOMPtr<nsIURI> uri;
         if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) {
             rv = AddURI(uri, aType);
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     return NS_OK;
@@ -1291,17 +1443,24 @@ nsOfflineCacheUpdate::Finish()
     nsOfflineCacheUpdateService* service =
         nsOfflineCacheUpdateService::EnsureService();
 
     if (!service)
         return NS_ERROR_FAILURE;
 
     if (!mPartialUpdate) {
         if (mSucceeded) {
-            nsresult rv = mApplicationCache->Activate();
+            nsIArray *namespaces = mManifestItem->GetNamespaces();
+            nsresult rv = mApplicationCache->AddNamespaces(namespaces);
+            if (NS_FAILED(rv)) {
+                NotifyError();
+                mSucceeded = PR_FALSE;
+            }
+
+            rv = mApplicationCache->Activate();
             if (NS_FAILED(rv)) {
                 NotifyError();
                 mSucceeded = PR_FALSE;
             }
         }
 
         if (!mSucceeded) {
             // Update was not merged, mark all the loads as failures
--- a/uriloader/prefetch/nsOfflineCacheUpdate.h
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -45,16 +45,17 @@
 #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 "nsIMutableArray.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsIApplicationCache.h"
 #include "nsIRequestObserver.h"
 #include "nsIRunnable.h"
 #include "nsIStreamListener.h"
 #include "nsIURI.h"
 #include "nsIWebProgressListener.h"
@@ -62,16 +63,17 @@
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsWeakReference.h"
 #include "nsICryptoHash.h"
 
 class nsOfflineCacheUpdate;
 
 class nsICacheEntryDescriptor;
+class nsIUTF8StringEnumerator;
 
 class nsOfflineCacheUpdateItem : public nsIDOMLoadStatus
                                , public nsIStreamListener
                                , public nsIRunnable
                                , public nsIInterfaceRequestor
                                , public nsIChannelEventSink
 {
 public:
@@ -120,29 +122,39 @@ public:
     nsOfflineManifestItem(nsOfflineCacheUpdate *aUpdate,
                           nsIURI *aURI,
                           nsIURI *aReferrerURI,
                           nsIApplicationCache *aPreviousApplicationCache,
                           const nsACString &aClientID);
     virtual ~nsOfflineManifestItem();
 
     nsCOMArray<nsIURI> &GetExplicitURIs() { return mExplicitURIs; }
+    nsCOMArray<nsIURI> &GetFallbackURIs() { return mFallbackURIs; }
+
+    nsTArray<nsCString> &GetOpportunisticNamespaces()
+        { return mOpportunisticNamespaces; }
+    nsIArray *GetNamespaces()
+        { return mNamespaces.get(); }
 
     PRBool ParseSucceeded()
         { return (mParserState != PARSE_INIT && mParserState != PARSE_ERROR); }
     PRBool NeedsUpdate() { return mParserState != PARSE_INIT && mNeedsUpdate; }
 
 private:
     static NS_METHOD ReadManifest(nsIInputStream *aInputStream,
                                   void *aClosure,
                                   const char *aFromSegment,
                                   PRUint32 aOffset,
                                   PRUint32 aCount,
                                   PRUint32 *aBytesConsumed);
 
+    nsresult AddNamespace(PRUint32 namespaceType,
+                          const nsCString &namespaceSpec,
+                          const nsCString &data);
+
     nsresult HandleManifestLine(const nsCString::const_iterator &aBegin,
                                 const nsCString::const_iterator &aEnd);
 
     /**
      * Saves "offline-manifest-hash" meta data from the old offline cache
      * token to mOldManifestHashValue member to be compared on
      * successfull load.
      */
@@ -150,27 +162,41 @@ private:
     /**
      * This method setups the mNeedsUpdate to PR_FALSE when hash value
      * of the just downloaded manifest file is the same as stored in cache's 
      * "offline-manifest-hash" meta data. Otherwise stores the new value
      * to this meta data.
      */
     nsresult CheckNewManifestContentHash(nsIRequest *aRequest);
 
+    void ReadStrictFileOriginPolicyPref();
+
     enum {
         PARSE_INIT,
         PARSE_CACHE_ENTRIES,
         PARSE_FALLBACK_ENTRIES,
-        PARSE_NETWORK_ENTRIES,
+        PARSE_BYPASS_ENTRIES,
         PARSE_ERROR
     } mParserState;
 
     nsCString mReadBuf;
+
     nsCOMArray<nsIURI> mExplicitURIs;
+    nsCOMArray<nsIURI> mFallbackURIs;
+
+    // All opportunistic caching namespaces.  Used to decide whether
+    // to include previously-opportunistically-cached entries.
+    nsTArray<nsCString> mOpportunisticNamespaces;
+
+    // Array of nsIApplicationCacheNamespace objects specified by the
+    // manifest.
+    nsCOMPtr<nsIMutableArray> mNamespaces;
+
     PRBool mNeedsUpdate;
+    PRBool mStrictFileOriginPolicy;
 
     // manifest hash data
     nsCOMPtr<nsICryptoHash> mManifestHash;
     PRBool mManifestHashInitialized;
     nsCString mOldManifestHashValue;
 };
 
 class nsOfflineCacheUpdate : public nsIOfflineCacheUpdate
@@ -192,17 +218,21 @@ public:
     void LoadCompleted();
 
 private:
     nsresult HandleManifest(PRBool *aDoUpdate);
     nsresult AddURI(nsIURI *aURI, PRUint32 aItemType);
 
     nsresult ProcessNextURI();
 
-    nsresult AddExistingItems(PRUint32 aType);
+    // Adds items from the previous cache witha type matching aType.
+    // If namespaceFilter is non-null, only items matching the
+    // 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 NotifyDownloading();
     nsresult NotifyStarted(nsOfflineCacheUpdateItem *aItem);
     nsresult NotifyCompleted(nsOfflineCacheUpdateItem *aItem);