Bug 367373 - DOM storage quota should exclude offline-app allowed domains, r+sr=jst
authorHonza Bambas <honzab.moz@firemni.cz>
Wed, 21 Oct 2009 12:18:08 +0200
changeset 34057 7923f101086ad83047aeb2716b556539b927117c
parent 34056 b0b3613662193974736d70dabc48292492635a9a
child 34058 f0ba915153e41cedbc8035788fe3685a271f243b
push id9849
push userhonzab.moz@firemni.cz
push dateWed, 21 Oct 2009 10:19:21 +0000
treeherdermozilla-central@f0ba915153e4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs367373
milestone1.9.3a1pre
Bug 367373 - DOM storage quota should exclude offline-app allowed domains, r+sr=jst
dom/src/storage/nsDOMStorage.cpp
dom/src/storage/nsDOMStorage.h
dom/src/storage/nsDOMStorageDBWrapper.cpp
dom/src/storage/nsDOMStorageDBWrapper.h
dom/src/storage/nsDOMStorageMemoryDB.cpp
dom/src/storage/nsDOMStorageMemoryDB.h
dom/src/storage/nsDOMStoragePersistentDB.cpp
dom/src/storage/nsDOMStoragePersistentDB.h
dom/tests/mochitest/localstorage/Makefile.in
dom/tests/mochitest/localstorage/frameQuota.html
dom/tests/mochitest/localstorage/test_localStorageQuota.html
dom/tests/mochitest/localstorage/test_localStorageQuotaSessionOnly.html
dom/tests/mochitest/localstorage/test_localStorageQuotaSessionOnly2.html
--- a/dom/src/storage/nsDOMStorage.cpp
+++ b/dom/src/storage/nsDOMStorage.cpp
@@ -147,54 +147,70 @@ IsCallerSecure()
   }
 
   PRBool isHttps = PR_FALSE;
   nsresult rv = innerUri->SchemeIs("https", &isHttps);
 
   return NS_SUCCEEDED(rv) && isHttps;
 }
 
-
-// Returns two quotas - A hard limit for which adding data will be an error,
-// and a limit after which a warning event will be sent to the observer
-// service.  The warn limit may be -1, in which case there will be no warning.
-static void
-GetQuota(const nsACString &aDomain, PRInt32 *aQuota, PRInt32 *aWarnQuota)
+PRUint32
+GetOfflinePermission(const nsACString &aDomain)
 {
   // Fake a URI for the permission manager
   nsCOMPtr<nsIURI> uri;
   NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + aDomain);
 
+  PRUint32 perm;
   if (uri) {
     nsCOMPtr<nsIPermissionManager> permissionManager =
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
 
-    PRUint32 perm;
     if (permissionManager &&
-        NS_SUCCEEDED(permissionManager->TestPermission(uri, "offline-app", &perm)) &&
-        perm != nsIPermissionManager::UNKNOWN_ACTION &&
-        perm != nsIPermissionManager::DENY_ACTION) {
-      // This is an offline app, give more space by default.
-      *aQuota = ((PRInt32)nsContentUtils::GetIntPref(kOfflineAppQuota,
-                                                     DEFAULT_OFFLINE_APP_QUOTA) * 1024);
+        NS_SUCCEEDED(permissionManager->TestPermission(uri, "offline-app", &perm)))
+        return perm;
+  }
+
+  return nsIPermissionManager::UNKNOWN_ACTION;
+}
+
+PRBool
+IsOfflineAllowed(const nsACString &aDomain)
+{
+  PRInt32 perm = GetOfflinePermission(aDomain);
+  return IS_PERMISSION_ALLOWED(perm);
+}
 
-      if (perm == nsIOfflineCacheUpdateService::ALLOW_NO_WARN) {
-        *aWarnQuota = -1;
-      } else {
-        *aWarnQuota = ((PRInt32)nsContentUtils::GetIntPref(kOfflineAppWarnQuota,
-                                                           DEFAULT_OFFLINE_WARN_QUOTA) * 1024);
-      }
-      return;
+// Returns two quotas - A hard limit for which adding data will be an error,
+// and a limit after which a warning event will be sent to the observer
+// service.  The warn limit may be -1, in which case there will be no warning.
+static PRUint32
+GetQuota(const nsACString &aDomain, PRInt32 *aQuota, PRInt32 *aWarnQuota)
+{
+  PRUint32 perm = GetOfflinePermission(aDomain);
+  if (IS_PERMISSION_ALLOWED(perm)) {
+    // This is an offline app, give more space by default.
+    *aQuota = ((PRInt32)nsContentUtils::GetIntPref(kOfflineAppQuota,
+                                                   DEFAULT_OFFLINE_APP_QUOTA) * 1024);
+
+    if (perm == nsIOfflineCacheUpdateService::ALLOW_NO_WARN) {
+      *aWarnQuota = -1;
+    } else {
+      *aWarnQuota = ((PRInt32)nsContentUtils::GetIntPref(kOfflineAppWarnQuota,
+                                                         DEFAULT_OFFLINE_WARN_QUOTA) * 1024);
     }
+    return perm;
   }
 
   // FIXME: per-domain quotas?
   *aQuota = ((PRInt32)nsContentUtils::GetIntPref(kDefaultQuota,
                                                  DEFAULT_QUOTA) * 1024);
   *aWarnQuota = -1;
+
+  return perm;
 }
 
 nsSessionStorageEntry::nsSessionStorageEntry(KeyTypePointer aStr)
   : nsStringHashKey(aStr), mItem(nsnull)
 {
 }
 
 nsSessionStorageEntry::nsSessionStorageEntry(const nsSessionStorageEntry& aToCopy)
@@ -634,17 +650,20 @@ nsDOMStorage::InitAsLocalStorage(nsIPrin
   nsDOMStorageDBWrapper::CreateOriginScopeDBKey(domainURI, mScopeDBKey);
 
   // XXX Bug 357323, we have to solve the issue how to define
   // origin for file URLs. In that case CreateOriginScopeDBKey
   // fails (the result is empty) and we must avoid database use
   // in that case because it produces broken entries w/o owner.
   mUseDB = !mScopeDBKey.IsEmpty();
 
-  nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(mDomain, PR_TRUE, mQuotaDomainDBKey);
+  nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(mDomain,
+      PR_TRUE, PR_FALSE, mQuotaDomainDBKey);
+  nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(mDomain,
+      PR_TRUE, PR_TRUE, mQuotaETLDplus1DomainDBKey);
 #endif
 
   mLocalStorage = PR_TRUE;
   return NS_OK;
 }
 
 nsresult
 nsDOMStorage::InitAsGlobalStorage(const nsACString &aDomainDemanded)
@@ -655,17 +674,20 @@ nsDOMStorage::InitAsGlobalStorage(const 
 
   // XXX Bug 357323, we have to solve the issue how to define
   // origin for file URLs. In that case CreateOriginScopeDBKey
   // fails (the result is empty) and we must avoid database use
   // in that case because it produces broken entries w/o owner.
   if (!(mUseDB = !mScopeDBKey.IsEmpty()))
     mScopeDBKey.AppendLiteral(":");
 
-  nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(aDomainDemanded, PR_TRUE, mQuotaDomainDBKey);
+  nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(aDomainDemanded,
+      PR_TRUE, PR_FALSE, mQuotaDomainDBKey);
+  nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(aDomainDemanded,
+      PR_TRUE, PR_TRUE, mQuotaETLDplus1DomainDBKey);
 #endif
   return NS_OK;
 }
 
 static PLDHashOperator
 CopyStorageItems(nsSessionStorageEntry* aEntry, void* userArg)
 {
   nsDOMStorage* newstorage = static_cast<nsDOMStorage*>(userArg);
@@ -1029,17 +1051,17 @@ NS_IMETHODIMP nsDOMStorage::RemoveItem(c
 
     nsAutoString value;
     PRBool secureItem;
     rv = GetDBValue(aKey, value, &secureItem);
     if (rv == NS_ERROR_DOM_NOT_FOUND_ERR)
       return NS_OK;
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = gStorageDB->RemoveKey(this, aKey,
+    rv = gStorageDB->RemoveKey(this, aKey, !IsOfflineAllowed(mDomain),
                                aKey.Length() + value.Length());
     NS_ENSURE_SUCCESS(rv, rv);
 
     mItemsCached = PR_FALSE;
 
     BroadcastChangeNotification();
 #endif
   }
@@ -1190,54 +1212,25 @@ nsDOMStorage::SetDBValue(const nsAString
 {
 #ifdef MOZ_STORAGE
   if (!UseDB())
     return NS_OK;
 
   nsresult rv = InitDB();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Get the current domain for quota enforcement
-  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
-  if (!ssm)
-    return NS_ERROR_FAILURE;
-
-  nsCOMPtr<nsIPrincipal> subjectPrincipal;
-  ssm->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
-
-  nsCAutoString currentDomain;
-
-  if (subjectPrincipal) {
-    nsCOMPtr<nsIURI> unused;
-    rv = GetPrincipalURIAndHost(subjectPrincipal, getter_AddRefs(unused),
-                                currentDomain);
-    // Don't bail out on NS_ERROR_DOM_SECURITY_ERR, since we want to allow
-    // trusted file:// URIs below.
-    if (NS_FAILED(rv) && rv != NS_ERROR_DOM_SECURITY_ERR) {
-      return rv;
-    }
-
-    if (currentDomain.IsEmpty()) {
-      // allow chrome urls and trusted file urls to write using
-      // the storage's domain
-      if (nsContentUtils::IsCallerTrustedForWrite())
-        currentDomain = mDomain;
-      else
-        return NS_ERROR_DOM_SECURITY_ERR;
-    }
-  } else {
-    currentDomain = mDomain;
-  }
-
+  PRInt32 offlineAppPermission;
   PRInt32 quota;
   PRInt32 warnQuota;
-  GetQuota(currentDomain, &quota, &warnQuota);
+  offlineAppPermission = GetQuota(mDomain, &quota, &warnQuota);
 
   PRInt32 usage;
-  rv = gStorageDB->SetKey(this, aKey, aValue, aSecure, quota, &usage);
+  rv = gStorageDB->SetKey(this, aKey, aValue, aSecure, quota,
+                          !IS_PERMISSION_ALLOWED(offlineAppPermission),
+                          &usage);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mItemsCached = PR_FALSE;
 
   if (warnQuota >= 0 && usage > warnQuota) {
     // try to include the window that exceeded the warn quota
     nsCOMPtr<nsIDOMWindow> window;
     JSContext *cx;
@@ -1249,17 +1242,17 @@ nsDOMStorage::SetDBValue(const nsAString
       if (scriptContext) {
         window = do_QueryInterface(scriptContext->GetGlobalObject());
       }
     }
 
     nsCOMPtr<nsIObserverService> os =
       do_GetService("@mozilla.org/observer-service;1");
     os->NotifyObservers(window, "dom-storage-warn-quota-exceeded",
-                        NS_ConvertUTF8toUTF16(currentDomain).get());
+                        NS_ConvertUTF8toUTF16(mDomain).get());
   }
 
   BroadcastChangeNotification();
 #endif
 
   return NS_OK;
 }
 
--- a/dom/src/storage/nsDOMStorage.h
+++ b/dom/src/storage/nsDOMStorage.h
@@ -42,30 +42,35 @@
 #define nsDOMStorage_h___
 
 #include "nscore.h"
 #include "nsAutoPtr.h"
 #include "nsIDOMStorageObsolete.h"
 #include "nsIDOMStorage.h"
 #include "nsIDOMStorageList.h"
 #include "nsIDOMStorageItem.h"
+#include "nsIPermissionManager.h"
 #include "nsInterfaceHashtable.h"
 #include "nsVoidArray.h"
 #include "nsTArray.h"
 #include "nsPIDOMStorage.h"
 #include "nsIDOMToString.h"
 #include "nsDOMEvent.h"
 #include "nsIDOMStorageEvent.h"
 #include "nsIDOMStorageManager.h"
 #include "nsCycleCollectionParticipant.h"
 
 #ifdef MOZ_STORAGE
 #include "nsDOMStorageDBWrapper.h"
 #endif
 
+#define IS_PERMISSION_ALLOWED(perm) \
+      ((perm) != nsIPermissionManager::UNKNOWN_ACTION && \
+      (perm) != nsIPermissionManager::DENY_ACTION)
+
 class nsDOMStorage;
 class nsIDOMStorage;
 class nsDOMStorageItem;
 
 class nsDOMStorageEntry : public nsVoidPtrHashKey
 {
 public:
   nsDOMStorageEntry(KeyTypePointer aStr);
@@ -240,30 +245,34 @@ protected:
   nsCString mDomain;
 
   // the key->value item pairs
   nsTHashtable<nsSessionStorageEntry> mItems;
 
   // keys are used for database queries.
   // see comments of the getters bellow.
   nsCString mScopeDBKey;
+  nsCString mQuotaETLDplus1DomainDBKey;
   nsCString mQuotaDomainDBKey;
 
   friend class nsIDOMStorage2;
   nsPIDOMStorage* mSecurityChecker;
 
 public:
   // e.g. "moc.rab.oof.:" or "moc.rab.oof.:http:80" depending
   // on association with a domain (globalStorage) or
   // an origin (localStorage).
   nsCString& GetScopeDBKey() {return mScopeDBKey;}
 
   // e.g. "moc.rab.%" - reversed eTLD+1 subpart of the domain or
-  // (in future) reversed offline application allowed domain.
-  nsCString& GetQuotaDomainDBKey() {return mQuotaDomainDBKey;}
+  // reversed offline application allowed domain.
+  nsCString& GetQuotaDomainDBKey(PRBool aOfflineAllowed)
+  {
+    return aOfflineAllowed ? mQuotaDomainDBKey : mQuotaETLDplus1DomainDBKey;
+  }
 
  #ifdef MOZ_STORAGE
    static nsDOMStorageDBWrapper* gStorageDB;
  #endif
 };
 
 class nsDOMStorage2 : public nsIDOMStorage,
                       public nsPIDOMStorage
@@ -442,9 +451,15 @@ NS_IMETHODIMP
 NS_NewDOMStorage(nsISupports* aOuter, REFNSIID aIID, void** aResult);
 
 NS_IMETHODIMP
 NS_NewDOMStorage2(nsISupports* aOuter, REFNSIID aIID, void** aResult);
 
 nsresult
 NS_NewDOMStorageList(nsIDOMStorageList** aResult);
 
+PRUint32
+GetOfflinePermission(const nsACString &aDomain);
+
+PRBool
+IsOfflineAllowed(const nsACString &aDomain);
+
 #endif /* nsDOMStorage_h___ */
--- a/dom/src/storage/nsDOMStorageDBWrapper.cpp
+++ b/dom/src/storage/nsDOMStorageDBWrapper.cpp
@@ -113,27 +113,28 @@ nsDOMStorageDBWrapper::GetKeyValue(nsDOM
 }
 
 nsresult
 nsDOMStorageDBWrapper::SetKey(nsDOMStorage* aStorage,
                               const nsAString& aKey,
                               const nsAString& aValue,
                               PRBool aSecure,
                               PRInt32 aQuota,
+                              PRBool aExcludeOfflineFromUsage,
                               PRInt32 *aNewUsage)
 {
   if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
     return mPrivateBrowsingDB.SetKey(aStorage, aKey, aValue, aSecure,
-                                          aQuota, aNewUsage);
+                                          aQuota, aExcludeOfflineFromUsage, aNewUsage);
   if (aStorage->SessionOnly())
     return mSessionOnlyDB.SetKey(aStorage, aKey, aValue, aSecure,
-                                      aQuota, aNewUsage);
+                                      aQuota, aExcludeOfflineFromUsage, aNewUsage);
 
   return mPersistentDB.SetKey(aStorage, aKey, aValue, aSecure,
-                                   aQuota, aNewUsage);
+                                   aQuota, aExcludeOfflineFromUsage, aNewUsage);
 }
 
 nsresult
 nsDOMStorageDBWrapper::SetSecure(nsDOMStorage* aStorage,
                                  const nsAString& aKey,
                                  const PRBool aSecure)
 {
   if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
@@ -142,24 +143,25 @@ nsDOMStorageDBWrapper::SetSecure(nsDOMSt
     return mSessionOnlyDB.SetSecure(aStorage, aKey, aSecure);
 
   return mPersistentDB.SetSecure(aStorage, aKey, aSecure);
 }
 
 nsresult
 nsDOMStorageDBWrapper::RemoveKey(nsDOMStorage* aStorage,
                                  const nsAString& aKey,
+                                 PRBool aExcludeOfflineFromUsage,
                                  PRInt32 aKeyUsage)
 {
   if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
-    return mPrivateBrowsingDB.RemoveKey(aStorage, aKey, aKeyUsage);
+    return mPrivateBrowsingDB.RemoveKey(aStorage, aKey, aExcludeOfflineFromUsage, aKeyUsage);
   if (aStorage->SessionOnly())
-    return mSessionOnlyDB.RemoveKey(aStorage, aKey, aKeyUsage);
+    return mSessionOnlyDB.RemoveKey(aStorage, aKey, aExcludeOfflineFromUsage, aKeyUsage);
 
-  return mPersistentDB.RemoveKey(aStorage, aKey, aKeyUsage);
+  return mPersistentDB.RemoveKey(aStorage, aKey, aExcludeOfflineFromUsage, aKeyUsage);
 }
 
 nsresult
 nsDOMStorageDBWrapper::ClearStorage(nsDOMStorage* aStorage)
 {
   if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
     return mPrivateBrowsingDB.ClearStorage(aStorage);
   if (aStorage->SessionOnly())
@@ -239,24 +241,25 @@ nsDOMStorageDBWrapper::RemoveAll()
 
   rv = mPersistentDB.RemoveAll();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return rv;
 }
 
 nsresult
-nsDOMStorageDBWrapper::GetUsage(nsDOMStorage* aStorage, PRInt32 *aUsage)
+nsDOMStorageDBWrapper::GetUsage(nsDOMStorage* aStorage,
+                                PRBool aExcludeOfflineFromUsage, PRInt32 *aUsage)
 {
   if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
-    return mPrivateBrowsingDB.GetUsage(aStorage, aUsage);
+    return mPrivateBrowsingDB.GetUsage(aStorage, aExcludeOfflineFromUsage, aUsage);
   if (aStorage->SessionOnly())
-    return mSessionOnlyDB.GetUsage(aStorage, aUsage);
+    return mSessionOnlyDB.GetUsage(aStorage, aExcludeOfflineFromUsage, aUsage);
 
-  return mPersistentDB.GetUsage(aStorage, aUsage);
+  return mPersistentDB.GetUsage(aStorage, aExcludeOfflineFromUsage, aUsage);
 }
 
 nsresult
 nsDOMStorageDBWrapper::GetUsage(const nsACString& aDomain,
                                 PRBool aIncludeSubDomains, PRInt32 *aUsage)
 {
   if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
     return mPrivateBrowsingDB.GetUsage(aDomain, aIncludeSubDomains, aUsage);
@@ -324,38 +327,55 @@ nsDOMStorageDBWrapper::CreateDomainScope
 
   aKey.AppendLiteral(".");
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(const nsACString& aAsciiDomain,
                                               PRBool aIncludeSubDomains,
+                                              PRBool aEffectiveTLDplus1Only,
                                               nsACString& aKey)
 {
   nsresult rv;
 
-  nsCOMPtr<nsIEffectiveTLDService> eTLDService(do_GetService(
-    NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCAutoString subdomainsDBKey;
+  if (aEffectiveTLDplus1Only) {
+    nsCOMPtr<nsIEffectiveTLDService> eTLDService(do_GetService(
+      NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
+    NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIURI> uri;
-  rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + aAsciiDomain);
-  NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsIURI> uri;
+    rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + aAsciiDomain);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCAutoString eTLDplusOne;
-  rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne);
-  if (NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS == rv) {
-    // XXX bug 357323 - what to do for localhost/file exactly?
-    eTLDplusOne = aAsciiDomain;
-    rv = NS_OK;
+    nsCAutoString eTLDplusOne;
+    rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne);
+    if (NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS == rv) {
+      // XXX bug 357323 - what to do for localhost/file exactly?
+      eTLDplusOne = aAsciiDomain;
+      rv = NS_OK;
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    CreateDomainScopeDBKey(eTLDplusOne, subdomainsDBKey);
   }
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCAutoString subdomainsDBKey;
-  CreateDomainScopeDBKey(eTLDplusOne, subdomainsDBKey);
+  else
+    CreateDomainScopeDBKey(aAsciiDomain, subdomainsDBKey);
 
   if (!aIncludeSubDomains)
     subdomainsDBKey.AppendLiteral(":");
 
   aKey.Assign(subdomainsDBKey);
   return NS_OK;
 }
+
+nsresult
+nsDOMStorageDBWrapper::GetDomainFromScopeKey(const nsACString& aScope,
+                                         nsACString& aDomain)
+{
+  nsCAutoString reverseDomain, scope;
+  scope = aScope;
+  scope.Left(reverseDomain, scope.FindChar(':')-1);
+
+  ReverseString(reverseDomain, aDomain);
+  return NS_OK;
+}
--- a/dom/src/storage/nsDOMStorageDBWrapper.h
+++ b/dom/src/storage/nsDOMStorageDBWrapper.h
@@ -114,16 +114,17 @@ public:
    * Set the value and secure flag for a key in storage.
    */
   nsresult
   SetKey(nsDOMStorage* aStorage,
          const nsAString& aKey,
          const nsAString& aValue,
          PRBool aSecure,
          PRInt32 aQuota,
+         PRBool aExcludeOfflineFromUsage,
          PRInt32* aNewUsage);
 
   /**
    * Set the secure flag for a key in storage. Does nothing if the key was
    * not found.
    */
   nsresult
   SetSecure(nsDOMStorage* aStorage,
@@ -131,16 +132,17 @@ public:
             const PRBool aSecure);
 
   /**
    * Removes a key from storage.
    */
   nsresult
   RemoveKey(nsDOMStorage* aStorage,
             const nsAString& aKey,
+            PRBool aExcludeOfflineFromUsage,
             PRInt32 aKeyUsage);
 
   /**
     * Remove all keys belonging to this storage.
     */
   nsresult
   ClearStorage(nsDOMStorage* aStorage);
 
@@ -175,17 +177,17 @@ public:
    */
   nsresult
   RemoveAll();
 
   /**
     * Returns usage for a storage using its GetQuotaDomainDBKey() as a key.
     */
   nsresult
-  GetUsage(nsDOMStorage* aStorage, PRInt32 *aUsage);
+  GetUsage(nsDOMStorage* aStorage, PRBool aExcludeOfflineFromUsage, PRInt32 *aUsage);
 
   /**
     * Returns usage of the domain and optionaly by any subdomain.
     */
   nsresult
   GetUsage(const nsACString& aDomain, PRBool aIncludeSubDomains, PRInt32 *aUsage);
 
   /**
@@ -203,17 +205,21 @@ public:
   static nsresult CreateDomainScopeDBKey(const nsACString& aAsciiDomain, nsACString& aKey);
 
   /**
     * Turns "foo.bar.com" to "moc.rab.",
     * i.e. extracts eTLD+1 from the host, reverses the result
     * and appends a dot.
     */
   static nsresult CreateQuotaDomainDBKey(const nsACString& aAsciiDomain,
-                                         PRBool aIncludeSubDomains, nsACString& aKey);
+                                         PRBool aIncludeSubDomains, PRBool aETLDplus1Only,
+                                         nsACString& aKey);
+
+  static nsresult GetDomainFromScopeKey(const nsACString& aScope,
+                                         nsACString& aDomain);
 
 protected:
   nsDOMStoragePersistentDB mPersistentDB;
   nsDOMStorageMemoryDB mSessionOnlyDB;
   nsDOMStorageMemoryDB mPrivateBrowsingDB;
 };
 
 #endif /* nsDOMStorageDB_h___ */
--- a/dom/src/storage/nsDOMStorageMemoryDB.cpp
+++ b/dom/src/storage/nsDOMStorageMemoryDB.cpp
@@ -183,27 +183,28 @@ nsDOMStorageMemoryDB::GetKeyValue(nsDOMS
 }
 
 nsresult
 nsDOMStorageMemoryDB::SetKey(nsDOMStorage* aStorage,
                              const nsAString& aKey,
                              const nsAString& aValue,
                              PRBool aSecure,
                              PRInt32 aQuota,
+                             PRBool aExcludeOfflineFromUsage,
                              PRInt32 *aNewUsage)
 {
   nsresult rv;
 
   nsInMemoryStorage* storage;
   rv = GetItemsTable(aStorage, &storage);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRInt32 usage = 0;
-  if (!aStorage->GetQuotaDomainDBKey().IsEmpty()) {
-    rv = GetUsage(aStorage, &usage);
+  if (!aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage).IsEmpty()) {
+    rv = GetUsage(aStorage, aExcludeOfflineFromUsage, &usage);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   usage += aKey.Length() + aValue.Length();
 
   nsInMemoryItem* item;
   if (!storage->mTable.Get(aKey, &item)) {
     if (usage > aQuota) {
@@ -253,16 +254,17 @@ nsDOMStorageMemoryDB::SetSecure(nsDOMSto
   item->mSecure = aSecure;
 
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageMemoryDB::RemoveKey(nsDOMStorage* aStorage,
                                 const nsAString& aKey,
+                                PRBool aExcludeOfflineFromUsage,
                                 PRInt32 aKeyUsage)
 {
   nsresult rv;
 
   nsInMemoryStorage* storage;
   rv = GetItemsTable(aStorage, &storage);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -380,68 +382,83 @@ nsDOMStorageMemoryDB::RemoveOwners(const
 nsresult
 nsDOMStorageMemoryDB::RemoveAll()
 {
   mData.Clear(); // XXX Check this releases all instances
   return NS_OK;
 }
 
 nsresult
-nsDOMStorageMemoryDB::GetUsage(nsDOMStorage* aStorage, PRInt32 *aUsage)
+nsDOMStorageMemoryDB::GetUsage(nsDOMStorage* aStorage,
+                               PRBool aExcludeOfflineFromUsage, PRInt32 *aUsage)
 {
-  return GetUsageInternal(aStorage->GetQuotaDomainDBKey(), aUsage);
+  return GetUsageInternal(aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage),
+                          aExcludeOfflineFromUsage, aUsage);
 }
 
 nsresult
 nsDOMStorageMemoryDB::GetUsage(const nsACString& aDomain,
                                PRBool aIncludeSubDomains,
                                PRInt32 *aUsage)
 {
   nsresult rv;
 
   nsCAutoString quotadomainDBKey;
   rv = nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(aDomain,
                                                      aIncludeSubDomains,
+                                                     PR_FALSE,
                                                      quotadomainDBKey);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return GetUsageInternal(quotadomainDBKey, aUsage);
+  return GetUsageInternal(quotadomainDBKey, PR_FALSE, aUsage);
 }
 
 struct GetUsageEnumStruc
 {
   PRInt32 mUsage;
+  PRInt32 mExcludeOfflineFromUsage;
   nsCString mSubdomain;
 };
 
 static PLDHashOperator
 GetUsageEnum(const nsACString& key,
              nsDOMStorageMemoryDB::nsInMemoryStorage* storageData,
              void *closure)
 {
   GetUsageEnumStruc* struc = (GetUsageEnumStruc*)closure;
 
-  if (StringBeginsWith(key, struc->mSubdomain))
+  if (StringBeginsWith(key, struc->mSubdomain)) {
+    if (struc->mExcludeOfflineFromUsage) {
+      nsCAutoString domain;
+      nsresult rv = nsDOMStorageDBWrapper::GetDomainFromScopeKey(key, domain);
+      if (NS_SUCCEEDED(rv) && IsOfflineAllowed(domain))
+        return PL_DHASH_NEXT;
+    }
+
     struc->mUsage += storageData->mUsageDelta;
+  }
 
   return PL_DHASH_NEXT;
 }
 
 nsresult
 nsDOMStorageMemoryDB::GetUsageInternal(const nsACString& aQuotaDomainDBKey,
+                                       PRBool aExcludeOfflineFromUsage,
                                        PRInt32 *aUsage)
 {
   GetUsageEnumStruc struc;
   struc.mUsage = 0;
+  struc.mExcludeOfflineFromUsage = aExcludeOfflineFromUsage;
   struc.mSubdomain = aQuotaDomainDBKey;
 
   if (mPreloadDB) {
     nsresult rv;
 
-    rv = mPreloadDB->GetUsageInternal(aQuotaDomainDBKey, &struc.mUsage);
+    rv = mPreloadDB->GetUsageInternal(aQuotaDomainDBKey,
+                                      aExcludeOfflineFromUsage, &struc.mUsage);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   mData.EnumerateRead(GetUsageEnum, &struc);
 
   *aUsage = struc.mUsage;
   return NS_OK;
 }
--- a/dom/src/storage/nsDOMStorageMemoryDB.h
+++ b/dom/src/storage/nsDOMStorageMemoryDB.h
@@ -107,16 +107,17 @@ public:
    * Set the value and secure flag for a key in storage.
    */
   nsresult
   SetKey(nsDOMStorage* aStorage,
          const nsAString& aKey,
          const nsAString& aValue,
          PRBool aSecure,
          PRInt32 aQuota,
+         PRBool aExcludeOfflineFromUsage,
          PRInt32* aNewUsage);
 
   /**
    * Set the secure flag for a key in storage. Does nothing if the key was
    * not found.
    */
   nsresult
   SetSecure(nsDOMStorage* aStorage,
@@ -124,16 +125,17 @@ public:
             const PRBool aSecure);
 
   /**
    * Removes a key from storage.
    */
   nsresult
   RemoveKey(nsDOMStorage* aStorage,
             const nsAString& aKey,
+            PRBool aExcludeOfflineFromUsage,
             PRInt32 aKeyUsage);
 
   /**
     * Remove all keys belonging to this storage.
     */
   nsresult
   ClearStorage(nsDOMStorage* aStorage);
 
@@ -162,27 +164,27 @@ public:
    */
   nsresult
   RemoveAll();
 
   /**
     * Returns usage for a storage using its GetQuotaDomainDBKey() as a key.
     */
   nsresult
-  GetUsage(nsDOMStorage* aStorage, PRInt32 *aUsage);
+  GetUsage(nsDOMStorage* aStorage, PRBool aExcludeOfflineFromUsage, PRInt32 *aUsage);
 
   /**
     * Returns usage of the domain and optionaly by any subdomain.
     */
   nsresult
   GetUsage(const nsACString& aDomain, PRBool aIncludeSubDomains, PRInt32 *aUsage);
 
 protected:
 
   nsClassHashtable<nsCStringHashKey, nsInMemoryStorage> mData;
   nsDOMStoragePersistentDB* mPreloadDB;
   PRBool mPreloading;
 
   nsresult
-  GetUsageInternal(const nsACString& aQuotaDomainDBKey, PRInt32 *aUsage);
+  GetUsageInternal(const nsACString& aQuotaDomainDBKey, PRBool aExcludeOfflineFromUsage, PRInt32 *aUsage);
 };
 
 #endif
--- a/dom/src/storage/nsDOMStoragePersistentDB.cpp
+++ b/dom/src/storage/nsDOMStoragePersistentDB.cpp
@@ -81,16 +81,53 @@ nsReverseStringSQLFunction::OnFunctionCa
   rv = outVar->SetAsAUTF8String(result);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aResult = outVar.get();
   outVar.forget();
   return NS_OK;
 }
 
+class nsIsOfflineSQLFunction : public mozIStorageFunction
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS1(nsIsOfflineSQLFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+nsIsOfflineSQLFunction::OnFunctionCall(
+    mozIStorageValueArray *aFunctionArguments, nsIVariant **aResult)
+{
+  nsresult rv;
+
+  nsCAutoString scope;
+  rv = aFunctionArguments->GetUTF8String(0, scope);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCAutoString domain;
+  rv = nsDOMStorageDBWrapper::GetDomainFromScopeKey(scope, domain);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool hasOfflinePermission = IsOfflineAllowed(domain);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIWritableVariant> outVar(do_CreateInstance(
+      NS_VARIANT_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = outVar->SetAsBool(hasOfflinePermission);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aResult = outVar.get();
+  outVar.forget();
+  return NS_OK;
+}
+
 nsresult
 nsDOMStoragePersistentDB::Init()
 {
   nsresult rv;
 
   nsCOMPtr<nsIFile> storageFile;
   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                               getter_AddRefs(storageFile));
@@ -121,20 +158,26 @@ nsDOMStoragePersistentDB::Init()
                             "owner TEXT)"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
         " ON webappsstore2(scope, key)"));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<mozIStorageFunction> function(new nsReverseStringSQLFunction());
-  NS_ENSURE_TRUE(function, NS_ERROR_OUT_OF_MEMORY);
+  nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
+  NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
 
-  rv = mConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function);
+  rv = mConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageFunction> function2(new nsIsOfflineSQLFunction());
+  NS_ENSURE_TRUE(function2, NS_ERROR_OUT_OF_MEMORY);
+
+  rv = mConnection->CreateFunction(NS_LITERAL_CSTRING("ISOFFLINE"), 1, function2);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool exists;
 
   // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
   // to actual webappsstore2 table and drop the obsolete table. First process
   // this newer table upgrade to priority potential duplicates from older
   // storage table.
@@ -239,22 +282,31 @@ nsDOMStoragePersistentDB::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   // remove all keys
   rv = mConnection->CreateStatement(
          NS_LITERAL_CSTRING("DELETE FROM webappsstore2"),
          getter_AddRefs(mRemoveAllStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // check the usage for a given owner
+  // check the usage for a given owner that is an offline-app allowed domain
   rv = mConnection->CreateStatement(
          NS_LITERAL_CSTRING("SELECT SUM(LENGTH(key) + LENGTH(value)) "
                             "FROM webappsstore2 "
                             "WHERE scope GLOB ?1"),
-         getter_AddRefs(mGetUsageStatement));
+         getter_AddRefs(mGetFullUsageStatement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // check the usage for a given owner that is not an offline-app allowed domain
+  rv = mConnection->CreateStatement(
+         NS_LITERAL_CSTRING("SELECT SUM(LENGTH(key) + LENGTH(value)) "
+                            "FROM webappsstore2 "
+                            "WHERE scope GLOB ?1 "
+                            "AND NOT ISOFFLINE(scope)"),
+         getter_AddRefs(mGetOfflineExcludedUsageStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::GetAllKeys(nsDOMStorage* aStorage,
                                      nsTHashtable<nsSessionStorageEntry>* aKeys)
@@ -325,24 +377,25 @@ nsDOMStoragePersistentDB::GetKeyValue(ns
 }
 
 nsresult
 nsDOMStoragePersistentDB::SetKey(nsDOMStorage* aStorage,
                                  const nsAString& aKey,
                                  const nsAString& aValue,
                                  PRBool aSecure,
                                  PRInt32 aQuota,
+                                 PRBool aExcludeOfflineFromUsage,
                                  PRInt32 *aNewUsage)
 {
   mozStorageStatementScoper scope(mGetKeyValueStatement);
 
   PRInt32 usage = 0;
   nsresult rv;
-  if (!aStorage->GetQuotaDomainDBKey().IsEmpty()) {
-    rv = GetUsage(aStorage, &usage);
+  if (!aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage).IsEmpty()) {
+    rv = GetUsage(aStorage, aExcludeOfflineFromUsage, &usage);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   usage += aKey.Length() + aValue.Length();
 
   rv = mGetKeyValueStatement->BindUTF8StringParameter(0,
                                                       aStorage->GetScopeDBKey());
   NS_ENSURE_SUCCESS(rv, rv);
@@ -405,18 +458,18 @@ nsDOMStoragePersistentDB::SetKey(nsDOMSt
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mInsertKeyStatement->BindInt32Parameter(3, aSecure);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mInsertKeyStatement->Execute();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  if (!aStorage->GetQuotaDomainDBKey().IsEmpty()) {
-    mCachedOwner = aStorage->GetQuotaDomainDBKey();
+  if (!aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage).IsEmpty()) {
+    mCachedOwner = aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage);
     mCachedUsage = usage;
   }
 
   *aNewUsage = usage;
 
   return NS_OK;
 }
 
@@ -437,21 +490,22 @@ nsDOMStoragePersistentDB::SetSecure(nsDO
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mSetSecureStatement->Execute();
 }
 
 nsresult
 nsDOMStoragePersistentDB::RemoveKey(nsDOMStorage* aStorage,
                                     const nsAString& aKey,
+                                    PRBool aExcludeOfflineFromUsage,
                                     PRInt32 aKeyUsage)
 {
   mozStorageStatementScoper scope(mRemoveKeyStatement);
 
-  if (aStorage->GetQuotaDomainDBKey() == mCachedOwner) {
+  if (aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage) == mCachedOwner) {
     mCachedUsage -= aKeyUsage;
   }
 
   nsresult rv = mRemoveKeyStatement->BindUTF8StringParameter(
                                                 0, aStorage->GetScopeDBKey());
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mRemoveKeyStatement->BindStringParameter(1, aKey);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -562,64 +616,75 @@ nsDOMStoragePersistentDB::RemoveOwners(c
 nsresult
 nsDOMStoragePersistentDB::RemoveAll()
 {
   mozStorageStatementScoper scope(mRemoveAllStatement);
   return mRemoveAllStatement->Execute();
 }
 
 nsresult
-nsDOMStoragePersistentDB::GetUsage(nsDOMStorage* aStorage, PRInt32 *aUsage)
+nsDOMStoragePersistentDB::GetUsage(nsDOMStorage* aStorage,
+                                   PRBool aExcludeOfflineFromUsage,
+                                   PRInt32 *aUsage)
 {
-  return GetUsageInternal(aStorage->GetQuotaDomainDBKey(), aUsage);
+  return GetUsageInternal(aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage),
+                                                        aExcludeOfflineFromUsage,
+                                                        aUsage);
 }
 
 nsresult
 nsDOMStoragePersistentDB::GetUsage(const nsACString& aDomain,
                                    PRBool aIncludeSubDomains,
                                    PRInt32 *aUsage)
 {
   nsresult rv;
 
   nsCAutoString quotadomainDBKey;
   rv = nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(aDomain,
                                                      aIncludeSubDomains,
+                                                     PR_FALSE,
                                                      quotadomainDBKey);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return GetUsageInternal(quotadomainDBKey, aUsage);
+  return GetUsageInternal(quotadomainDBKey, PR_FALSE, aUsage);
 }
 
 nsresult
 nsDOMStoragePersistentDB::GetUsageInternal(const nsACString& aQuotaDomainDBKey,
+                                           PRBool aExcludeOfflineFromUsage,
                                            PRInt32 *aUsage)
 {
   if (aQuotaDomainDBKey == mCachedOwner) {
     *aUsage = mCachedUsage;
     return NS_OK;
   }
 
-  mozStorageStatementScoper scope(mGetUsageStatement);
+  mozIStorageStatement* statement = aExcludeOfflineFromUsage
+    ? mGetOfflineExcludedUsageStatement : mGetFullUsageStatement;
+
+  mozStorageStatementScoper scope(statement);
 
   nsresult rv;
 
-  rv = mGetUsageStatement->BindUTF8StringParameter(0, aQuotaDomainDBKey +
-      NS_LITERAL_CSTRING("*"));
+  nsCAutoString scopeValue(aQuotaDomainDBKey);
+  scopeValue += NS_LITERAL_CSTRING("*");
+
+  rv = statement->BindUTF8StringParameter(0, scopeValue);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool exists;
-  rv = mGetUsageStatement->ExecuteStep(&exists);
+  rv = statement->ExecuteStep(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!exists) {
     *aUsage = 0;
     return NS_OK;
   }
 
-  rv = mGetUsageStatement->GetInt32(0, aUsage);
+  rv = statement->GetInt32(0, aUsage);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!aQuotaDomainDBKey.IsEmpty()) {
     mCachedOwner = aQuotaDomainDBKey;
     mCachedUsage = *aUsage;
   }
 
   return NS_OK;
--- a/dom/src/storage/nsDOMStoragePersistentDB.h
+++ b/dom/src/storage/nsDOMStoragePersistentDB.h
@@ -78,16 +78,17 @@ public:
    * Set the value and secure flag for a key in storage.
    */
   nsresult
   SetKey(nsDOMStorage* aStorage,
          const nsAString& aKey,
          const nsAString& aValue,
          PRBool aSecure,
          PRInt32 aQuota,
+         PRBool aExcludeOfflineFromUsage,
          PRInt32* aNewUsage);
 
   /**
    * Set the secure flag for a key in storage. Does nothing if the key was
    * not found.
    */
   nsresult
   SetSecure(nsDOMStorage* aStorage,
@@ -95,16 +96,17 @@ public:
             const PRBool aSecure);
 
   /**
    * Removes a key from storage.
    */
   nsresult
   RemoveKey(nsDOMStorage* aStorage,
             const nsAString& aKey,
+            PRBool aExcludeOfflineFromUsage,
             PRInt32 aKeyUsage);
 
   /**
     * Remove all keys belonging to this storage.
     */
   nsresult ClearStorage(nsDOMStorage* aStorage);
 
   /**
@@ -126,17 +128,17 @@ public:
    */
   nsresult
   RemoveAll();
 
   /**
     * Returns usage for a storage using its GetQuotaDomainDBKey() as a key.
     */
   nsresult
-  GetUsage(nsDOMStorage* aStorage, PRInt32 *aUsage);
+  GetUsage(nsDOMStorage* aStorage, PRBool aExcludeOfflineFromUsage, PRInt32 *aUsage);
 
   /**
     * Returns usage of the domain and optionaly by any subdomain.
     */
   nsresult
   GetUsage(const nsACString& aDomain, PRBool aIncludeSubDomains, PRInt32 *aUsage);
 
   /**
@@ -152,20 +154,21 @@ protected:
   nsCOMPtr<mozIStorageStatement> mGetKeyValueStatement;
   nsCOMPtr<mozIStorageStatement> mInsertKeyStatement;
   nsCOMPtr<mozIStorageStatement> mUpdateKeyStatement;
   nsCOMPtr<mozIStorageStatement> mSetSecureStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveKeyStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveOwnerStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveStorageStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveAllStatement;
-  nsCOMPtr<mozIStorageStatement> mGetUsageStatement;
+  nsCOMPtr<mozIStorageStatement> mGetOfflineExcludedUsageStatement;
+  nsCOMPtr<mozIStorageStatement> mGetFullUsageStatement;
 
   nsCString mCachedOwner;
   PRInt32 mCachedUsage;
 
   friend class nsDOMStorageDBWrapper;
   friend class nsDOMStorageMemoryDB;
   nsresult
-  GetUsageInternal(const nsACString& aQuotaDomainDBKey, PRInt32 *aUsage);
+  GetUsageInternal(const nsACString& aQuotaDomainDBKey, PRBool aExcludeOfflineFromUsage, PRInt32 *aUsage);
 };
 
 #endif /* nsDOMStorageDB_h___ */
--- a/dom/tests/mochitest/localstorage/Makefile.in
+++ b/dom/tests/mochitest/localstorage/Makefile.in
@@ -72,16 +72,17 @@ include $(topsrcdir)/config/rules.mk
     test_localStorageOriginsDiff.html \
     test_localStorageOriginsPortDiffs.html \
     test_localStorageOriginsDomainDiffs.html \
     test_localStorageOriginsSchemaDiffs.html \
     test_localStorageReplace.html \
     test_localStorageQuota.html \
     test_localStorageQuotaPrivateBrowsing.html \
     test_localStorageQuotaSessionOnly.html \
+    test_localStorageQuotaSessionOnly2.html \
     test_localStorageKeyOrder.html \
     test_removeOwnersAPI.html \
     test_removeOwnersAPISessionOnly.html \
     test_storageConstructor.html \
     $(NULL)
     
 _CHROME_FILES = \
     test_localStorageFromChrome.xhtml \
--- a/dom/tests/mochitest/localstorage/frameQuota.html
+++ b/dom/tests/mochitest/localstorage/frameQuota.html
@@ -75,17 +75,17 @@ function doStep()
             localStorage.setItem(keyName, "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
           }, DOM_QUOTA_REACHED);
           is(localStorage.getItem(keyName), "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "Key "+keyName+" left unchanged");
           break;
       }
 
       break;
 
-    case "":
+    default:
       switch (operation)
       {
         case "clear":
           localStorage.clear();
           break;
       }
 
       break;
--- a/dom/tests/mochitest/localstorage/test_localStorageQuota.html
+++ b/dom/tests/mochitest/localstorage/test_localStorageQuota.html
@@ -9,17 +9,38 @@
 
 <script type="text/javascript">
 
 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
 var currentTest = 1;
 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
             .getService(Components.interfaces.nsIPrefBranch);
-var quota;
+var quota, quotaOffline;
+
+function addOfflineApp(url)
+{
+  var permissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+    .getService(Components.interfaces.nsIPermissionManager);
+  var uri = Components.classes["@mozilla.org/network/io-service;1"]
+    .getService(Components.interfaces.nsIIOService)
+    .newURI(url, null, null);
+  permissionManager.add(uri, "offline-app",
+                        Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
+}
+
+function removeOfflineApp(url)
+{
+  var permissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+    .getService(Components.interfaces.nsIPermissionManager);
+  var uri = Components.classes["@mozilla.org/network/io-service;1"]
+    .getService(Components.interfaces.nsIIOService)
+    .newURI(url, null, null);
+  permissionManager.remove(uri.host, "offline-app");
+}
 
 function doNextTest()
 {
   slave = frame;
 
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   switch (currentTest)
@@ -28,16 +49,22 @@ function doNextTest()
     // set a 500 bytes key with name length 1 (allocate 501 bytes)
     case 1:
       try {
         quota = prefs.getIntPref("dom.storage.default_quota");
       } catch (ex) {
         quota = 5*1024;
       }
       prefs.setIntPref("dom.storage.default_quota", 1);
+      try {
+        quotaOffline = prefs.getIntPref("offline-apps.quota.max");
+      } catch (ex) {
+        quotaOffline = 200*1024;
+      }
+      prefs.setIntPref("offline-apps.quota.max", 2);
 
       slaveOrigin = "http://example.com";
       slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
       break;
 
     // In subdomain now set another key with length 500 bytes, i.e.
     // allocate 501 bytes
     case 2:
@@ -78,38 +105,100 @@ function doNextTest()
     // Now try again to set 500 bytes key, it must succeed.
     case 7:
       slaveOrigin = "https://test2.example.com";
       slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&success";
       break;
 
     case 8:
       // Do a clean up...
-      // TODO Bug 455070, use just ?clear what invokes call
-      // of clear() in the target frame. W/o clear method we must
-      // call clear implemented as removeItem for each item in
-      // the localStorage.
       slaveOrigin = "http://example.com";
-      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear&A&";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
       break;
 
     case 9:
       // Do a clean up...
       slaveOrigin = "http://test1.example.com";
-      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear&B&";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
       break;
 
     case 10:
       // Do a clean up...
       slaveOrigin = "https://test2.example.com";
-      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear&C&";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
       break;
 
     case 11:
+      // test1.example.com is now using its own offline app quota
+      addOfflineApp("http://test1.example.com");
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
+      break;
+
+    case 12:
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
+      break;
+
+    case 13:
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&success";
+      // Now we have 1503 bytes stored, this exceeds the default storage quota
+      break;
+
+    case 14:
+      // Now check that upper level domain that is not set as an offline app
+      // domain is allowed to store data and is using the default quota
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
+      break;
+
+    case 15:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
+      break;
+
+    case 16:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&failure";
+      break;
+
+    case 17:
+      slaveOrigin = "http://test2.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&D&failure";
+      break;
+
+    case 18:
+      // check an offline app domain may store some more data
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&D&success";
+      break;
+
+    case 19:
+      // check an offline app domain is using its own (larger) quota
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&E&failure";
+      break;
+
+    case 20:
+      // Do a clean up...
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
+      break;
+
+    case 21:
+      // Do a clean up...
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
+      break;
+
+    default: // end
+      removeOfflineApp("http://test1.example.com");
       prefs.setIntPref("dom.storage.default_quota", quota);
+      prefs.setIntPref("offline-apps.quota.max", quotaOffline);
       SimpleTest.finish();
   }
 
   ++currentTest;
 }
 
 function doStep()
 {
--- a/dom/tests/mochitest/localstorage/test_localStorageQuotaSessionOnly.html
+++ b/dom/tests/mochitest/localstorage/test_localStorageQuotaSessionOnly.html
@@ -17,17 +17,38 @@ var prefs = Components.classes["@mozilla
 var io = Components.classes["@mozilla.org/network/io-service;1"]
   .getService(Components.interfaces.nsIIOService);
 var uri = io.newURI(window.location, "", null);
 var cp = Components.classes["@mozilla.org/cookie/permission;1"]
   .getService(Components.interfaces.nsICookiePermission);
 
 cp.setAccess(uri, Components.interfaces.nsICookiePermission.ACCESS_SESSION);
 
-var quota;
+var quota, quotaOffline;
+
+function addOfflineApp(url)
+{
+  var permissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+    .getService(Components.interfaces.nsIPermissionManager);
+  var uri = Components.classes["@mozilla.org/network/io-service;1"]
+    .getService(Components.interfaces.nsIIOService)
+    .newURI(url, null, null);
+  permissionManager.add(uri, "offline-app",
+                        Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
+}
+
+function removeOfflineApp(url)
+{
+  var permissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+    .getService(Components.interfaces.nsIPermissionManager);
+  var uri = Components.classes["@mozilla.org/network/io-service;1"]
+    .getService(Components.interfaces.nsIIOService)
+    .newURI(url, null, null);
+  permissionManager.remove(uri.host, "offline-app");
+}
 
 function doNextTest()
 {
   slave = frame;
 
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   switch (currentTest)
@@ -36,16 +57,23 @@ function doNextTest()
     // set a 500 bytes key with name length 1 (allocate 501 bytes)
     case 1:
       try {
         quota = prefs.getIntPref("dom.storage.default_quota");
       } catch (ex) {
         quota = 5*1024;
       }
       prefs.setIntPref("dom.storage.default_quota", 1);
+      try {
+        quotaOffline = prefs.getIntPref("offline-apps.quota.max");
+      } catch (ex) {
+        quotaOffline = 200*1024;
+      }
+      prefs.setIntPref("offline-apps.quota.max", 2);
+
 
       slaveOrigin = "http://example.com";
       slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?add&A&success";
       break;
 
     // In subdomain now set another key with length 500 bytes, i.e.
     // allocate 501 bytes
     case 2:
@@ -86,38 +114,100 @@ function doNextTest()
     // Now try again to set 500 bytes key, it must succeed.
     case 7:
       slaveOrigin = "https://test2.example.com";
       slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?add&C&success";
       break;
 
     case 8:
       // Do a clean up...
-      // TODO Bug 455070, use just ?clear what invokes call
-      // of clear() in the target frame. W/o clear method we must
-      // call clear implemented as removeItem for each item in
-      // the localStorage.
       slaveOrigin = "http://example.com";
-      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?clear&A&";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?clear";
       break;
 
     case 9:
       // Do a clean up...
       slaveOrigin = "http://test1.example.com";
-      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?clear&B&";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?clear";
       break;
 
     case 10:
       // Do a clean up...
       slaveOrigin = "https://test2.example.com";
-      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?clear&C&";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?clear";
       break;
 
     case 11:
+      // test1.example.com is now using its own offline app quota
+      addOfflineApp("http://test1.example.com");
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
+      break;
+
+    case 12:
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
+      break;
+
+    case 13:
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&success";
+      // Now we have 1503 bytes stored, this exceeds the default storage quota
+      break;
+
+    case 14:
+      // Now check that upper level domain that is not set as an offline app
+      // domain is allowed to store data and is using the default quota
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
+      break;
+
+    case 15:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
+      break;
+
+    case 16:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&failure";
+      break;
+
+    case 17:
+      slaveOrigin = "http://test2.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&D&failure";
+      break;
+
+    case 18:
+      // check an offline app domain may store some more data
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&D&success";
+      break;
+
+    case 19:
+      // check an offline app domain is using its own (larger) quota
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&E&failure";
+      break;
+
+    case 20:
+      // Do a clean up...
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
+      break;
+
+    case 21:
+      // Do a clean up...
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
+      break;
+
+    default:
+      removeOfflineApp("http://test1.example.com");
       prefs.setIntPref("dom.storage.default_quota", quota);
+      prefs.setIntPref("offline-apps.quota.max", quotaOffline);
       cp.setAccess(uri, Components.interfaces.nsICookiePermission.ACCESS_DEFAULT);
       SimpleTest.finish();
   }
 
   ++currentTest;
 }
 
 function doStep()
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/localstorage/test_localStorageQuotaSessionOnly2.html
@@ -0,0 +1,227 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>localStorage and DOM quota test</title>
+
+<script type="text/javascript" src="/MochiKit/packed.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="interOriginTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+var currentTest = 1;
+var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+            .getService(Components.interfaces.nsIPrefBranch);
+var io = Components.classes["@mozilla.org/network/io-service;1"]
+  .getService(Components.interfaces.nsIIOService);
+var uri = io.newURI(window.location, "", null);
+var cp = Components.classes["@mozilla.org/cookie/permission;1"]
+  .getService(Components.interfaces.nsICookiePermission);
+
+var quota, quotaOffline;
+
+function addOfflineApp(url)
+{
+  var permissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+    .getService(Components.interfaces.nsIPermissionManager);
+  var uri = Components.classes["@mozilla.org/network/io-service;1"]
+    .getService(Components.interfaces.nsIIOService)
+    .newURI(url, null, null);
+  permissionManager.add(uri, "offline-app",
+                        Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
+}
+
+function removeOfflineApp(url)
+{
+  var permissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+    .getService(Components.interfaces.nsIPermissionManager);
+  var uri = Components.classes["@mozilla.org/network/io-service;1"]
+    .getService(Components.interfaces.nsIIOService)
+    .newURI(url, null, null);
+  permissionManager.remove(uri.host, "offline-app");
+}
+
+function doNextTest()
+{
+  slave = frame;
+
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  switch (currentTest)
+  {
+    // Initialy setup the quota to testing value of 1024B and
+    // set a 500 bytes key with name length 1 (allocate 501 bytes)
+    case 1:
+      try {
+        quota = prefs.getIntPref("dom.storage.default_quota");
+      } catch (ex) {
+        quota = 5*1024;
+      }
+      prefs.setIntPref("dom.storage.default_quota", 1);
+      try {
+        quotaOffline = prefs.getIntPref("offline-apps.quota.max");
+      } catch (ex) {
+        quotaOffline = 200*1024;
+      }
+      prefs.setIntPref("offline-apps.quota.max", 2);
+
+
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?add&A&success";
+      break;
+
+    // In subdomain now set another key with length 500 bytes, i.e.
+    // allocate 501 bytes
+    case 2:
+      cp.setAccess(uri, Components.interfaces.nsICookiePermission.ACCESS_SESSION);
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?add&B&success";
+      break;
+
+    // Try to set the same key value again to check we don't fail
+    // even 1002 bytes has already been exhausted from the quota
+    // We just change the value of an existing key.
+    case 3:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?add&B&success";
+      break;
+
+    // Try to set the same key to a larger value that would lead to
+    // quota reach and check that the value is still the old one
+    case 4:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?add2&B&failure";
+      break;
+
+    // Try to set a new 500 bytes key
+    // and check we fail because we are over the quota
+    case 5:
+      slaveOrigin = "https://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?add&C&failure";
+      break;
+
+    // Remove the key inherited from the non-session-only database
+    case 6:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?remove&A&success";
+      break;
+
+    // Now try again to set 500 bytes key, it must succeed.
+    case 7:
+      slaveOrigin = "https://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?add&C&success";
+      break;
+
+    case 8:
+      // Do a clean up...
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuotaSessionOnly.html?clear";
+      cp.setAccess(uri, Components.interfaces.nsICookiePermission.ACCESS_DEFAULT);
+      break;
+
+    case 9:
+      // test1.example.com is now using its own offline app quota
+      addOfflineApp("http://test1.example.com");
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
+      break;
+
+    case 10:
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
+      break;
+
+    case 11:
+      cp.setAccess(uri, Components.interfaces.nsICookiePermission.ACCESS_SESSION);
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&success";
+      // Now we have 1503 bytes stored, this exceeds the default storage quota
+      break;
+
+    case 12:
+      // Now check that upper level domain that is not set as an offline app
+      // domain is allowed to store data and is using the default quota
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
+      break;
+
+    case 13:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
+      break;
+
+    case 14:
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&failure";
+      break;
+
+    case 15:
+      slaveOrigin = "http://test2.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&D&failure";
+      break;
+
+    case 16:
+      // Check an offline app domain may store some more data
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&D&success";
+      break;
+
+    case 17:
+      // Check an offline app domain is using its own (larger) quota
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&E&failure";
+      break;
+
+    case 18:
+      // This test checks we correctly subtract A from the usage. A is inherited
+      // from the persistent database before we switch to session-only cookies
+      // mode
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?remove&A&success";
+      break;
+
+    case 19:
+      // now we shold have more space to store a new value
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?add&E&success";
+      break;
+
+    case 20:
+      // Do a clean up...
+      slaveOrigin = "http://example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
+      break;
+
+    case 21:
+      // Do a clean up...
+      slaveOrigin = "http://test1.example.com";
+      slave.location = slaveOrigin + slavePath + "frameQuota.html?clear";
+      break;
+
+    default:
+      removeOfflineApp("http://test1.example.com");
+      prefs.setIntPref("dom.storage.default_quota", quota);
+      prefs.setIntPref("offline-apps.quota.max", quotaOffline);
+      cp.setAccess(uri, Components.interfaces.nsICookiePermission.ACCESS_DEFAULT);
+      SimpleTest.finish();
+  }
+
+  ++currentTest;
+}
+
+function doStep()
+{
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body onload="doNextTest();">
+  <iframe src="" name="frame"></iframe>
+</body>
+</html>