Backed out changeset d6a5afef5837
authorDan Witte <dwitte@gmail.com>
Mon, 16 Mar 2009 01:33:54 -0700
changeset 26214 6453275512d544de1ff687b6423620190c2422c4
parent 26211 d6a5afef5837c4a1b950efc276ab81906d38ca20
child 26215 2fbc3d17e654bf3b0feb5b4992379d3006cc450f
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone1.9.2a1pre
backs outd6a5afef5837c4a1b950efc276ab81906d38ca20
Backed out changeset d6a5afef5837
extensions/cookie/test/Makefile.in
netwerk/cookie/public/nsICookieService.idl
netwerk/cookie/src/nsCookieService.cpp
netwerk/cookie/src/nsCookieService.h
netwerk/test/TestCookie.cpp
--- a/extensions/cookie/test/Makefile.in
+++ b/extensions/cookie/test/Makefile.in
@@ -66,17 +66,16 @@ MODULE          = test_cookies
   file_subdomain_inner.html \
   test_same_base_domain_2.html \
   test_same_base_domain_3.html \
   test_same_base_domain_4.html \
   file_localhost_inner.html \
   test_same_base_domain_5.html \
   test_same_base_domain_6.html \
   file_loopback_inner.html \
-  test_eviction.html \
 	$(NULL)
 
 # XXX see bug 454857
 #  test_loadflags.html \
 #  file_testloadflags.js \
 #  file_loadflags_inner.html \
 
 # the tests below use Firefox-specific features
--- a/netwerk/cookie/public/nsICookieService.idl
+++ b/netwerk/cookie/public/nsICookieService.idl
@@ -47,33 +47,28 @@ interface nsIChannel;
  * Provides methods for setting and getting cookies in the context of a
  * page load.  See nsICookieManager for methods to manipulate the cookie
  * database directly.  This separation of interface is mainly historical.
  *
  * This service broadcasts the following notifications when the cookie
  * list is changed, or a cookie is rejected:
  *
  * topic  : "cookie-changed"
- *          broadcast whenever the cookie list changes in some way. see
- *          explanation of data strings below.
- * subject: see below.
+ *          broadcast whenever the cookie list changes in some way. there
+ *          are four possible data strings for this notification; one
+ *          notification will be broadcast for each change, and will involve
+ *          a single cookie.
+ * subject: an nsICookie2 interface pointer representing the cookie object
+ *          that changed.
  * data   : "deleted"
- *          a cookie was deleted. the subject is an nsICookie2 representing
- *          the deleted cookie.
+ *          a cookie was deleted. the subject is the deleted cookie.
  *          "added"
- *          a cookie was added. the subject is an nsICookie2 representing
- *          the added cookie.
+ *          a cookie was added. the subject is the added cookie.
  *          "changed"
- *          a cookie was changed. the subject is an nsICookie2 representing
- *          the new cookie. (note that host, path, and name are invariant
- *          for a given cookie; other parameters may change.)
- *          "batch-deleted"
- *          a batch of cookies was deleted (for instance, as part of a purging
- *          operation). the subject is an nsIArray of nsICookie2's representing
- *          the deleted cookies.
+ *          a cookie was changed. the subject is the new cookie.
  *          "cleared"
  *          the entire cookie list was cleared. the subject is null.
  *          "reload"
  *          the entire cookie list should be reloaded.  the subject is null.
  *
  * topic  : "cookie-rejected"
  *          broadcast whenever a cookie was rejected from being set as a
  *          result of user prefs.
--- a/netwerk/cookie/src/nsCookieService.cpp
+++ b/netwerk/cookie/src/nsCookieService.cpp
@@ -50,19 +50,17 @@
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIChannel.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsILineInputStream.h"
 #include "nsIEffectiveTLDService.h"
 
-#include "nsTArray.h"
 #include "nsCOMArray.h"
-#include "nsIMutableArray.h"
 #include "nsArrayEnumerator.h"
 #include "nsAutoPtr.h"
 #include "nsReadableUtils.h"
 #include "nsCRT.h"
 #include "prtime.h"
 #include "prprf.h"
 #include "nsNetUtil.h"
 #include "nsNetCID.h"
@@ -83,17 +81,16 @@
 // This is a hack to hide HttpOnly cookies from older browsers
 //
 static const char kHttpOnlyPrefix[] = "#HttpOnly_";
 
 static const char kCookieFileName[] = "cookies.sqlite";
 #define COOKIES_SCHEMA_VERSION 2
 
 static const PRInt64 kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
-static const PRInt64 kCookiePurgeAge = 30 * 24 * 60 * 60 * PR_USEC_PER_SEC; // 30 days in microseconds
 
 static const char kOldCookieFileName[] = "cookies.txt";
 
 #undef  LIMIT
 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
 
 // default limits for the cookie list. these can be tuned by the
 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
@@ -115,17 +112,16 @@ static const PRUint32 STATUS_REJECTED_WI
 static const PRUint32 BEHAVIOR_ACCEPT        = 0;
 static const PRUint32 BEHAVIOR_REJECTFOREIGN = 1;
 static const PRUint32 BEHAVIOR_REJECT        = 2;
 
 // pref string constants
 static const char kPrefCookiesPermissions[] = "network.cookie.cookieBehavior";
 static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
 static const char kPrefMaxCookiesPerHost[]  = "network.cookie.maxPerHost";
-static const char kPrefCookiePurgeAge[]     = "network.cookie.purgeAge";
 
 // struct for temporarily storing cookie attributes during header parsing
 struct nsCookieAttributes
 {
   nsCAutoString name;
   nsCAutoString value;
   nsCAutoString host;
   nsCAutoString path;
@@ -205,23 +201,17 @@ struct nsEnumerationData
 #define SET_COOKIE PR_TRUE
 #define GET_COOKIE PR_FALSE
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo *sCookieLog = PR_NewLogModule("cookie");
 
 #define COOKIE_LOGFAILURE(a, b, c, d)    LogFailure(a, b, c, d)
 #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
-
-#define COOKIE_LOGEVICTED(a)                   \
-  PR_BEGIN_MACRO                               \
-    if (PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) \
-      LogEvicted(a);                           \
-  PR_END_MACRO
-
+#define COOKIE_LOGEVICTED(a)             LogEvicted(a)
 #define COOKIE_LOGSTRING(lvl, fmt)   \
   PR_BEGIN_MACRO                     \
     PR_LOG(sCookieLog, lvl, fmt);    \
     PR_LOG(sCookieLog, lvl, ("\n")); \
   PR_END_MACRO
 
 static void
 LogFailure(PRBool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
@@ -304,16 +294,21 @@ LogSuccess(PRBool aSetCookie, nsIURI *aH
   LogCookie(aCookie);
 
   PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n"));
 }
 
 static void
 LogEvicted(nsCookie *aCookie)
 {
+  // if logging isn't enabled, return now to save cycles
+  if (!PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) {
+    return;
+  }
+
   PR_LOG(sCookieLog, PR_LOG_DEBUG,("===== COOKIE EVICTED =====\n"));
 
   LogCookie(aCookie);
 
   PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n"));
 }
 
 // inline wrappers to make passing in nsAFlatCStrings easier
@@ -377,22 +372,20 @@ NS_IMPL_ISUPPORTS5(nsCookieService,
                    nsICookieService,
                    nsICookieManager,
                    nsICookieManager2,
                    nsIObserver,
                    nsISupportsWeakReference)
 
 nsCookieService::nsCookieService()
  : mHostTable(&mDefaultHostTable)
- , mCookieOldestTime(LL_MAXINT)
  , mCookieCount(0)
  , mCookiesPermissions(BEHAVIOR_ACCEPT)
  , mMaxNumberOfCookies(kMaxNumberOfCookies)
  , mMaxCookiesPerHost(kMaxCookiesPerHost)
- , mCookiePurgeAge(kCookiePurgeAge)
 {
 }
 
 nsresult
 nsCookieService::Init()
 {
   if (!mHostTable->Init()) {
     return NS_ERROR_OUT_OF_MEMORY;
@@ -403,17 +396,16 @@ nsCookieService::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   // init our pref and observer
   nsCOMPtr<nsIPrefBranch2> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefBranch) {
     prefBranch->AddObserver(kPrefCookiesPermissions, this, PR_TRUE);
     prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, PR_TRUE);
     prefBranch->AddObserver(kPrefMaxCookiesPerHost,  this, PR_TRUE);
-    prefBranch->AddObserver(kPrefCookiePurgeAge,     this, PR_TRUE);
     PrefChanged(prefBranch);
   }
 
   // failure here is non-fatal (we can run fine without
   // persistent storage - e.g. if there's no profile)
   rv = InitDB();
   if (rv == NS_ERROR_FILE_CORRUPTED) {
     // database is corrupt - delete and try again
@@ -777,29 +769,28 @@ nsCookieService::SetCookieStringInternal
 // notify observers that a cookie was rejected due to the users' prefs.
 void
 nsCookieService::NotifyRejected(nsIURI *aHostURI)
 {
   if (mObserverService)
     mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nsnull);
 }
 
-// notify observers that the cookie list changed. there are five possible
+// notify observers that the cookie list changed. there are four possible
 // values for aData:
-// "deleted" means a cookie was deleted. aSubject is the deleted cookie.
-// "added"   means a cookie was added. aSubject is the added cookie.
-// "changed" means a cookie was altered. aSubject is the new cookie.
-// "cleared" means the entire cookie list was cleared. aSubject is null.
-// "batch-deleted" means multiple cookies were deleted. aSubject is the list of cookies.
+// "deleted" means a cookie was deleted. aCookie is the deleted cookie.
+// "added"   means a cookie was added. aCookie is the added cookie.
+// "changed" means a cookie was altered. aCookie is the new cookie.
+// "cleared" means the entire cookie list was cleared. aCookie is null.
 void
-nsCookieService::NotifyChanged(nsISupports     *aSubject,
+nsCookieService::NotifyChanged(nsICookie2      *aCookie,
                                const PRUnichar *aData)
 {
   if (mObserverService)
-    mObserverService->NotifyObservers(aSubject, "cookie-changed", aData);
+    mObserverService->NotifyObservers(aCookie, "cookie-changed", aData);
 }
 
 /******************************************************************************
  * nsCookieService:
  * pref observer impl
  ******************************************************************************/
 
 void
@@ -809,19 +800,16 @@ nsCookieService::PrefChanged(nsIPrefBran
   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiesPermissions, &val)))
     mCookiesPermissions = (PRUint8) LIMIT(val, 0, 2, 0);
 
   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
     mMaxNumberOfCookies = (PRUint16) LIMIT(val, 0, 0xFFFF, 0xFFFF);
 
   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
     mMaxCookiesPerHost = (PRUint16) LIMIT(val, 0, 0xFFFF, 0xFFFF);
-
-  if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val)))
-    mCookiePurgeAge = val * PR_USEC_PER_SEC; // convert seconds to microseconds
 }
 
 /******************************************************************************
  * nsICookieManager impl:
  * nsICookieManager
  ******************************************************************************/
 
 NS_IMETHODIMP
@@ -902,17 +890,17 @@ nsCookieService::Add(const nsACString &a
                      currentTimeInUsec,
                      aIsSession,
                      aIsSecure,
                      aIsHttpOnly);
   if (!cookie) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  AddInternal(cookie, currentTimeInUsec, nsnull, nsnull, PR_TRUE);
+  AddInternal(cookie, currentTimeInUsec / PR_USEC_PER_SEC, nsnull, nsnull, PR_TRUE);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCookieService::Remove(const nsACString &aHost,
                         const nsACString &aName,
                         const nsACString &aPath,
                         PRBool           aBlocked)
@@ -1142,17 +1130,17 @@ nsCookieService::ImportCookies(nsIFile *
     
     // trick: preserve the most-recently-used cookie ordering,
     // by successively decrementing the lastAccessed time
     lastAccessedCounter--;
 
     if (originalCookieCount == 0)
       AddCookieToList(newCookie);
     else
-      AddInternal(newCookie, currentTimeInUsec, nsnull, nsnull, PR_TRUE);
+      AddInternal(newCookie, currentTime, nsnull, nsnull, PR_TRUE);
   }
 
   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported", mCookieCount));
 
   return NS_OK;
 }
 
 /******************************************************************************
@@ -1437,98 +1425,102 @@ nsCookieService::SetCookieInternal(nsIUR
 
     // update isSession and expiry attributes, in case they changed
     cookie->SetIsSession(cookieAttributes.isSession);
     cookie->SetExpiry(cookieAttributes.expiryTime);
   }
 
   // add the cookie to the list. AddInternal() takes care of logging.
   // we get the current time again here, since it may have changed during prompting
-  AddInternal(cookie, PR_Now(), aHostURI, savedCookieHeader.get(), aFromHttp);
+  AddInternal(cookie, PR_Now() / PR_USEC_PER_SEC, aHostURI, savedCookieHeader.get(), aFromHttp);
   return newCookie;
 }
 
 // this is a backend function for adding a cookie to the list, via SetCookie.
 // also used in the cookie manager, for profile migration from IE.
 // it either replaces an existing cookie; or adds the cookie to the hashtable,
 // and deletes a cookie (if maximum number of cookies has been
 // reached). also performs list maintenance by removing expired cookies.
 void
 nsCookieService::AddInternal(nsCookie   *aCookie,
-                             PRInt64     aCurrentTimeInUsec,
+                             PRInt64     aCurrentTime,
                              nsIURI     *aHostURI,
                              const char *aCookieHeader,
                              PRBool      aFromHttp)
 {
-  PRInt64 currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
-
   // if the new cookie is httponly, make sure we're not coming from script
   if (!aFromHttp && aCookie->IsHttpOnly()) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie is httponly; coming from script");
     return;
   }
 
   // start a transaction on the storage db, to optimize deletions/insertions.
   // transaction will automically commit on completion. if we already have a
   // transaction (e.g. from SetCookie*()), this will have no effect. 
   mozStorageTransaction transaction(mDBConn, PR_TRUE);
 
   nsListIter matchIter;
   PRBool foundCookie = FindCookie(aCookie->Host(), aCookie->Name(), aCookie->Path(),
-                                  matchIter, currentTime);
+                                  matchIter, aCurrentTime);
 
   nsRefPtr<nsCookie> oldCookie;
   if (foundCookie) {
     oldCookie = matchIter.current;
 
     // if the old cookie is httponly, make sure we're not coming from script
     if (!aFromHttp && oldCookie->IsHttpOnly()) {
       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie is httponly; coming from script");
       return;
     }
 
     RemoveCookieFromList(matchIter);
 
     // check if the cookie has expired
-    if (aCookie->Expiry() <= currentTime) {
+    if (aCookie->Expiry() <= aCurrentTime) {
       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie was deleted");
       NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
       return;
     }
 
     // preserve creation time of cookie
     if (oldCookie)
       aCookie->SetCreationID(oldCookie->CreationID());
 
   } else {
     // check if cookie has already expired
-    if (aCookie->Expiry() <= currentTime) {
+    if (aCookie->Expiry() <= aCurrentTime) {
       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie has already expired");
       return;
     }
 
     // check if we have to delete an old cookie.
-    nsEnumerationData data(currentTime, LL_MAXINT);
+    nsEnumerationData data(aCurrentTime, LL_MAXINT);
     if (CountCookiesFromHostInternal(aCookie->RawHost(), data) >= mMaxCookiesPerHost) {
       // remove the oldest cookie from host
       oldCookie = data.iter.current;
-      COOKIE_LOGEVICTED(oldCookie);
       RemoveCookieFromList(data.iter);
 
-      NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
+    } else if (mCookieCount >= mMaxNumberOfCookies) {
+      // try to make room, by removing expired cookies
+      RemoveExpiredCookies(aCurrentTime);
 
-    } else if (mCookieCount >= 1.1 * mMaxNumberOfCookies &&
-               aCurrentTimeInUsec - mCookieOldestTime >= 1.1 * mCookiePurgeAge) {
-      // we're over both size and age limits by 10%; time to purge the table!
-      // do this by:
-      // 1) removing expired cookies;
-      // 2) evicting the balance of old cookies, until we reach the size limit.
-      // note that the mCookieOldestTime indicator can be pessimistic - if it's
-      // older than the actual oldest cookie, we'll just purge more eagerly.
-      PurgeCookies(aCurrentTimeInUsec);
+      // check if we still have to get rid of something
+      if (mCookieCount >= mMaxNumberOfCookies) {
+        // find the position of the oldest cookie, and remove it
+        data.oldestTime = LL_MAXINT;
+        FindOldestCookie(data);
+        oldCookie = data.iter.current;
+        RemoveCookieFromList(data.iter);
+      }
+    }
+
+    // if we deleted an old cookie, notify consumers
+    if (oldCookie) {
+      COOKIE_LOGEVICTED(oldCookie);
+      NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
     }
   }
 
   // add the cookie to head of list
   AddCookieToList(aCookie);
   NotifyChanged(aCookie, foundCookie ? NS_LITERAL_STRING("changed").get()
                                      : NS_LITERAL_STRING("added").get());
 
@@ -2072,161 +2064,42 @@ nsCookieService::GetExpiry(nsCookieAttri
 
 void
 nsCookieService::RemoveAllFromMemory()
 {
   // clearing the hashtable will call each nsCookieEntry's dtor,
   // which releases all their respective children.
   mHostTable->Clear();
   mCookieCount = 0;
-  mCookieOldestTime = LL_MAXINT;
 }
 
-// stores temporary data for enumerating over the hash entries,
-// since enumeration is done using callback functions
-struct nsPurgeData
+PLDHashOperator
+removeExpiredCallback(nsCookieEntry *aEntry,
+                      void          *aArg)
 {
-  nsPurgeData(PRInt64 aCurrentTime,
-              PRInt64 aPurgeTime,
-              nsTArray<nsListIter> &aPurgeList,
-              nsIMutableArray *aRemovedList)
-   : currentTime(aCurrentTime)
-   , purgeTime(aPurgeTime)
-   , oldestTime(LL_MAXINT)
-   , purgeList(aPurgeList)
-   , removedList(aRemovedList) {}
-
-  // the current time, in seconds
-  PRInt64 currentTime;
-
-  // lastAccessed time older than which cookies are eligible for purge
-  PRInt64 purgeTime;
-
-  // lastAccessed time of the oldest cookie found during purge, to update our indicator
-  PRInt64 oldestTime;
-
-  // list of cookies over the age limit, for purging
-  nsTArray<nsListIter> &purgeList;
-
-  // list of all cookies we've removed, for notification
-  nsIMutableArray *removedList;
-};
-
-// comparator class for lastaccessed times of cookies.
-class CompareCookiesByAge {
-  public:
-    // returns true if (a == b); false otherwise.
-    PRBool Equals(const nsListIter &a, const nsListIter &b) const
-    {
-      return a.current->LastAccessed() == b.current->LastAccessed();
-    }
-
-    // returns true if (a < b); false otherwise.
-    PRBool LessThan(const nsListIter &a, const nsListIter &b) const
-    {
-      return a.current->LastAccessed() < b.current->LastAccessed();
-    }
-};
-
-PLDHashOperator
-purgeCookiesCallback(nsCookieEntry *aEntry,
-                     void          *aArg)
-{
-  nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
+  const PRInt64 &currentTime = *static_cast<PRInt64*>(aArg);
   for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ) {
-    // check if the cookie has expired
-    if (iter.current->Expiry() <= data.currentTime) {
-      nsCookie *cookie = iter.current;
-      data.removedList->AppendElement(cookie, PR_FALSE);
-      COOKIE_LOGEVICTED(cookie);
-
+    if (iter.current->Expiry() <= currentTime)
       // remove from list. this takes care of updating the iterator for us
       nsCookieService::gCookieService->RemoveCookieFromList(iter);
-
-    } else {
-      // check if the cookie is over the age limit
-      if (iter.current->LastAccessed() <= data.purgeTime) {
-        data.purgeList.AppendElement(iter);
-
-      } else if (iter.current->LastAccessed() < data.oldestTime) {
-        // reset our indicator
-        data.oldestTime = iter.current->LastAccessed();
-      }
-
+    else
       ++iter;
-    }
   }
   return PL_DHASH_NEXT;
 }
 
-// purges expired and old cookies in a batch operation.
+// removes any expired cookies from memory
 void
-nsCookieService::PurgeCookies(PRInt64 aCurrentTimeInUsec)
+nsCookieService::RemoveExpiredCookies(PRInt64 aCurrentTime)
 {
 #ifdef PR_LOGGING
   PRUint32 initialCookieCount = mCookieCount;
-  COOKIE_LOGSTRING(PR_LOG_DEBUG, ("PurgeCookies(): beginning purge with %ld cookies and %lld age",
-                                  mCookieCount, aCurrentTimeInUsec - mCookieOldestTime));
 #endif
-
-  nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
-
-  nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
-  if (!removedList)
-    return;
-
-  nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC, aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList);
-  mHostTable->EnumerateEntries(purgeCookiesCallback, &data);
-
-#ifdef PR_LOGGING
-  PRUint32 postExpiryCookieCount = mCookieCount;
-#endif
-
-  // now we have a list of iterators for cookies over the age limit.
-  // sort them by age, and then we'll see how many to remove...
-  purgeList.Sort(CompareCookiesByAge());
-
-  // only remove old cookies until we reach the max cookie limit, no more.
-  PRUint32 excess = mCookieCount - mMaxNumberOfCookies;
-  //printf("count %u, maxnum %u, listlen %u, excess %d\n", mCookieCount, mMaxNumberOfCookies, list.Length(), excess);
-  if (purgeList.Length() > excess) {
-    // we're not purging everything in the list, so update our indicator
-    data.oldestTime = purgeList[excess].current->LastAccessed();
-
-    purgeList.SetLength(excess);
-  }
-
-  // traverse the list and remove cookies. the iterators we've stored
-  // in the list aren't stable under list mutation, so we need to do a
-  // fresh linked list traversal from the hash entryclass for each cookie.
-  for (PRUint32 i = 0; i < purgeList.Length(); ++i) {
-    for (nsListIter iter(purgeList[i].entry, nsnull, purgeList[i].entry->Head()); iter.current; ++iter) {
-      if (iter.current == purgeList[i].current) {
-        // remove from list. this takes care of updating the iterator for us
-        nsCookie *cookie = iter.current;
-        removedList->AppendElement(cookie, PR_FALSE);
-        COOKIE_LOGEVICTED(cookie);
-
-        RemoveCookieFromList(iter);
-        break;
-      }
-    }
-  }
-
-  // take all the cookies in the removed list, and notify about them in one batch
-  NotifyChanged(removedList, NS_LITERAL_STRING("batch-deleted").get());
-
-  // reset the oldest time indicator
-  mCookieOldestTime = data.oldestTime;
-
-  COOKIE_LOGSTRING(PR_LOG_DEBUG, ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
-                                  initialCookieCount - postExpiryCookieCount,
-                                  mCookieCount - postExpiryCookieCount,
-                                  mCookieCount,
-                                  aCurrentTimeInUsec - mCookieOldestTime));
+  mHostTable->EnumerateEntries(removeExpiredCallback, &aCurrentTime);
+  COOKIE_LOGSTRING(PR_LOG_DEBUG, ("RemoveExpiredCookies(): %ld purged; %ld remain", initialCookieCount - mCookieCount, mCookieCount));
 }
 
 // find whether a given cookie has been previously set. this is provided by the
 // nsICookieManager2 interface.
 NS_IMETHODIMP
 nsCookieService::CookieExists(nsICookie2 *aCookie,
                               PRBool     *aFoundCookie)
 {
@@ -2404,20 +2277,16 @@ nsCookieService::AddCookieToList(nsCooki
   }
 
   NS_ADDREF(aCookie);
 
   aCookie->Next() = entry->Head();
   entry->Head() = aCookie;
   ++mCookieCount;
 
-  // keep track of the oldest cookie, for when it comes time to purge
-  if (aCookie->LastAccessed() < mCookieOldestTime)
-    mCookieOldestTime = aCookie->LastAccessed();
-
   // if it's a non-session cookie and hasn't just been read from the db, write it out.
   if (aWriteToDB && !aCookie->IsSession() && mStmtInsert) {
     // use our cached sqlite "insert" statement
     mozStorageStatementScoper scoper(mStmtInsert);
 
     nsresult rv = bindCookieParameters(mStmtInsert, aCookie);
     if (NS_SUCCEEDED(rv)) {
       PRBool hasResult;
@@ -2455,8 +2324,28 @@ nsCookieService::UpdateCookieInList(nsCo
 
     if (NS_FAILED(rv)) {
       NS_WARNING("db update failed!");
       COOKIE_LOGSTRING(PR_LOG_WARNING, ("UpdateCookieInList(): updating db gave error %x", rv));
     }
   }
 }
 
+static PLDHashOperator
+findOldestCallback(nsCookieEntry *aEntry,
+                   void          *aArg)
+{
+  nsEnumerationData *data = static_cast<nsEnumerationData*>(aArg);
+  for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ++iter) {
+    // check if we've found the oldest cookie so far
+    if (data->oldestTime > iter.current->LastAccessed()) {
+      data->oldestTime = iter.current->LastAccessed();
+      data->iter = iter;
+    }
+  }
+  return PL_DHASH_NEXT;
+}
+
+void
+nsCookieService::FindOldestCookie(nsEnumerationData &aData)
+{
+  mHostTable->EnumerateEntries(findOldestCallback, &aData);
+}
--- a/netwerk/cookie/src/nsCookieService.h
+++ b/netwerk/cookie/src/nsCookieService.h
@@ -166,58 +166,57 @@ class nsCookieService : public nsICookie
     void                          PrefChanged(nsIPrefBranch *aPrefBranch);
     nsresult                      InitDB(PRBool aDeleteExistingDB = PR_FALSE);
     nsresult                      CreateTable();
     void                          CloseDB();
     nsresult                      Read();
     void                          GetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, PRBool aHttpBound, char **aCookie);
     nsresult                      SetCookieStringInternal(nsIURI *aHostURI, nsIPrompt *aPrompt, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, PRBool aFromHttp);
     PRBool                        SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, nsDependentCString &aCookieHeader, PRInt64 aServerTime, PRBool aFromHttp);
-    void                          AddInternal(nsCookie *aCookie, PRInt64 aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp);
+    void                          AddInternal(nsCookie *aCookie, PRInt64 aCurrentTime, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp);
     void                          RemoveCookieFromList(nsListIter &aIter);
     PRBool                        AddCookieToList(nsCookie *aCookie, PRBool aWriteToDB = PR_TRUE);
     void                          UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed);
     static PRBool                 GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, PRBool &aEqualsFound);
     static PRBool                 ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie);
     PRBool                        IsForeign(nsIURI *aHostURI, nsIURI *aFirstURI);
     PRUint32                      CheckPrefs(nsIURI *aHostURI, nsIChannel *aChannel, const char *aCookieHeader);
     PRBool                        CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static PRBool                 CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static PRBool                 GetExpiry(nsCookieAttributes &aCookie, PRInt64 aServerTime, PRInt64 aCurrentTime);
     void                          RemoveAllFromMemory();
-    void                          PurgeCookies(PRInt64 aCurrentTimeInUsec);
+    void                          RemoveExpiredCookies(PRInt64 aCurrentTime);
     PRBool                        FindCookie(const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter, PRInt64 aCurrentTime);
+    void                          FindOldestCookie(nsEnumerationData &aData);
     PRUint32                      CountCookiesFromHostInternal(const nsACString &aHost, nsEnumerationData &aData);
     void                          NotifyRejected(nsIURI *aHostURI);
-    void                          NotifyChanged(nsISupports *aSubject, const PRUnichar *aData);
+    void                          NotifyChanged(nsICookie2 *aCookie, const PRUnichar *aData);
 
   protected:
     // cached members
     nsCOMPtr<mozIStorageConnection>  mDBConn;
     nsCOMPtr<mozIStorageStatement>   mStmtInsert;
     nsCOMPtr<mozIStorageStatement>   mStmtDelete;
     nsCOMPtr<mozIStorageStatement>   mStmtUpdate;
     nsCOMPtr<nsIObserverService>     mObserverService;
     nsCOMPtr<nsICookiePermission>    mPermissionService;
     nsCOMPtr<nsIEffectiveTLDService> mTLDService;
 
     // impl members
     nsTHashtable<nsCookieEntry>  *mHostTable;
     nsTHashtable<nsCookieEntry>   mDefaultHostTable;
     nsTHashtable<nsCookieEntry>   mPrivateHostTable;
-    PRInt64                       mCookieOldestTime;
     PRUint32                      mCookieCount;
 
     // cached prefs
     PRUint8                       mCookiesPermissions;   // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT}
     PRUint16                      mMaxNumberOfCookies;
     PRUint16                      mMaxCookiesPerHost;
-    PRUint64                      mCookiePurgeAge;
 
     // private static member, used to cache a ptr to nsCookieService,
     // so we can make nsCookieService a singleton xpcom object.
     static nsCookieService        *gCookieService;
 
     // this callback needs access to member functions
-    friend PLDHashOperator purgeCookiesCallback(nsCookieEntry *aEntry, void *aArg);
+    friend PLDHashOperator removeExpiredCallback(nsCookieEntry *aEntry, void *aArg);
 };
 
 #endif // nsCookieService_h__
--- a/netwerk/test/TestCookie.cpp
+++ b/netwerk/test/TestCookie.cpp
@@ -756,17 +756,51 @@ main(PRInt32 argc, char *argv[])
           expected += name;
           if (i < 59)
             expected += NS_LITERAL_CSTRING("; ");
         }
       }
       GetACookie(cookieService, "http://creation.ordering.tests/", nsnull, getter_Copies(cookie));
       rv[0] = CheckResult(cookie.get(), MUST_EQUAL, expected.get());
 
-      allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
+      // test that cookies are evicted by order of lastAccessed time, if the limit on total cookies
+      // (3000) is reached
+      nsCAutoString host;
+      for (PRInt32 i = 0; i < 3010; ++i) {
+        host = NS_LITERAL_CSTRING("http://eviction.");
+        host.AppendInt(i);
+        host += NS_LITERAL_CSTRING(".tests/");
+        SetACookie(cookieService, host.get(), nsnull, "test=eviction", nsnull);
+
+        if (i == 9) {
+          // sleep a couple of seconds, to make sure the first 10 cookies are older than
+          // subsequent ones (timer resolution varies on different platforms).
+          PR_Sleep(2 * PR_TicksPerSecond());
+        }
+      }
+      rv[1] = NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator)));
+      i = 0;
+      rv[2] = PR_FALSE; // init to failure in case we break from the while loop
+      while (NS_SUCCEEDED(enumerator->HasMoreElements(&more)) && more) {
+        nsCOMPtr<nsISupports> cookie;
+        if (NS_FAILED(enumerator->GetNext(getter_AddRefs(cookie)))) break;
+        ++i;
+        
+        // keep tabs on the third cookie, so we can check it later
+        nsCOMPtr<nsICookie2> cookie2(do_QueryInterface(cookie));
+        if (!cookie2) break;
+        nsCAutoString domain;
+        cookie2->GetRawHost(domain);
+        PRInt32 hostNumber;
+        PRInt32 numInts = PR_sscanf(domain.get(), "eviction.%ld.tests", &hostNumber);
+        if (numInts != 1 || hostNumber < 10) break;
+      }
+      rv[2] = i == 3000;
+
+      allTestsPassed = PrintResult(rv, 3) && allTestsPassed;
 
 
       // XXX the following are placeholders: add these tests please!
       // *** "noncompliant cookie" tests
       // *** IP address tests
       // *** speed tests