Bug 1609176 - Remove the obsolete baseDomain field of the moz_cookies table; r=baku
authorEhsan Akhgari <ehsan@mozilla.com>
Sat, 25 Jan 2020 00:18:34 +0000
changeset 511768 c930febec76e00c3dd61d1826d9696cce37cd095
parent 511767 b92980425b1a6672eba77516aa98aa87989c5378
child 511769 1acc873aa118398b449859f0a67b8993f9776cc2
push id106017
push usereakhgari@mozilla.com
push dateSat, 25 Jan 2020 00:32:26 +0000
treeherderautoland@c930febec76e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1609176
milestone74.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 1609176 - Remove the obsolete baseDomain field of the moz_cookies table; r=baku This change is backwards incompatible with the older cookies.sqlite files, which means files saved from newer versions of Firefox will no longer be possible to open in older versions of Firefox. Differential Revision: https://phabricator.services.mozilla.com/D60464
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsCookieService.h
netwerk/cookie/test/unit/test_bug1321912.js
netwerk/cookie/test/unit/test_rawSameSite.js
netwerk/test/unit/data/cookies_v10.sqlite
netwerk/test/unit/head_cookies.js
netwerk/test/unit/test_cookies_async_failure.js
netwerk/test/unit/test_cookies_read.js
netwerk/test/unit/test_cookies_sync_failure.js
netwerk/test/unit/test_cookies_upgrade_10-11.js
netwerk/test/unit/test_schema_10_migration.js
netwerk/test/unit/test_schema_2_migration.js
netwerk/test/unit/test_schema_3_migration.js
netwerk/test/unit/xpcshell.ini
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -62,50 +62,43 @@
 #include "nsIConsoleService.h"
 #include "nsTPriorityQueue.h"
 #include "nsVariant.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::net;
 
-// Create key from baseDomain that will access the default cookie namespace.
-// TODO: When we figure out what the API will look like for nsICookieManager{2}
-// on content processes (see bug 777620), change to use the appropriate app
-// namespace.  For now those IDLs aren't supported on child processes.
-#define DEFAULT_APP_KEY(baseDomain) nsCookieKey(baseDomain, OriginAttributes())
-
 /******************************************************************************
  * nsCookieService impl:
  * useful types & constants
  ******************************************************************************/
 
 static StaticRefPtr<nsCookieService> gCookieService;
 
 // XXX_hack. See bug 178993.
 // This is a hack to hide HttpOnly cookies from older browsers
 #define HTTP_ONLY_PREFIX "#HttpOnly_"
 
 #define COOKIES_FILE "cookies.sqlite"
-#define COOKIES_SCHEMA_VERSION 10
+#define COOKIES_SCHEMA_VERSION 11
 
 // parameter indexes; see |Read|
 #define IDX_NAME 0
 #define IDX_VALUE 1
 #define IDX_HOST 2
 #define IDX_PATH 3
 #define IDX_EXPIRY 4
 #define IDX_LAST_ACCESSED 5
 #define IDX_CREATION_TIME 6
 #define IDX_SECURE 7
 #define IDX_HTTPONLY 8
-#define IDX_BASE_DOMAIN 9
-#define IDX_ORIGIN_ATTRIBUTES 10
-#define IDX_SAME_SITE 11
-#define IDX_RAW_SAME_SITE 12
+#define IDX_ORIGIN_ATTRIBUTES 9
+#define IDX_SAME_SITE 10
+#define IDX_RAW_SAME_SITE 11
 
 static const int64_t kCookiePurgeAge =
     int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC;  // 30 days in microseconds
 
 #define OLD_COOKIE_FILE_NAME "cookies.txt"
 
 #undef LIMIT
 #define LIMIT(x, low, high, default) \
@@ -1242,17 +1235,19 @@ OpenDBResult nsCookieService::TryInitDB(
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Rename new_moz_cookies to moz_cookies.
         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
             "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Recreate our index.
-        rv = CreateIndex();
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
+            NS_LITERAL_CSTRING("CREATE INDEX moz_basedomain ON moz_cookies "
+                               "(baseDomain, originAttributes)"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         COOKIE_LOGSTRING(LogLevel::Debug,
                          ("Upgraded database to schema version 8"));
       }
         [[fallthrough]];
 
       case 8: {
@@ -1274,16 +1269,96 @@ OpenDBResult nsCookieService::TryInitDB(
 
         // Copy the current sameSite value into rawSameSite.
         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
             "UPDATE moz_cookies SET rawSameSite = sameSite"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         COOKIE_LOGSTRING(LogLevel::Debug,
                          ("Upgraded database to schema version 10"));
+      }
+        [[fallthrough]];
+
+      case 10: {
+        // Rename existing table
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+            "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        // Create a new moz_cookies table without the baseDomain field.
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
+            NS_LITERAL_CSTRING("CREATE TABLE moz_cookies("
+                               "id INTEGER PRIMARY KEY, "
+                               "originAttributes TEXT NOT NULL DEFAULT '', "
+                               "name TEXT, "
+                               "value TEXT, "
+                               "host TEXT, "
+                               "path TEXT, "
+                               "expiry INTEGER, "
+                               "lastAccessed INTEGER, "
+                               "creationTime INTEGER, "
+                               "isSecure INTEGER, "
+                               "isHttpOnly INTEGER, "
+                               "inBrowserElement INTEGER DEFAULT 0, "
+                               "sameSite INTEGER DEFAULT 0, "
+                               "rawSameSite INTEGER DEFAULT 0, "
+                               "CONSTRAINT moz_uniqueid UNIQUE (name, host, "
+                               "path, originAttributes)"
+                               ")"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        // Move the data over.
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
+            NS_LITERAL_CSTRING("INSERT INTO moz_cookies ("
+                               "id, "
+                               "originAttributes, "
+                               "name, "
+                               "value, "
+                               "host, "
+                               "path, "
+                               "expiry, "
+                               "lastAccessed, "
+                               "creationTime, "
+                               "isSecure, "
+                               "isHttpOnly, "
+                               "inBrowserElement, "
+                               "sameSite, "
+                               "rawSameSite "
+                               ") SELECT "
+                               "id, "
+                               "originAttributes, "
+                               "name, "
+                               "value, "
+                               "host, "
+                               "path, "
+                               "expiry, "
+                               "lastAccessed, "
+                               "creationTime, "
+                               "isSecure, "
+                               "isHttpOnly, "
+                               "inBrowserElement, "
+                               "sameSite, "
+                               "rawSameSite "
+                               "FROM moz_cookies_old "
+                               "WHERE baseDomain NOTNULL;"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        // Drop the old table
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
+            NS_LITERAL_CSTRING("DROP TABLE moz_cookies_old;"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        // Drop the moz_basedomain index from the database (if it hasn't been
+        // removed already by removing the table).
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
+            NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_basedomain;"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        COOKIE_LOGSTRING(LogLevel::Debug,
+                         ("Upgraded database to schema version 11"));
 
         // No more upgrades. Update the schema version.
         rv =
             mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       }
         [[fallthrough]];
 
@@ -1312,17 +1387,16 @@ OpenDBResult nsCookieService::TryInitDB(
       // 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 = mDefaultDBState->syncConn->CreateStatement(
             NS_LITERAL_CSTRING("SELECT "
                                "id, "
-                               "baseDomain, "
                                "originAttributes, "
                                "name, "
                                "value, "
                                "host, "
                                "path, "
                                "expiry, "
                                "lastAccessed, "
                                "creationTime, "
@@ -1460,31 +1534,29 @@ nsresult nsCookieService::InitDBConnInte
   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
       MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
   mDefaultDBState->dbConn->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = 16"));
 
   // cache frequently used statements (for insertion, deletion, and updating)
   rv = mDefaultDBState->dbConn->CreateAsyncStatement(
       NS_LITERAL_CSTRING("INSERT INTO moz_cookies ("
-                         "baseDomain, "
                          "originAttributes, "
                          "name, "
                          "value, "
                          "host, "
                          "path, "
                          "expiry, "
                          "lastAccessed, "
                          "creationTime, "
                          "isSecure, "
                          "isHttpOnly, "
                          "sameSite, "
                          "rawSameSite "
                          ") VALUES ("
-                         ":baseDomain, "
                          ":originAttributes, "
                          ":name, "
                          ":value, "
                          ":host, "
                          ":path, "
                          ":expiry, "
                          ":lastAccessed, "
                          ":creationTime, "
@@ -1517,17 +1589,16 @@ nsresult nsCookieService::CreateTableWor
   // We default originAttributes to empty string: this is so if users revert to
   // an older Firefox version that doesn't know about this field, any cookies
   // set will still work once they upgrade back.
   nsAutoCString command("CREATE TABLE ");
   command.Append(aName);
   command.AppendLiteral(
       " ("
       "id INTEGER PRIMARY KEY, "
-      "baseDomain TEXT, "
       "originAttributes TEXT NOT NULL DEFAULT '', "
       "name TEXT, "
       "value TEXT, "
       "host TEXT, "
       "path TEXT, "
       "expiry INTEGER, "
       "lastAccessed INTEGER, "
       "creationTime INTEGER, "
@@ -1546,24 +1617,17 @@ nsresult nsCookieService::CreateTable() 
   // Set the schema version, before creating the table.
   nsresult rv =
       mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
   if (NS_FAILED(rv)) return rv;
 
   rv = CreateTableWorker("moz_cookies");
   if (NS_FAILED(rv)) return rv;
 
-  return CreateIndex();
-}
-
-nsresult nsCookieService::CreateIndex() {
-  // Create an index on baseDomain.
-  return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
-      "originAttributes)"));
+  return NS_OK;
 }
 
 // Sets the schema version and creates the moz_cookies table.
 nsresult nsCookieService::CreateTableForSchemaVersion6() {
   // Set the schema version, before creating the table.
   nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion(6);
   if (NS_FAILED(rv)) return rv;
 
@@ -2528,17 +2592,16 @@ nsCookieService::RemoveNative(const nsAC
 /******************************************************************************
  * nsCookieService impl:
  * private file I/O functions
  ******************************************************************************/
 
 // Extract data from a single result row and create an nsCookie.
 mozilla::UniquePtr<CookieStruct> nsCookieService::GetCookieFromRow(
     mozIStorageStatement* aRow) {
-  // Skip reading 'baseDomain' -- up to the caller.
   nsCString name, value, host, path;
   DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
   NS_ASSERT_SUCCESS(rv);
   rv = aRow->GetUTF8String(IDX_VALUE, value);
   NS_ASSERT_SUCCESS(rv);
   rv = aRow->GetUTF8String(IDX_HOST, host);
   NS_ASSERT_SUCCESS(rv);
   rv = aRow->GetUTF8String(IDX_PATH, path);
@@ -2601,44 +2664,34 @@ void nsCookieService::EnsureReadComplete
       mEndInitDBConn = TimeStamp();
     }
   }
 }
 
 OpenDBResult nsCookieService::Read() {
   MOZ_ASSERT(NS_GetCurrentThread() == mThread);
 
-  // Set up a statement to delete any rows with a nullptr 'baseDomain'
-  // column. This takes care of any cookies set by browsers that don't
-  // understand the 'baseDomain' column, where the database schema version
-  // is from one that does. (This would occur when downgrading.)
-  nsresult rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
-      NS_LITERAL_CSTRING("DELETE FROM moz_cookies WHERE baseDomain ISNULL"));
-  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
-
   // Read in the data synchronously.
   // see IDX_NAME, etc. for parameter indexes
   nsCOMPtr<mozIStorageStatement> stmt;
-  rv = mDefaultDBState->syncConn->CreateStatement(
+  nsresult rv = mDefaultDBState->syncConn->CreateStatement(
       NS_LITERAL_CSTRING("SELECT "
                          "name, "
                          "value, "
                          "host, "
                          "path, "
                          "expiry, "
                          "lastAccessed, "
                          "creationTime, "
                          "isSecure, "
                          "isHttpOnly, "
-                         "baseDomain, "
                          "originAttributes, "
                          "sameSite, "
                          "rawSameSite "
-                         "FROM moz_cookies "
-                         "WHERE baseDomain NOTNULL"),
+                         "FROM moz_cookies"),
       getter_AddRefs(stmt));
 
   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   if (NS_WARN_IF(!mReadArray.IsEmpty())) {
     mReadArray.Clear();
   }
   mReadArray.SetCapacity(kMaxNumberOfCookies);
@@ -2649,18 +2702,16 @@ OpenDBResult nsCookieService::Read() {
     rv = stmt->ExecuteStep(&hasResult);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mReadArray.Clear();
       return RESULT_RETRY;
     }
 
     if (!hasResult) break;
 
-    // IDX_BASE_DOMAIN cannot be used, because updates to the public suffix list
-    // may invalidate the value of the stored baseDomain.
     stmt->GetUTF8String(IDX_HOST, host);
 
     rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
     if (NS_FAILED(rv)) {
       COOKIE_LOGSTRING(LogLevel::Debug,
                        ("Read(): Ignoring invalid host '%s'", host.get()));
       continue;
     }
@@ -2803,17 +2854,17 @@ nsCookieService::ImportCookies(nsIFile* 
     }
 
     // compute the baseDomain from the host
     rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
     if (NS_FAILED(rv)) continue;
 
     // pre-existing cookies have inIsolatedMozBrowser=false set by default
     // constructor of OriginAttributes().
-    nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
+    nsCookieKey key(baseDomain, OriginAttributes());
 
     // Create a new nsCookie and assign the data. We don't know the cookie
     // creation time, so just use the current time to generate a unique one.
     RefPtr<nsCookie> newCookie = nsCookie::Create(
         Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
         Substring(buffer, cookieIndex, buffer.Length() - cookieIndex), host,
         Substring(buffer, pathIndex, secureIndex - pathIndex - 1), expires,
         lastAccessedCounter,
@@ -4526,17 +4577,17 @@ nsCookieService::CountCookiesFromHost(co
   nsAutoCString host(aHost);
   nsresult rv = NormalizeHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString baseDomain;
   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
+  nsCookieKey key(baseDomain, OriginAttributes());
 
   // Return a count of all cookies, including expired.
   nsCookieEntry* entry = mDBState->hostTable.GetEntry(key);
   *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
   return NS_OK;
 }
 
 // get an enumerator of cookies stored by a particular host. this is provided by
@@ -4991,21 +5042,16 @@ void bindCookieParameters(mozIStorageBin
 
   // Use the asynchronous binding methods to ensure that we do not acquire the
   // database lock.
   nsCOMPtr<mozIStorageBindingParams> params;
   DebugOnly<nsresult> rv =
       aParamsArray->NewBindingParams(getter_AddRefs(params));
   NS_ASSERT_SUCCESS(rv);
 
-  // Bind our values to params
-  rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
-                                    aKey.mBaseDomain);
-  NS_ASSERT_SUCCESS(rv);
-
   nsAutoCString suffix;
   aKey.mOriginAttributes.CreateSuffix(suffix);
   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
                                     suffix);
   NS_ASSERT_SUCCESS(rv);
 
   rv =
       params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), aCookie->Name());
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -238,17 +238,16 @@ class nsCookieService final : public nsI
   virtual ~nsCookieService();
 
   void PrefChanged(nsIPrefBranch* aPrefBranch);
   void InitDBStates();
   OpenDBResult TryInitDB(bool aDeleteExistingDB);
   void InitDBConn();
   nsresult InitDBConnInternal();
   nsresult CreateTableWorker(const char* aName);
-  nsresult CreateIndex();
   nsresult CreateTable();
   nsresult CreateTableForSchemaVersion6();
   nsresult CreateTableForSchemaVersion5();
   void CloseDBStates();
   void CleanupCachedStatements();
   void CleanupDefaultDBConnection();
   void HandleDBClosed(DBState* aDBState);
   void HandleCorruptDB(DBState* aDBState);
--- a/netwerk/cookie/test/unit/test_bug1321912.js
+++ b/netwerk/cookie/test/unit/test_bug1321912.js
@@ -56,34 +56,33 @@ conn.executeSimpleSQL(
     now +
     ", 1, 1)"
 );
 
 // Now start the cookie service, and then check the fields in the table.
 // Get sessionCookies to wait for the initialization in cookie thread
 const cookies = Services.cookies.sessionCookies;
 
-Assert.equal(conn.schemaVersion, 10);
+Assert.equal(conn.schemaVersion, 11);
 let stmt = conn.createStatement(
   "SELECT sql FROM sqlite_master " +
     "WHERE type = 'table' AND " +
     "      name = 'moz_cookies'"
 );
 try {
   Assert.ok(stmt.executeStep());
   let sql = stmt.getString(0);
   Assert.equal(sql.indexOf("appId"), -1);
 } finally {
   stmt.finalize();
 }
 
 stmt = conn.createStatement(
   "SELECT * FROM moz_cookies " +
-    "WHERE baseDomain = 'foo.com' AND " +
-    "      host = '.foo.com' AND " +
+    "WHERE host = '.foo.com' AND " +
     "      name = 'foo' AND " +
     "      value = 'bar=baz' AND " +
     "      path = '/' AND " +
     "      expiry = " +
     now +
     " AND " +
     "      lastAccessed = " +
     now +
--- a/netwerk/cookie/test/unit/test_rawSameSite.js
+++ b/netwerk/cookie/test/unit/test_rawSameSite.js
@@ -97,17 +97,17 @@ add_task(async _ => {
       Services.obs.addObserver(observer, "cookie-saved-on-disk");
     });
 
     cs.setCookieStringFromHttp(uri, null, null, test.cookie, null, channel);
 
     await promise;
 
     conn = storage.openDatabase(dbFile);
-    Assert.equal(conn.schemaVersion, 10);
+    Assert.equal(conn.schemaVersion, 11);
 
     let stmt = conn.createStatement(
       "SELECT sameSite, rawSameSite FROM moz_cookies"
     );
 
     let success = stmt.executeStep();
     Assert.ok(success);
 
new file mode 100644
index 0000000000000000000000000000000000000000..2301731f8ee47d447add82edb8d2cb1befaf6c9d
GIT binary patch
literal 131072
zc%1FhO>YxN7y#h46G0V;bB-M;2Z&@<R23&KHOWGw8mHKf1gB`@Y+;47o9wR3hlEH~
zsR#ZQKdCn)F073M4j_8;<$0u;osZp_cix@hgI-pr;m@KxN$T)RbRM<a(XSyyQMA4&
z7usL+TTR)BzHdGH>GpruUcZahUcHH4y?_0-c>n+a0000000000000000000000000
z0000000000{EuSo>gM)V>+c`4{5XAnQoKAoN~-j@I7za6)*Bba(=4slcLs5H6o<Y3
zZu~G@el6_x!_uqa#%yYLKGg|DnLW<(Ze5qz(X>vh?SFP!S2wq|TL0d!lcPzx^x;yo
zz1VKlz0->?FP<GYL5$*i@gV#$*t_2yJPN<Zk2>M=rePF69DTj>d^q&?n{v?Wh28jW
z_n<cl*ROX%o}8qMul`CV(~I_#qN*3o)1-c~Xr|9kv+~775S=hds=7NKr&W`W&!giq
zP3o-3M_IG<r_rn$rsHXOY2>%MKHbkJODksiow9gVrDfdYC(Y|m2fm2;W+zll$iu8o
zzcp7T&xYTb-r4UDM}uxNGtcZa&(5Z4lh%X&-h+cU+?ZwK<KaJ^`x`%T`&RqP=H6B-
zn%|o0Y|?CXm`v+p-alOWVTV62b=H~-_rvVgU7J<stGxFB00000000000000000000
z000000000000000006Lh8?)+s#X$f700000000000000000000000000000000000
v0KkfE%&PMh2LS*80000000000000000000000000000000000004w$ZH%#3W
--- a/netwerk/test/unit/head_cookies.js
+++ b/netwerk/test/unit/head_cookies.js
@@ -159,28 +159,36 @@ function Cookie(
   value,
   host,
   path,
   expiry,
   lastAccessed,
   creationTime,
   isSession,
   isSecure,
-  isHttpOnly
+  isHttpOnly,
+  inBrowserElement = false,
+  originAttributes = {},
+  sameSite = Ci.nsICookie.SAMESITE_NONE,
+  rawSameSite = Ci.nsICookie.SAMESITE_NONE
 ) {
   this.name = name;
   this.value = value;
   this.host = host;
   this.path = path;
   this.expiry = expiry;
   this.lastAccessed = lastAccessed;
   this.creationTime = creationTime;
   this.isSession = isSession;
   this.isSecure = isSecure;
   this.isHttpOnly = isHttpOnly;
+  this.inBrowserElement = inBrowserElement;
+  this.originAttributes = originAttributes;
+  this.sameSite = sameSite;
+  this.rawSameSite = rawSameSite;
 
   let strippedHost = host.charAt(0) == "." ? host.slice(1) : host;
 
   try {
     this.baseDomain = Services.etld.getBaseDomainFromHost(strippedHost);
   } catch (e) {
     if (
       e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
@@ -413,16 +421,165 @@ function CookieDatabaseConnection(file, 
       this.stmtUpdate = this.db.createStatement(
         "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
            WHERE name = :name AND host = :host AND path = :path"
       );
 
       break;
     }
 
+    case 10: {
+      if (!exists) {
+        this.db.executeSimpleSQL(
+          "CREATE TABLE moz_cookies (                  \
+            id INTEGER PRIMARY KEY,                    \
+            baseDomain TEXT,                           \
+            originAttributes TEXT NOT NULL DEFAULT '', \
+            name TEXT,                                 \
+            value TEXT,                                \
+            host TEXT,                                 \
+            path TEXT,                                 \
+            expiry INTEGER,                            \
+            lastAccessed INTEGER,                      \
+            creationTime INTEGER,                      \
+            isSecure INTEGER,                          \
+            isHttpOnly INTEGER,                        \
+            inBrowserElement INTEGER DEFAULT 0,        \
+            sameSite INTEGER DEFAULT 0,                \
+            rawSameSite INTEGER DEFAULT 0,             \
+            CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes))"
+        );
+
+        this.db.executeSimpleSQL(
+          "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"
+        );
+
+        this.db.executeSimpleSQL("PRAGMA journal_mode = WAL");
+        this.db.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16");
+      }
+
+      this.stmtInsert = this.db.createStatement(
+        "INSERT INTO moz_cookies (        \
+           name,                          \
+           value,                         \
+           host,                          \
+           baseDomain,                    \
+           path,                          \
+           expiry,                        \
+           lastAccessed,                  \
+           creationTime,                  \
+           isSecure,                      \
+           isHttpOnly,                    \
+           inBrowserElement,              \
+           originAttributes,              \
+           sameSite,                      \
+           rawSameSite                    \
+         ) VALUES (                       \
+           :name,                         \
+           :value,                        \
+           :host,                         \
+           :baseDomain,                   \
+           :path,                         \
+           :expiry,                       \
+           :lastAccessed,                 \
+           :creationTime,                 \
+           :isSecure,                     \
+           :isHttpOnly,                   \
+           :inBrowserElement,             \
+           :originAttributes,             \
+           :sameSite,                     \
+           :rawSameSite)"
+      );
+
+      this.stmtDelete = this.db.createStatement(
+        "DELETE FROM moz_cookies          \
+           WHERE name = :name AND host = :host AND path = :path AND \
+                 originAttributes = :originAttributes"
+      );
+
+      this.stmtUpdate = this.db.createStatement(
+        "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
+           WHERE name = :name AND host = :host AND path = :path AND \
+                 originAttributes = :originAttributes"
+      );
+
+      break;
+    }
+
+    case 11: {
+      if (!exists) {
+        this.db.executeSimpleSQL(
+          "CREATE TABLE moz_cookies (                  \
+            id INTEGER PRIMARY KEY,                    \
+            originAttributes TEXT NOT NULL DEFAULT '', \
+            name TEXT,                                 \
+            value TEXT,                                \
+            host TEXT,                                 \
+            path TEXT,                                 \
+            expiry INTEGER,                            \
+            lastAccessed INTEGER,                      \
+            creationTime INTEGER,                      \
+            isSecure INTEGER,                          \
+            isHttpOnly INTEGER,                        \
+            inBrowserElement INTEGER DEFAULT 0,        \
+            sameSite INTEGER DEFAULT 0,                \
+            rawSameSite INTEGER DEFAULT 0,             \
+            CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes))"
+        );
+
+        this.db.executeSimpleSQL("PRAGMA journal_mode = WAL");
+        this.db.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16");
+      }
+
+      this.stmtInsert = this.db.createStatement(
+        "INSERT INTO moz_cookies (        \
+           name,                          \
+           value,                         \
+           host,                          \
+           path,                          \
+           expiry,                        \
+           lastAccessed,                  \
+           creationTime,                  \
+           isSecure,                      \
+           isHttpOnly,                    \
+           inBrowserElement,              \
+           originAttributes,              \
+           sameSite,                      \
+           rawSameSite                    \
+         ) VALUES (                       \
+           :name,                         \
+           :value,                        \
+           :host,                         \
+           :path,                         \
+           :expiry,                       \
+           :lastAccessed,                 \
+           :creationTime,                 \
+           :isSecure,                     \
+           :isHttpOnly,                   \
+           :inBrowserElement,             \
+           :originAttributes,             \
+           :sameSite,                     \
+           :rawSameSite)"
+      );
+
+      this.stmtDelete = this.db.createStatement(
+        "DELETE FROM moz_cookies          \
+           WHERE name = :name AND host = :host AND path = :path AND \
+                 originAttributes = :originAttributes"
+      );
+
+      this.stmtUpdate = this.db.createStatement(
+        "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
+           WHERE name = :name AND host = :host AND path = :path AND \
+                 originAttributes = :originAttributes"
+      );
+
+      break;
+    }
+
     default:
       do_throw("unrecognized schemaVersion!");
   }
 }
 
 CookieDatabaseConnection.prototype = {
   insertCookie(cookie) {
     if (!(cookie instanceof Cookie)) {
@@ -474,16 +631,55 @@ CookieDatabaseConnection.prototype = {
         this.stmtInsert.bindByName("path", cookie.path);
         this.stmtInsert.bindByName("expiry", cookie.expiry);
         this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
         this.stmtInsert.bindByName("creationTime", cookie.creationTime);
         this.stmtInsert.bindByName("isSecure", cookie.isSecure);
         this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
         break;
 
+      case 10:
+        this.stmtInsert.bindByName("name", cookie.name);
+        this.stmtInsert.bindByName("value", cookie.value);
+        this.stmtInsert.bindByName("host", cookie.host);
+        this.stmtInsert.bindByName("baseDomain", cookie.baseDomain);
+        this.stmtInsert.bindByName("path", cookie.path);
+        this.stmtInsert.bindByName("expiry", cookie.expiry);
+        this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+        this.stmtInsert.bindByName("creationTime", cookie.creationTime);
+        this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+        this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+        this.stmtInsert.bindByName("inBrowserElement", cookie.inBrowserElement);
+        this.stmtInsert.bindByName(
+          "originAttributes",
+          ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+        );
+        this.stmtInsert.bindByName("sameSite", cookie.sameSite);
+        this.stmtInsert.bindByName("rawSameSite", cookie.rawSameSite);
+        break;
+
+      case 11:
+        this.stmtInsert.bindByName("name", cookie.name);
+        this.stmtInsert.bindByName("value", cookie.value);
+        this.stmtInsert.bindByName("host", cookie.host);
+        this.stmtInsert.bindByName("path", cookie.path);
+        this.stmtInsert.bindByName("expiry", cookie.expiry);
+        this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+        this.stmtInsert.bindByName("creationTime", cookie.creationTime);
+        this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+        this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+        this.stmtInsert.bindByName("inBrowserElement", cookie.inBrowserElement);
+        this.stmtInsert.bindByName(
+          "originAttributes",
+          ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+        );
+        this.stmtInsert.bindByName("sameSite", cookie.sameSite);
+        this.stmtInsert.bindByName("rawSameSite", cookie.rawSameSite);
+        break;
+
       default:
         do_throw("unrecognized schemaVersion!");
     }
 
     do_execute_stmt(this.stmtInsert);
   },
 
   deleteCookie(cookie) {
@@ -499,16 +695,27 @@ CookieDatabaseConnection.prototype = {
         break;
 
       case 4:
         this.stmtDelete.bindByName("name", cookie.name);
         this.stmtDelete.bindByName("host", cookie.host);
         this.stmtDelete.bindByName("path", cookie.path);
         break;
 
+      case 10:
+      case 11:
+        this.stmtDelete.bindByName("name", cookie.name);
+        this.stmtDelete.bindByName("host", cookie.host);
+        this.stmtDelete.bindByName("path", cookie.path);
+        this.stmtDelete.bindByName(
+          "originAttributes",
+          ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+        );
+        break;
+
       default:
         do_throw("unrecognized schemaVersion!");
     }
 
     do_execute_stmt(this.stmtDelete);
   },
 
   updateCookie(cookie) {
@@ -525,16 +732,38 @@ CookieDatabaseConnection.prototype = {
         this.stmtUpdate.bindByName("id", cookie.creationTime);
         this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed);
         break;
 
       case 4:
         this.stmtDelete.bindByName("name", cookie.name);
         this.stmtDelete.bindByName("host", cookie.host);
         this.stmtDelete.bindByName("path", cookie.path);
+        this.stmtUpdate.bindByName("name", cookie.name);
+        this.stmtUpdate.bindByName("host", cookie.host);
+        this.stmtUpdate.bindByName("path", cookie.path);
+        this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed);
+        break;
+
+      case 10:
+      case 11:
+        this.stmtDelete.bindByName("name", cookie.name);
+        this.stmtDelete.bindByName("host", cookie.host);
+        this.stmtDelete.bindByName("path", cookie.path);
+        this.stmtDelete.bindByName(
+          "originAttributes",
+          ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+        );
+        this.stmtUpdate.bindByName("name", cookie.name);
+        this.stmtUpdate.bindByName("host", cookie.host);
+        this.stmtUpdate.bindByName("path", cookie.path);
+        this.stmtUpdate.bindByName(
+          "originAttributes",
+          ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+        );
         this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed);
         break;
 
       default:
         do_throw("unrecognized schemaVersion!");
     }
 
     do_execute_stmt(this.stmtUpdate);
--- a/netwerk/test/unit/test_cookies_async_failure.js
+++ b/netwerk/test/unit/test_cookies_async_failure.js
@@ -90,35 +90,35 @@ function do_get_backup_file(profile) {
 
 function do_get_rebuild_backup_file(profile) {
   let file = profile.clone();
   file.append("cookies.sqlite.bak-rebuild");
   return file;
 }
 
 function do_corrupt_db(file) {
-  // Sanity check: the database size should be larger than 450k, since we've
+  // Sanity check: the database size should be larger than 320k, since we've
   // written about 460k of data. If it's not, let's make it obvious now.
   let size = file.fileSize;
-  Assert.ok(size > 450e3);
+  Assert.ok(size > 320e3);
 
   // Corrupt the database by writing bad data to the end of the file. We
   // assume that the important metadata -- table structure etc -- is stored
   // elsewhere, and that doing this will not cause synchronous failure when
   // initializing the database connection. This is totally empirical --
   // overwriting between 1k and 100k of live data seems to work. (Note that the
   // database file will be larger than the actual content requires, since the
   // cookie service uses a large growth increment. So we calculate the offset
   // based on the expected size of the content, not just the file size.)
   let ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
     Ci.nsIFileOutputStream
   );
   ostream.init(file, 2, -1, 0);
   let sstream = ostream.QueryInterface(Ci.nsISeekableStream);
-  let n = size - 450e3 + 20e3;
+  let n = size - 320e3 + 20e3;
   sstream.seek(Ci.nsISeekableStream.NS_SEEK_SET, size - n);
   for (let i = 0; i < n; ++i) {
     ostream.write("a", 1);
   }
   ostream.flush();
   ostream.close();
 
   Assert.equal(file.clone().fileSize, size);
@@ -130,39 +130,24 @@ function* run_test_1(generator) {
   let uri = NetUtil.newURI("http://foo.com/");
   Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Open a database connection now, before we load the profile and begin
-  // asynchronous write operations. In order to tell when the async delete
-  // statement has completed, we do something tricky: open a schema 2 connection
-  // and add a cookie with null baseDomain. We can then wait until we see it
-  // deleted in the new database.
-  let db2 = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
-  db2.db.executeSimpleSQL("INSERT INTO moz_cookies (baseDomain) VALUES (NULL)");
-  db2.close();
-  let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
-  Assert.equal(do_count_cookies_in_db(db.db), 2);
+  // asynchronous write operations.
+  let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 11);
+  Assert.equal(do_count_cookies_in_db(db.db), 1);
 
   // Load the profile, and wait for async read completion...
   do_load_profile(sub_generator);
   yield;
 
-  // ... and the DELETE statement to finish.
-  while (do_count_cookies_in_db(db.db) == 2) {
-    executeSoon(function() {
-      do_run_generator(sub_generator);
-    });
-    yield;
-  }
-  Assert.equal(do_count_cookies_in_db(db.db), 1);
-
   // Insert a row.
   db.insertCookie(cookie);
   db.close();
 
   // Attempt to insert a cookie with the same (name, host, path) triplet.
   Services.cookiemgr.add(
     cookie.host,
     cookie.path,
@@ -485,17 +470,17 @@ function* run_test_5(generator) {
   Assert.equal(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
   Assert.equal(do_count_cookies(), 0);
   Assert.ok(do_get_backup_file(profile).exists());
   Assert.equal(do_get_backup_file(profile).fileSize, size);
   Assert.ok(!do_get_rebuild_backup_file(profile).exists());
 
   // Open a database connection, and write a row that will trigger a constraint
   // violation.
-  let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
+  let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 11);
   db.insertCookie(cookie);
   Assert.equal(do_count_cookies_in_db(db.db, "bar.com"), 1);
   Assert.equal(do_count_cookies_in_db(db.db), 1);
   db.close();
 
   // Check that the original backup and the database itself are gone.
   Assert.ok(do_get_backup_file(profile).exists());
   Assert.equal(do_get_backup_file(profile).fileSize, size);
--- a/netwerk/test/unit/test_cookies_read.js
+++ b/netwerk/test/unit/test_cookies_read.js
@@ -29,17 +29,17 @@ function* do_run_test() {
   // Start the cookieservice, to force creation of a database.
   // Get the sessionCookies to join the initialization in cookie thread
   Services.cookiemgr.sessionCookies;
 
   // Open a database connection now, after synchronous initialization has
   // completed. We may not be able to open one later once asynchronous writing
   // begins.
   Assert.ok(do_get_cookie_file(profile).exists());
-  let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
+  let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 11);
 
   for (let i = 0; i < CMAX; ++i) {
     let uri = NetUtil.newURI("http://" + i + ".com/");
     Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
   }
 
   Assert.equal(do_count_cookies(), CMAX);
 
--- a/netwerk/test/unit/test_cookies_sync_failure.js
+++ b/netwerk/test/unit/test_cookies_sync_failure.js
@@ -13,17 +13,17 @@
 // 4) Migration fails. This will have different modes depending on the initial
 //    version:
 //    a) Schema 1: the 'lastAccessed' column already exists.
 //    b) Schema 2: the 'baseDomain' column already exists; or 'baseDomain'
 //       cannot be computed for a particular host.
 //    c) Schema 3: the 'creationTime' column already exists; or the
 //       'moz_uniqueid' index already exists.
 
-var COOKIE_DATABASE_SCHEMA_CURRENT = 10;
+var COOKIE_DATABASE_SCHEMA_CURRENT = 11;
 
 var test_generator = do_run_test();
 
 function run_test() {
   do_test_pending();
   do_run_generator(test_generator);
 }
 
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_upgrade_10-11.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function getDBVersion(dbfile) {
+  let dbConnection = Services.storage.openDatabase(dbfile);
+  let version = dbConnection.schemaVersion;
+  dbConnection.close();
+
+  return version;
+}
+
+function indexExists(dbfile, indexname) {
+  let dbConnection = Services.storage.openDatabase(dbfile);
+  let result = dbConnection.indexExists(indexname);
+  dbConnection.close();
+
+  return result;
+}
+
+add_task(async function() {
+  try {
+    let testfile = do_get_file("data/cookies_v10.sqlite");
+    let profileDir = do_get_profile();
+
+    // Cleanup from any previous tests or failures.
+    let destFile = profileDir.clone();
+    destFile.append("cookies.sqlite");
+    if (destFile.exists()) {
+      destFile.remove(false);
+    }
+
+    testfile.copyTo(profileDir, "cookies.sqlite");
+    Assert.equal(10, getDBVersion(destFile));
+
+    Assert.ok(destFile.exists());
+
+    // Check that the index exists
+    Assert.ok(indexExists(destFile, "moz_basedomain"));
+
+    // Do something that will cause the cookie service access and upgrade the
+    // database.
+    let cookies = Services.cookies.cookies;
+
+    // Pretend that we're about to shut down, to tell the cookie manager
+    // to clean up its connection with its database.
+    Services.obs.notifyObservers(null, "profile-before-change");
+
+    // check for upgraded schema.
+    Assert.equal(11, getDBVersion(destFile));
+
+    // Check that the index was deleted
+    Assert.ok(!indexExists(destFile, "moz_basedomain"));
+  } catch (e) {
+    throw new Error(`FAILED: ${e}`);
+  }
+});
copy from netwerk/test/unit/test_schema_3_migration.js
copy to netwerk/test/unit/test_schema_10_migration.js
--- a/netwerk/test/unit/test_schema_3_migration.js
+++ b/netwerk/test/unit/test_schema_10_migration.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Test cookie database migration from version 3 (prerelease Gecko 2.0) to the
-// current version, presently 4 (Gecko 2.0).
+// Test cookie database migration from version 10 (prerelease Gecko 2.0) to the
+// current version, presently 11.
 
 var test_generator = do_run_test();
 
 function run_test() {
   do_test_pending();
   test_generator.next();
 }
 
@@ -28,18 +28,21 @@ function* do_run_test() {
 
   // Close the profile.
   do_close_profile(test_generator);
   yield;
 
   // Remove the cookie file in order to create another database file.
   do_get_cookie_file(profile).remove(false);
 
-  // Create a schema 3 database.
-  let schema3db = new CookieDatabaseConnection(do_get_cookie_file(profile), 3);
+  // Create a schema 10 database.
+  let schema10db = new CookieDatabaseConnection(
+    do_get_cookie_file(profile),
+    10
+  );
 
   let now = Date.now() * 1000;
   let futureExpiry = Math.round(now / 1e6 + 1000);
   let pastExpiry = Math.round(now / 1e6 - 1000);
 
   // Populate it, with:
   // 1) Unexpired, unique cookies.
   for (let i = 0; i < 20; ++i) {
@@ -51,17 +54,17 @@ function* do_run_test() {
       futureExpiry,
       now,
       now + i,
       false,
       false,
       false
     );
 
-    schema3db.insertCookie(cookie);
+    schema10db.insertCookie(cookie);
   }
 
   // 2) Expired, unique cookies.
   for (let i = 20; i < 40; ++i) {
     let cookie = new Cookie(
       "oh" + i,
       "hai",
       "bar.com",
@@ -69,17 +72,17 @@ function* do_run_test() {
       pastExpiry,
       now,
       now + i,
       false,
       false,
       false
     );
 
-    schema3db.insertCookie(cookie);
+    schema10db.insertCookie(cookie);
   }
 
   // 3) Many copies of the same cookie, some of which have expired and
   // some of which have not.
   for (let i = 40; i < 45; ++i) {
     let cookie = new Cookie(
       "oh",
       "hai",
@@ -88,120 +91,90 @@ function* do_run_test() {
       futureExpiry + i,
       now,
       now + i,
       false,
       false,
       false
     );
 
-    schema3db.insertCookie(cookie);
+    try {
+      schema10db.insertCookie(cookie);
+    } catch (e) {}
   }
   for (let i = 45; i < 50; ++i) {
     let cookie = new Cookie(
       "oh",
       "hai",
       "baz.com",
       "/",
       pastExpiry - i,
       now,
       now + i,
       false,
       false,
       false
     );
 
-    schema3db.insertCookie(cookie);
+    try {
+      schema10db.insertCookie(cookie);
+    } catch (e) {}
   }
   for (let i = 50; i < 55; ++i) {
     let cookie = new Cookie(
       "oh",
       "hai",
       "baz.com",
       "/",
       futureExpiry - i,
       now,
       now + i,
       false,
       false,
       false
     );
 
-    schema3db.insertCookie(cookie);
+    try {
+      schema10db.insertCookie(cookie);
+    } catch (e) {}
   }
   for (let i = 55; i < 60; ++i) {
     let cookie = new Cookie(
       "oh",
       "hai",
       "baz.com",
       "/",
       pastExpiry + i,
       now,
       now + i,
       false,
       false,
       false
     );
 
-    schema3db.insertCookie(cookie);
+    try {
+      schema10db.insertCookie(cookie);
+    } catch (e) {}
   }
 
   // Close it.
-  schema3db.close();
-  schema3db = null;
+  schema10db.close();
+  schema10db = null;
 
   // Load the database, forcing migration to the current schema version. Then
   // test the expected set of cookies:
   do_load_profile();
 
   // 1) All unexpired, unique cookies exist.
   Assert.equal(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
 
   // 2) All expired, unique cookies exist.
   Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
 
   // 3) Only one cookie remains, and it's the one with the highest expiration
   // time.
   Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
   let cookies = Services.cookiemgr.getCookiesFromHost("baz.com", {});
   let cookie = cookies[0];
-  Assert.equal(cookie.expiry, futureExpiry + 44);
-
-  do_close_profile(test_generator);
-  yield;
-
-  // Open the database so we can execute some more schema 3 statements on it.
-  schema3db = new CookieDatabaseConnection(do_get_cookie_file(profile), 3);
-
-  // Populate it with more cookies.
-  for (let i = 60; i < 80; ++i) {
-    let cookie = new Cookie(
-      "oh" + i,
-      "hai",
-      "cat.com",
-      "/",
-      futureExpiry,
-      now,
-      now + i,
-      false,
-      false,
-      false
-    );
-
-    schema3db.insertCookie(cookie);
-  }
-
-  // Close it.
-  schema3db.close();
-  schema3db = null;
-
-  // Load the database. The cookies added immediately prior will have a NULL
-  // creationTime column.
-  do_load_profile();
-
-  // Test the expected set of cookies.
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("cat.com"), 20);
-  cookies = Services.cookiemgr.getCookiesFromHost("cat.com", {});
-  cookie = cookies[0];
-  Assert.equal(cookie.creationTime, 0);
+  Assert.equal(cookie.expiry, futureExpiry + 40);
 
   finish_test();
 }
--- a/netwerk/test/unit/test_schema_2_migration.js
+++ b/netwerk/test/unit/test_schema_2_migration.js
@@ -238,72 +238,65 @@ function* do_run_test() {
   file.copyTo(null, copy.leafName);
 
   // Load the database asynchronously, forcing a purge of the newly-added
   // cookies. (Their baseDomain column will be NULL.)
   do_load_profile(test_generator);
   yield;
 
   // Test the expected set of cookies.
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("foo.com"), 40);
   Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 0);
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("cat.com"), 0);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("cat.com"), 20);
 
   do_close_profile(test_generator);
   yield;
 
-  // Open the database and prove that they were deleted.
-  schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
-  Assert.equal(do_count_cookies_in_db(schema2db.db), 40);
-  Assert.equal(do_count_cookies_in_db(schema2db.db, "foo.com"), 20);
-  Assert.equal(do_count_cookies_in_db(schema2db.db, "bar.com"), 20);
-  schema2db.close();
-
   // Copy the database back.
   file.remove(false);
   copy.copyTo(null, file.leafName);
 
   // Load the database host-at-a-time.
   do_load_profile();
 
   // Test the expected set of cookies.
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("foo.com"), 40);
   Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 0);
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("cat.com"), 0);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("cat.com"), 20);
 
   do_close_profile(test_generator);
   yield;
 
   // Open the database and prove that they were deleted.
   schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
-  Assert.equal(do_count_cookies_in_db(schema2db.db), 40);
-  Assert.equal(do_count_cookies_in_db(schema2db.db, "foo.com"), 20);
+  Assert.equal(do_count_cookies_in_db(schema2db.db), 81);
+  Assert.equal(do_count_cookies_in_db(schema2db.db, "foo.com"), 40);
   Assert.equal(do_count_cookies_in_db(schema2db.db, "bar.com"), 20);
   schema2db.close();
 
   // Copy the database back.
   file.remove(false);
   copy.copyTo(null, file.leafName);
 
   // Load the database synchronously, in its entirety.
   do_load_profile();
-  Assert.equal(do_count_cookies(), 40);
+  Assert.equal(do_count_cookies(), 81);
 
   // Test the expected set of cookies.
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("foo.com"), 40);
   Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 0);
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("cat.com"), 0);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+  Assert.equal(Services.cookiemgr.countCookiesFromHost("cat.com"), 20);
 
   do_close_profile(test_generator);
   yield;
 
   // Open the database and prove that they were deleted.
   schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
-  Assert.equal(do_count_cookies_in_db(schema2db.db), 40);
-  Assert.equal(do_count_cookies_in_db(schema2db.db, "foo.com"), 20);
+  Assert.equal(do_count_cookies_in_db(schema2db.db), 81);
+  Assert.equal(do_count_cookies_in_db(schema2db.db, "foo.com"), 40);
   Assert.equal(do_count_cookies_in_db(schema2db.db, "bar.com"), 20);
   schema2db.close();
 
   finish_test();
 }
--- a/netwerk/test/unit/test_schema_3_migration.js
+++ b/netwerk/test/unit/test_schema_3_migration.js
@@ -160,48 +160,10 @@ function* do_run_test() {
 
   // 3) Only one cookie remains, and it's the one with the highest expiration
   // time.
   Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
   let cookies = Services.cookiemgr.getCookiesFromHost("baz.com", {});
   let cookie = cookies[0];
   Assert.equal(cookie.expiry, futureExpiry + 44);
 
-  do_close_profile(test_generator);
-  yield;
-
-  // Open the database so we can execute some more schema 3 statements on it.
-  schema3db = new CookieDatabaseConnection(do_get_cookie_file(profile), 3);
-
-  // Populate it with more cookies.
-  for (let i = 60; i < 80; ++i) {
-    let cookie = new Cookie(
-      "oh" + i,
-      "hai",
-      "cat.com",
-      "/",
-      futureExpiry,
-      now,
-      now + i,
-      false,
-      false,
-      false
-    );
-
-    schema3db.insertCookie(cookie);
-  }
-
-  // Close it.
-  schema3db.close();
-  schema3db = null;
-
-  // Load the database. The cookies added immediately prior will have a NULL
-  // creationTime column.
-  do_load_profile();
-
-  // Test the expected set of cookies.
-  Assert.equal(Services.cookiemgr.countCookiesFromHost("cat.com"), 20);
-  cookies = Services.cookiemgr.getCookiesFromHost("cat.com", {});
-  cookie = cookies[0];
-  Assert.equal(cookie.creationTime, 0);
-
   finish_test();
 }
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 head = head_channels.js head_cache.js head_cache2.js head_cookies.js
 support-files =
   http2-ca.pem
   client_cert_chooser.js
   client_cert_chooser.manifest
+  data/cookies_v10.sqlite
   data/image.png
   data/system_root.lnk
   data/test_psl.txt
   data/test_readline1.txt
   data/test_readline2.txt
   data/test_readline3.txt
   data/test_readline4.txt
   data/test_readline5.txt
@@ -191,16 +192,17 @@ skip-if = os == "android" && processor =
 skip-if = true # Bug 863738
 [test_cookies_privatebrowsing.js]
 [test_cookies_profile_close.js]
 [test_cookies_read.js]
 [test_cookies_sync_failure.js]
 [test_cookies_thirdparty.js]
 [test_cookies_thirdparty_nonsecure_session.js]
 [test_cookies_thirdparty_session.js]
+[test_cookies_upgrade_10-11.js]
 [test_dns_cancel.js]
 [test_data_protocol.js]
 [test_dns_service.js]
 [test_dns_offline.js]
 [test_dns_onion.js]
 [test_dns_originAttributes.js]
 [test_dns_localredirect.js]
 [test_dns_proxy_bypass.js]
@@ -292,16 +294,17 @@ skip-if = os == "win"
 [test_redirect_protocol_telemetry.js]
 [test_reentrancy.js]
 [test_reopen.js]
 [test_resumable_channel.js]
 [test_resumable_truncate.js]
 [test_safeoutputstream.js]
 [test_schema_2_migration.js]
 [test_schema_3_migration.js]
+[test_schema_10_migration.js]
 [test_simple.js]
 [test_sockettransportsvc_available.js]
 [test_socks.js]
 skip-if = os == 'mac' && (verify || debug || os_version == '10.14') #Bug 1140656
 # Bug 675039: test fails consistently on Android
 fail-if = os == "android"
 # http2 unit tests require us to have node available to run the spdy and http2 server
 [test_http2.js]