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 71822 c828b73a419ac447f304317682255307499b3512
parent 71821 aee9017b0b4fb4e5c962d83d0ae8d038a2df62f0
child 71826 76cbaa594619a2385af50768042683fa285aaa52
push id262
push userbherland@mozilla.com
push dateMon, 27 Jun 2011 12:28:02 +0000
treeherdermozilla-inbound@c828b73a419a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs650995
milestone7.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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]