Bug 398478: Clear globalStorage for offline apps separately from cookies, and the offline cache separately from the main cache. r=enndeakin, r=mconnor, sr=biesi
authordcamp@mozilla.com
Tue, 22 Jan 2008 20:06:36 -0800
changeset 10563 25b6bca5517db621925b6f6b90b2128aba9a3c59
parent 10562 997f039a29bc72b36f17e9acf7dcdc786b91136a
child 10564 de87fc326c1270157451b6b283986b40b2220bbb
push idunknown
push userunknown
push dateunknown
reviewersenndeakin, mconnor, biesi
bugs398478
milestone1.9b3pre
Bug 398478: Clear globalStorage for offline apps separately from cookies, and the offline cache separately from the main cache. r=enndeakin, r=mconnor, sr=biesi
browser/base/content/sanitize.js
browser/base/content/sanitize.xul
browser/components/preferences/sanitize.xul
browser/locales/en-US/chrome/browser/sanitize.dtd
dom/public/idl/storage/nsIDOMStorageManager.idl
dom/src/storage/nsDOMStorage.cpp
dom/src/storage/nsDOMStorageDB.cpp
dom/src/storage/nsDOMStorageDB.h
netwerk/cache/src/nsCacheService.cpp
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -118,16 +118,38 @@ Sanitizer.prototype = {
       },
       
       get canClear()
       {
         return true;
       }
     },
     
+    offlineApps: {
+      clear: function ()
+      {
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        var cacheService = Cc["@mozilla.org/network/cache-service;1"].
+                           getService(Ci.nsICacheService);
+        try {
+          cacheService.evictEntries(Ci.nsICache.STORE_OFFLINE);
+        } catch(er) {}
+
+        var storageManagerService = Cc["@mozilla.org/dom/storagemanager;1"].
+                                    getService(Ci.nsIDOMStorageManager);
+        storageManagerService.clearOfflineApps();
+      },
+
+      get canClear()
+      {
+          return true;
+      }
+    },
+
     history: {
       clear: function ()
       {
         var globalHistory = Components.classes["@mozilla.org/browser/global-history;2"]
                                       .getService(Components.interfaces.nsIBrowserHistory);
         globalHistory.removeAllPages();
         
         try {
--- a/browser/base/content/sanitize.xul
+++ b/browser/base/content/sanitize.xul
@@ -119,16 +119,17 @@
 
     <preferences id="sanitizePreferences">
       <preference id="privacy.item.history"               name="privacy.item.history"               type="bool" readonly="true"/>
       <preference id="privacy.item.formdata"              name="privacy.item.formdata"              type="bool" readonly="true"/>
       <preference id="privacy.item.passwords"             name="privacy.item.passwords"             type="bool" readonly="true"/>
       <preference id="privacy.item.downloads"             name="privacy.item.downloads"             type="bool" readonly="true"/>
       <preference id="privacy.item.cookies"               name="privacy.item.cookies"               type="bool" readonly="true"/>
       <preference id="privacy.item.cache"                 name="privacy.item.cache"                 type="bool" readonly="true"/>
+      <preference id="privacy.item.offlineApps"           name="privacy.item.offlineApps"           type="bool"/>
       <preference id="privacy.item.sessions"              name="privacy.item.sessions"              type="bool" readonly="true"/>
     </preferences>
 
     <description>&sanitizeItems.label;</description>
 
     <checkbox label="&itemHistory.label;"
               accesskey="&itemHistory.accesskey;"
               preference="privacy.item.history"
@@ -144,16 +145,20 @@
     <checkbox label="&itemCache.label;"
               accesskey="&itemCache.accesskey;"
               preference="privacy.item.cache"
               onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
     <checkbox label="&itemCookies.label;"
               accesskey="&itemCookies.accesskey;"
               preference="privacy.item.cookies"
               onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+    <checkbox label="&itemOfflineApps.label;"
+              accesskey="&itemOfflineApps.accesskey;"
+              preference="privacy.item.offlineApps"
+              onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
     <checkbox label="&itemPasswords.label;"
               accesskey="&itemPasswords.accesskey;"
               preference="privacy.item.passwords"
               onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
     <checkbox label="&itemSessions.label;"
               accesskey="&itemSessions.accesskey;"
               preference="privacy.item.sessions"
               onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
--- a/browser/components/preferences/sanitize.xul
+++ b/browser/components/preferences/sanitize.xul
@@ -57,16 +57,17 @@
 
     <preferences>
       <preference id="privacy.item.history"               name="privacy.item.history"               type="bool"/>
       <preference id="privacy.item.formdata"              name="privacy.item.formdata"              type="bool"/>
       <preference id="privacy.item.passwords"             name="privacy.item.passwords"             type="bool"/>
       <preference id="privacy.item.downloads"             name="privacy.item.downloads"             type="bool"/>
       <preference id="privacy.item.cookies"               name="privacy.item.cookies"               type="bool"/>
       <preference id="privacy.item.cache"                 name="privacy.item.cache"                 type="bool"/>
+      <preference id="privacy.item.offlineApps"           name="privacy.item.offlineApps"           type="bool"/>
       <preference id="privacy.item.sessions"              name="privacy.item.sessions"              type="bool"/>
     </preferences>
 
     <description>&clearDataSettings.label;</description>
 
     <checkbox label="&itemHistory.label;"
               accesskey="&itemHistory.accesskey;"
               preference="privacy.item.history"/>
@@ -77,16 +78,19 @@
               accesskey="&itemFormSearchHistory.accesskey;"
               preference="privacy.item.formdata"/>
     <checkbox label="&itemCache.label;"
               accesskey="&itemCache.accesskey;"
               preference="privacy.item.cache"/>
     <checkbox label="&itemCookies.label;"
               accesskey="&itemCookies.accesskey;"
               preference="privacy.item.cookies"/>
+    <checkbox label="&itemOfflineApps.label;"
+	      accesskey="&itemOfflineApps.accesskey;"
+	      preference="privacy.item.offlineApps"/>
     <checkbox label="&itemPasswords.label;"
               accesskey="&itemPasswords.accesskey;"
               preference="privacy.item.passwords"/>
     <checkbox label="&itemSessions.label;"
               accesskey="&itemSessions.accesskey;"
               preference="privacy.item.sessions"/>
 
   </prefpane>
--- a/browser/locales/en-US/chrome/browser/sanitize.dtd
+++ b/browser/locales/en-US/chrome/browser/sanitize.dtd
@@ -10,13 +10,15 @@
 <!ENTITY itemFormSearchHistory.label  "Saved Form and Search History">
 <!ENTITY itemFormSearchHistory.accesskey "F">
 <!ENTITY itemPasswords.label          "Saved Passwords">
 <!ENTITY itemPasswords.accesskey      "P">
 <!ENTITY itemCookies.label            "Cookies">
 <!ENTITY itemCookies.accesskey        "C">
 <!ENTITY itemCache.label              "Cache">
 <!ENTITY itemCache.accesskey          "a">
+<!ENTITY itemOfflineApps.label        "Offline Website Data">
+<!ENTITY itemOfflineApps.accesskey    "O">
 <!ENTITY itemDownloads.label          "Download History">
 <!ENTITY itemDownloads.accesskey      "D">
 <!ENTITY itemSessions.label           "Authenticated Sessions">
 <!ENTITY itemSessions.accesskey       "S">
 <!ENTITY window.width                 "30em">
--- a/dom/public/idl/storage/nsIDOMStorageManager.idl
+++ b/dom/public/idl/storage/nsIDOMStorageManager.idl
@@ -32,21 +32,27 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(74dc93d1-8cdf-43b1-a52c-776dcb873475)]
+[scriptable, uuid(6e4bc25e-f056-4c6c-b27e-89152ca91834)]
 interface nsIDOMStorageManager : nsISupports
 {
   /**
    * Return the amount of disk space used by a domain.  Usage is checked
    * against the domain of the page that set the key (the owner domain), not
    * the domain of the storage object.
    *
    * @param aOwnerDomain The domain to check.
    * @returns the space usage of the domain, in bytes.
    */
   long getUsage(in AString aOwnerDomain);
+
+  /**
+   * Clear keys owned by offline applications.  All data owned by a domain
+   * with the "offline-app" permission will be removed from the database.
+   */
+  void clearOfflineApps();
 };
--- a/dom/src/storage/nsDOMStorage.cpp
+++ b/dom/src/storage/nsDOMStorage.cpp
@@ -47,16 +47,17 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsIURI.h"
 #include "nsReadableUtils.h"
 #include "nsIObserverService.h"
 #include "nsNetUtil.h"
 #include "nsIPrefBranch.h"
 #include "nsICookiePermission.h"
+#include "nsIPermission.h"
 #include "nsIPermissionManager.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsIJSContextStack.h"
 
 static const PRUint32 ASK_BEFORE_ACCEPT = 1;
 static const PRUint32 ACCEPT_SESSION = 2;
 static const PRUint32 BEHAVIOR_REJECT = 2;
@@ -227,16 +228,54 @@ nsDOMStorageManager::Shutdown()
 
 PR_STATIC_CALLBACK(PLDHashOperator)
 ClearStorage(nsDOMStorageEntry* aEntry, void* userArg)
 {
   aEntry->mStorage->ClearAll();
   return PL_DHASH_REMOVE;
 }
 
+static nsresult
+GetOfflineDomains(nsStringArray& aDomains)
+{
+  nsCOMPtr<nsIPermissionManager> permissionManager =
+    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+  if (permissionManager) {
+    nsCOMPtr<nsISimpleEnumerator> enumerator;
+    nsresult rv = permissionManager->GetEnumerator(getter_AddRefs(enumerator));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRBool hasMore;
+    while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+      nsCOMPtr<nsIPermission> perm;
+      rv = enumerator->GetNext(getter_AddRefs(perm));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      PRUint32 capability;
+      rv = perm->GetCapability(&capability);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (capability != nsIPermissionManager::DENY_ACTION) {
+        nsCAutoString type;
+        rv = perm->GetType(type);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (type.EqualsLiteral("offline-app")) {
+          nsCAutoString host;
+          rv = perm->GetHost(host);
+          NS_ENSURE_SUCCESS(rv, rv);
+
+          aDomains.AppendString(NS_ConvertUTF8toUTF16(host));
+        }
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
 nsresult
 nsDOMStorageManager::Observe(nsISupports *aSubject,
                              const char *aTopic,
                              const PRUnichar *aData)
 {
   if (!strcmp(aTopic, "offline-app-removed")) {
 #ifdef MOZ_STORAGE
     nsresult rv = nsDOMStorage::InitDB();
@@ -245,33 +284,50 @@ nsDOMStorageManager::Observe(nsISupports
 #endif
   } else if (!strcmp(aTopic, "cookie-changed") &&
              !nsCRT::strcmp(aData, NS_LITERAL_STRING("cleared").get())) {
     mStorages.EnumerateEntries(ClearStorage, nsnull);
 
 #ifdef MOZ_STORAGE
     nsresult rv = nsDOMStorage::InitDB();
     NS_ENSURE_SUCCESS(rv, rv);
-    return nsDOMStorage::gStorageDB->RemoveAll();
+
+    // Remove global storage for domains that aren't marked for offline use.
+    nsStringArray domains;
+    rv = GetOfflineDomains(domains);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return nsDOMStorage::gStorageDB->RemoveOwners(domains, PR_FALSE);
 #endif
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMStorageManager::GetUsage(const nsAString& aDomain,
                               PRInt32 *aUsage)
 {
   nsresult rv = nsDOMStorage::InitDB();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return nsDOMStorage::gStorageDB->GetUsage(aDomain, aUsage);
 }
 
+NS_IMETHODIMP
+nsDOMStorageManager::ClearOfflineApps()
+{
+    nsresult rv = nsDOMStorage::InitDB();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsStringArray domains;
+    rv = GetOfflineDomains(domains);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return nsDOMStorage::gStorageDB->RemoveOwners(domains, PR_TRUE);
+}
+
 void
 nsDOMStorageManager::AddToStoragesHash(nsDOMStorage* aStorage)
 {
   nsDOMStorageEntry* entry = mStorages.PutEntry(aStorage);
   if (entry)
     entry->mStorage = aStorage;
 }
 
--- a/dom/src/storage/nsDOMStorageDB.cpp
+++ b/dom/src/storage/nsDOMStorageDB.cpp
@@ -433,16 +433,57 @@ nsDOMStorageDB::RemoveOwner(const nsAStr
   }
 
   nsresult rv = mRemoveOwnerStatement->BindStringParameter(0, aOwner);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mRemoveOwnerStatement->Execute();
 }
 
+
+nsresult
+nsDOMStorageDB::RemoveOwners(const nsStringArray &aOwners, PRBool aMatch)
+{
+  if (aOwners.Count() == 0) {
+    if (aMatch) {
+      return NS_OK;
+    }
+
+    return RemoveAll();
+  }
+
+  nsCAutoString expression;
+
+  if (aMatch) {
+    expression.Assign(NS_LITERAL_CSTRING("DELETE FROM webappsstore "
+                                         "WHERE owner IN (?"));
+  } else {
+    expression.Assign(NS_LITERAL_CSTRING("DELETE FROM webappsstore "
+                                         "WHERE owner NOT IN (?"));
+  }
+
+  for (PRInt32 i = 1; i < aOwners.Count(); i++) {
+    expression.Append(", ?");
+  }
+  expression.Append(")");
+
+  nsCOMPtr<mozIStorageStatement> statement;
+
+  nsresult rv = mConnection->CreateStatement(expression,
+                                             getter_AddRefs(statement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  for (PRInt32 i = 0; i < aOwners.Count(); i++) {
+    rv = statement->BindStringParameter(i, *aOwners[i]);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return statement->Execute();
+}
+
 nsresult
 nsDOMStorageDB::RemoveAll()
 {
   mozStorageStatementScoper scope(mRemoveAllStatement);
   return mRemoveAllStatement->Execute();
 }
 
 nsresult
--- a/dom/src/storage/nsDOMStorageDB.h
+++ b/dom/src/storage/nsDOMStorageDB.h
@@ -108,16 +108,23 @@ public:
 
   /**
    * Removes all keys added by a given domain.
    */
   nsresult
   RemoveOwner(const nsAString& aOwner);
 
   /**
+   * Removes keys owned by domains that either match or don't match the
+   * list.
+   */
+  nsresult
+  RemoveOwners(const nsStringArray& aOwners, PRBool aMatch);
+
+  /**
    * Removes all keys from storage. Used when clearing storage.
    */
   nsresult
   RemoveAll();
 
   nsresult GetUsage(const nsAString &aOwner, PRInt32 *aUsage);
 
 protected:
--- a/netwerk/cache/src/nsCacheService.cpp
+++ b/netwerk/cache/src/nsCacheService.cpp
@@ -764,18 +764,18 @@ nsCacheService::EvictEntriesForClient(co
             }
             rv = mDiskDevice->EvictEntries(clientID);
             if (NS_FAILED(rv)) return rv;
         }
     }
 #endif // ! NECKO_DISK_CACHE
 
 #ifdef NECKO_OFFLINE_CACHE
-    if (storagePolicy == nsICache::STORE_ANYWHERE ||
-        storagePolicy == nsICache::STORE_OFFLINE) {
+    // Only clear the offline cache if it has been specifically asked for.
+    if (storagePolicy == nsICache::STORE_OFFLINE) {
         if (mEnableOfflineDevice) {
             if (!mOfflineDevice) {
                 rv = CreateOfflineDevice();
                 if (NS_FAILED(rv)) return rv;
             }
             rv = mOfflineDevice->EvictEntries(clientID);
             if (NS_FAILED(rv)) return rv;
         }