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 id22382
push userbmo@edmorley.co.uk
push dateSat, 31 Mar 2012 21:44:34 +0000
treeherdermozilla-central@bbe5086163c9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs674728
milestone14.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 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);
+}