Bug 397416: Raise globalStorage quota for domains with the offline-app permission. r=enndeakin, sr=dveditz, blocking1.9=sicking
authordcamp@mozilla.com
Tue, 22 Jan 2008 18:54:06 -0800
changeset 10561 1863c07feffa19bdaab7ebb7cf5ea432eddf2b3b
parent 10560 51957bb95f637afcfe0b1858002e8901b4270b13
child 10562 997f039a29bc72b36f17e9acf7dcdc786b91136a
push idunknown
push userunknown
push dateunknown
reviewersenndeakin, dveditz
bugs397416
milestone1.9b3pre
Bug 397416: Raise globalStorage quota for domains with the offline-app permission. r=enndeakin, sr=dveditz, blocking1.9=sicking
dom/src/storage/Makefile.in
dom/src/storage/nsDOMStorage.cpp
dom/src/storage/nsDOMStorageDB.cpp
dom/src/storage/nsDOMStorageDB.h
modules/libpref/src/init/all.js
uriloader/prefetch/nsIOfflineCacheUpdate.idl
--- a/dom/src/storage/Makefile.in
+++ b/dom/src/storage/Makefile.in
@@ -55,16 +55,17 @@ REQUIRES       = xpcom         \
                  js            \
                  layout        \
                  locale        \
                  necko         \
                  pref          \
                  unicharutil   \
                  widget        \
                  xpconnect     \
+                 prefetch      \
                  $(NULL)
 
 ifdef MOZ_STORAGE
 REQUIRES += storage
 endif
 
 CPPSRCS =                  \
        nsDOMStorage.cpp    \
--- a/dom/src/storage/nsDOMStorage.cpp
+++ b/dom/src/storage/nsDOMStorage.cpp
@@ -49,28 +49,36 @@
 #include "nsIURI.h"
 #include "nsReadableUtils.h"
 #include "nsIObserverService.h"
 #include "nsNetUtil.h"
 #include "nsIPrefBranch.h"
 #include "nsICookiePermission.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;
 
 static const PRUint32 DEFAULT_QUOTA = 5 * 1024;
+// Be generous with offline apps by default...
+static const PRUint32 DEFAULT_OFFLINE_APP_QUOTA = 200 * 1024;
+// ... but warn if it goes over this amount
+static const PRUint32 DEFAULT_OFFLINE_WARN_QUOTA = 50 * 1024;
 
 static const char kPermissionType[] = "cookie";
 static const char kStorageEnabled[] = "dom.storage.enabled";
 static const char kDefaultQuota[] = "dom.storage.default_quota";
 static const char kCookiesBehavior[] = "network.cookie.cookieBehavior";
 static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
+static const char kOfflineAppWarnQuota[] = "offline-apps.quota.warn";
+static const char kOfflineAppQuota[] = "offline-apps.quota.max";
 
 //
 // Helper that tells us whether the caller is secure or not.
 //
 
 static PRBool
 IsCallerSecure()
 {
@@ -99,21 +107,54 @@ IsCallerSecure()
   }
 
   PRBool isHttps = PR_FALSE;
   nsresult rv = innerUri->SchemeIs("https", &isHttps);
 
   return NS_SUCCEEDED(rv) && isHttps;
 }
 
-static PRInt32
-GetQuota(const nsAString &domain)
+
+// 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 nsAString &aDomain, PRInt32 *aQuota, PRInt32 *aWarnQuota)
 {
+  // Fake a URI for the permission manager
+  nsCOMPtr<nsIURI> uri;
+  NS_NewURI(getter_AddRefs(uri), NS_LITERAL_STRING("http://") + aDomain);
+
+  if (uri) {
+    nsCOMPtr<nsIPermissionManager> permissionManager =
+      do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+
+    PRUint32 perm;
+    if (permissionManager &&
+        NS_SUCCEEDED(permissionManager->TestExactPermission(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_WARN_QUOTA * 1024));
+
+      if (perm == nsIOfflineCacheUpdateService::ALLOW_NO_WARN) {
+        *aWarnQuota = -1;
+      } else {
+        *aWarnQuota = ((PRInt32)nsContentUtils::GetIntPref(kOfflineAppWarnQuota,
+                                                           DEFAULT_OFFLINE_WARN_QUOTA) * 1024);
+      }
+      return;
+    }
+  }
+
   // FIXME: per-domain quotas?
-  return ((PRInt32)nsContentUtils::GetIntPref(kDefaultQuota, DEFAULT_QUOTA) * 1024);
+  *aQuota = ((PRInt32)nsContentUtils::GetIntPref(kDefaultQuota,
+                                                 DEFAULT_QUOTA) * 1024);
+  *aWarnQuota = -1;
 }
 
 nsSessionStorageEntry::nsSessionStorageEntry(KeyTypePointer aStr)
   : nsStringHashKey(aStr), mItem(nsnull)
 {
 }
 
 nsSessionStorageEntry::nsSessionStorageEntry(const nsSessionStorageEntry& aToCopy)
@@ -766,22 +807,47 @@ nsDOMStorage::SetDBValue(const nsAString
         currentDomain = mDomain;
       else
         return NS_ERROR_DOM_SECURITY_ERR;
     }
   } else {
     currentDomain = mDomain;
   }
 
+  PRInt32 quota;
+  PRInt32 warnQuota;
+  GetQuota(currentDomain, &quota, &warnQuota);
+
+  PRInt32 usage;
   rv = gStorageDB->SetKey(mDomain, aKey, aValue, aSecure,
-                          currentDomain, GetQuota(currentDomain));
+                          currentDomain, quota, &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;
+    nsCOMPtr<nsIJSContextStack> stack =
+      do_GetService("@mozilla.org/js/xpc/ContextStack;1");
+    if (stack && NS_SUCCEEDED(stack->Peek(&cx)) && cx) {
+      nsCOMPtr<nsIScriptContext> scriptContext;
+      scriptContext = GetScriptContextFromJSContext(cx);
+      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",
+                        currentDomain.get());
+  }
+
   BroadcastChangeNotification();
 #endif
 
   return NS_OK;
 }
 
 nsresult
 nsDOMStorage::SetSecure(const nsAString& aKey, PRBool aSecure)
--- a/dom/src/storage/nsDOMStorageDB.cpp
+++ b/dom/src/storage/nsDOMStorageDB.cpp
@@ -266,17 +266,18 @@ nsDOMStorageDB::GetKeyValue(const nsAStr
 }
 
 nsresult
 nsDOMStorageDB::SetKey(const nsAString& aDomain,
                        const nsAString& aKey,
                        const nsAString& aValue,
                        PRBool aSecure,
                        const nsAString& aOwner,
-                       PRInt32 aQuota)
+                       PRInt32 aQuota,
+                       PRInt32 *aNewUsage)
 {
   mozStorageStatementScoper scope(mGetKeyValueStatement);
  
   PRInt32 usage = 0;
   nsresult rv;
   if (!aOwner.IsEmpty()) {
     rv = GetUsage(aOwner, &usage);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -357,16 +358,18 @@ nsDOMStorageDB::SetKey(const nsAString& 
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (!aOwner.IsEmpty()) {
     mCachedOwner = aOwner;
     mCachedUsage = usage;
   }
 
+  *aNewUsage = usage;
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageDB::SetSecure(const nsAString& aDomain,
                           const nsAString& aKey,
                           const PRBool aSecure)
 {
--- a/dom/src/storage/nsDOMStorageDB.h
+++ b/dom/src/storage/nsDOMStorageDB.h
@@ -80,17 +80,18 @@ public:
    * Set the value and secure flag for a key in storage.
    */
   nsresult
   SetKey(const nsAString& aDomain,
          const nsAString& aKey,
          const nsAString& aValue,
          PRBool aSecure,
          const nsAString& aOwner,
-         PRInt32 aQuota);
+         PRInt32 aQuota,
+         PRInt32* aNewUsage);
 
   /**
    * Set the secure flag for a key in storage. Does nothing if the key was
    * not found.
    */
   nsresult
   SetSecure(const nsAString& aDomain,
             const nsAString& aKey,
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -66,16 +66,24 @@ pref("browser.cache.memory.enable",     
 pref("browser.cache.disk_cache_ssl",        false);
 // 0 = once-per-session, 1 = each-time, 2 = never, 3 = when-appropriate/automatically
 pref("browser.cache.check_doc_frequency",   3);
 
 pref("browser.cache.offline.enable",           true);
 // offline cache capacity in kilobytes
 pref("browser.cache.offline.capacity",         10240);
 
+// offline apps should be limited to this much data in global storage
+// (in kilobytes)
+pref("offline-apps.quota.max",        204800);
+
+// the user should be warned if offline app disk usage exceeds this amount
+// (in kilobytes)
+pref("offline-apps.quota.warn",        51200);
+
 // Fastback caching - if this pref is negative, then we calculate the number
 // of content viewers to cache based on the amount of available memory.
 pref("browser.sessionhistory.max_total_viewers", -1);
 
 pref("browser.display.use_document_fonts",  1);  // 0 = never, 1 = quick, 2 = always
 pref("browser.display.use_document_colors", true);
 pref("browser.display.use_system_colors",   false);
 pref("browser.display.foreground_color",    "#000000");
--- a/uriloader/prefetch/nsIOfflineCacheUpdate.idl
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -200,16 +200,29 @@ interface nsIOfflineCacheUpdate : nsISup
    *        the observer to remove.
    */
   void removeObserver(in nsIOfflineCacheUpdateObserver aObserver);
 };
 
 [scriptable, uuid(3abee04b-5bbb-4405-b659-35f780e38da0)]
 interface nsIOfflineCacheUpdateService : nsISupports {
     /**
+     * Constants for the offline-app permission.
+     *
+     * XXX: This isn't a great place for this, but it's really the only
+     * private offline-app-related interface
+     */
+
+    /**
+     * Allow the domain to use offline APIs, and don't warn about excessive
+     * usage.
+     */
+    const unsigned long ALLOW_NO_WARN = 3;
+
+    /**
      * Access to the list of cache updates that have been scheduled.
      */
     readonly attribute unsigned long numUpdates;
     nsIOfflineCacheUpdate getUpdate(in unsigned long index);
 
     /**
      * Schedule a cache update for a given offline manifest.  If an
      * existing update is scheduled or running, that update will be returned.