[PrefService] Introduce a synchronous option to CommitPendingWrite()

This is required to remove the |local_state_task_runner| member
of BrowserProcessImpl only used to implicitly wait on pref store.
Ref. https://crrev.com/c/1163628.
Synchronous callback semantics are required on EndSession() as a nested
RunLoop is not suitable to observe a reply.
https://chromium-review.googlesource.com/c/chromium/src/+/1163628/8/chrome/browser/browser_process_impl.cc#594

Also implemented in services/preferences' SegregatedPrefStore but
not in the Mojom interface where I don't think it's used yet? Or if
it is then it was already wrong as |local_state_task_runner| is
decoupled from that Mojom. The DCHECK will tell and make this future
proof.

Bug: 848615
Cq-Include-Trybots: luci.chromium.try:linux_mojo;master.tryserver.chromium.android:android_cronet_tester;master.tryserver.chromium.mac:ios-simulator-cronet
Change-Id: Ie72f2d30d30bfa7f96a04d780d1591949a173b78
Reviewed-on: https://chromium-review.googlesource.com/1164522
Reviewed-by: Jonathan Ross <[email protected]>
Reviewed-by: Bernhard Bauer <[email protected]>
Commit-Queue: Gabriel Charette <[email protected]>
Cr-Commit-Position: refs/heads/master@{#581324}
diff --git a/components/prefs/json_pref_store.cc b/components/prefs/json_pref_store.cc
index c7a6e144..e777f54 100644
--- a/components/prefs/json_pref_store.cc
+++ b/components/prefs/json_pref_store.cc
@@ -272,7 +272,9 @@
       base::Bind(&JsonPrefStore::OnFileRead, AsWeakPtr()));
 }
 
-void JsonPrefStore::CommitPendingWrite(base::OnceClosure done_callback) {
+void JsonPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Schedule a write for any lossy writes that are outstanding to ensure that
@@ -282,13 +284,19 @@
   if (writer_.HasPendingWrite() && !read_only_)
     writer_.DoScheduledWrite();
 
-  if (done_callback) {
-    // Since disk operations occur on |file_task_runner_|, the reply of a task
-    // posted to |file_task_runner_| will run after currently pending disk
-    // operations. Also, by definition of PostTaskAndReply(), the reply will run
-    // on the current sequence.
+  // Since disk operations occur on |file_task_runner_|, the reply of a task
+  // posted to |file_task_runner_| will run after currently pending disk
+  // operations. Also, by definition of PostTaskAndReply(), the reply (in the
+  // |reply_callback| case will run on the current sequence.
+
+  if (synchronous_done_callback) {
+    file_task_runner_->PostTask(FROM_HERE,
+                                std::move(synchronous_done_callback));
+  }
+
+  if (reply_callback) {
     file_task_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
-                                        std::move(done_callback));
+                                        std::move(reply_callback));
   }
 }
 
@@ -444,7 +452,7 @@
 
 JsonPrefStore::~JsonPrefStore() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CommitPendingWrite(base::OnceClosure());
+  CommitPendingWrite();
 }
 
 bool JsonPrefStore::SerializeData(std::string* output) {
diff --git a/components/prefs/json_pref_store.h b/components/prefs/json_pref_store.h
index 383a257f..427c5cb6 100644
--- a/components/prefs/json_pref_store.h
+++ b/components/prefs/json_pref_store.h
@@ -94,7 +94,10 @@
   // See details in pref_filter.h.
   PrefReadError ReadPrefs() override;
   void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
-  void CommitPendingWrite(base::OnceClosure done_callback) override;
+  void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback =
+          base::OnceClosure()) override;
   void SchedulePendingLossyWrites() override;
   void ReportValueChanged(const std::string& key, uint32_t flags) override;
 
diff --git a/components/prefs/json_pref_store_unittest.cc b/components/prefs/json_pref_store_unittest.cc
index b084ebd..5673942 100644
--- a/components/prefs/json_pref_store_unittest.cc
+++ b/components/prefs/json_pref_store_unittest.cc
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/compiler_specific.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/location.h"
@@ -22,6 +23,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/test/simple_test_clock.h"
@@ -130,19 +132,50 @@
 };
 
 enum class CommitPendingWriteMode {
+  // Basic mode.
   WITHOUT_CALLBACK,
+  // With reply callback.
   WITH_CALLBACK,
+  // With synchronous notify callback (synchronous after the write -- shouldn't
+  // require pumping messages to observe).
+  WITH_SYNCHRONOUS_CALLBACK,
 };
 
+base::test::ScopedTaskEnvironment::ExecutionMode GetExecutionMode(
+    CommitPendingWriteMode commit_mode) {
+  switch (commit_mode) {
+    case CommitPendingWriteMode::WITHOUT_CALLBACK:
+      FALLTHROUGH;
+    case CommitPendingWriteMode::WITH_CALLBACK:
+      return base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED;
+    case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK:
+      // Synchronous callbacks require async tasks to run on their own.
+      return base::test::ScopedTaskEnvironment::ExecutionMode::ASYNC;
+  }
+}
+
 void CommitPendingWrite(
     JsonPrefStore* pref_store,
     CommitPendingWriteMode commit_pending_write_mode,
     base::test::ScopedTaskEnvironment* scoped_task_environment) {
-  if (commit_pending_write_mode == CommitPendingWriteMode::WITHOUT_CALLBACK) {
-    pref_store->CommitPendingWrite(OnceClosure());
-    scoped_task_environment->RunUntilIdle();
-  } else {
-    TestCommitPendingWriteWithCallback(pref_store, scoped_task_environment);
+  switch (commit_pending_write_mode) {
+    case CommitPendingWriteMode::WITHOUT_CALLBACK: {
+      pref_store->CommitPendingWrite();
+      scoped_task_environment->RunUntilIdle();
+      break;
+    }
+    case CommitPendingWriteMode::WITH_CALLBACK: {
+      TestCommitPendingWriteWithCallback(pref_store, scoped_task_environment);
+      break;
+    }
+    case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK: {
+      base::WaitableEvent written;
+      pref_store->CommitPendingWrite(
+          base::OnceClosure(),
+          base::BindOnce(&base::WaitableEvent::Signal, Unretained(&written)));
+      written.Wait();
+      break;
+    }
   }
 }
 
@@ -152,7 +185,7 @@
   JsonPrefStoreTest()
       : scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
-            base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED) {}
+            GetExecutionMode(GetParam())) {}
 
  protected:
   void SetUp() override {
@@ -170,7 +203,7 @@
 }  // namespace
 
 // Test fallback behavior for a nonexistent file.
-TEST_F(JsonPrefStoreTest, NonExistentFile) {
+TEST_P(JsonPrefStoreTest, NonExistentFile) {
   base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
   ASSERT_FALSE(PathExists(bogus_input_file));
   auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);
@@ -180,7 +213,7 @@
 }
 
 // Test fallback behavior for an invalid file.
-TEST_F(JsonPrefStoreTest, InvalidFile) {
+TEST_P(JsonPrefStoreTest, InvalidFile) {
   base::FilePath invalid_file = temp_dir_.GetPath().AppendASCII("invalid.json");
   ASSERT_LT(0, base::WriteFile(invalid_file,
                                kInvalidJson, arraysize(kInvalidJson) - 1));
@@ -371,7 +404,7 @@
 
 // This test is just documenting some potentially non-obvious behavior. It
 // shouldn't be taken as normative.
-TEST_F(JsonPrefStoreTest, RemoveClearsEmptyParent) {
+TEST_P(JsonPrefStoreTest, RemoveClearsEmptyParent) {
   FilePath pref_file = temp_dir_.GetPath().AppendASCII("empty_values.json");
 
   auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);
@@ -390,7 +423,7 @@
 }
 
 // Tests asynchronous reading of the file when there is no file.
-TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) {
+TEST_P(JsonPrefStoreTest, AsyncNonExistingFile) {
   base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
   ASSERT_FALSE(PathExists(bogus_input_file));
   auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);
@@ -514,7 +547,7 @@
                             &scoped_task_environment_);
 }
 
-TEST_F(JsonPrefStoreTest, WriteCountHistogramTestBasic) {
+TEST_P(JsonPrefStoreTest, WriteCountHistogramTestBasic) {
   base::HistogramTester histogram_tester;
 
   SimpleTestClock* test_clock = new SimpleTestClock;
@@ -542,7 +575,7 @@
   ASSERT_TRUE(histogram.GetHistogram()->HasConstructionArguments(1, 30, 31));
 }
 
-TEST_F(JsonPrefStoreTest, WriteCountHistogramTestSinglePeriod) {
+TEST_P(JsonPrefStoreTest, WriteCountHistogramTestSinglePeriod) {
   base::HistogramTester histogram_tester;
 
   SimpleTestClock* test_clock = new SimpleTestClock;
@@ -580,7 +613,7 @@
   histogram_tester.ExpectTotalCount(histogram_name, 1);
 }
 
-TEST_F(JsonPrefStoreTest, WriteCountHistogramTestMultiplePeriods) {
+TEST_P(JsonPrefStoreTest, WriteCountHistogramTestMultiplePeriods) {
   base::HistogramTester histogram_tester;
 
   SimpleTestClock* test_clock = new SimpleTestClock;
@@ -620,7 +653,7 @@
   histogram_tester.ExpectTotalCount(histogram_name, 3);
 }
 
-TEST_F(JsonPrefStoreTest, WriteCountHistogramTestPeriodWithGaps) {
+TEST_P(JsonPrefStoreTest, WriteCountHistogramTestPeriodWithGaps) {
   base::HistogramTester histogram_tester;
 
   SimpleTestClock* test_clock = new SimpleTestClock;
@@ -671,6 +704,10 @@
     WithCallback,
     JsonPrefStoreTest,
     ::testing::Values(CommitPendingWriteMode::WITH_CALLBACK));
+INSTANTIATE_TEST_CASE_P(
+    WithSynchronousCallback,
+    JsonPrefStoreTest,
+    ::testing::Values(CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));
 
 class JsonPrefStoreLossyWriteTest : public JsonPrefStoreTest {
  public:
@@ -706,7 +743,7 @@
   DISALLOW_COPY_AND_ASSIGN(JsonPrefStoreLossyWriteTest);
 };
 
-TEST_F(JsonPrefStoreLossyWriteTest, LossyWriteBasic) {
+TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteBasic) {
   scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
   ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
 
@@ -751,7 +788,7 @@
             GetTestFileContents());
 }
 
-TEST_F(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossyFirst) {
+TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossyFirst) {
   scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
   ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
 
@@ -773,7 +810,7 @@
   ASSERT_FALSE(file_writer->HasPendingWrite());
 }
 
-TEST_F(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossySecond) {
+TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossySecond) {
   scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
   ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
 
@@ -795,7 +832,7 @@
   ASSERT_FALSE(file_writer->HasPendingWrite());
 }
 
-TEST_F(JsonPrefStoreLossyWriteTest, ScheduleLossyWrite) {
+TEST_P(JsonPrefStoreLossyWriteTest, ScheduleLossyWrite) {
   scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
   ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
 
@@ -815,6 +852,19 @@
   ASSERT_EQ("{\"lossy\":\"lossy\"}", GetTestFileContents());
 }
 
+INSTANTIATE_TEST_CASE_P(
+    WithoutCallback,
+    JsonPrefStoreLossyWriteTest,
+    ::testing::Values(CommitPendingWriteMode::WITHOUT_CALLBACK));
+INSTANTIATE_TEST_CASE_P(
+    WithReply,
+    JsonPrefStoreLossyWriteTest,
+    ::testing::Values(CommitPendingWriteMode::WITH_CALLBACK));
+INSTANTIATE_TEST_CASE_P(
+    WithNotify,
+    JsonPrefStoreLossyWriteTest,
+    ::testing::Values(CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));
+
 class SuccessfulWriteReplyObserver {
  public:
   SuccessfulWriteReplyObserver() = default;
@@ -912,13 +962,13 @@
   return state;
 }
 
-class JsonPrefStoreCallbackTest : public JsonPrefStoreTest {
+class JsonPrefStoreCallbackTest : public testing::Test {
  public:
   JsonPrefStoreCallbackTest() = default;
 
  protected:
   void SetUp() override {
-    JsonPrefStoreTest::SetUp();
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     test_file_ = temp_dir_.GetPath().AppendASCII("test.json");
   }
 
@@ -943,6 +993,13 @@
   SuccessfulWriteReplyObserver successful_write_reply_observer_;
   WriteCallbacksObserver write_callback_observer_;
 
+ protected:
+  base::test::ScopedTaskEnvironment scoped_task_environment_{
+      base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
+      base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED};
+
+  base::ScopedTempDir temp_dir_;
+
  private:
   base::FilePath test_file_;
 
diff --git a/components/prefs/overlay_user_pref_store.cc b/components/prefs/overlay_user_pref_store.cc
index 2b45d8c64..e5240ab 100644
--- a/components/prefs/overlay_user_pref_store.cc
+++ b/components/prefs/overlay_user_pref_store.cc
@@ -180,8 +180,11 @@
   OnInitializationCompleted(/* ephemeral */ false, true);
 }
 
-void OverlayUserPrefStore::CommitPendingWrite(base::OnceClosure done_callback) {
-  persistent_user_pref_store_->CommitPendingWrite(std::move(done_callback));
+void OverlayUserPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  persistent_user_pref_store_->CommitPendingWrite(
+      std::move(reply_callback), std::move(synchronous_done_callback));
   // We do not write our content intentionally.
 }
 
diff --git a/components/prefs/overlay_user_pref_store.h b/components/prefs/overlay_user_pref_store.h
index 0817aed..cf9d8677 100644
--- a/components/prefs/overlay_user_pref_store.h
+++ b/components/prefs/overlay_user_pref_store.h
@@ -57,7 +57,8 @@
   PrefReadError GetReadError() const override;
   PrefReadError ReadPrefs() override;
   void ReadPrefsAsync(ReadErrorDelegate* delegate) override;
-  void CommitPendingWrite(base::OnceClosure done_callback) override;
+  void CommitPendingWrite(base::OnceClosure reply_callback,
+                          base::OnceClosure synchronous_done_callback) override;
   void SchedulePendingLossyWrites() override;
   void ReportValueChanged(const std::string& key, uint32_t flags) override;
 
diff --git a/components/prefs/persistent_pref_store.cc b/components/prefs/persistent_pref_store.cc
index 9d3a42af..1dea8b7d 100644
--- a/components/prefs/persistent_pref_store.cc
+++ b/components/prefs/persistent_pref_store.cc
@@ -8,11 +8,20 @@
 
 #include "base/threading/sequenced_task_runner_handle.h"
 
-void PersistentPrefStore::CommitPendingWrite(base::OnceClosure done_callback) {
+void PersistentPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
   // Default behavior for PersistentPrefStore implementation that don't issue
   // disk operations: schedule the callback immediately.
-  if (done_callback) {
+  // |synchronous_done_callback| is allowed to be invoked synchronously (and
+  // must be here since we have no other way to post it which isn't the current
+  // sequence).
+
+  if (synchronous_done_callback)
+    std::move(synchronous_done_callback).Run();
+
+  if (reply_callback) {
     base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                     std::move(done_callback));
+                                                     std::move(reply_callback));
   }
 }
diff --git a/components/prefs/persistent_pref_store.h b/components/prefs/persistent_pref_store.h
index d2667b0..f3a5bbb3 100644
--- a/components/prefs/persistent_pref_store.h
+++ b/components/prefs/persistent_pref_store.h
@@ -62,10 +62,16 @@
   // Owns |error_delegate|.
   virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) = 0;
 
-  // Starts an asynchronous attempt to commit pending writes to disk. Posts a
-  // task to run |done_callback| on the current sequence when disk operations,
-  // if any, are complete (even if they are unsuccessful).
-  virtual void CommitPendingWrite(base::OnceClosure done_callback);
+  // Lands pending writes to disk. |reply_callback| will be posted to the
+  // current sequence when changes have been written.
+  // |synchronous_done_callback| on the other hand will be invoked right away
+  // wherever the writes complete (could even be invoked synchronously if no
+  // writes need to occur); this is useful when the current thread cannot pump
+  // messages to observe the reply (e.g. nested loops banned on main thread
+  // during shutdown). |synchronous_done_callback| must be thread-safe.
+  virtual void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback = base::OnceClosure());
 
   // Schedule a write if there is any lossy data pending. Unlike
   // CommitPendingWrite() this does not immediately sync to disk, instead it
diff --git a/components/prefs/pref_service.cc b/components/prefs/pref_service.cc
index 97e9a69..519221e9 100644
--- a/components/prefs/pref_service.cc
+++ b/components/prefs/pref_service.cc
@@ -97,13 +97,12 @@
   }
 }
 
-void PrefService::CommitPendingWrite() {
-  CommitPendingWrite(base::OnceClosure());
-}
-
-void PrefService::CommitPendingWrite(base::OnceClosure done_callback) {
+void PrefService::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  user_pref_store_->CommitPendingWrite(std::move(done_callback));
+  user_pref_store_->CommitPendingWrite(std::move(reply_callback),
+                                       std::move(synchronous_done_callback));
 }
 
 void PrefService::SchedulePendingLossyWrites() {
diff --git a/components/prefs/pref_service.h b/components/prefs/pref_service.h
index 8ced3c9..c6ea3eb4f 100644
--- a/components/prefs/pref_service.h
+++ b/components/prefs/pref_service.h
@@ -176,13 +176,16 @@
   virtual ~PrefService();
 
   // Lands pending writes to disk. This should only be used if we need to save
-  // immediately (basically, during shutdown).
-  void CommitPendingWrite();
-
-  // Lands pending writes to disk. This should only be used if we need to save
-  // immediately. |done_callback| will be invoked when changes have been
-  // written.
-  void CommitPendingWrite(base::OnceClosure done_callback);
+  // immediately (basically, during shutdown). |reply_callback| will be posted
+  // to the current sequence when changes have been written.
+  // |synchronous_done_callback| on the other hand will be invoked right away
+  // wherever the writes complete (could even be invoked synchronously if no
+  // writes need to occur); this is useful when the current thread cannot pump
+  // messages to observe the reply (e.g. nested loops banned on main thread
+  // during shutdown). |synchronous_done_callback| must be thread-safe.
+  void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback = base::OnceClosure());
 
   // Schedule a write if there is any lossy data pending. Unlike
   // CommitPendingWrite() this does not immediately sync to disk, instead it
diff --git a/components/prefs/testing_pref_store.cc b/components/prefs/testing_pref_store.cc
index 5dbc56af..94b86eeb 100644
--- a/components/prefs/testing_pref_store.cc
+++ b/components/prefs/testing_pref_store.cc
@@ -98,9 +98,12 @@
     NotifyInitializationCompleted();
 }
 
-void TestingPrefStore::CommitPendingWrite(base::OnceClosure done_callback) {
+void TestingPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
   committed_ = true;
-  PersistentPrefStore::CommitPendingWrite(std::move(done_callback));
+  PersistentPrefStore::CommitPendingWrite(std::move(reply_callback),
+                                          std::move(synchronous_done_callback));
 }
 
 void TestingPrefStore::SchedulePendingLossyWrites() {}
diff --git a/components/prefs/testing_pref_store.h b/components/prefs/testing_pref_store.h
index c011815d..ed67fbc 100644
--- a/components/prefs/testing_pref_store.h
+++ b/components/prefs/testing_pref_store.h
@@ -45,7 +45,8 @@
   PrefReadError GetReadError() const override;
   PersistentPrefStore::PrefReadError ReadPrefs() override;
   void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
-  void CommitPendingWrite(base::OnceClosure done_callback) override;
+  void CommitPendingWrite(base::OnceClosure reply_callback,
+                          base::OnceClosure synchronous_done_callback) override;
   void SchedulePendingLossyWrites() override;
 
   // Marks the store as having completed initialization.
diff --git a/services/preferences/persistent_pref_store_impl.cc b/services/preferences/persistent_pref_store_impl.cc
index 68f9b1a..a0df6d6 100644
--- a/services/preferences/persistent_pref_store_impl.cc
+++ b/services/preferences/persistent_pref_store_impl.cc
@@ -129,6 +129,10 @@
   }
 
   void CommitPendingWrite(CommitPendingWriteCallback done_callback) override {
+    // Note: PersistentPrefStore's synchronous callback part of the
+    // CommitPendingWrite() API isn't supported on mojom::PersistentPrefStore at
+    // the moment (see PersistentPrefStoreClient::CommitPendingWrite() for
+    // details).
     pref_store_->CommitPendingWrite(std::move(done_callback));
   }
   void SchedulePendingLossyWrites() override {
diff --git a/services/preferences/persistent_pref_store_impl_unittest.cc b/services/preferences/persistent_pref_store_impl_unittest.cc
index 1291be4..152a2d70 100644
--- a/services/preferences/persistent_pref_store_impl_unittest.cc
+++ b/services/preferences/persistent_pref_store_impl_unittest.cc
@@ -23,9 +23,12 @@
 
 class PersistentPrefStoreMock : public InMemoryPrefStore {
  public:
-  void CommitPendingWrite(base::OnceClosure callback) override {
+  void CommitPendingWrite(
+      base::OnceClosure reply_callback,
+      base::OnceClosure synchronous_done_callback) override {
     CommitPendingWriteMock();
-    InMemoryPrefStore::CommitPendingWrite(std::move(callback));
+    InMemoryPrefStore::CommitPendingWrite(std::move(reply_callback),
+                                          std::move(synchronous_done_callback));
   }
 
   MOCK_METHOD0(CommitPendingWriteMock, void());
diff --git a/services/preferences/public/cpp/persistent_pref_store_client.cc b/services/preferences/public/cpp/persistent_pref_store_client.cc
index 8c83478..47d806c 100644
--- a/services/preferences/public/cpp/persistent_pref_store_client.cc
+++ b/services/preferences/public/cpp/persistent_pref_store_client.cc
@@ -225,11 +225,17 @@
     ReadErrorDelegate* error_delegate) {}
 
 void PersistentPrefStoreClient::CommitPendingWrite(
-    base::OnceClosure done_callback) {
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  // Supporting |synchronous_done_callback| semantics would require a sync IPC.
+  // This isn't implemented as such at the moment as this functionality isn't
+  // used in practice (if it ever becomes necessary, this check will fire).
+  DCHECK(!synchronous_done_callback);
+
   DCHECK(pref_store_);
   if (!pending_writes_.empty())
     FlushPendingWrites();
-  pref_store_->CommitPendingWrite(std::move(done_callback));
+  pref_store_->CommitPendingWrite(std::move(reply_callback));
 }
 
 void PersistentPrefStoreClient::SchedulePendingLossyWrites() {
@@ -251,7 +257,7 @@
   if (!pref_store_)
     return;
 
-  CommitPendingWrite(base::OnceClosure());
+  CommitPendingWrite();
 }
 
 void PersistentPrefStoreClient::QueueWrite(
diff --git a/services/preferences/public/cpp/persistent_pref_store_client.h b/services/preferences/public/cpp/persistent_pref_store_client.h
index 51561f4..d863f14 100644
--- a/services/preferences/public/cpp/persistent_pref_store_client.h
+++ b/services/preferences/public/cpp/persistent_pref_store_client.h
@@ -54,7 +54,10 @@
   PrefReadError GetReadError() const override;
   PrefReadError ReadPrefs() override;
   void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
-  void CommitPendingWrite(base::OnceClosure done_callback) override;
+  void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback =
+          base::OnceClosure()) override;
   void SchedulePendingLossyWrites() override;
   void ClearMutableValues() override;
   void OnStoreDeletionFromDisk() override;
diff --git a/services/preferences/tracked/segregated_pref_store.cc b/services/preferences/tracked/segregated_pref_store.cc
index 1fae1507..f62cc4d 100644
--- a/services/preferences/tracked/segregated_pref_store.cc
+++ b/services/preferences/tracked/segregated_pref_store.cc
@@ -161,12 +161,27 @@
   selected_pref_store_->ReadPrefsAsync(NULL);
 }
 
-void SegregatedPrefStore::CommitPendingWrite(base::OnceClosure done_callback) {
-  base::RepeatingClosure done_callback_wrapper =
-      done_callback ? base::BarrierClosure(2, std::move(done_callback))
-                    : base::RepeatingClosure();
-  default_pref_store_->CommitPendingWrite(done_callback_wrapper);
-  selected_pref_store_->CommitPendingWrite(done_callback_wrapper);
+void SegregatedPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  // A BarrierClosure will run its callback wherever the last instance of the
+  // returned wrapper is invoked. As such it is guaranteed to respect the reply
+  // vs synchronous semantics assuming |default_pref_store_| and
+  // |selected_pref_store_| honor it.
+
+  base::RepeatingClosure reply_callback_wrapper =
+      reply_callback ? base::BarrierClosure(2, std::move(reply_callback))
+                     : base::RepeatingClosure();
+
+  base::RepeatingClosure synchronous_callback_wrapper =
+      synchronous_done_callback
+          ? base::BarrierClosure(2, std::move(synchronous_done_callback))
+          : base::RepeatingClosure();
+
+  default_pref_store_->CommitPendingWrite(reply_callback_wrapper,
+                                          synchronous_callback_wrapper);
+  selected_pref_store_->CommitPendingWrite(reply_callback_wrapper,
+                                           synchronous_callback_wrapper);
 }
 
 void SegregatedPrefStore::SchedulePendingLossyWrites() {
diff --git a/services/preferences/tracked/segregated_pref_store.h b/services/preferences/tracked/segregated_pref_store.h
index 1a43e50..18bdaef 100644
--- a/services/preferences/tracked/segregated_pref_store.h
+++ b/services/preferences/tracked/segregated_pref_store.h
@@ -72,7 +72,10 @@
   PrefReadError GetReadError() const override;
   PrefReadError ReadPrefs() override;
   void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
-  void CommitPendingWrite(base::OnceClosure done_callback) override;
+  void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback =
+          base::OnceClosure()) override;
   void SchedulePendingLossyWrites() override;
   void ClearMutableValues() override;
   void OnStoreDeletionFromDisk() override;
diff --git a/services/preferences/tracked/segregated_pref_store_unittest.cc b/services/preferences/tracked/segregated_pref_store_unittest.cc
index 61b76ef..c9db593 100644
--- a/services/preferences/tracked/segregated_pref_store_unittest.cc
+++ b/services/preferences/tracked/segregated_pref_store_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/values.h"
 #include "components/prefs/persistent_pref_store.h"
@@ -56,8 +57,13 @@
 };
 
 enum class CommitPendingWriteMode {
+  // Basic mode.
   WITHOUT_CALLBACK,
+  // With reply callback.
   WITH_CALLBACK,
+  // With synchronous notify callback (synchronous after the write -- shouldn't
+  // require pumping messages to observe).
+  WITH_SYNCHRONOUS_CALLBACK,
 };
 
 class SegregatedPrefStoreTest
@@ -131,13 +137,28 @@
   ASSERT_FALSE(selected_store_->committed());
   ASSERT_FALSE(default_store_->committed());
 
-  if (GetParam() == CommitPendingWriteMode::WITHOUT_CALLBACK) {
-    segregated_store_->CommitPendingWrite(base::OnceClosure());
-    base::RunLoop().RunUntilIdle();
-  } else {
-    base::RunLoop run_loop;
-    segregated_store_->CommitPendingWrite(run_loop.QuitClosure());
-    run_loop.Run();
+  switch (GetParam()) {
+    case CommitPendingWriteMode::WITHOUT_CALLBACK: {
+      segregated_store_->CommitPendingWrite();
+      base::RunLoop().RunUntilIdle();
+      break;
+    }
+
+    case CommitPendingWriteMode::WITH_CALLBACK: {
+      base::RunLoop run_loop;
+      segregated_store_->CommitPendingWrite(run_loop.QuitClosure());
+      run_loop.Run();
+      break;
+    }
+
+    case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK: {
+      base::WaitableEvent written;
+      segregated_store_->CommitPendingWrite(
+          base::OnceClosure(),
+          base::BindOnce(&base::WaitableEvent::Signal, Unretained(&written)));
+      written.Wait();
+      break;
+    }
   }
 
   ASSERT_TRUE(selected_store_->committed());
@@ -327,3 +348,7 @@
     WithCallback,
     SegregatedPrefStoreTest,
     ::testing::Values(CommitPendingWriteMode::WITH_CALLBACK));
+INSTANTIATE_TEST_CASE_P(
+    WithSynchronousCallback,
+    SegregatedPrefStoreTest,
+    ::testing::Values(CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));