Bug 442812: Implement the application cache selection algorithm. r+sr=bz
☠☠ backed out by 695ba8eac3dc ☠ ☠
authorHonza Bambas <honzab@allpeers.com>
Tue, 19 Aug 2008 19:31:08 -0700
changeset 17091 1e3d4775197af65a4f9bcebf2280ad0d710b722a
parent 17090 ea551e6f0f409d0afd550d69cfbdd8a8d816c336
child 17092 cf6c811e127249ad399fe332627e24ed78aec1b4
child 17123 695ba8eac3dc1e4a5206ff29f25a36267bc2d3e0
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)
bugs442812
milestone1.9.1a2pre
Bug 442812: Implement the application cache selection algorithm. r+sr=bz
caps/src/nsScriptSecurityManager.cpp
content/base/public/nsContentUtils.h
content/base/src/Makefile.in
content/base/src/nsContentSink.cpp
content/base/src/nsContentSink.h
content/base/src/nsContentUtils.cpp
dom/src/offline/nsDOMOfflineResourceList.cpp
netwerk/base/public/nsNetUtil.h
netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
netwerk/cache/src/nsDiskCacheDeviceSQL.h
uriloader/prefetch/nsIOfflineCacheUpdate.idl
uriloader/prefetch/nsOfflineCacheUpdate.cpp
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -311,128 +311,17 @@ nsScriptSecurityManager::GetSafeJSContex
     return cx;
 }
 
 /* static */
 PRBool
 nsScriptSecurityManager::SecurityCompareURIs(nsIURI* aSourceURI,
                                              nsIURI* aTargetURI)
 {
-    // Note that this is not an Equals() test on purpose -- for URIs that don't
-    // support host/port, we want equality to basically be object identity, for
-    // security purposes.  Otherwise, for example, two javascript: URIs that
-    // are otherwise unrelated could end up "same origin", which would be
-    // unfortunate.
-    if (aSourceURI && aSourceURI == aTargetURI)
-    {
-        return PR_TRUE;
-    }
-
-    if (!aTargetURI || !aSourceURI) 
-    {
-        return PR_FALSE;
-    }
-
-    // If either URI is a nested URI, get the base URI
-    nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(aSourceURI);
-    nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI);
-
-    if (!sourceBaseURI || !targetBaseURI)
-        return PR_FALSE;
-
-    // Compare schemes
-    nsCAutoString targetScheme;
-    PRBool sameScheme = PR_FALSE;
-    if (NS_FAILED( targetBaseURI->GetScheme(targetScheme) ) ||
-        NS_FAILED( sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme) ) ||
-        !sameScheme)
-    {
-        // Not same-origin if schemes differ
-        return PR_FALSE;
-    }
-
-    // special handling for file: URIs
-    if (targetScheme.EqualsLiteral("file"))
-    {
-        // in traditional unsafe behavior all files are the same origin
-        if (!sStrictFileOriginPolicy)
-            return PR_TRUE;
-
-        nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(sourceBaseURI));
-        nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(targetBaseURI));
-
-        if (!sourceFileURL || !targetFileURL)
-            return PR_FALSE;
-
-        nsCOMPtr<nsIFile> sourceFile, targetFile;
-
-        sourceFileURL->GetFile(getter_AddRefs(sourceFile));
-        targetFileURL->GetFile(getter_AddRefs(targetFile));
-
-        if (!sourceFile || !targetFile)
-            return PR_FALSE;
-
-        // Otherwise they had better match
-        PRBool filesAreEqual = PR_FALSE;
-        nsresult rv = sourceFile->Equals(targetFile, &filesAreEqual);
-        return NS_SUCCEEDED(rv) && filesAreEqual;
-    }
-
-    // Special handling for mailnews schemes
-    if (targetScheme.EqualsLiteral("imap") ||
-        targetScheme.EqualsLiteral("mailbox") ||
-        targetScheme.EqualsLiteral("news"))
-    {
-        // Each message is a distinct trust domain; use the 
-        // whole spec for comparison
-        nsCAutoString targetSpec;
-        nsCAutoString sourceSpec;
-        return ( NS_SUCCEEDED( targetBaseURI->GetSpec(targetSpec) ) &&
-                 NS_SUCCEEDED( sourceBaseURI->GetSpec(sourceSpec) ) &&
-                 targetSpec.Equals(sourceSpec) );
-    }
-
-    // Compare hosts
-    nsCAutoString targetHost;
-    nsCAutoString sourceHost;
-    if (NS_FAILED( targetBaseURI->GetHost(targetHost) ) ||
-        NS_FAILED( sourceBaseURI->GetHost(sourceHost) ) ||
-        !targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator()))
-    {
-        // Not same-origin if hosts differ
-        return PR_FALSE;
-    }
-
-    // Compare ports
-    PRInt32 targetPort;
-    nsresult rv = targetBaseURI->GetPort(&targetPort);
-    PRInt32 sourcePort;
-    if (NS_SUCCEEDED(rv))
-        rv = sourceBaseURI->GetPort(&sourcePort);
-    PRBool result = NS_SUCCEEDED(rv) && targetPort == sourcePort;
-    // If the port comparison failed, see if either URL has a
-    // port of -1. If so, replace -1 with the default port
-    // for that scheme.
-    if (NS_SUCCEEDED(rv) && !result &&
-        (sourcePort == -1 || targetPort == -1))
-    {
-        NS_ENSURE_TRUE(sIOService, PR_FALSE);
-
-        PRInt32 defaultPort = NS_GetDefaultPort(targetScheme.get());
-        if (defaultPort == -1)
-            return PR_FALSE; // No default port for this scheme
-
-        if (sourcePort == -1)
-            sourcePort = defaultPort;
-        else if (targetPort == -1)
-            targetPort = defaultPort;
-        result = targetPort == sourcePort;
-    }
-
-    return result;
+    return NS_SecurityCompareURIs(aSourceURI, aTargetURI, sStrictFileOriginPolicy);
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::GetChannelPrincipal(nsIChannel* aChannel,
                                              nsIPrincipal** aPrincipal)
 {
     NS_PRECONDITION(aChannel, "Must have channel!");
     nsCOMPtr<nsISupports> owner;
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1240,16 +1240,21 @@ public:
   static void GetOfflineAppManifest(nsIDOMWindow *aWindow, nsIURI **aURI);
 
   /**
    * Check whether an application should be allowed to use offline APIs.
    */
   static PRBool OfflineAppAllowed(nsIURI *aURI);
 
   /**
+   * Check whether an application should be allowed to use offline APIs.
+   */
+  static PRBool OfflineAppAllowed(nsIPrincipal *aPrincipal);
+
+  /**
    * Increases the count of blockers preventing scripts from running.
    * NOTE: You might want to use nsAutoScriptBlocker rather than calling
    * this directly
    */
   static void AddScriptBlocker();
 
   /**
    * Decreases the count of blockers preventing scripts from running.
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -54,16 +54,17 @@ REQUIRES	= xpcom \
 		  layout \
 		  widget \
 		  view \
 		  locale \
 		  htmlparser \
 		  js \
 		  webshell \
 		  necko \
+		  nkcache \
 		  mimetype \
 		  exthandler \
 		  chardet \
 		  caps \
 		  lwbrk \
 		  uconv \
 		  docshell \
 		  pref \
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -68,16 +68,17 @@
 #include "nsGkAtoms.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsNetCID.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheService.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIDOMLoadStatus.h"
 #include "nsICookieService.h"
 #include "nsIPrompt.h"
 #include "nsServiceManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsParserUtils.h"
 #include "nsCRT.h"
@@ -91,16 +92,20 @@
 #include "nsIDOMNSDocument.h"
 #include "nsIRequest.h"
 #include "nsNodeUtils.h"
 #include "nsIDOMNode.h"
 #include "nsThreadUtils.h"
 #include "nsPresShellIterator.h"
 #include "nsPIDOMWindow.h"
 #include "mozAutoDocUpdate.h"
+#include "nsIWebNavigation.h"
+#include "nsIDocumentLoader.h"
+#include "nsICachingChannel.h"
+#include "nsICacheEntryDescriptor.h"
 
 PRLogModuleInfo* gContentSinkLogModuleInfo;
 
 class nsScriptLoaderObserverProxy : public nsIScriptLoaderObserver
 {
 public:
   nsScriptLoaderObserverProxy(nsIScriptLoaderObserver* aInner)
     : mInner(do_GetWeakReference(aInner))
@@ -842,89 +847,314 @@ nsContentSink::PrefetchHref(const nsAStr
               mDocumentBaseURI);
     if (uri) {
       nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aSource);
       prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit);
     }
   }
 }
 
+nsresult
+nsContentSink::GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey)
+{
+  aCacheKey.Truncate();
+
+  nsresult rv;
+  nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsISupports> token;
+  rv = cachingChannel->GetCacheToken(getter_AddRefs(token));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsICacheEntryDescriptor> descriptor = do_QueryInterface(token, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = descriptor->GetKey(aCacheKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+nsContentSink::SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache,
+                                 nsIURI *aManifestURI,
+                                 PRBool aIsTopDocument,
+                                 PRBool aFetchedWithHTTPGetOrEquiv,
+                                 CacheSelectionAction *aAction)
+{
+  *aAction = CACHE_SELECTION_NONE;
+
+  nsCOMPtr<nsIApplicationCacheContainer> applicationCacheDocument =
+    do_QueryInterface(mDocument);
+  NS_ASSERTION(applicationCacheDocument,
+               "mDocument must implement nsIApplicationCacheContainer.");
+
+  nsresult rv;
+
+  // We might decide on a new application cache...
+  nsCOMPtr<nsIApplicationCache> applicationCache = aLoadApplicationCache;
+
+  if (applicationCache) {
+    nsCAutoString groupID;
+    rv = applicationCache->GetGroupID(groupID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIURI> groupURI;
+    rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRBool equal = PR_FALSE;
+    rv = groupURI->Equals(aManifestURI, &equal);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!equal) {
+      // This is a foreign entry, mark it as such.  If this is a
+      // toplevel load, force a reload to avoid loading the foreign
+      // entry.  The next attempt will not choose this cache entry
+      // (because it has been marked foreign).
+
+      nsCAutoString cachekey;
+      rv = GetChannelCacheKey(mDocument->GetChannel(), cachekey);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = applicationCache->MarkEntry(cachekey,
+                                       nsIApplicationCache::ITEM_FOREIGN);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (aIsTopDocument) {
+        *aAction = CACHE_SELECTION_RELOAD;
+      }
+
+      return NS_OK;
+    }
+
+    if (aIsTopDocument) {
+      // This is a top level document and the http manifest attribute
+      // URI is equal to the manifest URI of the cache the document
+      // was loaded from - associate the document with that cache and
+      // invoke the cache update process.
+      rv = applicationCacheDocument->SetApplicationCache(applicationCache);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      *aAction = CACHE_SELECTION_UPDATE;
+    }
+  }
+  else {
+    // The document was not loaded from an application cache
+    // Here we know the manifest has the same origin as the
+    // document. There is call to CheckMayLoad() on it above.
+
+    if (!aFetchedWithHTTPGetOrEquiv) {
+      // The document was not loaded using HTTP GET or equivalent
+      // method. The spec says to run the cache selection algorithm w/o
+      // the manifest specified but we can just do return NS_OK here.
+
+      return NS_OK;
+    }
+
+    // If there is an existing application cache for this manifest,
+    // associate it with the document.
+    nsCAutoString manifestURISpec;
+    rv = aManifestURI->GetAsciiSpec(manifestURISpec);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIApplicationCacheService> appCacheService =
+      do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
+    if (!appCacheService) {
+      // No application cache service, nothing to do here.
+      return NS_OK;
+    }
+
+    rv = appCacheService->GetActiveCache(manifestURISpec,
+                                         getter_AddRefs(applicationCache));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (applicationCache) {
+      rv = applicationCacheDocument->SetApplicationCache(applicationCache);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    else {
+      // XXX bug 443023: if there is already a scheduled update or
+      // update in progress we have to add this document as
+      // an implicit entry.
+    }
+
+    // Always do an update in this case
+    *aAction = CACHE_SELECTION_UPDATE;
+  }
+
+  if (applicationCache) {
+    // We are now associated with an application cache.  This item
+    // should be marked as an implicit entry.
+    nsCAutoString cachekey;
+    rv = GetChannelCacheKey(mDocument->GetChannel(), cachekey);
+    if (NS_SUCCEEDED(rv)) {
+      rv = applicationCache->MarkEntry(cachekey,
+                                       nsIApplicationCache::ITEM_IMPLICIT);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsContentSink::SelectDocAppCacheNoManifest(nsIApplicationCache *aLoadApplicationCache,
+                                           PRBool aIsTopDocument,
+                                           nsIURI **aManifestURI,
+                                           CacheSelectionAction *aAction)
+{
+  *aManifestURI = nsnull;
+  *aAction = CACHE_SELECTION_NONE;
+
+  if (!aIsTopDocument || !aLoadApplicationCache) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  // The document was loaded from an application cache, use that
+  // application cache as the document's application cache.
+  nsCOMPtr<nsIApplicationCacheContainer> applicationCacheDocument =
+    do_QueryInterface(mDocument);
+  NS_ASSERTION(applicationCacheDocument,
+               "mDocument must implement nsIApplicationCacheContainer.");
+
+  rv = applicationCacheDocument->SetApplicationCache(aLoadApplicationCache);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Return the uri and invoke the update process for the selected
+  // application cache.
+  nsCAutoString groupID;
+  rv = aLoadApplicationCache->GetGroupID(groupID);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = NS_NewURI(aManifestURI, groupID);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aAction = CACHE_SELECTION_UPDATE;
+
+  return NS_OK;
+}
+
 void
 nsContentSink::ProcessOfflineManifest(nsIContent *aElement)
 {
+  // Only check the manifest for root document nodes.
+  if (aElement != mDocument->GetRootContent()) {
+    return;
+  }
+
+  nsresult rv;
+
   // Check for a manifest= attribute.
   nsAutoString manifestSpec;
   aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec);
 
-  if (manifestSpec.IsEmpty() ||
-      manifestSpec.FindChar('#') != kNotFound) {
-    return;
-  }
+  // Grab the application cache the document was loaded from, if any.
+  nsCOMPtr<nsIApplicationCache> applicationCache;
 
-  // We only care about manifests in toplevel windows.
-  nsCOMPtr<nsPIDOMWindow> pwindow =
-    do_QueryInterface(mDocument->GetScriptGlobalObject());
-  if (!pwindow) {
-    return;
+  nsCOMPtr<nsIApplicationCacheContainer> applicationCacheChannel =
+    do_QueryInterface(mDocument->GetChannel());
+  if (applicationCacheChannel) {
+    rv = applicationCacheChannel->GetApplicationCache(
+      getter_AddRefs(applicationCache));
+    if (NS_FAILED(rv)) {
+      return;
+    }
   }
 
-  nsCOMPtr<nsIDOMWindow> window =
-    do_QueryInterface(pwindow->GetOuterWindow());
-  if (!window) {
-    return;
-  }
-
-  nsCOMPtr<nsIDOMWindow> parent;
-  window->GetParent(getter_AddRefs(parent));
-  if (parent.get() != window.get()) {
-    return;
-  }
-
-  // Only update if the document has permission to use offline APIs.
-  if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
+  if (manifestSpec.IsEmpty() && !applicationCache) {
+    // Not loaded from an application cache, and no manifest
+    // attribute.  Nothing to do here.
     return;
   }
 
-  // XXX: at this point in the spec there is an algorithm for
-  // confirming whether the cache that was selected at load time was
-  // the proper application cache for this document.  This will
-  // be implemented in a separate patch;  For now just assume that we
-  // chose an acceptable application cache.
+  // The manifest attribute is handled differently if the document is
+  // not toplevel.
+  nsCOMPtr<nsIDOMWindow> window = mDocument->GetWindow();
+  if (!window)
+    return;
+  nsCOMPtr<nsIDOMWindow> parent;
+  window->GetParent(getter_AddRefs(parent));
+  PRBool isTop = (parent == window);
+
+  CacheSelectionAction action = CACHE_SELECTION_NONE;
+  nsCOMPtr<nsIURI> manifestURI;
 
-  nsCOMPtr<nsIApplicationCacheContainer> channelContainer =
-    do_QueryInterface(mDocument->GetChannel());
+  if (manifestSpec.IsEmpty()) {
+    rv = SelectDocAppCacheNoManifest(applicationCache,
+                                     isTop,
+                                     getter_AddRefs(manifestURI),
+                                     &action);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+  }
+  else {
+    nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI),
+                                              manifestSpec, mDocument,
+                                              mDocumentURI);
+    if (!manifestURI) {
+      return;
+    }
 
-  nsCOMPtr<nsIApplicationCacheContainer> docContainer =
-    do_QueryInterface(mDocument);
+    // Documents must list a manifest from the same origin
+    rv = mDocument->NodePrincipal()->CheckMayLoad(manifestURI, PR_TRUE);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+
+    // Only continue if the document has permission to use offline APIs.
+    if (!nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal())) {
+      return;
+    }
 
-  if (channelContainer && docContainer) {
-    nsCOMPtr<nsIApplicationCache> appCache;
-    channelContainer->GetApplicationCache(getter_AddRefs(appCache));
-    docContainer->SetApplicationCache(appCache);
+    PRBool fetchedWithHTTPGetOrEquiv = PR_FALSE;
+    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mDocument->GetChannel()));
+    if (httpChannel) {
+      nsCAutoString method;
+      rv = httpChannel->GetRequestMethod(method);
+      if (NS_SUCCEEDED(rv))
+        fetchedWithHTTPGetOrEquiv = method.Equals("GET");
+    }
+
+    rv = SelectDocAppCache(applicationCache, manifestURI, isTop,
+                           fetchedWithHTTPGetOrEquiv, &action);
+    if (NS_FAILED(rv)) {
+      return;
+    }
   }
 
-  nsCOMPtr<nsIURI> manifestURI;
-  nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI),
-                                            manifestSpec, mDocument,
-                                            mDocumentURI);
-  if (!manifestURI) {
+  switch (action)
+  {
+  case CACHE_SELECTION_NONE:
     return;
-  }
+  case CACHE_SELECTION_UPDATE: {
+    nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
+      do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
 
-  // Documents must list a manifest from the same origin
-  nsresult rv = mDocument->NodePrincipal()->CheckMayLoad(manifestURI, PR_TRUE);
-  if (NS_FAILED(rv)) {
-    return;
+    if (updateService) {
+      nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(mDocument);
+      updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI, domdoc);
+    }
+    break;
   }
+  case CACHE_SELECTION_RELOAD: {
+    // This situation occurs only for toplevel documents, see bottom
+    // of SelectDocAppCache method.
+    NS_ASSERTION(isTop, "Should only reload toplevel documents!");
+    nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(mDocShell);
 
-  // Start the update
-  nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(mDocument);
-  nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
-    do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
-  updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI, domdoc);
+    webNav->Stop(nsIWebNavigation::STOP_ALL);
+    webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE);
+    break;
+  }
+  }
 }
 
 void
 nsContentSink::ScrollToRef()
 {
   if (mRef.IsEmpty()) {
     return;
   }
--- a/content/base/src/nsContentSink.h
+++ b/content/base/src/nsContentSink.h
@@ -71,16 +71,17 @@ class nsIDocShell;
 class nsICSSLoader;
 class nsIParser;
 class nsIAtom;
 class nsIChannel;
 class nsIContent;
 class nsIViewManager;
 class nsNodeInfoManager;
 class nsScriptLoader;
+class nsIApplicationCache;
 
 #ifdef NS_DEBUG
 
 extern PRLogModuleInfo* gContentSinkLogModuleInfo;
 
 #define SINK_TRACE_CALLS              0x1
 #define SINK_TRACE_REFLOW             0x2
 #define SINK_ALWAYS_REFLOW            0x4
@@ -145,16 +146,35 @@ class nsContentSink : public nsICSSLoade
   virtual void EndUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType);
 
   virtual void UpdateChildCounts() = 0;
 
 protected:
   nsContentSink();
   virtual ~nsContentSink();
 
+  enum CacheSelectionAction {
+    // There is no offline cache manifest specified by the document,
+    // or the document was loaded from a cache other than the one it
+    // specifies via its manifest attribute and IS NOT a top-level
+    // document, or an error occurred during the cache selection
+    // algorithm.
+    CACHE_SELECTION_NONE = 0,
+
+    // The offline cache manifest must be updated.
+    CACHE_SELECTION_UPDATE = 1,
+
+    // The document was loaded from a cache other than the one it
+    // specifies via its manifest attribute and IS a top-level
+    // document.  In this case, the document is marked as foreign in
+    // the cache it was loaded from and must be reloaded from the
+    // correct cache (the one it specifies).
+    CACHE_SELECTION_RELOAD = 2
+  };
+
   nsresult Init(nsIDocument* aDoc, nsIURI* aURI,
                 nsISupports* aContainer, nsIChannel* aChannel);
 
   nsresult ProcessHTTPHeaders(nsIChannel* aChannel);
   nsresult ProcessHeaderData(nsIAtom* aHeader, const nsAString& aValue,
                              nsIContent* aContent = nsnull);
   nsresult ProcessLinkHeader(nsIContent* aElement,
                              const nsAString& aLinkData);
@@ -166,16 +186,70 @@ protected:
                                     const nsSubstring& aHref,
                                     PRBool aAlternate,
                                     const nsSubstring& aTitle,
                                     const nsSubstring& aType,
                                     const nsSubstring& aMedia);
 
   void PrefetchHref(const nsAString &aHref, nsIContent *aSource,
                     PRBool aExplicit);
+
+  // Gets the cache key (used to identify items in a cache) of the channel.
+  nsresult GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey);
+
+  // There is an offline cache manifest attribute specified and the
+  // document is allowed to use the offline cache.  Process the cache
+  // selection algorithm for this document and the manifest. Result is
+  // an action that must be taken on the manifest, see
+  // CacheSelectionAction enum above.
+  //
+  // @param aLoadApplicationCache
+  //        The application cache from which the load originated, if
+  //        any.
+  // @param aManifestURI
+  //        The manifest URI listed in the document.
+  // @param aIsTopDocument
+  //        TRUE if this is a toplevel document.
+  // @param aFetchedWithHTTPGetOrEquiv
+  //        TRUE if this was fetched using the HTTP GET method.
+  // @param aAction
+  //        Out parameter, returns the action that should be performed
+  //        by the calling function.
+  nsresult SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache,
+                             nsIURI *aManifestURI,
+                             PRBool aIsTopDocument,
+                             PRBool aFetchedWithHTTPGetOrEquiv,
+                             CacheSelectionAction *aAction);
+
+  // There is no offline cache manifest attribute specified.  Process
+  // the cache selection algorithm w/o the manifest. Result is an
+  // action that must be taken, see CacheSelectionAction enum
+  // above. In case the offline cache manifest has to be updated the
+  // manifest URI is returned in aManifestURI.
+  //
+  // @param aLoadApplicationCache
+  //        The application cache from which the load originated, if
+  //        any.
+  // @param aIsTopDocument
+  //        TRUE if this is a toplevel document.
+  // @param aManifestURI
+  //        Out parameter, returns the manifest URI of the cache that
+  //        was selected.
+  // @param aAction
+  //        Out parameter, returns the action that should be performed
+  //        by the calling function.
+  nsresult SelectDocAppCacheNoManifest(nsIApplicationCache *aLoadApplicationCache,
+                                       PRBool aIsTopDocument,
+                                       nsIURI **aManifestURI,
+                                       CacheSelectionAction *aAction);
+
+  // Searches for the offline cache manifest attribute and calls one
+  // of the above defined methods to select the document's application
+  // cache, let it be associated with the document and eventually
+  // schedule the cache update process.
   void ProcessOfflineManifest(nsIContent *aElement);
 
   // Tries to scroll to the URI's named anchor. Once we've successfully
   // done that, further calls to this method will be ignored.
   void ScrollToRef();
   nsresult RefreshIfEnabled(nsIViewManager* vm);
 
   // Start layout.  If aIgnorePendingSheets is true, this will happen even if
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -802,16 +802,26 @@ nsContentUtils::GetOfflineAppManifest(ns
 
 /* static */
 PRBool
 nsContentUtils::OfflineAppAllowed(nsIURI *aURI)
 {
   return NS_OfflineAppAllowed(aURI, sPrefBranch);
 }
 
+/* static */
+PRBool
+nsContentUtils::OfflineAppAllowed(nsIPrincipal *aPrincipal)
+{
+  nsCOMPtr<nsIURI> codebaseURI;
+  aPrincipal->GetURI(getter_AddRefs(codebaseURI));
+
+  return OfflineAppAllowed(codebaseURI);
+}
+
 // static
 void
 nsContentUtils::Shutdown()
 {
   sInitialized = PR_FALSE;
 
   NS_HTMLParanoidFragmentSinkShutdown();
   NS_XHTMLParanoidFragmentSinkShutdown();
--- a/dom/src/offline/nsDOMOfflineResourceList.cpp
+++ b/dom/src/offline/nsDOMOfflineResourceList.cpp
@@ -334,17 +334,21 @@ nsDOMOfflineResourceList::Add(const nsAS
   if (length > maxEntries) return NS_ERROR_NOT_AVAILABLE;
 
   ClearCachedKeys();
 
   nsCOMPtr<nsIOfflineCacheUpdate> update =
     do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = update->Init(PR_TRUE, mManifestURI, mDocumentURI);
+  nsCAutoString clientID;
+  rv = appCache->GetClientID(clientID);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = update->InitPartial(mManifestURI, clientID, mDocumentURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = update->AddDynamicURI(requestedURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = update->Schedule();
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/netwerk/base/public/nsNetUtil.h
+++ b/netwerk/base/public/nsNetUtil.h
@@ -69,16 +69,17 @@
 #include "nsIStreamLoader.h"
 #include "nsIUnicharStreamLoader.h"
 #include "nsIPipe.h"
 #include "nsIProtocolHandler.h"
 #include "nsIFileProtocolHandler.h"
 #include "nsIStringStream.h"
 #include "nsILocalFile.h"
 #include "nsIFileStreams.h"
+#include "nsIFileURL.h"
 #include "nsIProtocolProxyService.h"
 #include "nsIProxyInfo.h"
 #include "nsIFileStreams.h"
 #include "nsIBufferedStreams.h"
 #include "nsIInputStreamPump.h"
 #include "nsIAsyncStreamCopier.h"
 #include "nsIPersistentProperties2.h"
 #include "nsISyncStreamListener.h"
@@ -1460,9 +1461,133 @@ NS_OfflineAppAllowed(nsIURI *aURI, nsIPr
 
     PRBool allowed;
     rv = util->OfflineAppAllowed(aURI, aPrefBranch, &allowed);
     NS_ENSURE_SUCCESS(rv, PR_FALSE);
 
     return allowed;
 }
 
+inline PRBool
+NS_SecurityCompareURIs(nsIURI* aSourceURI,
+                       nsIURI* aTargetURI,
+                       PRBool aStrictFileOriginPolicy)
+{
+    // Note that this is not an Equals() test on purpose -- for URIs that don't
+    // support host/port, we want equality to basically be object identity, for
+    // security purposes.  Otherwise, for example, two javascript: URIs that
+    // are otherwise unrelated could end up "same origin", which would be
+    // unfortunate.
+    if (aSourceURI && aSourceURI == aTargetURI)
+    {
+        return PR_TRUE;
+    }
+
+    if (!aTargetURI || !aSourceURI)
+    {
+        return PR_FALSE;
+    }
+
+    // If either URI is a nested URI, get the base URI
+    nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(aSourceURI);
+    nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI);
+
+    if (!sourceBaseURI || !targetBaseURI)
+        return PR_FALSE;
+
+    // Compare schemes
+    nsCAutoString targetScheme;
+    PRBool sameScheme = PR_FALSE;
+    if (NS_FAILED( targetBaseURI->GetScheme(targetScheme) ) ||
+        NS_FAILED( sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme) ) ||
+        !sameScheme)
+    {
+        // Not same-origin if schemes differ
+        return PR_FALSE;
+    }
+
+    // special handling for file: URIs
+    if (targetScheme.EqualsLiteral("file"))
+    {
+        // in traditional unsafe behavior all files are the same origin
+        if (!aStrictFileOriginPolicy)
+            return PR_TRUE;
+
+        nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(sourceBaseURI));
+        nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(targetBaseURI));
+
+        if (!sourceFileURL || !targetFileURL)
+            return PR_FALSE;
+
+        nsCOMPtr<nsIFile> sourceFile, targetFile;
+
+        sourceFileURL->GetFile(getter_AddRefs(sourceFile));
+        targetFileURL->GetFile(getter_AddRefs(targetFile));
+
+        if (!sourceFile || !targetFile)
+            return PR_FALSE;
+
+        // Otherwise they had better match
+        PRBool filesAreEqual = PR_FALSE;
+        nsresult rv = sourceFile->Equals(targetFile, &filesAreEqual);
+        return NS_SUCCEEDED(rv) && filesAreEqual;
+    }
+
+    // Special handling for mailnews schemes
+    if (targetScheme.EqualsLiteral("imap") ||
+        targetScheme.EqualsLiteral("mailbox") ||
+        targetScheme.EqualsLiteral("news"))
+    {
+        // Each message is a distinct trust domain; use the
+        // whole spec for comparison
+        nsCAutoString targetSpec;
+        nsCAutoString sourceSpec;
+        return ( NS_SUCCEEDED( targetBaseURI->GetSpec(targetSpec) ) &&
+                 NS_SUCCEEDED( sourceBaseURI->GetSpec(sourceSpec) ) &&
+                 targetSpec.Equals(sourceSpec) );
+    }
+
+    // Compare hosts
+    nsCAutoString targetHost;
+    nsCAutoString sourceHost;
+    if (NS_FAILED( targetBaseURI->GetAsciiHost(targetHost) ) ||
+        NS_FAILED( sourceBaseURI->GetAsciiHost(sourceHost) ))
+    {
+        return PR_FALSE;
+    }
+
+#ifdef MOZILLA_INTERNAL_API
+    if (!targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator() ))
+#else
+    if (!targetHost.Equals(sourceHost, CaseInsensitiveCompare))
+#endif
+    {
+        return PR_FALSE;
+    }
+
+    // Compare ports
+    PRInt32 targetPort;
+    nsresult rv = targetBaseURI->GetPort(&targetPort);
+    PRInt32 sourcePort;
+    if (NS_SUCCEEDED(rv))
+        rv = sourceBaseURI->GetPort(&sourcePort);
+    PRBool result = NS_SUCCEEDED(rv) && targetPort == sourcePort;
+    // If the port comparison failed, see if either URL has a
+    // port of -1. If so, replace -1 with the default port
+    // for that scheme.
+    if (NS_SUCCEEDED(rv) && !result &&
+        (sourcePort == -1 || targetPort == -1))
+    {
+        PRInt32 defaultPort = NS_GetDefaultPort(targetScheme.get());
+        if (defaultPort == -1)
+            return PR_FALSE; // No default port for this scheme
+
+        if (sourcePort == -1)
+            sourcePort = defaultPort;
+        else if (targetPort == -1)
+            targetPort = defaultPort;
+        result = targetPort == sourcePort;
+    }
+
+    return result;
+}
+
 #endif // !nsNetUtil_h__
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp
@@ -39,16 +39,18 @@
 #include "nsCache.h"
 #include "nsDiskCache.h"
 #include "nsDiskCacheDeviceSQL.h"
 #include "nsCacheService.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 "nsIVariant.h"
 
 #include "mozIStorageService.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageFunction.h"
@@ -687,16 +689,30 @@ nsOfflineCacheDevice::nsOfflineCacheDevi
 {
 }
 
 nsOfflineCacheDevice::~nsOfflineCacheDevice()
 {
   Shutdown();
 }
 
+/* static */
+PRBool
+nsOfflineCacheDevice::GetStrictFileOriginPolicy()
+{
+    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+    PRBool retval;
+    if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval)))
+        return retval;
+
+    // As default value use true (be more strict)
+    return PR_TRUE;
+}
+
 PRUint32
 nsOfflineCacheDevice::CacheSize()
 {
   AutoResetStatement statement(mStatement_CacheSize);
 
   PRBool hasRows;
   nsresult rv = statement->ExecuteStep(&hasRows);
   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
@@ -1740,23 +1756,50 @@ nsOfflineCacheDevice::ChooseApplicationC
   nsresult 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) {
-    nsCString clientID;
-    rv = statement->GetUTF8String(0, clientID);
+    PRInt32 itemType;
+    rv = statement->GetInt32(1, &itemType);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (mActiveCaches.Contains(clientID))
-      return GetApplicationCache(clientID, out);
+    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);
+        }
+      }
+    }
 
     rv = statement->ExecuteStep(&hasRows);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
--- a/netwerk/cache/src/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.h
@@ -179,16 +179,18 @@ public:
   
 private:
   friend class nsApplicationCache;
 
   static PLDHashOperator ShutdownApplicationCache(const nsACString &key,
                                                   nsIWeakReference *weakRef,
                                                   void *ctx);
 
+  static PRBool GetStrictFileOriginPolicy();
+
   PRBool   Initialized() { return mDB != nsnull; }
 
   nsresult InitActiveCaches();
   nsresult UpdateEntry(nsCacheEntry *entry);
   nsresult UpdateEntrySize(nsCacheEntry *entry, PRUint32 newSize);
   nsresult DeleteEntry(nsCacheEntry *entry, PRBool deleteData);
   nsresult DeleteData(nsCacheEntry *entry);
   nsresult EnableEvictionObserver();
--- a/uriloader/prefetch/nsIOfflineCacheUpdate.idl
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -110,17 +110,17 @@ interface nsIOfflineCacheUpdateObserver 
  * Each update object maintains a list of nsIDOMLoadStatus items for the
  * resources it is updating.  The list of these items will be available
  * after the object is scheduled.
  *
  * One update object will be updating at a time.  The active object will
  * load its items one by one, sending itemCompleted() to any registered
  * observers.
  */
-[scriptable, uuid(4b206247-82ee-46cf-a8b7-f7284e753bc2)]
+[scriptable, uuid(877261bb-b952-4d27-847e-859bdd47c0ec)]
 interface nsIOfflineCacheUpdate : nsISupports {
   /**
    * Fetch the status of the running update.  This will return a value
    * defined in nsIDOMOfflineResourceList.
    */
   readonly attribute unsigned short status;
 
   /**
@@ -149,31 +149,40 @@ interface nsIOfflineCacheUpdate : nsISup
   /**
    * TRUE if the cache update completed successfully.
    */
   readonly attribute boolean succeeded;
 
   /**
    * Initialize the update.
    *
-   * @param aPartialUpdate
-   *        TRUE if the update should just update the URIs given to it,
-   *        FALSE if all URLs for the owner domain should be added.
    * @param aManifestURI
-   *        The manifest URI to be checked, or for partial updates the
-   *        manifest that should own resources that are added.
+   *        The manifest URI to be checked.
    * @param aDocumentURI
    *        The page that is requesting the update.
    */
-  void init(in boolean aPartialUpdate,
-            in nsIURI aManifestURI,
-            in nsIURI aDocumentURI);
+  void init(in nsIURI aManifestURI, in nsIURI aDocumentURI);
 
   /**
-   * Add a URI to the offline cache as part of the update.
+   * Initialize the update for partial processing. 
+   *
+   * @param aManifestURI
+   *        The manifest URI of the related cache.
+   * @param aClientID
+   *        Client  ID of the cache to store resource to. This ClientID
+   *        must be ID of cache in the cache group identified by
+   *        the manifest URI passed in the first parameter.
+   * @param aDocumentURI
+   *        The page that is requesting the update. May be null 
+   *        when this information is unknown.
+   */
+  void initPartial(in nsIURI aManifestURI, in ACString aClientID, in nsIURI aDocumentURI);
+
+  /**
+   * Add a dynamic URI to the offline cache as part of the update.
    *
    * @param aURI
    *        The URI to add.
    */
   void addDynamicURI(in nsIURI aURI);
 
   /**
    * Add the update to the offline update queue.  An offline-cache-update-added
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -41,27 +41,29 @@
 #include "nsCPrefetchService.h"
 #include "nsCURILoader.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsIApplicationCacheService.h"
 #include "nsICache.h"
 #include "nsICacheService.h"
 #include "nsICacheSession.h"
 #include "nsICachingChannel.h"
+#include "nsIDocumentLoader.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsIObserverService.h"
 #include "nsIURL.h"
 #include "nsIWebProgress.h"
 #include "nsICryptoHash.h"
 #include "nsICacheEntryDescriptor.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStreamUtils.h"
+#include "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):
@@ -572,16 +574,17 @@ nsOfflineManifestItem::HandleManifestLin
     nsresult rv;
 
     switch(mParserState) {
     case PARSE_INIT:
     case PARSE_ERROR: {
         // this should have been dealt with earlier
         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;
 
         nsCAutoString scheme;
         uri->GetScheme(scheme);
@@ -805,31 +808,30 @@ nsOfflineCacheUpdate::GetCacheKey(nsIURI
 
     rv = newURI->GetAsciiSpec(aKey);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
 nsresult
-nsOfflineCacheUpdate::Init(PRBool aPartialUpdate,
-                           nsIURI *aManifestURI,
+nsOfflineCacheUpdate::Init(nsIURI *aManifestURI,
                            nsIURI *aDocumentURI)
 {
     nsresult rv;
 
     // Make sure the service has been initialized
     nsOfflineCacheUpdateService* service =
         nsOfflineCacheUpdateService::EnsureService();
     if (!service)
         return NS_ERROR_FAILURE;
 
-    LOG(("nsOfflineCacheUpdate::Init [%p, %d]", this, aPartialUpdate));
+    LOG(("nsOfflineCacheUpdate::Init [%p]", this));
 
-    mPartialUpdate = aPartialUpdate;
+    mPartialUpdate = PR_FALSE;
 
     // Only http and https applications are supported.
     PRBool match;
     rv = aManifestURI->SchemeIs("http", &match);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!match) {
         rv = aManifestURI->SchemeIs("https", &match);
@@ -853,30 +855,76 @@ nsOfflineCacheUpdate::Init(PRBool aParti
     nsCOMPtr<nsIApplicationCacheService> cacheService =
         do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = cacheService->GetActiveCache(manifestSpec,
                                       getter_AddRefs(mPreviousApplicationCache));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Partial updates to existing application caches don't need a new cache.
-    if (aPartialUpdate && mPreviousApplicationCache) {
-        mApplicationCache = mPreviousApplicationCache;
-    } else {
-        rv = cacheService->CreateApplicationCache(manifestSpec,
-                                                  getter_AddRefs(mApplicationCache));
-        NS_ENSURE_SUCCESS(rv, rv);
-    }
+    rv = cacheService->CreateApplicationCache(manifestSpec,
+                                              getter_AddRefs(mApplicationCache));
+    NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mApplicationCache->GetClientID(mClientID);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mState = STATE_INITIALIZED;
+    return NS_OK;
+}
 
+nsresult
+nsOfflineCacheUpdate::InitPartial(nsIURI *aManifestURI,
+                                  const nsACString& clientID,
+                                  nsIURI *aDocumentURI)
+{
+    nsresult rv;
+
+    // Make sure the service has been initialized
+    nsOfflineCacheUpdateService* service =
+        nsOfflineCacheUpdateService::EnsureService();
+    if (!service)
+        return NS_ERROR_FAILURE;
+
+    LOG(("nsOfflineCacheUpdate::InitPartial [%p]", this));
+
+    mPartialUpdate = PR_TRUE;
+    mClientID = clientID;
+    mDocumentURI = aDocumentURI;
+
+    mManifestURI = aManifestURI;
+    rv = mManifestURI->GetAsciiHost(mUpdateDomain);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIApplicationCacheService> cacheService =
+        do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = cacheService->GetApplicationCache(mClientID,
+                                           getter_AddRefs(mApplicationCache));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!mApplicationCache) {
+        nsCAutoString manifestSpec;
+        rv = GetCacheKey(mManifestURI, manifestSpec);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = cacheService->CreateApplicationCache
+            (manifestSpec, getter_AddRefs(mApplicationCache));
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    nsCAutoString groupID;
+    rv = mApplicationCache->GetGroupID(groupID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = NS_NewURI(getter_AddRefs(mManifestURI), groupID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mState = STATE_INITIALIZED;
     return NS_OK;
 }
 
 nsresult
 nsOfflineCacheUpdate::HandleManifest(PRBool *aDoUpdate)
 {
     // Be pessimistic
     *aDoUpdate = PR_FALSE;
@@ -1605,16 +1653,17 @@ nsOfflineCacheUpdateService::Schedule(ns
 NS_IMETHODIMP
 nsOfflineCacheUpdateService::ScheduleOnDocumentStop(nsIURI *aManifestURI,
                                                     nsIURI *aDocumentURI,
                                                     nsIDOMDocument *aDocument)
 {
     LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, manifestURI=%p, documentURI=%p doc=%p]",
          this, aManifestURI, aDocumentURI, aDocument));
 
+    // Proceed with cache update
     PendingUpdate *update = new PendingUpdate();
     update->mManifestURI = aManifestURI;
     update->mDocumentURI = aDocumentURI;
     if (!mDocUpdates.Put(aDocument, update))
         return NS_ERROR_FAILURE;
 
     return NS_OK;
 }
@@ -1732,17 +1781,17 @@ nsOfflineCacheUpdateService::ScheduleUpd
     }
 
     // There is no existing update, start one.
 
     nsRefPtr<nsOfflineCacheUpdate> update = new nsOfflineCacheUpdate();
     if (!update)
         return NS_ERROR_OUT_OF_MEMORY;
 
-    rv = update->Init(PR_FALSE, aManifestURI, aDocumentURI);
+    rv = update->Init(aManifestURI, aDocumentURI);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = update->Schedule();
     NS_ENSURE_SUCCESS(rv, rv);
 
     NS_ADDREF(*aUpdate = update);
 
     return NS_OK;