Bug 1286858 - Data storage and interface changes for SameSite cookies. r=valentin
authorAmy Chung <amchung@mozilla.com>
Mon, 25 Sep 2017 01:27:04 +0800
changeset 382655 23221e95f9ca4d61a436fc6b79f2757de4ab1c60
parent 382654 0249f8e3d48010fd10fc35be30dc368b7a07a546
child 382656 54c418ed72b1201b0f4f5978a8c3beecb0a847dc
push id32572
push userarchaeopteryx@coole-files.de
push dateMon, 25 Sep 2017 09:58:42 +0000
treeherdermozilla-central@5f3f19824efa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvalentin
bugs1286858
milestone58.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 1286858 - Data storage and interface changes for SameSite cookies. r=valentin
extensions/cookie/test/unit/test_cookies_sync_failure.js
netwerk/cookie/CookieServiceChild.cpp
netwerk/cookie/CookieServiceParent.cpp
netwerk/cookie/nsCookie.cpp
netwerk/cookie/nsCookie.h
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsCookieService.h
netwerk/cookie/nsICookie2.idl
netwerk/cookie/nsICookieManager2.idl
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/test/TestCookie.cpp
--- a/extensions/cookie/test/unit/test_cookies_sync_failure.js
+++ b/extensions/cookie/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 = 8;
+var COOKIE_DATABASE_SCHEMA_CURRENT = 9;
 
 var test_generator = do_run_test();
 
 function run_test() {
   do_test_pending();
   do_run_generator(test_generator);
 }
 
--- a/netwerk/cookie/CookieServiceChild.cpp
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -173,17 +173,18 @@ CookieServiceChild::RecvAddCookie(const 
                                              aCookie.host(),
                                              aCookie.path(),
                                              aCookie.expiry(),
                                              aCookie.lastAccessed(),
                                              aCookie.creationTime(),
                                              aCookie.isSession(),
                                              aCookie.isSecure(),
                                              false,
-                                             aAttrs);
+                                             aAttrs,
+                                             aCookie.sameSite());
   RecordDocumentCookie(cookie, aAttrs);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 CookieServiceChild::RecvRemoveBatchDeletedCookies(nsTArray<CookieStruct>&& aCookiesList,
                                                   nsTArray<OriginAttributes>&& aAttrsList)
 {
@@ -205,17 +206,18 @@ CookieServiceChild::RecvTrackCookiesLoad
                                                aCookiesList[i].host(),
                                                aCookiesList[i].path(),
                                                aCookiesList[i].expiry(),
                                                aCookiesList[i].lastAccessed(),
                                                aCookiesList[i].creationTime(),
                                                aCookiesList[i].isSession(),
                                                aCookiesList[i].isSecure(),
                                                false,
-                                               aAttrs);
+                                               aAttrs,
+                                               aCookiesList[i].sameSite());
     RecordDocumentCookie(cookie, aAttrs);
   }
 
   return IPC_OK();
 }
 
 void
 CookieServiceChild::PrefChanged(nsIPrefBranch *aPrefBranch)
@@ -360,17 +362,18 @@ CookieServiceChild::SetCookieInternal(ns
                      aCookieAttributes.host,
                      aCookieAttributes.path,
                      aCookieAttributes.expiryTime,
                      currentTimeInUsec,
                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
                      aCookieAttributes.isSession,
                      aCookieAttributes.isSecure,
                      aCookieAttributes.isHttpOnly,
-                     aAttrs);
+                     aAttrs,
+                     aCookieAttributes.sameSite);
 
   RecordDocumentCookie(cookie, aAttrs);
 }
 
 bool
 CookieServiceChild::RequireThirdPartyCheck()
 {
   return mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
--- a/netwerk/cookie/CookieServiceParent.cpp
+++ b/netwerk/cookie/CookieServiceParent.cpp
@@ -86,16 +86,17 @@ GetInfoFromCookie(nsCookie         *aCoo
   aCookieStruct.value() = aCookie->Value();
   aCookieStruct.host() = aCookie->Host();
   aCookieStruct.path() = aCookie->Path();
   aCookieStruct.expiry() = aCookie->Expiry();
   aCookieStruct.lastAccessed() = aCookie->LastAccessed();
   aCookieStruct.creationTime() = aCookie->CreationTime();
   aCookieStruct.isSession() = aCookie->IsSession();
   aCookieStruct.isSecure() = aCookie->IsSecure();
+  aCookieStruct.sameSite() = aCookie->SameSite();
 }
 
 void
 CookieServiceParent::RemoveBatchDeletedCookies(nsIArray *aCookieList) {
   uint32_t len = 0;
   aCookieList->GetLength(&len);
   OriginAttributes attrs;
   CookieStruct cookieStruct;
@@ -181,16 +182,17 @@ CookieServiceParent::SerialializeCookieL
     cookieStruct->value() = cookie->Value();
     cookieStruct->host() = cookie->Host();
     cookieStruct->path() = cookie->Path();
     cookieStruct->expiry() = cookie->Expiry();
     cookieStruct->lastAccessed() = cookie->LastAccessed();
     cookieStruct->creationTime() = cookie->CreationTime();
     cookieStruct->isSession() = cookie->IsSession();
     cookieStruct->isSecure() = cookie->IsSecure();
+    cookieStruct->sameSite() = cookie->SameSite();
   }
 }
 
 mozilla::ipc::IPCResult
 CookieServiceParent::RecvPrepareCookieList(const URIParams        &aHost,
                                            const bool             &aIsForeign,
                                            const OriginAttributes &aAttrs)
 {
--- a/netwerk/cookie/nsCookie.cpp
+++ b/netwerk/cookie/nsCookie.cpp
@@ -74,17 +74,18 @@ nsCookie::Create(const nsACString &aName
                  const nsACString &aHost,
                  const nsACString &aPath,
                  int64_t           aExpiry,
                  int64_t           aLastAccessed,
                  int64_t           aCreationTime,
                  bool              aIsSession,
                  bool              aIsSecure,
                  bool              aIsHttpOnly,
-                 const OriginAttributes& aOriginAttributes)
+                 const OriginAttributes& aOriginAttributes,
+                 int32_t           aSameSite)
 {
   // Ensure mValue contains a valid UTF-8 sequence. Otherwise XPConnect will
   // truncate the string after the first invalid octet.
   RefPtr<nsUTF8ConverterService> converter = new nsUTF8ConverterService();
   nsAutoCString aUTF8Value;
   converter->ConvertStringToUTF8(aValue, "UTF-8", false, true, 1, aUTF8Value);
 
   // find the required string buffer size, adding 4 for the terminating nulls
@@ -103,21 +104,26 @@ nsCookie::Create(const nsACString &aName
   StrBlockCopy(aName, aUTF8Value, aHost, aPath,
                name, value, host, path, end);
 
   // If the creationTime given to us is higher than the running maximum, update
   // our maximum.
   if (aCreationTime > gLastCreationTime)
     gLastCreationTime = aCreationTime;
 
+  // If aSameSite is not a sensible value, assume strict
+  if (aSameSite < 0 || aSameSite > nsICookie2::SAMESITE_STRICT) {
+    aSameSite = nsICookie2::SAMESITE_STRICT;
+  }
+
   // construct the cookie. placement new, oh yeah!
   return new (place) nsCookie(name, value, host, path, end,
                               aExpiry, aLastAccessed, aCreationTime,
                               aIsSession, aIsSecure, aIsHttpOnly,
-                              aOriginAttributes);
+                              aOriginAttributes, aSameSite);
 }
 
 size_t
 nsCookie::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
     // There is no need to measure the sizes of the individual string
     // members, since the strings are stored in-line with the nsCookie.
     return aMallocSizeOf(this);
@@ -146,16 +152,17 @@ NS_IMETHODIMP nsCookie::GetExpiry(int64_
 NS_IMETHODIMP nsCookie::GetIsSession(bool *aIsSession)   { *aIsSession = IsSession(); return NS_OK; }
 NS_IMETHODIMP nsCookie::GetIsDomain(bool *aIsDomain)     { *aIsDomain = IsDomain();   return NS_OK; }
 NS_IMETHODIMP nsCookie::GetIsSecure(bool *aIsSecure)     { *aIsSecure = IsSecure();   return NS_OK; }
 NS_IMETHODIMP nsCookie::GetIsHttpOnly(bool *aHttpOnly)   { *aHttpOnly = IsHttpOnly(); return NS_OK; }
 NS_IMETHODIMP nsCookie::GetStatus(nsCookieStatus *aStatus) { *aStatus = 0;              return NS_OK; }
 NS_IMETHODIMP nsCookie::GetPolicy(nsCookiePolicy *aPolicy) { *aPolicy = 0;              return NS_OK; }
 NS_IMETHODIMP nsCookie::GetCreationTime(int64_t *aCreation){ *aCreation = CreationTime(); return NS_OK; }
 NS_IMETHODIMP nsCookie::GetLastAccessed(int64_t *aTime)    { *aTime = LastAccessed();   return NS_OK; }
+NS_IMETHODIMP nsCookie::GetSameSite(int32_t *aSameSite)    { *aSameSite = SameSite();   return NS_OK; }
 
 NS_IMETHODIMP
 nsCookie::GetOriginAttributes(JSContext *aCx, JS::MutableHandle<JS::Value> aVal)
 {
   if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
--- a/netwerk/cookie/nsCookie.h
+++ b/netwerk/cookie/nsCookie.h
@@ -43,29 +43,31 @@ class nsCookie : public nsICookie2
              const char     *aPath,
              const char     *aEnd,
              int64_t         aExpiry,
              int64_t         aLastAccessed,
              int64_t         aCreationTime,
              bool            aIsSession,
              bool            aIsSecure,
              bool            aIsHttpOnly,
-             const OriginAttributes& aOriginAttributes)
+             const OriginAttributes& aOriginAttributes,
+             int32_t         aSameSite)
      : mName(aName)
      , mValue(aValue)
      , mHost(aHost)
      , mPath(aPath)
      , mEnd(aEnd)
      , mExpiry(aExpiry)
      , mLastAccessed(aLastAccessed)
      , mCreationTime(aCreationTime)
      , mIsSession(aIsSession)
      , mIsSecure(aIsSecure)
      , mIsHttpOnly(aIsHttpOnly)
      , mOriginAttributes(aOriginAttributes)
+     , mSameSite(aSameSite)
     {
     }
 
     static int CookieStaleThreshold()
     {
       static bool initialized = false;
       static int value = 60;
       if (!initialized) {
@@ -87,17 +89,18 @@ class nsCookie : public nsICookie2
                              const nsACString &aHost,
                              const nsACString &aPath,
                              int64_t           aExpiry,
                              int64_t           aLastAccessed,
                              int64_t           aCreationTime,
                              bool              aIsSession,
                              bool              aIsSecure,
                              bool              aIsHttpOnly,
-                             const OriginAttributes& aOriginAttributes);
+                             const OriginAttributes& aOriginAttributes,
+                             int32_t           aSameSite);
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
     // 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); }
@@ -105,16 +108,17 @@ class nsCookie : public nsICookie2
     inline int64_t Expiry()                 const { return mExpiry; }        // in seconds
     inline int64_t LastAccessed()           const { return mLastAccessed; }  // in microseconds
     inline int64_t CreationTime()           const { return mCreationTime; }  // in microseconds
     inline bool IsSession()               const { return mIsSession; }
     inline bool IsDomain()                const { return *mHost == '.'; }
     inline bool IsSecure()                const { return mIsSecure; }
     inline bool IsHttpOnly()              const { return mIsHttpOnly; }
     inline const OriginAttributes& OriginAttributesRef() const { return mOriginAttributes; }
+    inline int32_t SameSite()               const { return mSameSite; }
 
     // setters
     inline void SetExpiry(int64_t aExpiry)        { mExpiry = aExpiry; }
     inline void SetLastAccessed(int64_t aTime)    { mLastAccessed = aTime; }
     inline void SetIsSession(bool aIsSession)     { mIsSession = aIsSession; }
     // Set the creation time manually, overriding the monotonicity checks in
     // Create(). Use with caution!
     inline void SetCreationTime(int64_t aTime)    { mCreationTime = aTime; }
@@ -139,16 +143,17 @@ class nsCookie : public nsICookie2
     const char  *mEnd;
     int64_t      mExpiry;
     int64_t      mLastAccessed;
     int64_t      mCreationTime;
     bool mIsSession;
     bool mIsSecure;
     bool mIsHttpOnly;
     mozilla::OriginAttributes mOriginAttributes;
+    int32_t     mSameSite;
 };
 
 // Comparator class for sorting cookies before sending to a server.
 class CompareCookiesForSending
 {
 public:
   bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
   {
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -75,31 +75,32 @@ using namespace mozilla::net;
 
 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 8
+#define COOKIES_SCHEMA_VERSION 9
 
 // parameter indexes; see EnsureReadDomain, EnsureReadComplete and
 // ReadCookieDBListener::HandleResult
 #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 TOPIC_CLEAR_ORIGIN_DATA "clear-origin-attributes-data"
 
 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"
 
@@ -1386,16 +1387,26 @@ nsCookieService::TryInitDB(bool aRecreat
 
         // Recreate our index.
         rv = CreateIndex();
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         COOKIE_LOGSTRING(LogLevel::Debug,
           ("Upgraded database to schema version 8"));
       }
+      MOZ_FALLTHROUGH;
+
+    case 8:
+      {
+        // Add the sameSite column to the table.
+        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(
+          NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD sameSite INTEGER"));
+        COOKIE_LOGSTRING(LogLevel::Debug,
+          ("Upgraded database to schema version 9"));
+      }
 
       // No more upgrades. Update the schema version.
       rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
       NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       MOZ_FALLTHROUGH;
 
     case COOKIES_SCHEMA_VERSION:
       break;
@@ -1433,17 +1444,18 @@ nsCookieService::TryInitDB(bool aRecreat
             "name, "
             "value, "
             "host, "
             "path, "
             "expiry, "
             "lastAccessed, "
             "creationTime, "
             "isSecure, "
-            "isHttpOnly "
+            "isHttpOnly, "
+            "sameSite "
           "FROM moz_cookies"), getter_AddRefs(stmt));
         if (NS_SUCCEEDED(rv))
           break;
 
         // our columns aren't there - drop the table!
         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "DROP TABLE moz_cookies"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
@@ -1474,29 +1486,31 @@ nsCookieService::TryInitDB(bool aRecreat
       "name, "
       "value, "
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
-      "isHttpOnly"
+      "isHttpOnly, "
+      "sameSite "
     ") VALUES ("
       ":baseDomain, "
       ":originAttributes, "
       ":name, "
       ":value, "
       ":host, "
       ":path, "
       ":expiry, "
       ":lastAccessed, "
       ":creationTime, "
       ":isSecure, "
-      ":isHttpOnly"
+      ":isHttpOnly, "
+      ":sameSite"
     ")"),
     getter_AddRefs(mDefaultDBState->stmtInsert));
   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "DELETE FROM moz_cookies "
     "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
     getter_AddRefs(mDefaultDBState->stmtDelete));
@@ -1553,16 +1567,17 @@ nsCookieService::CreateTableWorker(const
       "host TEXT, "
       "path TEXT, "
       "expiry INTEGER, "
       "lastAccessed INTEGER, "
       "creationTime INTEGER, "
       "isSecure INTEGER, "
       "isHttpOnly INTEGER, "
       "inBrowserElement INTEGER DEFAULT 0, "
+      "sameSite INTEGER DEFAULT 0, "
       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
     ")");
   return mDefaultDBState->dbConn->ExecuteSimpleSQL(command);
 }
 
 // Sets the schema version and creates the moz_cookies table.
 nsresult
 nsCookieService::CreateTable()
@@ -2471,44 +2486,46 @@ nsCookieService::Add(const nsACString &a
                      const nsACString &aPath,
                      const nsACString &aName,
                      const nsACString &aValue,
                      bool              aIsSecure,
                      bool              aIsHttpOnly,
                      bool              aIsSession,
                      int64_t           aExpiry,
                      JS::HandleValue   aOriginAttributes,
+                     int32_t           aSameSite,
                      JSContext*        aCx,
                      uint8_t           aArgc)
 {
   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
 
   OriginAttributes attrs;
   nsresult rv = InitializeOriginAttributes(&attrs,
                                            aOriginAttributes,
                                            aCx,
                                            aArgc,
                                            u"nsICookieManager2.add()",
                                            u"2");
   NS_ENSURE_SUCCESS(rv, rv);
 
   return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
-                   aIsSession, aExpiry, &attrs);
+                   aIsSession, aExpiry, &attrs, aSameSite);
 }
 
 NS_IMETHODIMP_(nsresult)
 nsCookieService::AddNative(const nsACString &aHost,
                            const nsACString &aPath,
                            const nsACString &aName,
                            const nsACString &aValue,
                            bool              aIsSecure,
                            bool              aIsHttpOnly,
                            bool              aIsSession,
                            int64_t           aExpiry,
-                           OriginAttributes* aOriginAttributes)
+                           OriginAttributes* aOriginAttributes,
+                           int32_t           aSameSite)
 {
   if (NS_WARN_IF(!aOriginAttributes)) {
     return NS_ERROR_FAILURE;
   }
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
@@ -2534,17 +2551,18 @@ nsCookieService::AddNative(const nsACStr
   RefPtr<nsCookie> cookie =
     nsCookie::Create(aName, aValue, host, aPath,
                      aExpiry,
                      currentTimeInUsec,
                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
                      aIsSession,
                      aIsSecure,
                      aIsHttpOnly,
-                     key.mOriginAttributes);
+                     key.mOriginAttributes,
+                     aSameSite);
   if (!cookie) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   AddInternal(key, cookie, currentTimeInUsec, nullptr, nullptr, true);
   return NS_OK;
 }
 
@@ -2666,17 +2684,18 @@ nsCookieService::Read()
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
       "isHttpOnly, "
       "baseDomain, "
-      "originAttributes "
+      "originAttributes, "
+      "sameSite "
     "FROM moz_cookies "
     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   // 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.)
@@ -2727,26 +2746,28 @@ nsCookieService::GetCookieFromRow(T &aRo
   rv = aRow->GetUTF8String(IDX_PATH, path);
   NS_ASSERT_SUCCESS(rv);
 
   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
+  int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE);
 
   // Create a new nsCookie and assign the data.
   return nsCookie::Create(name, value, host, path,
                           expiry,
                           lastAccessed,
                           creationTime,
                           false,
                           isSecure,
                           isHttpOnly,
-                          aOriginAttributes);
+                          aOriginAttributes,
+                          sameSite);
 }
 
 void
 nsCookieService::AsyncReadComplete()
 {
   // We may be in the private browsing DB state, with a pending read on the
   // default DB state. (This would occur if we started up in private browsing
   // mode.) As long as we do all our operations on the default state, we're OK.
@@ -2845,17 +2866,20 @@ nsCookieService::EnsureReadDomain(const 
         "name, "
         "value, "
         "host, "
         "path, "
         "expiry, "
         "lastAccessed, "
         "creationTime, "
         "isSecure, "
-        "isHttpOnly "
+        "isHttpOnly, "
+        "baseDomain, "
+        "originAttributes, "
+        "sameSite "
       "FROM moz_cookies "
       "WHERE baseDomain = :baseDomain "
       "  AND originAttributes = :originAttributes"),
       getter_AddRefs(mDefaultDBState->stmtReadDomain));
 
     if (NS_FAILED(rv)) {
       // Recreate the database.
       COOKIE_LOGSTRING(LogLevel::Debug,
@@ -2942,17 +2966,18 @@ nsCookieService::EnsureReadComplete()
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
       "isHttpOnly, "
       "baseDomain, "
-      "originAttributes  "
+      "originAttributes, "
+      "sameSite "
     "FROM moz_cookies "
     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
 
   if (NS_FAILED(rv)) {
     // Recreate the database.
     COOKIE_LOGSTRING(LogLevel::Debug,
       ("EnsureReadComplete(): corruption detected when creating statement "
        "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
@@ -3146,17 +3171,18 @@ nsCookieService::ImportCookies(nsIFile *
                        host,
                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
                        expires,
                        lastAccessedCounter,
                        nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
                        false,
                        Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
                        isHttpOnly,
-                       key.mOriginAttributes);
+                       key.mOriginAttributes,
+                       nsICookie2::SAMESITE_UNSET);
     if (!newCookie) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     // trick: preserve the most-recently-used cookie ordering,
     // by successively decrementing the lastAccessed time
     lastAccessedCounter--;
 
@@ -3596,17 +3622,18 @@ nsCookieService::SetCookieInternal(nsIUR
                      cookieAttributes.host,
                      cookieAttributes.path,
                      cookieAttributes.expiryTime,
                      currentTimeInUsec,
                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
                      cookieAttributes.isSession,
                      cookieAttributes.isSecure,
                      cookieAttributes.isHttpOnly,
-                     aKey.mOriginAttributes);
+                     aKey.mOriginAttributes,
+                     cookieAttributes.sameSite);
   if (!cookie)
     return newCookie;
 
   // check permissions from site permission list, or ask the user,
   // to determine if we can set the cookie
   if (mPermissionService) {
     bool permission;
     mPermissionService->CanSetCookie(aHostURI,
@@ -3988,24 +4015,27 @@ nsCookieService::ParseAttributes(nsDepen
                                  nsCookieAttributes &aCookieAttributes)
 {
   static const char kPath[]    = "path";
   static const char kDomain[]  = "domain";
   static const char kExpires[] = "expires";
   static const char kMaxage[]  = "max-age";
   static const char kSecure[]  = "secure";
   static const char kHttpOnly[]  = "httponly";
+  static const char kSameSite[]       = "samesite";
+  static const char kSameSiteLax[]    = "lax";
 
   nsACString::const_char_iterator tempBegin, tempEnd;
   nsACString::const_char_iterator cookieStart, cookieEnd;
   aCookieHeader.BeginReading(cookieStart);
   aCookieHeader.EndReading(cookieEnd);
 
   aCookieAttributes.isSecure = false;
   aCookieAttributes.isHttpOnly = false;
+  aCookieAttributes.sameSite = nsICookie2::SAMESITE_UNSET;
 
   nsDependentCSubstring tokenString(cookieStart, cookieStart);
   nsDependentCSubstring tokenValue (cookieStart, cookieStart);
   bool newCookie, equalsFound;
 
   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
   // if we find multiple cookies, return for processing
   // note: if there's no '=', we assume token is <VALUE>. this is required by
@@ -4044,16 +4074,24 @@ nsCookieService::ParseAttributes(nsDepen
     // ignore any tokenValue for isSecure; just set the boolean
     else if (tokenString.LowerCaseEqualsLiteral(kSecure))
       aCookieAttributes.isSecure = true;
 
     // ignore any tokenValue for isHttpOnly (see bug 178993);
     // just set the boolean
     else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
       aCookieAttributes.isHttpOnly = true;
+
+    else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) {
+      if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) {
+        aCookieAttributes.sameSite = nsICookie2::SAMESITE_LAX;
+      } else {
+        aCookieAttributes.sameSite = nsICookie2::SAMESITE_STRICT;
+      }
+    }
   }
 
   // rebind aCookieHeader, in case we need to process another cookie
   aCookieHeader.Rebind(cookieStart, cookieEnd);
   return newCookie;
 }
 
 /******************************************************************************
@@ -5208,16 +5246,20 @@ bindCookieParameters(mozIStorageBindingP
   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
                                aCookie->IsSecure());
   NS_ASSERT_SUCCESS(rv);
 
   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
                                aCookie->IsHttpOnly());
   NS_ASSERT_SUCCESS(rv);
 
+  rv = params->BindInt32ByName(NS_LITERAL_CSTRING("sameSite"),
+                               aCookie->SameSite());
+  NS_ASSERT_SUCCESS(rv);
+
   // Bind the params to the array.
   rv = aParamsArray->AddParams(params);
   NS_ASSERT_SUCCESS(rv);
 }
 
 void
 nsCookieService::UpdateCookieOldestTime(DBState* aDBState,
                                         nsCookie* aCookie)
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -190,16 +190,17 @@ struct nsCookieAttributes
   nsAutoCString host;
   nsAutoCString path;
   nsAutoCString expires;
   nsAutoCString maxage;
   int64_t expiryTime;
   bool isSession;
   bool isSecure;
   bool isHttpOnly;
+  int8_t sameSite;
 };
 
 class nsCookieService final : public nsICookieService
                             , public nsICookieManager2
                             , public nsIObserver
                             , public nsSupportsWeakReference
                             , public nsIMemoryReporter
 {
--- a/netwerk/cookie/nsICookie2.idl
+++ b/netwerk/cookie/nsICookie2.idl
@@ -6,20 +6,23 @@
 #include "nsICookie.idl"
 
 /** 
  * Main cookie object interface for use by consumers:
  * extends nsICookie, a frozen interface for external
  * access of cookie objects
  */
 
-[scriptable, uuid(05c420e5-03d0-4c7b-a605-df7ebe5ca326)]
+[scriptable, uuid(be205dae-4f4c-11e6-80ba-ea5cd310c1a8)]
 
 interface nsICookie2 : nsICookie
 {
+    const uint32_t SAMESITE_UNSET  = 0;
+    const uint32_t SAMESITE_LAX    = 1;
+    const uint32_t SAMESITE_STRICT = 2;
 
     /**
      * the host (possibly fully qualified) of the cookie,
      * without a leading dot to represent if it is a
      * domain cookie.
      */
     readonly attribute AUTF8String rawHost;
 
@@ -55,9 +58,20 @@ interface nsICookie2 : nsICookie
      * the last time the cookie was accessed (i.e. created,
      * modified, or read by the server), in microseconds
      * since midnight (00:00:00), January 1, 1970 UTC.
      *
      * note that this time may be approximate.
      */
     readonly attribute int64_t lastAccessed;
 
+    /**
+     * the sameSite attribute; this controls the cookie behavior for cross-site
+     * requests as per
+     * https://tools.ietf.org/html/draft-west-first-party-cookies-07
+     *
+     * This should be one of:
+     * - SAMESITE_UNSET - the SameSite attribute is not present
+     * - SAMESITE_LAX - the SameSite attribute is present, but not strict
+     * - SAMESITE_STRICT - the SameSite attribute is present and strict
+     */
+    readonly attribute int32_t sameSite;
 };
--- a/netwerk/cookie/nsICookieManager2.idl
+++ b/netwerk/cookie/nsICookieManager2.idl
@@ -41,40 +41,46 @@ interface nsICookieManager2 : nsICookieM
    *        modified by, an http connection.
    * @param aIsSession
    *        true if the cookie should exist for the current session only.
    *        see aExpiry.
    * @param aExpiry
    *        expiration date, in seconds since midnight (00:00:00), January 1,
    *        1970 UTC. note that expiry time will also be honored for session cookies;
    *        in this way, the more restrictive of the two will take effect.
-   * @param aOriginAttributes The originAttributes of this cookie. This
-   *                          attribute is optional to avoid breaking add-ons.
+   * @param aOriginAttributes
+   *        the originAttributes of this cookie. This attribute is optional to
+   *        avoid breaking add-ons.
+   * @param aSameSite
+   *        the SameSite attribute. This attribute is optional to avoid breaking
+   *        addons
    */
   [implicit_jscontext, optional_argc]
   void add(in AUTF8String aHost,
            in AUTF8String aPath,
            in ACString    aName,
            in ACString    aValue,
            in boolean     aIsSecure,
            in boolean     aIsHttpOnly,
            in boolean     aIsSession,
            in int64_t     aExpiry,
-           [optional] in jsval aOriginAttributes);
+           [optional] in jsval aOriginAttributes,
+           [optional] in int32_t aSameSite);
 
   [notxpcom]
   nsresult addNative(in AUTF8String aHost,
                      in AUTF8String aPath,
                      in ACString    aName,
                      in ACString    aValue,
                      in boolean     aIsSecure,
                      in boolean     aIsHttpOnly,
                      in boolean     aIsSession,
                      in int64_t     aExpiry,
-                     in OriginAttributesPtr aOriginAttributes);
+                     in OriginAttributesPtr aOriginAttributes,
+                     in int32_t aSameSite);
 
   /**
    * Find whether a given cookie already exists.
    *
    * @param aCookie
    *        the cookie to look for
    * @param aOriginAttributes
    *        nsICookie2 contains an originAttributes but if nsICookie2 is
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -207,12 +207,13 @@ struct CookieStruct
   nsCString value;
   nsCString host;
   nsCString path;
   int64_t   expiry;
   int64_t   lastAccessed;
   int64_t   creationTime;
   bool      isSession;
   bool      isSecure;
+  int8_t    sameSite;
 };
 
 } // namespace ipc
 } // namespace mozilla
--- a/netwerk/test/TestCookie.cpp
+++ b/netwerk/test/TestCookie.cpp
@@ -629,35 +629,38 @@ TEST(TestCookie,TestCookieMain)
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("cookiemgr.test"), // domain
                                                    NS_LITERAL_CSTRING("/foo"),           // path
                                                    NS_LITERAL_CSTRING("test1"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    false,                             // is httponly
                                                    true,                              // is session
                                                    INT64_MAX,                            // expiry time
-                                                   &attrs)));                         // originAttributes
+                                                   &attrs,                            // originAttributes
+                                                   nsICookie2::SAMESITE_UNSET)));
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("cookiemgr.test"), // domain
                                                    NS_LITERAL_CSTRING("/foo"),           // path
                                                    NS_LITERAL_CSTRING("test2"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    true,                              // is httponly
                                                    true,                              // is session
                                                    PR_Now() / PR_USEC_PER_SEC + 2,       // expiry time
-                                                   &attrs)));                         // originAttributes
+                                                   &attrs,                            // originAttributes
+                                                   nsICookie2::SAMESITE_UNSET)));
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"),     // domain
                                                    NS_LITERAL_CSTRING("/rabbit"),        // path
                                                    NS_LITERAL_CSTRING("test3"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    false,                             // is httponly
                                                    true,                              // is session
                                                    INT64_MAX,                            // expiry time
-                                                   &attrs)));                         // originAttributes
+                                                   &attrs,                            // originAttributes
+                                                   nsICookie2::SAMESITE_UNSET)));
     // confirm using enumerator
     nsCOMPtr<nsISimpleEnumerator> enumerator;
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator))));
     int32_t i = 0;
     bool more;
     nsCOMPtr<nsICookie2> expiredCookie, newDomainCookie;
     while (NS_SUCCEEDED(enumerator->HasMoreElements(&more)) && more) {
         nsCOMPtr<nsISupports> cookie;
@@ -701,17 +704,18 @@ TEST(TestCookie,TestCookieMain)
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"),     // domain
                                                    NS_LITERAL_CSTRING("/rabbit"),        // path
                                                    NS_LITERAL_CSTRING("test3"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    false,                             // is httponly
                                                    true,                              // is session
                                                    INT64_MIN,                            // expiry time
-                                                   &attrs)));                         // originAttributes
+                                                   &attrs,                            // originAttributes
+                                                   nsICookie2::SAMESITE_UNSET)));
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)));
     EXPECT_FALSE(found);
     // sleep four seconds, to make sure the second cookie has expired
     PR_Sleep(4 * PR_TicksPerSecond());
     // check that both CountCookiesFromHost() and CookieExistsNative() count the
     // expired cookie
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CountCookiesFromHost(NS_LITERAL_CSTRING("cookiemgr.test"), &hostCookies)));
     EXPECT_EQ(hostCookies, 2u);
@@ -759,15 +763,60 @@ TEST(TestCookie,TestCookieMain)
         name += NS_LITERAL_CSTRING("; secure");
         SetACookie(cookieService, "https://creation.ordering.tests/", nullptr, name.get(), nullptr);
       } else {
         // non-security cookies will be removed beside the latest cookie that be created.
         SetACookie(cookieService, "http://creation.ordering.tests/", nullptr, name.get(), nullptr);
       }
     }
     GetACookie(cookieService, "http://creation.ordering.tests/", nullptr, getter_Copies(cookie));
+
     EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
 
+
+    // *** SameSite attribute - parsing and cookie storage tests
+    // Clear the cookies
+    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->RemoveAll()));
+
+    // Set cookies with various incantations of the samesite attribute:
+    // No same site attribute present
+    SetACookie(cookieService, "http://samesite.test", nullptr, "unset=yes", nullptr);
+    // samesite attribute present but with no value
+    SetACookie(cookieService, "http://samesite.test", nullptr, "unspecified=yes; samesite", nullptr);
+    // samesite=strict
+    SetACookie(cookieService, "http://samesite.test", nullptr, "strict=yes; samesite=strict", nullptr);
+    // samesite=lax
+    SetACookie(cookieService, "http://samesite.test", nullptr, "lax=yes; samesite=lax", nullptr);
+
+    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator))));
+    i = 0;
+
+    // check the cookies for the required samesite value
+    while (NS_SUCCEEDED(enumerator->HasMoreElements(&more)) && more) {
+      nsCOMPtr<nsISupports> cookie;
+      if (NS_FAILED(enumerator->GetNext(getter_AddRefs(cookie)))) break;
+      ++i;
+
+      // keep tabs on the second and third cookies, so we can check them later
+      nsCOMPtr<nsICookie2> cookie2(do_QueryInterface(cookie));
+      if (!cookie2) break;
+      nsAutoCString name;
+      cookie2->GetName(name);
+      int32_t sameSiteAttr;
+      cookie2->GetSameSite(&sameSiteAttr);
+      if (name.EqualsLiteral("unset")) {
+        EXPECT_TRUE(sameSiteAttr == nsICookie2::SAMESITE_UNSET);
+      } else if (name.EqualsLiteral("unspecified")) {
+        EXPECT_TRUE(sameSiteAttr == nsICookie2::SAMESITE_STRICT);
+      } else if (name.EqualsLiteral("strict")) {
+        EXPECT_TRUE(sameSiteAttr == nsICookie2::SAMESITE_STRICT);
+      } else if (name.EqualsLiteral("lax")) {
+        EXPECT_TRUE(sameSiteAttr == nsICookie2::SAMESITE_LAX);
+      }
+    }
+
+    EXPECT_TRUE(i == 4);
+
     // XXX the following are placeholders: add these tests please!
     // *** "noncompliant cookie" tests
     // *** IP address tests
     // *** speed tests
 }