Bug 650995 - Support max_entry_size prefs for disk & memory cache, r=michal.novotny
authorbjarne@runitsoft.com
Mon, 27 Jun 2011 14:21:15 +0200
changeset 71874 c828b73a419ac447f304317682255307499b3512
parent 71821 aee9017b0b4fb4e5c962d83d0ae8d038a2df62f0
child 71875 76cbaa594619a2385af50768042683fa285aaa52
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmichal.novotny
bugs650995
milestone7.0a1
Bug 650995 - Support max_entry_size prefs for disk & memory cache, r=michal.novotny
modules/libpref/src/init/all.js
netwerk/cache/nsCacheService.cpp
netwerk/cache/nsCacheService.h
netwerk/cache/nsDiskCacheDevice.cpp
netwerk/cache/nsDiskCacheDevice.h
netwerk/cache/nsDiskCacheMap.h
netwerk/cache/nsMemoryCacheDevice.cpp
netwerk/cache/nsMemoryCacheDevice.h
netwerk/test/unit/test_bug650955.js
netwerk/test/unit/xpcshell.ini
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -61,19 +61,25 @@ pref("browser.bookmarks.max_backups",   
 
 pref("browser.cache.disk.enable",           true);
 // Is this the first-time smartsizing has been introduced?
 pref("browser.cache.disk.smart_size.first_run", true);
 // Does the user want smart-sizing?
 pref("browser.cache.disk.smart_size.enabled", true);
 // Size explicitly set by the user. Used when smart_size.enabled == false
 pref("browser.cache.disk.capacity",         256000);
+// User-controllable max-size for entries in disk-cache. Regardless of this
+// setting, no entries bigger than 1/8 of disk-cache will be cached
+pref("browser.cache.disk.max_entry_size",    5120);
 pref("browser.cache.memory.enable",         true);
+// -1 = determine dynamically, 0 = none, n = memory capacity in kilobytes
 //pref("browser.cache.memory.capacity",     -1);
-// -1 = determine dynamically, 0 = none, n = memory capacity in kilobytes
+// User-controllable max-size for entries in mem-cache. Regardless of this
+// setting, no entries bigger than 90% of the mem-cache will be cached
+pref("browser.cache.memory.max_entry_size",  5120);
 pref("browser.cache.disk_cache_ssl",        true);
 // 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",         512000);
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -111,24 +111,26 @@ static const char * observerList[] = {
     NS_PRIVATE_BROWSING_SWITCH_TOPIC
 };
 static const char * prefList[] = { 
 #ifdef NECKO_DISK_CACHE
     DISK_CACHE_ENABLE_PREF,
     DISK_CACHE_SMART_SIZE_ENABLED_PREF,
     DISK_CACHE_CAPACITY_PREF,
     DISK_CACHE_DIR_PREF,
+    DISK_CACHE_MAX_ENTRY_SIZE_PREF,
 #endif
 #ifdef NECKO_OFFLINE_CACHE
     OFFLINE_CACHE_ENABLE_PREF,
     OFFLINE_CACHE_CAPACITY_PREF,
     OFFLINE_CACHE_DIR_PREF,
 #endif
     MEMORY_CACHE_ENABLE_PREF,
-    MEMORY_CACHE_CAPACITY_PREF
+    MEMORY_CACHE_CAPACITY_PREF,
+    MEMORY_CACHE_MAX_ENTRY_SIZE_PREF
 };
 
 // Cache sizes, in KB
 const PRInt32 DEFAULT_CACHE_SIZE = 250 * 1024;  // 250 MB
 const PRInt32 MIN_CACHE_SIZE = 50 * 1024;       //  50 MB
 const PRInt32 MAX_CACHE_SIZE = 1024 * 1024;     //   1 GB
 // Default cache size was 50 MB for many years until FF 4:
 const PRInt32 PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
@@ -138,58 +140,64 @@ class nsCacheProfilePrefObserver : publi
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
 
     nsCacheProfilePrefObserver()
         : mHaveProfile(PR_FALSE)
         , mDiskCacheEnabled(PR_FALSE)
         , mDiskCacheCapacity(0)
+        , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
         , mOfflineCacheEnabled(PR_FALSE)
         , mOfflineCacheCapacity(0)
         , mMemoryCacheEnabled(PR_TRUE)
         , mMemoryCacheCapacity(-1)
+        , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
         , mInPrivateBrowsing(PR_FALSE)
     {
     }
 
     virtual ~nsCacheProfilePrefObserver() {}
     
     nsresult        Install();
     void            Remove();
     nsresult        ReadPrefs(nsIPrefBranch* branch);
     
     PRBool          DiskCacheEnabled();
     PRInt32         DiskCacheCapacity()         { return mDiskCacheCapacity; }
     void            SetDiskCacheCapacity(PRInt32);
+    PRInt32         DiskCacheMaxEntrySize()     { return mDiskCacheMaxEntrySize; }
     nsILocalFile *  DiskCacheParentDirectory()  { return mDiskCacheParentDirectory; }
 
     PRBool          OfflineCacheEnabled();
     PRInt32         OfflineCacheCapacity()         { return mOfflineCacheCapacity; }
     nsILocalFile *  OfflineCacheParentDirectory()  { return mOfflineCacheParentDirectory; }
     
     PRBool          MemoryCacheEnabled();
     PRInt32         MemoryCacheCapacity();
+    PRInt32         MemoryCacheMaxEntrySize()     { return mMemoryCacheMaxEntrySize; }
 
     static PRUint32 GetSmartCacheSize(const nsAString& cachePath);
 
 private:
     bool                    PermittedToSmartSize(nsIPrefBranch*, PRBool firstRun);
     PRBool                  mHaveProfile;
     
     PRBool                  mDiskCacheEnabled;
     PRInt32                 mDiskCacheCapacity; // in kilobytes
+    PRInt32                 mDiskCacheMaxEntrySize; // in kilobytes
     nsCOMPtr<nsILocalFile>  mDiskCacheParentDirectory;
 
     PRBool                  mOfflineCacheEnabled;
     PRInt32                 mOfflineCacheCapacity; // in kilobytes
     nsCOMPtr<nsILocalFile>  mOfflineCacheParentDirectory;
     
     PRBool                  mMemoryCacheEnabled;
     PRInt32                 mMemoryCacheCapacity; // in kilobytes
+    PRInt32                 mMemoryCacheMaxEntrySize; // in kilobytes
 
     PRBool                  mInPrivateBrowsing;
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheProfilePrefObserver, nsIObserver)
 
 // Runnable sent to main thread after the cache IO thread calculates available
 // disk space, so that there is no race in setting mDiskCacheCapacity.
@@ -446,17 +454,27 @@ nsCacheProfilePrefObserver::Observe(nsIS
                 rv = nsCacheService::DispatchToCacheIOThread(event);
             } else {
                 // Smart sizing switched off: use user specified size
                 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
                 if (NS_FAILED(rv)) 
                     return rv;
                 mDiskCacheCapacity = NS_MAX(0, newCapacity);
                 nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
-            } 
+            }
+        } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
+            PRInt32 newMaxSize;
+            rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+                                    &newMaxSize);
+            if (NS_FAILED(rv)) 
+                return rv;
+
+            mDiskCacheMaxEntrySize = NS_MAX(-1, newMaxSize);
+            nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
+          
 #if 0            
         } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
             // XXX We probaby don't want to respond to this pref except after
             // XXX profile changes.  Ideally, there should be somekind of user
             // XXX notification that the pref change won't take effect until
             // XXX the next time the profile changes (browser launch)
 #endif            
         } else 
@@ -499,16 +517,25 @@ nsCacheProfilePrefObserver::Observe(nsIS
             nsCacheService::SetMemoryCache();
             
         } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
 
             mMemoryCacheCapacity = -1;
             (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
                                       &mMemoryCacheCapacity);
             nsCacheService::SetMemoryCache();
+        } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
+            PRInt32 newMaxSize;
+            rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+                                     &newMaxSize);
+            if (NS_FAILED(rv)) 
+                return rv;
+            
+            mMemoryCacheMaxEntrySize = NS_MAX(-1, newMaxSize);
+            nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
         }
     } else if (!strcmp(NS_PRIVATE_BROWSING_SWITCH_TOPIC, topic)) {
         if (!strcmp(NS_PRIVATE_BROWSING_ENTER, data.get())) {
             mInPrivateBrowsing = PR_TRUE;
 
             nsCacheService::OnEnterExitPrivateBrowsing();
 
 #ifdef NECKO_DISK_CACHE
@@ -652,16 +679,20 @@ nsCacheProfilePrefObserver::ReadPrefs(ns
         mDiskCacheEnabled = PR_TRUE;  // presume disk cache is enabled
         (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
     }
 
     mDiskCacheCapacity = DISK_CACHE_CAPACITY;
     (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
     mDiskCacheCapacity = NS_MAX(0, mDiskCacheCapacity);
 
+    (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+                              &mDiskCacheMaxEntrySize);
+    mDiskCacheMaxEntrySize = NS_MAX(-1, mDiskCacheMaxEntrySize);
+    
     (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF,     // ignore error
                                    NS_GET_IID(nsILocalFile),
                                    getter_AddRefs(mDiskCacheParentDirectory));
     
     if (!mDiskCacheParentDirectory) {
         nsCOMPtr<nsIFile>  directory;
 
         // try to get the disk cache parent directory
@@ -786,17 +817,20 @@ nsCacheProfilePrefObserver::ReadPrefs(ns
 #endif // !NECKO_OFFLINE_CACHE
     
     // read memory cache device prefs
     (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
 
     mMemoryCacheCapacity = -1;
     (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
                               &mMemoryCacheCapacity);
-        
+
+    (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+                              &mMemoryCacheMaxEntrySize);
+    mMemoryCacheMaxEntrySize = NS_MAX(-1, mMemoryCacheMaxEntrySize);
     return rv;
 }
 
 nsresult
 nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
 {
     if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
     return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
@@ -1326,16 +1360,17 @@ nsCacheService::CreateDiskDevice()
     if (mDiskDevice)        return NS_OK;
 
     mDiskDevice = new nsDiskCacheDevice;
     if (!mDiskDevice)       return NS_ERROR_OUT_OF_MEMORY;
 
     // set the preferences
     mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
     mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
+    mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
     
     nsresult rv = mDiskDevice->Init();
     if (NS_FAILED(rv)) {
 #if DEBUG
         printf("###\n");
         printf("### mDiskDevice->Init() failed (0x%.8x)\n", rv);
         printf("###    - disabling disk cache for this session.\n");
         printf("###\n");
@@ -1395,16 +1430,17 @@ nsCacheService::CreateMemoryDevice()
 
     mMemoryDevice = new nsMemoryCacheDevice;
     if (!mMemoryDevice)       return NS_ERROR_OUT_OF_MEMORY;
     
     // set preference
     PRInt32 capacity = mObserver->MemoryCacheCapacity();
     CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
     mMemoryDevice->SetCapacity(capacity);
+    mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
 
     nsresult rv = mMemoryDevice->Init();
     if (NS_FAILED(rv)) {
         NS_WARNING("Initialization of Memory Cache failed.");
         delete mMemoryDevice;
         mMemoryDevice = nsnull;
     }
     return rv;
@@ -2052,16 +2088,40 @@ nsCacheService::SetDiskCacheCapacity(PRI
     }
 #endif // !NECKO_DISK_CACHE
     
     if (gService->mObserver)
         gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
 }
 
 void
+nsCacheService::SetDiskCacheMaxEntrySize(PRInt32  maxSize)
+{
+    if (!gService)  return;
+    nsCacheServiceAutoLock lock;
+
+#ifdef NECKO_DISK_CACHE
+    if (gService->mDiskDevice) {
+        gService->mDiskDevice->SetMaxEntrySize(maxSize);
+    }
+#endif // !NECKO_DISK_CACHE
+}
+
+void
+nsCacheService::SetMemoryCacheMaxEntrySize(PRInt32  maxSize)
+{
+    if (!gService)  return;
+    nsCacheServiceAutoLock lock;
+
+    if (gService->mMemoryDevice) {
+        gService->mMemoryDevice->SetMaxEntrySize(maxSize);
+    }
+}
+
+void
 nsCacheService::SetOfflineCacheEnabled(PRBool  enabled)
 {
     if (!gService)  return;
     nsCacheServiceAutoLock lock;
     gService->mEnableOfflineDevice = enabled;
 }
 
 void
--- a/netwerk/cache/nsCacheService.h
+++ b/netwerk/cache/nsCacheService.h
@@ -156,16 +156,20 @@ public:
      * Methods called by nsCacheProfilePrefObserver
      */
     static void      OnProfileShutdown(PRBool cleanse);
     static void      OnProfileChanged();
 
     static void      SetDiskCacheEnabled(PRBool  enabled);
     // Sets the disk cache capacity (in kilobytes)
     static void      SetDiskCacheCapacity(PRInt32  capacity);
+    // Set max size for a disk-cache entry (in bytes). -1 disables this limit
+    static void      SetDiskCacheMaxEntrySize(PRInt32  maxSize);
+    // Set max size for a memory-cache entry (in bytes). -1 disables this limit
+    static void      SetMemoryCacheMaxEntrySize(PRInt32  maxSize);
 
     static void      SetOfflineCacheEnabled(PRBool  enabled);
     // Sets the offline cache capacity (in kilobytes)
     static void      SetOfflineCacheCapacity(PRInt32  capacity);
 
     static void      SetMemoryCache();
 
     static void      OnEnterExitPrivateBrowsing();
--- a/netwerk/cache/nsDiskCacheDevice.cpp
+++ b/netwerk/cache/nsDiskCacheDevice.cpp
@@ -343,16 +343,17 @@ nsDiskCache::Truncate(PRFileDesc *  fd, 
 
 
 /******************************************************************************
  *  nsDiskCacheDevice
  *****************************************************************************/
 
 nsDiskCacheDevice::nsDiskCacheDevice()
     : mCacheCapacity(0)
+    , mMaxEntrySize(-1) // -1 means "no limit"
     , mInitialized(PR_FALSE)
 {
 }
 
 nsDiskCacheDevice::~nsDiskCacheDevice()
 {
     Shutdown();
 }
@@ -932,22 +933,25 @@ nsDiskCacheDevice::Visit(nsICacheVisitor
     if (keepGoing) {
         EntryInfoVisitor  infoVisitor(&mCacheMap, visitor);
         return mCacheMap.VisitRecords(&infoVisitor);
     }
 
     return NS_OK;
 }
 
-// Max allowed size for an entry is currently MIN(5MB, 1/8 CacheCapacity)
+// Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
 bool
 nsDiskCacheDevice::EntryIsTooBig(PRInt64 entrySize)
 {
-    return entrySize > kMaxDataFileSize
-           || entrySize > (static_cast<PRInt64>(mCacheCapacity) * 1024 / 8);
+    if (mMaxEntrySize == -1) // no limit
+        return entrySize > (static_cast<PRInt64>(mCacheCapacity) * 1024 / 8);
+    else 
+        return entrySize > mMaxEntrySize ||
+               entrySize > (static_cast<PRInt64>(mCacheCapacity) * 1024 / 8);
 }
 
 nsresult
 nsDiskCacheDevice::EvictEntries(const char * clientID)
 {
     CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
 
     if (!Initialized())  return NS_ERROR_NOT_INITIALIZED;
@@ -1138,8 +1142,19 @@ PRUint32 nsDiskCacheDevice::getCacheSize
     return mCacheMap.TotalSize();
 }
 
 
 PRUint32 nsDiskCacheDevice::getEntryCount()
 {
     return mCacheMap.EntryCount();
 }
+
+void
+nsDiskCacheDevice::SetMaxEntrySize(PRInt32 maxSizeInKilobytes)
+{
+    // Internal units are bytes. Changing this only takes effect *after* the
+    // change and has no consequences for existing cache-entries
+    if (maxSizeInKilobytes >= 0)
+        mMaxEntrySize = maxSizeInKilobytes * 1024;
+    else
+        mMaxEntrySize = -1;
+}
--- a/netwerk/cache/nsDiskCacheDevice.h
+++ b/netwerk/cache/nsDiskCacheDevice.h
@@ -89,16 +89,17 @@ public:
 
     bool                    EntryIsTooBig(PRInt64 entrySize);
 
     /**
      * Preference accessors
      */
     void                    SetCacheParentDirectory(nsILocalFile * parentDir);
     void                    SetCapacity(PRUint32  capacity);
+    void                    SetMaxEntrySize(PRInt32  maxSizeInKilobytes);
 
 /* private: */
 
     void                    getCacheDirectory(nsILocalFile ** result);
     PRUint32                getCacheCapacity();
     PRUint32                getCacheSize();
     PRUint32                getEntryCount();
     
@@ -130,14 +131,15 @@ private:
     nsresult                EvictDiskCacheEntries(PRUint32  targetCapacity);
     
     /**
      *  Member variables
      */
     nsCOMPtr<nsILocalFile>  mCacheDirectory;
     nsDiskCacheBindery      mBindery;
     PRUint32                mCacheCapacity;     // Unit is KiB's
+    PRInt32                 mMaxEntrySize;      // Unit is bytes internally
     // XXX need soft/hard limits, currentTotal
     nsDiskCacheMap          mCacheMap;
     PRPackedBool            mInitialized;
 };
 
 #endif // _nsDiskCacheDevice_h_
--- a/netwerk/cache/nsDiskCacheMap.h
+++ b/netwerk/cache/nsDiskCacheMap.h
@@ -94,17 +94,16 @@ struct nsDiskCacheEntry;
 #define SIZE_SHIFT(idx)            (2 * ((idx) - 1))
 #define BLOCK_SIZE_FOR_INDEX(idx)  ((idx) ? (256    << SIZE_SHIFT(idx)) : 0)
 #define BITMAP_SIZE_FOR_INDEX(idx) ((idx) ? (131072 >> SIZE_SHIFT(idx)) : 0)
 
 // Min and max values for the number of records in the DiskCachemap
 #define kMinRecordCount    512
 
 #define kSeparateFile      0
-#define kMaxDataFileSize   5 * 1024 * 1024  // 5 MB (in bytes) 
 #define kBuckets           (1 << 5)    // must be a power of 2!
 
 // Maximum size in K which can be stored in the location (see eFileSizeMask).
 // Both data and metadata can be larger, but only up to kMaxDataSizeK can be
 // counted into total cache size. I.e. if there are entries where either data or
 // metadata is larger than kMaxDataSizeK, the total cache size will be
 // inaccurate (smaller) than the actual cache size. The alternative is to stat
 // the files to find the real size, which was decided against for performance
--- a/netwerk/cache/nsMemoryCacheDevice.cpp
+++ b/netwerk/cache/nsMemoryCacheDevice.cpp
@@ -64,17 +64,18 @@ const char *gMemoryDeviceID      = "memo
 nsMemoryCacheDevice::nsMemoryCacheDevice()
     : mInitialized(PR_FALSE),
       mEvictionThreshold(PR_INT32_MAX),
       mHardLimit(4 * 1024 * 1024),       // default, if no pref
       mSoftLimit((mHardLimit * 9) / 10), // default, if no pref
       mTotalSize(0),
       mInactiveSize(0),
       mEntryCount(0),
-      mMaxEntryCount(0)
+      mMaxEntryCount(0),
+      mMaxEntrySize(-1) // -1 means "no limit"
 {
     for (int i=0; i<kQueueCount; ++i)
         PR_INIT_CLIST(&mEvictionList[i]);
 }
 
 
 nsMemoryCacheDevice::~nsMemoryCacheDevice()
 {    
@@ -292,17 +293,23 @@ nsMemoryCacheDevice::GetFileForEntry( ns
                                       nsIFile **        result )
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 bool
 nsMemoryCacheDevice::EntryIsTooBig(PRInt64 entrySize)
 {
-    return entrySize > mSoftLimit;
+    CACHE_LOG_DEBUG(("nsMemoryCacheDevice::EntryIsTooBig "
+                     "[size=%d max=%d soft=%d]\n",
+                     entrySize, mMaxEntrySize, mSoftLimit));
+    if (mMaxEntrySize == -1)
+        return entrySize > mSoftLimit;
+    else
+        return (entrySize > mSoftLimit || entrySize > mMaxEntrySize);
 }
 
 
 nsresult
 nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, PRInt32 deltaSize)
 {
     if (entry->IsStreamData()) {
         // we have the right to refuse or pre-evict
@@ -483,16 +490,26 @@ nsMemoryCacheDevice::EvictEntries(const 
 void
 nsMemoryCacheDevice::SetCapacity(PRInt32  capacity)
 {
     PRInt32 hardLimit = capacity * 1024;  // convert k into bytes
     PRInt32 softLimit = (hardLimit * 9) / 10;
     AdjustMemoryLimits(softLimit, hardLimit);
 }
 
+void
+nsMemoryCacheDevice::SetMaxEntrySize(PRInt32 maxSizeInKilobytes)
+{
+    // Internal unit is bytes. Changing this only takes effect *after* the
+    // change and has no consequences for existing cache-entries
+    if (maxSizeInKilobytes >= 0)
+        mMaxEntrySize = maxSizeInKilobytes * 1024;
+    else
+        mMaxEntrySize = -1;
+}
 
 #ifdef DEBUG
 static PLDHashOperator
 CountEntry(PLDHashTable * table, PLDHashEntryHdr * hdr, PRUint32 number, void * arg)
 {
     PRInt32 *entryCount = (PRInt32 *)arg;
     ++(*entryCount);
     return PL_DHASH_NEXT;
--- a/netwerk/cache/nsMemoryCacheDevice.h
+++ b/netwerk/cache/nsMemoryCacheDevice.h
@@ -83,16 +83,17 @@ public:
 
     virtual nsresult OnDataSizeChange( nsCacheEntry * entry, PRInt32 deltaSize );
 
     virtual nsresult Visit( nsICacheVisitor * visitor );
 
     virtual nsresult EvictEntries(const char * clientID);
     
     void             SetCapacity(PRInt32  capacity);
+    void             SetMaxEntrySize(PRInt32  maxSizeInKilobytes);
 
     bool             EntryIsTooBig(PRInt64 entrySize);
 private:
     friend class nsMemoryCacheDeviceInfo;
     enum      { DELETE_ENTRY        = PR_TRUE,
                 DO_NOT_DELETE_ENTRY = PR_FALSE };
 
     void      AdjustMemoryLimits( PRInt32  softLimit, PRInt32  hardLimit);
@@ -119,16 +120,17 @@ private:
     PRInt32                mHardLimit;
     PRInt32                mSoftLimit;
 
     PRInt32                mTotalSize;
     PRInt32                mInactiveSize;
 
     PRInt32                mEntryCount;
     PRInt32                mMaxEntryCount;
+    PRInt32                mMaxEntrySize; // internal unit is bytes
 
     // XXX what other stats do we want to keep?
 };
 
 
 /******************************************************************************
  * nsMemoryCacheDeviceInfo - used to call nsIVisitor for about:cache
  ******************************************************************************/
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_bug650955.js
@@ -0,0 +1,134 @@
+//
+// Test that "max_entry_size" prefs for disk- and memory-cache prevents
+// caching resources with size out of bounds
+//
+do_load_httpd_js();
+do_get_profile();
+
+const prefService = Cc["@mozilla.org/preferences-service;1"]
+                       .getService(Ci.nsIPrefBranch);
+
+const httpserver = new nsHttpServer();
+
+// Repeats the given data until the total size is larger than 1K
+function repeatToLargerThan1K(data) {
+    while(data.length <= 1024)
+        data += data;
+    return data;
+}
+
+function clearCache() {
+    var service = Components.classes["@mozilla.org/network/cache-service;1"]
+        .getService(Components.interfaces.nsICacheService);
+    service.evictEntries(
+        Components.interfaces.nsICache.STORE_ANYWHERE);
+}
+
+function setupChannel(suffix, value) {
+    var ios = Components.classes["@mozilla.org/network/io-service;1"]
+            .getService(Ci.nsIIOService);
+    var chan = ios.newChannel("http://localhost:4444" + suffix, "", null);
+    var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+    httpChan.setRequestHeader("x-request", value, false);
+    
+    return httpChan;
+}
+
+var tests = [
+             new InitializeCacheDevices(true, false), // enable and create mem-device
+             new TestCacheEntrySize(
+                 function() { prefService.setIntPref("browser.cache.memory.max_entry_size", 1); },
+                              "012345", "9876543210", "012345"), // expect cached value
+             new TestCacheEntrySize(
+                 function() { prefService.setIntPref("browser.cache.memory.max_entry_size", 1); },
+                              "0123456789a", "9876543210", "9876543210"), // expect fresh value
+             new TestCacheEntrySize(
+                 function() { prefService.setIntPref("browser.cache.memory.max_entry_size", -1); },
+                              "0123456789a", "9876543210", "0123456789a"), // expect cached value
+
+             new InitializeCacheDevices(false, true), // enable and create disk-device
+             new TestCacheEntrySize(
+                 function() { prefService.setIntPref("browser.cache.disk.max_entry_size", 1); },
+                              "012345", "9876543210", "012345"), // expect cached value
+             new TestCacheEntrySize(
+                 function() { prefService.setIntPref("browser.cache.disk.max_entry_size", 1); },
+                              "0123456789a", "9876543210", "9876543210"), // expect fresh value
+             new TestCacheEntrySize(
+                 function() { prefService.setIntPref("browser.cache.disk.max_entry_size", -1); },
+                              "0123456789a", "9876543210", "0123456789a"), // expect cached value
+            ];
+
+function nextTest() {
+    clearCache();
+    var aTest = tests.shift();
+    if (!aTest) {
+        httpserver.stop(do_test_finished);
+        return;
+    }
+    do_execute_soon(function() { aTest.start(); } );
+}
+
+// Just make sure devices are created
+function InitializeCacheDevices(memDevice, diskDevice) {
+    this.start = function() {
+        prefService.setBoolPref("browser.cache.memory.enable", memDevice);
+        prefService.setBoolPref("browser.cache.disk.enable", diskDevice);
+        var channel = setupChannel("/bug650995", "Initial value");
+        channel.asyncOpen(new ChannelListener(
+            nextTest, null),
+            null);
+    }
+}
+
+function TestCacheEntrySize(setSizeFunc, firstRequest, secondRequest, secondExpectedReply) {
+
+    // Initially, this test used 10 bytes as the limit for caching entries.
+    // Since we now use 1K granularity we have to extend lengths to be larger
+    // than 1K if it is larger than 10
+    if (firstRequest.length > 10)
+        firstRequest = repeatToLargerThan1K(firstRequest);
+    if (secondExpectedReply.length > 10)
+        secondExpectedReply = repeatToLargerThan1K(secondExpectedReply);
+
+    this.start = function() {
+        setSizeFunc();
+        var channel = setupChannel("/bug650995", firstRequest);
+        channel.asyncOpen(new ChannelListener(this.initialLoad, this), null);
+    },
+
+    this.initialLoad = function(request, data, ctx) {
+        do_check_eq(firstRequest, data);
+        var channel = setupChannel("/bug650995", secondRequest);
+        do_execute_soon(function() {
+            channel.asyncOpen(new ChannelListener(ctx.testAndTriggerNext, ctx), null);
+            });
+    },
+
+    this.testAndTriggerNext = function(request, data, ctx) {
+        do_check_eq(secondExpectedReply, data);
+        do_execute_soon(nextTest);
+    }
+}
+
+function run_test()
+{
+    httpserver.registerPathHandler("/bug650995", handler);
+    httpserver.start(4444);
+
+    prefService.setBoolPref("browser.cache.offline.enable", false);
+
+    nextTest();
+    do_test_pending();
+}
+
+function handler(metadata, response) {
+    var body = "BOOM!";
+    try {
+        body = metadata.getHeader("x-request");
+    } catch(e) {}
+
+    response.setStatusLine(metadata.httpVersion, 200, "Ok");
+    response.setHeader("Content-Type", "text/plain", false);
+    response.setHeader("Cache-Control", "max-age=3600", false);
+    response.bodyOutputStream.write(body, body.length);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -54,16 +54,17 @@ tail =
 [test_bug553970.js]
 [test_bug561276.js]
 [test_bug580508.js]
 [test_bug586908.js]
 [test_bug588389.js]
 [test_bug596443.js]
 [test_bug618835.js]
 [test_bug633743.js]
+[test_bug650955.js]
 [test_bug652761.js]
 [test_bug651100.js]
 [test_bug654926.js]
 [test_bug654926_doom_and_read.js]
 [test_bug654926_test_seek.js]
 [test_bug659569.js]
 [test_bug660066.js]
 [test_cacheflags.js]