Add a SetCanonicalCookie method for CookieMonster.

This includes some refactoring of creation time defaulting, as well
as a histogram revision bump because the information about whether a
cookie is being set based on a null URL is no longer available at
the point of histogram creation.

BUG=721395, 723734
[email protected]

Review-Url: https://codereview.chromium.org/2882063002
Cr-Commit-Position: refs/heads/master@{#481227}
diff --git a/android_webview/browser/net/aw_cookie_store_wrapper.cc b/android_webview/browser/net/aw_cookie_store_wrapper.cc
index 70f7b4c..17423fd 100644
--- a/android_webview/browser/net/aw_cookie_store_wrapper.cc
+++ b/android_webview/browser/net/aw_cookie_store_wrapper.cc
@@ -16,8 +16,8 @@
 namespace {
 
 // Posts |task| to the thread that the global CookieStore lives on.
-void PostTaskToCookieStoreTaskRunner(const base::Closure& task) {
-  GetCookieStoreTaskRunner()->PostTask(FROM_HERE, task);
+void PostTaskToCookieStoreTaskRunner(base::OnceClosure task) {
+  GetCookieStoreTaskRunner()->PostTask(FROM_HERE, std::move(task));
 }
 
 // Wraps a subscription to cookie change notifications for the global
@@ -128,6 +128,15 @@
       last_access_time, secure, http_only, same_site, priority, callback);
 }
 
+void SetCanonicalCookieAsyncOnCookieThread(
+    std::unique_ptr<net::CanonicalCookie> cookie,
+    bool secure_source,
+    bool modify_http_only,
+    const net::CookieStore::SetCookiesCallback& callback) {
+  GetCookieStore()->SetCanonicalCookieAsync(std::move(cookie), secure_source,
+                                            modify_http_only, callback);
+}
+
 void GetCookiesWithOptionsAsyncOnCookieThread(
     const GURL& url,
     const net::CookieOptions& options,
@@ -230,6 +239,17 @@
                  CreateWrappedCallback<bool>(callback)));
 }
 
+void AwCookieStoreWrapper::SetCanonicalCookieAsync(
+    std::unique_ptr<net::CanonicalCookie> cookie,
+    bool secure_source,
+    bool modify_http_only,
+    const SetCookiesCallback& callback) {
+  DCHECK(client_task_runner_->RunsTasksOnCurrentThread());
+  PostTaskToCookieStoreTaskRunner(base::BindOnce(
+      &SetCanonicalCookieAsyncOnCookieThread, std::move(cookie), secure_source,
+      modify_http_only, CreateWrappedCallback<bool>(callback)));
+}
+
 void AwCookieStoreWrapper::GetCookiesWithOptionsAsync(
     const GURL& url,
     const net::CookieOptions& options,
diff --git a/android_webview/browser/net/aw_cookie_store_wrapper.h b/android_webview/browser/net/aw_cookie_store_wrapper.h
index cf8bfbd..860f81a4a 100644
--- a/android_webview/browser/net/aw_cookie_store_wrapper.h
+++ b/android_webview/browser/net/aw_cookie_store_wrapper.h
@@ -58,6 +58,10 @@
                                  net::CookieSameSite same_site,
                                  net::CookiePriority priority,
                                  const SetCookiesCallback& callback) override;
+  void SetCanonicalCookieAsync(std::unique_ptr<net::CanonicalCookie> cookie,
+                               bool secure_source,
+                               bool modify_http_only,
+                               const SetCookiesCallback& callback) override;
   void GetCookiesWithOptionsAsync(const GURL& url,
                                   const net::CookieOptions& options,
                                   const GetCookiesCallback& callback) override;
diff --git a/chrome/browser/extensions/api/cookies/cookies_unittest.cc b/chrome/browser/extensions/api/cookies/cookies_unittest.cc
index a236be2..6aeccbf 100644
--- a/chrome/browser/extensions/api/cookies/cookies_unittest.cc
+++ b/chrome/browser/extensions/api/cookies/cookies_unittest.cc
@@ -188,7 +188,7 @@
   std::unique_ptr<net::CanonicalCookie> canonical_cookie(
       net::CanonicalCookie::Create(GURL("http://test.com"),
                                    "=011Q255bNX_1!yd\203e+;path=/path\203",
-                                   base::Time(), net::CookieOptions()));
+                                   base::Time::Now(), net::CookieOptions()));
   ASSERT_NE(nullptr, canonical_cookie.get());
   Cookie cookie =
       cookies_helpers::CreateCookie(*canonical_cookie, "some cookie store");
diff --git a/headless/public/util/testing/generic_url_request_mocks.cc b/headless/public/util/testing/generic_url_request_mocks.cc
index 54f1ed9..a4053dc 100644
--- a/headless/public/util/testing/generic_url_request_mocks.cc
+++ b/headless/public/util/testing/generic_url_request_mocks.cc
@@ -84,6 +84,14 @@
   CHECK(false);
 }
 
+void MockCookieStore::SetCanonicalCookieAsync(
+    std::unique_ptr<net::CanonicalCookie> cookie,
+    bool secure_source,
+    bool can_modify_httponly,
+    const SetCookiesCallback& callback) {
+  CHECK(false);
+}
+
 void MockCookieStore::GetCookiesWithOptionsAsync(
     const GURL& url,
     const net::CookieOptions& options,
diff --git a/headless/public/util/testing/generic_url_request_mocks.h b/headless/public/util/testing/generic_url_request_mocks.h
index 94e0e5c0..98f5e9d 100644
--- a/headless/public/util/testing/generic_url_request_mocks.h
+++ b/headless/public/util/testing/generic_url_request_mocks.h
@@ -74,6 +74,11 @@
                                  net::CookiePriority priority,
                                  const SetCookiesCallback& callback) override;
 
+  void SetCanonicalCookieAsync(std::unique_ptr<net::CanonicalCookie> cookie,
+                               bool secure_source,
+                               bool modify_http_only,
+                               const SetCookiesCallback& callback) override;
+
   void GetCookiesWithOptionsAsync(const GURL& url,
                                   const net::CookieOptions& options,
                                   const GetCookiesCallback& callback) override;
diff --git a/ios/net/cookies/cookie_store_ios.h b/ios/net/cookies/cookie_store_ios.h
index 85671c9..68ac20f 100644
--- a/ios/net/cookies/cookie_store_ios.h
+++ b/ios/net/cookies/cookie_store_ios.h
@@ -89,6 +89,10 @@
                                  CookieSameSite same_site,
                                  CookiePriority priority,
                                  const SetCookiesCallback& callback) override;
+  void SetCanonicalCookieAsync(std::unique_ptr<CanonicalCookie> cookie,
+                               bool secure_source,
+                               bool modify_http_only,
+                               const SetCookiesCallback& callback) override;
   void GetCookiesWithOptionsAsync(const GURL& url,
                                   const net::CookieOptions& options,
                                   const GetCookiesCallback& callback) override;
diff --git a/ios/net/cookies/cookie_store_ios.mm b/ios/net/cookies/cookie_store_ios.mm
index 2ea0715..f24def3 100644
--- a/ios/net/cookies/cookie_store_ios.mm
+++ b/ios/net/cookies/cookie_store_ios.mm
@@ -25,6 +25,7 @@
 #include "base/task_runner_util.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
 #include "ios/net/cookies/cookie_creation_time_manager.h"
 #include "ios/net/cookies/cookie_store_ios_client.h"
 #include "ios/net/cookies/system_cookie_util.h"
@@ -404,8 +405,6 @@
   cookie_path = std::string(canon_path.data() + canon_path_component.begin,
                             canon_path_component.len);
 
-  // First create a CanonicalCookie, to normalize the arguments,
-  // particularly domain and path, and perform validation.
   std::unique_ptr<net::CanonicalCookie> canonical_cookie =
       base::MakeUnique<net::CanonicalCookie>(
           name, value, cookie_domain, cookie_path, creation_time,
@@ -428,6 +427,40 @@
     callback.Run(success);
 }
 
+void CookieStoreIOS::SetCanonicalCookieAsync(
+    std::unique_ptr<net::CanonicalCookie> cookie,
+    bool secure_source,
+    bool modify_http_only,
+    const SetCookiesCallback& callback) {
+  DCHECK(cookie->IsCanonical());
+  // The exclude_httponly() option would only be used by a javascript
+  // engine.
+  DCHECK(modify_http_only);
+
+  if (cookie->IsSecure() && !secure_source) {
+    if (!callback.is_null())
+      callback.Run(false);
+    return;
+  }
+
+  NSHTTPCookie* ns_cookie = SystemCookieFromCanonicalCookie(*cookie.get());
+
+  if (ns_cookie != nil) {
+    [system_store_ setCookie:ns_cookie];
+    creation_time_manager_->SetCreationTime(
+        ns_cookie,
+        creation_time_manager_->MakeUniqueCreationTime(
+            cookie->CreationDate().is_null() ? base::Time::Now()
+                                             : cookie->CreationDate()));
+    if (!callback.is_null())
+      callback.Run(true);
+    return;
+  }
+
+  if (!callback.is_null())
+    callback.Run(false);
+}
+
 void CookieStoreIOS::GetCookiesWithOptionsAsync(
     const GURL& url,
     const net::CookieOptions& options,
diff --git a/ios/net/cookies/cookie_store_ios_persistent.h b/ios/net/cookies/cookie_store_ios_persistent.h
index ce3bd470..18dba8f 100644
--- a/ios/net/cookies/cookie_store_ios_persistent.h
+++ b/ios/net/cookies/cookie_store_ios_persistent.h
@@ -53,6 +53,10 @@
                                  CookieSameSite same_site,
                                  CookiePriority priority,
                                  const SetCookiesCallback& callback) override;
+  void SetCanonicalCookieAsync(std::unique_ptr<CanonicalCookie> cookie,
+                               bool secure_source,
+                               bool modify_http_only,
+                               const SetCookiesCallback& callback) override;
   void GetCookiesWithOptionsAsync(const GURL& url,
                                   const net::CookieOptions& options,
                                   const GetCookiesCallback& callback) override;
diff --git a/ios/net/cookies/cookie_store_ios_persistent.mm b/ios/net/cookies/cookie_store_ios_persistent.mm
index a0ed1ada..9a8527c 100644
--- a/ios/net/cookies/cookie_store_ios_persistent.mm
+++ b/ios/net/cookies/cookie_store_ios_persistent.mm
@@ -57,6 +57,18 @@
       WrapSetCallback(callback));
 }
 
+void CookieStoreIOSPersistent::SetCanonicalCookieAsync(
+    std::unique_ptr<CanonicalCookie> cookie,
+    bool secure_source,
+    bool modify_http_only,
+    const SetCookiesCallback& callback) {
+  DCHECK(thread_checker().CalledOnValidThread());
+
+  cookie_monster()->SetCanonicalCookieAsync(std::move(cookie), secure_source,
+                                            modify_http_only,
+                                            WrapSetCallback(callback));
+}
+
 void CookieStoreIOSPersistent::GetCookiesWithOptionsAsync(
     const GURL& url,
     const net::CookieOptions& options,
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc
index 8e3158a..4372bde3 100644
--- a/net/cookies/canonical_cookie.cc
+++ b/net/cookies/canonical_cookie.cc
@@ -226,6 +226,7 @@
   if (options.has_server_time())
     server_time = options.server_time();
 
+  DCHECK(!creation_time.is_null());
   Time cookie_expires = CanonicalCookie::CanonExpiration(parsed_cookie,
                                                          creation_time,
                                                          server_time);
@@ -447,6 +448,11 @@
   return true;
 }
 
+void CanonicalCookie::SetCreationDate(base::Time new_creation_date) {
+  DCHECK(CreationDate().is_null());
+  creation_date_ = new_creation_date;
+}
+
 // static
 CanonicalCookie::CookiePrefix CanonicalCookie::GetCookiePrefix(
     const std::string& name) {
diff --git a/net/cookies/canonical_cookie.h b/net/cookies/canonical_cookie.h
index 1d3b3ed5..78870ec 100644
--- a/net/cookies/canonical_cookie.h
+++ b/net/cookies/canonical_cookie.h
@@ -47,8 +47,8 @@
   // Supports the default copy constructor.
 
   // Creates a new |CanonicalCookie| from the |cookie_line| and the
-  // |creation_time|. Canonicalizes and validates inputs. May return NULL if
-  // an attribute value is invalid.
+  // |creation_time|.  Canonicalizes and validates inputs. May return NULL if
+  // an attribute value is invalid.  |creation_time| may not be null.
   static std::unique_ptr<CanonicalCookie> Create(
       const GURL& url,
       const std::string& cookie_line,
@@ -159,6 +159,11 @@
   // greater than the last access time.
   bool IsCanonical() const;
 
+  // Sets the creation date of the cookie to the specified value.  It
+  // is only valid to call this method if the existing creation date
+  // is null.
+  void SetCreationDate(base::Time new_creation_date);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestPrefixHistograms);
 
diff --git a/net/cookies/canonical_cookie_unittest.cc b/net/cookies/canonical_cookie_unittest.cc
index aaa9c69d..a2f3cf7 100644
--- a/net/cookies/canonical_cookie_unittest.cc
+++ b/net/cookies/canonical_cookie_unittest.cc
@@ -864,6 +864,17 @@
                    .IsCanonical());
 }
 
+TEST(CanonicalCookieTest, TestSetCreationDate) {
+  CanonicalCookie cookie("A", "B", "x.y", "/path", base::Time(), base::Time(),
+                         base::Time(), false, false,
+                         CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_LOW);
+  EXPECT_TRUE(cookie.CreationDate().is_null());
+
+  base::Time now(base::Time::Now());
+  cookie.SetCreationDate(now);
+  EXPECT_EQ(now, cookie.CreationDate());
+}
+
 TEST(CanonicalCookieTest, TestPrefixHistograms) {
   base::HistogramTester histograms;
   const char kCookiePrefixHistogram[] = "Cookie.CookiePrefix";
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index 7ccf452..60eccfe 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -694,6 +694,42 @@
   return this->cookie_monster()->DeleteCanonicalCookie(cookie_);
 }
 
+// Task class for SetCanonicalCookie call.
+class CookieMonster::SetCanonicalCookieTask : public CookieMonsterTask {
+ public:
+  SetCanonicalCookieTask(CookieMonster* cookie_monster,
+                         std::unique_ptr<CanonicalCookie> cookie,
+                         bool secure_source,
+                         bool modify_http_only,
+                         const SetCookiesCallback& callback)
+      : CookieMonsterTask(cookie_monster),
+        cookie_(std::move(cookie)),
+        secure_source_(secure_source),
+        modify_http_only_(modify_http_only),
+        callback_(callback) {}
+
+  // CookieMonsterTask:
+  void Run() override;
+
+ protected:
+  ~SetCanonicalCookieTask() override {}
+
+ private:
+  std::unique_ptr<CanonicalCookie> cookie_;
+  bool secure_source_;
+  bool modify_http_only_;
+  SetCookiesCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(SetCanonicalCookieTask);
+};
+
+void CookieMonster::SetCanonicalCookieTask::Run() {
+  bool result = this->cookie_monster()->SetCanonicalCookie(
+      std::move(cookie_), secure_source_, modify_http_only_);
+  if (!callback_.is_null())
+    callback_.Run(result);
+}
+
 // Task class for SetCookieWithOptions call.
 class CookieMonster::SetCookieWithOptionsTask : public CookieMonsterTask {
  public:
@@ -902,6 +938,22 @@
   DoCookieTask(task);
 }
 
+void CookieMonster::SetCanonicalCookieAsync(
+    std::unique_ptr<CanonicalCookie> cookie,
+    bool secure_source,
+    bool modify_http_only,
+    const SetCookiesCallback& callback) {
+  DCHECK(cookie->IsCanonical());
+  scoped_refptr<SetCanonicalCookieTask> task = new SetCanonicalCookieTask(
+      this, std::move(cookie), secure_source, modify_http_only, callback);
+
+  // TODO(rdsmith): Switch to DoCookieTaskForURL (or the equivalent).
+  // This is tricky because we don't have the scheme in this routine
+  // and DoCookieTaskForURL uses cookie_util::GetEffectiveDomain(scheme, host)
+  // to generate the database key to block behind.
+  DoCookieTask(task);
+}
+
 void CookieMonster::SetCookieWithOptionsAsync(
     const GURL& url,
     const std::string& cookie_line,
@@ -1067,16 +1119,6 @@
   if (!HasCookieableScheme(url))
     return false;
 
-  // TODO(mmenke): This class assumes each cookie to have a unique creation
-  // time. Allowing the caller to set the creation time violates that
-  // assumption. Worth fixing? Worth noting that time changes between browser
-  // restarts can cause the same issue.
-  base::Time actual_creation_time = creation_time;
-  if (creation_time.is_null()) {
-    actual_creation_time = CurrentTime();
-    last_time_seen_ = actual_creation_time;
-  }
-
   // Validate consistency of passed arguments.
   if (ParsedCookie::ParseTokenString(name) != name ||
       ParsedCookie::ParseValueString(value) != value ||
@@ -1103,9 +1145,8 @@
                             canon_path_component.len);
 
   std::unique_ptr<CanonicalCookie> cc(base::MakeUnique<CanonicalCookie>(
-      name, value, cookie_domain, cookie_path, actual_creation_time,
-      expiration_time, last_access_time, secure, http_only, same_site,
-      priority));
+      name, value, cookie_domain, cookie_path, creation_time, expiration_time,
+      last_access_time, secure, http_only, same_site, priority));
 
   return SetCanonicalCookie(std::move(cc), url.SchemeIsCryptographic(), true);
 }
@@ -1765,9 +1806,19 @@
   if (cc->IsSecure() && !secure_source)
     return false;
 
-  Time creation_time = cc->CreationDate();
   const std::string key(GetKey(cc->Domain()));
-  bool already_expired = cc->IsExpired(creation_time);
+
+  // TODO(mmenke): This class assumes each cookie to have a unique creation
+  // time. Allowing the caller to set the creation time violates that
+  // assumption. Worth fixing? Worth noting that time changes between browser
+  // restarts can cause the same issue.
+  base::Time creation_date = cc->CreationDate();
+  if (creation_date.is_null()) {
+    creation_date = CurrentTime();
+    cc->SetCreationDate(creation_date);
+    last_time_seen_ = creation_date;
+  }
+  bool already_expired = cc->IsExpired(creation_date);
 
   if (DeleteAnyEquivalentCookie(key, *cc, secure_source, !modify_http_only,
                                 already_expired)) {
@@ -1789,7 +1840,7 @@
     // See InitializeHistograms() for details.
     if (cc->IsPersistent()) {
       histogram_expiration_duration_minutes_->Add(
-          (cc->ExpiryDate() - creation_time).InMinutes());
+          (cc->ExpiryDate() - creation_date).InMinutes());
     }
 
     // Histogram the type of scheme used on URLs that set cookies. This
@@ -1817,7 +1868,7 @@
   // make sure that we garbage collect...  We can also make the assumption that
   // if a cookie was set, in the common case it will be used soon after,
   // and we will purge the expired cookies in GetCookies().
-  GarbageCollect(creation_time, key);
+  GarbageCollect(creation_date, key);
 
   return true;
 }
diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h
index 42afd9a..9382e935 100644
--- a/net/cookies/cookie_monster.h
+++ b/net/cookies/cookie_monster.h
@@ -176,6 +176,10 @@
                                  CookieSameSite same_site,
                                  CookiePriority priority,
                                  const SetCookiesCallback& callback) override;
+  void SetCanonicalCookieAsync(std::unique_ptr<CanonicalCookie> cookie,
+                               bool secure_source,
+                               bool modify_http_only,
+                               const SetCookiesCallback& callback) override;
   void GetCookiesWithOptionsAsync(const GURL& url,
                                   const CookieOptions& options,
                                   const GetCookiesCallback& callback) override;
@@ -247,6 +251,7 @@
   class SetAllCookiesTask;
   class SetCookieWithDetailsTask;
   class SetCookieWithOptionsTask;
+  class SetCanonicalCookieTask;
   class DeleteSessionCookiesTask;
 
   // Testing support.
@@ -427,6 +432,15 @@
                             CookieSameSite same_site,
                             CookiePriority priority);
 
+  // Sets a canonical cookie, deletes equivalents and performs garbage
+  // collection.  |source_secure| indicates if the cookie is being set
+  // from a secure source (e.g. a cryptographic scheme).
+  // |modify_http_only| indicates if this setting operation is allowed
+  // to affect http_only cookies.
+  bool SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cookie,
+                          bool secure_source,
+                          bool can_modify_httponly);
+
   CookieList GetAllCookies();
 
   CookieList GetCookieListWithOptions(const GURL& url,
@@ -545,15 +559,6 @@
                                            const base::Time& creation_time,
                                            const CookieOptions& options);
 
-  // Sets a canonical cookie, deletes equivalents and performs garbage
-  // collection.  |source_secure| indicates if the cookie is being set
-  // from a secure source (e.g. a cryptographic scheme).
-  // |modify_http_only| indicates if this setting operation is allowed
-  // to affect http_only cookies.
-  bool SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cookie,
-                          bool secure_source,
-                          bool can_modify_httponly);
-
   // Sets all cookies from |list| after deleting any equivalent cookie.
   // For data gathering purposes, this routine is treated as if it is
   // restoring saved cookies; some statistics are not gathered in this case.
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index 0b60812a..6878898 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -148,6 +148,20 @@
     return callback.result();
   }
 
+  bool SetCanonicalCookie(std::unique_ptr<CookieMonster> cm,
+                          std::unique_ptr<CanonicalCookie> cookie,
+                          bool secure_source,
+                          bool modify_http_only) {
+    DCHECK(cm);
+    ResultSavingCookieCallback<bool> callback;
+    cm->SetCanonicalCookieAsync(
+        std::move(cookie), secure_source, modify_http_only,
+        base::Bind(&ResultSavingCookieCallback<bool>::Run,
+                   base::Unretained(&callback)));
+    callback.WaitUntilDone();
+    return callback.result();
+  }
+
   int DeleteAllCreatedBetween(CookieMonster* cm,
                               const base::Time& delete_begin,
                               const base::Time& delete_end) {
@@ -2303,7 +2317,7 @@
   // When passed to the CookieMonster, it takes ownership of the pointed to
   // cookies.
   cookies.push_back(
-      CanonicalCookie::Create(kUrl, "a=b", base::Time(), CookieOptions()));
+      CanonicalCookie::Create(kUrl, "a=b", base::Time::Now(), CookieOptions()));
   ASSERT_TRUE(cookies[0]);
   store->commands()[0].loaded_callback.Run(std::move(cookies));
 
diff --git a/net/cookies/cookie_store.h b/net/cookies/cookie_store.h
index a2be255..931b2ea1 100644
--- a/net/cookies/cookie_store.h
+++ b/net/cookies/cookie_store.h
@@ -127,6 +127,18 @@
       CookiePriority priority,
       const SetCookiesCallback& callback) = 0;
 
+  // TODO(rdsmith): Remove SetCookieWithDetailsAsync in favor of this.
+  // Set the cookie on the cookie store.  |cookie.IsCanonical()| must
+  // be true.  |secure_source| indicates if the source of the setting
+  // may be considered secure (if from a URL, the scheme is
+  // cryptographic), and |modify_http_only| indicates if the source of
+  // the setting may modify http_only cookies.  The current time will
+  // be used in place of a null creation time.
+  virtual void SetCanonicalCookieAsync(std::unique_ptr<CanonicalCookie> cookie,
+                                       bool secure_source,
+                                       bool modify_http_only,
+                                       const SetCookiesCallback& callback) = 0;
+
   // TODO(???): what if the total size of all the cookies >4k, can we have a
   // header that big or do we need multiple Cookie: headers?
   // Note: Some sites, such as Facebook, occasionally use Cookie headers >4k.
diff --git a/net/cookies/cookie_store_test_helpers.cc b/net/cookies/cookie_store_test_helpers.cc
index bed506f1..d1c1ed5 100644
--- a/net/cookies/cookie_store_test_helpers.cc
+++ b/net/cookies/cookie_store_test_helpers.cc
@@ -92,6 +92,14 @@
   NOTREACHED();
 }
 
+void DelayedCookieMonster::SetCanonicalCookieAsync(
+    std::unique_ptr<CanonicalCookie> cookie,
+    bool secure_source,
+    bool modify_http_only,
+    const SetCookiesCallback& callback) {
+  NOTREACHED();
+}
+
 void DelayedCookieMonster::GetCookiesWithOptionsAsync(
     const GURL& url,
     const CookieOptions& options,
diff --git a/net/cookies/cookie_store_test_helpers.h b/net/cookies/cookie_store_test_helpers.h
index bb6d656b..7a9c34c 100644
--- a/net/cookies/cookie_store_test_helpers.h
+++ b/net/cookies/cookie_store_test_helpers.h
@@ -45,6 +45,11 @@
                                  CookiePriority priority,
                                  const SetCookiesCallback& callback) override;
 
+  void SetCanonicalCookieAsync(std::unique_ptr<CanonicalCookie> cookie,
+                               bool secure_source,
+                               bool modify_http_only,
+                               const SetCookiesCallback& callback) override;
+
   void GetCookiesWithOptionsAsync(
       const GURL& url,
       const CookieOptions& options,
diff --git a/net/cookies/cookie_store_unittest.h b/net/cookies/cookie_store_unittest.h
index 14aa3e9..4aa2a3c 100644
--- a/net/cookies/cookie_store_unittest.h
+++ b/net/cookies/cookie_store_unittest.h
@@ -181,6 +181,20 @@
     return callback.result();
   }
 
+  bool SetCanonicalCookie(CookieStore* cs,
+                          std::unique_ptr<CanonicalCookie> cookie,
+                          bool secure_source,
+                          bool can_modify_httponly) {
+    DCHECK(cs);
+    ResultSavingCookieCallback<bool> callback;
+    cs->SetCanonicalCookieAsync(
+        std::move(cookie), secure_source, can_modify_httponly,
+        base::Bind(&ResultSavingCookieCallback<bool>::Run,
+                   base::Unretained(&callback)));
+    callback.WaitUntilDone();
+    return callback.result();
+  }
+
   bool SetCookieWithServerTime(CookieStore* cs,
                                const GURL& url,
                                const std::string& cookie_line,
@@ -465,6 +479,131 @@
   EXPECT_TRUE(++it == cookies.end());
 }
 
+TYPED_TEST_P(CookieStoreTest, SetCanonicalCookieTest) {
+  CookieStore* cs = this->GetCookieStore();
+
+  base::Time two_hours_ago = base::Time::Now() - base::TimeDelta::FromHours(2);
+  base::Time one_hour_ago = base::Time::Now() - base::TimeDelta::FromHours(1);
+  base::Time one_hour_from_now =
+      base::Time::Now() + base::TimeDelta::FromHours(1);
+
+  std::string foo_foo_host(this->www_foo_foo_.url().host());
+  std::string foo_bar_domain(this->www_foo_bar_.domain());
+  std::string http_foo_host(this->http_www_foo_.url().host());
+  std::string https_foo_host(this->https_www_foo_.url().host());
+
+  EXPECT_TRUE(this->SetCanonicalCookie(
+      cs,
+      base::MakeUnique<CanonicalCookie>(
+          "A", "B", foo_foo_host, "/foo", one_hour_ago, one_hour_from_now,
+          base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+          COOKIE_PRIORITY_DEFAULT),
+      false, true));
+  // Note that for the creation time to be set exactly, without modification,
+  // it must be different from the one set by the line above.
+  EXPECT_TRUE(this->SetCanonicalCookie(
+      cs,
+      base::MakeUnique<CanonicalCookie>(
+          "C", "D", "." + foo_bar_domain, "/bar", two_hours_ago, base::Time(),
+          one_hour_ago, false, true, CookieSameSite::DEFAULT_MODE,
+          COOKIE_PRIORITY_DEFAULT),
+      false, true));
+
+  // A secure source is required for creating secure cookies.
+  EXPECT_FALSE(this->SetCanonicalCookie(
+      cs,
+      base::MakeUnique<CanonicalCookie>(
+          "E", "F", http_foo_host, "/", base::Time(), base::Time(),
+          base::Time(), true, false, CookieSameSite::DEFAULT_MODE,
+          COOKIE_PRIORITY_DEFAULT),
+      false, true));
+
+  // A secure source is also required for overwriting secure cookies.  Writing
+  // a secure cookie then overwriting it from a non-secure source should fail.
+  EXPECT_TRUE(this->SetCanonicalCookie(
+      cs,
+      base::MakeUnique<CanonicalCookie>(
+          "E", "F", http_foo_host, "/", base::Time(), base::Time(),
+          base::Time(), true, false, CookieSameSite::DEFAULT_MODE,
+          COOKIE_PRIORITY_DEFAULT),
+      true, true));
+
+  EXPECT_FALSE(this->SetCanonicalCookie(
+      cs,
+      base::MakeUnique<CanonicalCookie>(
+          "E", "F", http_foo_host, "/", base::Time(), base::Time(),
+          base::Time(), true, false, CookieSameSite::DEFAULT_MODE,
+          COOKIE_PRIORITY_DEFAULT),
+      false, true));
+
+  // Get all the cookies for a given URL, regardless of properties. This 'get()'
+  // operation shouldn't update the access time, as the test checks that the
+  // access time is set properly upon creation. Updating the access time would
+  // make that difficult.
+  CookieOptions options;
+  options.set_include_httponly();
+  options.set_same_site_cookie_mode(
+      CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
+  options.set_do_not_update_access_time();
+
+  CookieList cookies =
+      this->GetCookieListWithOptions(cs, this->www_foo_foo_.url(), options);
+  CookieList::iterator it = cookies.begin();
+
+  ASSERT_EQ(1u, cookies.size());
+  EXPECT_EQ("A", it->Name());
+  EXPECT_EQ("B", it->Value());
+  EXPECT_EQ(this->www_foo_foo_.host(), it->Domain());
+  EXPECT_EQ("/foo", it->Path());
+  EXPECT_EQ(one_hour_ago, it->CreationDate());
+  EXPECT_TRUE(it->IsPersistent());
+  // Expect expiration date is in the right range.  Some cookie implementations
+  // may not record it with millisecond accuracy.
+  EXPECT_LE((one_hour_from_now - it->ExpiryDate()).magnitude().InSeconds(), 5);
+  // Some CookieStores don't store last access date.
+  if (!it->LastAccessDate().is_null())
+    EXPECT_EQ(one_hour_ago, it->LastAccessDate());
+  EXPECT_FALSE(it->IsSecure());
+  EXPECT_FALSE(it->IsHttpOnly());
+
+  // Get the cookie using the wide open |options|:
+  cookies =
+      this->GetCookieListWithOptions(cs, this->www_foo_bar_.url(), options);
+  ASSERT_EQ(1u, cookies.size());
+  it = cookies.begin();
+
+  EXPECT_EQ("C", it->Name());
+  EXPECT_EQ("D", it->Value());
+  EXPECT_EQ(this->www_foo_bar_.Format(".%D"), it->Domain());
+  EXPECT_EQ("/bar", it->Path());
+  EXPECT_EQ(two_hours_ago, it->CreationDate());
+  EXPECT_FALSE(it->IsPersistent());
+  // Some CookieStores don't store last access date.
+  if (!it->LastAccessDate().is_null())
+    EXPECT_EQ(one_hour_ago, it->LastAccessDate());
+  EXPECT_FALSE(it->IsSecure());
+  EXPECT_TRUE(it->IsHttpOnly());
+
+  cookies =
+      this->GetCookieListWithOptions(cs, this->https_www_foo_.url(), options);
+  ASSERT_EQ(1u, cookies.size());
+  it = cookies.begin();
+
+  EXPECT_EQ("E", it->Name());
+  EXPECT_EQ("F", it->Value());
+  EXPECT_EQ("/", it->Path());
+  EXPECT_EQ(this->https_www_foo_.host(), it->Domain());
+  // Cookie should have its creation time set, and be in a reasonable range.
+  EXPECT_LE((base::Time::Now() - it->CreationDate()).magnitude().InMinutes(),
+            2);
+  EXPECT_FALSE(it->IsPersistent());
+  // Some CookieStores don't store last access date.
+  if (!it->LastAccessDate().is_null())
+    EXPECT_EQ(it->CreationDate(), it->LastAccessDate());
+  EXPECT_TRUE(it->IsSecure());
+  EXPECT_FALSE(it->IsHttpOnly());
+}
+
 // Test enforcement around setting secure cookies.
 TYPED_TEST_P(CookieStoreTest, SetCookieWithDetailsSecureEnforcement) {
   CookieStore* cs = this->GetCookieStore();
@@ -1476,6 +1615,7 @@
 
 REGISTER_TYPED_TEST_CASE_P(CookieStoreTest,
                            SetCookieWithDetailsAsync,
+                           SetCanonicalCookieTest,
                            SetCookieWithDetailsSecureEnforcement,
                            EmptyKeyTest,
                            DomainTest,