Bug 674728 - Part 1: Pinned apps get higher priority for cache. r=honzab
authorSinker Li <thinker@codemud.net>
Fri, 30 Mar 2012 20:52:06 -0400
changeset 90760 cdd005fde96d6649e4b7779a344f1b9cccda8e27
parent 90759 324368cce885f9a9fef478d1cdd8218e35ddf34b
child 90761 dd0291644c67b6e7356f5e8fff6b3ce35d3db0e8
push idunknown
push userunknown
push dateunknown
reviewershonzab
bugs674728
milestone14.0a1
Bug 674728 - Part 1: Pinned apps get higher priority for cache. r=honzab
netwerk/base/public/nsIApplicationCacheService.idl
netwerk/cache/nsDiskCacheDeviceSQL.cpp
netwerk/cache/nsDiskCacheDeviceSQL.h
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
uriloader/prefetch/nsOfflineCacheUpdateService.cpp
--- 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(611161c8-37d0-450f-a4fe-457c47bbaf64)]
+[scriptable, uuid(10fdea21-1224-4c29-8507-8f3205a121d5)]
 interface nsIApplicationCacheService : nsISupports
 {
     /**
      * Create a new, empty application cache for the given cache
      * group.
      */
     nsIApplicationCache createApplicationCache(in ACString group);
 
@@ -88,9 +88,16 @@ interface nsIApplicationCacheService : n
      */
     void cacheOpportunistically(in nsIApplicationCache cache, in ACString key);
 
     /**
      * Get the list of application cache groups.
      */
     void getGroups([optional] out unsigned long count,
                    [array, size_is(count), retval] out string groupIDs);
+
+    /**
+     * Get the list of application cache groups in the order of
+     * activating time.
+     */
+    void getGroupsTimeOrdered([optional] out unsigned long count,
+                              [array, size_is(count), retval] out string groupIDs);
 };
--- a/netwerk/cache/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -1167,17 +1167,18 @@ nsOfflineCacheDevice::Init()
                                                      "  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 NameSpace, Data, ItemType FROM moz_cache_namespaces"
                                                      " WHERE ClientID = ?1"
                                                      " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
                                                      " ORDER BY NameSpace DESC;"),
     StatementSql ( mStatement_InsertNamespaceEntry,  "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
-    StatementSql ( mStatement_EnumerateGroups,       "SELECT GroupID, ActiveClientID FROM moz_cache_groups;")
+    StatementSql ( mStatement_EnumerateGroups,       "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
+    StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
   };
   for (PRUint32 i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(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);
@@ -1298,16 +1299,17 @@ nsOfflineCacheDevice::Shutdown()
   mStatement_InsertNamespaceEntry = nsnull;
   mStatement_CleanupUnmarked = nsnull;
   mStatement_GatherEntries = nsnull;
   mStatement_ActivateClient = nsnull;
   mStatement_DeactivateGroup = nsnull;
   mStatement_FindClient = nsnull;
   mStatement_FindClientByNamespace = nsnull;
   mStatement_EnumerateGroups = nsnull;
+  mStatement_EnumerateGroupsTimeOrder = nsnull;
   }
 
   // Close Database on the correct thread
   bool isOnCurrentThread = true;
   if (mInitThread)
     mInitThread->IsOnCurrentThread(&isOnCurrentThread);
 
   if (!isOnCurrentThread) {
@@ -1747,27 +1749,47 @@ nsOfflineCacheDevice::EvictEntries(const
   if (clientID)
   {
     rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=? AND Flags = 0;"),
                               getter_AddRefs(statement));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
     NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = statement->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
+                              getter_AddRefs(statement));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = statement->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
   }
   else
   {
     rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE Flags = 0;"),
                               getter_AddRefs(statement));
     NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = statement->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
+                              getter_AddRefs(statement));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = statement->Execute();
+    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));
@@ -2035,20 +2057,29 @@ nsOfflineCacheDevice::GetUsage(const nsA
 
 NS_IMETHODIMP
 nsOfflineCacheDevice::GetGroups(PRUint32 *count,
                                  char ***keys)
 {
 
   LOG(("nsOfflineCacheDevice::GetGroups"));
 
-  AutoResetStatement statement(mStatement_EnumerateGroups);
   return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
 }
 
+NS_IMETHODIMP
+nsOfflineCacheDevice::GetGroupsTimeOrdered(PRUint32 *count,
+					   char ***keys)
+{
+
+  LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
+
+  return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, keys);
+}
+
 nsresult
 nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
                                      PRUint32 resultIndex,
                                      PRUint32 * count,
                                      char *** values)
 {
   bool hasRows;
   nsresult rv = statement->ExecuteStep(&hasRows);
--- a/netwerk/cache/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -258,16 +258,17 @@ private:
   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<mozIStorageStatement>  mStatement_EnumerateGroups;
+  nsCOMPtr<mozIStorageStatement>  mStatement_EnumerateGroupsTimeOrder;
 
   nsCOMPtr<nsILocalFile>          mCacheDirectory;
   PRUint32                        mCacheCapacity; // in bytes
   PRInt32                         mDeltaCounter;
 
   nsInterfaceHashtable<nsCStringHashKey, nsIWeakReference> mCaches;
   nsClassHashtable<nsCStringHashKey, nsCString> mActiveCachesByGroup;
   nsTHashtable<nsCStringHashKey> mActiveCaches;
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -71,16 +71,18 @@
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Preferences.h"
 
 #include "nsXULAppAPI.h"
 
 using namespace mozilla;
 
 static const PRUint32 kRescheduleLimit = 3;
+// Max number of retries for every entry of pinned app.
+static const PRUint32 kPinnedEntryRetriesLimit = 3;
 
 #if defined(PR_LOGGING)
 //
 // To enable logging (see prlog.h for full details):
 //
 //    set NSPR_LOG_MODULES=nsOfflineCacheUpdate:5
 //    set NSPR_LOG_FILE=offlineupdate.log
 //
@@ -728,17 +730,17 @@ nsOfflineManifestItem::ReadManifest(nsII
 
     nsCString::const_iterator begin, iter, end;
     manifest->mReadBuf.BeginReading(begin);
     manifest->mReadBuf.EndReading(end);
 
     for (iter = begin; iter != end; iter++) {
         if (*iter == '\r' || *iter == '\n') {
             nsresult rv = manifest->HandleManifestLine(begin, iter);
-            
+
             if (NS_FAILED(rv)) {
                 LOG(("HandleManifestLine failed with 0x%08x", rv));
                 return NS_ERROR_ABORT;
             }
 
             begin = iter;
             begin++;
         }
@@ -950,41 +952,41 @@ nsOfflineManifestItem::HandleManifestLin
                      spec, EmptyCString());
         break;
     }
     }
 
     return NS_OK;
 }
 
-nsresult 
+nsresult
 nsOfflineManifestItem::GetOldManifestContentHash(nsIRequest *aRequest)
 {
     nsresult rv;
 
     nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // load the main cache token that is actually the old offline cache token and 
+    // load the main cache token that is actually the old offline cache token and
     // read previous manifest content hash value
     nsCOMPtr<nsISupports> cacheToken;
     cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
     if (cacheToken) {
         nsCOMPtr<nsICacheEntryDescriptor> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
         NS_ENSURE_SUCCESS(rv, rv);
-    
+
         rv = cacheDescriptor->GetMetaDataElement("offline-manifest-hash", getter_Copies(mOldManifestHashValue));
         if (NS_FAILED(rv))
             mOldManifestHashValue.Truncate();
     }
 
     return NS_OK;
 }
 
-nsresult 
+nsresult
 nsOfflineManifestItem::CheckNewManifestContentHash(nsIRequest *aRequest)
 {
     nsresult rv;
 
     if (!mManifestHash) {
         // Nothing to compare against...
         return NS_OK;
     }
@@ -1014,17 +1016,17 @@ nsOfflineManifestItem::CheckNewManifestC
     nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsISupports> cacheToken;
     cachingChannel->GetOfflineCacheToken(getter_AddRefs(cacheToken));
     if (cacheToken) {
         nsCOMPtr<nsICacheEntryDescriptor> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
         NS_ENSURE_SUCCESS(rv, rv);
-    
+
         rv = cacheDescriptor->SetMetaDataElement("offline-manifest-hash", mManifestHashValue.get());
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return NS_OK;
 }
 
 void
@@ -1135,16 +1137,18 @@ nsOfflineCacheUpdate::nsOfflineCacheUpda
     : mState(STATE_UNINITIALIZED)
     , mOwner(nsnull)
     , mAddedItems(false)
     , mPartialUpdate(false)
     , mSucceeded(true)
     , mObsolete(false)
     , mCurrentItem(-1)
     , mRescheduleCount(0)
+    , mPinnedEntryRetriesCount(0)
+    , mPinned(false)
 {
 }
 
 nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
 {
     LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
 }
 
@@ -1215,16 +1219,21 @@ nsOfflineCacheUpdate::Init(nsIURI *aMani
 
     rv = cacheService->CreateApplicationCache(manifestSpec,
                                               getter_AddRefs(mApplicationCache));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mApplicationCache->GetClientID(mClientID);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
+                                                             NULL,
+                                                             &mPinned);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     mState = STATE_INITIALIZED;
     return NS_OK;
 }
 
 nsresult
 nsOfflineCacheUpdate::InitPartial(nsIURI *aManifestURI,
                                   const nsACString& clientID,
                                   nsIURI *aDocumentURI)
@@ -1267,16 +1276,21 @@ nsOfflineCacheUpdate::InitPartial(nsIURI
 
     nsCAutoString groupID;
     rv = mApplicationCache->GetGroupID(groupID);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = NS_NewURI(getter_AddRefs(mManifestURI), groupID);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
+                                                             NULL,
+                                                             &mPinned);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     mState = STATE_INITIALIZED;
     return NS_OK;
 }
 
 nsresult
 nsOfflineCacheUpdate::HandleManifest(bool *aDoUpdate)
 {
     // Be pessimistic
@@ -1354,21 +1368,27 @@ 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.
         PRUint16 status;
         rv = mManifestItem->GetStatus(&status);
         if (status == 404 || status == 410) {
             mSucceeded = false;
-            mObsolete = true;
             if (mPreviousApplicationCache) {
-                NotifyState(nsIOfflineCacheUpdateObserver::STATE_OBSOLETE);
+                if (mPinned) {
+                    // Do not obsolete a pinned application.
+                    NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
+                } else {
+                    NotifyState(nsIOfflineCacheUpdateObserver::STATE_OBSOLETE);
+                    mObsolete = true;
+                }
             } else {
                 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+                mObsolete = true;
             }
             Finish();
             return;
         }
 
         bool doUpdate;
         if (NS_FAILED(HandleManifest(&doUpdate))) {
             mSucceeded = false;
@@ -1409,21 +1429,56 @@ nsOfflineCacheUpdate::LoadCompleted()
         ProcessNextURI();
 
         return;
     }
 
     // Normal load finished.
 
     nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[mCurrentItem];
-    mCurrentItem++;
 
     bool succeeded;
     rv = item->GetRequestSucceeded(&succeeded);
 
+    if (mPinned) {
+        PRUint32 dummy_cache_type;
+        rv = mApplicationCache->GetTypes(item->mCacheKey, &dummy_cache_type);
+        bool item_doomed = NS_FAILED(rv); // can not find it? -> doomed
+
+        if (item_doomed &&
+            mPinnedEntryRetriesCount < kPinnedEntryRetriesLimit &&
+            (item->mItemType & (nsIApplicationCache::ITEM_EXPLICIT |
+                                nsIApplicationCache::ITEM_FALLBACK))) {
+            rv = EvictOneNonPinned();
+            if (NS_FAILED(rv)) {
+                mSucceeded = false;
+                NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+                Finish();
+                return;
+            }
+
+            rv = item->Cancel();
+            if (NS_FAILED(rv)) {
+                mSucceeded = false;
+                NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+                Finish();
+                return;
+            }
+
+            mPinnedEntryRetriesCount++;
+            // Retry current item, so mCurrentItem is not advanced.
+            ProcessNextURI();
+            return;
+        }
+    }
+
+    // Advance to next item.
+    mCurrentItem++;
+    mPinnedEntryRetriesCount = 0;
+
     // Check for failures.  3XX, 4XX and 5XX errors on items explicitly
     // listed in the manifest will cause the update to fail.
     if (NS_FAILED(rv) || !succeeded) {
         if (item->mItemType &
             (nsIApplicationCache::ITEM_EXPLICIT |
              nsIApplicationCache::ITEM_FALLBACK)) {
             mSucceeded = false;
         }
@@ -1463,26 +1518,26 @@ nsOfflineCacheUpdate::ManifestCheckCompl
     }
 
     if (NS_FAILED(aStatus)) {
         mSucceeded = false;
         NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
     }
 
     if (NS_FAILED(aStatus) && mRescheduleCount < kRescheduleLimit) {
-        // Do the final stuff but prevent notification of STATE_FINISHED.  
+        // Do the final stuff but prevent notification of STATE_FINISHED.
         // That would disconnect listeners that are responsible for document
         // association after a successful update. Forwarding notifications
         // from a new update through this dead update to them is absolutely
         // correct.
         FinishNoNotify();
 
         nsRefPtr<nsOfflineCacheUpdate> newUpdate =
             new nsOfflineCacheUpdate();
-        // Leave aDocument argument null. Only glues and children keep 
+        // Leave aDocument argument null. Only glues and children keep
         // document instances.
         newUpdate->Init(mManifestURI, mDocumentURI, nsnull);
 
         // In a rare case the manifest will not be modified on the next refetch
         // transfer all master document URIs to the new update to ensure that
         // all documents refering it will be properly cached.
         for (PRInt32 i = 0; i < mDocumentURIs.Count(); i++) {
             newUpdate->StickDocument(mDocumentURIs[i]);
@@ -1750,17 +1805,17 @@ nsOfflineCacheUpdate::ScheduleImplicit()
     else {
         clientID = mClientID;
     }
 
     rv = update->InitPartial(mManifestURI, clientID, mDocumentURI);
     NS_ENSURE_SUCCESS(rv, rv);
 
     for (PRInt32 i = 0; i < mDocumentURIs.Count(); i++) {
-        rv = update->AddURI(mDocumentURIs[i], 
+        rv = update->AddURI(mDocumentURIs[i],
               nsIApplicationCache::ITEM_IMPLICIT);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     update->SetOwner(this);
     rv = update->Begin();
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1829,16 +1884,70 @@ nsOfflineCacheUpdate::Finish()
 {
     nsresult rv = FinishNoNotify();
 
     NotifyState(nsIOfflineCacheUpdateObserver::STATE_FINISHED);
 
     return rv;
 }
 
+static nsresult
+EvictOneOfCacheGroups(nsIApplicationCacheService *cacheService,
+                      PRUint32 count, const char * const *groups)
+{
+    nsresult rv;
+    unsigned int i;
+
+    for (i = 0; i < count; i++) {
+        nsCOMPtr<nsIURI> uri;
+        rv = NS_NewURI(getter_AddRefs(uri), groups[i]);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        nsDependentCString group_name(groups[i]);
+        nsCOMPtr<nsIApplicationCache> cache;
+        rv = cacheService->GetActiveCache(group_name, getter_AddRefs(cache));
+        // Maybe someone in another thread or process have deleted it.
+        if (NS_FAILED(rv) || !cache)
+            continue;
+
+        bool pinned;
+        rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(uri,
+                                                                 NULL,
+                                                                 &pinned);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (!pinned) {
+            rv = cache->Discard();
+            return NS_OK;
+        }
+    }
+
+    return NS_ERROR_FILE_NOT_FOUND;
+}
+
+nsresult
+nsOfflineCacheUpdate::EvictOneNonPinned()
+{
+    nsresult rv;
+
+    nsCOMPtr<nsIApplicationCacheService> cacheService =
+        do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRUint32 count;
+    char **groups;
+    rv = cacheService->GetGroupsTimeOrdered(&count, &groups);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = EvictOneOfCacheGroups(cacheService, count, groups);
+
+    NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, groups);
+    return rv;
+}
+
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdate::nsIOfflineCacheUpdate
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain)
 {
     NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
@@ -1937,17 +2046,17 @@ nsOfflineCacheUpdate::AddURI(nsIURI *aUR
     mAddedItems = true;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdate::AddDynamicURI(nsIURI *aURI)
 {
-    if (GeckoProcessType_Default != XRE_GetProcessType()) 
+    if (GeckoProcessType_Default != XRE_GetProcessType())
         return NS_ERROR_NOT_IMPLEMENTED;
 
     // If this is a partial update and the resource is already in the
     // cache, we should only mark the entry, not fetch it again.
     if (mPartialUpdate) {
         nsCAutoString key;
         GetCacheKey(aURI, key);
 
@@ -2026,17 +2135,17 @@ nsOfflineCacheUpdate::Schedule()
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdate::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate,
                                          PRUint32 aState)
 {
     if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
         // Take the mSucceeded flag from the underlying update, we will be
-        // queried for it soon. mSucceeded of this update is false (manifest 
+        // queried for it soon. mSucceeded of this update is false (manifest
         // check failed) but the subsequent re-fetch update might succeed
         bool succeeded;
         aUpdate->GetSucceeded(&succeeded);
         mSucceeded = succeeded;
     }
 
     nsresult rv = NotifyState(aState);
     if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED)
--- a/uriloader/prefetch/nsOfflineCacheUpdate.h
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -252,16 +252,19 @@ private:
     nsresult ScheduleImplicit();
     nsresult AssociateDocuments(nsIApplicationCache* cache);
 
     nsresult GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers);
     nsresult NotifyState(PRUint32 state);
     nsresult Finish();
     nsresult FinishNoNotify();
 
+    // Find one non-pinned cache group and evict it.
+    nsresult EvictOneNonPinned();
+
     enum {
         STATE_UNINITIALIZED,
         STATE_INITIALIZED,
         STATE_CHECKING,
         STATE_DOWNLOADING,
         STATE_CANCELLED,
         STATE_FINISHED
     } mState;
@@ -295,17 +298,23 @@ private:
 
     /* Documents that requested this update */
     nsCOMArray<nsIURI> mDocumentURIs;
 
     /* Reschedule count.  When an update is rescheduled due to
      * mismatched manifests, the reschedule count will be increased. */
     PRUint32 mRescheduleCount;
 
+    /* Whena an entry for a pinned app is retried, retries count is
+     * increaded. */
+    PRUint32 mPinnedEntryRetriesCount;
+
     nsRefPtr<nsOfflineCacheUpdate> mImplicitUpdate;
+
+    bool                           mPinned;
 };
 
 class nsOfflineCacheUpdateService : public nsIOfflineCacheUpdateService
                                   , public nsIObserver
                                   , public nsOfflineCacheUpdateOwner
                                   , public nsSupportsWeakReference
 {
 public:
@@ -335,16 +344,20 @@ public:
      * Returns the singleton nsOfflineCacheUpdateService without an addref, or
      * nsnull if the service couldn't be created.
      */
     static nsOfflineCacheUpdateService *EnsureService();
 
     /** Addrefs and returns the singleton nsOfflineCacheUpdateService. */
     static nsOfflineCacheUpdateService *GetInstance();
 
+    static nsresult OfflineAppPinnedForURI(nsIURI *aDocumentURI,
+                                           nsIPrefBranch *aPrefBranch,
+                                           bool *aPinned);
+
 private:
     nsresult ProcessNextUpdate();
 
     nsTArray<nsRefPtr<nsOfflineCacheUpdate> > mUpdates;
 
     bool mDisabled;
     bool mUpdateRunning;
 };
--- a/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
@@ -530,20 +530,21 @@ nsOfflineCacheUpdateService::OfflineAppA
 {
     nsCOMPtr<nsIURI> codebaseURI;
     nsresult rv = aPrincipal->GetURI(getter_AddRefs(codebaseURI));
     NS_ENSURE_SUCCESS(rv, rv);
 
     return OfflineAppAllowedForURI(codebaseURI, aPrefBranch, aAllowed);
 }
 
-NS_IMETHODIMP
-nsOfflineCacheUpdateService::OfflineAppAllowedForURI(nsIURI *aURI,
-                                                     nsIPrefBranch *aPrefBranch,
-                                                     bool *aAllowed)
+static nsresult
+OfflineAppPermForURI(nsIURI *aURI,
+                     nsIPrefBranch *aPrefBranch,
+                     bool pinned,
+                     bool *aAllowed)
 {
     *aAllowed = false;
     if (!aURI)
         return NS_OK;
 
     nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
     if (!innerURI)
         return NS_OK;
@@ -563,29 +564,44 @@ nsOfflineCacheUpdateService::OfflineAppA
 
     nsCOMPtr<nsIPermissionManager> permissionManager =
         do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
     if (!permissionManager) {
         return NS_OK;
     }
 
     PRUint32 perm;
-    permissionManager->TestExactPermission(innerURI, "offline-app", &perm);
+    const char *permName = pinned ? "pin-app" : "offline-app";
+    permissionManager->TestExactPermission(innerURI, permName, &perm);
 
-    if (perm == nsIPermissionManager::UNKNOWN_ACTION) {
+    if (perm == nsIPermissionManager::UNKNOWN_ACTION && !pinned) {
         static const char kPrefName[] = "offline-apps.allow_by_default";
         if (aPrefBranch) {
             aPrefBranch->GetBoolPref(kPrefName, aAllowed);
         } else {
             *aAllowed = Preferences::GetBool(kPrefName, false);
         }
 
         return NS_OK;
     }
 
-    if (perm == nsIPermissionManager::DENY_ACTION) {
-        return NS_OK;
+    if (perm == nsIPermissionManager::ALLOW_ACTION) {
+        *aAllowed = true;
     }
 
-    *aAllowed = true;
-
     return NS_OK;
 }
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OfflineAppAllowedForURI(nsIURI *aURI,
+                                                     nsIPrefBranch *aPrefBranch,
+                                                     bool *aAllowed)
+{
+    return OfflineAppPermForURI(aURI, aPrefBranch, false, aAllowed);
+}
+
+nsresult
+nsOfflineCacheUpdateService::OfflineAppPinnedForURI(nsIURI *aDocumentURI,
+                                                    nsIPrefBranch *aPrefBranch,
+                                                    bool *aPinned)
+{
+    return OfflineAppPermForURI(aDocumentURI, aPrefBranch, true, aPinned);
+}