relanding bug 403372.
authordwitte@stanford.edu
Fri, 16 Nov 2007 22:08:10 -0800
changeset 8114 e97fe496b71ab032c72cd0d1e0e7f2e63d202663
parent 8113 2e8ef22d2b1a4c15640e589379721ea1616c418c
child 8115 ec5a37d4238a2631d8f670ec33e4cf91d8e3f178
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)
bugs403372
milestone1.9b2pre
relanding bug 403372.
netwerk/cookie/src/nsCookie.cpp
netwerk/cookie/src/nsCookie.h
netwerk/cookie/src/nsCookieService.cpp
netwerk/cookie/src/nsCookieService.h
netwerk/test/TestCookie.cpp
--- a/netwerk/cookie/src/nsCookie.cpp
+++ b/netwerk/cookie/src/nsCookie.cpp
@@ -84,16 +84,17 @@ StrBlockCopy(const nsACString &aSource1,
 static PRInt64 gLastCreationID;
 
 nsCookie *
 nsCookie::Create(const nsACString &aName,
                  const nsACString &aValue,
                  const nsACString &aHost,
                  const nsACString &aPath,
                  PRInt64           aExpiry,
+                 PRInt64           aLastAccessed,
                  PRInt64           aCreationID,
                  PRBool            aIsSession,
                  PRBool            aIsSecure,
                  PRBool            aIsHttpOnly)
 {
   // find the required string buffer size, adding 4 for the terminating nulls
   const PRUint32 stringLength = aName.Length() + aValue.Length() +
                                 aHost.Length() + aPath.Length() + 4;
@@ -114,17 +115,17 @@ nsCookie::Create(const nsACString &aName
   // (it should always be monotonically increasing). if it's not, make up our own.
   if (aCreationID > gLastCreationID)
     gLastCreationID = aCreationID;
   else
     aCreationID = ++gLastCreationID;
 
   // construct the cookie. placement new, oh yeah!
   return new (place) nsCookie(name, value, host, path, end,
-                              aExpiry, aCreationID,
+                              aExpiry, aLastAccessed, aCreationID,
                               aIsSession, aIsSecure, aIsHttpOnly);
 }
 
 /******************************************************************************
  * nsCookie:
  * xpcom impl
  ******************************************************************************/
 
--- a/netwerk/cookie/src/nsCookie.h
+++ b/netwerk/cookie/src/nsCookie.h
@@ -65,66 +65,69 @@ class nsCookie : public nsICookie2
   private:
     // for internal use only. see nsCookie::Create().
     nsCookie(const char     *aName,
              const char     *aValue,
              const char     *aHost,
              const char     *aPath,
              const char     *aEnd,
              PRInt64         aExpiry,
+             PRInt64         aLastAccessed,
              PRInt64         aCreationID,
              PRBool          aIsSession,
              PRBool          aIsSecure,
              PRBool          aIsHttpOnly)
      : mNext(nsnull)
      , mName(aName)
      , mValue(aValue)
      , mHost(aHost)
      , mPath(aPath)
      , mEnd(aEnd)
      , mExpiry(aExpiry)
+     , mLastAccessed(aLastAccessed)
      , mCreationID(aCreationID)
      , mIsSession(aIsSession != PR_FALSE)
      , mIsSecure(aIsSecure != PR_FALSE)
      , mIsHttpOnly(aIsHttpOnly != PR_FALSE)
     {
     }
 
   public:
     // public helper to create an nsCookie object. use |operator delete|
     // to destroy an object created by this method.
     static nsCookie * Create(const nsACString &aName,
                              const nsACString &aValue,
                              const nsACString &aHost,
                              const nsACString &aPath,
                              PRInt64           aExpiry,
+                             PRInt64           aLastAccessed,
                              PRInt64           aCreationID,
                              PRBool            aIsSession,
                              PRBool            aIsSecure,
                              PRBool            aIsHttpOnly);
 
     virtual ~nsCookie() {}
 
     // fast (inline, non-xpcom) getters
     inline const nsDependentCString Name()  const { return nsDependentCString(mName, mValue - 1); }
     inline const nsDependentCString Value() const { return nsDependentCString(mValue, mHost - 1); }
     inline const nsDependentCString Host()  const { return nsDependentCString(mHost, mPath - 1); }
     inline const nsDependentCString RawHost() const { return nsDependentCString(IsDomain() ? mHost + 1 : mHost, mPath - 1); }
     inline const nsDependentCString Path()  const { return nsDependentCString(mPath, mEnd); }
-    inline PRInt64 Expiry()                 const { return mExpiry; }
-    inline PRInt64 CreationID()             const { return mCreationID; }
-    // cookie creation time, in seconds
-    inline PRInt64 CreationTime()           const { return mCreationID / PR_USEC_PER_SEC; }
+    inline PRInt64 Expiry()                 const { return mExpiry; }        // in seconds
+    inline PRInt64 LastAccessed()           const { return mLastAccessed; }  // in microseconds
+    inline PRInt64 CreationID()             const { return mCreationID; }    // in microseconds
     inline PRBool IsSession()               const { return mIsSession; }
     inline PRBool IsDomain()                const { return *mHost == '.'; }
     inline PRBool IsSecure()                const { return mIsSecure; }
     inline PRBool IsHttpOnly()              const { return mIsHttpOnly; }
 
     // setters
     inline void SetExpiry(PRInt64 aExpiry)        { mExpiry = aExpiry; }
+    inline void SetLastAccessed(PRInt64 aTime)    { mLastAccessed = aTime; }
     inline void SetIsSession(PRBool aIsSession)   { mIsSession = aIsSession; }
     // set the creation id manually, overriding the monotonicity checks in Create().
     // use with caution!
     inline void SetCreationID(PRInt64 aID)        { mCreationID = aID; }
 
     // linked list management helper
     inline nsCookie*& Next() { return mNext; }
 
@@ -137,16 +140,17 @@ class nsCookie : public nsICookie2
 
     nsCookie    *mNext;
     const char  *mName;
     const char  *mValue;
     const char  *mHost;
     const char  *mPath;
     const char  *mEnd;
     PRInt64      mExpiry;
+    PRInt64      mLastAccessed;
     // creation id is unique for each cookie and approximately represents the cookie
     // creation time, in microseconds.
     PRInt64      mCreationID;
     PRPackedBool mIsSession;
     PRPackedBool mIsSecure;
     PRPackedBool mIsHttpOnly;
 };
 
--- a/netwerk/cookie/src/nsCookieService.cpp
+++ b/netwerk/cookie/src/nsCookieService.cpp
@@ -1,10 +1,9 @@
-// vim:ts=2:sw=2:et:
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -78,17 +77,19 @@
  ******************************************************************************/
 
 // XXX_hack. See bug 178993.
 // 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 1
+#define COOKIES_SCHEMA_VERSION 2
+
+static const PRInt64 kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute 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.
@@ -121,17 +122,16 @@ struct nsCookieAttributes
 {
   nsCAutoString name;
   nsCAutoString value;
   nsCAutoString host;
   nsCAutoString path;
   nsCAutoString expires;
   nsCAutoString maxage;
   PRInt64 expiryTime;
-  PRInt64 creationID;
   PRBool isSession;
   PRBool isSecure;
   PRBool isHttpOnly;
 };
 
 // stores linked list iteration state, and provides a rudimentary
 // list traversal method
 struct nsListIter
@@ -157,27 +157,27 @@ struct nsListIter
   nsCookie      *current;
 };
 
 // stores temporary data for enumerating over the hash entries,
 // since enumeration is done using callback functions
 struct nsEnumerationData
 {
   nsEnumerationData(PRInt64 aCurrentTime,
-                    PRInt64 aOldestID)
+                    PRInt64 aOldestTime)
    : currentTime(aCurrentTime)
-   , oldestID(aOldestID)
+   , oldestTime(aOldestTime)
    , iter(nsnull, nsnull, nsnull) {}
 
-  // the current time
+  // the current time, in seconds
   PRInt64 currentTime;
 
-  // oldest creation id in the cookie list. use aOldestID = LL_MAXINT
+  // oldest lastAccessed time in the cookie list. use aOldestTime = LL_MAXINT
   // to enable this search, LL_MININT to disable it.
-  PRInt64 oldestID;
+  PRInt64 oldestTime;
 
   // an iterator object that points to the desired cookie
   nsListIter iter;
 };
 
 /******************************************************************************
  * Cookie logging handlers
  * used for logging in nsCookieService
@@ -477,86 +477,96 @@ nsCookieService::InitDB()
       rv = CreateTable();
       NS_ENSURE_SUCCESS(rv, rv);
 
   } else {
     // table already exists; check the schema version before reading
     PRInt32 dbSchemaVersion;
     rv = mDBConn->GetSchemaVersion(&dbSchemaVersion);
     NS_ENSURE_SUCCESS(rv, rv);
-    
-    if (dbSchemaVersion == 0) {
-      NS_WARNING("couldn't get schema version!");
-        
-      // the table may be usable; someone might've just clobbered the schema
-      // version. we can treat this case like a downgrade using the codepath
-      // below, by verifying the columns we care about are all there. for now,
-      // re-set the schema version in the db, in case the checks succeed (if
-      // they don't, we're dropping the table anyway).
-      rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
-      NS_ENSURE_SUCCESS(rv, rv);
 
-      // set this to a large number, to force the downgrade codepath
-      dbSchemaVersion = PR_INT32_MAX;
-    }
+    switch (dbSchemaVersion) {
+    // upgrading.
+    // every time you increment the database schema, you need to implement
+    // the upgrading code from the previous version to the new one.
+    case 1:
+      {
+        // add the lastAccessed column to the table
+        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+          "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // update the schema version
+        rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      // fall through to the next upgrade
+
+    case COOKIES_SCHEMA_VERSION:
+      break;
 
-    if (dbSchemaVersion != COOKIES_SCHEMA_VERSION) {
-      // migration how-to:
-      //
-      // 1. increment COOKIES_SCHEMA_VERSION.
-      // 2. implement a method that performs up/sidegrade to your version
-      //    from the current version.
+    case 0:
+      {
+        NS_WARNING("couldn't get schema version!");
+          
+        // the table may be usable; someone might've just clobbered the schema
+        // version. we can treat this case like a downgrade using the codepath
+        // below, by verifying the columns we care about are all there. for now,
+        // re-set the schema version in the db, in case the checks succeed (if
+        // they don't, we're dropping the table anyway).
+        rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      // fall through to downgrade check
 
-      if (dbSchemaVersion > COOKIES_SCHEMA_VERSION) {
-        // downgrading.
-        // if columns have been added to the table, we can still use the ones we
-        // understand safely. if columns have been deleted or altered, just
-        // blow away the table and start from scratch! if you change the way
-        // a column is interpreted, make sure you also change its name so this
-        // check will catch it.
-        
-        // NOTE: if you change the code below, make sure the db schema version
-        // getter above still falls through to this codepath on failure!
-        
+    // downgrading.
+    // if columns have been added to the table, we can still use the ones we
+    // understand safely. if columns have been deleted or altered, just
+    // blow away the table and start from scratch! if you change the way
+    // a column is interpreted, make sure you also change its name so this
+    // check will catch it.
+    default:
+      {
         // check if all the expected columns exist
         nsCOMPtr<mozIStorageStatement> stmt;
         rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
           "SELECT id, name, value, host, path, expiry, isSecure, isHttpOnly "
           "FROM moz_cookies"), getter_AddRefs(stmt));
-        if (NS_SUCCEEDED(rv)) {
-          PRBool hasResult;
-          rv = stmt->ExecuteStep(&hasResult);
-        }
-        
-        if (NS_FAILED(rv)) {   
-          // our columns aren't there - drop the table!
-          rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_cookies"));
-          NS_ENSURE_SUCCESS(rv, rv);
+        if (NS_SUCCEEDED(rv))
+          break;
 
-          rv = CreateTable();
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
+        // our columns aren't there - drop the table!
+        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_cookies"));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = CreateTable();
+        NS_ENSURE_SUCCESS(rv, rv);
       }
+      break;
     }
   }
 
   // make operations on the table asynchronous, for performance
   mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
 
   // cache frequently used statements (for insertion, deletion, and updating)
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
     "INSERT INTO moz_cookies "
-    "(id, name, value, host, path, expiry, isSecure, isHttpOnly) "
-    "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)"), getter_AddRefs(mStmtInsert));
+    "(id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly) "
+    "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)"), getter_AddRefs(mStmtInsert));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
     "DELETE FROM moz_cookies WHERE id = ?1"), getter_AddRefs(mStmtDelete));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+    "UPDATE moz_cookies SET lastAccessed = ?1 WHERE id = ?2"), getter_AddRefs(mStmtUpdate));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // check whether to import or just read in the db
   if (tableExists)
     return Read();
 
   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(cookieFile));
   NS_ENSURE_SUCCESS(rv, rv);
 
   cookieFile->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName));
@@ -575,17 +585,17 @@ nsCookieService::CreateTable()
   // set the schema version, before creating the table
   nsresult rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
   if (NS_FAILED(rv)) return rv;
 
   // create the table
   return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE moz_cookies ("
     "id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT,"
-    "expiry INTEGER, isSecure INTEGER, isHttpOnly INTEGER)"));
+    "expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)"));
 }
 
 nsCookieService::~nsCookieService()
 {
   gCookieService = nsnull;
 }
 
 NS_IMETHODIMP
@@ -594,23 +604,24 @@ nsCookieService::Observe(nsISupports    
                          const PRUnichar *aData)
 {
   // check the topic
   if (!strcmp(aTopic, "profile-before-change")) {
     // The profile is about to change,
     // or is going away because the application is shutting down.
     RemoveAllFromMemory();
 
-    if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get()) && mDBConn)
+    if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get()) && mDBConn) {
       // clear the cookie file
       if (mDBConn) {
         nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
         if (NS_FAILED(rv))
           NS_WARNING("db delete failed");
       }
+    }
 
   } else if (!strcmp(aTopic, "profile-do-change")) {
     // the profile has already changed; init the db from the new location
     InitDB();
 
   } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
     nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
     if (prefBranch)
@@ -820,16 +831,17 @@ nsCookieService::Add(const nsACString &a
                      PRInt64           aExpiry)
 {
   PRInt64 currentTimeInUsec = PR_Now();
 
   nsRefPtr<nsCookie> cookie =
     nsCookie::Create(aName, aValue, aDomain, aPath,
                      aExpiry,
                      currentTimeInUsec,
+                     currentTimeInUsec,
                      aIsSession,
                      aIsSecure,
                      aIsHttpOnly);
   if (!cookie) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   AddInternal(cookie, currentTimeInUsec / PR_USEC_PER_SEC, nsnull, nsnull, PR_TRUE);
@@ -888,38 +900,40 @@ nsCookieService::Read()
     PRBool hasResult;
     rv = stmtDeleteExpired->ExecuteStep(&hasResult);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // let the reading begin!
   nsCOMPtr<mozIStorageStatement> stmt;
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT id, name, value, host, path, expiry, isSecure, isHttpOnly "
+    "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly "
     "FROM moz_cookies"), getter_AddRefs(stmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCAutoString name, value, host, path;
   PRBool hasResult;
   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
     PRInt64 creationID = stmt->AsInt64(0);
     
     stmt->GetUTF8String(1, name);
     stmt->GetUTF8String(2, value);
     stmt->GetUTF8String(3, host);
     stmt->GetUTF8String(4, path);
 
     PRInt64 expiry = stmt->AsInt64(5);
-    PRBool isSecure = stmt->AsInt32(6);
-    PRBool isHttpOnly = stmt->AsInt32(7);
+    PRInt64 lastAccessed = stmt->AsInt64(6);
+    PRBool isSecure = stmt->AsInt32(7);
+    PRBool isHttpOnly = stmt->AsInt32(8);
 
     // create a new nsCookie and assign the data.
     nsCookie* newCookie =
       nsCookie::Create(name, value, host, path,
                        expiry,
+                       lastAccessed,
                        creationID,
                        PR_FALSE,
                        isSecure,
                        isHttpOnly);
     if (!newCookie)
       return NS_ERROR_OUT_OF_MEMORY;
 
     if (!AddCookieToList(newCookie, PR_FALSE))
@@ -959,32 +973,33 @@ nsCookieService::ImportCookies(nsIFile *
   PRBool isMore = PR_TRUE;
   PRInt32 hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
   nsASingleFragmentCString::char_iterator iter;
   PRInt32 numInts;
   PRInt64 expires;
   PRBool isDomain, isHttpOnly = PR_FALSE;
   PRUint32 originalCookieCount = mCookieCount;
 
-  // generate a creation id for all the cookies we're going to read in, by
-  // using the current time and successively decrementing it, to keep
-  // the most-recently-used cookie ordering. the actual creation time is
-  // unknown, so this is the best we can do.
-  PRInt64 creationIDCounter = PR_Now();
-  PRInt64 currentTime = creationIDCounter / PR_USEC_PER_SEC;
+  PRInt64 currentTimeInUsec = PR_Now();
+  PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
+  // we use lastAccessedCounter to keep cookies in recently-used order,
+  // so we start by initializing to currentTime (somewhat arbitrary)
+  PRInt64 lastAccessedCounter = currentTimeInUsec;
 
   /* file format is:
    *
    * host \t isDomain \t path \t secure \t expires \t name \t cookie
    *
    * if this format isn't respected we move onto the next line in the file.
    * isDomain is "TRUE" or "FALSE" (default to "FALSE")
    * isSecure is "TRUE" or "FALSE" (default to "TRUE")
    * expires is a PRInt64 integer
-   * note: cookie can contain tabs.
+   * note 1: cookie can contain tabs.
+   * note 2: cookies will be stored in order of lastAccessed time:
+   *         most-recently used come first; least-recently-used come last.
    */
 
   /*
    * ...but due to bug 178933, we hide HttpOnly cookies from older code
    * in a comment, so they don't expose HttpOnly cookies to JS.
    *
    * The format for HttpOnly cookies is
    *
@@ -1030,34 +1045,37 @@ nsCookieService::ImportCookies(nsIFile *
     // check for bad legacy cookies (domain not starting with a dot, or containing a port),
     // and discard
     if (isDomain && !host.IsEmpty() && host.First() != '.' ||
         host.FindChar(':') != kNotFound) {
       continue;
     }
 
     // create a new nsCookie and assign the data.
+    // we don't know the cookie creation time, so just use the current time;
+    // this is okay, since nsCookie::Create() will make sure the creation id
+    // ends up monotonically increasing.
     nsRefPtr<nsCookie> newCookie =
       nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
                        Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
                        host,
                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
                        expires,
-                       creationIDCounter,
+                       lastAccessedCounter,
+                       currentTimeInUsec,
                        PR_FALSE,
                        Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
                        isHttpOnly);
     if (!newCookie) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
     
-    // manually set the creation id. this is okay, since nsCookie::Create() will keep
-    // track of the largest id we've used, and we're setting it to a smaller
-    // known unique one here.
-    newCookie->SetCreationID(--creationIDCounter);
+    // trick: preserve the most-recently-used cookie ordering,
+    // by successively decrementing the lastAccessed time
+    lastAccessedCounter--;
 
     if (originalCookieCount == 0)
       AddCookieToList(newCookie);
     else
       AddInternal(newCookie, currentTime, nsnull, nsnull, PR_TRUE);
   }
 
   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported", mCookieCount));
@@ -1117,19 +1135,21 @@ nsCookieService::GetCookieInternal(nsIUR
   // if SchemeIs fails, assume an insecure connection, to be on the safe side
   PRBool isSecure;
   if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
     isSecure = PR_FALSE;
   }
 
   nsCookie *cookie;
   nsAutoVoidArray foundCookieList;
-  PRInt64 currentTime = PR_Now() / PR_USEC_PER_SEC;
+  PRInt64 currentTimeInUsec = PR_Now();
+  PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
   const char *currentDot = hostFromURI.get();
   const char *nextDot = currentDot + 1;
+  PRBool stale = PR_FALSE;
 
   // begin hash lookup, walking up the subdomain levels.
   // we use nextDot to force a lookup of the original host (without leading dot).
   do {
     nsCookieEntry *entry = mHostTable.GetEntry(currentDot);
     cookie = entry ? entry->Head() : nsnull;
     for (; cookie; cookie = cookie->Next()) {
       // if the cookie is secure and the host scheme isn't, we can't send it
@@ -1167,33 +1187,53 @@ nsCookieService::GetCookieInternal(nsIUR
         continue;
       }
 
       // check if the cookie has expired
       if (cookie->Expiry() <= currentTime) {
         continue;
       }
 
-      // all checks passed - add to list
+      // all checks passed - add to list and check if lastAccessed stamp needs updating
       foundCookieList.AppendElement(cookie);
+      if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
+        stale = PR_TRUE;
     }
 
     currentDot = nextDot;
     if (currentDot)
       nextDot = strchr(currentDot + 1, '.');
 
   } while (currentDot);
 
+  PRInt32 count = foundCookieList.Count();
+  if (count == 0)
+    return;
+
+  // update lastAccessed timestamps. we only do this if the timestamp is stale
+  // by a certain amount, to avoid thrashing the db during pageload.
+  if (stale) {
+    // start a transaction on the storage db, to optimize updates.
+    // transaction will automically commit on completion.
+    mozStorageTransaction transaction(mDBConn, PR_TRUE);
+
+    for (PRInt32 i = 0; i < count; ++i) {
+      cookie = static_cast<nsCookie*>(foundCookieList.ElementAt(i));
+
+      if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
+        UpdateCookieInList(cookie, currentTime);
+    }
+  }
+
   // return cookies in order of path length; longest to shortest.
   // this is required per RFC2109.  if cookies match in length,
   // then sort by creation time (see bug 236772).
   foundCookieList.Sort(compareCookiesForSending, nsnull);
 
   nsCAutoString cookieData;
-  PRInt32 count = foundCookieList.Count();
   for (PRInt32 i = 0; i < count; ++i) {
     cookie = static_cast<nsCookie*>(foundCookieList.ElementAt(i));
 
     // check if we have anything to write
     if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
       // if we've already added a cookie to the return list, append a "; " so
       // that subsequent cookies are delimited in the final list.
       if (!cookieData.IsEmpty()) {
@@ -1237,19 +1277,17 @@ nsCookieService::SetCookieInternal(nsIUR
   // aCookieHeader is an in/out param to point to the next cookie, if
   // there is one. Save the present value for logging purposes
   nsDependentCString savedCookieHeader(aCookieHeader);
 
   // newCookie says whether there are multiple cookies in the header;
   // so we can handle them separately.
   PRBool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
 
-  // generate a creation id for the cookie
   PRInt64 currentTimeInUsec = PR_Now();
-  cookieAttributes.creationID = currentTimeInUsec;
 
   // calculate expiry time of cookie.
   cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
                                          currentTimeInUsec / PR_USEC_PER_SEC);
 
   // reject cookie if it's over the size limit, per RFC2109
   if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
@@ -1273,17 +1311,18 @@ nsCookieService::SetCookieInternal(nsIUR
 
   // create a new nsCookie and copy attributes
   nsRefPtr<nsCookie> cookie =
     nsCookie::Create(cookieAttributes.name,
                      cookieAttributes.value,
                      cookieAttributes.host,
                      cookieAttributes.path,
                      cookieAttributes.expiryTime,
-                     cookieAttributes.creationID,
+                     currentTimeInUsec,
+                     currentTimeInUsec,
                      cookieAttributes.isSession,
                      cookieAttributes.isSecure,
                      cookieAttributes.isHttpOnly);
   if (!cookie)
     return newCookie;
 
   // check permissions from site permission list, or ask the user,
   // to determine if we can set the cookie
@@ -1355,16 +1394,20 @@ nsCookieService::AddInternal(nsCookie   
 
     // check if the cookie has expired
     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() <= aCurrentTime) {
       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie has already expired");
       return;
     }
 
     // check if we have to delete an old cookie.
@@ -1376,17 +1419,17 @@ nsCookieService::AddInternal(nsCookie   
 
     } else if (mCookieCount >= mMaxNumberOfCookies) {
       // try to make room, by removing expired cookies
       RemoveExpiredCookies(aCurrentTime);
 
       // check if we still have to get rid of something
       if (mCookieCount >= mMaxNumberOfCookies) {
         // find the position of the oldest cookie, and remove it
-        data.oldestID = LL_MAXINT;
+        data.oldestTime = LL_MAXINT;
         FindOldestCookie(data);
         oldCookie = data.iter.current;
         RemoveCookieFromList(data.iter);
       }
     }
 
     // if we deleted an old cookie, notify consumers
     if (oldCookie) {
@@ -2086,18 +2129,18 @@ nsCookieService::CountCookiesFromHostInt
   do {
     nsCookieEntry *entry = mHostTable.GetEntry(currentDot);
     for (nsListIter iter(entry); iter.current; ++iter) {
       // only count non-expired cookies
       if (iter.current->Expiry() > aData.currentTime) {
         ++countFromHost;
 
         // check if we've found the oldest cookie so far
-        if (aData.oldestID > iter.current->CreationID()) {
-          aData.oldestID = iter.current->CreationID();
+        if (aData.oldestTime > iter.current->LastAccessed()) {
+          aData.oldestTime = iter.current->LastAccessed();
           aData.iter = iter;
         }
       }
     }
 
     currentDot = nextDot;
     if (currentDot)
       nextDot = strchr(currentDot + 1, '.');
@@ -2200,20 +2243,23 @@ bindCookieParameters(mozIStorageStatemen
   if (NS_FAILED(rv)) return rv;
   
   rv = aStmt->BindUTF8StringParameter(4, aCookie->Path());
   if (NS_FAILED(rv)) return rv;
   
   rv = aStmt->BindInt64Parameter(5, aCookie->Expiry());
   if (NS_FAILED(rv)) return rv;
   
-  rv = aStmt->BindInt32Parameter(6, aCookie->IsSecure());
+  rv = aStmt->BindInt64Parameter(6, aCookie->LastAccessed());
   if (NS_FAILED(rv)) return rv;
   
-  rv = aStmt->BindInt32Parameter(7, aCookie->IsHttpOnly());
+  rv = aStmt->BindInt32Parameter(7, aCookie->IsSecure());
+  if (NS_FAILED(rv)) return rv;
+  
+  rv = aStmt->BindInt32Parameter(8, aCookie->IsHttpOnly());
   return rv;
 }
 
 PRBool
 nsCookieService::AddCookieToList(nsCookie *aCookie, PRBool aWriteToDB)
 {
   nsCookieEntry *entry = mHostTable.PutEntry(aCookie->Host().get());
 
@@ -2243,28 +2289,56 @@ nsCookieService::AddCookieToList(nsCooki
       NS_WARNING("db insert failed!");
       COOKIE_LOGSTRING(PR_LOG_WARNING, ("AddCookieToList(): adding to db gave error %x", rv));
     }
   }
 
   return PR_TRUE;
 }
 
+void
+nsCookieService::UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed)
+{
+  // update the lastAccessed timestamp
+  aCookie->SetLastAccessed(aLastAccessed);
+
+  // if it's a non-session cookie, update it in the db too
+  if (!aCookie->IsSession() && mStmtUpdate) {
+    // use our cached sqlite "update" statement
+    mozStorageStatementScoper scoper(mStmtUpdate);
+
+    nsresult rv = mStmtUpdate->BindInt64Parameter(0, aLastAccessed);
+    if (NS_SUCCEEDED(rv)) {
+      rv = mStmtUpdate->BindInt64Parameter(1, aCookie->CreationID());
+      if (NS_SUCCEEDED(rv)) {
+        PRBool hasResult;
+        rv = mStmtUpdate->ExecuteStep(&hasResult);
+      }
+    }
+
+    if (NS_FAILED(rv)) {
+      NS_WARNING("db update failed!");
+      COOKIE_LOGSTRING(PR_LOG_WARNING, ("UpdateCookieInList(): updating db gave error %x", rv));
+    }
+  }
+}
+
 PR_STATIC_CALLBACK(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->oldestID > iter.current->CreationID()) {
-      data->oldestID = iter.current->CreationID();
+    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
@@ -168,16 +168,17 @@ class nsCookieService : public nsICookie
     nsresult                      CreateTable();
     nsresult                      Read();
     void                          GetCookieInternal(nsIURI *aHostURI, nsIURI *aFirstURI, nsIChannel *aChannel, PRBool aHttpBound, char **aCookie);
     nsresult                      SetCookieStringInternal(nsIURI *aHostURI, nsIURI *aFirstURI, 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 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);
     static PRBool                 IsIPAddress(const nsAFlatCString &aHost);
     static PRBool                 IsInDomain(const nsACString &aDomain, const nsACString &aHost, PRBool aIsDomain = PR_TRUE);
     static PRBool                 IsForeign(nsIURI *aHostURI, nsIURI *aFirstURI);
     PRUint32                      CheckPrefs(nsIURI *aHostURI, nsIURI *aFirstURI, nsIChannel *aChannel, const char *aCookieHeader);
     static PRBool                 CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static PRBool                 CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
@@ -190,16 +191,17 @@ class nsCookieService : public nsICookie
     void                          NotifyRejected(nsIURI *aHostURI);
     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;
 
     // impl members
     nsTHashtable<nsCookieEntry>   mHostTable;
     PRUint32                      mCookieCount;
 
     // cached prefs
--- a/netwerk/test/TestCookie.cpp
+++ b/netwerk/test/TestCookie.cpp
@@ -781,17 +781,17 @@ main(PRInt32 argc, char *argv[])
       allTestsPassed = PrintResult(rv, 16) && allTestsPassed;
 
 
       // *** eviction and creation ordering tests
       printf("*** Beginning eviction and creation ordering tests...\n");
 
       // test that cookies are
       // a) returned by order of creation time (oldest first, newest last)
-      // b) evicted by order of creation time, if the limit on cookies per host (50) is reached
+      // b) evicted by order of lastAccessed time, if the limit on cookies per host (50) is reached
       nsCAutoString name;
       nsCAutoString expected;
       for (PRInt32 i = 0; i < 60; ++i) {
         name = NS_LITERAL_CSTRING("test");
         name.AppendInt(i);
         name += NS_LITERAL_CSTRING("=creation");
         SetACookie(cookieService, "http://creation.ordering.tests/", nsnull, name.get(), nsnull);
 
@@ -799,17 +799,17 @@ 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());
 
-      // test that cookies are evicted by order of creation time, if the limit on total cookies
+      // test that cookies are evicted by order of lastAccessed time, if the limit on total cookies
       // (1000) is reached
       nsCAutoString host;
       for (PRInt32 i = 0; i < 1010; ++i) {
         host = NS_LITERAL_CSTRING("http://eviction.");
         host.AppendInt(i);
         host += NS_LITERAL_CSTRING(".tests/");
         SetACookie(cookieService, host.get(), nsnull, "test=eviction", nsnull);
       }