Bug 751754 - Allow separation between the update-available and start-download states in appcache, r=jduell
authorHonza Bambas <honzab.moz@firemni.cz>
Tue, 02 Oct 2012 15:55:00 +0200
changeset 120170 17a6bcffcbebe40a14609c3c0b30c063b1ad444d
parent 120169 4b2d566ad1ac77c285b3a0af352211b0e81b5718
child 120171 113d5069e67e7bb8c12e5b5f1a36faadcb01c667
push id273
push userlsblakk@mozilla.com
push dateThu, 14 Feb 2013 23:19:38 +0000
treeherdermozilla-release@c5e807a3f8b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs751754
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 751754 - Allow separation between the update-available and start-download states in appcache, r=jduell
dom/tests/mochitest/ajax/offline/Makefile.in
dom/tests/mochitest/ajax/offline/test_updateCheck.html
netwerk/base/public/nsIApplicationCacheService.idl
netwerk/cache/nsApplicationCacheService.cpp
netwerk/cache/nsApplicationCacheService.h
netwerk/cache/nsDiskCacheDeviceSQL.cpp
netwerk/cache/nsDiskCacheDeviceSQL.h
netwerk/protocol/http/nsHttpChannel.cpp
uriloader/prefetch/OfflineCacheUpdateChild.cpp
uriloader/prefetch/OfflineCacheUpdateGlue.h
uriloader/prefetch/nsIOfflineCacheUpdate.idl
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
uriloader/prefetch/nsOfflineCacheUpdateService.cpp
--- a/dom/tests/mochitest/ajax/offline/Makefile.in
+++ b/dom/tests/mochitest/ajax/offline/Makefile.in
@@ -42,16 +42,17 @@ MOCHITEST_FILES	= \
 	test_foreign.html \
 	test_fallback.html \
 	test_overlap.html \
 	test_redirectManifest.html \
 	test_redirectUpdateItem.html \
 	overlap.cacheManifest \
 	overlap.cacheManifest^headers^ \
 	test_updatingManifest.html \
+	test_updateCheck.html \
 	445544_part1.html \
 	445544_part2.html \
 	445544.cacheManifest \
 	445544.cacheManifest^headers^ \
 	460353_iframe_nomanifest.html \
 	460353_iframe_ownmanifest.html \
 	460353_iframe_samemanifest.html \
 	test_obsolete.html \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/test_updateCheck.html
@@ -0,0 +1,86 @@
+<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/updatingManifest.sjs">
+<head>
+<title>Cache update test</title>
+
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * The test is checking nsIOfflineCacheUpdateService.checkForUpdate API:
+ * - cache a manifest
+ * - check for an update of it, expected is "no update avail"
+ * - modify the manifest on the server
+ * - check for an update again, expected is "update avail"
+ * - check for an update ones again, expected is "update avail" (secondary check to probe
+ *   we didn't screw state of the manifest in the current cache with the first check)
+ * - cache the modified manifest, new version is now in the cache
+ * - last check for an update, expected is "no update avail" again
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var manifest = "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/updatingManifest.sjs";
+var manifestURI = Cc["@mozilla.org/network/io-service;1"]
+                  .getService(Ci.nsIIOService)
+                  .newURI(manifest, null, null);
+var updateService = Cc['@mozilla.org/offlinecacheupdate-service;1']
+                    .getService(Ci.nsIOfflineCacheUpdateService);
+
+function manifestCached()
+{
+  // Run first check for an update
+  updateService.checkForUpdate(manifestURI, 0, false, {
+    observe: function(subject, topic, data) {
+      OfflineTest.is(topic, "offline-cache-update-unavailable", "No update avail");
+
+      // Change the manifest content
+      OfflineTest.setSJSState(manifest, "second");
+
+      // Check we now get notification on update ready
+      updateService.checkForUpdate(manifestURI, 0, false, {
+        observe: function(subject, topic, data) {
+          OfflineTest.is(topic, "offline-cache-update-available", "Update avail (1)");
+
+          // Do the check again.  We must get the same result.  Double check is here
+          // to make sure we don't overwrite any data in the cache by the check it self.
+          updateService.checkForUpdate(manifestURI, 0, false, {
+            observe: function(subject, topic, data) {
+              OfflineTest.is(topic, "offline-cache-update-available", "Update avail (2)");
+
+              // Update the manifest, invokes manifestUpdated()
+              applicationCache.onupdateready = OfflineTest.priv(manifestUpdated);
+              applicationCache.update();
+            }
+          });
+        }
+      });
+    }
+  });
+}
+
+function manifestUpdated()
+{
+  // Check for an update after manifest has been updated
+  updateService.checkForUpdate(manifestURI, 0, false, {
+    observe: function(subject, topic, data) {
+      OfflineTest.is(topic, "offline-cache-update-unavailable", "No update avail (2)");
+
+      OfflineTest.teardown();
+      OfflineTest.finish();
+    }
+  });
+}
+
+if (OfflineTest.setup()) {
+  applicationCache.onerror = OfflineTest.failEvent;
+  applicationCache.oncached = OfflineTest.priv(manifestCached);
+}
+
+</script>
+</head>
+<body>
+</body>
+</html>
--- a/netwerk/base/public/nsIApplicationCacheService.idl
+++ b/netwerk/base/public/nsIApplicationCacheService.idl
@@ -10,27 +10,34 @@ interface nsIApplicationCache;
 interface nsIFile;
 interface nsIURI;
 interface nsILoadContext;
 
 /**
  * The application cache service manages the set of application cache
  * groups.
  */
-[scriptable, uuid(1750F671-0170-4d3f-A836-455501141E32)]
+[scriptable, uuid(9b5b2cde-d5dd-48d3-87f8-8e8b776952a8)]
 interface nsIApplicationCacheService : nsISupports
 {
     /**
      * Create group string identifying cache group according the manifest
      * URL and the given load context.
      */
     ACString buildGroupID(in nsIURI aManifestURL,
                           in nsILoadContext aLoadContext);
 
     /**
+     * Same as buildGroupID method, just doesn't require load context.
+     */
+    ACString buildGroupIDForApp(in nsIURI aManifestURL,
+                                in unsigned long aAppID,
+                                in boolean aInBrowser);
+
+    /**
      * Create a new, empty application cache for the given cache
      * group.
      */
     nsIApplicationCache createApplicationCache(in ACString group);
 
     /**
      * Create a new, empty application cache for the given cache
      * group residing in a custom directory with a custom quota.
--- a/netwerk/cache/nsApplicationCacheService.cpp
+++ b/netwerk/cache/nsApplicationCacheService.cpp
@@ -26,18 +26,44 @@ nsApplicationCacheService::nsApplication
     mCacheService = nsCacheService::GlobalInstance();
 }
 
 NS_IMETHODIMP
 nsApplicationCacheService::BuildGroupID(nsIURI *aManifestURL,
                                         nsILoadContext *aLoadContext,
                                         nsACString &_result)
 {
+    nsresult rv;
+
+    uint32_t appId = NECKO_NO_APP_ID;
+    bool isInBrowserElement = false;
+
+    if (aLoadContext) {
+        rv = aLoadContext->GetAppId(&appId);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = aLoadContext->GetIsInBrowserElement(&isInBrowserElement);
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    rv = nsOfflineCacheDevice::BuildApplicationCacheGroupID(
+        aManifestURL, appId, isInBrowserElement, _result);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::BuildGroupIDForApp(nsIURI *aManifestURL,
+                                              uint32_t aAppId,
+                                              bool aIsInBrowser,
+                                              nsACString &_result)
+{
     nsresult rv = nsOfflineCacheDevice::BuildApplicationCacheGroupID(
-        aManifestURL, aLoadContext, _result);
+        aManifestURL, aAppId, aIsInBrowser, _result);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsApplicationCacheService::CreateApplicationCache(const nsACString &group,
                                                   nsIApplicationCache **out)
--- a/netwerk/cache/nsApplicationCacheService.h
+++ b/netwerk/cache/nsApplicationCacheService.h
@@ -16,16 +16,12 @@ public:
     nsApplicationCacheService();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIAPPLICATIONCACHESERVICE
 
     static void AppClearDataObserverInit();
 
 private:
-    nsresult GetJARIdentifier(nsIURI *aURI,
-                              nsILoadContext *aLoadContext,
-                              nsACString &_result);
-
     nsRefPtr<nsCacheService> mCacheService;
 };
 
 #endif // _nsApplicationCacheService_h_
--- a/netwerk/cache/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -1254,63 +1254,52 @@ AppendJARIdentifier(nsACString &_result,
     _result.Append('+');
     _result.Append(isInBrowserElement ? 't' : 'f');
 
     return NS_OK;
 }
 
 nsresult
 GetJARIdentifier(nsIURI *aURI,
-                 nsILoadContext *aLoadContext,
+                 uint32_t appId, bool isInBrowserElement,
                  nsACString &_result)
 {
     _result.Truncate();
 
-    if (!aLoadContext)
-        return NS_OK;
-
     // These lines are here for compatibility only.  We must not fill the
     // JAR identifier when this is no-app context, otherwise web content
     // offline application cache loads would not be satisfied (cache would
     // not be found).
-    bool isInBrowserElement;
-    nsresult rv = aLoadContext->GetIsInBrowserElement(&isInBrowserElement);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    uint32_t appId;
-    rv = aLoadContext->GetAppId(&appId);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     if (!isInBrowserElement && appId == NECKO_NO_APP_ID)
         return NS_OK;
 
     // This load context has some special attributes, create a jar identifier
     return AppendJARIdentifier(_result, appId, isInBrowserElement);
 }
 
 } // anon namespace
 
 // static
 nsresult
 nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI *aManifestURL,
-                                                   nsILoadContext *aLoadContext,
+                                                   uint32_t appId, bool isInBrowserElement,
                                                    nsACString &_result)
 {
   nsCOMPtr<nsIURI> newURI;
   nsresult rv = aManifestURL->CloneIgnoringRef(getter_AddRefs(newURI));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString manifestSpec;
   rv = newURI->GetAsciiSpec(manifestSpec);
   NS_ENSURE_SUCCESS(rv, rv);
 
   _result.Assign(manifestSpec);
 
   nsAutoCString jarid;
-  rv = GetJARIdentifier(aManifestURL, aLoadContext, jarid);
+  rv = GetJARIdentifier(aManifestURL, appId, isInBrowserElement, jarid);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Include JAR ID, i.e. the extended origin if present.
   if (!jarid.IsEmpty())
     _result.Append(jarid);
 
   return NS_OK;
 }
@@ -2437,21 +2426,34 @@ nsOfflineCacheDevice::CanUseCache(nsIURI
   // 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 false;
 
+  // Get extended origin attributes
+  uint32_t appId = NECKO_NO_APP_ID;
+  bool isInBrowserElement = false;
+
+  if (loadContext) {
+      rv = loadContext->GetAppId(&appId);
+      NS_ENSURE_SUCCESS(rv, false);
+
+      rv = loadContext->GetIsInBrowserElement(&isInBrowserElement);
+      NS_ENSURE_SUCCESS(rv, false);
+  }
+
   // Check the groupID we found is equal to groupID based
   // on the load context demanding load from app cache.
   // This is check of extended origin.
   nsAutoCString demandedGroupID;
-  rv = BuildApplicationCacheGroupID(groupURI, loadContext, demandedGroupID);
+  rv = BuildApplicationCacheGroupID(groupURI, appId, isInBrowserElement,
+                                    demandedGroupID);
   NS_ENSURE_SUCCESS(rv, false);
 
   if (groupID != demandedGroupID)
     return false;
 
   return true;
 }
 
--- a/netwerk/cache/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -128,17 +128,17 @@ public:
                                      const nsACString &       key,
                                      bool *                 isOwned);
 
   nsresult                ClearKeysOwnedByDomain(const char *clientID,
                                                  const nsACString &ownerDomain);
   nsresult                EvictUnownedEntries(const char *clientID);
 
   static nsresult         BuildApplicationCacheGroupID(nsIURI *aManifestURL,
-                                                       nsILoadContext *aLoadContext,
+                                                       uint32_t appId, bool isInBrowserElement,
                                                        nsACString &_result);
 
   nsresult                ActivateCache(const nsCSubstring &group,
                                         const nsCSubstring &clientID);
   bool                    IsActiveCache(const nsCSubstring &group,
                                         const nsCSubstring &clientID);
   nsresult                CreateApplicationCache(const nsACString &group,
                                                  nsIApplicationCache **out);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2535,16 +2535,21 @@ nsHttpChannel::OpenOfflineCacheEntryForW
     NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open");
 
     bool offline = gIOService->IsOffline();
     if (offline) {
         // only put things in the offline cache while online
         return NS_OK;
     }
 
+    if (mLoadFlags & INHIBIT_CACHING) {
+        // respect demand not to cache
+        return NS_OK;
+    }
+
     if (mRequestHead.Method() != nsHttp::Get) {
         // only cache complete documents offline
         return NS_OK;
     }
 
     // Don't cache byte range requests which are subranges, only cache 0-
     // byte range requests.
     if (IsSubRangeRequest(mRequestHead))
--- a/uriloader/prefetch/OfflineCacheUpdateChild.cpp
+++ b/uriloader/prefetch/OfflineCacheUpdateChild.cpp
@@ -236,16 +236,27 @@ OfflineCacheUpdateChild::InitPartial(nsI
 {
     NS_NOTREACHED("Not expected to do partial offline cache updates"
                   " on the child process");
     // For now leaving this method, we may discover we need it.
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
+OfflineCacheUpdateChild::InitForUpdateCheck(nsIURI *aManifestURI,
+                                            uint32_t aAppID,
+                                            bool aInBrowser,
+                                            nsIObserver *aObserver)
+{
+    NS_NOTREACHED("Not expected to do only update checks"
+                  " from the child process");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 OfflineCacheUpdateChild::GetUpdateDomain(nsACString &aUpdateDomain)
 {
     NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
 
     aUpdateDomain = mUpdateDomain;
     return NS_OK;
 }
 
--- a/uriloader/prefetch/OfflineCacheUpdateGlue.h
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.h
@@ -25,16 +25,17 @@ namespace docshell {
 #define NS_ADJUSTED_FORWARD_NSIOFFLINECACHEUPDATE(_to) \
   NS_IMETHOD GetStatus(uint16_t *aStatus) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetStatus(aStatus); } \
   NS_IMETHOD GetPartial(bool *aPartial) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetPartial(aPartial); } \
   NS_IMETHOD GetIsUpgrade(bool *aIsUpgrade) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetIsUpgrade(aIsUpgrade); } \
   NS_IMETHOD GetUpdateDomain(nsACString & aUpdateDomain) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetUpdateDomain(aUpdateDomain); } \
   NS_IMETHOD GetManifestURI(nsIURI **aManifestURI) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetManifestURI(aManifestURI); } \
   NS_IMETHOD GetSucceeded(bool *aSucceeded) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetSucceeded(aSucceeded); } \
   NS_IMETHOD InitPartial(nsIURI *aManifestURI, const nsACString & aClientID, nsIURI *aDocumentURI) { return !_to ? NS_ERROR_NULL_POINTER : _to->InitPartial(aManifestURI, aClientID, aDocumentURI); } \
+  NS_IMETHOD InitForUpdateCheck(nsIURI *aManifestURI, uint32_t aAppID, bool aInBrowser, nsIObserver *aObserver) { return !_to ? NS_ERROR_NULL_POINTER : _to->InitForUpdateCheck(aManifestURI, aAppID, aInBrowser, aObserver); } \
   NS_IMETHOD AddDynamicURI(nsIURI *aURI) { return !_to ? NS_ERROR_NULL_POINTER : _to->AddDynamicURI(aURI); } \
   NS_IMETHOD AddObserver(nsIOfflineCacheUpdateObserver *aObserver, bool aHoldWeak) { return !_to ? NS_ERROR_NULL_POINTER : _to->AddObserver(aObserver, aHoldWeak); } \
   NS_IMETHOD RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver) { return !_to ? NS_ERROR_NULL_POINTER : _to->RemoveObserver(aObserver); } \
   NS_IMETHOD GetByteProgress(uint64_t * _result) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetByteProgress(_result); }
 
 class OfflineCacheUpdateGlue MOZ_FINAL : public nsSupportsWeakReference
                                        , public nsIOfflineCacheUpdate
                                        , public nsIOfflineCacheUpdateObserver
--- a/uriloader/prefetch/nsIOfflineCacheUpdate.idl
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -11,16 +11,17 @@ interface nsIDOMNode;
 interface nsIDOMDocument;
 interface nsIDOMLoadStatus;
 interface nsIOfflineCacheUpdate;
 interface nsIPrincipal;
 interface nsIPrefBranch;
 interface nsIApplicationCache;
 interface nsIFile;
 interface nsILoadContext;
+interface nsIObserver;
 
 [scriptable, uuid(47360d57-8ef4-4a5d-8865-1a27a739ad1a)]
 interface nsIOfflineCacheUpdateObserver : nsISupports {
   const unsigned long STATE_ERROR = 1;
   const unsigned long STATE_CHECKING = 2;
   const unsigned long STATE_NOUPDATE = 3;
   const unsigned long STATE_OBSOLETE = 4;
   const unsigned long STATE_DOWNLOADING = 5;
@@ -58,17 +59,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(D47966B2-1EBC-45c5-8639-2937ED200281)]
+[scriptable, uuid(91d356fa-4eaf-4de2-9870-bb92854cccfe)]
 interface nsIOfflineCacheUpdate : nsISupports {
   /**
    * Fetch the status of the running update.  This will return a value
    * defined in nsIDOMOfflineResourceList.
    */
   readonly attribute unsigned short status;
 
   /**
@@ -122,16 +123,39 @@ interface nsIOfflineCacheUpdate : nsISup
    *        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);
 
   /**
+   * Initialize the update to only check whether there is an update
+   * to the manifest available (if it has actually changed on the server).
+   *
+   * @param aManifestURI
+   *        The manifest URI of the related cache.
+   * @param aAppID
+   *        Local ID of an app (optional) to check the cache update for.
+   * @param aInBrowser
+   *        Whether to check for a cache populated from browser element.
+   * @param aObserver
+   *        nsIObserver implementation that receives the result.
+   *        When aTopic == "offline-cache-update-available" there is an update to
+   *        to download. Update of the app cache will lead to a new version
+   *        download.
+   *        When aTopic == "offline-cache-update-unavailable" then there is no
+   *        update available (the manifest has not changed on the server).
+   */
+  void initForUpdateCheck(in nsIURI aManifestURI,
+                          in unsigned long aAppID,
+                          in boolean aInBrowser,
+                          in nsIObserver aObserver);
+
+  /**
    * Add a dynamic URI to the offline cache as part of the update.
    *
    * @param aURI
    *        The URI to add.
    */
   void addDynamicURI(in nsIURI aURI);
 
   /**
@@ -161,17 +185,17 @@ interface nsIOfflineCacheUpdate : nsISup
   void removeObserver(in nsIOfflineCacheUpdateObserver aObserver);
 
   /**
    * Return the number of bytes downloaded so far
    */
   readonly attribute uint64_t byteProgress;
 };
 
-[scriptable, uuid(dc5de18c-197c-41d2-9584-dd7ac7494611)]
+[scriptable, uuid(9ff7f81f-114a-4876-ad1b-f3910418e4a6)]
 interface nsIOfflineCacheUpdateService : nsISupports {
     /**
      * Constants for the offline-app permission.
      *
      * XXX: This isn't a great place for this, but it's really the only
      * private offline-app-related interface
      */
 
@@ -209,16 +233,32 @@ interface nsIOfflineCacheUpdateService :
      * Schedule a cache update for a manifest when the document finishes
      * loading.
      */
     void scheduleOnDocumentStop(in nsIURI aManifestURI,
                                 in nsIURI aDocumentURI,
                                 in nsIDOMDocument aDocument);
 
     /**
+     * Schedule a check to see if an update is available.
+     *
+     * This will not update or make any changes to the appcache.
+     * It only notifies the observer to indicate whether the manifest has
+     * changed on the server (or not): a changed manifest means that an
+     * update is available.
+     *
+     * For arguments see nsIOfflineCacheUpdate.initForUpdateCheck() method
+     * description.
+     */
+    void checkForUpdate(in nsIURI aManifestURI,
+                        in unsigned long aAppID,
+                        in boolean aInBrowser,
+                        in nsIObserver aObserver);
+
+    /**
      * Checks whether a principal should have access to the offline
      * cache.
      * @param aPrincipal
      *        The principal to check.
      * @param aPrefBranch
      *        The pref branch to use to check the
      *        offline-apps.allow_by_default pref.  If not specified,
      *        the pref service will be used.
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -327,35 +327,44 @@ nsOfflineCacheUpdateItem::OpenChannel(ns
         // and its Run() call.  We must never open channel on this item again.
         LOG(("  %p is already running! ignoring", this));
         return NS_ERROR_ALREADY_OPENED;
     }
 
     nsresult rv = nsOfflineCacheUpdate::GetCacheKey(mURI, mCacheKey);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    uint32_t flags = nsIRequest::LOAD_BACKGROUND |
+                     nsICachingChannel::LOAD_ONLY_IF_MODIFIED |
+                     nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;
+
+    if (mApplicationCache == mPreviousApplicationCache) {
+        // Same app cache to read from and to write to is used during
+        // an only-update-check procedure.  Here we protect the existing
+        // cache from being modified.
+        flags |= nsIRequest::INHIBIT_CACHING;
+    }
+
     rv = NS_NewChannel(getter_AddRefs(mChannel),
                        mURI,
                        nullptr, nullptr, this,
-                       nsIRequest::LOAD_BACKGROUND |
-                       nsICachingChannel::LOAD_ONLY_IF_MODIFIED |
-                       nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE);
+                       flags);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
         do_QueryInterface(mChannel, &rv);
 
     // Support for nsIApplicationCacheChannel is required.
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Use the existing application cache as the cache to check.
     rv = appCacheChannel->SetApplicationCache(mPreviousApplicationCache);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Set the new application cache as the targer for write.
+    // Set the new application cache as the target for write.
     rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // configure HTTP specific stuff
     nsCOMPtr<nsIHttpChannel> httpChannel =
         do_QueryInterface(mChannel);
     if (httpChannel) {
         httpChannel->SetReferrer(mReferrerURI);
@@ -1154,16 +1163,17 @@ NS_IMPL_ISUPPORTS3(nsOfflineCacheUpdate,
 // nsOfflineCacheUpdate <public>
 //-----------------------------------------------------------------------------
 
 nsOfflineCacheUpdate::nsOfflineCacheUpdate()
     : mState(STATE_UNINITIALIZED)
     , mOwner(nullptr)
     , mAddedItems(false)
     , mPartialUpdate(false)
+    , mOnlyCheckUpdate(false)
     , mSucceeded(true)
     , mObsolete(false)
     , mItemsInProgress(0)
     , mRescheduleCount(0)
     , mPinnedEntryRetriesCount(0)
     , mPinned(false)
 {
 }
@@ -1185,34 +1195,20 @@ nsOfflineCacheUpdate::GetCacheKey(nsIURI
 
     rv = newURI->GetAsciiSpec(aKey);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
 nsresult
-nsOfflineCacheUpdate::Init(nsIURI *aManifestURI,
-                           nsIURI *aDocumentURI,
-                           nsIDOMDocument *aDocument,
-                           nsIFile *aCustomProfileDir,
-                           nsILoadContext *aLoadContext)
+nsOfflineCacheUpdate::InitInternal(nsIURI *aManifestURI)
 {
     nsresult rv;
 
-    // Make sure the service has been initialized
-    nsOfflineCacheUpdateService* service =
-        nsOfflineCacheUpdateService::EnsureService();
-    if (!service)
-        return NS_ERROR_FAILURE;
-
-    LOG(("nsOfflineCacheUpdate::Init [%p]", this));
-
-    mPartialUpdate = false;
-
     // Only http and https applications are supported.
     bool match;
     rv = aManifestURI->SchemeIs("http", &match);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!match) {
         rv = aManifestURI->SchemeIs("https", &match);
         NS_ENSURE_SUCCESS(rv, rv);
@@ -1220,16 +1216,41 @@ nsOfflineCacheUpdate::Init(nsIURI *aMani
             return NS_ERROR_ABORT;
     }
 
     mManifestURI = aManifestURI;
 
     rv = mManifestURI->GetAsciiHost(mUpdateDomain);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    mPartialUpdate = false;
+
+    return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::Init(nsIURI *aManifestURI,
+                           nsIURI *aDocumentURI,
+                           nsIDOMDocument *aDocument,
+                           nsIFile *aCustomProfileDir,
+                           nsILoadContext *aLoadContext)
+{
+    nsresult rv;
+
+    // Make sure the service has been initialized
+    nsOfflineCacheUpdateService* service =
+        nsOfflineCacheUpdateService::EnsureService();
+    if (!service)
+        return NS_ERROR_FAILURE;
+
+    LOG(("nsOfflineCacheUpdate::Init [%p]", this));
+
+    rv = InitInternal(aManifestURI);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     nsCOMPtr<nsIApplicationCacheService> cacheService =
         do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mDocumentURI = aDocumentURI;
 
     if (aCustomProfileDir) {
         rv = GetCacheKey(aManifestURI, mGroupID);
@@ -1273,16 +1294,68 @@ nsOfflineCacheUpdate::Init(nsIURI *aMani
 
     mLoadContext = aLoadContext;
 
     mState = STATE_INITIALIZED;
     return NS_OK;
 }
 
 nsresult
+nsOfflineCacheUpdate::InitForUpdateCheck(nsIURI *aManifestURI,
+                                         uint32_t aAppID,
+                                         bool aInBrowser,
+                                         nsIObserver *aObserver)
+{
+    nsresult rv;
+
+    // Make sure the service has been initialized
+    nsOfflineCacheUpdateService* service =
+        nsOfflineCacheUpdateService::EnsureService();
+    if (!service)
+        return NS_ERROR_FAILURE;
+
+    LOG(("nsOfflineCacheUpdate::InitForUpdateCheck [%p]", this));
+
+    rv = InitInternal(aManifestURI);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIApplicationCacheService> cacheService =
+        do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = cacheService->BuildGroupIDForApp(aManifestURI,
+                                          aAppID, aInBrowser,
+                                          mGroupID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = cacheService->GetActiveCache(mGroupID,
+                                      getter_AddRefs(mPreviousApplicationCache));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // To load the manifest properly using current app cache to satisfy and
+    // also to compare the cached content hash value we have to set 'some'
+    // app cache to write to on the channel.  Otherwise the cached version will
+    // be used and no actual network request will be made.  We use the same
+    // app cache here.  OpenChannel prevents caching in this case using
+    // INHIBIT_CACHING load flag.
+    mApplicationCache = mPreviousApplicationCache;
+
+    rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aManifestURI,
+                                                             NULL,
+                                                             &mPinned);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mUpdateAvailableObserver = aObserver;
+    mOnlyCheckUpdate = true;
+
+    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 =
@@ -1383,16 +1456,45 @@ nsOfflineCacheUpdate::HandleManifest(boo
                           &mManifestItem->GetOpportunisticNamespaces());
     NS_ENSURE_SUCCESS(rv, rv);
 
     *aDoUpdate = true;
 
     return NS_OK;
 }
 
+bool
+nsOfflineCacheUpdate::CheckUpdateAvailability()
+{
+    nsresult rv;
+
+    bool succeeded;
+    rv = mManifestItem->GetRequestSucceeded(&succeeded);
+    NS_ENSURE_SUCCESS(rv, false);
+
+    if (!succeeded || !mManifestItem->ParseSucceeded()) {
+        return false;
+    }
+
+    if (!mPinned) {
+        uint16_t status;
+        rv = mManifestItem->GetStatus(&status);
+        NS_ENSURE_SUCCESS(rv, false);
+
+        // Treat these as there would be an update available,
+        // since this is indication of demand to remove this
+        // offline cache.
+        if (status == 404 || status == 410) {
+            return true;
+        }
+    }
+
+    return mManifestItem->NeedsUpdate();
+}
+
 void
 nsOfflineCacheUpdate::LoadCompleted(nsOfflineCacheUpdateItem *aItem)
 {
     nsresult rv;
 
     LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this));
 
     if (mState == STATE_FINISHED) {
@@ -1406,16 +1508,22 @@ nsOfflineCacheUpdate::LoadCompleted(nsOf
     if (mState == STATE_CANCELLED) {
         Finish();
         return;
     }
 
     if (mState == STATE_CHECKING) {
         // Manifest load finished.
 
+        if (mOnlyCheckUpdate) {
+            Finish();
+            NotifyUpdateAvailability(CheckUpdateAvailability());
+            return;
+        }
+
         NS_ASSERTION(mManifestItem,
                      "Must have a manifest item in STATE_CHECKING.");
         NS_ASSERTION(mManifestItem == aItem,
                      "Unexpected aItem in nsOfflineCacheUpdate::LoadCompleted");
 
         // A 404 or 410 is interpreted as an intentional removal of
         // the manifest file, rather than a transient server error.
         // Obsolete this cache group if one of these is returned.
@@ -1827,16 +1935,34 @@ nsOfflineCacheUpdate::NotifyState(uint32
     GatherObservers(observers);
 
     for (int32_t i = 0; i < observers.Count(); i++) {
         observers[i]->UpdateStateChanged(this, state);
     }
 }
 
 void
+nsOfflineCacheUpdate::NotifyUpdateAvailability(bool updateAvailable)
+{
+    if (!mUpdateAvailableObserver)
+        return;
+
+    LOG(("nsOfflineCacheUpdate::NotifyUpdateAvailability [this=%p, avail=%d]",
+         this, updateAvailable));
+
+    const char* topic = updateAvailable
+                      ? "offline-cache-update-available"
+                      : "offline-cache-update-unavailable";
+
+    nsCOMPtr<nsIObserver> observer;
+    observer.swap(mUpdateAvailableObserver);
+    observer->Observe(mManifestURI, topic, nullptr);
+}
+
+void
 nsOfflineCacheUpdate::AssociateDocuments(nsIApplicationCache* cache)
 {
     nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
     GatherObservers(observers);
 
     for (int32_t i = 0; i < observers.Count(); i++) {
         observers[i]->ApplicationCacheAvailable(cache);
     }
@@ -1929,17 +2055,17 @@ nsOfflineCacheUpdate::ScheduleImplicit()
 
 nsresult
 nsOfflineCacheUpdate::FinishNoNotify()
 {
     LOG(("nsOfflineCacheUpdate::Finish [%p]", this));
 
     mState = STATE_FINISHED;
 
-    if (!mPartialUpdate) {
+    if (!mPartialUpdate && !mOnlyCheckUpdate) {
         if (mSucceeded) {
             nsIArray *namespaces = mManifestItem->GetNamespaces();
             nsresult rv = mApplicationCache->AddNamespaces(namespaces);
             if (NS_FAILED(rv)) {
                 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
                 mSucceeded = false;
             }
 
@@ -2075,17 +2201,17 @@ nsOfflineCacheUpdate::GetStatus(uint16_t
     }
 
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdate::GetPartial(bool *aPartial)
 {
-    *aPartial = mPartialUpdate;
+    *aPartial = mPartialUpdate || mOnlyCheckUpdate;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdate::GetManifestURI(nsIURI **aManifestURI)
 {
     NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
 
--- a/uriloader/prefetch/nsOfflineCacheUpdate.h
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -213,28 +213,31 @@ public:
 
     virtual nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate);
 
 protected:
     friend class nsOfflineCacheUpdateItem;
     void OnByteProgress(uint64_t byteIncrement);
 
 private:
+    nsresult InitInternal(nsIURI *aManifestURI);
     nsresult HandleManifest(bool *aDoUpdate);
     nsresult AddURI(nsIURI *aURI, uint32_t aItemType);
 
     nsresult ProcessNextURI();
 
     // 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(uint32_t aType,
                               nsTArray<nsCString>* namespaceFilter = nullptr);
     nsresult ScheduleImplicit();
     void AssociateDocuments(nsIApplicationCache* cache);
+    bool CheckUpdateAvailability();
+    void NotifyUpdateAvailability(bool updateAvailable);
 
     void GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers);
     void NotifyState(uint32_t state);
     nsresult Finish();
     nsresult FinishNoNotify();
 
     // Find one non-pinned cache group and evict it.
     nsresult EvictOneNonPinned();
@@ -247,25 +250,27 @@ private:
         STATE_CANCELLED,
         STATE_FINISHED
     } mState;
 
     nsOfflineCacheUpdateOwner *mOwner;
 
     bool mAddedItems;
     bool mPartialUpdate;
+    bool mOnlyCheckUpdate;
     bool mSucceeded;
     bool mObsolete;
 
     nsCString mUpdateDomain;
     nsCString mGroupID;
     nsCOMPtr<nsIURI> mManifestURI;
     nsCOMPtr<nsIURI> mDocumentURI;
     nsCOMPtr<nsIFile> mCustomProfileDir;
     nsCOMPtr<nsILoadContext> mLoadContext;
+    nsCOMPtr<nsIObserver> mUpdateAvailableObserver;
 
     nsCOMPtr<nsIApplicationCache> mApplicationCache;
     nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
 
     nsCOMPtr<nsIObserverService> mObserverService;
 
     nsRefPtr<nsOfflineManifestItem> mManifestItem;
 
--- a/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
@@ -494,16 +494,39 @@ nsOfflineCacheUpdateService::ScheduleCus
                                                          nsIOfflineCacheUpdate **aUpdate)
 {
     // The profile directory is mandatory
     NS_ENSURE_ARG(aProfileDir);
 
     return Schedule(aManifestURI, aDocumentURI, nullptr, nullptr, aProfileDir, aUpdate);
 }
 
+NS_IMETHODIMP nsOfflineCacheUpdateService::CheckForUpdate(nsIURI *aManifestURI,
+                                                          uint32_t aAppID,
+                                                          bool aInBrowser,
+                                                          nsIObserver *aObserver)
+{
+    if (GeckoProcessType_Default != XRE_GetProcessType()) {
+        // Not intended to support this on child processes
+        return NS_ERROR_NOT_IMPLEMENTED;
+    }
+
+    nsCOMPtr<nsIOfflineCacheUpdate> update = new OfflineCacheUpdateGlue();
+
+    nsresult rv;
+
+    rv = update->InitForUpdateCheck(aManifestURI, aAppID, aInBrowser, aObserver);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = update->Schedule();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdateService::nsIObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsOfflineCacheUpdateService::Observe(nsISupports     *aSubject,
                                      const char      *aTopic,
                                      const PRUnichar *aData)